From e542977becdbf00db0069adafc7b58c915c636b5 Mon Sep 17 00:00:00 2001 From: Dmitry Shachnev Date: Mon, 7 Sep 2020 11:35:34 +0100 Subject: [PATCH] Import qbs_1.17.0.orig.tar.gz [dgit import orig qbs_1.17.0.orig.tar.gz] --- .clang-tidy | 272 + .dockerignore | 3 + .gitmodules | 3 + .mailmap | 28 + .travis.yml | 230 + CONTRIBUTING.md | 102 + LGPL_EXCEPTION.txt | 22 + LICENSE.GPL3-EXCEPT | 704 ++ LICENSE.LGPLv21 | 504 + LICENSE.LGPLv3 | 173 + README.md | 36 + VERSION | 1 + bin/ibmsvc.xml | 12 + bin/ibqbs.bat | 1 + changelogs/changes-1.10.0.md | 43 + changelogs/changes-1.10.1.md | 11 + changelogs/changes-1.11.0.md | 57 + changelogs/changes-1.11.1.md | 5 + changelogs/changes-1.12.0.md | 41 + changelogs/changes-1.12.1.md | 8 + changelogs/changes-1.12.2.md | 10 + changelogs/changes-1.13.0.md | 37 + changelogs/changes-1.13.1.md | 9 + changelogs/changes-1.14.0.md | 35 + changelogs/changes-1.14.1.md | 4 + changelogs/changes-1.15.0.md | 44 + changelogs/changes-1.16.0.md | 126 + changelogs/changes-1.17.0.md | 98 + changelogs/changes-1.6.0 | 16 + changelogs/changes-1.6.1 | 6 + changelogs/changes-1.7.0 | 23 + changelogs/changes-1.7.1 | 5 + changelogs/changes-1.7.2 | 5 + changelogs/changes-1.8.0 | 33 + changelogs/changes-1.8.1 | 12 + changelogs/changes-1.9.0.md | 58 + changelogs/changes-1.9.1.md | 4 + dist/.gitignore | 1 + doc/appendix/json-api.qdoc | 812 ++ doc/appendix/qbs-porting.qdoc | 504 + doc/classic.css | 295 + doc/codeattributions.qdoc | 201 + doc/config/macros.qdocconf | 65 + doc/config/qbs-project.qdocconf | 37 + doc/config/style/qt5-sidebar.html | 18 + doc/doc.pri | 18 + doc/doc.qbs | 111 + doc/doc_shared.pri | 14 + doc/doc_targets.pri | 86 + doc/external-resources.qdoc | 137 + doc/fix-qmlimports.py | 153 + doc/fixnavi.pl | 182 + doc/howtos.qdoc | 748 ++ doc/images/qbs-build-process.png | Bin 0 -> 5335 bytes doc/images/qbs-dmg.png | Bin 0 -> 41386 bytes doc/images/qbs-settings-gui.png | Bin 0 -> 31364 bytes doc/man/man.pri | 5 + doc/man/man.qbs | 75 + doc/man/qbs.1 | 79 + doc/man/see-also.h2m | 11 + doc/qbs-online.qdocconf | 19 + doc/qbs.qdoc | 2047 ++++ doc/qbs.qdocconf | 2 + doc/reference/cli/builtin/cli-build.qdoc | 122 + doc/reference/cli/builtin/cli-clean.qdoc | 72 + .../cli/builtin/cli-dump-nodes-tree.qdoc | 66 + doc/reference/cli/builtin/cli-generate.qdoc | 83 + doc/reference/cli/builtin/cli-help.qdoc | 64 + doc/reference/cli/builtin/cli-install.qdoc | 81 + .../cli/builtin/cli-list-products.qdoc | 63 + doc/reference/cli/builtin/cli-resolve.qdoc | 73 + doc/reference/cli/builtin/cli-run.qdoc | 95 + doc/reference/cli/builtin/cli-session.qdoc | 54 + doc/reference/cli/builtin/cli-shell.qdoc | 69 + doc/reference/cli/builtin/cli-status.qdoc | 63 + .../cli/builtin/cli-update-timestamps.qdoc | 69 + doc/reference/cli/builtin/cli-version.qdoc | 50 + doc/reference/cli/cli-options.qdocinc | 537 ++ doc/reference/cli/cli-parameters.qdocinc | 104 + doc/reference/cli/cli.qdoc | 43 + doc/reference/cli/tools/cli-config-ui.qdoc | 68 + doc/reference/cli/tools/cli-config.qdoc | 122 + .../cli/tools/cli-create-project.qdoc | 72 + .../cli/tools/cli-setup-android.qdoc | 69 + doc/reference/cli/tools/cli-setup-qt.qdoc | 76 + .../cli/tools/cli-setup-toolchains.qdoc | 86 + doc/reference/commands.qdoc | 243 + .../appleapplicationdiskimage.qdoc | 100 + .../items/convenience/applediskimage.qdoc | 44 + .../items/convenience/application.qdoc | 95 + .../convenience/applicationextension.qdoc | 43 + .../items/convenience/autotestrunner.qdoc | 183 + .../items/convenience/cppapplication.qdoc | 47 + .../items/convenience/dynamiclibrary.qdoc | 49 + .../items/convenience/innosetup.qdoc | 41 + .../items/convenience/installpackage.qdoc | 90 + .../convenience/javaclasscollection.qdoc | 43 + .../items/convenience/javajarfile.qdoc | 50 + doc/reference/items/convenience/library.qdoc | 130 + .../items/convenience/loadablemodule.qdoc | 42 + .../items/convenience/qtapplication.qdoc | 49 + .../items/convenience/qtguiapplication.qdoc | 41 + .../items/convenience/staticlibrary.qdoc | 41 + .../items/convenience/xpcservice.qdoc | 41 + doc/reference/items/language/artifact.qdoc | 86 + doc/reference/items/language/depends.qdoc | 197 + doc/reference/items/language/export.qdoc | 126 + doc/reference/items/language/filetagger.qdoc | 109 + doc/reference/items/language/group.qbs | 44 + doc/reference/items/language/group.qdoc | 206 + doc/reference/items/language/joblimit.qdoc | 107 + doc/reference/items/language/module.qdoc | 357 + .../items/language/moduleprovider.qdoc | 108 + doc/reference/items/language/parameter.qdoc | 52 + doc/reference/items/language/parameters.qdoc | 67 + doc/reference/items/language/probe.qdoc | 85 + doc/reference/items/language/product.qdoc | 286 + doc/reference/items/language/profile.qdoc | 94 + doc/reference/items/language/project.qdoc | 128 + doc/reference/items/language/properties.qdoc | 130 + .../items/language/propertyoptions.qdoc | 64 + doc/reference/items/language/rule.qdoc | 349 + doc/reference/items/language/scanner.qdoc | 108 + doc/reference/items/language/subproject.qdoc | 127 + doc/reference/items/probe/binary-probe.qdoc | 85 + .../items/probe/conanfile-probe.qdoc | 249 + .../items/probe/framework-probe.qdoc | 60 + doc/reference/items/probe/iar-probe.qdoc | 93 + doc/reference/items/probe/include-probe.qdoc | 55 + doc/reference/items/probe/keil-probe.qdoc | 93 + doc/reference/items/probe/library-probe.qdoc | 62 + doc/reference/items/probe/path-probe.qdoc | 238 + .../items/probe/pkgconfig-probe.qdoc | 237 + doc/reference/items/probe/sdcc-probe.qdoc | 93 + .../jsextensions/jsextension-binaryfile.qdoc | 114 + .../jsextensions/jsextension-environment.qdoc | 69 + .../jsextensions/jsextension-file.qdoc | 94 + .../jsextensions/jsextension-fileinfo.qdoc | 153 + .../jsextensions/jsextension-process.qdoc | 182 + .../jsextension-propertylist.qdoc | 131 + .../jsextension-temporarydir.qdoc | 68 + .../jsextensions/jsextension-textfile.qdoc | 113 + .../jsextensions/jsextension-utilities.qdoc | 82 + .../jsextensions/jsextension-xml.qdoc | 342 + .../jsextensions/jsextensions-general.qdoc | 157 + doc/reference/modules/android-ndk-module.qdoc | 110 + doc/reference/modules/android-sdk-module.qdoc | 252 + doc/reference/modules/archiver-module.qdoc | 178 + doc/reference/modules/autotest-module.qdoc | 80 + doc/reference/modules/bundle-module.qdoc | 476 + .../modules/capnprotocpp-module.qdoc | 116 + doc/reference/modules/cpp-module.qdoc | 1872 ++++ doc/reference/modules/cpufeatures-module.qdoc | 220 + doc/reference/modules/dmg-module.qdoc | 422 + .../modules/exporter-pkgconfig-module.qdoc | 210 + .../modules/exporter-qbs-module.qdoc | 130 + doc/reference/modules/freedesktop-module.qdoc | 141 + doc/reference/modules/ib-module.qdoc | 282 + doc/reference/modules/ico-module.qdoc | 124 + doc/reference/modules/innosetup-module.qdoc | 188 + doc/reference/modules/java-module.qdoc | 245 + doc/reference/modules/lexyacc-module.qdoc | 156 + doc/reference/modules/nodejs-module.qdoc | 48 + doc/reference/modules/nsis-module.qdoc | 228 + doc/reference/modules/pkgconfig-module.qdoc | 92 + doc/reference/modules/protobufcpp-module.qdoc | 209 + .../modules/protobufobjc-module.qdoc | 113 + doc/reference/modules/qbs-module.qdoc | 903 ++ doc/reference/modules/qnx-module.qdoc | 85 + .../modules/qt-android_support-module.qdoc | 95 + doc/reference/modules/qt-core-module.qdoc | 471 + doc/reference/modules/qt-dbus-module.qdoc | 88 + .../modules/qt-declarative-module.qdoc | 62 + doc/reference/modules/qt-gui-module.qdoc | 63 + doc/reference/modules/qt-modules.qdoc | 209 + .../modules/qt-plugin_support-module.qdoc | 85 + doc/reference/modules/qt-qml-module.qdoc | 189 + doc/reference/modules/qt-quick-module.qdoc | 102 + doc/reference/modules/qt-scxml-module.qdoc | 95 + .../modules/texttemplate-module.qdoc | 118 + doc/reference/modules/typescript-module.qdoc | 204 + doc/reference/modules/vcs-module.qdoc | 108 + doc/reference/modules/wix-module.qdoc | 343 + doc/reference/modules/xcode-module.qdoc | 166 + doc/reference/reference.qdoc | 116 + doc/targets/qbs-target-android.qdoc | 77 + doc/targets/qbs-target-apple-common.qdocinc | 108 + doc/targets/qbs-target-integrity.qdoc | 41 + doc/targets/qbs-target-ios.qdoc | 41 + doc/targets/qbs-target-linux.qdoc | 37 + doc/targets/qbs-target-macos.qdoc | 85 + doc/targets/qbs-target-platforms.qdoc | 51 + doc/targets/qbs-target-qnx.qdoc | 69 + doc/targets/qbs-target-tvos.qdoc | 41 + doc/targets/qbs-target-vxworks.qdoc | 41 + doc/targets/qbs-target-watchos.qdoc | 41 + doc/targets/qbs-target-windows.qdoc | 79 + docker-compose.yml | 61 + docker/bionic/Dockerfile | 207 + docker/bionic/entrypoint.sh | 96 + docker/bionic/test-android.Dockerfile | 111 + docker/docker.qbs | 8 + docker/windowsservercore/Dockerfile | 51 + examples/app-and-lib/app-and-lib.qbs | 59 + examples/app-and-lib/app/app.qbs | 59 + examples/app-and-lib/app/main.cpp | 58 + examples/app-and-lib/lib/lib.cpp | 61 + examples/app-and-lib/lib/lib.h | 56 + examples/app-and-lib/lib/lib.qbs | 67 + .../at90can128olimex/at90can128olimex.qbs | 58 + .../at90can128olimex/redblink/README.md | 9 + .../at90can128olimex/redblink/gpio.c | 76 + .../at90can128olimex/redblink/gpio.h | 65 + .../at90can128olimex/redblink/main.c | 69 + .../at90can128olimex/redblink/redblink.qbs | 130 + examples/baremetal/baremetal.qbs | 65 + .../cc2540usbdongle/cc2540usbdongle.qbs | 58 + .../cc2540usbdongle/greenblink/README.md | 9 + .../cc2540usbdongle/greenblink/gpio.c | 65 + .../cc2540usbdongle/greenblink/gpio.h | 65 + .../cc2540usbdongle/greenblink/greenblink.qbs | 131 + .../cc2540usbdongle/greenblink/main.c | 69 + .../cc2540usbdongle/greenblink/system.h | 85 + examples/baremetal/cy7c68013a/cy7c68013a.qbs | 58 + .../cy7c68013a/nes-gamepads/README.md | 42 + .../baremetal/cy7c68013a/nes-gamepads/core.c | 86 + .../baremetal/cy7c68013a/nes-gamepads/core.h | 298 + .../baremetal/cy7c68013a/nes-gamepads/defs.h | 173 + .../baremetal/cy7c68013a/nes-gamepads/gpio.c | 161 + .../baremetal/cy7c68013a/nes-gamepads/gpio.h | 65 + .../baremetal/cy7c68013a/nes-gamepads/hid.c | 85 + .../baremetal/cy7c68013a/nes-gamepads/hid.h | 92 + .../cy7c68013a/nes-gamepads/hiddesc.c | 345 + .../cy7c68013a/nes-gamepads/hidep0.c | 377 + .../cy7c68013a/nes-gamepads/hidep1.c | 89 + .../baremetal/cy7c68013a/nes-gamepads/irqs.h | 77 + .../baremetal/cy7c68013a/nes-gamepads/main.c | 85 + .../cy7c68013a/nes-gamepads/nes-gamepads.qbs | 138 + .../baremetal/cy7c68013a/nes-gamepads/regs.h | 779 ++ .../baremetal/cy7c68013a/nes-gamepads/usb.c | 118 + .../baremetal/cy7c68013a/nes-gamepads/usb.h | 217 + .../baremetal/msp430f5529/msp430f5529.qbs | 59 + .../msp430f5529/nes-gamepads/README.md | 36 + .../msp430f5529/nes-gamepads/gamepads.ld | 57 + .../baremetal/msp430f5529/nes-gamepads/gpio.c | 167 + .../baremetal/msp430f5529/nes-gamepads/gpio.h | 110 + .../baremetal/msp430f5529/nes-gamepads/hid.h | 116 + .../msp430f5529/nes-gamepads/hiddesc.c | 303 + .../msp430f5529/nes-gamepads/hidep0.c | 554 ++ .../msp430f5529/nes-gamepads/hidep1.c | 214 + .../msp430f5529/nes-gamepads/hwdefs.h | 81 + .../baremetal/msp430f5529/nes-gamepads/main.c | 92 + .../msp430f5529/nes-gamepads/nes-gamepads.qbs | 148 + .../baremetal/msp430f5529/nes-gamepads/pmm.c | 237 + .../baremetal/msp430f5529/nes-gamepads/pmm.h | 73 + .../baremetal/msp430f5529/nes-gamepads/ucs.c | 264 + .../baremetal/msp430f5529/nes-gamepads/ucs.h | 106 + .../baremetal/msp430f5529/nes-gamepads/usb.c | 317 + .../baremetal/msp430f5529/nes-gamepads/usb.h | 148 + .../msp430f5529/nes-gamepads/wdt_a.c | 60 + .../msp430f5529/nes-gamepads/wdt_a.h | 64 + .../baremetal/msp430f5529/redblink/README.md | 9 + .../baremetal/msp430f5529/redblink/gpio.c | 76 + .../baremetal/msp430f5529/redblink/gpio.h | 65 + .../baremetal/msp430f5529/redblink/main.c | 72 + .../msp430f5529/redblink/redblink.qbs | 129 + .../baremetal/msp430f5529/redblink/system.c | 65 + .../baremetal/msp430f5529/redblink/system.h | 64 + .../baremetal/pca10040/greenblink/README.md | 9 + .../pca10040/greenblink/gcc/flash.ld | 151 + .../pca10040/greenblink/gcc/startup.s | 253 + examples/baremetal/pca10040/greenblink/gpio.c | 96 + examples/baremetal/pca10040/greenblink/gpio.h | 65 + .../pca10040/greenblink/greenblink.qbs | 140 + .../pca10040/greenblink/keil/flash.sct | 65 + .../pca10040/greenblink/keil/startup.s | 342 + examples/baremetal/pca10040/greenblink/main.c | 69 + .../baremetal/pca10040/greenblink/system.h | 84 + examples/baremetal/pca10040/pca10040.qbs | 58 + .../baremetal/stm32f103/greenblink/README.md | 9 + .../stm32f103/greenblink/gcc/flash.ld | 172 + .../stm32f103/greenblink/gcc/startup.s | 177 + .../baremetal/stm32f103/greenblink/gpio.c | 83 + .../baremetal/stm32f103/greenblink/gpio.h | 65 + .../stm32f103/greenblink/greenblink.qbs | 138 + .../stm32f103/greenblink/keil/flash.sct | 65 + .../stm32f103/greenblink/keil/startup.s | 145 + .../baremetal/stm32f103/greenblink/main.c | 69 + .../baremetal/stm32f103/greenblink/system.h | 102 + examples/baremetal/stm32f103/stm32f103.qbs | 58 + .../stm32f4discovery/blueblink/README.md | 10 + .../stm32f4discovery/blueblink/blueblink.qbs | 169 + .../stm32f4discovery/blueblink/gcc/flash.ld | 185 + .../stm32f4discovery/blueblink/gcc/startup.s | 211 + .../stm32f4discovery/blueblink/gpio.c | 79 + .../stm32f4discovery/blueblink/gpio.h | 65 + .../stm32f4discovery/blueblink/iar/flash.icf | 77 + .../stm32f4discovery/blueblink/iar/startup.s | 171 + .../stm32f4discovery/blueblink/keil/flash.sct | 64 + .../stm32f4discovery/blueblink/keil/startup.s | 184 + .../stm32f4discovery/blueblink/main.c | 69 + .../stm32f4discovery/blueblink/system.h | 129 + .../stm32f4discovery/stm32f4discovery.qbs | 58 + .../baremetal/stm8s103f3/redblink/README.md | 8 + examples/baremetal/stm8s103f3/redblink/gpio.c | 68 + examples/baremetal/stm8s103f3/redblink/gpio.h | 65 + examples/baremetal/stm8s103f3/redblink/main.c | 69 + .../stm8s103f3/redblink/redblink.qbs | 111 + .../baremetal/stm8s103f3/redblink/system.h | 84 + examples/baremetal/stm8s103f3/stm8s103f3.qbs | 58 + .../addressbook_cpp/addressbook.capnp | 55 + .../capnproto/addressbook_cpp/addressbook.cpp | 288 + .../addressbook_cpp/addressbook_cpp.qbs | 11 + .../calculator_cpp/calculator-client.cpp | 367 + .../calculator_cpp/calculator-server.cpp | 215 + .../capnproto/calculator_cpp/calculator.capnp | 118 + .../calculator_cpp/calculator_cpp.qbs | 26 + .../project.pbxproj | 309 + .../CocoaApplication/AppDelegate.h | 58 + .../CocoaApplication/AppDelegate.m | 47 + .../CocoaApplication-Info.plist | 34 + .../CocoaApplication-Prefix.pch | 33 + .../AppIcon.appiconset/Contents.json | 68 + .../AppIcon.appiconset/icon_128x128.png | Bin 0 -> 5256 bytes .../AppIcon.appiconset/icon_128x128@2x.png | Bin 0 -> 11304 bytes .../AppIcon.appiconset/icon_16x16.png | Bin 0 -> 487 bytes .../AppIcon.appiconset/icon_16x16@2x.png | Bin 0 -> 1374 bytes .../AppIcon.appiconset/icon_256x256.png | Bin 0 -> 11304 bytes .../AppIcon.appiconset/icon_256x256@2x.png | Bin 0 -> 26984 bytes .../AppIcon.appiconset/icon_32x32.png | Bin 0 -> 1374 bytes .../AppIcon.appiconset/icon_32x32@2x.png | Bin 0 -> 2423 bytes .../AppIcon.appiconset/icon_512x512.png | Bin 0 -> 26984 bytes .../AppIcon.appiconset/icon_512x512@2x.png | Bin 0 -> 63576 bytes .../CocoaApplication/background.png | Bin 0 -> 86627 bytes .../CocoaApplication/background@2x.png | Bin 0 -> 217596 bytes .../dmg.iconset/icon_128x128.png | Bin 0 -> 5256 bytes .../dmg.iconset/icon_128x128@2x.png | Bin 0 -> 11304 bytes .../dmg.iconset/icon_16x16.png | Bin 0 -> 487 bytes .../dmg.iconset/icon_16x16@2x.png | Bin 0 -> 1374 bytes .../dmg.iconset/icon_256x256.png | Bin 0 -> 11304 bytes .../dmg.iconset/icon_256x256@2x.png | Bin 0 -> 26984 bytes .../dmg.iconset/icon_32x32.png | Bin 0 -> 1374 bytes .../dmg.iconset/icon_32x32@2x.png | Bin 0 -> 2423 bytes .../dmg.iconset/icon_512x512.png | Bin 0 -> 26984 bytes .../dmg.iconset/icon_512x512@2x.png | Bin 0 -> 63576 bytes .../CocoaApplication/en.lproj/Credits.rtf | 29 + .../en.lproj/InfoPlist.strings | 1 + .../CocoaApplication/en.lproj/LICENSE | 27 + .../CocoaApplication/en.lproj/MainMenu.xib | 4666 +++++++++ .../CocoaApplication/en_US.lproj | 1 + .../cocoa-application/CocoaApplication/main.m | 36 + examples/cocoa-application/app.qbs | 103 + .../cocoa-application/cocoa-application.qbs | 66 + examples/cocoa-application/dmg.qbs | 85 + .../project.pbxproj | 348 + .../CocoaTouchApplication/AppDelegate.h | 67 + .../CocoaTouchApplication/AppDelegate.m | 105 + .../CocoaTouchApplication-Info.plist | 55 + .../CocoaTouchApplication-Prefix.pch | 40 + .../CocoaTouchApplication/Default-568h@2x.png | Bin 0 -> 18594 bytes .../CocoaTouchApplication/Default.png | Bin 0 -> 6540 bytes .../CocoaTouchApplication/Default@2x.png | Bin 0 -> 16107 bytes .../DetailViewController.h | 65 + .../DetailViewController.m | 116 + .../MasterViewController.h | 64 + .../MasterViewController.m | 162 + .../en.lproj/DetailViewController_iPad.xib | 223 + .../en.lproj/DetailViewController_iPhone.xib | 253 + .../en.lproj/InfoPlist.strings | 1 + .../en.lproj/MasterViewController_iPad.xib | 152 + .../en.lproj/MasterViewController_iPhone.xib | 147 + .../CocoaTouchApplication/main.m | 40 + .../cocoa-touch-application.qbs | 104 + examples/code-generator/code-generator.qbs | 83 + examples/code-generator/hwgen.cpp | 65 + examples/collidingmice/collidingmice.qbs | 70 + examples/collidingmice/images/cheese.jpg | Bin 0 -> 3029 bytes examples/collidingmice/main.cpp | 104 + examples/collidingmice/mice.qrc | 5 + examples/collidingmice/mouse.cpp | 209 + examples/collidingmice/mouse.h | 80 + examples/compiled-qml/MainForm.ui.qml | 68 + examples/compiled-qml/cheese.jpg | Bin 0 -> 3029 bytes examples/compiled-qml/compiled-qml.qbs | 66 + examples/compiled-qml/main.cpp | 62 + examples/compiled-qml/main.qml | 62 + examples/compiled-qml/qml.qrc | 7 + examples/examples.qbs | 73 + examples/grpc/client.cpp | 115 + examples/grpc/grpc.qbs | 75 + examples/grpc/ping-pong-grpc.proto | 15 + examples/grpc/server.cpp | 98 + .../helloworld-complex/helloworld-complex.qbs | 89 + examples/helloworld-complex/src/foo.cpp | 59 + examples/helloworld-complex/src/foo.h | 57 + examples/helloworld-complex/src/main.cpp | 79 + .../helloworld-complex/src/specialfeature.cpp | 58 + .../helloworld-complex/src/specialfeature.h | 56 + .../helloworld-minimal/helloworld-minimal.qbs | 56 + examples/helloworld-minimal/main.cpp | 56 + examples/helloworld-qt/helloworld-qt.qbs | 56 + examples/helloworld-qt/main.cpp | 61 + examples/install-bundle/MainMenu.xib | 680 ++ examples/install-bundle/Storyboard.storyboard | 52 + .../other.imageset/Contents.json | 22 + .../other.imageset/icon_16x16.png | Bin 0 -> 649 bytes .../other.imageset/icon_16x16@2x.png | Bin 0 -> 665 bytes .../other.imageset/Contents.json | 22 + .../other.imageset/icon_16x16.png | Bin 0 -> 649 bytes .../other.imageset/icon_16x16@2x.png | Bin 0 -> 665 bytes examples/install-bundle/coreutils.cpp | 56 + examples/install-bundle/coreutils.h | 54 + examples/install-bundle/install-bundle.qbs | 52 + examples/install-bundle/main.cpp | 60 + .../white.iconset/icon_16x16.png | Bin 0 -> 649 bytes .../white.iconset/icon_16x16@2x.png | Bin 0 -> 665 bytes examples/protobuf/addressbook_cpp/README.md | 13 + .../addressbook_cpp/addressbook_cpp.qbs | 18 + examples/protobuf/addressbook_cpp/main.cpp | 179 + examples/protobuf/addressbook_objc/README.md | 5 + .../addressbook_objc/addressbook_objc.qbs | 14 + examples/protobuf/addressbook_objc/main.m | 191 + examples/protobuf/shared/addressbook.proto | 51 + examples/rpaths/main.cpp | 68 + examples/rpaths/objecta.cpp | 71 + examples/rpaths/objecta.h | 70 + examples/rpaths/objectb.cpp | 64 + examples/rpaths/objectb.h | 71 + examples/rpaths/rpaths.qbs | 61 + examples/rule/lorem_ipsum.txt | 4 + examples/rule/rule.qbs | 36 + qbs-resources/imports/QbsApp.qbs | 32 + qbs-resources/imports/QbsAutotest.qbs | 42 + qbs-resources/imports/QbsLibrary.qbs | 53 + qbs-resources/imports/QbsLibraryBase.qbs | 24 + qbs-resources/imports/QbsProduct.qbs | 25 + qbs-resources/imports/QbsStaticLibrary.qbs | 9 + .../modules/qbsbuildconfig/qbsbuildconfig.qbs | 79 + .../modules/qbsversion/qbsversion.qbs | 24 + qbs.pro | 75 + qbs.qbs | 65 + qbs_version.pri | 4 + scripts/address-sanitizer-suppressions.txt | 3 + scripts/build-qbs-with-qbs.sh | 136 + scripts/build-qbs-with-qmake.sh | 84 + scripts/install-qt.sh | 319 + scripts/make-release-archives.bat | 77 + scripts/make-release-archives.sh | 50 + scripts/run-analyzer.sh | 117 + scripts/scripts.qbs | 9 + scripts/test-qt-for-android.sh | 82 + scripts/thread-sanitizer-suppressions.txt | 1 + scripts/update-dmgbuild.sh | 49 + scripts/update-xcspecs.sh | 53 + .../imports/qbs/BundleTools/bundle-tools.js | 78 + .../imports/qbs/DarwinTools/darwin-tools.js | 271 + share/qbs/imports/qbs/ModUtils/utils.js | 636 ++ share/qbs/imports/qbs/PathTools/path-tools.js | 232 + .../imports/qbs/Probes/AndroidNdkProbe.qbs | 135 + .../imports/qbs/Probes/AndroidSdkProbe.qbs | 74 + share/qbs/imports/qbs/Probes/BinaryProbe.qbs | 35 + .../qbs/imports/qbs/Probes/ClBinaryProbe.qbs | 71 + .../imports/qbs/Probes/ClangClBinaryProbe.qbs | 73 + share/qbs/imports/qbs/Probes/ClangClProbe.qbs | 90 + .../qbs/imports/qbs/Probes/ConanfileProbe.qbs | 130 + .../qbs/imports/qbs/Probes/FrameworkProbe.qbs | 39 + .../qbs/imports/qbs/Probes/GccBinaryProbe.qbs | 78 + share/qbs/imports/qbs/Probes/GccProbe.qbs | 109 + .../imports/qbs/Probes/GccVersionProbe.qbs | 67 + share/qbs/imports/qbs/Probes/IarProbe.qbs | 89 + .../qbs/Probes/IcoUtilsVersionProbe.qbs | 44 + share/qbs/imports/qbs/Probes/IncludeProbe.qbs | 41 + .../qbs/imports/qbs/Probes/InnoSetupProbe.qbs | 53 + share/qbs/imports/qbs/Probes/JdkProbe.qbs | 53 + .../imports/qbs/Probes/JdkVersionProbe.qbs | 44 + share/qbs/imports/qbs/Probes/KeilProbe.qbs | 87 + share/qbs/imports/qbs/Probes/LibraryProbe.qbs | 85 + share/qbs/imports/qbs/Probes/MsvcProbe.qbs | 96 + share/qbs/imports/qbs/Probes/NodeJsProbe.qbs | 48 + share/qbs/imports/qbs/Probes/NpmProbe.qbs | 86 + share/qbs/imports/qbs/Probes/PathProbe.qbs | 73 + .../qbs/imports/qbs/Probes/PkgConfigProbe.qbs | 136 + share/qbs/imports/qbs/Probes/SdccProbe.qbs | 73 + .../imports/qbs/Probes/TypeScriptProbe.qbs | 93 + share/qbs/imports/qbs/Probes/WiXProbe.qbs | 75 + share/qbs/imports/qbs/Probes/XcodeProbe.qbs | 102 + share/qbs/imports/qbs/Probes/path-probe.js | 146 + share/qbs/imports/qbs/UnixUtils/unix-utils.js | 59 + .../imports/qbs/WindowsUtils/windows-utils.js | 105 + share/qbs/imports/qbs/base/AndroidApk.qbs | 38 + .../qbs/base/AppleApplicationDiskImage.qbs | 109 + share/qbs/imports/qbs/base/AppleDiskImage.qbs | 34 + share/qbs/imports/qbs/base/Application.qbs | 77 + .../imports/qbs/base/ApplicationExtension.qbs | 67 + share/qbs/imports/qbs/base/AutotestRunner.qbs | 107 + share/qbs/imports/qbs/base/CppApplication.qbs | 35 + share/qbs/imports/qbs/base/DynamicLibrary.qbs | 33 + share/qbs/imports/qbs/base/InnoSetup.qbs | 34 + share/qbs/imports/qbs/base/InstallPackage.qbs | 68 + .../imports/qbs/base/JavaClassCollection.qbs | 34 + share/qbs/imports/qbs/base/JavaJarFile.qbs | 35 + share/qbs/imports/qbs/base/Library.qbs | 90 + share/qbs/imports/qbs/base/LoadableModule.qbs | 33 + share/qbs/imports/qbs/base/NSISSetup.qbs | 34 + share/qbs/imports/qbs/base/NativeBinary.qbs | 69 + share/qbs/imports/qbs/base/NetModule.qbs | 4 + .../imports/qbs/base/NodeJSApplication.qbs | 33 + share/qbs/imports/qbs/base/QtApplication.qbs | 37 + .../qbs/imports/qbs/base/QtGuiApplication.qbs | 33 + share/qbs/imports/qbs/base/StaticLibrary.qbs | 33 + .../qbs/base/WindowsInstallerPackage.qbs | 34 + .../imports/qbs/base/WindowsSetupPackage.qbs | 34 + share/qbs/imports/qbs/base/XPCService.qbs | 47 + share/qbs/module-providers/Qt/provider.qbs | 6 + share/qbs/module-providers/Qt/setup-qt.js | 1598 ++++ .../Qt/templates/QtModule.qbs | 86 + .../Qt/templates/QtPlugin.qbs | 51 + .../Qt/templates/android_support.qbs | 400 + .../module-providers/Qt/templates/core.qbs | 538 ++ .../qbs/module-providers/Qt/templates/dbus.js | 61 + .../module-providers/Qt/templates/dbus.qbs | 74 + .../qbs/module-providers/Qt/templates/gui.qbs | 71 + .../qbs/module-providers/Qt/templates/moc.js | 114 + .../module-providers/Qt/templates/module.qbs | 30 + .../module-providers/Qt/templates/plugin.qbs | 27 + .../Qt/templates/plugin_support.qbs | 77 + .../qbs/module-providers/Qt/templates/qdoc.js | 84 + .../qbs/module-providers/Qt/templates/qml.js | 69 + .../qbs/module-providers/Qt/templates/qml.qbs | 203 + .../Qt/templates/qmlcache.qbs | 85 + .../module-providers/Qt/templates/quick.js | 84 + .../module-providers/Qt/templates/quick.qbs | 211 + .../module-providers/Qt/templates/scxml.qbs | 80 + .../module-providers/__fallback/fallback.qbs | 77 + .../module-providers/__fallback/provider.qbs | 53 + share/qbs/modules/Android/android-utils.js | 50 + share/qbs/modules/Android/ndk/ndk.qbs | 127 + share/qbs/modules/Android/ndk/utils.js | 105 + share/qbs/modules/Android/sdk/sdk.qbs | 549 ++ share/qbs/modules/Android/sdk/utils.js | 432 + .../modules/Exporter/pkgconfig/pkgconfig.js | 228 + .../modules/Exporter/pkgconfig/pkgconfig.qbs | 87 + share/qbs/modules/Exporter/qbs/qbsexporter.js | 272 + .../qbs/modules/Exporter/qbs/qbsexporter.qbs | 79 + share/qbs/modules/archiver/archiver.qbs | 240 + share/qbs/modules/autotest/autotest.qbs | 6 + share/qbs/modules/bundle/BundleModule.qbs | 806 ++ .../bundle/MacOSX-Package-Types.xcspec | 462 + .../bundle/MacOSX-Product-Types.xcspec | 590 ++ share/qbs/modules/bundle/bundle.js | 300 + share/qbs/modules/capnproto/capnproto.js | 97 + share/qbs/modules/capnproto/capnprotobase.qbs | 65 + .../modules/capnproto/cpp/capnprotocpp.qbs | 64 + share/qbs/modules/cli/CLIModule.qbs | 195 + share/qbs/modules/cli/cli.js | 181 + share/qbs/modules/cli/mono.qbs | 26 + share/qbs/modules/cli/windows-dotnet.qbs | 35 + share/qbs/modules/cpp/CppModule.qbs | 547 ++ share/qbs/modules/cpp/DarwinGCC.qbs | 287 + share/qbs/modules/cpp/GenericGCC.qbs | 732 ++ share/qbs/modules/cpp/LinuxGCC.qbs | 72 + share/qbs/modules/cpp/MingwBaseModule.qbs | 113 + share/qbs/modules/cpp/UnixGCC.qbs | 48 + share/qbs/modules/cpp/android-gcc.qbs | 197 + share/qbs/modules/cpp/cpp.js | 46 + share/qbs/modules/cpp/darwin.js | 193 + share/qbs/modules/cpp/freebsd-gcc.qbs | 42 + share/qbs/modules/cpp/freebsd.js | 10 + share/qbs/modules/cpp/gcc.js | 1579 +++ share/qbs/modules/cpp/iar.js | 851 ++ share/qbs/modules/cpp/iar.qbs | 143 + share/qbs/modules/cpp/ios-gcc.qbs | 84 + share/qbs/modules/cpp/keil.js | 1215 +++ share/qbs/modules/cpp/keil.qbs | 144 + share/qbs/modules/cpp/macos-gcc.qbs | 48 + share/qbs/modules/cpp/msvc.js | 703 ++ share/qbs/modules/cpp/qnx-qcc.qbs | 96 + share/qbs/modules/cpp/sdcc.js | 631 ++ share/qbs/modules/cpp/sdcc.qbs | 142 + share/qbs/modules/cpp/setuprunenv.js | 146 + share/qbs/modules/cpp/tvos-gcc.qbs | 45 + share/qbs/modules/cpp/watchos-gcc.qbs | 46 + share/qbs/modules/cpp/windows-clang-cl.qbs | 97 + share/qbs/modules/cpp/windows-clang-mingw.qbs | 98 + share/qbs/modules/cpp/windows-mingw.qbs | 107 + share/qbs/modules/cpp/windows-msvc-base.qbs | 353 + share/qbs/modules/cpp/windows-msvc.qbs | 67 + share/qbs/modules/cpufeatures/cpufeatures.qbs | 23 + share/qbs/modules/dmg/DMGModule.qbs | 152 + share/qbs/modules/dmg/dmg.js | 212 + share/qbs/modules/freedesktop/FreeDesktop.qbs | 124 + share/qbs/modules/freedesktop/freedesktop.js | 69 + share/qbs/modules/ib/IBModule.qbs | 238 + share/qbs/modules/ib/ib.js | 367 + share/qbs/modules/ico/IcoModule.qbs | 101 + share/qbs/modules/ico/ico.js | 98 + .../qbs/modules/innosetup/InnoSetupModule.qbs | 142 + share/qbs/modules/java/JavaModule.qbs | 356 + .../qbs/modules/java/io/qt/qbs/Artifact.java | 76 + .../io/qt/qbs/ArtifactListJsonWriter.java | 153 + .../java/io/qt/qbs/ArtifactListWriter.java | 39 + .../qt/qbs/tools/JavaCompilerScannerTool.java | 51 + .../qbs/tools/utils/JavaCompilerOptions.java | 102 + .../qbs/tools/utils/JavaCompilerScanner.java | 107 + .../io/qt/qbs/tools/utils/NullFileObject.java | 147 + share/qbs/modules/java/utils.js | 361 + share/qbs/modules/lex_yacc/lexyacc.js | 84 + share/qbs/modules/lex_yacc/lexyacc.qbs | 102 + share/qbs/modules/nodejs/NodeJS.qbs | 155 + share/qbs/modules/nodejs/nodejs.js | 44 + share/qbs/modules/nsis/NSISModule.qbs | 242 + share/qbs/modules/pkgconfig/pkgconfig.qbs | 67 + .../qbs/modules/protobuf/cpp/protobufcpp.qbs | 120 + .../modules/protobuf/objc/protobufobjc.qbs | 64 + share/qbs/modules/protobuf/protobuf.js | 142 + share/qbs/modules/protobuf/protobufbase.qbs | 31 + share/qbs/modules/qbs/common.qbs | 213 + share/qbs/modules/qnx/qnx.qbs | 127 + .../qbs/modules/texttemplate/texttemplate.qbs | 64 + .../modules/typescript/TypeScriptModule.qbs | 294 + .../typescript/qbs-tsc-scan/.gitignore | 5 + .../typescript/qbs-tsc-scan/qbs-tsc-scan.ts | 68 + share/qbs/modules/typescript/typescript.js | 273 + share/qbs/modules/vcs/vcs-module.qbs | 154 + share/qbs/modules/wix/WiXModule.qbs | 452 + share/qbs/modules/xcode/xcode.js | 220 + share/qbs/modules/xcode/xcode.qbs | 444 + share/share.qbs | 146 + src/3rdparty/python/.gitignore | 3 + src/3rdparty/python/bin/dmgbuild | 41 + .../python2.7/site-packages/biplist/LICENSE | 25 + .../site-packages/biplist/__init__.py | 977 ++ .../site-packages/biplist/qt_attribution.json | 13 + .../python2.7/site-packages/dmgbuild/LICENSE | 19 + .../site-packages/dmgbuild/__init__.py | 3 + .../python2.7/site-packages/dmgbuild/badge.py | 143 + .../site-packages/dmgbuild/colors.py | 494 + .../python2.7/site-packages/dmgbuild/core.py | 597 ++ .../site-packages/dmgbuild/licensing.py | 461 + .../dmgbuild/qt_attribution.json | 13 + .../site-packages/dmgbuild/resources.py | 355 + .../python2.7/site-packages/ds_store/LICENSE | 19 + .../site-packages/ds_store/__init__.py | 3 + .../python2.7/site-packages/ds_store/buddy.py | 478 + .../ds_store/qt_attribution.json | 13 + .../python2.7/site-packages/ds_store/store.py | 1251 +++ .../python2.7/site-packages/mac_alias/LICENSE | 19 + .../site-packages/mac_alias/__init__.py | 27 + .../site-packages/mac_alias/alias.py | 607 ++ .../site-packages/mac_alias/bookmark.py | 665 ++ .../python2.7/site-packages/mac_alias/osx.py | 827 ++ .../mac_alias/qt_attribution.json | 13 + .../site-packages/mac_alias/utils.py | 18 + src/app/app.pri | 23 + src/app/app.pro | 10 + src/app/apps.qbs | 13 + src/app/config-ui/Info.plist | 14 + src/app/config-ui/commandlineparser.cpp | 111 + src/app/config-ui/commandlineparser.h | 69 + src/app/config-ui/config-ui.pro | 26 + src/app/config-ui/config-ui.qbs | 34 + src/app/config-ui/fgapp.mm | 47 + src/app/config-ui/main.cpp | 69 + src/app/config-ui/mainwindow.cpp | 252 + src/app/config-ui/mainwindow.h | 77 + src/app/config-ui/mainwindow.ui | 37 + src/app/config/config.pro | 13 + src/app/config/config.qbs | 14 + src/app/config/configcommand.h | 60 + src/app/config/configcommandexecutor.cpp | 161 + src/app/config/configcommandexecutor.h | 67 + src/app/config/configcommandlineparser.cpp | 158 + src/app/config/configcommandlineparser.h | 81 + src/app/config/configmain.cpp | 76 + .../create-project-main.cpp | 93 + src/app/qbs-create-project/createproject.cpp | 233 + src/app/qbs-create-project/createproject.h | 90 + .../qbs-create-project/qbs-create-project.pro | 9 + .../qbs-create-project/qbs-create-project.qbs | 10 + src/app/qbs-setup-android/android-setup.cpp | 256 + src/app/qbs-setup-android/android-setup.h | 55 + .../qbs-setup-android/commandlineparser.cpp | 147 + src/app/qbs-setup-android/commandlineparser.h | 80 + src/app/qbs-setup-android/main.cpp | 70 + .../qbs-setup-android.exe.manifest | 13 + .../qbs-setup-android/qbs-setup-android.pro | 12 + .../qbs-setup-android/qbs-setup-android.qbs | 22 + .../qbs-setup-android/qbs-setup-android.rc | 4 + src/app/qbs-setup-qt/commandlineparser.cpp | 137 + src/app/qbs-setup-qt/commandlineparser.h | 76 + src/app/qbs-setup-qt/main.cpp | 109 + .../qbs-setup-qt/qbs-setup-qt.exe.manifest | 13 + src/app/qbs-setup-qt/qbs-setup-qt.pro | 16 + src/app/qbs-setup-qt/qbs-setup-qt.qbs | 23 + src/app/qbs-setup-qt/qbs-setup-qt.rc | 4 + src/app/qbs-setup-qt/setupqt.cpp | 391 + src/app/qbs-setup-qt/setupqt.h | 81 + src/app/qbs-setup-toolchains/clangclprobe.cpp | 134 + src/app/qbs-setup-toolchains/clangclprobe.h | 61 + .../commandlineparser.cpp | 145 + .../qbs-setup-toolchains/commandlineparser.h | 79 + src/app/qbs-setup-toolchains/gccprobe.cpp | 596 ++ src/app/qbs-setup-toolchains/gccprobe.h | 62 + src/app/qbs-setup-toolchains/iarewprobe.cpp | 324 + src/app/qbs-setup-toolchains/iarewprobe.h | 61 + src/app/qbs-setup-toolchains/keilprobe.cpp | 481 + src/app/qbs-setup-toolchains/keilprobe.h | 61 + src/app/qbs-setup-toolchains/main.cpp | 82 + src/app/qbs-setup-toolchains/msvcprobe.cpp | 207 + src/app/qbs-setup-toolchains/msvcprobe.h | 61 + src/app/qbs-setup-toolchains/probe.cpp | 303 + src/app/qbs-setup-toolchains/probe.h | 84 + .../qbs-setup-toolchains.exe.manifest | 13 + .../qbs-setup-toolchains.pro | 30 + .../qbs-setup-toolchains.qbs | 37 + .../qbs-setup-toolchains.rc | 4 + src/app/qbs-setup-toolchains/sdccprobe.cpp | 302 + src/app/qbs-setup-toolchains/sdccprobe.h | 75 + src/app/qbs-setup-toolchains/xcodeprobe.cpp | 235 + src/app/qbs-setup-toolchains/xcodeprobe.h | 52 + src/app/qbs/application.cpp | 78 + src/app/qbs/application.h | 66 + src/app/qbs/commandlinefrontend.cpp | 704 ++ src/app/qbs/commandlinefrontend.h | 130 + src/app/qbs/consoleprogressobserver.cpp | 117 + src/app/qbs/consoleprogressobserver.h | 71 + src/app/qbs/ctrlchandler.cpp | 80 + src/app/qbs/ctrlchandler.h | 45 + src/app/qbs/main.cpp | 96 + src/app/qbs/parser/commandlineoption.cpp | 701 ++ src/app/qbs/parser/commandlineoption.h | 428 + src/app/qbs/parser/commandlineoptionpool.cpp | 290 + src/app/qbs/parser/commandlineoptionpool.h | 89 + src/app/qbs/parser/commandlineparser.cpp | 630 ++ src/app/qbs/parser/commandlineparser.h | 98 + src/app/qbs/parser/commandpool.cpp | 106 + src/app/qbs/parser/commandpool.h | 66 + src/app/qbs/parser/commandtype.h | 53 + src/app/qbs/parser/parser.pri | 16 + src/app/qbs/parser/parsercommand.cpp | 617 ++ src/app/qbs/parser/parsercommand.h | 280 + src/app/qbs/qbs.pro | 54 + src/app/qbs/qbs.qbs | 59 + src/app/qbs/qbstool.cpp | 104 + src/app/qbs/qbstool.h | 66 + src/app/qbs/session.cpp | 769 ++ src/app/qbs/session.h | 51 + src/app/qbs/sessionpacket.cpp | 113 + src/app/qbs/sessionpacket.h | 70 + src/app/qbs/sessionpacketreader.cpp | 85 + src/app/qbs/sessionpacketreader.h | 72 + src/app/qbs/status.cpp | 166 + src/app/qbs/status.h | 50 + src/app/qbs/stdinreader.cpp | 166 + src/app/qbs/stdinreader.h | 66 + src/app/shared/logging/coloredoutput.cpp | 111 + src/app/shared/logging/coloredoutput.h | 73 + src/app/shared/logging/consolelogger.cpp | 117 + src/app/shared/logging/consolelogger.h | 88 + src/app/shared/logging/logging.pri | 2 + src/install_prefix.pri | 1 + src/lib/bundledlibs.pri | 3 + src/lib/corelib/api/api.pri | 52 + src/lib/corelib/api/changeset.cpp | 397 + src/lib/corelib/api/changeset.h | 140 + src/lib/corelib/api/internaljobs.cpp | 464 + src/lib/corelib/api/internaljobs.h | 230 + src/lib/corelib/api/jobs.cpp | 375 + src/lib/corelib/api/jobs.h | 173 + src/lib/corelib/api/languageinfo.cpp | 134 + src/lib/corelib/api/languageinfo.h | 62 + src/lib/corelib/api/project.cpp | 1160 +++ src/lib/corelib/api/project.h | 182 + src/lib/corelib/api/project_p.h | 138 + src/lib/corelib/api/projectdata.cpp | 1022 ++ src/lib/corelib/api/projectdata.h | 263 + src/lib/corelib/api/projectdata_p.h | 137 + src/lib/corelib/api/projectfileupdater.cpp | 552 ++ src/lib/corelib/api/projectfileupdater.h | 147 + src/lib/corelib/api/propertymap_p.h | 57 + src/lib/corelib/api/qmljsrewriter.cpp | 728 ++ src/lib/corelib/api/qmljsrewriter.h | 130 + src/lib/corelib/api/rulecommand.cpp | 154 + src/lib/corelib/api/rulecommand.h | 90 + src/lib/corelib/api/rulecommand_p.h | 69 + src/lib/corelib/api/runenvironment.cpp | 493 + src/lib/corelib/api/runenvironment.h | 104 + src/lib/corelib/api/transformerdata.cpp | 56 + src/lib/corelib/api/transformerdata.h | 78 + src/lib/corelib/api/transformerdata_p.h | 59 + .../buildgraph/abstractcommandexecutor.cpp | 107 + .../buildgraph/abstractcommandexecutor.h | 104 + src/lib/corelib/buildgraph/artifact.cpp | 187 + src/lib/corelib/buildgraph/artifact.h | 178 + .../corelib/buildgraph/artifactcleaner.cpp | 232 + src/lib/corelib/buildgraph/artifactcleaner.h | 72 + .../buildgraph/artifactsscriptvalue.cpp | 216 + .../corelib/buildgraph/artifactsscriptvalue.h | 63 + .../corelib/buildgraph/artifactvisitor.cpp | 83 + src/lib/corelib/buildgraph/artifactvisitor.h | 71 + src/lib/corelib/buildgraph/buildgraph.cpp | 859 ++ src/lib/corelib/buildgraph/buildgraph.h | 102 + src/lib/corelib/buildgraph/buildgraph.pri | 89 + .../corelib/buildgraph/buildgraphloader.cpp | 974 ++ src/lib/corelib/buildgraph/buildgraphloader.h | 154 + src/lib/corelib/buildgraph/buildgraphnode.cpp | 87 + src/lib/corelib/buildgraph/buildgraphnode.h | 104 + .../corelib/buildgraph/buildgraphvisitor.h | 68 + src/lib/corelib/buildgraph/cycledetector.cpp | 110 + src/lib/corelib/buildgraph/cycledetector.h | 77 + .../dependencyparametersscriptvalue.cpp | 85 + .../dependencyparametersscriptvalue.h | 55 + src/lib/corelib/buildgraph/depscanner.cpp | 270 + src/lib/corelib/buildgraph/depscanner.h | 129 + .../buildgraph/emptydirectoriesremover.cpp | 116 + .../buildgraph/emptydirectoriesremover.h | 73 + .../buildgraph/environmentscriptrunner.cpp | 223 + .../buildgraph/environmentscriptrunner.h | 78 + src/lib/corelib/buildgraph/executor.cpp | 1332 +++ src/lib/corelib/buildgraph/executor.h | 200 + src/lib/corelib/buildgraph/executorjob.cpp | 180 + src/lib/corelib/buildgraph/executorjob.h | 103 + src/lib/corelib/buildgraph/filedependency.cpp | 90 + src/lib/corelib/buildgraph/filedependency.h | 96 + src/lib/corelib/buildgraph/forward_decls.h | 76 + .../buildgraph/inputartifactscanner.cpp | 399 + .../corelib/buildgraph/inputartifactscanner.h | 150 + .../corelib/buildgraph/jscommandexecutor.cpp | 289 + .../corelib/buildgraph/jscommandexecutor.h | 84 + src/lib/corelib/buildgraph/nodeset.cpp | 73 + src/lib/corelib/buildgraph/nodeset.h | 131 + src/lib/corelib/buildgraph/nodetreedumper.cpp | 127 + src/lib/corelib/buildgraph/nodetreedumper.h | 83 + .../buildgraph/processcommandexecutor.cpp | 424 + .../buildgraph/processcommandexecutor.h | 101 + .../corelib/buildgraph/productbuilddata.cpp | 142 + src/lib/corelib/buildgraph/productbuilddata.h | 120 + .../corelib/buildgraph/productinstaller.cpp | 254 + src/lib/corelib/buildgraph/productinstaller.h | 85 + .../corelib/buildgraph/projectbuilddata.cpp | 483 + src/lib/corelib/buildgraph/projectbuilddata.h | 138 + src/lib/corelib/buildgraph/qtmocscanner.cpp | 278 + src/lib/corelib/buildgraph/qtmocscanner.h | 83 + .../buildgraph/rawscanneddependency.cpp | 83 + .../corelib/buildgraph/rawscanneddependency.h | 87 + src/lib/corelib/buildgraph/rawscanresults.cpp | 72 + src/lib/corelib/buildgraph/rawscanresults.h | 106 + .../corelib/buildgraph/requestedartifacts.cpp | 204 + .../corelib/buildgraph/requestedartifacts.h | 106 + .../buildgraph/requesteddependencies.cpp | 91 + .../buildgraph/requesteddependencies.h | 77 + .../buildgraph/rescuableartifactdata.h | 139 + src/lib/corelib/buildgraph/rulecommands.cpp | 489 + src/lib/corelib/buildgraph/rulecommands.h | 237 + src/lib/corelib/buildgraph/rulegraph.cpp | 150 + src/lib/corelib/buildgraph/rulegraph.h | 92 + src/lib/corelib/buildgraph/rulenode.cpp | 321 + src/lib/corelib/buildgraph/rulenode.h | 122 + .../corelib/buildgraph/rulesapplicator.cpp | 684 ++ src/lib/corelib/buildgraph/rulesapplicator.h | 137 + .../buildgraph/rulesevaluationcontext.cpp | 125 + .../buildgraph/rulesevaluationcontext.h | 98 + .../buildgraph/scriptclasspropertyiterator.h | 110 + .../corelib/buildgraph/timestampsupdater.cpp | 97 + .../corelib/buildgraph/timestampsupdater.h | 60 + src/lib/corelib/buildgraph/transformer.cpp | 331 + src/lib/corelib/buildgraph/transformer.h | 140 + .../buildgraph/transformerchangetracking.cpp | 381 + .../buildgraph/transformerchangetracking.h | 53 + src/lib/corelib/corelib.pro | 42 + src/lib/corelib/corelib.qbs | 528 + .../generators/generatableprojectiterator.cpp | 89 + .../generators/generatableprojectiterator.h | 63 + src/lib/corelib/generators/generator.cpp | 251 + src/lib/corelib/generators/generator.h | 97 + src/lib/corelib/generators/generatordata.cpp | 155 + src/lib/corelib/generators/generatordata.h | 183 + src/lib/corelib/generators/generators.pri | 38 + src/lib/corelib/generators/generatorutils.cpp | 261 + src/lib/corelib/generators/generatorutils.h | 99 + .../generators/generatorversioninfo.cpp | 51 + .../corelib/generators/generatorversioninfo.h | 85 + .../generators/igeneratableprojectvisitor.h | 107 + src/lib/corelib/generators/ixmlnodevisitor.h | 69 + src/lib/corelib/generators/xmlproject.cpp | 50 + src/lib/corelib/generators/xmlproject.h | 54 + .../corelib/generators/xmlprojectwriter.cpp | 93 + src/lib/corelib/generators/xmlprojectwriter.h | 70 + src/lib/corelib/generators/xmlproperty.cpp | 56 + src/lib/corelib/generators/xmlproperty.h | 88 + .../corelib/generators/xmlpropertygroup.cpp | 66 + src/lib/corelib/generators/xmlpropertygroup.h | 78 + src/lib/corelib/generators/xmlworkspace.cpp | 66 + src/lib/corelib/generators/xmlworkspace.h | 69 + .../corelib/generators/xmlworkspacewriter.cpp | 93 + .../corelib/generators/xmlworkspacewriter.h | 70 + src/lib/corelib/jsextensions/binaryfile.cpp | 276 + src/lib/corelib/jsextensions/domxml.cpp | 465 + .../jsextensions/environmentextension.cpp | 160 + src/lib/corelib/jsextensions/file.cpp | 290 + .../jsextensions/fileinfoextension.cpp | 330 + src/lib/corelib/jsextensions/jsextensions.cpp | 105 + src/lib/corelib/jsextensions/jsextensions.h | 66 + src/lib/corelib/jsextensions/jsextensions.pri | 26 + .../corelib/jsextensions/moduleproperties.cpp | 357 + .../corelib/jsextensions/moduleproperties.h | 86 + src/lib/corelib/jsextensions/process.cpp | 354 + src/lib/corelib/jsextensions/propertylist.cpp | 48 + src/lib/corelib/jsextensions/propertylist.h | 115 + src/lib/corelib/jsextensions/propertylist.mm | 373 + .../corelib/jsextensions/propertylistutils.h | 74 + .../corelib/jsextensions/propertylistutils.mm | 185 + src/lib/corelib/jsextensions/temporarydir.cpp | 122 + src/lib/corelib/jsextensions/textfile.cpp | 263 + .../jsextensions/utilitiesextension.cpp | 945 ++ .../corelib/language/artifactproperties.cpp | 72 + src/lib/corelib/language/artifactproperties.h | 85 + .../corelib/language/astimportshandler.cpp | 305 + src/lib/corelib/language/astimportshandler.h | 94 + .../language/astpropertiesitemhandler.cpp | 192 + .../language/astpropertiesitemhandler.h | 63 + src/lib/corelib/language/asttools.cpp | 74 + src/lib/corelib/language/asttools.h | 58 + .../corelib/language/builtindeclarations.cpp | 603 ++ .../corelib/language/builtindeclarations.h | 102 + src/lib/corelib/language/deprecationinfo.h | 71 + src/lib/corelib/language/evaluationdata.h | 66 + src/lib/corelib/language/evaluator.cpp | 278 + src/lib/corelib/language/evaluator.h | 138 + .../corelib/language/evaluatorscriptclass.cpp | 776 ++ .../corelib/language/evaluatorscriptclass.h | 133 + src/lib/corelib/language/filecontext.cpp | 64 + src/lib/corelib/language/filecontext.h | 74 + src/lib/corelib/language/filecontextbase.cpp | 53 + src/lib/corelib/language/filecontextbase.h | 79 + src/lib/corelib/language/filetags.cpp | 99 + src/lib/corelib/language/filetags.h | 96 + src/lib/corelib/language/forward_decls.h | 159 + src/lib/corelib/language/identifiersearch.cpp | 77 + src/lib/corelib/language/identifiersearch.h | 70 + src/lib/corelib/language/item.cpp | 391 + src/lib/corelib/language/item.h | 174 + src/lib/corelib/language/itemdeclaration.cpp | 64 + src/lib/corelib/language/itemdeclaration.h | 84 + src/lib/corelib/language/itemobserver.h | 58 + src/lib/corelib/language/itempool.cpp | 62 + src/lib/corelib/language/itempool.h | 71 + src/lib/corelib/language/itemreader.cpp | 140 + src/lib/corelib/language/itemreader.h | 99 + .../corelib/language/itemreaderastvisitor.cpp | 405 + .../corelib/language/itemreaderastvisitor.h | 97 + .../language/itemreadervisitorstate.cpp | 198 + .../corelib/language/itemreadervisitorstate.h | 81 + src/lib/corelib/language/itemtype.h | 91 + src/lib/corelib/language/jsimports.h | 91 + src/lib/corelib/language/language.cpp | 982 ++ src/lib/corelib/language/language.h | 751 ++ src/lib/corelib/language/language.pri | 82 + src/lib/corelib/language/loader.cpp | 222 + src/lib/corelib/language/loader.h | 88 + src/lib/corelib/language/moduleloader.cpp | 4271 +++++++++ src/lib/corelib/language/moduleloader.h | 490 + src/lib/corelib/language/modulemerger.cpp | 264 + src/lib/corelib/language/modulemerger.h | 89 + src/lib/corelib/language/moduleproviderinfo.h | 93 + .../language/preparescriptobserver.cpp | 97 + .../corelib/language/preparescriptobserver.h | 96 + src/lib/corelib/language/projectresolver.cpp | 1875 ++++ src/lib/corelib/language/projectresolver.h | 205 + src/lib/corelib/language/property.cpp | 61 + src/lib/corelib/language/property.h | 108 + .../corelib/language/propertydeclaration.cpp | 236 + .../corelib/language/propertydeclaration.h | 124 + .../corelib/language/propertymapinternal.cpp | 108 + .../corelib/language/propertymapinternal.h | 90 + src/lib/corelib/language/qualifiedid.cpp | 70 + src/lib/corelib/language/qualifiedid.h | 73 + .../corelib/language/resolvedfilecontext.cpp | 61 + .../corelib/language/resolvedfilecontext.h | 79 + src/lib/corelib/language/scriptengine.cpp | 828 ++ src/lib/corelib/language/scriptengine.h | 385 + src/lib/corelib/language/scriptimporter.cpp | 162 + src/lib/corelib/language/scriptimporter.h | 68 + .../language/scriptpropertyobserver.cpp | 54 + .../corelib/language/scriptpropertyobserver.h | 81 + src/lib/corelib/language/value.cpp | 221 + src/lib/corelib/language/value.h | 230 + src/lib/corelib/logging/categories.cpp | 55 + src/lib/corelib/logging/categories.h | 60 + src/lib/corelib/logging/ilogsink.cpp | 128 + src/lib/corelib/logging/ilogsink.h | 91 + src/lib/corelib/logging/logger.cpp | 235 + src/lib/corelib/logging/logger.h | 143 + src/lib/corelib/logging/logging.pri | 18 + src/lib/corelib/logging/translator.h | 57 + src/lib/corelib/parser/parser.pri | 21 + src/lib/corelib/parser/qmlerror.cpp | 294 + src/lib/corelib/parser/qmlerror.h | 86 + src/lib/corelib/parser/qmljs.g | 3011 ++++++ src/lib/corelib/parser/qmljsast.cpp | 925 ++ src/lib/corelib/parser/qmljsast_p.h | 2633 +++++ src/lib/corelib/parser/qmljsastfwd_p.h | 182 + src/lib/corelib/parser/qmljsastvisitor.cpp | 50 + src/lib/corelib/parser/qmljsastvisitor_p.h | 326 + src/lib/corelib/parser/qmljsengine_p.cpp | 158 + src/lib/corelib/parser/qmljsengine_p.h | 128 + src/lib/corelib/parser/qmljsglobal_p.h | 71 + src/lib/corelib/parser/qmljsgrammar.cpp | 1011 ++ src/lib/corelib/parser/qmljsgrammar_p.h | 211 + src/lib/corelib/parser/qmljskeywords_p.h | 862 ++ src/lib/corelib/parser/qmljslexer.cpp | 1155 +++ src/lib/corelib/parser/qmljslexer_p.h | 244 + src/lib/corelib/parser/qmljsmemorypool_p.h | 167 + src/lib/corelib/parser/qmljsparser.cpp | 1819 ++++ src/lib/corelib/parser/qmljsparser_p.h | 241 + src/lib/corelib/qbs.h | 68 + src/lib/corelib/tools/applecodesignutils.cpp | 156 + src/lib/corelib/tools/applecodesignutils.h | 56 + src/lib/corelib/tools/architectures.cpp | 159 + src/lib/corelib/tools/architectures.h | 58 + src/lib/corelib/tools/buildgraphlocker.cpp | 166 + src/lib/corelib/tools/buildgraphlocker.h | 87 + src/lib/corelib/tools/buildoptions.cpp | 463 + src/lib/corelib/tools/buildoptions.h | 125 + src/lib/corelib/tools/clangclinfo.cpp | 176 + src/lib/corelib/tools/clangclinfo.h | 61 + src/lib/corelib/tools/cleanoptions.cpp | 158 + src/lib/corelib/tools/cleanoptions.h | 80 + src/lib/corelib/tools/codelocation.cpp | 184 + src/lib/corelib/tools/codelocation.h | 94 + src/lib/corelib/tools/commandechomode.cpp | 101 + src/lib/corelib/tools/commandechomode.h | 70 + src/lib/corelib/tools/dynamictypecheck.h | 42 + src/lib/corelib/tools/error.cpp | 328 + src/lib/corelib/tools/error.h | 132 + src/lib/corelib/tools/executablefinder.cpp | 153 + src/lib/corelib/tools/executablefinder.h | 77 + src/lib/corelib/tools/fileinfo.cpp | 572 ++ src/lib/corelib/tools/fileinfo.h | 112 + src/lib/corelib/tools/filesaver.cpp | 121 + src/lib/corelib/tools/filesaver.h | 76 + src/lib/corelib/tools/filetime.cpp | 213 + src/lib/corelib/tools/filetime.h | 134 + src/lib/corelib/tools/generateoptions.cpp | 94 + src/lib/corelib/tools/generateoptions.h | 71 + src/lib/corelib/tools/hostosinfo.h | 265 + src/lib/corelib/tools/id.cpp | 285 + src/lib/corelib/tools/id.h | 95 + src/lib/corelib/tools/installoptions.cpp | 242 + src/lib/corelib/tools/installoptions.h | 96 + src/lib/corelib/tools/iosutils.h | 108 + src/lib/corelib/tools/joblimits.cpp | 176 + src/lib/corelib/tools/joblimits.h | 102 + src/lib/corelib/tools/jsliterals.cpp | 107 + src/lib/corelib/tools/jsliterals.h | 58 + src/lib/corelib/tools/jsonhelper.h | 89 + src/lib/corelib/tools/launcherinterface.cpp | 168 + src/lib/corelib/tools/launcherinterface.h | 88 + src/lib/corelib/tools/launcherpackets.cpp | 174 + src/lib/corelib/tools/launcherpackets.h | 178 + src/lib/corelib/tools/launchersocket.cpp | 153 + src/lib/corelib/tools/launchersocket.h | 93 + src/lib/corelib/tools/msvcinfo.cpp | 699 ++ src/lib/corelib/tools/msvcinfo.h | 142 + src/lib/corelib/tools/pathutils.h | 65 + src/lib/corelib/tools/persistence.cpp | 238 + src/lib/corelib/tools/persistence.h | 532 + src/lib/corelib/tools/preferences.cpp | 204 + src/lib/corelib/tools/preferences.h | 84 + src/lib/corelib/tools/processresult.cpp | 154 + src/lib/corelib/tools/processresult.h | 88 + src/lib/corelib/tools/processresult_p.h | 66 + src/lib/corelib/tools/processutils.cpp | 133 + src/lib/corelib/tools/processutils.h | 57 + src/lib/corelib/tools/profile.cpp | 257 + src/lib/corelib/tools/profile.h | 111 + src/lib/corelib/tools/profiling.cpp | 126 + src/lib/corelib/tools/profiling.h | 82 + src/lib/corelib/tools/progressobserver.cpp | 105 + src/lib/corelib/tools/progressobserver.h | 72 + .../corelib/tools/projectgeneratormanager.cpp | 81 + .../corelib/tools/projectgeneratormanager.h | 74 + src/lib/corelib/tools/qbs_export.h | 75 + src/lib/corelib/tools/qbsassert.cpp | 60 + src/lib/corelib/tools/qbsassert.h | 69 + src/lib/corelib/tools/qbspluginmanager.cpp | 162 + src/lib/corelib/tools/qbspluginmanager.h | 90 + src/lib/corelib/tools/qbsprocess.cpp | 185 + src/lib/corelib/tools/qbsprocess.h | 106 + src/lib/corelib/tools/qttools.cpp | 59 + src/lib/corelib/tools/qttools.h | 123 + .../corelib/tools/scannerpluginmanager.cpp | 86 + src/lib/corelib/tools/scannerpluginmanager.h | 72 + src/lib/corelib/tools/scripttools.cpp | 82 + src/lib/corelib/tools/scripttools.h | 96 + src/lib/corelib/tools/set.h | 436 + src/lib/corelib/tools/settings.cpp | 259 + src/lib/corelib/tools/settings.h | 109 + src/lib/corelib/tools/settingscreator.cpp | 157 + src/lib/corelib/tools/settingscreator.h | 80 + src/lib/corelib/tools/settingsmodel.cpp | 401 + src/lib/corelib/tools/settingsmodel.h | 89 + .../corelib/tools/settingsrepresentation.cpp | 82 + .../corelib/tools/settingsrepresentation.h | 55 + .../corelib/tools/setupprojectparameters.cpp | 691 ++ .../corelib/tools/setupprojectparameters.h | 153 + src/lib/corelib/tools/shellutils.cpp | 262 + src/lib/corelib/tools/shellutils.h | 94 + src/lib/corelib/tools/stlutils.h | 125 + src/lib/corelib/tools/stringconstants.h | 261 + src/lib/corelib/tools/stringutils.h | 135 + src/lib/corelib/tools/toolchains.cpp | 122 + src/lib/corelib/tools/toolchains.h | 52 + src/lib/corelib/tools/tools.pri | 146 + src/lib/corelib/tools/version.cpp | 86 + src/lib/corelib/tools/version.h | 137 + .../corelib/tools/visualstudioversioninfo.cpp | 190 + .../corelib/tools/visualstudioversioninfo.h | 86 + .../corelib/tools/vsenvironmentdetector.cpp | 284 + src/lib/corelib/tools/vsenvironmentdetector.h | 80 + src/lib/corelib/tools/weakpointer.h | 94 + src/lib/corelib/use_corelib.pri | 47 + src/lib/corelib/use_installed_corelib.pri | 38 + src/lib/library.pri | 32 + src/lib/library_base.pri | 21 + src/lib/libs.qbs | 9 + src/lib/msbuild/io/msbuildprojectwriter.cpp | 238 + src/lib/msbuild/io/msbuildprojectwriter.h | 58 + .../msbuild/io/visualstudiosolutionwriter.cpp | 167 + .../msbuild/io/visualstudiosolutionwriter.h | 67 + src/lib/msbuild/msbuild.pro | 58 + src/lib/msbuild/msbuild.qbs | 88 + src/lib/msbuild/msbuild/imsbuildgroup.cpp | 65 + src/lib/msbuild/msbuild/imsbuildgroup.h | 66 + src/lib/msbuild/msbuild/imsbuildnode.cpp | 37 + src/lib/msbuild/msbuild/imsbuildnode.h | 47 + src/lib/msbuild/msbuild/imsbuildnodevisitor.h | 81 + src/lib/msbuild/msbuild/imsbuildproperty.cpp | 81 + src/lib/msbuild/msbuild/imsbuildproperty.h | 67 + .../msbuild/items/msbuildclcompile.cpp | 42 + .../msbuild/msbuild/items/msbuildclcompile.h | 48 + .../msbuild/items/msbuildclinclude.cpp | 42 + .../msbuild/msbuild/items/msbuildclinclude.h | 48 + .../msbuild/msbuild/items/msbuildfileitem.cpp | 74 + .../msbuild/msbuild/items/msbuildfileitem.h | 60 + .../msbuild/msbuild/items/msbuildfilter.cpp | 117 + src/lib/msbuild/msbuild/items/msbuildfilter.h | 68 + src/lib/msbuild/msbuild/items/msbuildlink.cpp | 44 + src/lib/msbuild/msbuild/items/msbuildlink.h | 49 + src/lib/msbuild/msbuild/items/msbuildnone.cpp | 40 + src/lib/msbuild/msbuild/items/msbuildnone.h | 48 + src/lib/msbuild/msbuild/msbuildimport.cpp | 86 + src/lib/msbuild/msbuild/msbuildimport.h | 72 + .../msbuild/msbuild/msbuildimportgroup.cpp | 74 + src/lib/msbuild/msbuild/msbuildimportgroup.h | 66 + src/lib/msbuild/msbuild/msbuilditem.cpp | 93 + src/lib/msbuild/msbuild/msbuilditem.h | 74 + .../msbuild/msbuilditemdefinitiongroup.cpp | 57 + .../msbuild/msbuilditemdefinitiongroup.h | 59 + src/lib/msbuild/msbuild/msbuilditemgroup.cpp | 76 + src/lib/msbuild/msbuild/msbuilditemgroup.h | 68 + .../msbuild/msbuild/msbuilditemmetadata.cpp | 57 + src/lib/msbuild/msbuild/msbuilditemmetadata.h | 60 + src/lib/msbuild/msbuild/msbuildproject.cpp | 97 + src/lib/msbuild/msbuild/msbuildproject.h | 70 + src/lib/msbuild/msbuild/msbuildproperty.cpp | 57 + src/lib/msbuild/msbuild/msbuildproperty.h | 59 + .../msbuild/msbuild/msbuildpropertygroup.cpp | 80 + .../msbuild/msbuild/msbuildpropertygroup.h | 68 + .../solution/ivisualstudiosolutionproject.cpp | 71 + .../solution/ivisualstudiosolutionproject.h | 67 + .../msbuild/solution/visualstudiosolution.cpp | 117 + .../msbuild/solution/visualstudiosolution.h | 85 + .../visualstudiosolutionfileproject.cpp | 76 + .../visualstudiosolutionfileproject.h | 63 + .../visualstudiosolutionfolderproject.cpp | 47 + .../visualstudiosolutionfolderproject.h | 50 + .../visualstudiosolutionglobalsection.cpp | 86 + .../visualstudiosolutionglobalsection.h | 65 + src/lib/msbuild/use_installed_msbuild.pri | 20 + src/lib/msbuild/use_msbuild.pri | 38 + .../include/QtScript/qtscriptglobal.h | 46 + src/lib/scriptengine/scriptengine.pro | 92 + src/lib/scriptengine/scriptengine.qbs | 439 + src/lib/scriptengine/use_scriptengine.pri | 19 + src/lib/staticlibrary.pri | 4 + src/libexec/libexec.pri | 11 + src/libexec/libexec.pro | 3 + src/libexec/libexec.qbs | 7 + .../qbs_processlauncher/launcherlogging.cpp | 46 + .../qbs_processlauncher/launcherlogging.h | 54 + .../launchersockethandler.cpp | 295 + .../launchersockethandler.h | 92 + .../processlauncher-main.cpp | 71 + .../qbs_processlauncher.pro | 21 + .../qbs_processlauncher.qbs | 39 + src/library_dirname.pri | 1 + src/packages/archive/archive.qbs | 86 + src/packages/chocolatey/chocolatey.qbs | 130 + src/packages/chocolatey/chocolateyinstall.ps1 | 23 + src/packages/chocolatey/icon.png | Bin 0 -> 6915 bytes src/packages/chocolatey/qbs.nuspec | 23 + src/packages/packages.qbs | 16 + .../clangcompilationdb/clangcompilationdb.pri | 1 + .../clangcompilationdb/clangcompilationdb.pro | 11 + .../clangcompilationdb/clangcompilationdb.qbs | 11 + .../clangcompilationdbgenerator.cpp | 158 + .../clangcompilationdbgenerator.h | 67 + .../clangcompilationdbgeneratorplugin.cpp | 64 + src/plugins/generator/generator.pro | 6 + .../archs/arm/armarchiversettingsgroup_v8.cpp | 95 + .../archs/arm/armarchiversettingsgroup_v8.h | 58 + .../arm/armassemblersettingsgroup_v8.cpp | 230 + .../archs/arm/armassemblersettingsgroup_v8.h | 63 + .../arm/armbuildconfigurationgroup_v8.cpp | 98 + .../archs/arm/armbuildconfigurationgroup_v8.h | 71 + .../archs/arm/armcompilersettingsgroup_v8.cpp | 478 + .../archs/arm/armcompilersettingsgroup_v8.h | 63 + .../archs/arm/armgeneralsettingsgroup_v8.cpp | 551 ++ .../archs/arm/armgeneralsettingsgroup_v8.h | 63 + .../archs/arm/armlinkersettingsgroup_v8.cpp | 488 + .../archs/arm/armlinkersettingsgroup_v8.h | 71 + .../archs/avr/avrarchiversettingsgroup_v7.cpp | 94 + .../archs/avr/avrarchiversettingsgroup_v7.h | 58 + .../avr/avrassemblersettingsgroup_v7.cpp | 226 + .../archs/avr/avrassemblersettingsgroup_v7.h | 61 + .../avr/avrbuildconfigurationgroup_v7.cpp | 98 + .../archs/avr/avrbuildconfigurationgroup_v7.h | 72 + .../archs/avr/avrcompilersettingsgroup_v7.cpp | 492 + .../archs/avr/avrcompilersettingsgroup_v7.h | 64 + .../archs/avr/avrgeneralsettingsgroup_v7.cpp | 774 ++ .../archs/avr/avrgeneralsettingsgroup_v7.h | 63 + .../archs/avr/avrlinkersettingsgroup_v7.cpp | 387 + .../archs/avr/avrlinkersettingsgroup_v7.h | 66 + .../mcs51/mcs51archiversettingsgroup_v10.cpp | 95 + .../mcs51/mcs51archiversettingsgroup_v10.h | 59 + .../mcs51/mcs51assemblersettingsgroup_v10.cpp | 228 + .../mcs51/mcs51assemblersettingsgroup_v10.h | 62 + .../mcs51buildconfigurationgroup_v10.cpp | 98 + .../mcs51/mcs51buildconfigurationgroup_v10.h | 71 + .../mcs51/mcs51compilersettingsgroup_v10.cpp | 476 + .../mcs51/mcs51compilersettingsgroup_v10.h | 65 + .../mcs51/mcs51generalsettingsgroup_v10.cpp | 1013 ++ .../mcs51/mcs51generalsettingsgroup_v10.h | 66 + .../mcs51/mcs51linkersettingsgroup_v10.cpp | 335 + .../mcs51/mcs51linkersettingsgroup_v10.h | 68 + .../msp430/msp430archiversettingsgroup_v7.cpp | 94 + .../msp430/msp430archiversettingsgroup_v7.h | 59 + .../msp430assemblersettingsgroup_v7.cpp | 229 + .../msp430/msp430assemblersettingsgroup_v7.h | 62 + .../msp430buildconfigurationgroup_v7.cpp | 98 + .../msp430/msp430buildconfigurationgroup_v7.h | 72 + .../msp430/msp430compilersettingsgroup_v7.cpp | 486 + .../msp430/msp430compilersettingsgroup_v7.h | 65 + .../msp430/msp430generalsettingsgroup_v7.cpp | 482 + .../msp430/msp430generalsettingsgroup_v7.h | 64 + .../msp430/msp430linkersettingsgroup_v7.cpp | 289 + .../msp430/msp430linkersettingsgroup_v7.h | 65 + .../stm8/stm8archiversettingsgroup_v3.cpp | 94 + .../archs/stm8/stm8archiversettingsgroup_v3.h | 59 + .../stm8/stm8assemblersettingsgroup_v3.cpp | 229 + .../stm8/stm8assemblersettingsgroup_v3.h | 62 + .../stm8/stm8buildconfigurationgroup_v3.cpp | 98 + .../stm8/stm8buildconfigurationgroup_v3.h | 72 + .../stm8/stm8compilersettingsgroup_v3.cpp | 442 + .../archs/stm8/stm8compilersettingsgroup_v3.h | 64 + .../stm8/stm8generalsettingsgroup_v3.cpp | 366 + .../archs/stm8/stm8generalsettingsgroup_v3.h | 64 + .../archs/stm8/stm8linkersettingsgroup_v3.cpp | 410 + .../archs/stm8/stm8linkersettingsgroup_v3.h | 73 + src/plugins/generator/iarew/iarew.pri | 1 + src/plugins/generator/iarew/iarew.pro | 131 + src/plugins/generator/iarew/iarew.qbs | 131 + .../iarew/iarewfileversionproperty.cpp | 57 + .../iarew/iarewfileversionproperty.h | 48 + .../generator/iarew/iarewgenerator.cpp | 158 + src/plugins/generator/iarew/iarewgenerator.h | 70 + .../generator/iarew/iarewgeneratorplugin.cpp | 67 + .../iarew/iarewoptionpropertygroup.cpp | 57 + .../iarew/iarewoptionpropertygroup.h | 50 + src/plugins/generator/iarew/iarewproject.cpp | 130 + src/plugins/generator/iarew/iarewproject.h | 54 + .../generator/iarew/iarewprojectwriter.cpp | 52 + .../generator/iarew/iarewprojectwriter.h | 51 + .../iarew/iarewsettingspropertygroup.cpp | 106 + .../iarew/iarewsettingspropertygroup.h | 68 + .../iarew/iarewsourcefilepropertygroup.cpp | 54 + .../iarew/iarewsourcefilepropertygroup.h | 52 + .../iarew/iarewsourcefilespropertygroup.cpp | 56 + .../iarew/iarewsourcefilespropertygroup.h | 50 + .../iarew/iarewtoolchainpropertygroup.cpp | 43 + .../iarew/iarewtoolchainpropertygroup.h | 48 + src/plugins/generator/iarew/iarewutils.cpp | 156 + src/plugins/generator/iarew/iarewutils.h | 75 + .../generator/iarew/iarewversioninfo.h | 60 + .../generator/iarew/iarewworkspace.cpp | 63 + src/plugins/generator/iarew/iarewworkspace.h | 58 + .../generator/iarew/iarewworkspacewriter.cpp | 52 + .../generator/iarew/iarewworkspacewriter.h | 51 + .../archs/arm/armbuildtargetgroup_v5.cpp | 108 + .../keiluv/archs/arm/armbuildtargetgroup_v5.h | 70 + .../archs/arm/armcommonpropertygroup_v5.cpp | 50 + .../archs/arm/armcommonpropertygroup_v5.h | 54 + .../archs/arm/armdebugoptiongroup_v5.cpp | 50 + .../keiluv/archs/arm/armdebugoptiongroup_v5.h | 54 + .../keiluv/archs/arm/armdlloptiongroup_v5.cpp | 50 + .../keiluv/archs/arm/armdlloptiongroup_v5.h | 54 + .../archs/arm/armtargetassemblergroup_v5.cpp | 154 + .../archs/arm/armtargetassemblergroup_v5.h | 54 + .../arm/armtargetcommonoptionsgroup_v5.cpp | 208 + .../arm/armtargetcommonoptionsgroup_v5.h | 54 + .../archs/arm/armtargetcompilergroup_v5.cpp | 218 + .../archs/arm/armtargetcompilergroup_v5.h | 54 + .../keiluv/archs/arm/armtargetgroup_v5.cpp | 56 + .../keiluv/archs/arm/armtargetgroup_v5.h | 54 + .../archs/arm/armtargetlinkergroup_v5.cpp | 163 + .../archs/arm/armtargetlinkergroup_v5.h | 54 + .../archs/arm/armtargetmiscgroup_v5.cpp | 75 + .../keiluv/archs/arm/armtargetmiscgroup_v5.h | 54 + .../keiluv/archs/arm/armutilitiesgroup_v5.cpp | 50 + .../keiluv/archs/arm/armutilitiesgroup_v5.h | 54 + .../archs/mcs51/mcs51buildtargetgroup_v5.cpp | 109 + .../archs/mcs51/mcs51buildtargetgroup_v5.h | 70 + .../mcs51/mcs51commonpropertygroup_v5.cpp | 50 + .../archs/mcs51/mcs51commonpropertygroup_v5.h | 54 + .../archs/mcs51/mcs51debugoptiongroup_v5.cpp | 50 + .../archs/mcs51/mcs51debugoptiongroup_v5.h | 54 + .../archs/mcs51/mcs51dlloptiongroup_v5.cpp | 50 + .../archs/mcs51/mcs51dlloptiongroup_v5.h | 54 + .../mcs51/mcs51targetassemblergroup_v5.cpp | 143 + .../mcs51/mcs51targetassemblergroup_v5.h | 54 + .../mcs51targetcommonoptionsgroup_v5.cpp | 140 + .../mcs51/mcs51targetcommonoptionsgroup_v5.h | 54 + .../mcs51/mcs51targetcompilergroup_v5.cpp | 281 + .../archs/mcs51/mcs51targetcompilergroup_v5.h | 54 + .../archs/mcs51/mcs51targetgroup_v5.cpp | 56 + .../keiluv/archs/mcs51/mcs51targetgroup_v5.h | 54 + .../archs/mcs51/mcs51targetlinkergroup_v5.cpp | 240 + .../archs/mcs51/mcs51targetlinkergroup_v5.h | 54 + .../archs/mcs51/mcs51targetmiscgroup_v5.cpp | 104 + .../archs/mcs51/mcs51targetmiscgroup_v5.h | 54 + .../archs/mcs51/mcs51utilitiesgroup_v5.cpp | 50 + .../archs/mcs51/mcs51utilitiesgroup_v5.h | 54 + .../keiluv/archs/mcs51/mcs51utils.cpp | 83 + .../generator/keiluv/archs/mcs51/mcs51utils.h | 56 + src/plugins/generator/keiluv/keiluv.pri | 1 + src/plugins/generator/keiluv/keiluv.pro | 90 + src/plugins/generator/keiluv/keiluv.qbs | 89 + .../generator/keiluv/keiluvconstants.h | 44 + .../keiluv/keiluvfilesgroupspropertygroup.cpp | 196 + .../keiluv/keiluvfilesgroupspropertygroup.h | 52 + .../generator/keiluv/keiluvgenerator.cpp | 149 + .../generator/keiluv/keiluvgenerator.h | 71 + .../keiluv/keiluvgeneratorplugin.cpp | 67 + .../generator/keiluv/keiluvproject.cpp | 113 + src/plugins/generator/keiluv/keiluvproject.h | 56 + .../generator/keiluv/keiluvprojectwriter.cpp | 58 + .../generator/keiluv/keiluvprojectwriter.h | 51 + src/plugins/generator/keiluv/keiluvutils.cpp | 123 + src/plugins/generator/keiluv/keiluvutils.h | 64 + .../generator/keiluv/keiluvversioninfo.h | 60 + .../generator/keiluv/keiluvworkspace.cpp | 68 + .../generator/keiluv/keiluvworkspace.h | 56 + .../keiluv/keiluvworkspacewriter.cpp | 58 + .../generator/keiluv/keiluvworkspacewriter.h | 51 + .../makefilegenerator/makefilegenerator.cpp | 362 + .../makefilegenerator/makefilegenerator.h | 55 + .../makefilegenerator/makefilegenerator.pri | 1 + .../makefilegenerator/makefilegenerator.pro | 11 + .../makefilegenerator/makefilegenerator.qbs | 11 + .../makefilegeneratorplugin.cpp | 64 + .../visualstudio/msbuildfiltersproject.cpp | 153 + .../visualstudio/msbuildfiltersproject.h | 54 + .../msbuildqbsgenerateproject.cpp | 70 + .../visualstudio/msbuildqbsgenerateproject.h | 53 + .../visualstudio/msbuildqbsproductproject.cpp | 428 + .../visualstudio/msbuildqbsproductproject.h | 74 + ...msbuildsharedsolutionpropertiesproject.cpp | 151 + .../msbuildsharedsolutionpropertiesproject.h | 53 + .../msbuildsolutionpropertiesproject.cpp | 69 + .../msbuildsolutionpropertiesproject.h | 56 + .../visualstudio/msbuildtargetproject.cpp | 136 + .../visualstudio/msbuildtargetproject.h | 73 + .../generator/visualstudio/msbuildutils.h | 115 + .../generator/visualstudio/visualstudio.pri | 1 + .../generator/visualstudio/visualstudio.pro | 32 + .../generator/visualstudio/visualstudio.qbs | 34 + .../visualstudio/visualstudiogenerator.cpp | 367 + .../visualstudio/visualstudiogenerator.h | 80 + .../visualstudiogeneratorplugin.cpp | 67 + .../visualstudio/visualstudioguidpool.cpp | 93 + .../visualstudio/visualstudioguidpool.h | 62 + src/plugins/plugins.pri | 21 + src/plugins/plugins.pro | 2 + src/plugins/plugins.qbs | 14 + src/plugins/qbs_plugin_common.pri | 9 + src/plugins/qbsplugin.qbs | 49 + .../cpp/CPlusPlusForwardDeclarations.h | 153 + src/plugins/scanner/cpp/Lexer.cpp | 669 ++ src/plugins/scanner/cpp/Lexer.h | 161 + src/plugins/scanner/cpp/Token.cpp | 148 + src/plugins/scanner/cpp/Token.h | 368 + src/plugins/scanner/cpp/cpp.pri | 1 + src/plugins/scanner/cpp/cpp.pro | 10 + src/plugins/scanner/cpp/cpp.qbs | 18 + src/plugins/scanner/cpp/cpp_global.h | 49 + src/plugins/scanner/cpp/cppscanner.cpp | 335 + src/plugins/scanner/qt/qt.pri | 1 + src/plugins/scanner/qt/qt.pro | 8 + src/plugins/scanner/qt/qt.qbs | 11 + src/plugins/scanner/qt/qtscanner.cpp | 195 + src/plugins/scanner/scanner.h | 106 + src/plugins/scanner/scanner.pro | 3 + src/plugins/use_plugin.pri | 10 + src/shared/bundledqt/bundledqt.qbs | 173 + src/shared/bundledqt/qt.conf | 2 + src/shared/json/README.md | 2 + src/shared/json/json.cpp | 4964 ++++++++++ src/shared/json/json.h | 589 ++ src/shared/json/json.pri | 3 + src/shared/json/json.qbs | 16 + src/src.qbs | 13 + static-res.pro | 74 + static.pro | 83 + tests/auto/api/api.pro | 28 + tests/auto/api/api.qbs | 20 + tests/auto/api/testdata/QBS-728/QBS-728.qbs | 5 + .../add-qobject-macro-to-cpp-file.qbs | 4 + .../add-qobject-macro-to-cpp-file/main.cpp | 35 + .../add-qobject-macro-to-cpp-file/object.cpp | 41 + .../add-qobject-macro-to-cpp-file/object.h | 32 + .../added-file-persistent.qbs | 6 + .../testdata/added-file-persistent/file.cpp | 29 + .../testdata/added-file-persistent/main.cpp | 31 + .../auto/api/testdata/app-without-sources/a.c | 29 + .../app-without-sources.qbs | 38 + .../auto/api/testdata/app-without-sources/b.c | 37 + .../testdata/base-properties/imports/Bar.qbs | 4 + .../testdata/base-properties/imports/Foo.qbs | 6 + .../api/testdata/base-properties/main.cpp | 42 + .../auto/api/testdata/base-properties/prj.qbs | 9 + .../build-error-code-location.qbs | 10 + .../build-properties-source.qbs | 15 + .../testdata/build-properties-source/main.cpp | 38 + .../build-single-file/build-single-file.qbs | 32 + .../testdata/build-single-file/compiled.cpp | 34 + .../testdata/build-single-file/ignored1.cpp | 0 .../testdata/build-single-file/ignored2.cpp | 0 .../auto/api/testdata/build-single-file/pch.h | 1 + .../buildgraph-info/buildgraph-info.qbs | 3 + .../buildgraph-locking/buildgraph-locking.qbs | 2 + .../change-dependent-lib.qbs | 26 + .../testdata/change-dependent-lib/main.cpp | 40 + .../testdata/change-dependent-lib/mylib.cpp | 37 + .../testdata/check-outputs/check-outputs.qbs | 36 + tests/auto/api/testdata/check-outputs/foo.txt | 1 + tests/auto/api/testdata/codegen/codegen.qbs | 73 + tests/auto/api/testdata/codegen/foo.txt | 1 + .../command-extraction/command-extraction.qbs | 3 + .../api/testdata/command-extraction/main.cpp | 29 + .../dependency-on-multiplexed-type.qbs | 18 + .../disabled-product/disabled-product.qbs | 8 + .../api/testdata/disabled-product/main.cpp | 1 + .../disabled-project/disabled-project.qbs | 6 + .../disabled_install_group.qbs | 9 + .../testdata/disabled_install_group/main.cpp | 29 + .../disappeared-wildcard-file.qbs | 4 + .../disappeared-wildcard-file/file1.txt | 0 .../disappeared-wildcard-file/file2.txt | 0 .../duplicate-product-names/explicit.qbs | 5 + .../implicit-indirect.qbs | 3 + .../duplicate-product-names/implicit.qbs | 5 + .../subdir1/subproject.qbs | 1 + .../subdir2/subproject.qbs | 1 + .../empty-filetag-list/dontcompilethis.cpp | 1 + .../empty-filetag-list/empty-filetag-list.qbs | 6 + .../empty-submodules-list.qbs | 6 + .../enable-and-disable-product.qbs | 6 + .../enable-and-disable-product/main.cpp | 29 + .../error-in-setup-run-environment.qbs | 3 + .../modules/mymodule/mymodule.qbs | 5 + .../excluded-inputs/excluded-inputs.qbs | 113 + .../api/testdata/explicitly-depends-on/a.in | 0 .../api/testdata/explicitly-depends-on/b.in | 0 .../api/testdata/explicitly-depends-on/c.in | 0 .../explicitly-depends-on/compiler.cpp | 11 + .../explicitly-depends-on.qbs | 69 + .../export-item-with-group.qbs | 15 + .../testdata/export-item-with-group/main.cpp | 29 + .../testdata/export-simple/export-simple.qbs | 51 + .../auto/api/testdata/export-simple/lib1.cpp | 36 + .../auto/api/testdata/export-simple/main.cpp | 39 + .../export-with-recursive-depends.qbs | 13 + .../export-with-recursive-depends/main1.cpp | 29 + .../export-with-recursive-depends/main2.cpp | 34 + .../modules/module1/module1.qbs | 3 + .../modules/module2/module2.qbs | 4 + .../testdata/fallback-gcc/fallback-gcc.qbs | 20 + tests/auto/api/testdata/file-tagger/bla.txt | 15 + .../auto/api/testdata/file-tagger/moc_cpp.qbs | 42 + .../filetagsfilter_override/InstalledApp.qbs | 9 + .../filetagsfilter_override.qbs | 10 + .../testdata/filetagsfilter_override/main.cpp | 29 + .../generated-files-list.qbs | 14 + .../testdata/generated-files-list/main.cpp | 39 + .../generated-files-list/mainwindow.cpp | 42 + .../generated-files-list/mainwindow.h | 50 + .../generated-files-list/mainwindow.ui | 24 + .../infinite-loop-js/infinite-loop.qbs | 19 + .../infinite-loop-process/infinite-loop.qbs | 32 + .../testdata/infinite-loop-process/main.cpp | 35 + .../infinite-loop-resolving.qbs | 3 + .../inherit-qbs-search-paths/imports/Foo.qbs | 6 + .../inherit-qbs-search-paths/main.cpp | 37 + .../testdata/inherit-qbs-search-paths/prj.qbs | 12 + .../subdir/modules/bli/m.qbs | 5 + .../subdir2/modules/bla/m.qbs | 5 + .../installed-artifact/installed-artifact.qbs | 27 + .../api/testdata/installed-artifact/main.cpp | 29 + .../api/testdata/is-runnable/is-runnable.qbs | 12 + .../lib-same-source/lib-same-source.qbs | 21 + .../api/testdata/lib-same-source/main.cpp | 34 + .../link-dynamiclibs-staticlibs/dynamic1.cpp | 12 + .../link-dynamiclibs-staticlibs/dynamic2.cpp | 10 + .../link-dynamiclibs-staticlibs.qbs | 45 + .../link-dynamiclibs-staticlibs/main.cpp | 11 + .../link-dynamiclibs-staticlibs/static1.cpp | 10 + .../link-dynamiclibs-staticlibs/static2.cpp | 7 + .../link-dynamiclibs-staticlibs/static2.h | 10 + .../api/testdata/link-dynamiclibs/lib1.cpp | 39 + .../api/testdata/link-dynamiclibs/lib2.cpp | 39 + .../api/testdata/link-dynamiclibs/lib3.cpp | 42 + .../api/testdata/link-dynamiclibs/lib4.cpp | 43 + .../auto/api/testdata/link-dynamiclibs/lib4.h | 51 + .../link-dynamiclibs/link-dynamiclibs.qbs | 71 + .../api/testdata/link-dynamiclibs/main.cpp | 42 + .../link-static-lib/helper1/helper1.cpp | 35 + .../link-static-lib/helper1/helper1.h | 34 + .../link-static-lib/helper2/helper2.cpp | 34 + .../link-static-lib/helper2/helper2.h | 34 + .../link-static-lib/link-static-lib.qbs | 45 + .../api/testdata/link-static-lib/main.cpp | 37 + .../testdata/link-static-lib/mystaticlib.cpp | 37 + .../link-static-lib/mystaticlibhelper.cpp | 33 + .../link-staticlib-dynamiclib.qbs | 19 + .../link-staticlib-dynamiclib/main.cpp | 7 + .../mydynamiclib.cpp | 3 + .../link-staticlib-dynamiclib/mystaticlib.cpp | 5 + .../link-staticlibs-dynamiclibs/dynamic1.cpp | 11 + .../link-staticlibs-dynamiclibs/dynamic2.cpp | 7 + .../link-staticlibs-dynamiclibs.qbs | 52 + .../link-staticlibs-dynamiclibs/main.cpp | 10 + .../link-staticlibs-dynamiclibs/static1.cpp | 10 + .../link-staticlibs-dynamiclibs/static2.cpp | 11 + .../link-staticlibs-dynamiclibs/static2.h | 10 + .../local-profiles/local-profiles.qbs | 44 + .../api/testdata/lots-of-dots/dotty.matrix.ui | 21 + .../testdata/lots-of-dots/lots-of-dots.qbs | 16 + .../api/testdata/lots-of-dots/m.a.i.n.cpp | 38 + .../api/testdata/lots-of-dots/object.narf.cpp | 35 + .../api/testdata/lots-of-dots/object.narf.h | 41 + .../api/testdata/lots-of-dots/polka.dots.qrc | 5 + .../testdata/missing-qobject-header/main.cpp | 34 + .../missing-qobject-header/myobject.cpp | 33 + .../missing-qobject-header/myobject.h | 38 + .../testdata/missing-source-file/file1.txt | 0 .../missing-source-file/file2.txt.missing | 0 .../testdata/missing-source-file/file3.txt | 0 .../missing-source-file.qbs | 7 + tests/auto/api/testdata/moc-cpp/bla.cpp | 43 + tests/auto/api/testdata/moc-cpp/moc-cpp.qbs | 13 + .../moc-hpp-included/moc-hpp-included.qbs | 19 + .../api/testdata/moc-hpp-included/object.cpp | 43 + .../api/testdata/moc-hpp-included/object.h | 41 + .../api/testdata/moc-hpp-included/object2.h | 41 + .../api/testdata/moc-hpp-included/object2.mm | 16 + tests/auto/api/testdata/moc-hpp/moc-hpp.qbs | 17 + tests/auto/api/testdata/moc-hpp/object.cpp | 41 + tests/auto/api/testdata/moc-hpp/object.h | 41 + .../api/testdata/multi-arch/host+target.input | 0 .../api/testdata/multi-arch/host-tool.input | 0 .../api/testdata/multi-arch/multi-arch.qbs | 56 + .../testdata/multiplexing/multiplexing.qbs | 94 + .../new-output-artifact-in-dependency/lib.cpp | 31 + .../main.cpp | 34 + .../new-output-artifact-in-dependency.qbs | 17 + .../new-pattern-match/new-pattern-match.qbs | 3 + .../invalidaccessfromproduct.qbs | 1 + .../nonexistingprojectproperties.qbs | 1 + tests/auto/api/testdata/objc/main.mm | 13 + tests/auto/api/testdata/objc/objc.qbs | 7 + .../precompiled-header-dynamic/autogen.h.in | 6 + .../precompiled-header-dynamic/main.cpp | 32 + .../testdata/precompiled-header-dynamic/pch.h | 29 + .../precompiled-header-dynamic.qbs | 29 + .../testdata/precompiled-header-new/main.cpp | 55 + .../precompiled-header-new/myobject.cpp | 41 + .../precompiled-header-new/myobject.h | 40 + .../precompiled-header-new.qbs | 10 + .../testdata/precompiled-header-new/stable.h | 37 + .../auto/api/testdata/process-result/main.cpp | 37 + .../process-result/process-result.qbs | 35 + .../api/testdata/productNameWithDots/app.cpp | 29 + .../api/testdata/productNameWithDots/lib.cpp | 29 + .../productNameWithDots.qbs | 13 + .../file.cpp | 29 + .../main.cpp | 29 + ...roject-data-after-product-invalidation.qbs | 7 + .../project-editing/existingfile1.txt | 0 .../project-editing/existingfile2.txt | 0 .../project-editing/existingfile3.txt | 0 .../api/testdata/project-editing/file.cpp | 27 + .../auto/api/testdata/project-editing/file.h | 27 + .../api/testdata/project-editing/main.cpp | 27 + .../api/testdata/project-editing/newfile1.txt | 0 .../api/testdata/project-editing/newfile2.txt | 0 .../api/testdata/project-editing/newfile3.txt | 0 .../api/testdata/project-editing/newfile4.txt | 0 .../project-editing/project-editing.qbs | 37 + .../project-editing/project-with-no-files.qbs | 5 + .../testdata/project-editing/subdir/file.txt | 0 .../testdata/project-editing/test.wildcard | 0 .../project.early-error.qbs | 4 + .../project.late-error.qbs | 12 + .../project-invalidation/project.no-error.qbs | 3 + .../project-locking/project-locking.qbs | 2 + .../project-properties-by-name/main1.cpp | 38 + .../project-properties-by-name/main2.cpp | 38 + .../project-properties-by-name.qbs | 20 + .../project-with-probe-and-profile-item.qbs | 19 + .../project-with-properties-item.qbs | 10 + tests/auto/api/testdata/projectd | 0 .../api/testdata/properties-blocks/main.cpp | 46 + .../properties-blocks/properties-blocks.qbs | 27 + .../api/testdata/qt5-plugin/echointerface.h | 51 + .../api/testdata/qt5-plugin/echoplugin.cpp | 34 + .../auto/api/testdata/qt5-plugin/echoplugin.h | 45 + .../qt5-plugin/echoplugin.json.source | 1 + .../testdata/qt5-plugin/echoplugin_dummy.cpp | 29 + .../api/testdata/qt5-plugin/qt5-plugin.qbs | 51 + tests/auto/api/testdata/rc/main.cpp | 32 + tests/auto/api/testdata/rc/rc.qbs | 15 + .../auto/api/testdata/rc/subdir/rc-include.h | 0 tests/auto/api/testdata/rc/test.rc | 24 + .../recursive-wildcards/dir/file1.txt | 0 .../recursive-wildcards/dir/subdir/file2.txt | 0 .../recursive-wildcards.qbs | 8 + .../ambiguousdir/p1.qbs | 0 .../ambiguousdir/p2.qbs | 0 .../testdata/referenced-file-errors/cycle.qbs | 5 + .../emptydir/.gitignore | 0 .../modules/brokenmodule/brokenmodule.qbs | 3 + .../testdata/referenced-file-errors/okay.qbs | 1 + .../testdata/referenced-file-errors/okay2.qbs | 1 + .../referenced-file-errors.qbs | 30 + .../referenced-file-errors/wrongtype.qbs | 1 + .../auto/api/testdata/references/invalid1.qbs | 3 + .../auto/api/testdata/references/invalid2.qbs | 3 + .../subproject1.qbs | 0 .../subproject2.qbs | 0 .../subproject3.qbs | 0 .../subdir-with-no-project/test.txt | 0 .../references/subdir-with-one-project/p.qbs | 1 + .../subdir-with-one-project/test.txt | 0 tests/auto/api/testdata/references/valid.qbs | 3 + .../relaxed-mode-recovery.qbs | 8 + .../testdata/remove-file-dependency/main.cpp | 37 + .../removeFileDependency.qbs | 5 + .../remove-file-dependency/someheader.h | 29 + .../auto/api/testdata/rename-product/lib.cpp | 31 + .../auto/api/testdata/rename-product/main.cpp | 34 + .../api/testdata/rename-product/rename.qbs | 18 + .../testdata/rename-target-artifact/lib.cpp | 31 + .../testdata/rename-target-artifact/main.cpp | 34 + .../rename-target-artifact/rename.qbs | 21 + .../renamed-qbs-source-file.qbs | 9 + .../the-product/the-prodduct.qbs | 1 + .../api/testdata/restored-warnings/file.cpp | 1 + .../api/testdata/restored-warnings/main.cpp | 1 + .../restored-warnings/restored-warnings.qbs | 14 + .../auto/api/testdata/rule-conflict/main.cpp | 29 + tests/auto/api/testdata/rule-conflict/pch1.h | 27 + tests/auto/api/testdata/rule-conflict/pch2.h | 27 + .../testdata/rule-conflict/rule-conflict.qbs | 8 + .../run-disabled-product.qbs | 4 + tests/auto/api/testdata/same-base-name/lib.c | 34 + .../auto/api/testdata/same-base-name/lib.cpp | 34 + tests/auto/api/testdata/same-base-name/lib.m | 6 + tests/auto/api/testdata/same-base-name/lib.mm | 8 + tests/auto/api/testdata/same-base-name/main.c | 46 + .../same-base-name/same-base-name.qbs | 31 + tests/auto/api/testdata/simple-probe/main.cpp | 29 + .../testdata/simple-probe/simple-probe.qbs | 31 + .../api/testdata/soft-dependency/main.cpp | 32 + .../soft-dependency/soft-dependency.qbs | 10 + .../source-file-in-build-dir/file.cpp | 29 + .../source-file-in-build-dir.qbs | 27 + .../auto/api/testdata/static-lib-deps/a1.cpp | 35 + .../auto/api/testdata/static-lib-deps/a2.cpp | 35 + tests/auto/api/testdata/static-lib-deps/b.cpp | 35 + tests/auto/api/testdata/static-lib-deps/c.cpp | 35 + tests/auto/api/testdata/static-lib-deps/d.cpp | 64 + tests/auto/api/testdata/static-lib-deps/d.mm | 8 + tests/auto/api/testdata/static-lib-deps/e.cpp | 35 + .../api/testdata/static-lib-deps/main.cpp | 35 + .../static-lib-deps/static-lib-deps.qbs | 95 + .../resources/imports/LibraryType/type.js | 1 + .../modules/QtCoreDepender/qtcoredepender.qbs | 3 + .../resources/modules/cute/core/core.qbs | 4 + .../resources/modules/cute/core/cuteglobal.h | 1 + .../testdata/subprojects/subproject1/main.cpp | 34 + .../subprojects/subproject2/subproject2.qbs | 13 + .../subproject2/subproject3/subproject3.qbs | 15 + .../subproject2/subproject3/testlib.cpp | 32 + .../testdata/subprojects/toplevelproject.qbs | 17 + .../target-artifact-status.qbs | 28 + .../auto/api/testdata/timeout-js/timeout.qbs | 20 + .../api/testdata/timeout-process/main.cpp | 37 + .../api/testdata/timeout-process/timeout.qbs | 40 + .../modules/thetool/thetool.qbs | 27 + .../use-outside-project.qbs | 5 + .../use-within-project/main.cpp | 10 + .../use-within-project/tool-input.txt | 0 .../use-within-project/use-within-project.qbs | 46 + .../transformer-data/transformer-data.qbs | 35 + tests/auto/api/testdata/transformers/main.cpp | 70 + .../testdata/transformers/transformers.qbs | 92 + .../modules/mymodule/mymodule.qbs | 23 + .../modules/myothermodule/myothermodule.qbs | 3 + .../two-default-property-values/test.txt | 0 .../two-default-property-values.qbs | 11 + tests/auto/api/testdata/type-change/main.cpp | 29 + .../api/testdata/type-change/type-change.qbs | 5 + tests/auto/api/testdata/uic/bla.cpp | 36 + tests/auto/api/testdata/uic/bla.h | 29 + tests/auto/api/testdata/uic/ui.h | 29 + tests/auto/api/testdata/uic/ui.ui | 31 + tests/auto/api/testdata/uic/uic.qbs | 14 + tests/auto/api/tst_api.cpp | 3130 ++++++ tests/auto/api/tst_api.h | 173 + tests/auto/auto.pri | 18 + tests/auto/auto.pro | 21 + tests/auto/auto.qbs | 21 + tests/auto/blackbox/blackbox-android.pro | 21 + tests/auto/blackbox/blackbox-android.qbs | 21 + tests/auto/blackbox/blackbox-apple.pro | 20 + tests/auto/blackbox/blackbox-apple.qbs | 22 + tests/auto/blackbox/blackbox-baremetal.pro | 18 + tests/auto/blackbox/blackbox-baremetal.qbs | 21 + tests/auto/blackbox/blackbox-clangdb.pro | 18 + tests/auto/blackbox/blackbox-clangdb.qbs | 24 + tests/auto/blackbox/blackbox-examples.pro | 18 + tests/auto/blackbox/blackbox-examples.qbs | 21 + tests/auto/blackbox/blackbox-java.pro | 18 + tests/auto/blackbox/blackbox-java.qbs | 21 + tests/auto/blackbox/blackbox-joblimits.pro | 18 + tests/auto/blackbox/blackbox-joblimits.qbs | 20 + tests/auto/blackbox/blackbox-qt.pro | 18 + tests/auto/blackbox/blackbox-qt.qbs | 21 + tests/auto/blackbox/blackbox.pro | 21 + tests/auto/blackbox/blackbox.qbs | 28 + tests/auto/blackbox/find/find-android.qbs | 90 + tests/auto/blackbox/find/find-jdk.qbs | 35 + tests/auto/blackbox/find/find-nodejs.qbs | 33 + tests/auto/blackbox/find/find-typescript.qbs | 34 + tests/auto/blackbox/find/find-xcode.qbs | 40 + .../testdata-android/aidl/AndroidManifest.xml | 13 + .../blackbox/testdata-android/aidl/aidl.qbs | 8 + .../aidl/io/qbs/aidltest/Interface1.aidl | 7 + .../aidl/io/qbs/aidltest/Interface2.aidl | 5 + .../aidl/io/qbs/aidltest/MainActivity.java | 18 + .../minimal-native/libdependency.so | 0 .../minimal-native/minimal-native.qbs | 13 + .../src/main/AndroidManifest.xml | 13 + .../main/java/my/minimal/MinimalNative.java | 22 + .../minimal-native/src/main/native/native.c | 9 + .../multiple-apks-per-project.qbs | 6 + .../product1/product1.qbs | 34 + .../product1/src/main/AndroidManifest.xml | 14 + .../src/main/java/io/qt/dummy1/Dummy.java | 19 + .../product1/src/main/jni/lib1.cpp | 29 + .../product1/src/main/jni/lib2.cpp | 29 + .../product1/src/main/res/values/strings.xml | 3 + .../product2/product2.qbs | 29 + .../product2/src/main/AndroidManifest.xml | 14 + .../src/main/java/io/qt/dummy2/Dummy.java | 17 + .../product2/src/main/jni/lib1.cpp | 29 + .../product2/src/main/jni/lib2.cpp | 29 + .../io/qbs/lib3/lib3.java | 6 + .../multiple-libs-per-apk/lib4.java | 2 + .../multiple-libs-per-apk/lib5.java | 2 + .../multiple-libs-per-apk/lib6.java | 2 + .../multiple-libs-per-apk/lib7.java | 2 + .../multiple-libs-per-apk/lib8.java | 2 + .../multiple-libs-per-apk.qbs | 84 + .../src/main/AndroidManifest.xml | 14 + .../src/main/java/io/qt/dummy/Dummy.java | 19 + .../src/main/jni/lib1.cpp | 29 + .../src/main/jni/lib2.cpp | 29 + .../src/main/res/values/strings.xml | 3 + .../testdata-android/no-native/no-native.qbs | 9 + .../testdata-android/qml-app/main.cpp | 21 + .../testdata-android/qml-app/main.qml | 9 + .../testdata-android/qml-app/qml-app.qbs | 16 + .../blackbox/testdata-android/qml-app/qml.qrc | 5 + .../qml-app/src/main/AndroidManifest.xml | 81 + .../qml-app/src/main/assets/dummyasset.txt | 0 .../testdata-android/teapot/teapot.qbs | 150 + .../aggregateDependencyLinking.qbs | 37 + .../aggregateDependencyLinking/app.c | 8 + .../aggregateDependencyLinking/lib.c | 12 + .../testdata-apple/apple-dmg/apple-dmg.qbs | 81 + .../apple-dmg/de_DE.lproj/eula.txt | 6 + .../apple-dmg/en_GB.lproj/eula.txt | 6 + .../apple-dmg/en_US.lproj/eula.txt | 6 + .../apple-dmg/fr_FR.lproj/eula.txt | 6 + .../testdata-apple/apple-dmg/hello.icns | Bin 0 -> 1393763 bytes .../testdata-apple/apple-dmg/hello.tif | Bin 0 -> 866742 bytes .../apple-dmg/ja_JP.lproj/eula.txt | 6 + .../apple-dmg/ko_KR.lproj/eula.rtf | 49 + .../blackbox/testdata-apple/apple-dmg/main.c | 1 + .../apple-dmg/ru_RU.lproj/eula.txt | 6 + .../apple-dmg/white.iconset/icon_16x16.png | Bin 0 -> 649 bytes .../apple-dmg/white.iconset/icon_16x16@2x.png | Bin 0 -> 665 bytes .../apple-dmg/zh_CN.lproj/eula.odt | Bin 0 -> 2680 bytes .../apple-dmg/zh_TW.lproj/eula.docx | Bin 0 -> 12338 bytes .../testdata-apple/apple-multiconfig/app.c | 2 + .../apple-multiconfig/apple-multiconfig.qbs | 177 + .../apple-multiconfig/helpers.js | 67 + .../testdata-apple/apple-multiconfig/lib.c | 12 + .../bundle-structure/bundle-structure.qbs | 143 + .../testdata-apple/bundle-structure/dummy.c | 29 + .../testdata-apple/bundle-structure/dummy.h | 27 + .../testdata-apple/bundle-structure/dummy_p.h | 27 + .../bundle-structure/resource.txt | 0 .../deploymentTarget/deployment.qbs | 14 + .../testdata-apple/deploymentTarget/main.c | 29 + .../embedInfoPlist/embedInfoPlist.qbs | 48 + .../testdata-apple/embedInfoPlist/main.m | 9 + .../frameworkStructure/BaseResource | 0 .../frameworkStructure/Widget.cpp | 29 + .../frameworkStructure/Widget.h | 29 + .../frameworkStructure/WidgetPrivate.h | 27 + .../en.lproj/EnglishResource | 0 .../frameworkStructure/frameworkStructure.qbs | 19 + .../assetcatalog/EmptyStoryboard.storyboard | 8 + .../ib/assetcatalog/MainMenu.xib | 4666 +++++++++ .../ib/assetcatalog/Storyboard.storyboard | 53 + .../ib/assetcatalog/assetcatalogempty.qbs | 27 + .../empty.iconset/icon_16x16.png | Bin 0 -> 649 bytes .../empty.iconset/icon_16x16@2x.png | Bin 0 -> 665 bytes .../other.imageset/Contents.json | 22 + .../other.imageset/icon_16x16.png | Bin 0 -> 649 bytes .../other.imageset/icon_16x16@2x.png | Bin 0 -> 665 bytes .../testdata-apple/ib/assetcatalog/main.c | 32 + .../assetcatalog1.xcassets/.keep | 0 .../assetcatalog2.xcassets/.keep | 0 .../ib/empty-asset-catalogs/main.c | 29 + .../multiple-asset-catalogs.qbs | 8 + .../testdata-apple/ib/iconset/iconset.qbs | 5 + .../ib/iconset/white.iconset/icon_16x16.png | Bin 0 -> 649 bytes .../iconset/white.iconset/icon_16x16@2x.png | Bin 0 -> 665 bytes .../ib/iconsetapp/iconsetapp.qbs | 9 + .../testdata-apple/ib/iconsetapp/main.c | 32 + .../iconsetapp/white.iconset/icon_16x16.png | Bin 0 -> 649 bytes .../white.iconset/icon_16x16@2x.png | Bin 0 -> 665 bytes .../other.imageset/Contents.json | 22 + .../other.imageset/icon_16x16.png | Bin 0 -> 649 bytes .../other.imageset/icon_16x16@2x.png | Bin 0 -> 665 bytes .../other.imageset/Contents.json | 22 + .../other.imageset/icon_16x16.png | Bin 0 -> 649 bytes .../other.imageset/icon_16x16@2x.png | Bin 0 -> 665 bytes .../ib/multiple-asset-catalogs/main.c | 29 + .../multiple-asset-catalogs.qbs | 8 + .../testdata-apple/infoplist/infoplist.qbs | 4 + .../blackbox/testdata-apple/infoplist/main.c | 29 + .../blackbox/testdata-apple/objc-arc/arc.m | 3 + .../blackbox/testdata-apple/objc-arc/arc.mm | 3 + .../blackbox/testdata-apple/objc-arc/main.m | 4 + .../blackbox/testdata-apple/objc-arc/mrc.m | 3 + .../blackbox/testdata-apple/objc-arc/mrc.mm | 3 + .../testdata-apple/objc-arc/objc-arc.qbs | 19 + .../overrideInfoPlist/Override-Info.plist | 10 + .../testdata-apple/overrideInfoPlist/main.c | 1 + .../overrideInfoPlist/overrideInfoPlist.qbs | 16 + .../testdata-apple/xcode/xcode-project.qbs | 58 + .../BareMetalApplication.qbs | 104 + .../BareMetalStaticLibrary.qbs | 104 + .../testdata-baremetal/defines/defines.qbs | 6 + .../testdata-baremetal/defines/main.c | 11 + .../distribution-include-paths/bar/bar.h | 6 + .../distribution-include-paths.qbs | 6 + .../distribution-include-paths/foo/foo.h | 6 + .../distribution-include-paths/main.c | 7 + .../do-not-generate-compiler-listing.qbs | 30 + .../do-not-generate-compiler-listing/fun.c | 4 + .../do-not-generate-compiler-listing/main.c | 6 + .../do-not-generate-linker-map.qbs | 16 + .../do-not-generate-linker-map/main.c | 4 + .../external-static-libraries.qbs | 48 + .../external-static-libraries/lib-a.c | 4 + .../external-static-libraries/lib-b.c | 6 + .../external-static-libraries/main.c | 6 + .../generate-compiler-listing/fun.c | 4 + .../generate-compiler-listing.qbs | 30 + .../generate-compiler-listing/main.c | 6 + .../generate-linker-map.qbs | 16 + .../generate-linker-map/main.c | 4 + .../one-object-application/main.c | 4 + .../one-object-application.qbs | 5 + .../one-object-asm-application/78k-iar.s | 6 + .../one-object-asm-application/arm-gcc.s | 5 + .../one-object-asm-application/arm-iar.s | 7 + .../one-object-asm-application/arm-keil.s | 7 + .../one-object-asm-application/avr-gcc.s | 6 + .../one-object-asm-application/avr-iar.s | 7 + .../one-object-asm-application/avr32-gcc.s | 8 + .../one-object-asm-application/avr32-iar.s | 7 + .../one-object-asm-application/c166-keil.s | 7 + .../one-object-asm-application/cr16-iar.s | 6 + .../one-object-asm-application/m16c-iar.s | 6 + .../one-object-asm-application/m32c-gcc.s | 5 + .../one-object-asm-application/m32r-gcc.s | 9 + .../one-object-asm-application/m68k-gcc.s | 7 + .../one-object-asm-application/mcs251-keil.s | 8 + .../one-object-asm-application/mcs51-iar.s | 7 + .../one-object-asm-application/mcs51-keil.s | 8 + .../one-object-asm-application/mcs51-sdcc.s | 7 + .../one-object-asm-application/msp430-gcc.s | 5 + .../one-object-asm-application/msp430-iar.s | 6 + .../one-object-asm-application.qbs | 97 + .../one-object-asm-application/r32c-iar.s | 7 + .../one-object-asm-application/rh850-iar.s | 7 + .../one-object-asm-application/riscv-gcc.s | 11 + .../one-object-asm-application/rl78-gcc.s | 11 + .../one-object-asm-application/rl78-iar.s | 7 + .../one-object-asm-application/rx-gcc.s | 8 + .../one-object-asm-application/sh-iar.s | 7 + .../one-object-asm-application/stm8-iar.s | 7 + .../one-object-asm-application/stm8-sdcc.s | 7 + .../one-object-asm-application/v850-gcc.s | 11 + .../one-object-asm-application/v850-iar.s | 7 + .../one-object-asm-application/xtensa-gcc.s | 11 + .../preinclude-headers/main.c | 4 + .../preinclude-headers/preinclude-headers.qbs | 18 + .../preinclude-headers/preinclude.h | 6 + .../static-library-dependencies/a1.c | 4 + .../static-library-dependencies/a2.c | 4 + .../static-library-dependencies/app.c | 6 + .../static-library-dependencies/b.c | 6 + .../static-library-dependencies/c.c | 6 + .../static-library-dependencies/d.c | 7 + .../static-library-dependencies/e.c | 7 + .../static-library-dependencies.qbs | 40 + .../system-include-paths/bar/bar.h | 6 + .../system-include-paths/foo/foo.h | 6 + .../system-include-paths/main.c | 7 + .../system-include-paths.qbs | 6 + .../target-platform/target-platform.qbs | 18 + .../two-object-application/fun.c | 4 + .../two-object-application/main.c | 6 + .../two-object-application.qbs | 5 + .../user-include-paths/bar/bar.h | 6 + .../user-include-paths/foo/foo.h | 6 + .../user-include-paths/main.c | 7 + .../user-include-paths/user-include-paths.qbs | 6 + .../project1/i like spaces.cpp | 42 + .../testdata-clangdb/project1/project.qbs | 41 + .../auto/blackbox/testdata-java/java/Car.java | 23 + .../blackbox/testdata-java/java/Car8.java | 28 + .../testdata-java/java/HelloWorld.java | 15 + .../testdata-java/java/HelloWorld8.java | 8 + .../auto/blackbox/testdata-java/java/Jet.java | 7 + .../blackbox/testdata-java/java/Manifest.mf | 1 + .../blackbox/testdata-java/java/Manifest2.mf | 2 + .../testdata-java/java/NoPackage.java | 5 + .../testdata-java/java/RandomStuff.java | 6 + .../blackbox/testdata-java/java/Ship.java | 12 + .../blackbox/testdata-java/java/Vehicle.java | 4 + .../blackbox/testdata-java/java/Vehicles.java | 37 + .../auto/blackbox/testdata-java/java/engine.c | 38 + .../java/inner-class/InnerClass.java | 6 + .../java/inner-class/inner-class.qbs | 3 + .../blackbox/testdata-java/java/vehicles.qbs | 107 + .../job-limits/job-limits.qbs | 97 + .../testdata-joblimits/job-limits/main.cpp | 87 + ...dd-qobject-macro-to-generated-cpp-file.qbs | 25 + .../main.cpp | 35 + .../object.cpp.in | 41 + .../object.h | 32 + .../testdata-qt/auto-qrc/auto-qrc.qbs | 39 + .../blackbox/testdata-qt/auto-qrc/main.cpp | 18 + .../auto-qrc/qrc-base/resource1.txt | 1 + .../auto-qrc/qrc-base/subdir/resource2.txt | 1 + .../auto-qrc/qrc-base/subdir/resource3.txt | 1 + .../testdata-qt/cached-qml/MainForm.ui.qml | 29 + .../testdata-qt/cached-qml/cached-qml.qbs | 38 + .../blackbox/testdata-qt/cached-qml/main.cpp | 14 + .../blackbox/testdata-qt/cached-qml/main.qml | 17 + .../blackbox/testdata-qt/cached-qml/qml.qrc | 7 + .../blackbox/testdata-qt/cached-qml/stuff.js | 1 + .../testdata-qt/combined-moc/combined-moc.qbs | 7 + .../testdata-qt/combined-moc/main.cpp | 6 + .../testdata-qt/combined-moc/theobject.h | 6 + .../testdata-qt/create-project/dummy.txt | 0 .../THIS.IS.A.STRANGE.FILENAME.CAR.XML | 11 + .../testdata-qt/dbus-adaptors/car.cpp | 147 + .../blackbox/testdata-qt/dbus-adaptors/car.h | 61 + .../testdata-qt/dbus-adaptors/car.qbs | 18 + .../testdata-qt/dbus-adaptors/main.cpp | 82 + .../testdata-qt/dbus-interfaces/car.xml | 11 + .../dbus-interfaces/controller.cpp | 92 + .../testdata-qt/dbus-interfaces/controller.h | 57 + .../dbus-interfaces/controller.qbs | 19 + .../testdata-qt/dbus-interfaces/controller.ui | 64 + .../testdata-qt/dbus-interfaces/main.cpp | 62 + .../testdata-qt/forced-moc/createqtclass.h | 14 + .../testdata-qt/forced-moc/forced-moc.qbs | 19 + .../blackbox/testdata-qt/forced-moc/main.cpp | 14 + .../testdata-qt/forced-moc/myqtclass.h | 8 + .../included-moc-cpp/included-moc-cpp.qbs | 9 + .../testdata-qt/included-moc-cpp/main.cpp | 7 + .../testdata-qt/included-moc-cpp/myobject.cpp | 3 + .../testdata-qt/included-moc-cpp/myobject.h | 11 + .../testdata-qt/linker-variant/main.cpp | 6 + .../linker-variant/qt-linker-variant.qbs | 10 + .../auto/blackbox/testdata-qt/lrelease/de.ts | 23 + .../auto/blackbox/testdata-qt/lrelease/hu.ts | 15 + .../testdata-qt/lrelease/lrelease.qbs | 6 + .../testdata-qt/metatypes/metatypes.qbs | 28 + .../testdata-qt/metatypes/mocableclass1.cpp | 3 + .../testdata-qt/metatypes/mocableclass1.h | 8 + .../testdata-qt/metatypes/mocableclass2.cpp | 10 + .../testdata-qt/metatypes/unmocableclass.cpp | 7 + .../testdata-qt/mixed-build-variants/main.cpp | 34 + .../mixed-build-variants.qbs | 9 + .../moc-and-cxx-combining/main.cpp | 6 + .../moc-and-cxx-combining.qbs | 4 + .../moc-and-cxx-combining/myobject.cpp | 14 + .../moc-and-cxx-combining/myobject.h | 6 + .../testdata-qt/moc-compiler-defines/main.cpp | 7 + .../moc-compiler-defines.qbs | 3 + .../moc-compiler-defines/object.cpp | 6 + .../testdata-qt/moc-compiler-defines/object.h | 58 + .../blackbox/testdata-qt/moc-flags/blubb.h | 37 + .../blackbox/testdata-qt/moc-flags/main.cpp | 34 + .../testdata-qt/moc-flags/moc-flags.qbs | 3 + .../testdata-qt/moc-same-file-name/main.cpp | 8 + .../moc-same-file-name/moc-same-file-name.qbs | 25 + .../moc-same-file-name/src1/someclass.cpp | 7 + .../moc-same-file-name/src1/someclass.h | 17 + .../moc-same-file-name/src1/somefile.cpp | 13 + .../moc-same-file-name/src2/someclass.cpp | 7 + .../moc-same-file-name/src2/someclass.h | 17 + .../moc-same-file-name/src2/somefile.cpp | 13 + .../blackbox/testdata-qt/pkgconfig/main.cpp | 46 + .../testdata-qt/pkgconfig/pkgconfig.qbs | 23 + .../testdata-qt/plugin-meta-data/app.cpp | 83 + .../plugin-meta-data/metadata.json | 3 + .../plugin-meta-data/plugin-meta-data.qbs | 59 + .../plugin-meta-data/theplugin.cpp | 38 + .../plugin-support/modules/m1/m1.qbs | 12 + .../plugin-support/modules/m2/m2.qbs | 7 + .../plugin-support/plugin-support-main.cpp | 1 + .../plugin-support/plugin-support.qbs | 18 + .../testdata-qt/qml-debugging/main.cpp | 56 + .../qml-debugging/qml-debugging.qbs | 7 + .../testdata-qt/qmltyperegistrar/example.qml | 58 + .../testdata-qt/qmltyperegistrar/main.cpp | 71 + .../testdata-qt/qmltyperegistrar/person.cpp | 78 + .../testdata-qt/qmltyperegistrar/person.h | 78 + .../qmltyperegistrar/qmltyperegistrar.qbs | 33 + .../testdata-qt/qobject-in-mm/main.mm | 13 + .../qobject-in-mm/qobject-in-mm.qbs | 4 + tests/auto/blackbox/testdata-qt/qrc/bla.cpp | 44 + tests/auto/blackbox/testdata-qt/qrc/bla.qrc | 6 + tests/auto/blackbox/testdata-qt/qrc/i.qbs | 24 + tests/auto/blackbox/testdata-qt/qrc/stuff.txt | 1 + .../blackbox/testdata-qt/qrc/subdir/dummy.txt | 0 tests/auto/blackbox/testdata-qt/qrc/test.cpp | 1 + .../blackbox/testdata-qt/qt-keywords/main.cpp | 14 + .../testdata-qt/qt-keywords/qt-keywords.qbs | 3 + .../qtscxml/dummystatemachine.scxml | 3 + .../blackbox/testdata-qt/qtscxml/main.cpp | 15 + .../blackbox/testdata-qt/qtscxml/qtscxml.qbs | 62 + .../testdata-qt/quick-compiler/main.cpp | 18 + .../testdata-qt/quick-compiler/qml.qrc | 5 + .../quick-compiler/qml/subdir/test.qml | 5 + .../quick-compiler/quick-compiler.qbs | 22 + .../remove-moc-header-from-file-list/file.cpp | 10 + .../remove-moc-header-from-file-list/file.h | 13 + .../remove-moc-header-from-file-list.qbs | 7 + .../static-qt-plugin-linking/lib.cpp | 1 + .../static-qt-plugin-linking/main.cpp | 7 + .../static-qt-plugin-linking.qbs | 26 + .../trackAddMocInclude/after/main.cpp | 49 + .../trackAddMocInclude/before/main.cpp | 47 + .../trackAddMocInclude/before/test.qbs | 6 + .../testdata-qt/trackQObjChange/bla.cpp | 35 + .../trackQObjChange/bla_noqobject.h | 34 + .../testdata-qt/trackQObjChange/bla_qobject.h | 35 + .../testdata-qt/trackQObjChange/i.qbs | 17 + .../auto/blackbox/testdata-qt/unmocable/foo.h | 2 + .../blackbox/testdata-qt/unmocable/main.cpp | 6 + .../testdata-qt/unmocable/unmocable.qbs | 9 + .../QTBUG-51237/modules/mymodule/mymodule.qbs | 4 + .../testdata/QTBUG-51237/qtbug-51237.qbs | 17 + .../add-filetag-to-generated-artifact.qbs | 35 + .../main.cpp | 1 + .../blackbox/testdata/always-run/dummy.txt | 0 .../blackbox/testdata/always-run/rule.qbs | 28 + .../testdata/always-run/transformer.qbs | 22 + .../blackbox/testdata/archiver/archivable.qbs | 11 + .../auto/blackbox/testdata/archiver/list.txt | 2 + .../auto/blackbox/testdata/archiver/test.txt | 0 .../artifact-scanning/artifact-scanning.qbs | 30 + .../artifact-scanning/external-indirect.h | 0 .../testdata/artifact-scanning/external.h | 1 + .../testdata/artifact-scanning/p1.cpp | 4 + .../testdata/artifact-scanning/p2.cpp | 3 + .../testdata/artifact-scanning/p3.cpp | 3 + .../testdata/artifact-scanning/shared.h | 1 + .../artifact-scanning/subdir/external2.h | 0 .../artifacts-map-change-tracking.qbs | 85 + .../artifacts-map-change-tracking/dummy.in | 0 .../artifacts-map-change-tracking/main.cpp | 3 + .../artifacts-map-change-tracking/test.cpp.in | 1 + .../artifacts-map-change-tracking/test.txt | 0 .../artifacts-map-invalidation.qbs | 57 + .../artifacts-map-invalidation/file.in | 0 .../artifacts-map-race-condition.qbs | 73 + .../blackbox/testdata/assembly/assembly.qbs | 74 + tests/auto/blackbox/testdata/assembly/testa.s | 3 + tests/auto/blackbox/testdata/assembly/testb.S | 3 + .../auto/blackbox/testdata/assembly/testc.sx | 3 + .../blackbox/testdata/assembly/testd_x86.asm | 11 + .../testdata/assembly/testd_x86_64.asm | 8 + .../autotest-timeout/autotests-timeout.qbs | 26 + .../testdata/autotest-timeout/test-main.cpp | 37 + .../autotest-with-dependencies.qbs | 37 + .../helper-main.cpp | 7 + .../autotest-with-dependencies/test-main.cpp | 10 + .../blackbox/testdata/autotests/autotests.qbs | 15 + .../testdata/autotests/test1/test1.cpp | 12 + .../testdata/autotests/test1/test1.qbs | 9 + .../autotests/test2/test2-resource.txt | 0 .../testdata/autotests/test2/test2.cpp | 13 + .../testdata/autotests/test2/test2.qbs | 9 + .../testdata/autotests/test3/test3.cpp | 9 + .../testdata/autotests/test3/test3.qbs | 9 + .../aux-inputs-from-deps.qbs | 79 + .../testdata/aux-inputs-from-deps/main.cpp | 3 + .../testdata/aux-inputs-from-deps/util.js | 8 + .../badInterpreter/badInterpreter.qbs | 54 + .../qbs/modules/script-test/script-test.qbs | 38 + .../badInterpreter/script-interp-missing | 1 + .../badInterpreter/script-interp-noexec | 1 + .../testdata/badInterpreter/script-noexec | 0 .../testdata/badInterpreter/script-ok | 1 + .../testdata/bom-sources/bom-sources.qbs | 4 + .../blackbox/testdata/bom-sources/main.cpp | 3 + .../blackbox/testdata/bom-sources/theheader.h | 0 .../build-data-of-disabled-product.qbs | 4 + .../build-data-of-disabled-product/main.cpp | 6 + .../build-data-of-disabled-product/test.cpp | 1 + .../build-directories/build-directories.qbs | 37 + .../build-graph-versions.qbs | 3 + .../testdata/build-graph-versions/main.cpp | 1 + .../build-variant-defaults.qbs | 16 + .../testdata/build-variant-defaults/main.cpp | 1 + .../buildenv-change/buildenv-change.qbs | 16 + .../blackbox/testdata/buildenv-change/file.c | 1 + .../testdata/buildenv-change/main.cpp | 3 + .../buildenv-change/subdir/theheader.h | 0 .../buildenv-change/subdir2/theheader.h | 0 .../blackbox/testdata/capnproto/bar.capnp | 8 + .../blackbox/testdata/capnproto/baz.capnp | 8 + .../capnproto/capnproto_absolute_import.cpp | 14 + .../capnproto/capnproto_absolute_import.qbs | 18 + .../testdata/capnproto/capnproto_cpp.cpp | 13 + .../testdata/capnproto/capnproto_cpp.qbs | 16 + .../capnproto/capnproto_relative_import.cpp | 14 + .../capnproto/capnproto_relative_import.qbs | 17 + .../blackbox/testdata/capnproto/foo.capnp | 6 + .../testdata/capnproto/greeter-client.cpp | 25 + .../testdata/capnproto/greeter-server.cpp | 27 + .../blackbox/testdata/capnproto/greeter.capnp | 13 + .../testdata/capnproto/greeter_cpp.qbs | 32 + .../testdata/capnproto/imports/foo.capnp | 6 + .../change-in-disabled-product.qbs | 7 + .../change-in-disabled-product/test1.txt | 0 .../change-in-disabled-product/test2.txt | 0 .../change-in-imported-file.qbs | 22 + .../change-in-imported-file/prepare.js | 3 + .../testdata/change-in-imported-file/test.txt | 0 .../change-tracking-and-multiplexing.qbs | 11 + .../change-tracking-and-multiplexing/lib.cpp | 1 + .../testdata/changed-files/changed-files.qbs | 29 + .../blackbox/testdata/changed-files/file1.cpp | 29 + .../blackbox/testdata/changed-files/file2.cpp | 29 + .../blackbox/testdata/changed-files/main.cpp | 29 + .../changed-inputs-from-dependencies.qbs | 58 + .../input.txt | 0 .../changed-rule-inputs.qbs | 40 + .../check-timestamps/check-timestamps.qbs | 8 + .../testdata/check-timestamps/file.cpp | 3 + .../blackbox/testdata/check-timestamps/file.h | 1 + .../testdata/check-timestamps/main.cpp | 1 + .../choose-module-instance.qbs | 17 + .../choose-module-instance/gerbil.txt.in | 5 + .../modules/limerick/lord.qbs | 5 + .../modules/limerick/ritchie.qbs | 7 + .../modules/limerick/generic.qbs | 3 + tests/auto/blackbox/testdata/clean/clean.qbs | 19 + tests/auto/blackbox/testdata/clean/dep.cpp | 31 + tests/auto/blackbox/testdata/clean/main.cpp | 29 + .../auto/blackbox/testdata/cli/HelloWorld.cs | 12 + tests/auto/blackbox/testdata/cli/Libby.cs | 10 + tests/auto/blackbox/testdata/cli/Libby2.cs | 10 + tests/auto/blackbox/testdata/cli/Module.cs | 10 + tests/auto/blackbox/testdata/cli/Module.vb | 7 + .../auto/blackbox/testdata/cli/dotnettest.qbs | 49 + tests/auto/blackbox/testdata/cli/fshello.fs | 1 + tests/auto/blackbox/testdata/cli/fshello.qbs | 6 + .../testdata/combined-sources/combinable.cpp | 1 + .../combined-sources/combined-sources.qbs | 11 + .../testdata/combined-sources/main.cpp | 1 + .../combined-sources/uncombinable.cpp | 1 + .../testdata/command-file/command-file.qbs | 19 + .../blackbox/testdata/command-file/lib.cpp | 1 + .../blackbox/testdata/command-file/list.gcc | 1 + .../blackbox/testdata/command-file/list.msvc | 1 + .../blackbox/testdata/command-file/main.cpp | 6 + .../CppDefinesApp.qbs | 22 + .../testdata/compilerDefinesByLanguage/app.c | 4 + .../compilerDefinesByLanguage.qbs | 169 + .../testdata/compilerDefinesByLanguage/test.c | 0 .../compilerDefinesByLanguage/test.cpp | 0 .../testdata/compilerDefinesByLanguage/test.m | 0 .../compilerDefinesByLanguage/test.mm | 0 .../testapp/conanfile-probe-project.qbs | 22 + .../conanfile-probe/testapp/conanfile.py | 25 + .../conanfile-probe/testlib/conanfile.py | 25 + .../concurrent-executor.qbs | 67 + .../testdata/concurrent-executor/dummy1.input | 0 .../testdata/concurrent-executor/dummy2.input | 0 .../testdata/concurrent-executor/util.js | 8 + .../conditional-export/conditional-export.qbs | 16 + .../testdata/conditional-export/main.cpp | 33 + .../conditional-filetagger.qbs | 10 + .../conditional-filetagger/main.custom | 1 + .../blackbox/testdata/configure/configure.qbs | 20 + .../auto/blackbox/testdata/configure/main.cpp | 36 + .../configure/modules/definition/module.qbs | 34 + .../conflicting-artifacts.qbs | 14 + .../testdata/conflicting-artifacts/main.cpp | 29 + .../testdata/cpu-features/cpu-features.qbs | 14 + .../blackbox/testdata/cpu-features/main.cpp | 1 + .../cxx-language-version.qbs | 35 + .../testdata/cxx-language-version/main.cpp | 1 + .../dependenciesProperty.qbs | 42 + .../dependenciesProperty/product2.cpp | 29 + .../dependency-scanning-loop.qbs | 34 + .../dependency-scanning-loop/main.cpp | 1 + .../deprecated-property.qbs | 8 + .../modules/themodule/m.qbs | 27 + .../disappeared-profile.qbs | 12 + .../testdata/disappeared-profile/in1.txt | 0 .../testdata/disappeared-profile/in2.txt | 0 .../modules-dir/modules/m/m.qbs | 32 + .../discard-unused-data.qbs | 27 + .../testdata/discard-unused-data/main.cpp | 3 + .../distribution-include-paths.qbs | 4 + .../distribution-include-paths/main.cpp | 8 + .../subdir/gagagugu.h | 4 + .../driver-linker-flags.qbs | 17 + .../testdata/driver-linker-flags/main.cpp | 1 + .../dynamic-library-in-module/Dll.qbs | 15 + .../dynamic-library-in-module/lib1.cpp | 7 + .../dynamic-library-in-module/lib2.cpp | 7 + .../dynamic-library-in-module/lib3.cpp | 7 + .../dynamic-library-in-module/lib4.cpp | 8 + .../dynamic-library-in-module/lib5.cpp | 1 + .../dynamic-library-in-module/main.cpp | 13 + .../modules/thelib/broken.cpp | 1 + .../modules/thelib/thelib.qbs | 27 + .../modules/theotherlib/theotherlib.qbs | 21 + .../modules/thethirdlib/thethirdlib.qbs | 21 + .../dynamic-library-in-module/theapp.qbs | 37 + .../dynamic-library-in-module/thelibs.qbs | 14 + .../dynamic-project/dynamic-project.qbs | 42 + .../testdata/dynamic-project/src/app/main.cpp | 1 + .../dynamic-project/src/app2/main.cpp | 1 + .../dynamicMultiplexRule.qbs | 32 + .../testdata/dynamicMultiplexRule/one.txt | 0 .../testdata/dynamicMultiplexRule/three.txt | 0 .../testdata/dynamicMultiplexRule/two.txt | 0 .../dynamicRuleOutputs/after/numbers.l | 81 + .../before/flexoptionsreader.js | 103 + .../dynamicRuleOutputs/before/genlexer.qbs | 114 + .../dynamicRuleOutputs/before/numbers.l | 82 + .../testdata/empty-profile/empty-profile.qbs | 3 + .../blackbox/testdata/empty-profile/main.cpp | 1 + .../testdata/enableExceptions/empty.m | 1 + .../testdata/enableExceptions/empty.mm | 1 + .../testdata/enableExceptions/emptymain.cpp | 29 + .../enableExceptions/exceptions-objc.qbs | 4 + .../exceptions-objcpp-cpp.qbs | 6 + .../enableExceptions/exceptions-objcpp.qbs | 7 + .../testdata/enableExceptions/exceptions.qbs | 5 + .../testdata/enableExceptions/main.cpp | 40 + .../blackbox/testdata/enableExceptions/main.m | 5 + .../testdata/enableExceptions/none.qbs | 8 + .../blackbox/testdata/enableRtti/main.cpp | 59 + .../blackbox/testdata/enableRtti/rtti.qbs | 11 + .../testdata/env-merging/env-merging.qbs | 29 + .../auto/blackbox/testdata/env-merging/main.c | 8 + .../env-normalization/env-normalization.qbs | 11 + .../nonexistentWorkingDir.qbs | 18 + .../outputArtifacts-missing-filePath/main.cpp | 1 + .../outputArtifacts-missing-filePath.qbs | 16 + .../outputArtifacts-missing-fileTags/main.cpp | 1 + .../outputArtifacts-missing-fileTags.qbs | 16 + .../erroneous/tag-mismatch/tag-mismatch.qbs | 35 + .../boom.txt.in | 1 + .../texttemplate-unknown-placeholder.qbs | 6 + .../testdata/error-info/error-info.qbs | 71 + .../blackbox/testdata/error-info/helper.js | 9 + .../escaped-linker-flags.qbs | 13 + .../testdata/escaped-linker-flags/main.cpp | 1 + .../explicitly-depends-on.qbs | 123 + .../modules/module1/module-fish.txt | 1 + .../modules/module1/module1.qbs | 7 + .../testdata/explicitly-depends-on/step1.txt | 1 + .../blackbox/testdata/export-rule/blubber.cpp | 29 + .../testdata/export-rule/export-rule.qbs | 39 + .../blackbox/testdata/export-rule/myapp.blubb | 8 + .../export-to-outside-searchpath.qbs | 19 + .../qbs-resources/modules/aModule/aModule.qbs | 2 + ...xported-dependency-in-disabled-product.qbs | 17 + .../main.cpp | 1 + .../modules/broken/broken.qbs | 3 + .../exported-property-in-disabled-product.qbs | 18 + .../main.cpp | 1 + .../modules/broken/broken.qbs | 3 + .../testdata/exports-pkgconfig/TheFirstLib.pc | 10 + .../exports-pkgconfig/TheFirstLib_windows.pc | 10 + .../exports-pkgconfig/TheSecondLib.pc | 9 + .../exports-pkgconfig/boringstaticlib.cpp | 1 + .../exports-pkgconfig/exports-pkgconfig.qbs | 128 + .../testdata/exports-pkgconfig/firstlib.cpp | 3 + .../testdata/exports-pkgconfig/firstlib.h | 9 + .../modules/helper1/helper1.qbs | 5 + .../modules/helper2/helper2.qbs | 4 + .../modules/helper3/helper3.qbs | 4 + .../testdata/exports-pkgconfig/secondlib.cpp | 7 + .../testdata/exports-pkgconfig/secondlib.h | 9 + .../testdata/exports-qbs/consumer.cpp | 6 + .../testdata/exports-qbs/consumer.qbs | 27 + .../exports-qbs/exports-qbs-products.qbs | 0 .../testdata/exports-qbs/exports-qbs.qbs | 16 + .../testdata/exports-qbs/helper.cpp.in | 14 + .../blackbox/testdata/exports-qbs/helper.js | 1 + .../exports-qbs/imports/Helper2/helper2.js | 1 + .../blackbox/testdata/exports-qbs/lib.qbs | 59 + .../blackbox/testdata/exports-qbs/mylib.cpp | 5 + .../blackbox/testdata/exports-qbs/mylib.h | 17 + .../blackbox/testdata/exports-qbs/tool.cpp | 18 + .../blackbox/testdata/exports-qbs/tool.qbs | 57 + .../testdata/external-libs/external-libs.qbs | 33 + .../blackbox/testdata/external-libs/lib1.cpp | 1 + .../blackbox/testdata/external-libs/lib2.cpp | 3 + .../blackbox/testdata/external-libs/main.cpp | 6 + .../fallback-module-provider.qbs | 8 + .../libdir/qbsmetatestmodule.pc | 5 + .../fallback-module-provider/main.cpp | 5 + .../fileDependencies/awesomelib/awesome.h | 36 + .../fileDependencies/awesomelib/magnificent.h | 34 + .../fileDependencies/fileDependencies.qbs | 15 + .../testdata/fileDependencies/src/narf.cpp | 35 + .../testdata/fileDependencies/src/narf.h | 30 + .../testdata/fileDependencies/src/zort.cpp | 36 + .../filetagsfilter-merging/MyApplication.qbs | 9 + .../filetagsfilter-merging.qbs | 28 + .../testdata/filetagsfilter-merging/main.cpp | 1 + .../auto/blackbox/testdata/find/find-cli.qbs | 33 + .../testdata/freedesktop/freedesktop.qbs | 25 + .../blackbox/testdata/freedesktop/main.cpp | 4 + .../testdata/freedesktop/myapp.appdata.xml | 15 + .../testdata/freedesktop/myapp.desktop | 4 + .../blackbox/testdata/freedesktop/myapp.png | 0 .../generate-linker-map-file.qbs | 28 + .../generate-linker-map-file/main.cpp | 29 + .../input.txt | 1 + .../p.qbs | 54 + .../blackbox/testdata/generator/generator.qbs | 35 + .../testdata/generator/input.both.txt | 2 + .../testdata/generator/input.file1.txt | 1 + .../testdata/generator/input.file2.txt | 1 + .../testdata/generator/input.none.txt | 0 .../auto/blackbox/testdata/generator/main.cpp | 8 + .../group-condition-change.qbs | 25 + .../group-condition-change/input_kaputt.txt | 0 .../groups-in-modules/groups-in-modules.qbs | 28 + .../modules/helper/chunk.coal | 1 + .../modules/helper/diamondc.c | 54 + .../modules/helper/helper.qbs | 46 + .../modules/helper2/helper2.c | 29 + .../modules/helper2/helper2.qbs | 14 + .../modules/helper3/helper3.c | 29 + .../modules/helper3/helper3.qbs | 10 + .../modules/helper4/helper4.c | 29 + .../modules/helper4/helper4.qbs | 9 + .../modules/helper5/helper5.c | 33 + .../modules/helper5/helper5.qbs | 13 + .../modules/helper6/helper6.c | 29 + .../modules/helper6/helper6.qbs | 10 + .../testdata/groups-in-modules/rock.coal | 1 + .../groups-in-modules/someotherfile.txt | 0 .../groups-in-modules/someotherfile2.txt | 0 tests/auto/blackbox/testdata/grpc/grpc.cpp | 49 + tests/auto/blackbox/testdata/grpc/grpc.proto | 15 + .../auto/blackbox/testdata/grpc/grpc_cpp.qbs | 32 + .../host-os-properties/host-os-properties.qbs | 14 + .../testdata/host-os-properties/main.cpp | 7 + .../testdata/ico/dmg.iconset/icon_128x128.png | Bin 0 -> 5256 bytes .../testdata/ico/dmg.iconset/icon_16x16.png | Bin 0 -> 487 bytes .../testdata/ico/dmg.iconset/icon_256x256.png | Bin 0 -> 11304 bytes .../testdata/ico/dmg.iconset/icon_32x32.png | Bin 0 -> 1374 bytes .../testdata/ico/dmg.iconset/icon_512x512.png | Bin 0 -> 26984 bytes tests/auto/blackbox/testdata/ico/ico.qbs | 86 + .../auto/blackbox/testdata/ico/icon_16x16.png | Bin 0 -> 649 bytes .../auto/blackbox/testdata/ico/icon_32x32.png | Bin 0 -> 665 bytes .../import-assignment/import-assignment.qbs | 22 + .../imports/MyImport/myimport.js | 2 + .../import-change-tracking/custom1command.js | 3 + .../import-change-tracking/custom1prepare1.js | 4 + .../import-change-tracking/custom1prepare2.js | 10 + .../custom2prepare/custom2prepare1.js | 0 .../custom2prepare/custom2prepare2.js | 9 + .../import-change-tracking-product.qbs | 57 + .../import-change-tracking.qbs | 4 + .../imports/custom2command/custom2command1.js | 1 + .../imports/custom2command/custom2command2.js | 3 + .../import-change-tracking/input1.txt | 0 .../import-change-tracking/input2.txt | 0 .../import-change-tracking/irrelevant.js | 1 + .../testdata/import-change-tracking/probe1.js | 3 + .../testdata/import-change-tracking/probe2.js | 1 + .../import-in-properties-condition.qbs | 3 + .../modules/amodule/m.qbs | 9 + .../modules/depmodule/m.qbs | 3 + .../import-searchpath/import-searchpath.qbs | 9 + .../qbs/imports/CppApplication.qbs | 2 + .../src/import-searchpath-app1.qbs | 3 + .../src/import-searchpath-app2.qbs | 3 + .../testdata/import-searchpath/src/main.cpp | 1 + .../import-searchpath/src/somefile.cpp | 0 .../testdata/importing-product/header.h.in | 1 + .../importing-product/importing-product.qbs | 43 + .../testdata/importing-product/main.cpp | 29 + .../imports-conflict/imports-conflict.qbs | 9 + .../imports-conflict/modules/themodule/m.qbs | 5 + .../modules/themodule/utils.js | 1 + .../testdata/includeLookup/includeLookup.qbs | 20 + .../blackbox/testdata/includeLookup/main.cpp | 36 + .../modules/definition/fakeopenssl/sha.h | 0 .../modules/definition/module.qbs | 13 + .../testdata/innosetup/inc/qbsinc.iss | 0 .../blackbox/testdata/innosetup/innosetup.qbs | 22 + .../auto/blackbox/testdata/innosetup/test.iss | 6 + .../innosetupDependencies.qbs | 72 + .../testdata/innosetupDependencies/main.c | 1 + .../testdata/innosetupDependencies/test.iss | 8 + .../input-tags-change-tracking.qbs | 63 + .../input-tags-change-tracking/input.txt | 1 + .../inputs-from-dependencies/file1.txt | 0 .../inputs-from-dependencies/file2.txt | 0 .../inputs-from-dependencies/file3.txt | 0 .../inputs-from-dependencies/file4.txt | 0 .../inputs-from-dependencies.qbs | 49 + .../install-duplicates-no-error/file1.txt | 0 .../install-duplicates-no-error/file2.txt | 0 .../install-duplicates-no-error/file3.txt | 0 .../install-duplicates-no-error.qbs | 35 + .../install-duplicates/dir1/file1.txt | 1 + .../install-duplicates/dir1/file2.txt | 1 + .../install-duplicates/dir2/file1.txt | 1 + .../install-duplicates/dir2/file2.txt | 1 + .../install-duplicates/dir2/file3.txt | 1 + .../install-duplicates/install-duplicates.qbs | 8 + .../install-locations/install-locations.qbs | 44 + .../testdata/install-locations/main.cpp | 1 + .../testdata/install-locations/thelib.cpp | 3 + .../testdata/install-locations/theplugin.cpp | 3 + .../install-root-from-project-file/file.txt | 0 .../install-root-from-project-file.qbs | 11 + .../testdata/install-tree/data/foo.txt | 0 .../install-tree/data/subdir1/bar.txt | 0 .../install-tree/data/subdir2/baz.txt | 0 .../testdata/install-tree/install-tree.qbs | 10 + .../blackbox/testdata/install-tree/main.cpp | 29 + .../installable-as-auxiliary-input.qbs | 84 + .../testdata/installable/installable.qbs | 41 + .../blackbox/testdata/installable/main.cpp | 29 + .../installed-source-files.qbs | 19 + .../testdata/installed-source-files/main.cpp | 29 + .../installed-source-files/readme.txt | 1 + .../installed-transformer-output/qbs668.qbs | 31 + .../installed_artifact/installed_artifact.qbs | 17 + .../testdata/installed_artifact/main.cpp | 29 + .../installpackage/installpackage.qbs | 49 + .../blackbox/testdata/installpackage/lib.cpp | 31 + .../blackbox/testdata/installpackage/lib.h | 37 + .../blackbox/testdata/installpackage/main.cpp | 34 + .../invalid-command-property/input.txt | 0 .../invalid-command-property.qbs | 32 + .../invalid-extension-instantiation.qbs | 26 + .../invalid-install-dir.qbs | 7 + .../testdata/invalid-install-dir/main.cpp | 1 + .../invalid-library-names.qbs | 10 + .../testdata/invalid-library-names/main.cpp | 29 + .../jsextensions-binaryfile/binaryfile.qbs | 44 + .../testdata/jsextensions-file/file.qbs | 52 + .../jsextensions-fileinfo/fileinfo.qbs | 47 + .../testdata/jsextensions-process/main.cpp | 20 + .../testdata/jsextensions-process/process.qbs | 96 + .../propertylist.qbs | 129 + .../jsextensions-temporarydir.qbs | 27 + .../jsextensions-textfile/textfile.qbs | 43 + .../last-module-candidate-broken.qbs | 5 + .../last-module-candidate-broken/main.cpp | 1 + .../qbs/modules/Foo/Foo1.qbs | 3 + .../qbs/modules/Foo/Foo2.qbs | 2 + tests/auto/blackbox/testdata/ld/coreutils.cpp | 34 + tests/auto/blackbox/testdata/ld/coreutils.h | 31 + tests/auto/blackbox/testdata/ld/ld.qbs | 23 + tests/auto/blackbox/testdata/ld/main.cpp | 36 + .../lexyacc/lex_outfile/lex_outfile.qbs | 16 + .../testdata/lexyacc/lex_outfile/lexer.l | 21 + .../testdata/lexyacc/lex_outfile/parser.y | 30 + .../testdata/lexyacc/lex_outfile/types.h | 50 + .../lexyacc/lex_prefix/lex_prefix.qbs | 16 + .../testdata/lexyacc/lex_prefix/lexer.l | 21 + .../testdata/lexyacc/lex_prefix/parser.y | 31 + .../testdata/lexyacc/lex_prefix/types.h | 50 + .../modules/bisonhelper/bisonhelper.qbs | 19 + .../testdata/lexyacc/one-grammar/lexer.l | 21 + .../lexyacc/one-grammar/one-grammar.qbs | 39 + .../testdata/lexyacc/one-grammar/parser.y | 30 + .../testdata/lexyacc/one-grammar/types.h | 50 + .../testdata/lexyacc/two-grammars/g1.l | 15 + .../testdata/lexyacc/two-grammars/g1.y | 7 + .../testdata/lexyacc/two-grammars/g2.l | 10 + .../testdata/lexyacc/two-grammars/g2.y | 7 + .../testdata/lexyacc/two-grammars/main.c | 37 + .../lexyacc/two-grammars/two-grammars.qbs | 12 + tests/auto/blackbox/testdata/lexyacc/unistd.h | 11 + .../testdata/lexyacc/yacc_output/lexer.l | 21 + .../testdata/lexyacc/yacc_output/parser.y | 31 + .../testdata/lexyacc/yacc_output/types.h | 50 + .../lexyacc/yacc_output/yacc_output.qbs | 14 + .../linker-library-duplicates/lib1.cpp | 3 + .../linker-library-duplicates/lib2.cpp | 3 + .../linker-library-duplicates/lib3.cpp | 3 + .../linker-library-duplicates/main.cpp | 13 + .../setup-run-environment.qbs | 48 + .../linker-module-definition.qbs | 20 + .../linker-module-definition/testapp.cpp | 39 + .../linker-module-definition/testlib.cpp | 41 + .../linker-module-definition/testlib.def | 3 + .../linker-variant/linker-variant.qbs | 20 + .../blackbox/testdata/linker-variant/main.cpp | 1 + .../testdata/linkerMode/linkerMode.qbs | 80 + .../auto/blackbox/testdata/linkerMode/main.c | 36 + .../blackbox/testdata/linkerMode/main.cpp | 36 + .../auto/blackbox/testdata/linkerMode/main.m | 7 + .../auto/blackbox/testdata/linkerMode/main.mm | 10 + .../auto/blackbox/testdata/linkerMode/main.s | 5 + .../testdata/linkerMode/staticlib.cpp | 37 + .../blackbox/testdata/linkerMode/staticmain.c | 34 + .../testdata/linkerscripts/linkerscript1 | 1 + .../testdata/linkerscripts/linkerscript2 | 1 + .../linkerscripts/linkerscript_recursive | 1 + .../linkerscripts/linkerscript_to_include | 2 + .../testdata/linkerscripts/linkerscripts.qbs | 61 + .../scripts/linkerscript_in_directory | 1 + .../blackbox/testdata/linkerscripts/testlib.c | 29 + .../testdata/list-products/list-products.qbs | 14 + .../list-properties-with-outer/dummy.txt | 0 .../list-properties-with-outer.qbs | 10 + .../modules/higher/higher.qbs | 4 + .../modules/lower/lower.qbs | 16 + .../testdata/list-property-order/dummy.txt | 0 .../modules/higher1/higher1.qbs | 4 + .../modules/higher2/higher2.qbs | 4 + .../modules/higher3/higher3.qbs | 4 + .../modules/lower/lower.qbs | 19 + .../testdata/list-property-order/product.qbs | 12 + .../testdata/loadablemodule/exported.cpp | 35 + .../testdata/loadablemodule/exported.h | 33 + .../loadablemodule/loadablemodule.qbs | 43 + .../blackbox/testdata/loadablemodule/main.cpp | 87 + .../localDeployment/localDeployment.qbs | 30 + .../testdata/localDeployment/main.cpp | 54 + .../testdata/makefile-generator/app.qbs | 22 + .../testdata/makefile-generator/main.cpp | 6 + .../maximum-c-language-version/main.c | 1 + .../maximum-c-language-version.qbs | 20 + .../modules/newermodule/newermodule.qbs | 4 + .../modules/newestmodule/newestmodule.qbs | 4 + .../modules/oldmodule/oldmodule.qbs | 4 + .../maximum-cxx-language-version/main.cpp | 1 + .../maximum-cxx-language-version.qbs | 10 + .../modules/newermodule/newermodule.qbs | 4 + .../modules/newestmodule/newestmodule.qbs | 4 + .../modules/oldmodule/oldmodule.qbs | 4 + .../minimumSystemVersion/fakewindows.qbs | 15 + .../minimumSystemVersion/macappstore.qbs | 15 + .../testdata/minimumSystemVersion/main.cpp | 103 + .../testdata/minimumSystemVersion/main.mm | 73 + .../minimumSystemVersion/specific.qbs | 30 + .../unspecified-forced.qbs | 28 + .../minimumSystemVersion/unspecified.qbs | 23 + .../testdata/missing-dependency/main.cpp | 33 + .../missing-dependency/missing-dependency.qbs | 32 + .../missing-override-prefix.qbs | 1 + .../missing-project-file/ambiguous-dir/p1.qbs | 0 .../missing-project-file/ambiguous-dir/p2.qbs | 0 .../missing-project-file/ambiguous-dir/p3.qbs | 0 .../empty-dir/irrelevant.txt | 0 .../missing-project-file/project-dir/file.cpp | 1 + .../missing-project-file/project-dir/main.cpp | 1 + .../project-dir/missing-project-file.qbs | 6 + .../module-conditions/module-conditions.qbs | 21 + .../module-conditions/modules/m/m1.qbs | 6 + .../module-conditions/modules/m/m2.qbs | 6 + .../module-conditions/modules/m/m3.qbs | 6 + .../module-conditions/modules/m/m4.qbs | 6 + .../testdata/module-providers/main.cpp | 8 + .../module-providers/module-providers.qbs | 32 + .../module-providers/mygenerator/provider.qbs | 31 + .../othergenerator/provider.qbs | 19 + .../testdata/moved-file-dependency/main.cpp | 3 + .../moved-file-dependency.qbs | 4 + .../moved-file-dependency/subdir1/theheader.h | 0 .../testdata/multiple-changes/dummy.txt | 0 .../multiple-changes/multiple-changes.qbs | 31 + .../testdata/multiple-configurations/file.cpp | 6 + .../testdata/multiple-configurations/file.h | 1 + .../testdata/multiple-configurations/lib.cpp | 1 + .../testdata/multiple-configurations/lib.h | 1 + .../testdata/multiple-configurations/main.cpp | 6 + .../multiple-configurations.qbs | 13 + .../multiplexed-tool/multiplexed-tool.qbs | 56 + .../testdata/multiplexed-tool/tool.cpp | 8 + .../blackbox/testdata/nested-groups/file3.cpp | 33 + .../blackbox/testdata/nested-groups/file3.h | 29 + .../blackbox/testdata/nested-groups/main.cpp | 40 + .../modules/themodule/themodule.qbs | 9 + .../testdata/nested-groups/nested-groups.qbs | 40 + .../testdata/nested-groups/subdir/file1.cpp | 37 + .../testdata/nested-groups/subdir/file1.h | 29 + .../testdata/nested-groups/subdir/file2.cpp | 37 + .../testdata/nested-groups/subdir/file2.h | 29 + .../testdata/nested-groups/subdir/main2.cpp | 29 + .../testdata/nested-groups/subdir/main3.cpp | 29 + .../testdata/nested-groups/subdir/other.cpp | 37 + .../testdata/nested-groups/subdir/other.h | 29 + .../testdata/nested-properties/dummy.txt | 0 .../modules/higherlevel/higher-level.qbs | 4 + .../modules/lowerlevel/lower-level.qbs | 17 + .../testdata/nested-properties/product.qbs | 20 + .../testdata/new-output-artifact/input.txt | 0 .../new-output-artifact.qbs | 37 + .../testdata/no-exported-symbols/lib.cpp | 1 + .../testdata/no-exported-symbols/lib.h | 6 + .../testdata/no-exported-symbols/main.cpp | 6 + .../no-exported-symbols.qbs | 28 + .../testdata/no-profile/no-profile.qbs | 3 + .../no-such-profile/no-such-profile.qbs | 6 + tests/auto/blackbox/testdata/nodejs/hello.js | 3 + tests/auto/blackbox/testdata/nodejs/hello.qbs | 10 + .../broken.cpp | 1 + .../fine.cpp | 29 + .../non-broken-files-in-broken-product.qbs | 4 + .../testdata/non-default-product/main.cpp | 29 + .../non-default-product.qbs | 15 + .../not-always-updated/not-always-updated.qbs | 31 + tests/auto/blackbox/testdata/nsis/hello.bat | 1 + tests/auto/blackbox/testdata/nsis/hello.nsi | 19 + tests/auto/blackbox/testdata/nsis/hello.qbs | 8 + .../testdata/nsisDependencies/hello.nsi | 8 + .../blackbox/testdata/nsisDependencies/main.c | 1 + .../nsisDependencies/nsisDependencies.qbs | 67 + .../testdata/out-of-date-marking/main.c | 3 + .../out-of-date-marking.qbs | 21 + .../broken.cpp.in | 1 + .../output-artifact-auto-tagging/main.cpp.in | 1 + .../output-artifact-auto-tagging.qbs | 30 + .../testdata/output-redirection/input.bin | Bin 0 -> 1024 bytes .../testdata/output-redirection/input.txt | 1 + .../output-redirection/output-redirection.qbs | 38 + .../testdata/output-redirection/output.bin | Bin 0 -> 2048 bytes .../testdata/output-redirection/output.txt | 2 + .../overrideProjectProperties/helper_lib.qbs | 9 + .../overrideProjectProperties/helperlib.cpp | 32 + .../overrideProjectProperties/main.cpp | 32 + .../overrideProjectProperties/main2.cpp | 38 + .../overrideProjectProperties.qbs | 33 + .../project_using_helper_lib.qbs | 12 + .../blackbox/testdata/path-probe/BaseApp.qbs | 134 + .../testdata/path-probe/bin/super-tool.1 | 0 .../blackbox/testdata/path-probe/bin/tool | 0 .../blackbox/testdata/path-probe/bin/tool.1 | 0 .../blackbox/testdata/path-probe/bin/tool.2 | 0 .../blackbox/testdata/path-probe/bin/tool.3 | 0 .../blackbox/testdata/path-probe/bin/tool.4 | 0 .../testdata/path-probe/candidate-filter.qbs | 13 + .../testdata/path-probe/environment-paths.qbs | 10 + .../blackbox/testdata/path-probe/main.cpp | 1 + .../path-probe/mult-files-common-suffixes.qbs | 10 + .../path-probe/mult-files-mult-suffixes.qbs | 9 + .../path-probe/mult-files-mult-variants.qbs | 10 + .../path-probe/mult-files-suffixes.qbs | 9 + .../testdata/path-probe/mult-files.qbs | 11 + .../testdata/path-probe/name-filter.qbs | 11 + .../path-probe/non-existent-selector.qbs | 9 + .../testdata/path-probe/non-existent.qbs | 5 + .../path-probe/single-file-mult-variants.qbs | 6 + .../path-probe/single-file-selector-array.qbs | 6 + .../path-probe/single-file-selector.qbs | 6 + .../path-probe/single-file-suffixes.qbs | 7 + .../testdata/path-probe/single-file.qbs | 6 + .../blackbox/testdata/path-probe/usr/bin/tool | 0 .../testdata/pch-change-tracking/header1.h | 35 + .../testdata/pch-change-tracking/header2.cpp | 8 + .../testdata/pch-change-tracking/header2.h | 30 + .../testdata/pch-change-tracking/main.cpp | 36 + .../pch-change-tracking.qbs | 12 + .../testdata/pch-change-tracking/pch.h | 29 + .../per-group-define-in-export-item/main.cpp | 3 + .../per-group-define-in-export-item.qbs | 16 + .../modules/themodule/themodule.qbs | 23 + .../pkg-config-probe-sysroot/pkg-config.qbs | 27 + .../sysroot1/usr/share/pkgconfig/dummy.pc | 7 + .../sysroot2/usr/share/pkgconfig/dummy.pc | 7 + .../pkg-config-probe/dummy1/dummy1.pc | 7 + .../pkg-config-probe/dummy2/dummy2.pc | 7 + .../modules/themodule/themodule.qbs | 34 + .../testdata/pkg-config-probe/pkg-config.qbs | 21 + .../testdata/plugin-dependency/helper1.cpp | 16 + .../testdata/plugin-dependency/helper2.cpp | 7 + .../testdata/plugin-dependency/main.cpp | 13 + .../plugin-dependency/plugin-dependency.qbs | 73 + .../testdata/plugin-dependency/plugin1.cpp | 7 + .../testdata/plugin-dependency/plugin2.cpp | 7 + .../testdata/plugin-dependency/plugin3.cpp | 7 + .../testdata/plugin-dependency/plugin4.cpp | 7 + .../precompiled-and-prefix-headers/main.cpp | 31 + .../precompiled-and-prefix-headers/pch.h | 28 + .../precompiled-and-prefix-headers.qbs | 11 + .../precompiled-and-prefix-headers/prefix.h | 28 + .../precompiled-headers-and-redefine/file.cpp | 1 + .../precompiled-headers-and-redefine/main.cpp | 33 + .../precompiled-headers-and-redefine/pch.h | 30 + .../precompiled-headers-and-redefine.qbs | 16 + .../prevent-floating-point-values.qbs | 4 + .../probe-change-tracking.qbs | 41 + .../probe-in-exported-module/dependee.qbs | 14 + .../probe-in-exported-module/dependency.qbs | 6 + .../modules/depmodule/depmodule.qbs | 18 + .../modules/mymodule/mymodule.qbs | 23 + .../modules/myothermodule/myothermodule.qbs | 4 + .../probe-in-exported-module.qbs | 3 + .../testdata/probe-in-exported-module/test.in | 0 .../probe-in-exported-module/test2.in | 0 .../testdata/probeProperties/bin/tool | 0 .../blackbox/testdata/probeProperties/main.c | 29 + .../probeProperties/probeProperties.qbs | 40 + .../modules/mymodule/mymodule.qbs | 26 + .../probes-and-array-properties.qbs | 6 + .../probes-and-shadow-products.qbs | 11 + .../modules/inner/inner.qbs | 19 + .../modules/outer/outer.qbs | 17 + .../probes-in-nested-modules.qbs | 36 + .../product-dependencies-by-type/main.cpp | 29 + .../modules/myconfig/myconfig.qbs | 3 + .../product-dependencies-by-type.qbs | 139 + .../modules/m/m.qbs | 3 + .../product-in-exported-module.qbs | 10 + .../testdata/productproperties/app.qbs | 10 + .../productproperties/blubb_header.h.in | 0 .../testdata/productproperties/header.qbs | 34 + .../testdata/productproperties/main.cpp | 36 + .../productproperties/productproperties.qbs | 4 + .../testdata/project_filepath_check/main.cpp | 29 + .../testdata/project_filepath_check/main2.cpp | 1 + .../project_filepath_check/project1.qbs | 3 + .../project_filepath_check/project2.qbs | 3 + .../blackbox/testdata/proper quoting/main.cpp | 41 + .../proper quoting/my static lib helper.cpp | 33 + .../testdata/proper quoting/my static lib.cpp | 37 + .../proper quoting/proper quoting.qbs | 46 + .../some helper/some helper.cpp | 35 + .../proper quoting/some helper/some helper.h | 35 + .../properties-in-export-items/main1.cpp | 31 + .../properties-in-export-items/main2.cpp | 31 + .../properties-in-export-items.qbs | 31 + .../main.cpp | 1 + .../modules/m/m.qbs | 9 + .../property-assignment-in-failed-module.qbs | 5 + ...perty-assignment-on-non-present-module.qbs | 4 + .../modules/base/base.qbs | 4 + .../modules/top/top.qbs | 6 + .../property-evaluation-context.qbs | 34 + .../testdata/property-precedence/dep.qbs | 9 + .../testdata/property-precedence/dummy.txt | 0 .../property-precedence/modules/leaf/leaf.qbs | 18 + .../modules/nonleaf/nonleaf.qbs | 6 + .../property-precedence.qbs | 16 + .../blackbox/testdata/propertyChanges/lib.cpp | 31 + .../modules/TestModule/module.qbs | 33 + .../propertyChanges/propertyChanges.qbs | 93 + .../testdata/propertyChanges/ruletest.qbs | 9 + .../testdata/propertyChanges/source1.cpp | 29 + .../testdata/propertyChanges/source2.cpp | 30 + .../testdata/propertyChanges/source3.cpp | 30 + .../blackbox/testdata/propertyChanges/test.in | 1 + .../testdata/protobuf/addressbook.proto | 51 + .../testdata/protobuf/addressbook_cpp.qbs | 27 + .../testdata/protobuf/addressbook_objc.qbs | 24 + .../testdata/protobuf/import-main.cpp | 38 + .../blackbox/testdata/protobuf/import.proto | 6 + .../blackbox/testdata/protobuf/import.qbs | 29 + .../auto/blackbox/testdata/protobuf/main.cpp | 58 + tests/auto/blackbox/testdata/protobuf/main.m | 50 + .../protobuf/needs-import-dir-main.cpp | 38 + .../testdata/protobuf/needs-import-dir.proto | 6 + .../testdata/protobuf/needs-import-dir.qbs | 30 + .../testdata/protobuf/subdir/myenum.proto | 6 + .../pseudo-multiplexing.qbs | 12 + .../blackbox/testdata/qbs-session/file1.cpp | 1 + .../blackbox/testdata/qbs-session/file2.cpp | 1 + .../blackbox/testdata/qbs-session/lib.cpp | 1 + .../auto/blackbox/testdata/qbs-session/lib.h | 1 + .../blackbox/testdata/qbs-session/main.cpp | 4 + .../qbs-session/modules/mymodule/mymodule.qbs | 5 + .../testdata/qbs-session/qbs-session.qbs | 25 + .../testdata/qbsVersion/qbs-version.qbs | 20 + .../rad-after-incomplete-build/dummy.txt | 0 .../project_with_rule.qbs | 25 + .../recursive_renaming/dir/subdir/blubb.txt | 0 .../recursive_renaming/dir/wasser.txt | 0 .../recursive_renaming/recursive_renaming.qbs | 8 + .../recursive_wildcards/dir/file1.txt | 0 .../recursive_wildcards/dir/subdir/file2.txt | 0 .../recursive_wildcards.qbs | 43 + .../testdata/referenceErrorInExport/main.c | 29 + .../referenceErrorInExport.qbs | 18 + .../remove-duplicate-libs/MyStaticLib.qbs | 10 + .../testdata/remove-duplicate-libs/main.c | 9 + .../testdata/remove-duplicate-libs/provider.c | 1 + .../remove-duplicate-libs/provider2.c | 1 + .../remove-duplicate-libs.qbs | 26 + .../remove-duplicate-libs/requestor1.c | 3 + .../remove-duplicate-libs/requestor2.c | 3 + .../testdata/renameDependency/after/lib2.cpp | 35 + .../testdata/renameDependency/after/lib2.h | 29 + .../testdata/renameDependency/before/lib.cpp | 35 + .../testdata/renameDependency/before/lib.h | 29 + .../testdata/renameDependency/before/main.cpp | 36 + .../before/renameDependency.qbs | 3 + .../testdata/reproducible-build/file1.cpp | 31 + .../testdata/reproducible-build/file2.cpp | 31 + .../testdata/reproducible-build/main.cpp | 36 + .../reproducible-build/reproducible-build.qbs | 5 + .../testdata/require-deprecated/blubb.js | 13 + .../testdata/require-deprecated/require.qbs | 21 + .../testdata/require-deprecated/zort.js | 11 + tests/auto/blackbox/testdata/require/blubb.js | 13 + .../blackbox/testdata/require/require.qbs | 21 + tests/auto/blackbox/testdata/require/zort.js | 11 + .../testdata/rescue-transformer-data/main.cpp | 1 + .../rescue-transformer-data/modules/m/m.qbs | 19 + .../transformer-data-rescue.qbs | 5 + .../response-files/cat-response-file.cpp | 67 + .../response-files/response-files.qbs | 82 + .../retagged-output-artifact.qbs | 42 + .../rule-connection-with-excluded-inputs.qbs | 40 + .../rule-with-no-inputs.qbs | 27 + .../rule-with-non-required-inputs/a.inp | 0 .../rule-with-non-required-inputs/b.inp | 0 .../rule-with-non-required-inputs/c.inp | 0 .../rule-with-non-required-inputs.qbs | 41 + .../blackbox/testdata/ruleConditions/foo.narf | 0 .../blackbox/testdata/ruleConditions/main.cpp | 29 + .../modules/narfzort/narfzort.qbs | 29 + .../ruleConditions/ruleConditions.qbs | 11 + .../ruleConditions/templates/zorduct.qbs | 10 + .../blackbox/testdata/ruleCycle/happy.grass | 1 + .../blackbox/testdata/ruleCycle/ruleCycle.qbs | 45 + .../blackbox/testdata/sanitizer/sanitizer.cpp | 4 + .../blackbox/testdata/sanitizer/sanitizer.qbs | 34 + .../scan-result-in-non-dependency/app/app.h | 1 + .../scan-result-in-non-dependency/app/app.qbs | 4 + .../app/main.cpp | 3 + .../scan-result-in-non-dependency/lib/lib.h | 3 + .../other/other.qbs | 24 + .../scan-result-in-non-dependency/p.qbs | 6 + .../scan-result-in-other-product/app/app.h | 1 + .../scan-result-in-other-product/app/app.qbs | 4 + .../scan-result-in-other-product/app/main.cpp | 3 + .../scan-result-in-other-product/lib/lib.h | 3 + .../scan-result-in-other-product/lib/lib.qbs | 7 + .../other/other.qbs | 24 + .../scan-result-in-other-product/p.qbs | 7 + .../testdata/scanner-item/modules/m/m.qbs | 9 + .../testdata/scanner-item/scanner-item.qbs | 24 + .../testdata/scanner-item/subdir1/file.inc | 0 .../testdata/scanner-item/subdir1/file1.in | 0 .../testdata/scanner-item/subdir2/file.inc | 0 .../testdata/scanner-item/subdir2/file2.in | 0 .../testdata/separate-debug-info/foo.cpp | 31 + .../testdata/separate-debug-info/main.cpp | 29 + .../separate-debug-info.qbs | 156 + .../modules/buildenv/buildenv.qbs | 8 + .../setup-build-environment/modules/m/m.qbs | 25 + .../setup-build-environment.qbs | 11 + .../testdata/setup-run-environment/lib1.cpp | 3 + .../testdata/setup-run-environment/lib2.cpp | 7 + .../testdata/setup-run-environment/lib3.cpp | 3 + .../testdata/setup-run-environment/lib4.cpp | 3 + .../testdata/setup-run-environment/lib5.cpp | 3 + .../testdata/setup-run-environment/main.cpp | 15 + .../setup-run-environment.qbs | 125 + .../blackbox/testdata/smart-relinking/lib.cpp | 24 + .../testdata/smart-relinking/main.cpp | 6 + .../smart-relinking/smart-relinking.qbs | 36 + .../testdata/smart-relinking/staticlib.cpp | 1 + .../modules/module_with_files/main.cpp | 1 + .../module_with_files/module_with_files.qbs | 15 + .../source-artifact-changes.qbs | 56 + .../header.h | 0 ...e-artifact-in-inputs-from-dependencies.qbs | 58 + .../auto/blackbox/testdata/soversion/lib.cpp | 1 + .../blackbox/testdata/soversion/soversion.qbs | 7 + .../static-lib-without-sources/lib.cpp | 1 + .../static-lib-without-sources.qbs | 14 + .../subprofile-change-tracking/main1.cpp | 29 + .../subprofile-change-tracking/main2.cpp | 29 + .../subprofile-change-tracking.qbs | 8 + .../testdata/successive-changes/input.in | 0 .../successive-changes/successive-changes.qbs | 29 + .../suspicious-calls/copy-command.qbs | 19 + .../testdata/suspicious-calls/copy-eval.qbs | 9 + .../suspicious-calls/copy-prepare.qbs | 20 + .../testdata/suspicious-calls/copy-probe.qbs | 13 + .../suspicious-calls/direntries-command.qbs | 21 + .../suspicious-calls/direntries-eval.qbs | 5 + .../suspicious-calls/direntries-prepare.qbs | 20 + .../suspicious-calls/direntries-probe.qbs | 14 + .../testdata/suspicious-calls/test.txt | 0 .../testdata/symbolLinkMode/indirect.cpp | 3 + .../blackbox/testdata/symbolLinkMode/lib.cpp | 11 + .../blackbox/testdata/symbolLinkMode/main.cpp | 17 + .../symbolLinkMode/symbolLinkMode.qbs | 114 + .../symlink-removal/symlink-removal.qbs | 17 + .../testdata/system-include-paths/main.cpp | 8 + .../system-include-paths/subdir/gagagugu.h | 4 + .../system-include-paths.qbs | 4 + .../testdata/system-run-paths/lib.cpp | 29 + .../testdata/system-run-paths/main.cpp | 34 + .../system-run-paths/system-run-paths.qbs | 23 + .../testdata/texttemplate/cdefgabc.txt.in | 1 + .../testdata/texttemplate/expected/lalala.txt | 1 + .../testdata/texttemplate/expected/output.txt | 12 + .../testdata/texttemplate/output.txt.in | 12 + .../texttemplate/texttemplatetest.qbs | 24 + .../qbs-resources/imports/MyProduct.qbs | 1 + .../toplevel-searchpath.qbs | 1 + .../testdata/trackAddFile/after/main.cpp | 41 + .../trackAddFile/after/trackAddFile.qbs | 20 + .../testdata/trackAddFile/after/zort.cpp | 35 + .../testdata/trackAddFile/after/zort.h | 38 + .../testdata/trackAddFile/before/main.cpp | 38 + .../testdata/trackAddFile/before/narf.cpp | 35 + .../testdata/trackAddFile/before/narf.h | 38 + .../trackAddFile/before/trackAddFile.qbs | 16 + .../environmentChange.cpp | 29 + .../trackExternalProductChanges/fileList.js | 6 + .../hidden/hiddenheaderqbs.h | 27 + .../trackExternalProductChanges/including.cpp | 31 + .../jsFileChange.cpp | 29 + .../trackExternalProductChanges/main.cpp | 29 + .../trackExternalProductChanges.qbs | 14 + .../testdata/trackFileTags/after/main.cpp | 37 + .../trackFileTags/after/trackFileTags.qbs | 58 + .../testdata/trackFileTags/before/main.cpp | 35 + .../trackFileTags/before/trackFileTags.qbs | 58 + .../testdata/trackProducts/after/product3.qbs | 6 + .../trackProducts/after/trackProducts.qbs | 5 + .../testdata/trackProducts/after/zoo.cpp | 34 + .../testdata/trackProducts/before/bar.cpp | 34 + .../testdata/trackProducts/before/foo.cpp | 34 + .../trackProducts/before/product1.qbs | 6 + .../trackProducts/before/product2.qbs | 6 + .../trackProducts/before/trackProducts.qbs | 5 + .../modules/a/a.qbs | 5 + .../modules/b/b.qbs | 3 + .../modules/c/c.qbs | 3 + .../modules/d/d.qbs | 4 + .../transitive-invalid-dependencies.qbs | 11 + .../modules/a/a.qbs | 3 + .../modules/b/b.qbs | 3 + .../transitive-optional-dependencies.qbs | 3 + .../blackbox/testdata/typescript/animals.ts | 21 + .../blackbox/testdata/typescript/extra.js | 3 + .../auto/blackbox/testdata/typescript/foo.ts | 5 + .../auto/blackbox/testdata/typescript/foo2.ts | 1 + .../blackbox/testdata/typescript/hello.ts | 1 + .../auto/blackbox/testdata/typescript/main.ts | 22 + .../testdata/typescript/typescript.qbs | 54 + .../testdata/typescript/woosh/extra.ts | 2 + .../undefined-target-platform.qbs | 13 + .../custom1.in | 0 .../custom2.in | 0 .../usings-as-sole-inputs-non-multiplexed.qbs | 61 + .../blackbox/testdata/variant-suffix/lib.cpp | 1 + .../variant-suffix/variant-suffix.qbs | 38 + tests/auto/blackbox/testdata/vcs/main.cpp | 7 + tests/auto/blackbox/testdata/vcs/vcstest.qbs | 11 + .../versioncheck/modules/higher/higher.qbs | 8 + .../versioncheck/modules/lower/lower.qbs | 1 + .../testdata/versioncheck/versioncheck.qbs | 10 + .../blackbox/testdata/versionscript/testlib.c | 30 + .../testdata/versionscript/versionscript | 1 + .../testdata/versionscript/versionscript.qbs | 27 + .../testdata/whole-archive/dynamiclib.cpp | 5 + .../blackbox/testdata/whole-archive/main1.cpp | 8 + .../blackbox/testdata/whole-archive/main2.cpp | 8 + .../blackbox/testdata/whole-archive/main3.cpp | 7 + .../blackbox/testdata/whole-archive/main4.cpp | 8 + .../testdata/whole-archive/unused1.cpp | 3 + .../testdata/whole-archive/unused2.cpp | 3 + .../testdata/whole-archive/unused3.cpp | 3 + .../testdata/whole-archive/unused4.cpp | 3 + .../blackbox/testdata/whole-archive/used.cpp | 1 + .../testdata/whole-archive/whole-archive.qbs | 71 + .../testdata/wildcard_renaming/pioniere.txt | 0 .../wildcard_renaming/wildcard_renaming.qbs | 7 + .../testdata/wildcards-and-rules/input1.inp | 0 .../wildcards-and-rules.qbs | 36 + .../blackbox/testdata/wix/ExampleScript.bat | 1 + .../blackbox/testdata/wix/QbsBootstrapper.wxs | 10 + tests/auto/blackbox/testdata/wix/QbsSetup.wxs | 34 + tests/auto/blackbox/testdata/wix/Qt.wxs | 5 + .../blackbox/testdata/wix/WiXInstallers.qbs | 38 + tests/auto/blackbox/testdata/wix/de.wxl | 1 + .../testdata/wixDependencies/QbsSetup.wxs | 37 + .../blackbox/testdata/wixDependencies/main.c | 1 + .../wixDependencies/wixDependencies.qbs | 67 + tests/auto/blackbox/tst_blackbox.cpp | 8516 +++++++++++++++++ tests/auto/blackbox/tst_blackbox.h | 350 + tests/auto/blackbox/tst_blackboxandroid.cpp | 820 ++ tests/auto/blackbox/tst_blackboxandroid.h | 49 + tests/auto/blackbox/tst_blackboxapple.cpp | 975 ++ tests/auto/blackbox/tst_blackboxapple.h | 74 + tests/auto/blackbox/tst_blackboxbaremetal.cpp | 219 + tests/auto/blackbox/tst_blackboxbaremetal.h | 70 + tests/auto/blackbox/tst_blackboxbase.cpp | 272 + tests/auto/blackbox/tst_blackboxbase.h | 109 + tests/auto/blackbox/tst_blackboxexamples.cpp | 104 + tests/auto/blackbox/tst_blackboxexamples.h | 53 + tests/auto/blackbox/tst_blackboxjava.cpp | 246 + tests/auto/blackbox/tst_blackboxjava.h | 48 + tests/auto/blackbox/tst_blackboxjoblimits.cpp | 173 + tests/auto/blackbox/tst_blackboxqt.cpp | 614 ++ tests/auto/blackbox/tst_blackboxqt.h | 79 + tests/auto/blackbox/tst_clangdb.cpp | 218 + tests/auto/blackbox/tst_clangdb.h | 64 + tests/auto/buildgraph/buildgraph.pro | 13 + tests/auto/buildgraph/buildgraph.qbs | 10 + tests/auto/buildgraph/tst_buildgraph.cpp | 153 + tests/auto/buildgraph/tst_buildgraph.h | 70 + tests/auto/cmdlineparser/cmdlineparser.pro | 7 + tests/auto/cmdlineparser/cmdlineparser.qbs | 31 + .../auto/cmdlineparser/tst_cmdlineparser.cpp | 215 + tests/auto/dllexport.h | 39 + tests/auto/language/language.pro | 24 + tests/auto/language/language.qbs | 30 + tests/auto/language/testdata/Banana | 1 + tests/auto/language/testdata/MyProperties.qbs | 2 + .../language/testdata/ParentWithExport.qbs | 6 + tests/auto/language/testdata/aboutdialog.cpp | 0 .../testdata/additional-product-types.qbs | 12 + .../testdata/base-validate/base-validate.qbs | 3 + .../base-validate/modules/m/MParent.qbs | 4 + .../testdata/base-validate/modules/m/m.qbs | 9 + tests/auto/language/testdata/baseproperty.qbs | 7 + .../language/testdata/baseproperty_base.qbs | 4 + .../testdata/broken-dependency-cycle1.qbs | 18 + .../testdata/broken-dependency-cycle2.qbs | 18 + .../testdata/buildconfigstringlistsyntax.qbs | 3 + .../builtinFunctionInSearchPathsProperty.qbs | 8 + .../testdata/canonicalArchitecture.qbs | 5 + .../chained-probes/chained-probes.qbs | 3 + .../testdata/chained-probes/modules/m/m.qbs | 15 + .../language/testdata/conditionaldepends.qbs | 87 + .../testdata/conditionaldepends_base.qbs | 8 + .../language/testdata/defaultvalue/egon.qbs | 12 + .../defaultvalue/modules/higher/higher.qbs | 5 + .../defaultvalue/modules/lower/lower.qbs | 5 + .../language/testdata/defaultvalue/test.txt | 0 .../testdata/delayed-error/modules/m/m.qbs | 3 + .../testdata/delayed-error/nonexisting.qbs | 8 + .../testdata/delayed-error/validation.qbs | 8 + .../testdata/dependencyOnAllProfiles.qbs | 16 + .../derived-sub-project/DerivedSubProject.qbs | 2 + .../testdata/derived-sub-project/project.qbs | 8 + .../derived-sub-project/subproject.qbs | 2 + .../dirwithmultipleprojects/project.qbs | 0 .../dirwithmultipleprojects/project2.qbs | 0 .../testdata/dirwithnoprojects/.gitignore | 2 + .../testdata/dirwithoneproject/project.qbs | 0 .../language/testdata/disabled-subproject.qbs | 26 + .../testdata/dotted-names/dotted-names.qbs | 24 + .../testdata/dotted-names/modules/x/y/xy.qbs | 5 + tests/auto/language/testdata/drawline.asm | 0 tests/auto/language/testdata/dummy.txt | 0 tests/auto/language/testdata/empty-js-file.js | 0 .../auto/language/testdata/empty-js-file.qbs | 4 + .../language/testdata/enum-project-props.qbs | 12 + .../language/testdata/environmentvariable.qbs | 5 + .../testdata/erroneous/ParentItem.qbs | 4 + .../testdata/erroneous/ParentWithExport.qbs | 5 + .../ambiguous-multiplex-dependency.qbs | 14 + .../conflicting-module-instances.qbs | 3 + ...conflicting-properties-in-export-items.qbs | 5 + .../erroneous/conflicting_fileTagsFilter.qbs | 11 + .../dependency-profile-mismatch-2.qbs | 17 + .../erroneous/dependency-profile-mismatch.qbs | 14 + .../testdata/erroneous/dependency_cycle.qbs | 17 + .../testdata/erroneous/dependency_cycle2.qbs | 21 + .../testdata/erroneous/dependency_cycle3.qbs | 11 + .../testdata/erroneous/dependency_cycle4.qbs | 3 + .../erroneous/duplicate-multiplex-value.qbs | 8 + .../erroneous/duplicate-multiplex-value2.qbs | 8 + .../testdata/erroneous/duplicate_sources.qbs | 6 + .../erroneous/duplicate_sources_wildcards.qbs | 6 + .../testdata/erroneous/importloop1.qbs | 4 + .../testdata/erroneous/importloop2.qbs | 4 + .../erroneous/invalid-parameter-rhs.qbs | 5 + .../erroneous/invalid-parameter-type.qbs | 8 + .../erroneous/invalid-property-option.qbs | 3 + .../testdata/erroneous/invalid-references.qbs | 3 + .../erroneous/invalid_child_item_type.qbs | 4 + .../testdata/erroneous/invalid_file.qbs | 3 + .../erroneous/invalid_property_type.qbs | 3 + .../erroneous/invalid_stringlist_element.qbs | 3 + .../auto/language/testdata/erroneous/main.cpp | 40 + .../mismatching-multiplex-dependency.qbs | 13 + .../testdata/erroneous/missing-colon.qbs | 3 + .../erroneous/misused-inherited-property.qbs | 3 + .../erroneous/module-depends-on-product.qbs | 9 + .../conflicting-instance1.qbs | 2 + .../conflicting-instance2.qbs | 2 + .../erroneous/modules/module-a/module-a.qbs | 3 + .../erroneous/modules/module-b/module-b.qbs | 3 + .../module-with-invalid-original.qbs | 3 + .../module-with-product-dependency.qbs | 3 + .../module-with-wrong-property-option/m.qbs | 7 + .../module_with_parameters.qbs | 6 + .../no_such_property/no-such-property.qbs | 4 + .../erroneous/modules/prefix1/prefix1.qbs | 2 + .../modules/prefix1/suffix/suffix.qbs | 3 + .../erroneous/modules/prefix2/prefix2.qbs | 3 + .../modules/prefix2/suffix/suffix.qbs | 2 + .../erroneous/modules/readonly/readonly.qbs | 3 + .../testdata/erroneous/multiple_exports.qbs | 4 + .../multiple_properties_in_subproject.qbs | 6 + .../erroneous/no-configure-in-probe.qbs | 5 + .../testdata/erroneous/nonexistentouter.qbs | 5 + .../testdata/erroneous/oldQbsVersion.qbs | 6 + .../erroneous/original-in-export-item.qbs | 14 + .../erroneous/original-in-export-item2.qbs | 14 + .../erroneous/original-in-export-item3.qbs | 17 + .../original-in-module-prototype.qbs | 5 + .../original-in-product-property.qbs | 3 + .../overwrite-inherited-readonly-property.qbs | 3 + .../overwrite-readonly-module-property.qbs | 4 + ...properties-item-with-invalid-condition.qbs | 7 + .../testdata/erroneous/references_cycle.qbs | 4 + .../testdata/erroneous/references_cycle2.qbs | 4 + .../testdata/erroneous/references_cycle3.qbs | 4 + .../erroneous/reserved_name_in_import.qbs | 3 + .../erroneous/rule-without-output-tags.qbs | 11 + .../erroneous/same-module-prefix1.qbs | 3 + .../erroneous/same-module-prefix2.qbs | 3 + .../testdata/erroneous/subproject_cycle.qbs | 6 + .../testdata/erroneous/subproject_cycle2.qbs | 6 + .../testdata/erroneous/subproject_cycle3.qbs | 6 + .../erroneous/syntax-error-in-probe.qbs | 6 + .../erroneous/throw_in_property_binding.qbs | 5 + .../erroneous/undeclared-parameter1.qbs | 5 + .../erroneous/undeclared-parameter2.qbs | 5 + .../testdata/erroneous/undeclared_item.qbs | 4 + .../undeclared_module_property_in_module.qbs | 4 + .../erroneous/undeclared_property.qbs | 4 + ...undeclared_property_in_Properties_item.qbs | 6 + .../undeclared_property_in_export_item.qbs | 12 + .../undeclared_property_in_export_item2.qbs | 11 + .../undeclared_property_in_export_item3.qbs | 7 + .../erroneous/undeclared_property_wrapper.qbs | 5 + .../undefined_stringlist_element.qbs | 4 + .../undefined_stringlist_element_in_probe.qbs | 9 + .../testdata/erroneous/unknown_item_type.qbs | 3 + .../testdata/erroneous/unknown_module.qbs | 3 + .../erroneous/wrong-toplevel-item.qbs | 2 + .../erroneous/wrongQbsVersionFormat.qbs | 3 + .../testdata/error-in-disabled-product.qbs | 54 + .../eval-error-in-non-present-module.qbs | 7 + tests/auto/language/testdata/exports.qbs | 158 + .../language/testdata/exports_product.qbs | 9 + .../testdata/file-in-product-and-module.qbs | 9 + .../testdata/filecontextproperties.qbs | 5 + tests/auto/language/testdata/filetags.qbs | 79 + .../language/testdata/getNativeSetting.qbs | 26 + .../language/testdata/groupconditions.qbs | 51 + tests/auto/language/testdata/groupname.qbs | 20 + .../auto/language/testdata/homeDirectory.qbs | 16 + .../auto/language/testdata/id-uniqueness.qbs | 11 + tests/auto/language/testdata/idusage.qbs | 29 + .../auto/language/testdata/idusage_group.qbs | 5 + .../auto/language/testdata/idusage_group2.qbs | 5 + tests/auto/language/testdata/idusagebase.qbs | 16 + .../language/testdata/idusagebasebase.qbs | 5 + .../import-collection/collection/file1.js | 1 + .../import-collection/collection/file2.js | 1 + .../imports/Collection/file1.js | 1 + .../imports/Collection/file2.js | 1 + .../testdata/import-collection/product.qbs | 7 + .../testdata/import-collection/project.qbs | 4 + .../imports/DebugName.qbs | 6 + .../imports/ReleaseName.qbs | 6 + .../inherited-properties-items-product.qbs | 7 + .../inherited-properties-items.qbs | 6 + .../language/testdata/invalid-overrides.qbs | 14 + .../testdata/invalidBindingInDisabledItem.qbs | 14 + tests/auto/language/testdata/jsextensions.js | 65 + .../testdata/jsimportsinmultiplescopes.js | 12 + .../testdata/jsimportsinmultiplescopes.qbs | 7 + tests/auto/language/testdata/main.cpp | 0 .../module-merging-variant-values.qbs | 5 + .../modules/m1/m1.qbs | 6 + .../modules/m2/m2.qbs | 14 + .../bar/modules/conflicting-instances/bar.qbs | 3 + .../foo/modules/conflicting-instances/foo.qbs | 3 + .../product.qbs | 3 + .../project.qbs | 3 + .../module-property-overrides-per-product.qbs | 17 + .../language/testdata/moduleproperties.qbs | 73 + .../testdata/modulepropertiesingroups.qbs | 83 + tests/auto/language/testdata/modules.qbs | 57 + .../testdata/modules/broken/broken.qbs | 19 + .../deepdummy/deep/moat/dummydeepmoat.qbs | 5 + .../language/testdata/modules/dummy/dummy.qbs | 25 + .../testdata/modules/dummy/dummy_base.qbs | 4 + .../testdata/modules/dummy2/dummy2.qbs | 7 + .../testdata/modules/dummy3/dummy3.qbs | 4 + .../modules/dummy3_loader/dummy3_loader.qbs | 4 + .../modules/dummyqt/core/dummycore.qbs | 21 + .../testdata/modules/dummyqt/gui/dummygui.qbs | 10 + .../modules/dummyqt/network/dummynetwork.qbs | 7 + .../testdata/modules/gmod/gmod1/gmod1.qbs | 16 + .../language/testdata/modules/gmod2/gmod2.qbs | 6 + .../language/testdata/modules/gmod3/qmod3.qbs | 5 + .../language/testdata/modules/gmod4/gmod4.qbs | 6 + .../module-with-properties-item.qbs | 8 + .../module_with_file/module-with-file.qbs | 14 + .../modules/multiple_backends/backend1.qbs | 4 + .../modules/multiple_backends/backend2.qbs | 5 + .../modules/multiple_backends/backend3.qbs | 5 + .../testdata/modules/scopemod/scopemod.qbs | 10 + tests/auto/language/testdata/modulescope.qbs | 13 + .../language/testdata/modulescope_base.qbs | 4 + .../language/testdata/multiplexed-exports.qbs | 18 + .../testdata/multiplexing-by-profile/p1.qbs | 15 + .../testdata/multiplexing-by-profile/p2.qbs | 19 + .../testdata/multiplexing-by-profile/p3.qbs | 27 + .../testdata/multiplexing-by-profile/p4.qbs | 19 + tests/auto/language/testdata/narf | 0 tests/auto/language/testdata/narf.zort | 0 .../auto/language/testdata/nativesettings.ini | 1 + ...-applicable-module-property-in-profile.qbs | 16 + .../testdata/non-required-products.qbs | 33 + tests/auto/language/testdata/outerInGroup.qbs | 12 + .../overridden-properties-and-prototypes.qbs | 4 + .../testdata/overridden-variant-property.qbs | 4 + .../language/testdata/parameter-types.qbs | 17 + .../auto/language/testdata/pathproperties.qbs | 9 + .../language/testdata/productconditions.qbs | 35 + .../language/testdata/productdirectories.qbs | 3 + .../profilevaluesandoverriddenvalues.qbs | 19 + .../testdata/properties-block-in-group.qbs | 14 + .../testdata/properties-item-in-module.qbs | 4 + .../language/testdata/propertiesblocks.qbs | 235 + .../testdata/propertiesblocks_base.qbs | 11 + .../property-assignment-in-exported-group.qbs | 16 + .../qbs-properties-in-project-condition.qbs | 7 + .../qbs-property-convenience-override.qbs | 4 + tests/auto/language/testdata/qbs1275.qbs | 40 + .../recursive-dependencies.qbs | 17 + .../testdata/relaxed-error-mode/file1.txt | 0 .../testdata/relaxed-error-mode/file2.txt | 0 .../relaxed-error-mode/relaxed-error-mode.qbs | 29 + .../complicated.qbs | 5 + .../dependency-via-export.qbs | 13 + .../dependency-via-module.qbs | 4 + .../direct-dependencies.qbs | 4 + .../failing-validation-indirect.qbs | 3 + .../failing-validation/failing-validation.qbs | 3 + .../required-chain-export-indirect.qbs | 20 + .../required-chain-export.qbs | 13 + .../required-chain-module.qbs | 4 + .../language/testdata/rfc1034identifier.qbs | 9 + .../testdata/subdir/exports-mylib.qbs | 15 + .../testdata/subdir/pathproperties_base.qbs | 4 + .../testdata/subdir2/exports-mylib2.qbs | 11 + .../suppressed-and-non-suppressed-errors.qbs | 11 + .../auto/language/testdata/throwing-probe.qbs | 11 + .../testdata/use-internal-profile.qbs | 13 + .../auto/language/testdata/versionCompare.qbs | 20 + tests/auto/language/testdata/zort | 0 tests/auto/language/tst_language.cpp | 3290 +++++++ tests/auto/language/tst_language.h | 191 + tests/auto/shared.h | 366 + tests/auto/tools/tools.pro | 6 + tests/auto/tools/tools.qbs | 15 + tests/auto/tools/tst_tools.cpp | 1284 +++ tests/auto/tools/tst_tools.h | 111 + tests/benchmarker/activities.h | 42 + tests/benchmarker/benchmarker-main.cpp | 134 + tests/benchmarker/benchmarker.cpp | 126 + tests/benchmarker/benchmarker.h | 82 + tests/benchmarker/benchmarker.pro | 20 + tests/benchmarker/benchmarker.qbs | 32 + tests/benchmarker/commandlineparser.cpp | 137 + tests/benchmarker/commandlineparser.h | 66 + tests/benchmarker/exception.h | 53 + tests/benchmarker/runsupport.cpp | 71 + tests/benchmarker/runsupport.h | 47 + tests/benchmarker/valgrindrunner.cpp | 249 + tests/benchmarker/valgrindrunner.h | 91 + tests/fuzzy-test/commandlineparser.cpp | 130 + tests/fuzzy-test/commandlineparser.h | 76 + tests/fuzzy-test/fuzzy-test.pro | 11 + tests/fuzzy-test/fuzzy-test.qbs | 21 + tests/fuzzy-test/fuzzytester.cpp | 308 + tests/fuzzy-test/fuzzytester.h | 84 + tests/fuzzy-test/main.cpp | 88 + tests/tests.pro | 4 + tests/tests.qbs | 43 + 3363 files changed, 282271 insertions(+) create mode 100644 .clang-tidy create mode 100644 .dockerignore create mode 100644 .gitmodules create mode 100644 .mailmap create mode 100644 .travis.yml create mode 100644 CONTRIBUTING.md create mode 100644 LGPL_EXCEPTION.txt create mode 100644 LICENSE.GPL3-EXCEPT create mode 100644 LICENSE.LGPLv21 create mode 100644 LICENSE.LGPLv3 create mode 100644 README.md create mode 100644 VERSION create mode 100644 bin/ibmsvc.xml create mode 100644 bin/ibqbs.bat create mode 100644 changelogs/changes-1.10.0.md create mode 100644 changelogs/changes-1.10.1.md create mode 100644 changelogs/changes-1.11.0.md create mode 100644 changelogs/changes-1.11.1.md create mode 100644 changelogs/changes-1.12.0.md create mode 100644 changelogs/changes-1.12.1.md create mode 100644 changelogs/changes-1.12.2.md create mode 100644 changelogs/changes-1.13.0.md create mode 100644 changelogs/changes-1.13.1.md create mode 100644 changelogs/changes-1.14.0.md create mode 100644 changelogs/changes-1.14.1.md create mode 100644 changelogs/changes-1.15.0.md create mode 100644 changelogs/changes-1.16.0.md create mode 100644 changelogs/changes-1.17.0.md create mode 100644 changelogs/changes-1.6.0 create mode 100644 changelogs/changes-1.6.1 create mode 100644 changelogs/changes-1.7.0 create mode 100644 changelogs/changes-1.7.1 create mode 100644 changelogs/changes-1.7.2 create mode 100644 changelogs/changes-1.8.0 create mode 100644 changelogs/changes-1.8.1 create mode 100644 changelogs/changes-1.9.0.md create mode 100644 changelogs/changes-1.9.1.md create mode 100644 dist/.gitignore create mode 100644 doc/appendix/json-api.qdoc create mode 100644 doc/appendix/qbs-porting.qdoc create mode 100644 doc/classic.css create mode 100644 doc/codeattributions.qdoc create mode 100644 doc/config/macros.qdocconf create mode 100644 doc/config/qbs-project.qdocconf create mode 100644 doc/config/style/qt5-sidebar.html create mode 100644 doc/doc.pri create mode 100644 doc/doc.qbs create mode 100644 doc/doc_shared.pri create mode 100644 doc/doc_targets.pri create mode 100644 doc/external-resources.qdoc create mode 100755 doc/fix-qmlimports.py create mode 100644 doc/fixnavi.pl create mode 100644 doc/howtos.qdoc create mode 100644 doc/images/qbs-build-process.png create mode 100644 doc/images/qbs-dmg.png create mode 100644 doc/images/qbs-settings-gui.png create mode 100644 doc/man/man.pri create mode 100644 doc/man/man.qbs create mode 100644 doc/man/qbs.1 create mode 100644 doc/man/see-also.h2m create mode 100644 doc/qbs-online.qdocconf create mode 100644 doc/qbs.qdoc create mode 100644 doc/qbs.qdocconf create mode 100644 doc/reference/cli/builtin/cli-build.qdoc create mode 100644 doc/reference/cli/builtin/cli-clean.qdoc create mode 100644 doc/reference/cli/builtin/cli-dump-nodes-tree.qdoc create mode 100644 doc/reference/cli/builtin/cli-generate.qdoc create mode 100644 doc/reference/cli/builtin/cli-help.qdoc create mode 100644 doc/reference/cli/builtin/cli-install.qdoc create mode 100644 doc/reference/cli/builtin/cli-list-products.qdoc create mode 100644 doc/reference/cli/builtin/cli-resolve.qdoc create mode 100644 doc/reference/cli/builtin/cli-run.qdoc create mode 100644 doc/reference/cli/builtin/cli-session.qdoc create mode 100644 doc/reference/cli/builtin/cli-shell.qdoc create mode 100644 doc/reference/cli/builtin/cli-status.qdoc create mode 100644 doc/reference/cli/builtin/cli-update-timestamps.qdoc create mode 100644 doc/reference/cli/builtin/cli-version.qdoc create mode 100644 doc/reference/cli/cli-options.qdocinc create mode 100644 doc/reference/cli/cli-parameters.qdocinc create mode 100644 doc/reference/cli/cli.qdoc create mode 100644 doc/reference/cli/tools/cli-config-ui.qdoc create mode 100644 doc/reference/cli/tools/cli-config.qdoc create mode 100644 doc/reference/cli/tools/cli-create-project.qdoc create mode 100644 doc/reference/cli/tools/cli-setup-android.qdoc create mode 100644 doc/reference/cli/tools/cli-setup-qt.qdoc create mode 100644 doc/reference/cli/tools/cli-setup-toolchains.qdoc create mode 100644 doc/reference/commands.qdoc create mode 100644 doc/reference/items/convenience/appleapplicationdiskimage.qdoc create mode 100644 doc/reference/items/convenience/applediskimage.qdoc create mode 100644 doc/reference/items/convenience/application.qdoc create mode 100644 doc/reference/items/convenience/applicationextension.qdoc create mode 100644 doc/reference/items/convenience/autotestrunner.qdoc create mode 100644 doc/reference/items/convenience/cppapplication.qdoc create mode 100644 doc/reference/items/convenience/dynamiclibrary.qdoc create mode 100644 doc/reference/items/convenience/innosetup.qdoc create mode 100644 doc/reference/items/convenience/installpackage.qdoc create mode 100644 doc/reference/items/convenience/javaclasscollection.qdoc create mode 100644 doc/reference/items/convenience/javajarfile.qdoc create mode 100644 doc/reference/items/convenience/library.qdoc create mode 100644 doc/reference/items/convenience/loadablemodule.qdoc create mode 100644 doc/reference/items/convenience/qtapplication.qdoc create mode 100644 doc/reference/items/convenience/qtguiapplication.qdoc create mode 100644 doc/reference/items/convenience/staticlibrary.qdoc create mode 100644 doc/reference/items/convenience/xpcservice.qdoc create mode 100644 doc/reference/items/language/artifact.qdoc create mode 100644 doc/reference/items/language/depends.qdoc create mode 100644 doc/reference/items/language/export.qdoc create mode 100644 doc/reference/items/language/filetagger.qdoc create mode 100644 doc/reference/items/language/group.qbs create mode 100644 doc/reference/items/language/group.qdoc create mode 100644 doc/reference/items/language/joblimit.qdoc create mode 100644 doc/reference/items/language/module.qdoc create mode 100644 doc/reference/items/language/moduleprovider.qdoc create mode 100644 doc/reference/items/language/parameter.qdoc create mode 100644 doc/reference/items/language/parameters.qdoc create mode 100644 doc/reference/items/language/probe.qdoc create mode 100644 doc/reference/items/language/product.qdoc create mode 100644 doc/reference/items/language/profile.qdoc create mode 100644 doc/reference/items/language/project.qdoc create mode 100644 doc/reference/items/language/properties.qdoc create mode 100644 doc/reference/items/language/propertyoptions.qdoc create mode 100644 doc/reference/items/language/rule.qdoc create mode 100644 doc/reference/items/language/scanner.qdoc create mode 100644 doc/reference/items/language/subproject.qdoc create mode 100644 doc/reference/items/probe/binary-probe.qdoc create mode 100644 doc/reference/items/probe/conanfile-probe.qdoc create mode 100644 doc/reference/items/probe/framework-probe.qdoc create mode 100644 doc/reference/items/probe/iar-probe.qdoc create mode 100644 doc/reference/items/probe/include-probe.qdoc create mode 100644 doc/reference/items/probe/keil-probe.qdoc create mode 100644 doc/reference/items/probe/library-probe.qdoc create mode 100644 doc/reference/items/probe/path-probe.qdoc create mode 100644 doc/reference/items/probe/pkgconfig-probe.qdoc create mode 100644 doc/reference/items/probe/sdcc-probe.qdoc create mode 100644 doc/reference/jsextensions/jsextension-binaryfile.qdoc create mode 100644 doc/reference/jsextensions/jsextension-environment.qdoc create mode 100644 doc/reference/jsextensions/jsextension-file.qdoc create mode 100644 doc/reference/jsextensions/jsextension-fileinfo.qdoc create mode 100644 doc/reference/jsextensions/jsextension-process.qdoc create mode 100644 doc/reference/jsextensions/jsextension-propertylist.qdoc create mode 100644 doc/reference/jsextensions/jsextension-temporarydir.qdoc create mode 100644 doc/reference/jsextensions/jsextension-textfile.qdoc create mode 100644 doc/reference/jsextensions/jsextension-utilities.qdoc create mode 100644 doc/reference/jsextensions/jsextension-xml.qdoc create mode 100644 doc/reference/jsextensions/jsextensions-general.qdoc create mode 100644 doc/reference/modules/android-ndk-module.qdoc create mode 100644 doc/reference/modules/android-sdk-module.qdoc create mode 100644 doc/reference/modules/archiver-module.qdoc create mode 100644 doc/reference/modules/autotest-module.qdoc create mode 100644 doc/reference/modules/bundle-module.qdoc create mode 100644 doc/reference/modules/capnprotocpp-module.qdoc create mode 100644 doc/reference/modules/cpp-module.qdoc create mode 100644 doc/reference/modules/cpufeatures-module.qdoc create mode 100644 doc/reference/modules/dmg-module.qdoc create mode 100644 doc/reference/modules/exporter-pkgconfig-module.qdoc create mode 100644 doc/reference/modules/exporter-qbs-module.qdoc create mode 100644 doc/reference/modules/freedesktop-module.qdoc create mode 100644 doc/reference/modules/ib-module.qdoc create mode 100644 doc/reference/modules/ico-module.qdoc create mode 100644 doc/reference/modules/innosetup-module.qdoc create mode 100644 doc/reference/modules/java-module.qdoc create mode 100644 doc/reference/modules/lexyacc-module.qdoc create mode 100644 doc/reference/modules/nodejs-module.qdoc create mode 100644 doc/reference/modules/nsis-module.qdoc create mode 100644 doc/reference/modules/pkgconfig-module.qdoc create mode 100644 doc/reference/modules/protobufcpp-module.qdoc create mode 100644 doc/reference/modules/protobufobjc-module.qdoc create mode 100644 doc/reference/modules/qbs-module.qdoc create mode 100644 doc/reference/modules/qnx-module.qdoc create mode 100644 doc/reference/modules/qt-android_support-module.qdoc create mode 100644 doc/reference/modules/qt-core-module.qdoc create mode 100644 doc/reference/modules/qt-dbus-module.qdoc create mode 100644 doc/reference/modules/qt-declarative-module.qdoc create mode 100644 doc/reference/modules/qt-gui-module.qdoc create mode 100644 doc/reference/modules/qt-modules.qdoc create mode 100644 doc/reference/modules/qt-plugin_support-module.qdoc create mode 100644 doc/reference/modules/qt-qml-module.qdoc create mode 100644 doc/reference/modules/qt-quick-module.qdoc create mode 100644 doc/reference/modules/qt-scxml-module.qdoc create mode 100644 doc/reference/modules/texttemplate-module.qdoc create mode 100644 doc/reference/modules/typescript-module.qdoc create mode 100644 doc/reference/modules/vcs-module.qdoc create mode 100644 doc/reference/modules/wix-module.qdoc create mode 100644 doc/reference/modules/xcode-module.qdoc create mode 100644 doc/reference/reference.qdoc create mode 100644 doc/targets/qbs-target-android.qdoc create mode 100644 doc/targets/qbs-target-apple-common.qdocinc create mode 100644 doc/targets/qbs-target-integrity.qdoc create mode 100644 doc/targets/qbs-target-ios.qdoc create mode 100644 doc/targets/qbs-target-linux.qdoc create mode 100644 doc/targets/qbs-target-macos.qdoc create mode 100644 doc/targets/qbs-target-platforms.qdoc create mode 100644 doc/targets/qbs-target-qnx.qdoc create mode 100644 doc/targets/qbs-target-tvos.qdoc create mode 100644 doc/targets/qbs-target-vxworks.qdoc create mode 100644 doc/targets/qbs-target-watchos.qdoc create mode 100644 doc/targets/qbs-target-windows.qdoc create mode 100644 docker-compose.yml create mode 100644 docker/bionic/Dockerfile create mode 100755 docker/bionic/entrypoint.sh create mode 100644 docker/bionic/test-android.Dockerfile create mode 100644 docker/docker.qbs create mode 100644 docker/windowsservercore/Dockerfile create mode 100644 examples/app-and-lib/app-and-lib.qbs create mode 100644 examples/app-and-lib/app/app.qbs create mode 100644 examples/app-and-lib/app/main.cpp create mode 100644 examples/app-and-lib/lib/lib.cpp create mode 100644 examples/app-and-lib/lib/lib.h create mode 100644 examples/app-and-lib/lib/lib.qbs create mode 100644 examples/baremetal/at90can128olimex/at90can128olimex.qbs create mode 100644 examples/baremetal/at90can128olimex/redblink/README.md create mode 100644 examples/baremetal/at90can128olimex/redblink/gpio.c create mode 100644 examples/baremetal/at90can128olimex/redblink/gpio.h create mode 100644 examples/baremetal/at90can128olimex/redblink/main.c create mode 100644 examples/baremetal/at90can128olimex/redblink/redblink.qbs create mode 100644 examples/baremetal/baremetal.qbs create mode 100644 examples/baremetal/cc2540usbdongle/cc2540usbdongle.qbs create mode 100644 examples/baremetal/cc2540usbdongle/greenblink/README.md create mode 100644 examples/baremetal/cc2540usbdongle/greenblink/gpio.c create mode 100644 examples/baremetal/cc2540usbdongle/greenblink/gpio.h create mode 100644 examples/baremetal/cc2540usbdongle/greenblink/greenblink.qbs create mode 100644 examples/baremetal/cc2540usbdongle/greenblink/main.c create mode 100644 examples/baremetal/cc2540usbdongle/greenblink/system.h create mode 100644 examples/baremetal/cy7c68013a/cy7c68013a.qbs create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/README.md create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/core.c create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/core.h create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/defs.h create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/gpio.c create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/gpio.h create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/hid.c create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/hid.h create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/hiddesc.c create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/hidep0.c create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/hidep1.c create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/irqs.h create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/main.c create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/nes-gamepads.qbs create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/regs.h create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/usb.c create mode 100644 examples/baremetal/cy7c68013a/nes-gamepads/usb.h create mode 100644 examples/baremetal/msp430f5529/msp430f5529.qbs create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/README.md create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/gamepads.ld create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/gpio.c create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/gpio.h create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/hid.h create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/hiddesc.c create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/hidep0.c create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/hidep1.c create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/hwdefs.h create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/main.c create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/nes-gamepads.qbs create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/pmm.c create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/pmm.h create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/ucs.c create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/ucs.h create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/usb.c create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/usb.h create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/wdt_a.c create mode 100644 examples/baremetal/msp430f5529/nes-gamepads/wdt_a.h create mode 100644 examples/baremetal/msp430f5529/redblink/README.md create mode 100644 examples/baremetal/msp430f5529/redblink/gpio.c create mode 100644 examples/baremetal/msp430f5529/redblink/gpio.h create mode 100644 examples/baremetal/msp430f5529/redblink/main.c create mode 100644 examples/baremetal/msp430f5529/redblink/redblink.qbs create mode 100644 examples/baremetal/msp430f5529/redblink/system.c create mode 100644 examples/baremetal/msp430f5529/redblink/system.h create mode 100644 examples/baremetal/pca10040/greenblink/README.md create mode 100644 examples/baremetal/pca10040/greenblink/gcc/flash.ld create mode 100644 examples/baremetal/pca10040/greenblink/gcc/startup.s create mode 100644 examples/baremetal/pca10040/greenblink/gpio.c create mode 100644 examples/baremetal/pca10040/greenblink/gpio.h create mode 100644 examples/baremetal/pca10040/greenblink/greenblink.qbs create mode 100644 examples/baremetal/pca10040/greenblink/keil/flash.sct create mode 100644 examples/baremetal/pca10040/greenblink/keil/startup.s create mode 100644 examples/baremetal/pca10040/greenblink/main.c create mode 100644 examples/baremetal/pca10040/greenblink/system.h create mode 100644 examples/baremetal/pca10040/pca10040.qbs create mode 100644 examples/baremetal/stm32f103/greenblink/README.md create mode 100644 examples/baremetal/stm32f103/greenblink/gcc/flash.ld create mode 100644 examples/baremetal/stm32f103/greenblink/gcc/startup.s create mode 100644 examples/baremetal/stm32f103/greenblink/gpio.c create mode 100644 examples/baremetal/stm32f103/greenblink/gpio.h create mode 100644 examples/baremetal/stm32f103/greenblink/greenblink.qbs create mode 100644 examples/baremetal/stm32f103/greenblink/keil/flash.sct create mode 100644 examples/baremetal/stm32f103/greenblink/keil/startup.s create mode 100644 examples/baremetal/stm32f103/greenblink/main.c create mode 100644 examples/baremetal/stm32f103/greenblink/system.h create mode 100644 examples/baremetal/stm32f103/stm32f103.qbs create mode 100644 examples/baremetal/stm32f4discovery/blueblink/README.md create mode 100644 examples/baremetal/stm32f4discovery/blueblink/blueblink.qbs create mode 100644 examples/baremetal/stm32f4discovery/blueblink/gcc/flash.ld create mode 100644 examples/baremetal/stm32f4discovery/blueblink/gcc/startup.s create mode 100644 examples/baremetal/stm32f4discovery/blueblink/gpio.c create mode 100644 examples/baremetal/stm32f4discovery/blueblink/gpio.h create mode 100644 examples/baremetal/stm32f4discovery/blueblink/iar/flash.icf create mode 100644 examples/baremetal/stm32f4discovery/blueblink/iar/startup.s create mode 100644 examples/baremetal/stm32f4discovery/blueblink/keil/flash.sct create mode 100644 examples/baremetal/stm32f4discovery/blueblink/keil/startup.s create mode 100644 examples/baremetal/stm32f4discovery/blueblink/main.c create mode 100644 examples/baremetal/stm32f4discovery/blueblink/system.h create mode 100644 examples/baremetal/stm32f4discovery/stm32f4discovery.qbs create mode 100644 examples/baremetal/stm8s103f3/redblink/README.md create mode 100644 examples/baremetal/stm8s103f3/redblink/gpio.c create mode 100644 examples/baremetal/stm8s103f3/redblink/gpio.h create mode 100644 examples/baremetal/stm8s103f3/redblink/main.c create mode 100644 examples/baremetal/stm8s103f3/redblink/redblink.qbs create mode 100644 examples/baremetal/stm8s103f3/redblink/system.h create mode 100644 examples/baremetal/stm8s103f3/stm8s103f3.qbs create mode 100644 examples/capnproto/addressbook_cpp/addressbook.capnp create mode 100644 examples/capnproto/addressbook_cpp/addressbook.cpp create mode 100644 examples/capnproto/addressbook_cpp/addressbook_cpp.qbs create mode 100644 examples/capnproto/calculator_cpp/calculator-client.cpp create mode 100644 examples/capnproto/calculator_cpp/calculator-server.cpp create mode 100644 examples/capnproto/calculator_cpp/calculator.capnp create mode 100644 examples/capnproto/calculator_cpp/calculator_cpp.qbs create mode 100644 examples/cocoa-application/CocoaApplication.xcodeproj/project.pbxproj create mode 100644 examples/cocoa-application/CocoaApplication/AppDelegate.h create mode 100644 examples/cocoa-application/CocoaApplication/AppDelegate.m create mode 100644 examples/cocoa-application/CocoaApplication/CocoaApplication-Info.plist create mode 100644 examples/cocoa-application/CocoaApplication/CocoaApplication-Prefix.pch create mode 100644 examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/Contents.json create mode 100644 examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_128x128.png create mode 100644 examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_128x128@2x.png create mode 100644 examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_16x16.png create mode 100644 examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_16x16@2x.png create mode 100644 examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_256x256.png create mode 100644 examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_256x256@2x.png create mode 100644 examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_32x32.png create mode 100644 examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_32x32@2x.png create mode 100644 examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_512x512.png create mode 100644 examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_512x512@2x.png create mode 100644 examples/cocoa-application/CocoaApplication/background.png create mode 100644 examples/cocoa-application/CocoaApplication/background@2x.png create mode 100644 examples/cocoa-application/CocoaApplication/dmg.iconset/icon_128x128.png create mode 100644 examples/cocoa-application/CocoaApplication/dmg.iconset/icon_128x128@2x.png create mode 100644 examples/cocoa-application/CocoaApplication/dmg.iconset/icon_16x16.png create mode 100644 examples/cocoa-application/CocoaApplication/dmg.iconset/icon_16x16@2x.png create mode 100644 examples/cocoa-application/CocoaApplication/dmg.iconset/icon_256x256.png create mode 100644 examples/cocoa-application/CocoaApplication/dmg.iconset/icon_256x256@2x.png create mode 100644 examples/cocoa-application/CocoaApplication/dmg.iconset/icon_32x32.png create mode 100644 examples/cocoa-application/CocoaApplication/dmg.iconset/icon_32x32@2x.png create mode 100644 examples/cocoa-application/CocoaApplication/dmg.iconset/icon_512x512.png create mode 100644 examples/cocoa-application/CocoaApplication/dmg.iconset/icon_512x512@2x.png create mode 100644 examples/cocoa-application/CocoaApplication/en.lproj/Credits.rtf create mode 100644 examples/cocoa-application/CocoaApplication/en.lproj/InfoPlist.strings create mode 100644 examples/cocoa-application/CocoaApplication/en.lproj/LICENSE create mode 100644 examples/cocoa-application/CocoaApplication/en.lproj/MainMenu.xib create mode 120000 examples/cocoa-application/CocoaApplication/en_US.lproj create mode 100644 examples/cocoa-application/CocoaApplication/main.m create mode 100644 examples/cocoa-application/app.qbs create mode 100644 examples/cocoa-application/cocoa-application.qbs create mode 100644 examples/cocoa-application/dmg.qbs create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication.xcodeproj/project.pbxproj create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/AppDelegate.h create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/AppDelegate.m create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/CocoaTouchApplication-Info.plist create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/CocoaTouchApplication-Prefix.pch create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/Default-568h@2x.png create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/Default.png create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/Default@2x.png create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/DetailViewController.h create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/DetailViewController.m create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/MasterViewController.h create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/MasterViewController.m create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/DetailViewController_iPad.xib create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/DetailViewController_iPhone.xib create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/InfoPlist.strings create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/MasterViewController_iPad.xib create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/MasterViewController_iPhone.xib create mode 100644 examples/cocoa-touch-application/CocoaTouchApplication/main.m create mode 100644 examples/cocoa-touch-application/cocoa-touch-application.qbs create mode 100644 examples/code-generator/code-generator.qbs create mode 100644 examples/code-generator/hwgen.cpp create mode 100644 examples/collidingmice/collidingmice.qbs create mode 100644 examples/collidingmice/images/cheese.jpg create mode 100644 examples/collidingmice/main.cpp create mode 100644 examples/collidingmice/mice.qrc create mode 100644 examples/collidingmice/mouse.cpp create mode 100644 examples/collidingmice/mouse.h create mode 100644 examples/compiled-qml/MainForm.ui.qml create mode 100644 examples/compiled-qml/cheese.jpg create mode 100644 examples/compiled-qml/compiled-qml.qbs create mode 100644 examples/compiled-qml/main.cpp create mode 100644 examples/compiled-qml/main.qml create mode 100644 examples/compiled-qml/qml.qrc create mode 100644 examples/examples.qbs create mode 100644 examples/grpc/client.cpp create mode 100644 examples/grpc/grpc.qbs create mode 100644 examples/grpc/ping-pong-grpc.proto create mode 100644 examples/grpc/server.cpp create mode 100644 examples/helloworld-complex/helloworld-complex.qbs create mode 100644 examples/helloworld-complex/src/foo.cpp create mode 100644 examples/helloworld-complex/src/foo.h create mode 100644 examples/helloworld-complex/src/main.cpp create mode 100644 examples/helloworld-complex/src/specialfeature.cpp create mode 100644 examples/helloworld-complex/src/specialfeature.h create mode 100644 examples/helloworld-minimal/helloworld-minimal.qbs create mode 100644 examples/helloworld-minimal/main.cpp create mode 100644 examples/helloworld-qt/helloworld-qt.qbs create mode 100644 examples/helloworld-qt/main.cpp create mode 100644 examples/install-bundle/MainMenu.xib create mode 100644 examples/install-bundle/Storyboard.storyboard create mode 100644 examples/install-bundle/assetcatalog1.xcassets/other.imageset/Contents.json create mode 100644 examples/install-bundle/assetcatalog1.xcassets/other.imageset/icon_16x16.png create mode 100644 examples/install-bundle/assetcatalog1.xcassets/other.imageset/icon_16x16@2x.png create mode 100644 examples/install-bundle/assetcatalog2.xcassets/other.imageset/Contents.json create mode 100644 examples/install-bundle/assetcatalog2.xcassets/other.imageset/icon_16x16.png create mode 100644 examples/install-bundle/assetcatalog2.xcassets/other.imageset/icon_16x16@2x.png create mode 100644 examples/install-bundle/coreutils.cpp create mode 100644 examples/install-bundle/coreutils.h create mode 100644 examples/install-bundle/install-bundle.qbs create mode 100644 examples/install-bundle/main.cpp create mode 100644 examples/install-bundle/white.iconset/icon_16x16.png create mode 100644 examples/install-bundle/white.iconset/icon_16x16@2x.png create mode 100644 examples/protobuf/addressbook_cpp/README.md create mode 100644 examples/protobuf/addressbook_cpp/addressbook_cpp.qbs create mode 100644 examples/protobuf/addressbook_cpp/main.cpp create mode 100644 examples/protobuf/addressbook_objc/README.md create mode 100644 examples/protobuf/addressbook_objc/addressbook_objc.qbs create mode 100644 examples/protobuf/addressbook_objc/main.m create mode 100644 examples/protobuf/shared/addressbook.proto create mode 100644 examples/rpaths/main.cpp create mode 100644 examples/rpaths/objecta.cpp create mode 100644 examples/rpaths/objecta.h create mode 100644 examples/rpaths/objectb.cpp create mode 100644 examples/rpaths/objectb.h create mode 100644 examples/rpaths/rpaths.qbs create mode 100644 examples/rule/lorem_ipsum.txt create mode 100644 examples/rule/rule.qbs create mode 100644 qbs-resources/imports/QbsApp.qbs create mode 100644 qbs-resources/imports/QbsAutotest.qbs create mode 100644 qbs-resources/imports/QbsLibrary.qbs create mode 100644 qbs-resources/imports/QbsLibraryBase.qbs create mode 100644 qbs-resources/imports/QbsProduct.qbs create mode 100644 qbs-resources/imports/QbsStaticLibrary.qbs create mode 100644 qbs-resources/modules/qbsbuildconfig/qbsbuildconfig.qbs create mode 100644 qbs-resources/modules/qbsversion/qbsversion.qbs create mode 100644 qbs.pro create mode 100644 qbs.qbs create mode 100644 qbs_version.pri create mode 100644 scripts/address-sanitizer-suppressions.txt create mode 100755 scripts/build-qbs-with-qbs.sh create mode 100755 scripts/build-qbs-with-qmake.sh create mode 100755 scripts/install-qt.sh create mode 100644 scripts/make-release-archives.bat create mode 100755 scripts/make-release-archives.sh create mode 100755 scripts/run-analyzer.sh create mode 100644 scripts/scripts.qbs create mode 100755 scripts/test-qt-for-android.sh create mode 100644 scripts/thread-sanitizer-suppressions.txt create mode 100755 scripts/update-dmgbuild.sh create mode 100755 scripts/update-xcspecs.sh create mode 100644 share/qbs/imports/qbs/BundleTools/bundle-tools.js create mode 100644 share/qbs/imports/qbs/DarwinTools/darwin-tools.js create mode 100644 share/qbs/imports/qbs/ModUtils/utils.js create mode 100644 share/qbs/imports/qbs/PathTools/path-tools.js create mode 100644 share/qbs/imports/qbs/Probes/AndroidNdkProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/AndroidSdkProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/BinaryProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/ClBinaryProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/ClangClBinaryProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/ClangClProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/ConanfileProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/FrameworkProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/GccBinaryProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/GccProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/GccVersionProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/IarProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/IcoUtilsVersionProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/IncludeProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/InnoSetupProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/JdkProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/JdkVersionProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/KeilProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/LibraryProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/MsvcProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/NodeJsProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/NpmProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/PathProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/PkgConfigProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/SdccProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/TypeScriptProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/WiXProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/XcodeProbe.qbs create mode 100644 share/qbs/imports/qbs/Probes/path-probe.js create mode 100644 share/qbs/imports/qbs/UnixUtils/unix-utils.js create mode 100644 share/qbs/imports/qbs/WindowsUtils/windows-utils.js create mode 100644 share/qbs/imports/qbs/base/AndroidApk.qbs create mode 100644 share/qbs/imports/qbs/base/AppleApplicationDiskImage.qbs create mode 100644 share/qbs/imports/qbs/base/AppleDiskImage.qbs create mode 100644 share/qbs/imports/qbs/base/Application.qbs create mode 100644 share/qbs/imports/qbs/base/ApplicationExtension.qbs create mode 100644 share/qbs/imports/qbs/base/AutotestRunner.qbs create mode 100644 share/qbs/imports/qbs/base/CppApplication.qbs create mode 100644 share/qbs/imports/qbs/base/DynamicLibrary.qbs create mode 100644 share/qbs/imports/qbs/base/InnoSetup.qbs create mode 100644 share/qbs/imports/qbs/base/InstallPackage.qbs create mode 100644 share/qbs/imports/qbs/base/JavaClassCollection.qbs create mode 100644 share/qbs/imports/qbs/base/JavaJarFile.qbs create mode 100644 share/qbs/imports/qbs/base/Library.qbs create mode 100644 share/qbs/imports/qbs/base/LoadableModule.qbs create mode 100644 share/qbs/imports/qbs/base/NSISSetup.qbs create mode 100644 share/qbs/imports/qbs/base/NativeBinary.qbs create mode 100644 share/qbs/imports/qbs/base/NetModule.qbs create mode 100644 share/qbs/imports/qbs/base/NodeJSApplication.qbs create mode 100644 share/qbs/imports/qbs/base/QtApplication.qbs create mode 100644 share/qbs/imports/qbs/base/QtGuiApplication.qbs create mode 100644 share/qbs/imports/qbs/base/StaticLibrary.qbs create mode 100644 share/qbs/imports/qbs/base/WindowsInstallerPackage.qbs create mode 100644 share/qbs/imports/qbs/base/WindowsSetupPackage.qbs create mode 100644 share/qbs/imports/qbs/base/XPCService.qbs create mode 100644 share/qbs/module-providers/Qt/provider.qbs create mode 100644 share/qbs/module-providers/Qt/setup-qt.js create mode 100644 share/qbs/module-providers/Qt/templates/QtModule.qbs create mode 100644 share/qbs/module-providers/Qt/templates/QtPlugin.qbs create mode 100644 share/qbs/module-providers/Qt/templates/android_support.qbs create mode 100644 share/qbs/module-providers/Qt/templates/core.qbs create mode 100644 share/qbs/module-providers/Qt/templates/dbus.js create mode 100644 share/qbs/module-providers/Qt/templates/dbus.qbs create mode 100644 share/qbs/module-providers/Qt/templates/gui.qbs create mode 100644 share/qbs/module-providers/Qt/templates/moc.js create mode 100644 share/qbs/module-providers/Qt/templates/module.qbs create mode 100644 share/qbs/module-providers/Qt/templates/plugin.qbs create mode 100644 share/qbs/module-providers/Qt/templates/plugin_support.qbs create mode 100644 share/qbs/module-providers/Qt/templates/qdoc.js create mode 100644 share/qbs/module-providers/Qt/templates/qml.js create mode 100644 share/qbs/module-providers/Qt/templates/qml.qbs create mode 100644 share/qbs/module-providers/Qt/templates/qmlcache.qbs create mode 100644 share/qbs/module-providers/Qt/templates/quick.js create mode 100644 share/qbs/module-providers/Qt/templates/quick.qbs create mode 100644 share/qbs/module-providers/Qt/templates/scxml.qbs create mode 100644 share/qbs/module-providers/__fallback/fallback.qbs create mode 100644 share/qbs/module-providers/__fallback/provider.qbs create mode 100644 share/qbs/modules/Android/android-utils.js create mode 100644 share/qbs/modules/Android/ndk/ndk.qbs create mode 100644 share/qbs/modules/Android/ndk/utils.js create mode 100644 share/qbs/modules/Android/sdk/sdk.qbs create mode 100644 share/qbs/modules/Android/sdk/utils.js create mode 100644 share/qbs/modules/Exporter/pkgconfig/pkgconfig.js create mode 100644 share/qbs/modules/Exporter/pkgconfig/pkgconfig.qbs create mode 100644 share/qbs/modules/Exporter/qbs/qbsexporter.js create mode 100644 share/qbs/modules/Exporter/qbs/qbsexporter.qbs create mode 100644 share/qbs/modules/archiver/archiver.qbs create mode 100644 share/qbs/modules/autotest/autotest.qbs create mode 100644 share/qbs/modules/bundle/BundleModule.qbs create mode 100644 share/qbs/modules/bundle/MacOSX-Package-Types.xcspec create mode 100644 share/qbs/modules/bundle/MacOSX-Product-Types.xcspec create mode 100644 share/qbs/modules/bundle/bundle.js create mode 100644 share/qbs/modules/capnproto/capnproto.js create mode 100644 share/qbs/modules/capnproto/capnprotobase.qbs create mode 100644 share/qbs/modules/capnproto/cpp/capnprotocpp.qbs create mode 100644 share/qbs/modules/cli/CLIModule.qbs create mode 100644 share/qbs/modules/cli/cli.js create mode 100644 share/qbs/modules/cli/mono.qbs create mode 100644 share/qbs/modules/cli/windows-dotnet.qbs create mode 100644 share/qbs/modules/cpp/CppModule.qbs create mode 100644 share/qbs/modules/cpp/DarwinGCC.qbs create mode 100644 share/qbs/modules/cpp/GenericGCC.qbs create mode 100644 share/qbs/modules/cpp/LinuxGCC.qbs create mode 100644 share/qbs/modules/cpp/MingwBaseModule.qbs create mode 100644 share/qbs/modules/cpp/UnixGCC.qbs create mode 100644 share/qbs/modules/cpp/android-gcc.qbs create mode 100644 share/qbs/modules/cpp/cpp.js create mode 100644 share/qbs/modules/cpp/darwin.js create mode 100644 share/qbs/modules/cpp/freebsd-gcc.qbs create mode 100644 share/qbs/modules/cpp/freebsd.js create mode 100644 share/qbs/modules/cpp/gcc.js create mode 100644 share/qbs/modules/cpp/iar.js create mode 100644 share/qbs/modules/cpp/iar.qbs create mode 100644 share/qbs/modules/cpp/ios-gcc.qbs create mode 100644 share/qbs/modules/cpp/keil.js create mode 100644 share/qbs/modules/cpp/keil.qbs create mode 100644 share/qbs/modules/cpp/macos-gcc.qbs create mode 100644 share/qbs/modules/cpp/msvc.js create mode 100644 share/qbs/modules/cpp/qnx-qcc.qbs create mode 100644 share/qbs/modules/cpp/sdcc.js create mode 100644 share/qbs/modules/cpp/sdcc.qbs create mode 100644 share/qbs/modules/cpp/setuprunenv.js create mode 100644 share/qbs/modules/cpp/tvos-gcc.qbs create mode 100644 share/qbs/modules/cpp/watchos-gcc.qbs create mode 100644 share/qbs/modules/cpp/windows-clang-cl.qbs create mode 100644 share/qbs/modules/cpp/windows-clang-mingw.qbs create mode 100644 share/qbs/modules/cpp/windows-mingw.qbs create mode 100644 share/qbs/modules/cpp/windows-msvc-base.qbs create mode 100644 share/qbs/modules/cpp/windows-msvc.qbs create mode 100644 share/qbs/modules/cpufeatures/cpufeatures.qbs create mode 100644 share/qbs/modules/dmg/DMGModule.qbs create mode 100644 share/qbs/modules/dmg/dmg.js create mode 100644 share/qbs/modules/freedesktop/FreeDesktop.qbs create mode 100644 share/qbs/modules/freedesktop/freedesktop.js create mode 100644 share/qbs/modules/ib/IBModule.qbs create mode 100644 share/qbs/modules/ib/ib.js create mode 100644 share/qbs/modules/ico/IcoModule.qbs create mode 100644 share/qbs/modules/ico/ico.js create mode 100644 share/qbs/modules/innosetup/InnoSetupModule.qbs create mode 100644 share/qbs/modules/java/JavaModule.qbs create mode 100644 share/qbs/modules/java/io/qt/qbs/Artifact.java create mode 100644 share/qbs/modules/java/io/qt/qbs/ArtifactListJsonWriter.java create mode 100644 share/qbs/modules/java/io/qt/qbs/ArtifactListWriter.java create mode 100644 share/qbs/modules/java/io/qt/qbs/tools/JavaCompilerScannerTool.java create mode 100644 share/qbs/modules/java/io/qt/qbs/tools/utils/JavaCompilerOptions.java create mode 100644 share/qbs/modules/java/io/qt/qbs/tools/utils/JavaCompilerScanner.java create mode 100644 share/qbs/modules/java/io/qt/qbs/tools/utils/NullFileObject.java create mode 100644 share/qbs/modules/java/utils.js create mode 100644 share/qbs/modules/lex_yacc/lexyacc.js create mode 100644 share/qbs/modules/lex_yacc/lexyacc.qbs create mode 100644 share/qbs/modules/nodejs/NodeJS.qbs create mode 100644 share/qbs/modules/nodejs/nodejs.js create mode 100644 share/qbs/modules/nsis/NSISModule.qbs create mode 100644 share/qbs/modules/pkgconfig/pkgconfig.qbs create mode 100644 share/qbs/modules/protobuf/cpp/protobufcpp.qbs create mode 100644 share/qbs/modules/protobuf/objc/protobufobjc.qbs create mode 100644 share/qbs/modules/protobuf/protobuf.js create mode 100644 share/qbs/modules/protobuf/protobufbase.qbs create mode 100644 share/qbs/modules/qbs/common.qbs create mode 100644 share/qbs/modules/qnx/qnx.qbs create mode 100644 share/qbs/modules/texttemplate/texttemplate.qbs create mode 100644 share/qbs/modules/typescript/TypeScriptModule.qbs create mode 100644 share/qbs/modules/typescript/qbs-tsc-scan/.gitignore create mode 100644 share/qbs/modules/typescript/qbs-tsc-scan/qbs-tsc-scan.ts create mode 100644 share/qbs/modules/typescript/typescript.js create mode 100644 share/qbs/modules/vcs/vcs-module.qbs create mode 100644 share/qbs/modules/wix/WiXModule.qbs create mode 100644 share/qbs/modules/xcode/xcode.js create mode 100644 share/qbs/modules/xcode/xcode.qbs create mode 100644 share/share.qbs create mode 100644 src/3rdparty/python/.gitignore create mode 100755 src/3rdparty/python/bin/dmgbuild create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/biplist/LICENSE create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/biplist/__init__.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/biplist/qt_attribution.json create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/LICENSE create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/__init__.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/badge.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/colors.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/core.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/licensing.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/qt_attribution.json create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/resources.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/ds_store/LICENSE create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/ds_store/__init__.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/ds_store/buddy.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/ds_store/qt_attribution.json create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/ds_store/store.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/mac_alias/LICENSE create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/mac_alias/__init__.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/mac_alias/alias.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/mac_alias/bookmark.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/mac_alias/osx.py create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/mac_alias/qt_attribution.json create mode 100644 src/3rdparty/python/lib/python2.7/site-packages/mac_alias/utils.py create mode 100644 src/app/app.pri create mode 100644 src/app/app.pro create mode 100644 src/app/apps.qbs create mode 100644 src/app/config-ui/Info.plist create mode 100644 src/app/config-ui/commandlineparser.cpp create mode 100644 src/app/config-ui/commandlineparser.h create mode 100644 src/app/config-ui/config-ui.pro create mode 100644 src/app/config-ui/config-ui.qbs create mode 100644 src/app/config-ui/fgapp.mm create mode 100644 src/app/config-ui/main.cpp create mode 100644 src/app/config-ui/mainwindow.cpp create mode 100644 src/app/config-ui/mainwindow.h create mode 100644 src/app/config-ui/mainwindow.ui create mode 100644 src/app/config/config.pro create mode 100644 src/app/config/config.qbs create mode 100644 src/app/config/configcommand.h create mode 100644 src/app/config/configcommandexecutor.cpp create mode 100644 src/app/config/configcommandexecutor.h create mode 100644 src/app/config/configcommandlineparser.cpp create mode 100644 src/app/config/configcommandlineparser.h create mode 100644 src/app/config/configmain.cpp create mode 100644 src/app/qbs-create-project/create-project-main.cpp create mode 100644 src/app/qbs-create-project/createproject.cpp create mode 100644 src/app/qbs-create-project/createproject.h create mode 100644 src/app/qbs-create-project/qbs-create-project.pro create mode 100644 src/app/qbs-create-project/qbs-create-project.qbs create mode 100644 src/app/qbs-setup-android/android-setup.cpp create mode 100644 src/app/qbs-setup-android/android-setup.h create mode 100644 src/app/qbs-setup-android/commandlineparser.cpp create mode 100644 src/app/qbs-setup-android/commandlineparser.h create mode 100644 src/app/qbs-setup-android/main.cpp create mode 100644 src/app/qbs-setup-android/qbs-setup-android.exe.manifest create mode 100644 src/app/qbs-setup-android/qbs-setup-android.pro create mode 100644 src/app/qbs-setup-android/qbs-setup-android.qbs create mode 100644 src/app/qbs-setup-android/qbs-setup-android.rc create mode 100644 src/app/qbs-setup-qt/commandlineparser.cpp create mode 100644 src/app/qbs-setup-qt/commandlineparser.h create mode 100644 src/app/qbs-setup-qt/main.cpp create mode 100644 src/app/qbs-setup-qt/qbs-setup-qt.exe.manifest create mode 100644 src/app/qbs-setup-qt/qbs-setup-qt.pro create mode 100644 src/app/qbs-setup-qt/qbs-setup-qt.qbs create mode 100644 src/app/qbs-setup-qt/qbs-setup-qt.rc create mode 100644 src/app/qbs-setup-qt/setupqt.cpp create mode 100644 src/app/qbs-setup-qt/setupqt.h create mode 100644 src/app/qbs-setup-toolchains/clangclprobe.cpp create mode 100644 src/app/qbs-setup-toolchains/clangclprobe.h create mode 100644 src/app/qbs-setup-toolchains/commandlineparser.cpp create mode 100644 src/app/qbs-setup-toolchains/commandlineparser.h create mode 100644 src/app/qbs-setup-toolchains/gccprobe.cpp create mode 100644 src/app/qbs-setup-toolchains/gccprobe.h create mode 100644 src/app/qbs-setup-toolchains/iarewprobe.cpp create mode 100644 src/app/qbs-setup-toolchains/iarewprobe.h create mode 100644 src/app/qbs-setup-toolchains/keilprobe.cpp create mode 100644 src/app/qbs-setup-toolchains/keilprobe.h create mode 100644 src/app/qbs-setup-toolchains/main.cpp create mode 100644 src/app/qbs-setup-toolchains/msvcprobe.cpp create mode 100644 src/app/qbs-setup-toolchains/msvcprobe.h create mode 100644 src/app/qbs-setup-toolchains/probe.cpp create mode 100644 src/app/qbs-setup-toolchains/probe.h create mode 100644 src/app/qbs-setup-toolchains/qbs-setup-toolchains.exe.manifest create mode 100644 src/app/qbs-setup-toolchains/qbs-setup-toolchains.pro create mode 100644 src/app/qbs-setup-toolchains/qbs-setup-toolchains.qbs create mode 100644 src/app/qbs-setup-toolchains/qbs-setup-toolchains.rc create mode 100644 src/app/qbs-setup-toolchains/sdccprobe.cpp create mode 100644 src/app/qbs-setup-toolchains/sdccprobe.h create mode 100644 src/app/qbs-setup-toolchains/xcodeprobe.cpp create mode 100644 src/app/qbs-setup-toolchains/xcodeprobe.h create mode 100644 src/app/qbs/application.cpp create mode 100644 src/app/qbs/application.h create mode 100644 src/app/qbs/commandlinefrontend.cpp create mode 100644 src/app/qbs/commandlinefrontend.h create mode 100644 src/app/qbs/consoleprogressobserver.cpp create mode 100644 src/app/qbs/consoleprogressobserver.h create mode 100644 src/app/qbs/ctrlchandler.cpp create mode 100644 src/app/qbs/ctrlchandler.h create mode 100644 src/app/qbs/main.cpp create mode 100644 src/app/qbs/parser/commandlineoption.cpp create mode 100644 src/app/qbs/parser/commandlineoption.h create mode 100644 src/app/qbs/parser/commandlineoptionpool.cpp create mode 100644 src/app/qbs/parser/commandlineoptionpool.h create mode 100644 src/app/qbs/parser/commandlineparser.cpp create mode 100644 src/app/qbs/parser/commandlineparser.h create mode 100644 src/app/qbs/parser/commandpool.cpp create mode 100644 src/app/qbs/parser/commandpool.h create mode 100644 src/app/qbs/parser/commandtype.h create mode 100644 src/app/qbs/parser/parser.pri create mode 100644 src/app/qbs/parser/parsercommand.cpp create mode 100644 src/app/qbs/parser/parsercommand.h create mode 100644 src/app/qbs/qbs.pro create mode 100644 src/app/qbs/qbs.qbs create mode 100644 src/app/qbs/qbstool.cpp create mode 100644 src/app/qbs/qbstool.h create mode 100644 src/app/qbs/session.cpp create mode 100644 src/app/qbs/session.h create mode 100644 src/app/qbs/sessionpacket.cpp create mode 100644 src/app/qbs/sessionpacket.h create mode 100644 src/app/qbs/sessionpacketreader.cpp create mode 100644 src/app/qbs/sessionpacketreader.h create mode 100644 src/app/qbs/status.cpp create mode 100644 src/app/qbs/status.h create mode 100644 src/app/qbs/stdinreader.cpp create mode 100644 src/app/qbs/stdinreader.h create mode 100644 src/app/shared/logging/coloredoutput.cpp create mode 100644 src/app/shared/logging/coloredoutput.h create mode 100644 src/app/shared/logging/consolelogger.cpp create mode 100644 src/app/shared/logging/consolelogger.h create mode 100644 src/app/shared/logging/logging.pri create mode 100644 src/install_prefix.pri create mode 100644 src/lib/bundledlibs.pri create mode 100644 src/lib/corelib/api/api.pri create mode 100644 src/lib/corelib/api/changeset.cpp create mode 100644 src/lib/corelib/api/changeset.h create mode 100644 src/lib/corelib/api/internaljobs.cpp create mode 100644 src/lib/corelib/api/internaljobs.h create mode 100644 src/lib/corelib/api/jobs.cpp create mode 100644 src/lib/corelib/api/jobs.h create mode 100644 src/lib/corelib/api/languageinfo.cpp create mode 100644 src/lib/corelib/api/languageinfo.h create mode 100644 src/lib/corelib/api/project.cpp create mode 100644 src/lib/corelib/api/project.h create mode 100644 src/lib/corelib/api/project_p.h create mode 100644 src/lib/corelib/api/projectdata.cpp create mode 100644 src/lib/corelib/api/projectdata.h create mode 100644 src/lib/corelib/api/projectdata_p.h create mode 100644 src/lib/corelib/api/projectfileupdater.cpp create mode 100644 src/lib/corelib/api/projectfileupdater.h create mode 100644 src/lib/corelib/api/propertymap_p.h create mode 100644 src/lib/corelib/api/qmljsrewriter.cpp create mode 100644 src/lib/corelib/api/qmljsrewriter.h create mode 100644 src/lib/corelib/api/rulecommand.cpp create mode 100644 src/lib/corelib/api/rulecommand.h create mode 100644 src/lib/corelib/api/rulecommand_p.h create mode 100644 src/lib/corelib/api/runenvironment.cpp create mode 100644 src/lib/corelib/api/runenvironment.h create mode 100644 src/lib/corelib/api/transformerdata.cpp create mode 100644 src/lib/corelib/api/transformerdata.h create mode 100644 src/lib/corelib/api/transformerdata_p.h create mode 100644 src/lib/corelib/buildgraph/abstractcommandexecutor.cpp create mode 100644 src/lib/corelib/buildgraph/abstractcommandexecutor.h create mode 100644 src/lib/corelib/buildgraph/artifact.cpp create mode 100644 src/lib/corelib/buildgraph/artifact.h create mode 100644 src/lib/corelib/buildgraph/artifactcleaner.cpp create mode 100644 src/lib/corelib/buildgraph/artifactcleaner.h create mode 100644 src/lib/corelib/buildgraph/artifactsscriptvalue.cpp create mode 100644 src/lib/corelib/buildgraph/artifactsscriptvalue.h create mode 100644 src/lib/corelib/buildgraph/artifactvisitor.cpp create mode 100644 src/lib/corelib/buildgraph/artifactvisitor.h create mode 100644 src/lib/corelib/buildgraph/buildgraph.cpp create mode 100644 src/lib/corelib/buildgraph/buildgraph.h create mode 100644 src/lib/corelib/buildgraph/buildgraph.pri create mode 100644 src/lib/corelib/buildgraph/buildgraphloader.cpp create mode 100644 src/lib/corelib/buildgraph/buildgraphloader.h create mode 100644 src/lib/corelib/buildgraph/buildgraphnode.cpp create mode 100644 src/lib/corelib/buildgraph/buildgraphnode.h create mode 100644 src/lib/corelib/buildgraph/buildgraphvisitor.h create mode 100644 src/lib/corelib/buildgraph/cycledetector.cpp create mode 100644 src/lib/corelib/buildgraph/cycledetector.h create mode 100644 src/lib/corelib/buildgraph/dependencyparametersscriptvalue.cpp create mode 100644 src/lib/corelib/buildgraph/dependencyparametersscriptvalue.h create mode 100644 src/lib/corelib/buildgraph/depscanner.cpp create mode 100644 src/lib/corelib/buildgraph/depscanner.h create mode 100644 src/lib/corelib/buildgraph/emptydirectoriesremover.cpp create mode 100644 src/lib/corelib/buildgraph/emptydirectoriesremover.h create mode 100644 src/lib/corelib/buildgraph/environmentscriptrunner.cpp create mode 100644 src/lib/corelib/buildgraph/environmentscriptrunner.h create mode 100644 src/lib/corelib/buildgraph/executor.cpp create mode 100644 src/lib/corelib/buildgraph/executor.h create mode 100644 src/lib/corelib/buildgraph/executorjob.cpp create mode 100644 src/lib/corelib/buildgraph/executorjob.h create mode 100644 src/lib/corelib/buildgraph/filedependency.cpp create mode 100644 src/lib/corelib/buildgraph/filedependency.h create mode 100644 src/lib/corelib/buildgraph/forward_decls.h create mode 100644 src/lib/corelib/buildgraph/inputartifactscanner.cpp create mode 100644 src/lib/corelib/buildgraph/inputartifactscanner.h create mode 100644 src/lib/corelib/buildgraph/jscommandexecutor.cpp create mode 100644 src/lib/corelib/buildgraph/jscommandexecutor.h create mode 100644 src/lib/corelib/buildgraph/nodeset.cpp create mode 100644 src/lib/corelib/buildgraph/nodeset.h create mode 100644 src/lib/corelib/buildgraph/nodetreedumper.cpp create mode 100644 src/lib/corelib/buildgraph/nodetreedumper.h create mode 100644 src/lib/corelib/buildgraph/processcommandexecutor.cpp create mode 100644 src/lib/corelib/buildgraph/processcommandexecutor.h create mode 100644 src/lib/corelib/buildgraph/productbuilddata.cpp create mode 100644 src/lib/corelib/buildgraph/productbuilddata.h create mode 100644 src/lib/corelib/buildgraph/productinstaller.cpp create mode 100644 src/lib/corelib/buildgraph/productinstaller.h create mode 100644 src/lib/corelib/buildgraph/projectbuilddata.cpp create mode 100644 src/lib/corelib/buildgraph/projectbuilddata.h create mode 100644 src/lib/corelib/buildgraph/qtmocscanner.cpp create mode 100644 src/lib/corelib/buildgraph/qtmocscanner.h create mode 100644 src/lib/corelib/buildgraph/rawscanneddependency.cpp create mode 100644 src/lib/corelib/buildgraph/rawscanneddependency.h create mode 100644 src/lib/corelib/buildgraph/rawscanresults.cpp create mode 100644 src/lib/corelib/buildgraph/rawscanresults.h create mode 100644 src/lib/corelib/buildgraph/requestedartifacts.cpp create mode 100644 src/lib/corelib/buildgraph/requestedartifacts.h create mode 100644 src/lib/corelib/buildgraph/requesteddependencies.cpp create mode 100644 src/lib/corelib/buildgraph/requesteddependencies.h create mode 100644 src/lib/corelib/buildgraph/rescuableartifactdata.h create mode 100644 src/lib/corelib/buildgraph/rulecommands.cpp create mode 100644 src/lib/corelib/buildgraph/rulecommands.h create mode 100644 src/lib/corelib/buildgraph/rulegraph.cpp create mode 100644 src/lib/corelib/buildgraph/rulegraph.h create mode 100644 src/lib/corelib/buildgraph/rulenode.cpp create mode 100644 src/lib/corelib/buildgraph/rulenode.h create mode 100644 src/lib/corelib/buildgraph/rulesapplicator.cpp create mode 100644 src/lib/corelib/buildgraph/rulesapplicator.h create mode 100644 src/lib/corelib/buildgraph/rulesevaluationcontext.cpp create mode 100644 src/lib/corelib/buildgraph/rulesevaluationcontext.h create mode 100644 src/lib/corelib/buildgraph/scriptclasspropertyiterator.h create mode 100644 src/lib/corelib/buildgraph/timestampsupdater.cpp create mode 100644 src/lib/corelib/buildgraph/timestampsupdater.h create mode 100644 src/lib/corelib/buildgraph/transformer.cpp create mode 100644 src/lib/corelib/buildgraph/transformer.h create mode 100644 src/lib/corelib/buildgraph/transformerchangetracking.cpp create mode 100644 src/lib/corelib/buildgraph/transformerchangetracking.h create mode 100644 src/lib/corelib/corelib.pro create mode 100644 src/lib/corelib/corelib.qbs create mode 100644 src/lib/corelib/generators/generatableprojectiterator.cpp create mode 100644 src/lib/corelib/generators/generatableprojectiterator.h create mode 100644 src/lib/corelib/generators/generator.cpp create mode 100644 src/lib/corelib/generators/generator.h create mode 100644 src/lib/corelib/generators/generatordata.cpp create mode 100644 src/lib/corelib/generators/generatordata.h create mode 100644 src/lib/corelib/generators/generators.pri create mode 100644 src/lib/corelib/generators/generatorutils.cpp create mode 100644 src/lib/corelib/generators/generatorutils.h create mode 100644 src/lib/corelib/generators/generatorversioninfo.cpp create mode 100644 src/lib/corelib/generators/generatorversioninfo.h create mode 100644 src/lib/corelib/generators/igeneratableprojectvisitor.h create mode 100644 src/lib/corelib/generators/ixmlnodevisitor.h create mode 100644 src/lib/corelib/generators/xmlproject.cpp create mode 100644 src/lib/corelib/generators/xmlproject.h create mode 100644 src/lib/corelib/generators/xmlprojectwriter.cpp create mode 100644 src/lib/corelib/generators/xmlprojectwriter.h create mode 100644 src/lib/corelib/generators/xmlproperty.cpp create mode 100644 src/lib/corelib/generators/xmlproperty.h create mode 100644 src/lib/corelib/generators/xmlpropertygroup.cpp create mode 100644 src/lib/corelib/generators/xmlpropertygroup.h create mode 100644 src/lib/corelib/generators/xmlworkspace.cpp create mode 100644 src/lib/corelib/generators/xmlworkspace.h create mode 100644 src/lib/corelib/generators/xmlworkspacewriter.cpp create mode 100644 src/lib/corelib/generators/xmlworkspacewriter.h create mode 100644 src/lib/corelib/jsextensions/binaryfile.cpp create mode 100644 src/lib/corelib/jsextensions/domxml.cpp create mode 100644 src/lib/corelib/jsextensions/environmentextension.cpp create mode 100644 src/lib/corelib/jsextensions/file.cpp create mode 100644 src/lib/corelib/jsextensions/fileinfoextension.cpp create mode 100644 src/lib/corelib/jsextensions/jsextensions.cpp create mode 100644 src/lib/corelib/jsextensions/jsextensions.h create mode 100644 src/lib/corelib/jsextensions/jsextensions.pri create mode 100644 src/lib/corelib/jsextensions/moduleproperties.cpp create mode 100644 src/lib/corelib/jsextensions/moduleproperties.h create mode 100644 src/lib/corelib/jsextensions/process.cpp create mode 100644 src/lib/corelib/jsextensions/propertylist.cpp create mode 100644 src/lib/corelib/jsextensions/propertylist.h create mode 100644 src/lib/corelib/jsextensions/propertylist.mm create mode 100644 src/lib/corelib/jsextensions/propertylistutils.h create mode 100644 src/lib/corelib/jsextensions/propertylistutils.mm create mode 100644 src/lib/corelib/jsextensions/temporarydir.cpp create mode 100644 src/lib/corelib/jsextensions/textfile.cpp create mode 100644 src/lib/corelib/jsextensions/utilitiesextension.cpp create mode 100644 src/lib/corelib/language/artifactproperties.cpp create mode 100644 src/lib/corelib/language/artifactproperties.h create mode 100644 src/lib/corelib/language/astimportshandler.cpp create mode 100644 src/lib/corelib/language/astimportshandler.h create mode 100644 src/lib/corelib/language/astpropertiesitemhandler.cpp create mode 100644 src/lib/corelib/language/astpropertiesitemhandler.h create mode 100644 src/lib/corelib/language/asttools.cpp create mode 100644 src/lib/corelib/language/asttools.h create mode 100644 src/lib/corelib/language/builtindeclarations.cpp create mode 100644 src/lib/corelib/language/builtindeclarations.h create mode 100644 src/lib/corelib/language/deprecationinfo.h create mode 100644 src/lib/corelib/language/evaluationdata.h create mode 100644 src/lib/corelib/language/evaluator.cpp create mode 100644 src/lib/corelib/language/evaluator.h create mode 100644 src/lib/corelib/language/evaluatorscriptclass.cpp create mode 100755 src/lib/corelib/language/evaluatorscriptclass.h create mode 100644 src/lib/corelib/language/filecontext.cpp create mode 100644 src/lib/corelib/language/filecontext.h create mode 100644 src/lib/corelib/language/filecontextbase.cpp create mode 100644 src/lib/corelib/language/filecontextbase.h create mode 100644 src/lib/corelib/language/filetags.cpp create mode 100644 src/lib/corelib/language/filetags.h create mode 100644 src/lib/corelib/language/forward_decls.h create mode 100644 src/lib/corelib/language/identifiersearch.cpp create mode 100644 src/lib/corelib/language/identifiersearch.h create mode 100644 src/lib/corelib/language/item.cpp create mode 100644 src/lib/corelib/language/item.h create mode 100644 src/lib/corelib/language/itemdeclaration.cpp create mode 100644 src/lib/corelib/language/itemdeclaration.h create mode 100644 src/lib/corelib/language/itemobserver.h create mode 100644 src/lib/corelib/language/itempool.cpp create mode 100644 src/lib/corelib/language/itempool.h create mode 100644 src/lib/corelib/language/itemreader.cpp create mode 100644 src/lib/corelib/language/itemreader.h create mode 100644 src/lib/corelib/language/itemreaderastvisitor.cpp create mode 100644 src/lib/corelib/language/itemreaderastvisitor.h create mode 100644 src/lib/corelib/language/itemreadervisitorstate.cpp create mode 100644 src/lib/corelib/language/itemreadervisitorstate.h create mode 100644 src/lib/corelib/language/itemtype.h create mode 100644 src/lib/corelib/language/jsimports.h create mode 100644 src/lib/corelib/language/language.cpp create mode 100644 src/lib/corelib/language/language.h create mode 100644 src/lib/corelib/language/language.pri create mode 100644 src/lib/corelib/language/loader.cpp create mode 100644 src/lib/corelib/language/loader.h create mode 100644 src/lib/corelib/language/moduleloader.cpp create mode 100644 src/lib/corelib/language/moduleloader.h create mode 100644 src/lib/corelib/language/modulemerger.cpp create mode 100644 src/lib/corelib/language/modulemerger.h create mode 100644 src/lib/corelib/language/moduleproviderinfo.h create mode 100644 src/lib/corelib/language/preparescriptobserver.cpp create mode 100644 src/lib/corelib/language/preparescriptobserver.h create mode 100644 src/lib/corelib/language/projectresolver.cpp create mode 100644 src/lib/corelib/language/projectresolver.h create mode 100644 src/lib/corelib/language/property.cpp create mode 100644 src/lib/corelib/language/property.h create mode 100644 src/lib/corelib/language/propertydeclaration.cpp create mode 100644 src/lib/corelib/language/propertydeclaration.h create mode 100644 src/lib/corelib/language/propertymapinternal.cpp create mode 100644 src/lib/corelib/language/propertymapinternal.h create mode 100644 src/lib/corelib/language/qualifiedid.cpp create mode 100644 src/lib/corelib/language/qualifiedid.h create mode 100644 src/lib/corelib/language/resolvedfilecontext.cpp create mode 100644 src/lib/corelib/language/resolvedfilecontext.h create mode 100644 src/lib/corelib/language/scriptengine.cpp create mode 100644 src/lib/corelib/language/scriptengine.h create mode 100644 src/lib/corelib/language/scriptimporter.cpp create mode 100644 src/lib/corelib/language/scriptimporter.h create mode 100644 src/lib/corelib/language/scriptpropertyobserver.cpp create mode 100644 src/lib/corelib/language/scriptpropertyobserver.h create mode 100644 src/lib/corelib/language/value.cpp create mode 100644 src/lib/corelib/language/value.h create mode 100644 src/lib/corelib/logging/categories.cpp create mode 100644 src/lib/corelib/logging/categories.h create mode 100644 src/lib/corelib/logging/ilogsink.cpp create mode 100644 src/lib/corelib/logging/ilogsink.h create mode 100644 src/lib/corelib/logging/logger.cpp create mode 100644 src/lib/corelib/logging/logger.h create mode 100644 src/lib/corelib/logging/logging.pri create mode 100644 src/lib/corelib/logging/translator.h create mode 100644 src/lib/corelib/parser/parser.pri create mode 100644 src/lib/corelib/parser/qmlerror.cpp create mode 100644 src/lib/corelib/parser/qmlerror.h create mode 100644 src/lib/corelib/parser/qmljs.g create mode 100644 src/lib/corelib/parser/qmljsast.cpp create mode 100644 src/lib/corelib/parser/qmljsast_p.h create mode 100644 src/lib/corelib/parser/qmljsastfwd_p.h create mode 100644 src/lib/corelib/parser/qmljsastvisitor.cpp create mode 100644 src/lib/corelib/parser/qmljsastvisitor_p.h create mode 100644 src/lib/corelib/parser/qmljsengine_p.cpp create mode 100644 src/lib/corelib/parser/qmljsengine_p.h create mode 100644 src/lib/corelib/parser/qmljsglobal_p.h create mode 100644 src/lib/corelib/parser/qmljsgrammar.cpp create mode 100644 src/lib/corelib/parser/qmljsgrammar_p.h create mode 100644 src/lib/corelib/parser/qmljskeywords_p.h create mode 100644 src/lib/corelib/parser/qmljslexer.cpp create mode 100644 src/lib/corelib/parser/qmljslexer_p.h create mode 100644 src/lib/corelib/parser/qmljsmemorypool_p.h create mode 100644 src/lib/corelib/parser/qmljsparser.cpp create mode 100644 src/lib/corelib/parser/qmljsparser_p.h create mode 100644 src/lib/corelib/qbs.h create mode 100644 src/lib/corelib/tools/applecodesignutils.cpp create mode 100644 src/lib/corelib/tools/applecodesignutils.h create mode 100644 src/lib/corelib/tools/architectures.cpp create mode 100644 src/lib/corelib/tools/architectures.h create mode 100644 src/lib/corelib/tools/buildgraphlocker.cpp create mode 100644 src/lib/corelib/tools/buildgraphlocker.h create mode 100644 src/lib/corelib/tools/buildoptions.cpp create mode 100644 src/lib/corelib/tools/buildoptions.h create mode 100644 src/lib/corelib/tools/clangclinfo.cpp create mode 100644 src/lib/corelib/tools/clangclinfo.h create mode 100644 src/lib/corelib/tools/cleanoptions.cpp create mode 100644 src/lib/corelib/tools/cleanoptions.h create mode 100644 src/lib/corelib/tools/codelocation.cpp create mode 100644 src/lib/corelib/tools/codelocation.h create mode 100644 src/lib/corelib/tools/commandechomode.cpp create mode 100644 src/lib/corelib/tools/commandechomode.h create mode 100644 src/lib/corelib/tools/dynamictypecheck.h create mode 100644 src/lib/corelib/tools/error.cpp create mode 100644 src/lib/corelib/tools/error.h create mode 100644 src/lib/corelib/tools/executablefinder.cpp create mode 100644 src/lib/corelib/tools/executablefinder.h create mode 100644 src/lib/corelib/tools/fileinfo.cpp create mode 100644 src/lib/corelib/tools/fileinfo.h create mode 100644 src/lib/corelib/tools/filesaver.cpp create mode 100644 src/lib/corelib/tools/filesaver.h create mode 100644 src/lib/corelib/tools/filetime.cpp create mode 100644 src/lib/corelib/tools/filetime.h create mode 100644 src/lib/corelib/tools/generateoptions.cpp create mode 100644 src/lib/corelib/tools/generateoptions.h create mode 100644 src/lib/corelib/tools/hostosinfo.h create mode 100644 src/lib/corelib/tools/id.cpp create mode 100644 src/lib/corelib/tools/id.h create mode 100644 src/lib/corelib/tools/installoptions.cpp create mode 100644 src/lib/corelib/tools/installoptions.h create mode 100644 src/lib/corelib/tools/iosutils.h create mode 100644 src/lib/corelib/tools/joblimits.cpp create mode 100644 src/lib/corelib/tools/joblimits.h create mode 100644 src/lib/corelib/tools/jsliterals.cpp create mode 100644 src/lib/corelib/tools/jsliterals.h create mode 100644 src/lib/corelib/tools/jsonhelper.h create mode 100644 src/lib/corelib/tools/launcherinterface.cpp create mode 100644 src/lib/corelib/tools/launcherinterface.h create mode 100644 src/lib/corelib/tools/launcherpackets.cpp create mode 100644 src/lib/corelib/tools/launcherpackets.h create mode 100644 src/lib/corelib/tools/launchersocket.cpp create mode 100644 src/lib/corelib/tools/launchersocket.h create mode 100644 src/lib/corelib/tools/msvcinfo.cpp create mode 100644 src/lib/corelib/tools/msvcinfo.h create mode 100644 src/lib/corelib/tools/pathutils.h create mode 100644 src/lib/corelib/tools/persistence.cpp create mode 100644 src/lib/corelib/tools/persistence.h create mode 100644 src/lib/corelib/tools/preferences.cpp create mode 100644 src/lib/corelib/tools/preferences.h create mode 100644 src/lib/corelib/tools/processresult.cpp create mode 100644 src/lib/corelib/tools/processresult.h create mode 100644 src/lib/corelib/tools/processresult_p.h create mode 100644 src/lib/corelib/tools/processutils.cpp create mode 100644 src/lib/corelib/tools/processutils.h create mode 100644 src/lib/corelib/tools/profile.cpp create mode 100644 src/lib/corelib/tools/profile.h create mode 100644 src/lib/corelib/tools/profiling.cpp create mode 100644 src/lib/corelib/tools/profiling.h create mode 100644 src/lib/corelib/tools/progressobserver.cpp create mode 100644 src/lib/corelib/tools/progressobserver.h create mode 100644 src/lib/corelib/tools/projectgeneratormanager.cpp create mode 100644 src/lib/corelib/tools/projectgeneratormanager.h create mode 100644 src/lib/corelib/tools/qbs_export.h create mode 100644 src/lib/corelib/tools/qbsassert.cpp create mode 100644 src/lib/corelib/tools/qbsassert.h create mode 100644 src/lib/corelib/tools/qbspluginmanager.cpp create mode 100644 src/lib/corelib/tools/qbspluginmanager.h create mode 100644 src/lib/corelib/tools/qbsprocess.cpp create mode 100644 src/lib/corelib/tools/qbsprocess.h create mode 100644 src/lib/corelib/tools/qttools.cpp create mode 100644 src/lib/corelib/tools/qttools.h create mode 100644 src/lib/corelib/tools/scannerpluginmanager.cpp create mode 100644 src/lib/corelib/tools/scannerpluginmanager.h create mode 100644 src/lib/corelib/tools/scripttools.cpp create mode 100644 src/lib/corelib/tools/scripttools.h create mode 100644 src/lib/corelib/tools/set.h create mode 100644 src/lib/corelib/tools/settings.cpp create mode 100644 src/lib/corelib/tools/settings.h create mode 100644 src/lib/corelib/tools/settingscreator.cpp create mode 100644 src/lib/corelib/tools/settingscreator.h create mode 100644 src/lib/corelib/tools/settingsmodel.cpp create mode 100644 src/lib/corelib/tools/settingsmodel.h create mode 100644 src/lib/corelib/tools/settingsrepresentation.cpp create mode 100644 src/lib/corelib/tools/settingsrepresentation.h create mode 100644 src/lib/corelib/tools/setupprojectparameters.cpp create mode 100644 src/lib/corelib/tools/setupprojectparameters.h create mode 100644 src/lib/corelib/tools/shellutils.cpp create mode 100644 src/lib/corelib/tools/shellutils.h create mode 100644 src/lib/corelib/tools/stlutils.h create mode 100644 src/lib/corelib/tools/stringconstants.h create mode 100644 src/lib/corelib/tools/stringutils.h create mode 100644 src/lib/corelib/tools/toolchains.cpp create mode 100644 src/lib/corelib/tools/toolchains.h create mode 100644 src/lib/corelib/tools/tools.pri create mode 100644 src/lib/corelib/tools/version.cpp create mode 100644 src/lib/corelib/tools/version.h create mode 100644 src/lib/corelib/tools/visualstudioversioninfo.cpp create mode 100644 src/lib/corelib/tools/visualstudioversioninfo.h create mode 100644 src/lib/corelib/tools/vsenvironmentdetector.cpp create mode 100644 src/lib/corelib/tools/vsenvironmentdetector.h create mode 100644 src/lib/corelib/tools/weakpointer.h create mode 100644 src/lib/corelib/use_corelib.pri create mode 100644 src/lib/corelib/use_installed_corelib.pri create mode 100644 src/lib/library.pri create mode 100644 src/lib/library_base.pri create mode 100644 src/lib/libs.qbs create mode 100644 src/lib/msbuild/io/msbuildprojectwriter.cpp create mode 100644 src/lib/msbuild/io/msbuildprojectwriter.h create mode 100644 src/lib/msbuild/io/visualstudiosolutionwriter.cpp create mode 100644 src/lib/msbuild/io/visualstudiosolutionwriter.h create mode 100644 src/lib/msbuild/msbuild.pro create mode 100644 src/lib/msbuild/msbuild.qbs create mode 100644 src/lib/msbuild/msbuild/imsbuildgroup.cpp create mode 100644 src/lib/msbuild/msbuild/imsbuildgroup.h create mode 100644 src/lib/msbuild/msbuild/imsbuildnode.cpp create mode 100644 src/lib/msbuild/msbuild/imsbuildnode.h create mode 100644 src/lib/msbuild/msbuild/imsbuildnodevisitor.h create mode 100644 src/lib/msbuild/msbuild/imsbuildproperty.cpp create mode 100644 src/lib/msbuild/msbuild/imsbuildproperty.h create mode 100644 src/lib/msbuild/msbuild/items/msbuildclcompile.cpp create mode 100644 src/lib/msbuild/msbuild/items/msbuildclcompile.h create mode 100644 src/lib/msbuild/msbuild/items/msbuildclinclude.cpp create mode 100644 src/lib/msbuild/msbuild/items/msbuildclinclude.h create mode 100644 src/lib/msbuild/msbuild/items/msbuildfileitem.cpp create mode 100644 src/lib/msbuild/msbuild/items/msbuildfileitem.h create mode 100644 src/lib/msbuild/msbuild/items/msbuildfilter.cpp create mode 100644 src/lib/msbuild/msbuild/items/msbuildfilter.h create mode 100644 src/lib/msbuild/msbuild/items/msbuildlink.cpp create mode 100644 src/lib/msbuild/msbuild/items/msbuildlink.h create mode 100644 src/lib/msbuild/msbuild/items/msbuildnone.cpp create mode 100644 src/lib/msbuild/msbuild/items/msbuildnone.h create mode 100644 src/lib/msbuild/msbuild/msbuildimport.cpp create mode 100644 src/lib/msbuild/msbuild/msbuildimport.h create mode 100644 src/lib/msbuild/msbuild/msbuildimportgroup.cpp create mode 100644 src/lib/msbuild/msbuild/msbuildimportgroup.h create mode 100644 src/lib/msbuild/msbuild/msbuilditem.cpp create mode 100644 src/lib/msbuild/msbuild/msbuilditem.h create mode 100644 src/lib/msbuild/msbuild/msbuilditemdefinitiongroup.cpp create mode 100644 src/lib/msbuild/msbuild/msbuilditemdefinitiongroup.h create mode 100644 src/lib/msbuild/msbuild/msbuilditemgroup.cpp create mode 100644 src/lib/msbuild/msbuild/msbuilditemgroup.h create mode 100644 src/lib/msbuild/msbuild/msbuilditemmetadata.cpp create mode 100644 src/lib/msbuild/msbuild/msbuilditemmetadata.h create mode 100644 src/lib/msbuild/msbuild/msbuildproject.cpp create mode 100644 src/lib/msbuild/msbuild/msbuildproject.h create mode 100644 src/lib/msbuild/msbuild/msbuildproperty.cpp create mode 100644 src/lib/msbuild/msbuild/msbuildproperty.h create mode 100644 src/lib/msbuild/msbuild/msbuildpropertygroup.cpp create mode 100644 src/lib/msbuild/msbuild/msbuildpropertygroup.h create mode 100644 src/lib/msbuild/solution/ivisualstudiosolutionproject.cpp create mode 100644 src/lib/msbuild/solution/ivisualstudiosolutionproject.h create mode 100644 src/lib/msbuild/solution/visualstudiosolution.cpp create mode 100644 src/lib/msbuild/solution/visualstudiosolution.h create mode 100644 src/lib/msbuild/solution/visualstudiosolutionfileproject.cpp create mode 100644 src/lib/msbuild/solution/visualstudiosolutionfileproject.h create mode 100644 src/lib/msbuild/solution/visualstudiosolutionfolderproject.cpp create mode 100644 src/lib/msbuild/solution/visualstudiosolutionfolderproject.h create mode 100644 src/lib/msbuild/solution/visualstudiosolutionglobalsection.cpp create mode 100644 src/lib/msbuild/solution/visualstudiosolutionglobalsection.h create mode 100644 src/lib/msbuild/use_installed_msbuild.pri create mode 100644 src/lib/msbuild/use_msbuild.pri create mode 100644 src/lib/scriptengine/include/QtScript/qtscriptglobal.h create mode 100644 src/lib/scriptengine/scriptengine.pro create mode 100644 src/lib/scriptengine/scriptengine.qbs create mode 100644 src/lib/scriptengine/use_scriptengine.pri create mode 100644 src/lib/staticlibrary.pri create mode 100644 src/libexec/libexec.pri create mode 100644 src/libexec/libexec.pro create mode 100644 src/libexec/libexec.qbs create mode 100644 src/libexec/qbs_processlauncher/launcherlogging.cpp create mode 100644 src/libexec/qbs_processlauncher/launcherlogging.h create mode 100644 src/libexec/qbs_processlauncher/launchersockethandler.cpp create mode 100644 src/libexec/qbs_processlauncher/launchersockethandler.h create mode 100644 src/libexec/qbs_processlauncher/processlauncher-main.cpp create mode 100644 src/libexec/qbs_processlauncher/qbs_processlauncher.pro create mode 100644 src/libexec/qbs_processlauncher/qbs_processlauncher.qbs create mode 100644 src/library_dirname.pri create mode 100644 src/packages/archive/archive.qbs create mode 100644 src/packages/chocolatey/chocolatey.qbs create mode 100644 src/packages/chocolatey/chocolateyinstall.ps1 create mode 100644 src/packages/chocolatey/icon.png create mode 100644 src/packages/chocolatey/qbs.nuspec create mode 100644 src/packages/packages.qbs create mode 100644 src/plugins/generator/clangcompilationdb/clangcompilationdb.pri create mode 100644 src/plugins/generator/clangcompilationdb/clangcompilationdb.pro create mode 100644 src/plugins/generator/clangcompilationdb/clangcompilationdb.qbs create mode 100644 src/plugins/generator/clangcompilationdb/clangcompilationdbgenerator.cpp create mode 100644 src/plugins/generator/clangcompilationdb/clangcompilationdbgenerator.h create mode 100644 src/plugins/generator/clangcompilationdb/clangcompilationdbgeneratorplugin.cpp create mode 100644 src/plugins/generator/generator.pro create mode 100644 src/plugins/generator/iarew/archs/arm/armarchiversettingsgroup_v8.cpp create mode 100644 src/plugins/generator/iarew/archs/arm/armarchiversettingsgroup_v8.h create mode 100644 src/plugins/generator/iarew/archs/arm/armassemblersettingsgroup_v8.cpp create mode 100644 src/plugins/generator/iarew/archs/arm/armassemblersettingsgroup_v8.h create mode 100644 src/plugins/generator/iarew/archs/arm/armbuildconfigurationgroup_v8.cpp create mode 100644 src/plugins/generator/iarew/archs/arm/armbuildconfigurationgroup_v8.h create mode 100644 src/plugins/generator/iarew/archs/arm/armcompilersettingsgroup_v8.cpp create mode 100644 src/plugins/generator/iarew/archs/arm/armcompilersettingsgroup_v8.h create mode 100644 src/plugins/generator/iarew/archs/arm/armgeneralsettingsgroup_v8.cpp create mode 100644 src/plugins/generator/iarew/archs/arm/armgeneralsettingsgroup_v8.h create mode 100644 src/plugins/generator/iarew/archs/arm/armlinkersettingsgroup_v8.cpp create mode 100644 src/plugins/generator/iarew/archs/arm/armlinkersettingsgroup_v8.h create mode 100644 src/plugins/generator/iarew/archs/avr/avrarchiversettingsgroup_v7.cpp create mode 100644 src/plugins/generator/iarew/archs/avr/avrarchiversettingsgroup_v7.h create mode 100644 src/plugins/generator/iarew/archs/avr/avrassemblersettingsgroup_v7.cpp create mode 100644 src/plugins/generator/iarew/archs/avr/avrassemblersettingsgroup_v7.h create mode 100644 src/plugins/generator/iarew/archs/avr/avrbuildconfigurationgroup_v7.cpp create mode 100644 src/plugins/generator/iarew/archs/avr/avrbuildconfigurationgroup_v7.h create mode 100644 src/plugins/generator/iarew/archs/avr/avrcompilersettingsgroup_v7.cpp create mode 100644 src/plugins/generator/iarew/archs/avr/avrcompilersettingsgroup_v7.h create mode 100644 src/plugins/generator/iarew/archs/avr/avrgeneralsettingsgroup_v7.cpp create mode 100644 src/plugins/generator/iarew/archs/avr/avrgeneralsettingsgroup_v7.h create mode 100644 src/plugins/generator/iarew/archs/avr/avrlinkersettingsgroup_v7.cpp create mode 100644 src/plugins/generator/iarew/archs/avr/avrlinkersettingsgroup_v7.h create mode 100644 src/plugins/generator/iarew/archs/mcs51/mcs51archiversettingsgroup_v10.cpp create mode 100644 src/plugins/generator/iarew/archs/mcs51/mcs51archiversettingsgroup_v10.h create mode 100644 src/plugins/generator/iarew/archs/mcs51/mcs51assemblersettingsgroup_v10.cpp create mode 100644 src/plugins/generator/iarew/archs/mcs51/mcs51assemblersettingsgroup_v10.h create mode 100644 src/plugins/generator/iarew/archs/mcs51/mcs51buildconfigurationgroup_v10.cpp create mode 100644 src/plugins/generator/iarew/archs/mcs51/mcs51buildconfigurationgroup_v10.h create mode 100644 src/plugins/generator/iarew/archs/mcs51/mcs51compilersettingsgroup_v10.cpp create mode 100644 src/plugins/generator/iarew/archs/mcs51/mcs51compilersettingsgroup_v10.h create mode 100644 src/plugins/generator/iarew/archs/mcs51/mcs51generalsettingsgroup_v10.cpp create mode 100644 src/plugins/generator/iarew/archs/mcs51/mcs51generalsettingsgroup_v10.h create mode 100644 src/plugins/generator/iarew/archs/mcs51/mcs51linkersettingsgroup_v10.cpp create mode 100644 src/plugins/generator/iarew/archs/mcs51/mcs51linkersettingsgroup_v10.h create mode 100644 src/plugins/generator/iarew/archs/msp430/msp430archiversettingsgroup_v7.cpp create mode 100644 src/plugins/generator/iarew/archs/msp430/msp430archiversettingsgroup_v7.h create mode 100644 src/plugins/generator/iarew/archs/msp430/msp430assemblersettingsgroup_v7.cpp create mode 100644 src/plugins/generator/iarew/archs/msp430/msp430assemblersettingsgroup_v7.h create mode 100644 src/plugins/generator/iarew/archs/msp430/msp430buildconfigurationgroup_v7.cpp create mode 100644 src/plugins/generator/iarew/archs/msp430/msp430buildconfigurationgroup_v7.h create mode 100644 src/plugins/generator/iarew/archs/msp430/msp430compilersettingsgroup_v7.cpp create mode 100644 src/plugins/generator/iarew/archs/msp430/msp430compilersettingsgroup_v7.h create mode 100644 src/plugins/generator/iarew/archs/msp430/msp430generalsettingsgroup_v7.cpp create mode 100644 src/plugins/generator/iarew/archs/msp430/msp430generalsettingsgroup_v7.h create mode 100644 src/plugins/generator/iarew/archs/msp430/msp430linkersettingsgroup_v7.cpp create mode 100644 src/plugins/generator/iarew/archs/msp430/msp430linkersettingsgroup_v7.h create mode 100644 src/plugins/generator/iarew/archs/stm8/stm8archiversettingsgroup_v3.cpp create mode 100644 src/plugins/generator/iarew/archs/stm8/stm8archiversettingsgroup_v3.h create mode 100644 src/plugins/generator/iarew/archs/stm8/stm8assemblersettingsgroup_v3.cpp create mode 100644 src/plugins/generator/iarew/archs/stm8/stm8assemblersettingsgroup_v3.h create mode 100644 src/plugins/generator/iarew/archs/stm8/stm8buildconfigurationgroup_v3.cpp create mode 100644 src/plugins/generator/iarew/archs/stm8/stm8buildconfigurationgroup_v3.h create mode 100644 src/plugins/generator/iarew/archs/stm8/stm8compilersettingsgroup_v3.cpp create mode 100644 src/plugins/generator/iarew/archs/stm8/stm8compilersettingsgroup_v3.h create mode 100644 src/plugins/generator/iarew/archs/stm8/stm8generalsettingsgroup_v3.cpp create mode 100644 src/plugins/generator/iarew/archs/stm8/stm8generalsettingsgroup_v3.h create mode 100644 src/plugins/generator/iarew/archs/stm8/stm8linkersettingsgroup_v3.cpp create mode 100644 src/plugins/generator/iarew/archs/stm8/stm8linkersettingsgroup_v3.h create mode 100644 src/plugins/generator/iarew/iarew.pri create mode 100644 src/plugins/generator/iarew/iarew.pro create mode 100644 src/plugins/generator/iarew/iarew.qbs create mode 100644 src/plugins/generator/iarew/iarewfileversionproperty.cpp create mode 100644 src/plugins/generator/iarew/iarewfileversionproperty.h create mode 100644 src/plugins/generator/iarew/iarewgenerator.cpp create mode 100644 src/plugins/generator/iarew/iarewgenerator.h create mode 100644 src/plugins/generator/iarew/iarewgeneratorplugin.cpp create mode 100644 src/plugins/generator/iarew/iarewoptionpropertygroup.cpp create mode 100644 src/plugins/generator/iarew/iarewoptionpropertygroup.h create mode 100644 src/plugins/generator/iarew/iarewproject.cpp create mode 100644 src/plugins/generator/iarew/iarewproject.h create mode 100644 src/plugins/generator/iarew/iarewprojectwriter.cpp create mode 100644 src/plugins/generator/iarew/iarewprojectwriter.h create mode 100644 src/plugins/generator/iarew/iarewsettingspropertygroup.cpp create mode 100644 src/plugins/generator/iarew/iarewsettingspropertygroup.h create mode 100644 src/plugins/generator/iarew/iarewsourcefilepropertygroup.cpp create mode 100644 src/plugins/generator/iarew/iarewsourcefilepropertygroup.h create mode 100644 src/plugins/generator/iarew/iarewsourcefilespropertygroup.cpp create mode 100644 src/plugins/generator/iarew/iarewsourcefilespropertygroup.h create mode 100644 src/plugins/generator/iarew/iarewtoolchainpropertygroup.cpp create mode 100644 src/plugins/generator/iarew/iarewtoolchainpropertygroup.h create mode 100644 src/plugins/generator/iarew/iarewutils.cpp create mode 100644 src/plugins/generator/iarew/iarewutils.h create mode 100644 src/plugins/generator/iarew/iarewversioninfo.h create mode 100644 src/plugins/generator/iarew/iarewworkspace.cpp create mode 100644 src/plugins/generator/iarew/iarewworkspace.h create mode 100644 src/plugins/generator/iarew/iarewworkspacewriter.cpp create mode 100644 src/plugins/generator/iarew/iarewworkspacewriter.h create mode 100644 src/plugins/generator/keiluv/archs/arm/armbuildtargetgroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/arm/armbuildtargetgroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/arm/armcommonpropertygroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/arm/armcommonpropertygroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/arm/armdebugoptiongroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/arm/armdebugoptiongroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/arm/armdlloptiongroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/arm/armdlloptiongroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/arm/armtargetassemblergroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/arm/armtargetassemblergroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/arm/armtargetcommonoptionsgroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/arm/armtargetcommonoptionsgroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/arm/armtargetcompilergroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/arm/armtargetcompilergroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/arm/armtargetgroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/arm/armtargetgroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/arm/armtargetlinkergroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/arm/armtargetlinkergroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/arm/armtargetmiscgroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/arm/armtargetmiscgroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/arm/armutilitiesgroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/arm/armutilitiesgroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51buildtargetgroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51buildtargetgroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51commonpropertygroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51commonpropertygroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51debugoptiongroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51debugoptiongroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51dlloptiongroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51dlloptiongroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51targetassemblergroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51targetassemblergroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51targetcommonoptionsgroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51targetcommonoptionsgroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51targetcompilergroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51targetcompilergroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51targetgroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51targetgroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51targetlinkergroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51targetlinkergroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51targetmiscgroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51targetmiscgroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51utilitiesgroup_v5.cpp create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51utilitiesgroup_v5.h create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51utils.cpp create mode 100644 src/plugins/generator/keiluv/archs/mcs51/mcs51utils.h create mode 100644 src/plugins/generator/keiluv/keiluv.pri create mode 100644 src/plugins/generator/keiluv/keiluv.pro create mode 100644 src/plugins/generator/keiluv/keiluv.qbs create mode 100644 src/plugins/generator/keiluv/keiluvconstants.h create mode 100644 src/plugins/generator/keiluv/keiluvfilesgroupspropertygroup.cpp create mode 100644 src/plugins/generator/keiluv/keiluvfilesgroupspropertygroup.h create mode 100644 src/plugins/generator/keiluv/keiluvgenerator.cpp create mode 100644 src/plugins/generator/keiluv/keiluvgenerator.h create mode 100644 src/plugins/generator/keiluv/keiluvgeneratorplugin.cpp create mode 100644 src/plugins/generator/keiluv/keiluvproject.cpp create mode 100644 src/plugins/generator/keiluv/keiluvproject.h create mode 100644 src/plugins/generator/keiluv/keiluvprojectwriter.cpp create mode 100644 src/plugins/generator/keiluv/keiluvprojectwriter.h create mode 100644 src/plugins/generator/keiluv/keiluvutils.cpp create mode 100644 src/plugins/generator/keiluv/keiluvutils.h create mode 100644 src/plugins/generator/keiluv/keiluvversioninfo.h create mode 100644 src/plugins/generator/keiluv/keiluvworkspace.cpp create mode 100644 src/plugins/generator/keiluv/keiluvworkspace.h create mode 100644 src/plugins/generator/keiluv/keiluvworkspacewriter.cpp create mode 100644 src/plugins/generator/keiluv/keiluvworkspacewriter.h create mode 100644 src/plugins/generator/makefilegenerator/makefilegenerator.cpp create mode 100644 src/plugins/generator/makefilegenerator/makefilegenerator.h create mode 100644 src/plugins/generator/makefilegenerator/makefilegenerator.pri create mode 100644 src/plugins/generator/makefilegenerator/makefilegenerator.pro create mode 100644 src/plugins/generator/makefilegenerator/makefilegenerator.qbs create mode 100644 src/plugins/generator/makefilegenerator/makefilegeneratorplugin.cpp create mode 100644 src/plugins/generator/visualstudio/msbuildfiltersproject.cpp create mode 100644 src/plugins/generator/visualstudio/msbuildfiltersproject.h create mode 100644 src/plugins/generator/visualstudio/msbuildqbsgenerateproject.cpp create mode 100644 src/plugins/generator/visualstudio/msbuildqbsgenerateproject.h create mode 100644 src/plugins/generator/visualstudio/msbuildqbsproductproject.cpp create mode 100644 src/plugins/generator/visualstudio/msbuildqbsproductproject.h create mode 100644 src/plugins/generator/visualstudio/msbuildsharedsolutionpropertiesproject.cpp create mode 100644 src/plugins/generator/visualstudio/msbuildsharedsolutionpropertiesproject.h create mode 100644 src/plugins/generator/visualstudio/msbuildsolutionpropertiesproject.cpp create mode 100644 src/plugins/generator/visualstudio/msbuildsolutionpropertiesproject.h create mode 100644 src/plugins/generator/visualstudio/msbuildtargetproject.cpp create mode 100644 src/plugins/generator/visualstudio/msbuildtargetproject.h create mode 100644 src/plugins/generator/visualstudio/msbuildutils.h create mode 100644 src/plugins/generator/visualstudio/visualstudio.pri create mode 100644 src/plugins/generator/visualstudio/visualstudio.pro create mode 100644 src/plugins/generator/visualstudio/visualstudio.qbs create mode 100644 src/plugins/generator/visualstudio/visualstudiogenerator.cpp create mode 100644 src/plugins/generator/visualstudio/visualstudiogenerator.h create mode 100644 src/plugins/generator/visualstudio/visualstudiogeneratorplugin.cpp create mode 100644 src/plugins/generator/visualstudio/visualstudioguidpool.cpp create mode 100644 src/plugins/generator/visualstudio/visualstudioguidpool.h create mode 100644 src/plugins/plugins.pri create mode 100644 src/plugins/plugins.pro create mode 100644 src/plugins/plugins.qbs create mode 100644 src/plugins/qbs_plugin_common.pri create mode 100644 src/plugins/qbsplugin.qbs create mode 100644 src/plugins/scanner/cpp/CPlusPlusForwardDeclarations.h create mode 100644 src/plugins/scanner/cpp/Lexer.cpp create mode 100644 src/plugins/scanner/cpp/Lexer.h create mode 100644 src/plugins/scanner/cpp/Token.cpp create mode 100644 src/plugins/scanner/cpp/Token.h create mode 100644 src/plugins/scanner/cpp/cpp.pri create mode 100644 src/plugins/scanner/cpp/cpp.pro create mode 100644 src/plugins/scanner/cpp/cpp.qbs create mode 100644 src/plugins/scanner/cpp/cpp_global.h create mode 100644 src/plugins/scanner/cpp/cppscanner.cpp create mode 100644 src/plugins/scanner/qt/qt.pri create mode 100644 src/plugins/scanner/qt/qt.pro create mode 100644 src/plugins/scanner/qt/qt.qbs create mode 100644 src/plugins/scanner/qt/qtscanner.cpp create mode 100644 src/plugins/scanner/scanner.h create mode 100644 src/plugins/scanner/scanner.pro create mode 100644 src/plugins/use_plugin.pri create mode 100644 src/shared/bundledqt/bundledqt.qbs create mode 100644 src/shared/bundledqt/qt.conf create mode 100644 src/shared/json/README.md create mode 100644 src/shared/json/json.cpp create mode 100644 src/shared/json/json.h create mode 100644 src/shared/json/json.pri create mode 100644 src/shared/json/json.qbs create mode 100644 src/src.qbs create mode 100644 static-res.pro create mode 100644 static.pro create mode 100644 tests/auto/api/api.pro create mode 100644 tests/auto/api/api.qbs create mode 100644 tests/auto/api/testdata/QBS-728/QBS-728.qbs create mode 100644 tests/auto/api/testdata/add-qobject-macro-to-cpp-file/add-qobject-macro-to-cpp-file.qbs create mode 100644 tests/auto/api/testdata/add-qobject-macro-to-cpp-file/main.cpp create mode 100644 tests/auto/api/testdata/add-qobject-macro-to-cpp-file/object.cpp create mode 100644 tests/auto/api/testdata/add-qobject-macro-to-cpp-file/object.h create mode 100644 tests/auto/api/testdata/added-file-persistent/added-file-persistent.qbs create mode 100644 tests/auto/api/testdata/added-file-persistent/file.cpp create mode 100644 tests/auto/api/testdata/added-file-persistent/main.cpp create mode 100644 tests/auto/api/testdata/app-without-sources/a.c create mode 100644 tests/auto/api/testdata/app-without-sources/app-without-sources.qbs create mode 100644 tests/auto/api/testdata/app-without-sources/b.c create mode 100644 tests/auto/api/testdata/base-properties/imports/Bar.qbs create mode 100644 tests/auto/api/testdata/base-properties/imports/Foo.qbs create mode 100644 tests/auto/api/testdata/base-properties/main.cpp create mode 100644 tests/auto/api/testdata/base-properties/prj.qbs create mode 100644 tests/auto/api/testdata/build-error-code-location/build-error-code-location.qbs create mode 100644 tests/auto/api/testdata/build-properties-source/build-properties-source.qbs create mode 100644 tests/auto/api/testdata/build-properties-source/main.cpp create mode 100644 tests/auto/api/testdata/build-single-file/build-single-file.qbs create mode 100644 tests/auto/api/testdata/build-single-file/compiled.cpp create mode 100644 tests/auto/api/testdata/build-single-file/ignored1.cpp create mode 100644 tests/auto/api/testdata/build-single-file/ignored2.cpp create mode 100644 tests/auto/api/testdata/build-single-file/pch.h create mode 100644 tests/auto/api/testdata/buildgraph-info/buildgraph-info.qbs create mode 100644 tests/auto/api/testdata/buildgraph-locking/buildgraph-locking.qbs create mode 100644 tests/auto/api/testdata/change-dependent-lib/change-dependent-lib.qbs create mode 100644 tests/auto/api/testdata/change-dependent-lib/main.cpp create mode 100644 tests/auto/api/testdata/change-dependent-lib/mylib.cpp create mode 100644 tests/auto/api/testdata/check-outputs/check-outputs.qbs create mode 100644 tests/auto/api/testdata/check-outputs/foo.txt create mode 100644 tests/auto/api/testdata/codegen/codegen.qbs create mode 100644 tests/auto/api/testdata/codegen/foo.txt create mode 100644 tests/auto/api/testdata/command-extraction/command-extraction.qbs create mode 100644 tests/auto/api/testdata/command-extraction/main.cpp create mode 100644 tests/auto/api/testdata/dependency-on-multiplexed-type/dependency-on-multiplexed-type.qbs create mode 100644 tests/auto/api/testdata/disabled-product/disabled-product.qbs create mode 100644 tests/auto/api/testdata/disabled-product/main.cpp create mode 100644 tests/auto/api/testdata/disabled-project/disabled-project.qbs create mode 100644 tests/auto/api/testdata/disabled_install_group/disabled_install_group.qbs create mode 100644 tests/auto/api/testdata/disabled_install_group/main.cpp create mode 100644 tests/auto/api/testdata/disappeared-wildcard-file/disappeared-wildcard-file.qbs create mode 100644 tests/auto/api/testdata/disappeared-wildcard-file/file1.txt create mode 100644 tests/auto/api/testdata/disappeared-wildcard-file/file2.txt create mode 100644 tests/auto/api/testdata/duplicate-product-names/explicit.qbs create mode 100644 tests/auto/api/testdata/duplicate-product-names/implicit-indirect.qbs create mode 100644 tests/auto/api/testdata/duplicate-product-names/implicit.qbs create mode 100644 tests/auto/api/testdata/duplicate-product-names/subdir1/subproject.qbs create mode 100644 tests/auto/api/testdata/duplicate-product-names/subdir2/subproject.qbs create mode 100644 tests/auto/api/testdata/empty-filetag-list/dontcompilethis.cpp create mode 100644 tests/auto/api/testdata/empty-filetag-list/empty-filetag-list.qbs create mode 100644 tests/auto/api/testdata/empty-submodules-list/empty-submodules-list.qbs create mode 100644 tests/auto/api/testdata/enable-and-disable-product/enable-and-disable-product.qbs create mode 100644 tests/auto/api/testdata/enable-and-disable-product/main.cpp create mode 100644 tests/auto/api/testdata/error-in-setup-run-environment/error-in-setup-run-environment.qbs create mode 100644 tests/auto/api/testdata/error-in-setup-run-environment/modules/mymodule/mymodule.qbs create mode 100644 tests/auto/api/testdata/excluded-inputs/excluded-inputs.qbs create mode 100644 tests/auto/api/testdata/explicitly-depends-on/a.in create mode 100644 tests/auto/api/testdata/explicitly-depends-on/b.in create mode 100644 tests/auto/api/testdata/explicitly-depends-on/c.in create mode 100644 tests/auto/api/testdata/explicitly-depends-on/compiler.cpp create mode 100644 tests/auto/api/testdata/explicitly-depends-on/explicitly-depends-on.qbs create mode 100644 tests/auto/api/testdata/export-item-with-group/export-item-with-group.qbs create mode 100644 tests/auto/api/testdata/export-item-with-group/main.cpp create mode 100644 tests/auto/api/testdata/export-simple/export-simple.qbs create mode 100644 tests/auto/api/testdata/export-simple/lib1.cpp create mode 100644 tests/auto/api/testdata/export-simple/main.cpp create mode 100644 tests/auto/api/testdata/export-with-recursive-depends/export-with-recursive-depends.qbs create mode 100644 tests/auto/api/testdata/export-with-recursive-depends/main1.cpp create mode 100644 tests/auto/api/testdata/export-with-recursive-depends/main2.cpp create mode 100644 tests/auto/api/testdata/export-with-recursive-depends/modules/module1/module1.qbs create mode 100644 tests/auto/api/testdata/export-with-recursive-depends/modules/module2/module2.qbs create mode 100644 tests/auto/api/testdata/fallback-gcc/fallback-gcc.qbs create mode 100644 tests/auto/api/testdata/file-tagger/bla.txt create mode 100644 tests/auto/api/testdata/file-tagger/moc_cpp.qbs create mode 100644 tests/auto/api/testdata/filetagsfilter_override/InstalledApp.qbs create mode 100644 tests/auto/api/testdata/filetagsfilter_override/filetagsfilter_override.qbs create mode 100644 tests/auto/api/testdata/filetagsfilter_override/main.cpp create mode 100644 tests/auto/api/testdata/generated-files-list/generated-files-list.qbs create mode 100644 tests/auto/api/testdata/generated-files-list/main.cpp create mode 100644 tests/auto/api/testdata/generated-files-list/mainwindow.cpp create mode 100644 tests/auto/api/testdata/generated-files-list/mainwindow.h create mode 100644 tests/auto/api/testdata/generated-files-list/mainwindow.ui create mode 100644 tests/auto/api/testdata/infinite-loop-js/infinite-loop.qbs create mode 100644 tests/auto/api/testdata/infinite-loop-process/infinite-loop.qbs create mode 100644 tests/auto/api/testdata/infinite-loop-process/main.cpp create mode 100644 tests/auto/api/testdata/infinite-loop-resolving/infinite-loop-resolving.qbs create mode 100644 tests/auto/api/testdata/inherit-qbs-search-paths/imports/Foo.qbs create mode 100644 tests/auto/api/testdata/inherit-qbs-search-paths/main.cpp create mode 100644 tests/auto/api/testdata/inherit-qbs-search-paths/prj.qbs create mode 100644 tests/auto/api/testdata/inherit-qbs-search-paths/subdir/modules/bli/m.qbs create mode 100644 tests/auto/api/testdata/inherit-qbs-search-paths/subdir2/modules/bla/m.qbs create mode 100644 tests/auto/api/testdata/installed-artifact/installed-artifact.qbs create mode 100644 tests/auto/api/testdata/installed-artifact/main.cpp create mode 100644 tests/auto/api/testdata/is-runnable/is-runnable.qbs create mode 100644 tests/auto/api/testdata/lib-same-source/lib-same-source.qbs create mode 100644 tests/auto/api/testdata/lib-same-source/main.cpp create mode 100644 tests/auto/api/testdata/link-dynamiclibs-staticlibs/dynamic1.cpp create mode 100644 tests/auto/api/testdata/link-dynamiclibs-staticlibs/dynamic2.cpp create mode 100644 tests/auto/api/testdata/link-dynamiclibs-staticlibs/link-dynamiclibs-staticlibs.qbs create mode 100644 tests/auto/api/testdata/link-dynamiclibs-staticlibs/main.cpp create mode 100644 tests/auto/api/testdata/link-dynamiclibs-staticlibs/static1.cpp create mode 100644 tests/auto/api/testdata/link-dynamiclibs-staticlibs/static2.cpp create mode 100644 tests/auto/api/testdata/link-dynamiclibs-staticlibs/static2.h create mode 100644 tests/auto/api/testdata/link-dynamiclibs/lib1.cpp create mode 100644 tests/auto/api/testdata/link-dynamiclibs/lib2.cpp create mode 100644 tests/auto/api/testdata/link-dynamiclibs/lib3.cpp create mode 100644 tests/auto/api/testdata/link-dynamiclibs/lib4.cpp create mode 100644 tests/auto/api/testdata/link-dynamiclibs/lib4.h create mode 100644 tests/auto/api/testdata/link-dynamiclibs/link-dynamiclibs.qbs create mode 100644 tests/auto/api/testdata/link-dynamiclibs/main.cpp create mode 100644 tests/auto/api/testdata/link-static-lib/helper1/helper1.cpp create mode 100644 tests/auto/api/testdata/link-static-lib/helper1/helper1.h create mode 100644 tests/auto/api/testdata/link-static-lib/helper2/helper2.cpp create mode 100644 tests/auto/api/testdata/link-static-lib/helper2/helper2.h create mode 100644 tests/auto/api/testdata/link-static-lib/link-static-lib.qbs create mode 100644 tests/auto/api/testdata/link-static-lib/main.cpp create mode 100644 tests/auto/api/testdata/link-static-lib/mystaticlib.cpp create mode 100644 tests/auto/api/testdata/link-static-lib/mystaticlibhelper.cpp create mode 100644 tests/auto/api/testdata/link-staticlib-dynamiclib/link-staticlib-dynamiclib.qbs create mode 100644 tests/auto/api/testdata/link-staticlib-dynamiclib/main.cpp create mode 100644 tests/auto/api/testdata/link-staticlib-dynamiclib/mydynamiclib.cpp create mode 100644 tests/auto/api/testdata/link-staticlib-dynamiclib/mystaticlib.cpp create mode 100644 tests/auto/api/testdata/link-staticlibs-dynamiclibs/dynamic1.cpp create mode 100644 tests/auto/api/testdata/link-staticlibs-dynamiclibs/dynamic2.cpp create mode 100644 tests/auto/api/testdata/link-staticlibs-dynamiclibs/link-staticlibs-dynamiclibs.qbs create mode 100644 tests/auto/api/testdata/link-staticlibs-dynamiclibs/main.cpp create mode 100644 tests/auto/api/testdata/link-staticlibs-dynamiclibs/static1.cpp create mode 100644 tests/auto/api/testdata/link-staticlibs-dynamiclibs/static2.cpp create mode 100644 tests/auto/api/testdata/link-staticlibs-dynamiclibs/static2.h create mode 100644 tests/auto/api/testdata/local-profiles/local-profiles.qbs create mode 100644 tests/auto/api/testdata/lots-of-dots/dotty.matrix.ui create mode 100644 tests/auto/api/testdata/lots-of-dots/lots-of-dots.qbs create mode 100644 tests/auto/api/testdata/lots-of-dots/m.a.i.n.cpp create mode 100644 tests/auto/api/testdata/lots-of-dots/object.narf.cpp create mode 100644 tests/auto/api/testdata/lots-of-dots/object.narf.h create mode 100644 tests/auto/api/testdata/lots-of-dots/polka.dots.qrc create mode 100644 tests/auto/api/testdata/missing-qobject-header/main.cpp create mode 100644 tests/auto/api/testdata/missing-qobject-header/myobject.cpp create mode 100644 tests/auto/api/testdata/missing-qobject-header/myobject.h create mode 100644 tests/auto/api/testdata/missing-source-file/file1.txt create mode 100644 tests/auto/api/testdata/missing-source-file/file2.txt.missing create mode 100644 tests/auto/api/testdata/missing-source-file/file3.txt create mode 100644 tests/auto/api/testdata/missing-source-file/missing-source-file.qbs create mode 100644 tests/auto/api/testdata/moc-cpp/bla.cpp create mode 100644 tests/auto/api/testdata/moc-cpp/moc-cpp.qbs create mode 100644 tests/auto/api/testdata/moc-hpp-included/moc-hpp-included.qbs create mode 100644 tests/auto/api/testdata/moc-hpp-included/object.cpp create mode 100644 tests/auto/api/testdata/moc-hpp-included/object.h create mode 100644 tests/auto/api/testdata/moc-hpp-included/object2.h create mode 100644 tests/auto/api/testdata/moc-hpp-included/object2.mm create mode 100644 tests/auto/api/testdata/moc-hpp/moc-hpp.qbs create mode 100644 tests/auto/api/testdata/moc-hpp/object.cpp create mode 100644 tests/auto/api/testdata/moc-hpp/object.h create mode 100644 tests/auto/api/testdata/multi-arch/host+target.input create mode 100644 tests/auto/api/testdata/multi-arch/host-tool.input create mode 100644 tests/auto/api/testdata/multi-arch/multi-arch.qbs create mode 100644 tests/auto/api/testdata/multiplexing/multiplexing.qbs create mode 100644 tests/auto/api/testdata/new-output-artifact-in-dependency/lib.cpp create mode 100644 tests/auto/api/testdata/new-output-artifact-in-dependency/main.cpp create mode 100644 tests/auto/api/testdata/new-output-artifact-in-dependency/new-output-artifact-in-dependency.qbs create mode 100644 tests/auto/api/testdata/new-pattern-match/new-pattern-match.qbs create mode 100644 tests/auto/api/testdata/nonexistingprojectproperties/invalidaccessfromproduct.qbs create mode 100644 tests/auto/api/testdata/nonexistingprojectproperties/nonexistingprojectproperties.qbs create mode 100644 tests/auto/api/testdata/objc/main.mm create mode 100644 tests/auto/api/testdata/objc/objc.qbs create mode 100644 tests/auto/api/testdata/precompiled-header-dynamic/autogen.h.in create mode 100644 tests/auto/api/testdata/precompiled-header-dynamic/main.cpp create mode 100644 tests/auto/api/testdata/precompiled-header-dynamic/pch.h create mode 100644 tests/auto/api/testdata/precompiled-header-dynamic/precompiled-header-dynamic.qbs create mode 100644 tests/auto/api/testdata/precompiled-header-new/main.cpp create mode 100644 tests/auto/api/testdata/precompiled-header-new/myobject.cpp create mode 100644 tests/auto/api/testdata/precompiled-header-new/myobject.h create mode 100644 tests/auto/api/testdata/precompiled-header-new/precompiled-header-new.qbs create mode 100644 tests/auto/api/testdata/precompiled-header-new/stable.h create mode 100644 tests/auto/api/testdata/process-result/main.cpp create mode 100644 tests/auto/api/testdata/process-result/process-result.qbs create mode 100644 tests/auto/api/testdata/productNameWithDots/app.cpp create mode 100644 tests/auto/api/testdata/productNameWithDots/lib.cpp create mode 100644 tests/auto/api/testdata/productNameWithDots/productNameWithDots.qbs create mode 100644 tests/auto/api/testdata/project-data-after-product-invalidation/file.cpp create mode 100644 tests/auto/api/testdata/project-data-after-product-invalidation/main.cpp create mode 100644 tests/auto/api/testdata/project-data-after-product-invalidation/project-data-after-product-invalidation.qbs create mode 100644 tests/auto/api/testdata/project-editing/existingfile1.txt create mode 100644 tests/auto/api/testdata/project-editing/existingfile2.txt create mode 100644 tests/auto/api/testdata/project-editing/existingfile3.txt create mode 100644 tests/auto/api/testdata/project-editing/file.cpp create mode 100644 tests/auto/api/testdata/project-editing/file.h create mode 100644 tests/auto/api/testdata/project-editing/main.cpp create mode 100644 tests/auto/api/testdata/project-editing/newfile1.txt create mode 100644 tests/auto/api/testdata/project-editing/newfile2.txt create mode 100644 tests/auto/api/testdata/project-editing/newfile3.txt create mode 100644 tests/auto/api/testdata/project-editing/newfile4.txt create mode 100644 tests/auto/api/testdata/project-editing/project-editing.qbs create mode 100644 tests/auto/api/testdata/project-editing/project-with-no-files.qbs create mode 100644 tests/auto/api/testdata/project-editing/subdir/file.txt create mode 100644 tests/auto/api/testdata/project-editing/test.wildcard create mode 100644 tests/auto/api/testdata/project-invalidation/project.early-error.qbs create mode 100644 tests/auto/api/testdata/project-invalidation/project.late-error.qbs create mode 100644 tests/auto/api/testdata/project-invalidation/project.no-error.qbs create mode 100644 tests/auto/api/testdata/project-locking/project-locking.qbs create mode 100644 tests/auto/api/testdata/project-properties-by-name/main1.cpp create mode 100644 tests/auto/api/testdata/project-properties-by-name/main2.cpp create mode 100644 tests/auto/api/testdata/project-properties-by-name/project-properties-by-name.qbs create mode 100644 tests/auto/api/testdata/project-with-probe-and-profile-item/project-with-probe-and-profile-item.qbs create mode 100644 tests/auto/api/testdata/project-with-properties-item/project-with-properties-item.qbs create mode 100644 tests/auto/api/testdata/projectd create mode 100644 tests/auto/api/testdata/properties-blocks/main.cpp create mode 100644 tests/auto/api/testdata/properties-blocks/properties-blocks.qbs create mode 100644 tests/auto/api/testdata/qt5-plugin/echointerface.h create mode 100644 tests/auto/api/testdata/qt5-plugin/echoplugin.cpp create mode 100644 tests/auto/api/testdata/qt5-plugin/echoplugin.h create mode 100644 tests/auto/api/testdata/qt5-plugin/echoplugin.json.source create mode 100644 tests/auto/api/testdata/qt5-plugin/echoplugin_dummy.cpp create mode 100644 tests/auto/api/testdata/qt5-plugin/qt5-plugin.qbs create mode 100644 tests/auto/api/testdata/rc/main.cpp create mode 100644 tests/auto/api/testdata/rc/rc.qbs create mode 100644 tests/auto/api/testdata/rc/subdir/rc-include.h create mode 100644 tests/auto/api/testdata/rc/test.rc create mode 100644 tests/auto/api/testdata/recursive-wildcards/dir/file1.txt create mode 100644 tests/auto/api/testdata/recursive-wildcards/dir/subdir/file2.txt create mode 100644 tests/auto/api/testdata/recursive-wildcards/recursive-wildcards.qbs create mode 100644 tests/auto/api/testdata/referenced-file-errors/ambiguousdir/p1.qbs create mode 100644 tests/auto/api/testdata/referenced-file-errors/ambiguousdir/p2.qbs create mode 100644 tests/auto/api/testdata/referenced-file-errors/cycle.qbs create mode 100644 tests/auto/api/testdata/referenced-file-errors/emptydir/.gitignore create mode 100644 tests/auto/api/testdata/referenced-file-errors/modules/brokenmodule/brokenmodule.qbs create mode 100644 tests/auto/api/testdata/referenced-file-errors/okay.qbs create mode 100644 tests/auto/api/testdata/referenced-file-errors/okay2.qbs create mode 100644 tests/auto/api/testdata/referenced-file-errors/referenced-file-errors.qbs create mode 100644 tests/auto/api/testdata/referenced-file-errors/wrongtype.qbs create mode 100644 tests/auto/api/testdata/references/invalid1.qbs create mode 100644 tests/auto/api/testdata/references/invalid2.qbs create mode 100644 tests/auto/api/testdata/references/subdir-with-multiple-projects/subproject1.qbs create mode 100644 tests/auto/api/testdata/references/subdir-with-multiple-projects/subproject2.qbs create mode 100644 tests/auto/api/testdata/references/subdir-with-multiple-projects/subproject3.qbs create mode 100644 tests/auto/api/testdata/references/subdir-with-no-project/test.txt create mode 100644 tests/auto/api/testdata/references/subdir-with-one-project/p.qbs create mode 100644 tests/auto/api/testdata/references/subdir-with-one-project/test.txt create mode 100644 tests/auto/api/testdata/references/valid.qbs create mode 100644 tests/auto/api/testdata/relaxed-mode-recovery/relaxed-mode-recovery.qbs create mode 100644 tests/auto/api/testdata/remove-file-dependency/main.cpp create mode 100644 tests/auto/api/testdata/remove-file-dependency/removeFileDependency.qbs create mode 100644 tests/auto/api/testdata/remove-file-dependency/someheader.h create mode 100644 tests/auto/api/testdata/rename-product/lib.cpp create mode 100644 tests/auto/api/testdata/rename-product/main.cpp create mode 100644 tests/auto/api/testdata/rename-product/rename.qbs create mode 100644 tests/auto/api/testdata/rename-target-artifact/lib.cpp create mode 100644 tests/auto/api/testdata/rename-target-artifact/main.cpp create mode 100644 tests/auto/api/testdata/rename-target-artifact/rename.qbs create mode 100644 tests/auto/api/testdata/renamed-qbs-source-file/renamed-qbs-source-file.qbs create mode 100644 tests/auto/api/testdata/renamed-qbs-source-file/the-product/the-prodduct.qbs create mode 100644 tests/auto/api/testdata/restored-warnings/file.cpp create mode 100644 tests/auto/api/testdata/restored-warnings/main.cpp create mode 100644 tests/auto/api/testdata/restored-warnings/restored-warnings.qbs create mode 100644 tests/auto/api/testdata/rule-conflict/main.cpp create mode 100644 tests/auto/api/testdata/rule-conflict/pch1.h create mode 100644 tests/auto/api/testdata/rule-conflict/pch2.h create mode 100644 tests/auto/api/testdata/rule-conflict/rule-conflict.qbs create mode 100644 tests/auto/api/testdata/run-disabled-product/run-disabled-product.qbs create mode 100644 tests/auto/api/testdata/same-base-name/lib.c create mode 100644 tests/auto/api/testdata/same-base-name/lib.cpp create mode 100644 tests/auto/api/testdata/same-base-name/lib.m create mode 100644 tests/auto/api/testdata/same-base-name/lib.mm create mode 100644 tests/auto/api/testdata/same-base-name/main.c create mode 100644 tests/auto/api/testdata/same-base-name/same-base-name.qbs create mode 100644 tests/auto/api/testdata/simple-probe/main.cpp create mode 100644 tests/auto/api/testdata/simple-probe/simple-probe.qbs create mode 100644 tests/auto/api/testdata/soft-dependency/main.cpp create mode 100644 tests/auto/api/testdata/soft-dependency/soft-dependency.qbs create mode 100644 tests/auto/api/testdata/source-file-in-build-dir/file.cpp create mode 100644 tests/auto/api/testdata/source-file-in-build-dir/source-file-in-build-dir.qbs create mode 100644 tests/auto/api/testdata/static-lib-deps/a1.cpp create mode 100644 tests/auto/api/testdata/static-lib-deps/a2.cpp create mode 100644 tests/auto/api/testdata/static-lib-deps/b.cpp create mode 100644 tests/auto/api/testdata/static-lib-deps/c.cpp create mode 100644 tests/auto/api/testdata/static-lib-deps/d.cpp create mode 100644 tests/auto/api/testdata/static-lib-deps/d.mm create mode 100644 tests/auto/api/testdata/static-lib-deps/e.cpp create mode 100644 tests/auto/api/testdata/static-lib-deps/main.cpp create mode 100644 tests/auto/api/testdata/static-lib-deps/static-lib-deps.qbs create mode 100644 tests/auto/api/testdata/subprojects/resources/imports/LibraryType/type.js create mode 100644 tests/auto/api/testdata/subprojects/resources/modules/QtCoreDepender/qtcoredepender.qbs create mode 100644 tests/auto/api/testdata/subprojects/resources/modules/cute/core/core.qbs create mode 100644 tests/auto/api/testdata/subprojects/resources/modules/cute/core/cuteglobal.h create mode 100644 tests/auto/api/testdata/subprojects/subproject1/main.cpp create mode 100644 tests/auto/api/testdata/subprojects/subproject2/subproject2.qbs create mode 100644 tests/auto/api/testdata/subprojects/subproject2/subproject3/subproject3.qbs create mode 100644 tests/auto/api/testdata/subprojects/subproject2/subproject3/testlib.cpp create mode 100644 tests/auto/api/testdata/subprojects/toplevelproject.qbs create mode 100644 tests/auto/api/testdata/target-artifact-status/target-artifact-status.qbs create mode 100644 tests/auto/api/testdata/timeout-js/timeout.qbs create mode 100644 tests/auto/api/testdata/timeout-process/main.cpp create mode 100644 tests/auto/api/testdata/timeout-process/timeout.qbs create mode 100644 tests/auto/api/testdata/tool-in-module/use-outside-project/modules/thetool/thetool.qbs create mode 100644 tests/auto/api/testdata/tool-in-module/use-outside-project/use-outside-project.qbs create mode 100644 tests/auto/api/testdata/tool-in-module/use-within-project/main.cpp create mode 100644 tests/auto/api/testdata/tool-in-module/use-within-project/tool-input.txt create mode 100644 tests/auto/api/testdata/tool-in-module/use-within-project/use-within-project.qbs create mode 100644 tests/auto/api/testdata/transformer-data/transformer-data.qbs create mode 100644 tests/auto/api/testdata/transformers/main.cpp create mode 100644 tests/auto/api/testdata/transformers/transformers.qbs create mode 100644 tests/auto/api/testdata/two-default-property-values/modules/mymodule/mymodule.qbs create mode 100644 tests/auto/api/testdata/two-default-property-values/modules/myothermodule/myothermodule.qbs create mode 100644 tests/auto/api/testdata/two-default-property-values/test.txt create mode 100644 tests/auto/api/testdata/two-default-property-values/two-default-property-values.qbs create mode 100644 tests/auto/api/testdata/type-change/main.cpp create mode 100644 tests/auto/api/testdata/type-change/type-change.qbs create mode 100644 tests/auto/api/testdata/uic/bla.cpp create mode 100644 tests/auto/api/testdata/uic/bla.h create mode 100644 tests/auto/api/testdata/uic/ui.h create mode 100644 tests/auto/api/testdata/uic/ui.ui create mode 100644 tests/auto/api/testdata/uic/uic.qbs create mode 100644 tests/auto/api/tst_api.cpp create mode 100644 tests/auto/api/tst_api.h create mode 100644 tests/auto/auto.pri create mode 100644 tests/auto/auto.pro create mode 100644 tests/auto/auto.qbs create mode 100644 tests/auto/blackbox/blackbox-android.pro create mode 100644 tests/auto/blackbox/blackbox-android.qbs create mode 100644 tests/auto/blackbox/blackbox-apple.pro create mode 100644 tests/auto/blackbox/blackbox-apple.qbs create mode 100644 tests/auto/blackbox/blackbox-baremetal.pro create mode 100644 tests/auto/blackbox/blackbox-baremetal.qbs create mode 100644 tests/auto/blackbox/blackbox-clangdb.pro create mode 100644 tests/auto/blackbox/blackbox-clangdb.qbs create mode 100644 tests/auto/blackbox/blackbox-examples.pro create mode 100644 tests/auto/blackbox/blackbox-examples.qbs create mode 100644 tests/auto/blackbox/blackbox-java.pro create mode 100644 tests/auto/blackbox/blackbox-java.qbs create mode 100644 tests/auto/blackbox/blackbox-joblimits.pro create mode 100644 tests/auto/blackbox/blackbox-joblimits.qbs create mode 100644 tests/auto/blackbox/blackbox-qt.pro create mode 100644 tests/auto/blackbox/blackbox-qt.qbs create mode 100644 tests/auto/blackbox/blackbox.pro create mode 100644 tests/auto/blackbox/blackbox.qbs create mode 100644 tests/auto/blackbox/find/find-android.qbs create mode 100644 tests/auto/blackbox/find/find-jdk.qbs create mode 100644 tests/auto/blackbox/find/find-nodejs.qbs create mode 100644 tests/auto/blackbox/find/find-typescript.qbs create mode 100644 tests/auto/blackbox/find/find-xcode.qbs create mode 100644 tests/auto/blackbox/testdata-android/aidl/AndroidManifest.xml create mode 100644 tests/auto/blackbox/testdata-android/aidl/aidl.qbs create mode 100644 tests/auto/blackbox/testdata-android/aidl/io/qbs/aidltest/Interface1.aidl create mode 100644 tests/auto/blackbox/testdata-android/aidl/io/qbs/aidltest/Interface2.aidl create mode 100644 tests/auto/blackbox/testdata-android/aidl/io/qbs/aidltest/MainActivity.java create mode 100644 tests/auto/blackbox/testdata-android/minimal-native/libdependency.so create mode 100644 tests/auto/blackbox/testdata-android/minimal-native/minimal-native.qbs create mode 100644 tests/auto/blackbox/testdata-android/minimal-native/src/main/AndroidManifest.xml create mode 100644 tests/auto/blackbox/testdata-android/minimal-native/src/main/java/my/minimal/MinimalNative.java create mode 100644 tests/auto/blackbox/testdata-android/minimal-native/src/main/native/native.c create mode 100644 tests/auto/blackbox/testdata-android/multiple-apks-per-project/multiple-apks-per-project.qbs create mode 100644 tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/product1.qbs create mode 100644 tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/AndroidManifest.xml create mode 100644 tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/java/io/qt/dummy1/Dummy.java create mode 100644 tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/jni/lib1.cpp create mode 100644 tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/jni/lib2.cpp create mode 100644 tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/res/values/strings.xml create mode 100644 tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/product2.qbs create mode 100644 tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/AndroidManifest.xml create mode 100644 tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/java/io/qt/dummy2/Dummy.java create mode 100644 tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/jni/lib1.cpp create mode 100644 tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/jni/lib2.cpp create mode 100644 tests/auto/blackbox/testdata-android/multiple-libs-per-apk/io/qbs/lib3/lib3.java create mode 100644 tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib4.java create mode 100644 tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib5.java create mode 100644 tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib6.java create mode 100644 tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib7.java create mode 100644 tests/auto/blackbox/testdata-android/multiple-libs-per-apk/lib8.java create mode 100644 tests/auto/blackbox/testdata-android/multiple-libs-per-apk/multiple-libs-per-apk.qbs create mode 100644 tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/AndroidManifest.xml create mode 100644 tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/java/io/qt/dummy/Dummy.java create mode 100644 tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/jni/lib1.cpp create mode 100644 tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/jni/lib2.cpp create mode 100644 tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/res/values/strings.xml create mode 100644 tests/auto/blackbox/testdata-android/no-native/no-native.qbs create mode 100644 tests/auto/blackbox/testdata-android/qml-app/main.cpp create mode 100644 tests/auto/blackbox/testdata-android/qml-app/main.qml create mode 100644 tests/auto/blackbox/testdata-android/qml-app/qml-app.qbs create mode 100644 tests/auto/blackbox/testdata-android/qml-app/qml.qrc create mode 100644 tests/auto/blackbox/testdata-android/qml-app/src/main/AndroidManifest.xml create mode 100644 tests/auto/blackbox/testdata-android/qml-app/src/main/assets/dummyasset.txt create mode 100644 tests/auto/blackbox/testdata-android/teapot/teapot.qbs create mode 100644 tests/auto/blackbox/testdata-apple/aggregateDependencyLinking/aggregateDependencyLinking.qbs create mode 100644 tests/auto/blackbox/testdata-apple/aggregateDependencyLinking/app.c create mode 100644 tests/auto/blackbox/testdata-apple/aggregateDependencyLinking/lib.c create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/apple-dmg.qbs create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/de_DE.lproj/eula.txt create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/en_GB.lproj/eula.txt create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/en_US.lproj/eula.txt create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/fr_FR.lproj/eula.txt create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/hello.icns create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/hello.tif create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/ja_JP.lproj/eula.txt create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/ko_KR.lproj/eula.rtf create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/main.c create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/ru_RU.lproj/eula.txt create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/white.iconset/icon_16x16.png create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/white.iconset/icon_16x16@2x.png create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/zh_CN.lproj/eula.odt create mode 100644 tests/auto/blackbox/testdata-apple/apple-dmg/zh_TW.lproj/eula.docx create mode 100644 tests/auto/blackbox/testdata-apple/apple-multiconfig/app.c create mode 100644 tests/auto/blackbox/testdata-apple/apple-multiconfig/apple-multiconfig.qbs create mode 100644 tests/auto/blackbox/testdata-apple/apple-multiconfig/helpers.js create mode 100644 tests/auto/blackbox/testdata-apple/apple-multiconfig/lib.c create mode 100644 tests/auto/blackbox/testdata-apple/bundle-structure/bundle-structure.qbs create mode 100644 tests/auto/blackbox/testdata-apple/bundle-structure/dummy.c create mode 100644 tests/auto/blackbox/testdata-apple/bundle-structure/dummy.h create mode 100644 tests/auto/blackbox/testdata-apple/bundle-structure/dummy_p.h create mode 100644 tests/auto/blackbox/testdata-apple/bundle-structure/resource.txt create mode 100644 tests/auto/blackbox/testdata-apple/deploymentTarget/deployment.qbs create mode 100644 tests/auto/blackbox/testdata-apple/deploymentTarget/main.c create mode 100644 tests/auto/blackbox/testdata-apple/embedInfoPlist/embedInfoPlist.qbs create mode 100644 tests/auto/blackbox/testdata-apple/embedInfoPlist/main.m create mode 100644 tests/auto/blackbox/testdata-apple/frameworkStructure/BaseResource create mode 100644 tests/auto/blackbox/testdata-apple/frameworkStructure/Widget.cpp create mode 100644 tests/auto/blackbox/testdata-apple/frameworkStructure/Widget.h create mode 100644 tests/auto/blackbox/testdata-apple/frameworkStructure/WidgetPrivate.h create mode 100644 tests/auto/blackbox/testdata-apple/frameworkStructure/en.lproj/EnglishResource create mode 100644 tests/auto/blackbox/testdata-apple/frameworkStructure/frameworkStructure.qbs create mode 100644 tests/auto/blackbox/testdata-apple/ib/assetcatalog/EmptyStoryboard.storyboard create mode 100644 tests/auto/blackbox/testdata-apple/ib/assetcatalog/MainMenu.xib create mode 100644 tests/auto/blackbox/testdata-apple/ib/assetcatalog/Storyboard.storyboard create mode 100644 tests/auto/blackbox/testdata-apple/ib/assetcatalog/assetcatalogempty.qbs create mode 100644 tests/auto/blackbox/testdata-apple/ib/assetcatalog/empty.xcassets/empty.iconset/icon_16x16.png create mode 100644 tests/auto/blackbox/testdata-apple/ib/assetcatalog/empty.xcassets/empty.iconset/icon_16x16@2x.png create mode 100644 tests/auto/blackbox/testdata-apple/ib/assetcatalog/empty.xcassets/other.imageset/Contents.json create mode 100644 tests/auto/blackbox/testdata-apple/ib/assetcatalog/empty.xcassets/other.imageset/icon_16x16.png create mode 100644 tests/auto/blackbox/testdata-apple/ib/assetcatalog/empty.xcassets/other.imageset/icon_16x16@2x.png create mode 100644 tests/auto/blackbox/testdata-apple/ib/assetcatalog/main.c create mode 100644 tests/auto/blackbox/testdata-apple/ib/empty-asset-catalogs/assetcatalog1.xcassets/.keep create mode 100644 tests/auto/blackbox/testdata-apple/ib/empty-asset-catalogs/assetcatalog2.xcassets/.keep create mode 100644 tests/auto/blackbox/testdata-apple/ib/empty-asset-catalogs/main.c create mode 100644 tests/auto/blackbox/testdata-apple/ib/empty-asset-catalogs/multiple-asset-catalogs.qbs create mode 100644 tests/auto/blackbox/testdata-apple/ib/iconset/iconset.qbs create mode 100644 tests/auto/blackbox/testdata-apple/ib/iconset/white.iconset/icon_16x16.png create mode 100644 tests/auto/blackbox/testdata-apple/ib/iconset/white.iconset/icon_16x16@2x.png create mode 100644 tests/auto/blackbox/testdata-apple/ib/iconsetapp/iconsetapp.qbs create mode 100644 tests/auto/blackbox/testdata-apple/ib/iconsetapp/main.c create mode 100644 tests/auto/blackbox/testdata-apple/ib/iconsetapp/white.iconset/icon_16x16.png create mode 100644 tests/auto/blackbox/testdata-apple/ib/iconsetapp/white.iconset/icon_16x16@2x.png create mode 100644 tests/auto/blackbox/testdata-apple/ib/multiple-asset-catalogs/assetcatalog1.xcassets/other.imageset/Contents.json create mode 100644 tests/auto/blackbox/testdata-apple/ib/multiple-asset-catalogs/assetcatalog1.xcassets/other.imageset/icon_16x16.png create mode 100644 tests/auto/blackbox/testdata-apple/ib/multiple-asset-catalogs/assetcatalog1.xcassets/other.imageset/icon_16x16@2x.png create mode 100644 tests/auto/blackbox/testdata-apple/ib/multiple-asset-catalogs/assetcatalog2.xcassets/other.imageset/Contents.json create mode 100644 tests/auto/blackbox/testdata-apple/ib/multiple-asset-catalogs/assetcatalog2.xcassets/other.imageset/icon_16x16.png create mode 100644 tests/auto/blackbox/testdata-apple/ib/multiple-asset-catalogs/assetcatalog2.xcassets/other.imageset/icon_16x16@2x.png create mode 100644 tests/auto/blackbox/testdata-apple/ib/multiple-asset-catalogs/main.c create mode 100644 tests/auto/blackbox/testdata-apple/ib/multiple-asset-catalogs/multiple-asset-catalogs.qbs create mode 100644 tests/auto/blackbox/testdata-apple/infoplist/infoplist.qbs create mode 100644 tests/auto/blackbox/testdata-apple/infoplist/main.c create mode 100644 tests/auto/blackbox/testdata-apple/objc-arc/arc.m create mode 100644 tests/auto/blackbox/testdata-apple/objc-arc/arc.mm create mode 100644 tests/auto/blackbox/testdata-apple/objc-arc/main.m create mode 100644 tests/auto/blackbox/testdata-apple/objc-arc/mrc.m create mode 100644 tests/auto/blackbox/testdata-apple/objc-arc/mrc.mm create mode 100644 tests/auto/blackbox/testdata-apple/objc-arc/objc-arc.qbs create mode 100644 tests/auto/blackbox/testdata-apple/overrideInfoPlist/Override-Info.plist create mode 100644 tests/auto/blackbox/testdata-apple/overrideInfoPlist/main.c create mode 100644 tests/auto/blackbox/testdata-apple/overrideInfoPlist/overrideInfoPlist.qbs create mode 100644 tests/auto/blackbox/testdata-apple/xcode/xcode-project.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/BareMetalApplication.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/BareMetalStaticLibrary.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/defines/defines.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/defines/main.c create mode 100644 tests/auto/blackbox/testdata-baremetal/distribution-include-paths/bar/bar.h create mode 100644 tests/auto/blackbox/testdata-baremetal/distribution-include-paths/distribution-include-paths.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/distribution-include-paths/foo/foo.h create mode 100644 tests/auto/blackbox/testdata-baremetal/distribution-include-paths/main.c create mode 100644 tests/auto/blackbox/testdata-baremetal/do-not-generate-compiler-listing/do-not-generate-compiler-listing.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/do-not-generate-compiler-listing/fun.c create mode 100644 tests/auto/blackbox/testdata-baremetal/do-not-generate-compiler-listing/main.c create mode 100644 tests/auto/blackbox/testdata-baremetal/do-not-generate-linker-map/do-not-generate-linker-map.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/do-not-generate-linker-map/main.c create mode 100644 tests/auto/blackbox/testdata-baremetal/external-static-libraries/external-static-libraries.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/external-static-libraries/lib-a.c create mode 100644 tests/auto/blackbox/testdata-baremetal/external-static-libraries/lib-b.c create mode 100644 tests/auto/blackbox/testdata-baremetal/external-static-libraries/main.c create mode 100644 tests/auto/blackbox/testdata-baremetal/generate-compiler-listing/fun.c create mode 100644 tests/auto/blackbox/testdata-baremetal/generate-compiler-listing/generate-compiler-listing.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/generate-compiler-listing/main.c create mode 100644 tests/auto/blackbox/testdata-baremetal/generate-linker-map/generate-linker-map.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/generate-linker-map/main.c create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-application/main.c create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-application/one-object-application.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/78k-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/arm-gcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/arm-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/arm-keil.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr-gcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr32-gcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr32-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/c166-keil.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/cr16-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m16c-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m32c-gcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m32r-gcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m68k-gcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs251-keil.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs51-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs51-keil.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs51-sdcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/msp430-gcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/msp430-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/one-object-asm-application.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/r32c-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rh850-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/riscv-gcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rl78-gcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rl78-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rx-gcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/sh-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/stm8-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/stm8-sdcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/v850-gcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/v850-iar.s create mode 100644 tests/auto/blackbox/testdata-baremetal/one-object-asm-application/xtensa-gcc.s create mode 100644 tests/auto/blackbox/testdata-baremetal/preinclude-headers/main.c create mode 100644 tests/auto/blackbox/testdata-baremetal/preinclude-headers/preinclude-headers.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/preinclude-headers/preinclude.h create mode 100644 tests/auto/blackbox/testdata-baremetal/static-library-dependencies/a1.c create mode 100644 tests/auto/blackbox/testdata-baremetal/static-library-dependencies/a2.c create mode 100644 tests/auto/blackbox/testdata-baremetal/static-library-dependencies/app.c create mode 100644 tests/auto/blackbox/testdata-baremetal/static-library-dependencies/b.c create mode 100644 tests/auto/blackbox/testdata-baremetal/static-library-dependencies/c.c create mode 100644 tests/auto/blackbox/testdata-baremetal/static-library-dependencies/d.c create mode 100644 tests/auto/blackbox/testdata-baremetal/static-library-dependencies/e.c create mode 100644 tests/auto/blackbox/testdata-baremetal/static-library-dependencies/static-library-dependencies.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/system-include-paths/bar/bar.h create mode 100644 tests/auto/blackbox/testdata-baremetal/system-include-paths/foo/foo.h create mode 100644 tests/auto/blackbox/testdata-baremetal/system-include-paths/main.c create mode 100644 tests/auto/blackbox/testdata-baremetal/system-include-paths/system-include-paths.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/target-platform/target-platform.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/two-object-application/fun.c create mode 100644 tests/auto/blackbox/testdata-baremetal/two-object-application/main.c create mode 100644 tests/auto/blackbox/testdata-baremetal/two-object-application/two-object-application.qbs create mode 100644 tests/auto/blackbox/testdata-baremetal/user-include-paths/bar/bar.h create mode 100644 tests/auto/blackbox/testdata-baremetal/user-include-paths/foo/foo.h create mode 100644 tests/auto/blackbox/testdata-baremetal/user-include-paths/main.c create mode 100644 tests/auto/blackbox/testdata-baremetal/user-include-paths/user-include-paths.qbs create mode 100644 tests/auto/blackbox/testdata-clangdb/project1/i like spaces.cpp create mode 100644 tests/auto/blackbox/testdata-clangdb/project1/project.qbs create mode 100644 tests/auto/blackbox/testdata-java/java/Car.java create mode 100644 tests/auto/blackbox/testdata-java/java/Car8.java create mode 100644 tests/auto/blackbox/testdata-java/java/HelloWorld.java create mode 100644 tests/auto/blackbox/testdata-java/java/HelloWorld8.java create mode 100644 tests/auto/blackbox/testdata-java/java/Jet.java create mode 100644 tests/auto/blackbox/testdata-java/java/Manifest.mf create mode 100644 tests/auto/blackbox/testdata-java/java/Manifest2.mf create mode 100644 tests/auto/blackbox/testdata-java/java/NoPackage.java create mode 100644 tests/auto/blackbox/testdata-java/java/RandomStuff.java create mode 100644 tests/auto/blackbox/testdata-java/java/Ship.java create mode 100644 tests/auto/blackbox/testdata-java/java/Vehicle.java create mode 100644 tests/auto/blackbox/testdata-java/java/Vehicles.java create mode 100644 tests/auto/blackbox/testdata-java/java/engine.c create mode 100644 tests/auto/blackbox/testdata-java/java/inner-class/InnerClass.java create mode 100644 tests/auto/blackbox/testdata-java/java/inner-class/inner-class.qbs create mode 100644 tests/auto/blackbox/testdata-java/java/vehicles.qbs create mode 100644 tests/auto/blackbox/testdata-joblimits/job-limits/job-limits.qbs create mode 100644 tests/auto/blackbox/testdata-joblimits/job-limits/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/add-qobject-macro-to-generated-cpp-file.qbs create mode 100644 tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/object.cpp.in create mode 100644 tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/object.h create mode 100644 tests/auto/blackbox/testdata-qt/auto-qrc/auto-qrc.qbs create mode 100644 tests/auto/blackbox/testdata-qt/auto-qrc/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/auto-qrc/qrc-base/resource1.txt create mode 100644 tests/auto/blackbox/testdata-qt/auto-qrc/qrc-base/subdir/resource2.txt create mode 100644 tests/auto/blackbox/testdata-qt/auto-qrc/qrc-base/subdir/resource3.txt create mode 100644 tests/auto/blackbox/testdata-qt/cached-qml/MainForm.ui.qml create mode 100644 tests/auto/blackbox/testdata-qt/cached-qml/cached-qml.qbs create mode 100644 tests/auto/blackbox/testdata-qt/cached-qml/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/cached-qml/main.qml create mode 100644 tests/auto/blackbox/testdata-qt/cached-qml/qml.qrc create mode 100644 tests/auto/blackbox/testdata-qt/cached-qml/stuff.js create mode 100644 tests/auto/blackbox/testdata-qt/combined-moc/combined-moc.qbs create mode 100644 tests/auto/blackbox/testdata-qt/combined-moc/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/combined-moc/theobject.h create mode 100644 tests/auto/blackbox/testdata-qt/create-project/dummy.txt create mode 100644 tests/auto/blackbox/testdata-qt/dbus-adaptors/THIS.IS.A.STRANGE.FILENAME.CAR.XML create mode 100644 tests/auto/blackbox/testdata-qt/dbus-adaptors/car.cpp create mode 100644 tests/auto/blackbox/testdata-qt/dbus-adaptors/car.h create mode 100644 tests/auto/blackbox/testdata-qt/dbus-adaptors/car.qbs create mode 100644 tests/auto/blackbox/testdata-qt/dbus-adaptors/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/dbus-interfaces/car.xml create mode 100644 tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.cpp create mode 100644 tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.h create mode 100644 tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.qbs create mode 100644 tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.ui create mode 100644 tests/auto/blackbox/testdata-qt/dbus-interfaces/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/forced-moc/createqtclass.h create mode 100644 tests/auto/blackbox/testdata-qt/forced-moc/forced-moc.qbs create mode 100644 tests/auto/blackbox/testdata-qt/forced-moc/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/forced-moc/myqtclass.h create mode 100644 tests/auto/blackbox/testdata-qt/included-moc-cpp/included-moc-cpp.qbs create mode 100644 tests/auto/blackbox/testdata-qt/included-moc-cpp/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/included-moc-cpp/myobject.cpp create mode 100644 tests/auto/blackbox/testdata-qt/included-moc-cpp/myobject.h create mode 100644 tests/auto/blackbox/testdata-qt/linker-variant/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/linker-variant/qt-linker-variant.qbs create mode 100644 tests/auto/blackbox/testdata-qt/lrelease/de.ts create mode 100644 tests/auto/blackbox/testdata-qt/lrelease/hu.ts create mode 100644 tests/auto/blackbox/testdata-qt/lrelease/lrelease.qbs create mode 100644 tests/auto/blackbox/testdata-qt/metatypes/metatypes.qbs create mode 100644 tests/auto/blackbox/testdata-qt/metatypes/mocableclass1.cpp create mode 100644 tests/auto/blackbox/testdata-qt/metatypes/mocableclass1.h create mode 100644 tests/auto/blackbox/testdata-qt/metatypes/mocableclass2.cpp create mode 100644 tests/auto/blackbox/testdata-qt/metatypes/unmocableclass.cpp create mode 100644 tests/auto/blackbox/testdata-qt/mixed-build-variants/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/mixed-build-variants/mixed-build-variants.qbs create mode 100644 tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/moc-and-cxx-combining.qbs create mode 100644 tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/myobject.cpp create mode 100644 tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/myobject.h create mode 100644 tests/auto/blackbox/testdata-qt/moc-compiler-defines/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/moc-compiler-defines/moc-compiler-defines.qbs create mode 100644 tests/auto/blackbox/testdata-qt/moc-compiler-defines/object.cpp create mode 100644 tests/auto/blackbox/testdata-qt/moc-compiler-defines/object.h create mode 100644 tests/auto/blackbox/testdata-qt/moc-flags/blubb.h create mode 100644 tests/auto/blackbox/testdata-qt/moc-flags/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/moc-flags/moc-flags.qbs create mode 100644 tests/auto/blackbox/testdata-qt/moc-same-file-name/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/moc-same-file-name/moc-same-file-name.qbs create mode 100644 tests/auto/blackbox/testdata-qt/moc-same-file-name/src1/someclass.cpp create mode 100644 tests/auto/blackbox/testdata-qt/moc-same-file-name/src1/someclass.h create mode 100644 tests/auto/blackbox/testdata-qt/moc-same-file-name/src1/somefile.cpp create mode 100644 tests/auto/blackbox/testdata-qt/moc-same-file-name/src2/someclass.cpp create mode 100644 tests/auto/blackbox/testdata-qt/moc-same-file-name/src2/someclass.h create mode 100644 tests/auto/blackbox/testdata-qt/moc-same-file-name/src2/somefile.cpp create mode 100644 tests/auto/blackbox/testdata-qt/pkgconfig/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/pkgconfig/pkgconfig.qbs create mode 100644 tests/auto/blackbox/testdata-qt/plugin-meta-data/app.cpp create mode 100644 tests/auto/blackbox/testdata-qt/plugin-meta-data/metadata.json create mode 100644 tests/auto/blackbox/testdata-qt/plugin-meta-data/plugin-meta-data.qbs create mode 100644 tests/auto/blackbox/testdata-qt/plugin-meta-data/theplugin.cpp create mode 100644 tests/auto/blackbox/testdata-qt/plugin-support/modules/m1/m1.qbs create mode 100644 tests/auto/blackbox/testdata-qt/plugin-support/modules/m2/m2.qbs create mode 100644 tests/auto/blackbox/testdata-qt/plugin-support/plugin-support-main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/plugin-support/plugin-support.qbs create mode 100644 tests/auto/blackbox/testdata-qt/qml-debugging/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/qml-debugging/qml-debugging.qbs create mode 100644 tests/auto/blackbox/testdata-qt/qmltyperegistrar/example.qml create mode 100644 tests/auto/blackbox/testdata-qt/qmltyperegistrar/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/qmltyperegistrar/person.cpp create mode 100644 tests/auto/blackbox/testdata-qt/qmltyperegistrar/person.h create mode 100644 tests/auto/blackbox/testdata-qt/qmltyperegistrar/qmltyperegistrar.qbs create mode 100644 tests/auto/blackbox/testdata-qt/qobject-in-mm/main.mm create mode 100644 tests/auto/blackbox/testdata-qt/qobject-in-mm/qobject-in-mm.qbs create mode 100644 tests/auto/blackbox/testdata-qt/qrc/bla.cpp create mode 100644 tests/auto/blackbox/testdata-qt/qrc/bla.qrc create mode 100644 tests/auto/blackbox/testdata-qt/qrc/i.qbs create mode 100644 tests/auto/blackbox/testdata-qt/qrc/stuff.txt create mode 100644 tests/auto/blackbox/testdata-qt/qrc/subdir/dummy.txt create mode 100644 tests/auto/blackbox/testdata-qt/qrc/test.cpp create mode 100644 tests/auto/blackbox/testdata-qt/qt-keywords/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/qt-keywords/qt-keywords.qbs create mode 100644 tests/auto/blackbox/testdata-qt/qtscxml/dummystatemachine.scxml create mode 100644 tests/auto/blackbox/testdata-qt/qtscxml/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/qtscxml/qtscxml.qbs create mode 100644 tests/auto/blackbox/testdata-qt/quick-compiler/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/quick-compiler/qml.qrc create mode 100644 tests/auto/blackbox/testdata-qt/quick-compiler/qml/subdir/test.qml create mode 100644 tests/auto/blackbox/testdata-qt/quick-compiler/quick-compiler.qbs create mode 100644 tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/file.cpp create mode 100644 tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/file.h create mode 100644 tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/remove-moc-header-from-file-list.qbs create mode 100644 tests/auto/blackbox/testdata-qt/static-qt-plugin-linking/lib.cpp create mode 100644 tests/auto/blackbox/testdata-qt/static-qt-plugin-linking/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/static-qt-plugin-linking/static-qt-plugin-linking.qbs create mode 100644 tests/auto/blackbox/testdata-qt/trackAddMocInclude/after/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/trackAddMocInclude/before/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/trackAddMocInclude/before/test.qbs create mode 100644 tests/auto/blackbox/testdata-qt/trackQObjChange/bla.cpp create mode 100644 tests/auto/blackbox/testdata-qt/trackQObjChange/bla_noqobject.h create mode 100644 tests/auto/blackbox/testdata-qt/trackQObjChange/bla_qobject.h create mode 100644 tests/auto/blackbox/testdata-qt/trackQObjChange/i.qbs create mode 100644 tests/auto/blackbox/testdata-qt/unmocable/foo.h create mode 100644 tests/auto/blackbox/testdata-qt/unmocable/main.cpp create mode 100644 tests/auto/blackbox/testdata-qt/unmocable/unmocable.qbs create mode 100644 tests/auto/blackbox/testdata/QTBUG-51237/modules/mymodule/mymodule.qbs create mode 100644 tests/auto/blackbox/testdata/QTBUG-51237/qtbug-51237.qbs create mode 100644 tests/auto/blackbox/testdata/add-filetag-to-generated-artifact/add-filetag-to-generated-artifact.qbs create mode 100644 tests/auto/blackbox/testdata/add-filetag-to-generated-artifact/main.cpp create mode 100644 tests/auto/blackbox/testdata/always-run/dummy.txt create mode 100644 tests/auto/blackbox/testdata/always-run/rule.qbs create mode 100644 tests/auto/blackbox/testdata/always-run/transformer.qbs create mode 100644 tests/auto/blackbox/testdata/archiver/archivable.qbs create mode 100644 tests/auto/blackbox/testdata/archiver/list.txt create mode 100644 tests/auto/blackbox/testdata/archiver/test.txt create mode 100644 tests/auto/blackbox/testdata/artifact-scanning/artifact-scanning.qbs create mode 100644 tests/auto/blackbox/testdata/artifact-scanning/external-indirect.h create mode 100644 tests/auto/blackbox/testdata/artifact-scanning/external.h create mode 100644 tests/auto/blackbox/testdata/artifact-scanning/p1.cpp create mode 100644 tests/auto/blackbox/testdata/artifact-scanning/p2.cpp create mode 100644 tests/auto/blackbox/testdata/artifact-scanning/p3.cpp create mode 100644 tests/auto/blackbox/testdata/artifact-scanning/shared.h create mode 100644 tests/auto/blackbox/testdata/artifact-scanning/subdir/external2.h create mode 100644 tests/auto/blackbox/testdata/artifacts-map-change-tracking/artifacts-map-change-tracking.qbs create mode 100644 tests/auto/blackbox/testdata/artifacts-map-change-tracking/dummy.in create mode 100644 tests/auto/blackbox/testdata/artifacts-map-change-tracking/main.cpp create mode 100644 tests/auto/blackbox/testdata/artifacts-map-change-tracking/test.cpp.in create mode 100644 tests/auto/blackbox/testdata/artifacts-map-change-tracking/test.txt create mode 100644 tests/auto/blackbox/testdata/artifacts-map-invalidation/artifacts-map-invalidation.qbs create mode 100644 tests/auto/blackbox/testdata/artifacts-map-invalidation/file.in create mode 100644 tests/auto/blackbox/testdata/artifacts-map-race-condition/artifacts-map-race-condition.qbs create mode 100644 tests/auto/blackbox/testdata/assembly/assembly.qbs create mode 100644 tests/auto/blackbox/testdata/assembly/testa.s create mode 100644 tests/auto/blackbox/testdata/assembly/testb.S create mode 100644 tests/auto/blackbox/testdata/assembly/testc.sx create mode 100644 tests/auto/blackbox/testdata/assembly/testd_x86.asm create mode 100644 tests/auto/blackbox/testdata/assembly/testd_x86_64.asm create mode 100644 tests/auto/blackbox/testdata/autotest-timeout/autotests-timeout.qbs create mode 100644 tests/auto/blackbox/testdata/autotest-timeout/test-main.cpp create mode 100644 tests/auto/blackbox/testdata/autotest-with-dependencies/autotest-with-dependencies.qbs create mode 100644 tests/auto/blackbox/testdata/autotest-with-dependencies/helper-main.cpp create mode 100644 tests/auto/blackbox/testdata/autotest-with-dependencies/test-main.cpp create mode 100644 tests/auto/blackbox/testdata/autotests/autotests.qbs create mode 100644 tests/auto/blackbox/testdata/autotests/test1/test1.cpp create mode 100644 tests/auto/blackbox/testdata/autotests/test1/test1.qbs create mode 100644 tests/auto/blackbox/testdata/autotests/test2/test2-resource.txt create mode 100644 tests/auto/blackbox/testdata/autotests/test2/test2.cpp create mode 100644 tests/auto/blackbox/testdata/autotests/test2/test2.qbs create mode 100644 tests/auto/blackbox/testdata/autotests/test3/test3.cpp create mode 100644 tests/auto/blackbox/testdata/autotests/test3/test3.qbs create mode 100644 tests/auto/blackbox/testdata/aux-inputs-from-deps/aux-inputs-from-deps.qbs create mode 100644 tests/auto/blackbox/testdata/aux-inputs-from-deps/main.cpp create mode 100644 tests/auto/blackbox/testdata/aux-inputs-from-deps/util.js create mode 100644 tests/auto/blackbox/testdata/badInterpreter/badInterpreter.qbs create mode 100644 tests/auto/blackbox/testdata/badInterpreter/qbs/modules/script-test/script-test.qbs create mode 100755 tests/auto/blackbox/testdata/badInterpreter/script-interp-missing create mode 100755 tests/auto/blackbox/testdata/badInterpreter/script-interp-noexec create mode 100644 tests/auto/blackbox/testdata/badInterpreter/script-noexec create mode 100755 tests/auto/blackbox/testdata/badInterpreter/script-ok create mode 100644 tests/auto/blackbox/testdata/bom-sources/bom-sources.qbs create mode 100644 tests/auto/blackbox/testdata/bom-sources/main.cpp create mode 100644 tests/auto/blackbox/testdata/bom-sources/theheader.h create mode 100644 tests/auto/blackbox/testdata/build-data-of-disabled-product/build-data-of-disabled-product.qbs create mode 100644 tests/auto/blackbox/testdata/build-data-of-disabled-product/main.cpp create mode 100644 tests/auto/blackbox/testdata/build-data-of-disabled-product/test.cpp create mode 100644 tests/auto/blackbox/testdata/build-directories/build-directories.qbs create mode 100644 tests/auto/blackbox/testdata/build-graph-versions/build-graph-versions.qbs create mode 100644 tests/auto/blackbox/testdata/build-graph-versions/main.cpp create mode 100644 tests/auto/blackbox/testdata/build-variant-defaults/build-variant-defaults.qbs create mode 100644 tests/auto/blackbox/testdata/build-variant-defaults/main.cpp create mode 100644 tests/auto/blackbox/testdata/buildenv-change/buildenv-change.qbs create mode 100644 tests/auto/blackbox/testdata/buildenv-change/file.c create mode 100644 tests/auto/blackbox/testdata/buildenv-change/main.cpp create mode 100644 tests/auto/blackbox/testdata/buildenv-change/subdir/theheader.h create mode 100644 tests/auto/blackbox/testdata/buildenv-change/subdir2/theheader.h create mode 100644 tests/auto/blackbox/testdata/capnproto/bar.capnp create mode 100644 tests/auto/blackbox/testdata/capnproto/baz.capnp create mode 100644 tests/auto/blackbox/testdata/capnproto/capnproto_absolute_import.cpp create mode 100644 tests/auto/blackbox/testdata/capnproto/capnproto_absolute_import.qbs create mode 100644 tests/auto/blackbox/testdata/capnproto/capnproto_cpp.cpp create mode 100644 tests/auto/blackbox/testdata/capnproto/capnproto_cpp.qbs create mode 100644 tests/auto/blackbox/testdata/capnproto/capnproto_relative_import.cpp create mode 100644 tests/auto/blackbox/testdata/capnproto/capnproto_relative_import.qbs create mode 100644 tests/auto/blackbox/testdata/capnproto/foo.capnp create mode 100644 tests/auto/blackbox/testdata/capnproto/greeter-client.cpp create mode 100644 tests/auto/blackbox/testdata/capnproto/greeter-server.cpp create mode 100644 tests/auto/blackbox/testdata/capnproto/greeter.capnp create mode 100644 tests/auto/blackbox/testdata/capnproto/greeter_cpp.qbs create mode 100644 tests/auto/blackbox/testdata/capnproto/imports/foo.capnp create mode 100644 tests/auto/blackbox/testdata/change-in-disabled-product/change-in-disabled-product.qbs create mode 100644 tests/auto/blackbox/testdata/change-in-disabled-product/test1.txt create mode 100644 tests/auto/blackbox/testdata/change-in-disabled-product/test2.txt create mode 100644 tests/auto/blackbox/testdata/change-in-imported-file/change-in-imported-file.qbs create mode 100644 tests/auto/blackbox/testdata/change-in-imported-file/prepare.js create mode 100644 tests/auto/blackbox/testdata/change-in-imported-file/test.txt create mode 100644 tests/auto/blackbox/testdata/change-tracking-and-multiplexing/change-tracking-and-multiplexing.qbs create mode 100644 tests/auto/blackbox/testdata/change-tracking-and-multiplexing/lib.cpp create mode 100644 tests/auto/blackbox/testdata/changed-files/changed-files.qbs create mode 100644 tests/auto/blackbox/testdata/changed-files/file1.cpp create mode 100644 tests/auto/blackbox/testdata/changed-files/file2.cpp create mode 100644 tests/auto/blackbox/testdata/changed-files/main.cpp create mode 100644 tests/auto/blackbox/testdata/changed-inputs-from-dependencies/changed-inputs-from-dependencies.qbs create mode 100644 tests/auto/blackbox/testdata/changed-inputs-from-dependencies/input.txt create mode 100644 tests/auto/blackbox/testdata/changed-rule-inputs/changed-rule-inputs.qbs create mode 100644 tests/auto/blackbox/testdata/check-timestamps/check-timestamps.qbs create mode 100644 tests/auto/blackbox/testdata/check-timestamps/file.cpp create mode 100644 tests/auto/blackbox/testdata/check-timestamps/file.h create mode 100644 tests/auto/blackbox/testdata/check-timestamps/main.cpp create mode 100644 tests/auto/blackbox/testdata/choose-module-instance/choose-module-instance.qbs create mode 100644 tests/auto/blackbox/testdata/choose-module-instance/gerbil.txt.in create mode 100644 tests/auto/blackbox/testdata/choose-module-instance/modules/limerick/lord.qbs create mode 100644 tests/auto/blackbox/testdata/choose-module-instance/modules/limerick/ritchie.qbs create mode 100644 tests/auto/blackbox/testdata/choose-module-instance/other-searchpath/modules/limerick/generic.qbs create mode 100644 tests/auto/blackbox/testdata/clean/clean.qbs create mode 100644 tests/auto/blackbox/testdata/clean/dep.cpp create mode 100644 tests/auto/blackbox/testdata/clean/main.cpp create mode 100755 tests/auto/blackbox/testdata/cli/HelloWorld.cs create mode 100755 tests/auto/blackbox/testdata/cli/Libby.cs create mode 100755 tests/auto/blackbox/testdata/cli/Libby2.cs create mode 100644 tests/auto/blackbox/testdata/cli/Module.cs create mode 100755 tests/auto/blackbox/testdata/cli/Module.vb create mode 100644 tests/auto/blackbox/testdata/cli/dotnettest.qbs create mode 100644 tests/auto/blackbox/testdata/cli/fshello.fs create mode 100644 tests/auto/blackbox/testdata/cli/fshello.qbs create mode 100644 tests/auto/blackbox/testdata/combined-sources/combinable.cpp create mode 100644 tests/auto/blackbox/testdata/combined-sources/combined-sources.qbs create mode 100644 tests/auto/blackbox/testdata/combined-sources/main.cpp create mode 100644 tests/auto/blackbox/testdata/combined-sources/uncombinable.cpp create mode 100644 tests/auto/blackbox/testdata/command-file/command-file.qbs create mode 100644 tests/auto/blackbox/testdata/command-file/lib.cpp create mode 100644 tests/auto/blackbox/testdata/command-file/list.gcc create mode 100644 tests/auto/blackbox/testdata/command-file/list.msvc create mode 100644 tests/auto/blackbox/testdata/command-file/main.cpp create mode 100644 tests/auto/blackbox/testdata/compilerDefinesByLanguage/CppDefinesApp.qbs create mode 100644 tests/auto/blackbox/testdata/compilerDefinesByLanguage/app.c create mode 100644 tests/auto/blackbox/testdata/compilerDefinesByLanguage/compilerDefinesByLanguage.qbs create mode 100644 tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.c create mode 100644 tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.cpp create mode 100644 tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.m create mode 100644 tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.mm create mode 100644 tests/auto/blackbox/testdata/conanfile-probe/testapp/conanfile-probe-project.qbs create mode 100644 tests/auto/blackbox/testdata/conanfile-probe/testapp/conanfile.py create mode 100644 tests/auto/blackbox/testdata/conanfile-probe/testlib/conanfile.py create mode 100644 tests/auto/blackbox/testdata/concurrent-executor/concurrent-executor.qbs create mode 100644 tests/auto/blackbox/testdata/concurrent-executor/dummy1.input create mode 100644 tests/auto/blackbox/testdata/concurrent-executor/dummy2.input create mode 100644 tests/auto/blackbox/testdata/concurrent-executor/util.js create mode 100644 tests/auto/blackbox/testdata/conditional-export/conditional-export.qbs create mode 100644 tests/auto/blackbox/testdata/conditional-export/main.cpp create mode 100644 tests/auto/blackbox/testdata/conditional-filetagger/conditional-filetagger.qbs create mode 100644 tests/auto/blackbox/testdata/conditional-filetagger/main.custom create mode 100644 tests/auto/blackbox/testdata/configure/configure.qbs create mode 100644 tests/auto/blackbox/testdata/configure/main.cpp create mode 100644 tests/auto/blackbox/testdata/configure/modules/definition/module.qbs create mode 100644 tests/auto/blackbox/testdata/conflicting-artifacts/conflicting-artifacts.qbs create mode 100644 tests/auto/blackbox/testdata/conflicting-artifacts/main.cpp create mode 100644 tests/auto/blackbox/testdata/cpu-features/cpu-features.qbs create mode 100644 tests/auto/blackbox/testdata/cpu-features/main.cpp create mode 100644 tests/auto/blackbox/testdata/cxx-language-version/cxx-language-version.qbs create mode 100644 tests/auto/blackbox/testdata/cxx-language-version/main.cpp create mode 100644 tests/auto/blackbox/testdata/dependenciesProperty/dependenciesProperty.qbs create mode 100644 tests/auto/blackbox/testdata/dependenciesProperty/product2.cpp create mode 100644 tests/auto/blackbox/testdata/dependency-scanning-loop/dependency-scanning-loop.qbs create mode 100644 tests/auto/blackbox/testdata/dependency-scanning-loop/main.cpp create mode 100644 tests/auto/blackbox/testdata/deprecated-property/deprecated-property.qbs create mode 100644 tests/auto/blackbox/testdata/deprecated-property/modules/themodule/m.qbs create mode 100644 tests/auto/blackbox/testdata/disappeared-profile/disappeared-profile.qbs create mode 100644 tests/auto/blackbox/testdata/disappeared-profile/in1.txt create mode 100644 tests/auto/blackbox/testdata/disappeared-profile/in2.txt create mode 100644 tests/auto/blackbox/testdata/disappeared-profile/modules-dir/modules/m/m.qbs create mode 100644 tests/auto/blackbox/testdata/discard-unused-data/discard-unused-data.qbs create mode 100644 tests/auto/blackbox/testdata/discard-unused-data/main.cpp create mode 100644 tests/auto/blackbox/testdata/distribution-include-paths/distribution-include-paths.qbs create mode 100644 tests/auto/blackbox/testdata/distribution-include-paths/main.cpp create mode 100644 tests/auto/blackbox/testdata/distribution-include-paths/subdir/gagagugu.h create mode 100644 tests/auto/blackbox/testdata/driver-linker-flags/driver-linker-flags.qbs create mode 100644 tests/auto/blackbox/testdata/driver-linker-flags/main.cpp create mode 100644 tests/auto/blackbox/testdata/dynamic-library-in-module/Dll.qbs create mode 100644 tests/auto/blackbox/testdata/dynamic-library-in-module/lib1.cpp create mode 100644 tests/auto/blackbox/testdata/dynamic-library-in-module/lib2.cpp create mode 100644 tests/auto/blackbox/testdata/dynamic-library-in-module/lib3.cpp create mode 100644 tests/auto/blackbox/testdata/dynamic-library-in-module/lib4.cpp create mode 100644 tests/auto/blackbox/testdata/dynamic-library-in-module/lib5.cpp create mode 100644 tests/auto/blackbox/testdata/dynamic-library-in-module/main.cpp create mode 100644 tests/auto/blackbox/testdata/dynamic-library-in-module/modules/thelib/broken.cpp create mode 100644 tests/auto/blackbox/testdata/dynamic-library-in-module/modules/thelib/thelib.qbs create mode 100644 tests/auto/blackbox/testdata/dynamic-library-in-module/modules/theotherlib/theotherlib.qbs create mode 100644 tests/auto/blackbox/testdata/dynamic-library-in-module/modules/thethirdlib/thethirdlib.qbs create mode 100644 tests/auto/blackbox/testdata/dynamic-library-in-module/theapp.qbs create mode 100644 tests/auto/blackbox/testdata/dynamic-library-in-module/thelibs.qbs create mode 100644 tests/auto/blackbox/testdata/dynamic-project/dynamic-project.qbs create mode 100644 tests/auto/blackbox/testdata/dynamic-project/src/app/main.cpp create mode 100644 tests/auto/blackbox/testdata/dynamic-project/src/app2/main.cpp create mode 100644 tests/auto/blackbox/testdata/dynamicMultiplexRule/dynamicMultiplexRule.qbs create mode 100644 tests/auto/blackbox/testdata/dynamicMultiplexRule/one.txt create mode 100644 tests/auto/blackbox/testdata/dynamicMultiplexRule/three.txt create mode 100644 tests/auto/blackbox/testdata/dynamicMultiplexRule/two.txt create mode 100644 tests/auto/blackbox/testdata/dynamicRuleOutputs/after/numbers.l create mode 100644 tests/auto/blackbox/testdata/dynamicRuleOutputs/before/flexoptionsreader.js create mode 100644 tests/auto/blackbox/testdata/dynamicRuleOutputs/before/genlexer.qbs create mode 100644 tests/auto/blackbox/testdata/dynamicRuleOutputs/before/numbers.l create mode 100644 tests/auto/blackbox/testdata/empty-profile/empty-profile.qbs create mode 100644 tests/auto/blackbox/testdata/empty-profile/main.cpp create mode 100644 tests/auto/blackbox/testdata/enableExceptions/empty.m create mode 100644 tests/auto/blackbox/testdata/enableExceptions/empty.mm create mode 100644 tests/auto/blackbox/testdata/enableExceptions/emptymain.cpp create mode 100644 tests/auto/blackbox/testdata/enableExceptions/exceptions-objc.qbs create mode 100644 tests/auto/blackbox/testdata/enableExceptions/exceptions-objcpp-cpp.qbs create mode 100644 tests/auto/blackbox/testdata/enableExceptions/exceptions-objcpp.qbs create mode 100644 tests/auto/blackbox/testdata/enableExceptions/exceptions.qbs create mode 100644 tests/auto/blackbox/testdata/enableExceptions/main.cpp create mode 100644 tests/auto/blackbox/testdata/enableExceptions/main.m create mode 100644 tests/auto/blackbox/testdata/enableExceptions/none.qbs create mode 100644 tests/auto/blackbox/testdata/enableRtti/main.cpp create mode 100644 tests/auto/blackbox/testdata/enableRtti/rtti.qbs create mode 100644 tests/auto/blackbox/testdata/env-merging/env-merging.qbs create mode 100644 tests/auto/blackbox/testdata/env-merging/main.c create mode 100644 tests/auto/blackbox/testdata/env-normalization/env-normalization.qbs create mode 100644 tests/auto/blackbox/testdata/erroneous/nonexistentWorkingDir/nonexistentWorkingDir.qbs create mode 100644 tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-filePath/main.cpp create mode 100644 tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-filePath/outputArtifacts-missing-filePath.qbs create mode 100644 tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-fileTags/main.cpp create mode 100644 tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-fileTags/outputArtifacts-missing-fileTags.qbs create mode 100644 tests/auto/blackbox/testdata/erroneous/tag-mismatch/tag-mismatch.qbs create mode 100644 tests/auto/blackbox/testdata/erroneous/texttemplate-unknown-placeholder/boom.txt.in create mode 100644 tests/auto/blackbox/testdata/erroneous/texttemplate-unknown-placeholder/texttemplate-unknown-placeholder.qbs create mode 100644 tests/auto/blackbox/testdata/error-info/error-info.qbs create mode 100644 tests/auto/blackbox/testdata/error-info/helper.js create mode 100644 tests/auto/blackbox/testdata/escaped-linker-flags/escaped-linker-flags.qbs create mode 100644 tests/auto/blackbox/testdata/escaped-linker-flags/main.cpp create mode 100644 tests/auto/blackbox/testdata/explicitly-depends-on/explicitly-depends-on.qbs create mode 100644 tests/auto/blackbox/testdata/explicitly-depends-on/modules/module1/module-fish.txt create mode 100644 tests/auto/blackbox/testdata/explicitly-depends-on/modules/module1/module1.qbs create mode 100644 tests/auto/blackbox/testdata/explicitly-depends-on/step1.txt create mode 100644 tests/auto/blackbox/testdata/export-rule/blubber.cpp create mode 100644 tests/auto/blackbox/testdata/export-rule/export-rule.qbs create mode 100644 tests/auto/blackbox/testdata/export-rule/myapp.blubb create mode 100644 tests/auto/blackbox/testdata/export-to-outside-searchpath/export-to-outside-searchpath.qbs create mode 100644 tests/auto/blackbox/testdata/export-to-outside-searchpath/qbs-resources/modules/aModule/aModule.qbs create mode 100644 tests/auto/blackbox/testdata/exported-dependency-in-disabled-product/exported-dependency-in-disabled-product.qbs create mode 100644 tests/auto/blackbox/testdata/exported-dependency-in-disabled-product/main.cpp create mode 100644 tests/auto/blackbox/testdata/exported-dependency-in-disabled-product/modules/broken/broken.qbs create mode 100644 tests/auto/blackbox/testdata/exported-property-in-disabled-product/exported-property-in-disabled-product.qbs create mode 100644 tests/auto/blackbox/testdata/exported-property-in-disabled-product/main.cpp create mode 100644 tests/auto/blackbox/testdata/exported-property-in-disabled-product/modules/broken/broken.qbs create mode 100644 tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib.pc create mode 100644 tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib_windows.pc create mode 100644 tests/auto/blackbox/testdata/exports-pkgconfig/TheSecondLib.pc create mode 100644 tests/auto/blackbox/testdata/exports-pkgconfig/boringstaticlib.cpp create mode 100644 tests/auto/blackbox/testdata/exports-pkgconfig/exports-pkgconfig.qbs create mode 100644 tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.cpp create mode 100644 tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.h create mode 100644 tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper1/helper1.qbs create mode 100644 tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper2/helper2.qbs create mode 100644 tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper3/helper3.qbs create mode 100644 tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.cpp create mode 100644 tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.h create mode 100644 tests/auto/blackbox/testdata/exports-qbs/consumer.cpp create mode 100644 tests/auto/blackbox/testdata/exports-qbs/consumer.qbs create mode 100644 tests/auto/blackbox/testdata/exports-qbs/exports-qbs-products.qbs create mode 100644 tests/auto/blackbox/testdata/exports-qbs/exports-qbs.qbs create mode 100644 tests/auto/blackbox/testdata/exports-qbs/helper.cpp.in create mode 100644 tests/auto/blackbox/testdata/exports-qbs/helper.js create mode 100644 tests/auto/blackbox/testdata/exports-qbs/imports/Helper2/helper2.js create mode 100644 tests/auto/blackbox/testdata/exports-qbs/lib.qbs create mode 100644 tests/auto/blackbox/testdata/exports-qbs/mylib.cpp create mode 100644 tests/auto/blackbox/testdata/exports-qbs/mylib.h create mode 100644 tests/auto/blackbox/testdata/exports-qbs/tool.cpp create mode 100644 tests/auto/blackbox/testdata/exports-qbs/tool.qbs create mode 100644 tests/auto/blackbox/testdata/external-libs/external-libs.qbs create mode 100644 tests/auto/blackbox/testdata/external-libs/lib1.cpp create mode 100644 tests/auto/blackbox/testdata/external-libs/lib2.cpp create mode 100644 tests/auto/blackbox/testdata/external-libs/main.cpp create mode 100644 tests/auto/blackbox/testdata/fallback-module-provider/fallback-module-provider.qbs create mode 100644 tests/auto/blackbox/testdata/fallback-module-provider/libdir/qbsmetatestmodule.pc create mode 100644 tests/auto/blackbox/testdata/fallback-module-provider/main.cpp create mode 100644 tests/auto/blackbox/testdata/fileDependencies/awesomelib/awesome.h create mode 100644 tests/auto/blackbox/testdata/fileDependencies/awesomelib/magnificent.h create mode 100644 tests/auto/blackbox/testdata/fileDependencies/fileDependencies.qbs create mode 100644 tests/auto/blackbox/testdata/fileDependencies/src/narf.cpp create mode 100644 tests/auto/blackbox/testdata/fileDependencies/src/narf.h create mode 100644 tests/auto/blackbox/testdata/fileDependencies/src/zort.cpp create mode 100644 tests/auto/blackbox/testdata/filetagsfilter-merging/MyApplication.qbs create mode 100644 tests/auto/blackbox/testdata/filetagsfilter-merging/filetagsfilter-merging.qbs create mode 100644 tests/auto/blackbox/testdata/filetagsfilter-merging/main.cpp create mode 100644 tests/auto/blackbox/testdata/find/find-cli.qbs create mode 100644 tests/auto/blackbox/testdata/freedesktop/freedesktop.qbs create mode 100644 tests/auto/blackbox/testdata/freedesktop/main.cpp create mode 100644 tests/auto/blackbox/testdata/freedesktop/myapp.appdata.xml create mode 100644 tests/auto/blackbox/testdata/freedesktop/myapp.desktop create mode 100644 tests/auto/blackbox/testdata/freedesktop/myapp.png create mode 100644 tests/auto/blackbox/testdata/generate-linker-map-file/generate-linker-map-file.qbs create mode 100644 tests/auto/blackbox/testdata/generate-linker-map-file/main.cpp create mode 100644 tests/auto/blackbox/testdata/generated-artifact-as-input-to-dynamic-rule/input.txt create mode 100644 tests/auto/blackbox/testdata/generated-artifact-as-input-to-dynamic-rule/p.qbs create mode 100644 tests/auto/blackbox/testdata/generator/generator.qbs create mode 100644 tests/auto/blackbox/testdata/generator/input.both.txt create mode 100644 tests/auto/blackbox/testdata/generator/input.file1.txt create mode 100644 tests/auto/blackbox/testdata/generator/input.file2.txt create mode 100644 tests/auto/blackbox/testdata/generator/input.none.txt create mode 100644 tests/auto/blackbox/testdata/generator/main.cpp create mode 100644 tests/auto/blackbox/testdata/group-condition-change/group-condition-change.qbs create mode 100644 tests/auto/blackbox/testdata/group-condition-change/input_kaputt.txt create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/groups-in-modules.qbs create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/modules/helper/chunk.coal create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/modules/helper/diamondc.c create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/modules/helper/helper.qbs create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/modules/helper2/helper2.c create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/modules/helper2/helper2.qbs create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/modules/helper3/helper3.c create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/modules/helper3/helper3.qbs create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/modules/helper4/helper4.c create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/modules/helper4/helper4.qbs create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/modules/helper5/helper5.c create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/modules/helper5/helper5.qbs create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/modules/helper6/helper6.c create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/modules/helper6/helper6.qbs create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/rock.coal create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/someotherfile.txt create mode 100644 tests/auto/blackbox/testdata/groups-in-modules/someotherfile2.txt create mode 100644 tests/auto/blackbox/testdata/grpc/grpc.cpp create mode 100644 tests/auto/blackbox/testdata/grpc/grpc.proto create mode 100644 tests/auto/blackbox/testdata/grpc/grpc_cpp.qbs create mode 100644 tests/auto/blackbox/testdata/host-os-properties/host-os-properties.qbs create mode 100644 tests/auto/blackbox/testdata/host-os-properties/main.cpp create mode 100644 tests/auto/blackbox/testdata/ico/dmg.iconset/icon_128x128.png create mode 100644 tests/auto/blackbox/testdata/ico/dmg.iconset/icon_16x16.png create mode 100644 tests/auto/blackbox/testdata/ico/dmg.iconset/icon_256x256.png create mode 100644 tests/auto/blackbox/testdata/ico/dmg.iconset/icon_32x32.png create mode 100644 tests/auto/blackbox/testdata/ico/dmg.iconset/icon_512x512.png create mode 100644 tests/auto/blackbox/testdata/ico/ico.qbs create mode 100644 tests/auto/blackbox/testdata/ico/icon_16x16.png create mode 100644 tests/auto/blackbox/testdata/ico/icon_32x32.png create mode 100644 tests/auto/blackbox/testdata/import-assignment/import-assignment.qbs create mode 100644 tests/auto/blackbox/testdata/import-assignment/imports/MyImport/myimport.js create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/custom1command.js create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/custom1prepare1.js create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/custom1prepare2.js create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/custom2prepare/custom2prepare1.js create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/custom2prepare/custom2prepare2.js create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/import-change-tracking-product.qbs create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/import-change-tracking.qbs create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/imports/custom2command/custom2command1.js create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/imports/custom2command/custom2command2.js create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/input1.txt create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/input2.txt create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/irrelevant.js create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/probe1.js create mode 100644 tests/auto/blackbox/testdata/import-change-tracking/probe2.js create mode 100644 tests/auto/blackbox/testdata/import-in-properties-condition/import-in-properties-condition.qbs create mode 100644 tests/auto/blackbox/testdata/import-in-properties-condition/modules/amodule/m.qbs create mode 100644 tests/auto/blackbox/testdata/import-in-properties-condition/modules/depmodule/m.qbs create mode 100644 tests/auto/blackbox/testdata/import-searchpath/import-searchpath.qbs create mode 100644 tests/auto/blackbox/testdata/import-searchpath/qbs/imports/CppApplication.qbs create mode 100644 tests/auto/blackbox/testdata/import-searchpath/src/import-searchpath-app1.qbs create mode 100644 tests/auto/blackbox/testdata/import-searchpath/src/import-searchpath-app2.qbs create mode 100644 tests/auto/blackbox/testdata/import-searchpath/src/main.cpp create mode 100644 tests/auto/blackbox/testdata/import-searchpath/src/somefile.cpp create mode 100644 tests/auto/blackbox/testdata/importing-product/header.h.in create mode 100644 tests/auto/blackbox/testdata/importing-product/importing-product.qbs create mode 100644 tests/auto/blackbox/testdata/importing-product/main.cpp create mode 100644 tests/auto/blackbox/testdata/imports-conflict/imports-conflict.qbs create mode 100644 tests/auto/blackbox/testdata/imports-conflict/modules/themodule/m.qbs create mode 100644 tests/auto/blackbox/testdata/imports-conflict/modules/themodule/utils.js create mode 100644 tests/auto/blackbox/testdata/includeLookup/includeLookup.qbs create mode 100644 tests/auto/blackbox/testdata/includeLookup/main.cpp create mode 100644 tests/auto/blackbox/testdata/includeLookup/modules/definition/fakeopenssl/sha.h create mode 100644 tests/auto/blackbox/testdata/includeLookup/modules/definition/module.qbs create mode 100644 tests/auto/blackbox/testdata/innosetup/inc/qbsinc.iss create mode 100644 tests/auto/blackbox/testdata/innosetup/innosetup.qbs create mode 100644 tests/auto/blackbox/testdata/innosetup/test.iss create mode 100644 tests/auto/blackbox/testdata/innosetupDependencies/innosetupDependencies.qbs create mode 100644 tests/auto/blackbox/testdata/innosetupDependencies/main.c create mode 100644 tests/auto/blackbox/testdata/innosetupDependencies/test.iss create mode 100644 tests/auto/blackbox/testdata/input-tags-change-tracking/input-tags-change-tracking.qbs create mode 100644 tests/auto/blackbox/testdata/input-tags-change-tracking/input.txt create mode 100644 tests/auto/blackbox/testdata/inputs-from-dependencies/file1.txt create mode 100644 tests/auto/blackbox/testdata/inputs-from-dependencies/file2.txt create mode 100644 tests/auto/blackbox/testdata/inputs-from-dependencies/file3.txt create mode 100644 tests/auto/blackbox/testdata/inputs-from-dependencies/file4.txt create mode 100644 tests/auto/blackbox/testdata/inputs-from-dependencies/inputs-from-dependencies.qbs create mode 100644 tests/auto/blackbox/testdata/install-duplicates-no-error/file1.txt create mode 100644 tests/auto/blackbox/testdata/install-duplicates-no-error/file2.txt create mode 100644 tests/auto/blackbox/testdata/install-duplicates-no-error/file3.txt create mode 100644 tests/auto/blackbox/testdata/install-duplicates-no-error/install-duplicates-no-error.qbs create mode 100644 tests/auto/blackbox/testdata/install-duplicates/dir1/file1.txt create mode 100644 tests/auto/blackbox/testdata/install-duplicates/dir1/file2.txt create mode 100644 tests/auto/blackbox/testdata/install-duplicates/dir2/file1.txt create mode 100644 tests/auto/blackbox/testdata/install-duplicates/dir2/file2.txt create mode 100644 tests/auto/blackbox/testdata/install-duplicates/dir2/file3.txt create mode 100644 tests/auto/blackbox/testdata/install-duplicates/install-duplicates.qbs create mode 100644 tests/auto/blackbox/testdata/install-locations/install-locations.qbs create mode 100644 tests/auto/blackbox/testdata/install-locations/main.cpp create mode 100644 tests/auto/blackbox/testdata/install-locations/thelib.cpp create mode 100644 tests/auto/blackbox/testdata/install-locations/theplugin.cpp create mode 100644 tests/auto/blackbox/testdata/install-root-from-project-file/file.txt create mode 100644 tests/auto/blackbox/testdata/install-root-from-project-file/install-root-from-project-file.qbs create mode 100644 tests/auto/blackbox/testdata/install-tree/data/foo.txt create mode 100644 tests/auto/blackbox/testdata/install-tree/data/subdir1/bar.txt create mode 100644 tests/auto/blackbox/testdata/install-tree/data/subdir2/baz.txt create mode 100644 tests/auto/blackbox/testdata/install-tree/install-tree.qbs create mode 100644 tests/auto/blackbox/testdata/install-tree/main.cpp create mode 100644 tests/auto/blackbox/testdata/installable-as-auxiliary-input/installable-as-auxiliary-input.qbs create mode 100644 tests/auto/blackbox/testdata/installable/installable.qbs create mode 100644 tests/auto/blackbox/testdata/installable/main.cpp create mode 100644 tests/auto/blackbox/testdata/installed-source-files/installed-source-files.qbs create mode 100644 tests/auto/blackbox/testdata/installed-source-files/main.cpp create mode 100644 tests/auto/blackbox/testdata/installed-source-files/readme.txt create mode 100644 tests/auto/blackbox/testdata/installed-transformer-output/qbs668.qbs create mode 100644 tests/auto/blackbox/testdata/installed_artifact/installed_artifact.qbs create mode 100644 tests/auto/blackbox/testdata/installed_artifact/main.cpp create mode 100644 tests/auto/blackbox/testdata/installpackage/installpackage.qbs create mode 100644 tests/auto/blackbox/testdata/installpackage/lib.cpp create mode 100644 tests/auto/blackbox/testdata/installpackage/lib.h create mode 100644 tests/auto/blackbox/testdata/installpackage/main.cpp create mode 100644 tests/auto/blackbox/testdata/invalid-command-property/input.txt create mode 100644 tests/auto/blackbox/testdata/invalid-command-property/invalid-command-property.qbs create mode 100644 tests/auto/blackbox/testdata/invalid-extension-instantiation/invalid-extension-instantiation.qbs create mode 100644 tests/auto/blackbox/testdata/invalid-install-dir/invalid-install-dir.qbs create mode 100644 tests/auto/blackbox/testdata/invalid-install-dir/main.cpp create mode 100644 tests/auto/blackbox/testdata/invalid-library-names/invalid-library-names.qbs create mode 100644 tests/auto/blackbox/testdata/invalid-library-names/main.cpp create mode 100644 tests/auto/blackbox/testdata/jsextensions-binaryfile/binaryfile.qbs create mode 100644 tests/auto/blackbox/testdata/jsextensions-file/file.qbs create mode 100644 tests/auto/blackbox/testdata/jsextensions-fileinfo/fileinfo.qbs create mode 100644 tests/auto/blackbox/testdata/jsextensions-process/main.cpp create mode 100644 tests/auto/blackbox/testdata/jsextensions-process/process.qbs create mode 100644 tests/auto/blackbox/testdata/jsextensions-propertylist/propertylist.qbs create mode 100644 tests/auto/blackbox/testdata/jsextensions-temporarydir/jsextensions-temporarydir.qbs create mode 100644 tests/auto/blackbox/testdata/jsextensions-textfile/textfile.qbs create mode 100644 tests/auto/blackbox/testdata/last-module-candidate-broken/last-module-candidate-broken.qbs create mode 100644 tests/auto/blackbox/testdata/last-module-candidate-broken/main.cpp create mode 100644 tests/auto/blackbox/testdata/last-module-candidate-broken/qbs/modules/Foo/Foo1.qbs create mode 100644 tests/auto/blackbox/testdata/last-module-candidate-broken/qbs/modules/Foo/Foo2.qbs create mode 100644 tests/auto/blackbox/testdata/ld/coreutils.cpp create mode 100644 tests/auto/blackbox/testdata/ld/coreutils.h create mode 100644 tests/auto/blackbox/testdata/ld/ld.qbs create mode 100644 tests/auto/blackbox/testdata/ld/main.cpp create mode 100644 tests/auto/blackbox/testdata/lexyacc/lex_outfile/lex_outfile.qbs create mode 100644 tests/auto/blackbox/testdata/lexyacc/lex_outfile/lexer.l create mode 100644 tests/auto/blackbox/testdata/lexyacc/lex_outfile/parser.y create mode 100644 tests/auto/blackbox/testdata/lexyacc/lex_outfile/types.h create mode 100644 tests/auto/blackbox/testdata/lexyacc/lex_prefix/lex_prefix.qbs create mode 100644 tests/auto/blackbox/testdata/lexyacc/lex_prefix/lexer.l create mode 100644 tests/auto/blackbox/testdata/lexyacc/lex_prefix/parser.y create mode 100644 tests/auto/blackbox/testdata/lexyacc/lex_prefix/types.h create mode 100644 tests/auto/blackbox/testdata/lexyacc/modules/bisonhelper/bisonhelper.qbs create mode 100644 tests/auto/blackbox/testdata/lexyacc/one-grammar/lexer.l create mode 100644 tests/auto/blackbox/testdata/lexyacc/one-grammar/one-grammar.qbs create mode 100644 tests/auto/blackbox/testdata/lexyacc/one-grammar/parser.y create mode 100644 tests/auto/blackbox/testdata/lexyacc/one-grammar/types.h create mode 100644 tests/auto/blackbox/testdata/lexyacc/two-grammars/g1.l create mode 100644 tests/auto/blackbox/testdata/lexyacc/two-grammars/g1.y create mode 100644 tests/auto/blackbox/testdata/lexyacc/two-grammars/g2.l create mode 100644 tests/auto/blackbox/testdata/lexyacc/two-grammars/g2.y create mode 100644 tests/auto/blackbox/testdata/lexyacc/two-grammars/main.c create mode 100644 tests/auto/blackbox/testdata/lexyacc/two-grammars/two-grammars.qbs create mode 100644 tests/auto/blackbox/testdata/lexyacc/unistd.h create mode 100644 tests/auto/blackbox/testdata/lexyacc/yacc_output/lexer.l create mode 100644 tests/auto/blackbox/testdata/lexyacc/yacc_output/parser.y create mode 100644 tests/auto/blackbox/testdata/lexyacc/yacc_output/types.h create mode 100644 tests/auto/blackbox/testdata/lexyacc/yacc_output/yacc_output.qbs create mode 100644 tests/auto/blackbox/testdata/linker-library-duplicates/lib1.cpp create mode 100644 tests/auto/blackbox/testdata/linker-library-duplicates/lib2.cpp create mode 100644 tests/auto/blackbox/testdata/linker-library-duplicates/lib3.cpp create mode 100644 tests/auto/blackbox/testdata/linker-library-duplicates/main.cpp create mode 100644 tests/auto/blackbox/testdata/linker-library-duplicates/setup-run-environment.qbs create mode 100644 tests/auto/blackbox/testdata/linker-module-definition/linker-module-definition.qbs create mode 100644 tests/auto/blackbox/testdata/linker-module-definition/testapp.cpp create mode 100644 tests/auto/blackbox/testdata/linker-module-definition/testlib.cpp create mode 100644 tests/auto/blackbox/testdata/linker-module-definition/testlib.def create mode 100644 tests/auto/blackbox/testdata/linker-variant/linker-variant.qbs create mode 100644 tests/auto/blackbox/testdata/linker-variant/main.cpp create mode 100644 tests/auto/blackbox/testdata/linkerMode/linkerMode.qbs create mode 100644 tests/auto/blackbox/testdata/linkerMode/main.c create mode 100644 tests/auto/blackbox/testdata/linkerMode/main.cpp create mode 100644 tests/auto/blackbox/testdata/linkerMode/main.m create mode 100644 tests/auto/blackbox/testdata/linkerMode/main.mm create mode 100644 tests/auto/blackbox/testdata/linkerMode/main.s create mode 100644 tests/auto/blackbox/testdata/linkerMode/staticlib.cpp create mode 100644 tests/auto/blackbox/testdata/linkerMode/staticmain.c create mode 100644 tests/auto/blackbox/testdata/linkerscripts/linkerscript1 create mode 100644 tests/auto/blackbox/testdata/linkerscripts/linkerscript2 create mode 100644 tests/auto/blackbox/testdata/linkerscripts/linkerscript_recursive create mode 100644 tests/auto/blackbox/testdata/linkerscripts/linkerscript_to_include create mode 100644 tests/auto/blackbox/testdata/linkerscripts/linkerscripts.qbs create mode 100644 tests/auto/blackbox/testdata/linkerscripts/scripts/linkerscript_in_directory create mode 100644 tests/auto/blackbox/testdata/linkerscripts/testlib.c create mode 100644 tests/auto/blackbox/testdata/list-products/list-products.qbs create mode 100644 tests/auto/blackbox/testdata/list-properties-with-outer/dummy.txt create mode 100644 tests/auto/blackbox/testdata/list-properties-with-outer/list-properties-with-outer.qbs create mode 100644 tests/auto/blackbox/testdata/list-properties-with-outer/modules/higher/higher.qbs create mode 100644 tests/auto/blackbox/testdata/list-properties-with-outer/modules/lower/lower.qbs create mode 100644 tests/auto/blackbox/testdata/list-property-order/dummy.txt create mode 100644 tests/auto/blackbox/testdata/list-property-order/modules/higher1/higher1.qbs create mode 100644 tests/auto/blackbox/testdata/list-property-order/modules/higher2/higher2.qbs create mode 100644 tests/auto/blackbox/testdata/list-property-order/modules/higher3/higher3.qbs create mode 100644 tests/auto/blackbox/testdata/list-property-order/modules/lower/lower.qbs create mode 100644 tests/auto/blackbox/testdata/list-property-order/product.qbs create mode 100644 tests/auto/blackbox/testdata/loadablemodule/exported.cpp create mode 100644 tests/auto/blackbox/testdata/loadablemodule/exported.h create mode 100644 tests/auto/blackbox/testdata/loadablemodule/loadablemodule.qbs create mode 100644 tests/auto/blackbox/testdata/loadablemodule/main.cpp create mode 100644 tests/auto/blackbox/testdata/localDeployment/localDeployment.qbs create mode 100644 tests/auto/blackbox/testdata/localDeployment/main.cpp create mode 100644 tests/auto/blackbox/testdata/makefile-generator/app.qbs create mode 100644 tests/auto/blackbox/testdata/makefile-generator/main.cpp create mode 100644 tests/auto/blackbox/testdata/maximum-c-language-version/main.c create mode 100644 tests/auto/blackbox/testdata/maximum-c-language-version/maximum-c-language-version.qbs create mode 100644 tests/auto/blackbox/testdata/maximum-c-language-version/modules/newermodule/newermodule.qbs create mode 100644 tests/auto/blackbox/testdata/maximum-c-language-version/modules/newestmodule/newestmodule.qbs create mode 100644 tests/auto/blackbox/testdata/maximum-c-language-version/modules/oldmodule/oldmodule.qbs create mode 100644 tests/auto/blackbox/testdata/maximum-cxx-language-version/main.cpp create mode 100644 tests/auto/blackbox/testdata/maximum-cxx-language-version/maximum-cxx-language-version.qbs create mode 100644 tests/auto/blackbox/testdata/maximum-cxx-language-version/modules/newermodule/newermodule.qbs create mode 100644 tests/auto/blackbox/testdata/maximum-cxx-language-version/modules/newestmodule/newestmodule.qbs create mode 100644 tests/auto/blackbox/testdata/maximum-cxx-language-version/modules/oldmodule/oldmodule.qbs create mode 100644 tests/auto/blackbox/testdata/minimumSystemVersion/fakewindows.qbs create mode 100644 tests/auto/blackbox/testdata/minimumSystemVersion/macappstore.qbs create mode 100644 tests/auto/blackbox/testdata/minimumSystemVersion/main.cpp create mode 100644 tests/auto/blackbox/testdata/minimumSystemVersion/main.mm create mode 100644 tests/auto/blackbox/testdata/minimumSystemVersion/specific.qbs create mode 100644 tests/auto/blackbox/testdata/minimumSystemVersion/unspecified-forced.qbs create mode 100644 tests/auto/blackbox/testdata/minimumSystemVersion/unspecified.qbs create mode 100644 tests/auto/blackbox/testdata/missing-dependency/main.cpp create mode 100644 tests/auto/blackbox/testdata/missing-dependency/missing-dependency.qbs create mode 100644 tests/auto/blackbox/testdata/missing-override-prefix/missing-override-prefix.qbs create mode 100644 tests/auto/blackbox/testdata/missing-project-file/ambiguous-dir/p1.qbs create mode 100644 tests/auto/blackbox/testdata/missing-project-file/ambiguous-dir/p2.qbs create mode 100644 tests/auto/blackbox/testdata/missing-project-file/ambiguous-dir/p3.qbs create mode 100644 tests/auto/blackbox/testdata/missing-project-file/empty-dir/irrelevant.txt create mode 100644 tests/auto/blackbox/testdata/missing-project-file/project-dir/file.cpp create mode 100644 tests/auto/blackbox/testdata/missing-project-file/project-dir/main.cpp create mode 100644 tests/auto/blackbox/testdata/missing-project-file/project-dir/missing-project-file.qbs create mode 100644 tests/auto/blackbox/testdata/module-conditions/module-conditions.qbs create mode 100644 tests/auto/blackbox/testdata/module-conditions/modules/m/m1.qbs create mode 100644 tests/auto/blackbox/testdata/module-conditions/modules/m/m2.qbs create mode 100644 tests/auto/blackbox/testdata/module-conditions/modules/m/m3.qbs create mode 100644 tests/auto/blackbox/testdata/module-conditions/modules/m/m4.qbs create mode 100644 tests/auto/blackbox/testdata/module-providers/main.cpp create mode 100644 tests/auto/blackbox/testdata/module-providers/module-providers.qbs create mode 100644 tests/auto/blackbox/testdata/module-providers/module-providers/mygenerator/provider.qbs create mode 100644 tests/auto/blackbox/testdata/module-providers/module-providers/othergenerator/provider.qbs create mode 100644 tests/auto/blackbox/testdata/moved-file-dependency/main.cpp create mode 100644 tests/auto/blackbox/testdata/moved-file-dependency/moved-file-dependency.qbs create mode 100644 tests/auto/blackbox/testdata/moved-file-dependency/subdir1/theheader.h create mode 100644 tests/auto/blackbox/testdata/multiple-changes/dummy.txt create mode 100644 tests/auto/blackbox/testdata/multiple-changes/multiple-changes.qbs create mode 100644 tests/auto/blackbox/testdata/multiple-configurations/file.cpp create mode 100644 tests/auto/blackbox/testdata/multiple-configurations/file.h create mode 100644 tests/auto/blackbox/testdata/multiple-configurations/lib.cpp create mode 100644 tests/auto/blackbox/testdata/multiple-configurations/lib.h create mode 100644 tests/auto/blackbox/testdata/multiple-configurations/main.cpp create mode 100644 tests/auto/blackbox/testdata/multiple-configurations/multiple-configurations.qbs create mode 100644 tests/auto/blackbox/testdata/multiplexed-tool/multiplexed-tool.qbs create mode 100644 tests/auto/blackbox/testdata/multiplexed-tool/tool.cpp create mode 100644 tests/auto/blackbox/testdata/nested-groups/file3.cpp create mode 100644 tests/auto/blackbox/testdata/nested-groups/file3.h create mode 100644 tests/auto/blackbox/testdata/nested-groups/main.cpp create mode 100644 tests/auto/blackbox/testdata/nested-groups/modules/themodule/themodule.qbs create mode 100644 tests/auto/blackbox/testdata/nested-groups/nested-groups.qbs create mode 100644 tests/auto/blackbox/testdata/nested-groups/subdir/file1.cpp create mode 100644 tests/auto/blackbox/testdata/nested-groups/subdir/file1.h create mode 100644 tests/auto/blackbox/testdata/nested-groups/subdir/file2.cpp create mode 100644 tests/auto/blackbox/testdata/nested-groups/subdir/file2.h create mode 100644 tests/auto/blackbox/testdata/nested-groups/subdir/main2.cpp create mode 100644 tests/auto/blackbox/testdata/nested-groups/subdir/main3.cpp create mode 100644 tests/auto/blackbox/testdata/nested-groups/subdir/other.cpp create mode 100644 tests/auto/blackbox/testdata/nested-groups/subdir/other.h create mode 100644 tests/auto/blackbox/testdata/nested-properties/dummy.txt create mode 100644 tests/auto/blackbox/testdata/nested-properties/modules/higherlevel/higher-level.qbs create mode 100644 tests/auto/blackbox/testdata/nested-properties/modules/lowerlevel/lower-level.qbs create mode 100644 tests/auto/blackbox/testdata/nested-properties/product.qbs create mode 100644 tests/auto/blackbox/testdata/new-output-artifact/input.txt create mode 100644 tests/auto/blackbox/testdata/new-output-artifact/new-output-artifact.qbs create mode 100644 tests/auto/blackbox/testdata/no-exported-symbols/lib.cpp create mode 100644 tests/auto/blackbox/testdata/no-exported-symbols/lib.h create mode 100644 tests/auto/blackbox/testdata/no-exported-symbols/main.cpp create mode 100644 tests/auto/blackbox/testdata/no-exported-symbols/no-exported-symbols.qbs create mode 100644 tests/auto/blackbox/testdata/no-profile/no-profile.qbs create mode 100644 tests/auto/blackbox/testdata/no-such-profile/no-such-profile.qbs create mode 100644 tests/auto/blackbox/testdata/nodejs/hello.js create mode 100644 tests/auto/blackbox/testdata/nodejs/hello.qbs create mode 100644 tests/auto/blackbox/testdata/non-broken-files-in-broken-product/broken.cpp create mode 100644 tests/auto/blackbox/testdata/non-broken-files-in-broken-product/fine.cpp create mode 100644 tests/auto/blackbox/testdata/non-broken-files-in-broken-product/non-broken-files-in-broken-product.qbs create mode 100644 tests/auto/blackbox/testdata/non-default-product/main.cpp create mode 100644 tests/auto/blackbox/testdata/non-default-product/non-default-product.qbs create mode 100644 tests/auto/blackbox/testdata/not-always-updated/not-always-updated.qbs create mode 100644 tests/auto/blackbox/testdata/nsis/hello.bat create mode 100644 tests/auto/blackbox/testdata/nsis/hello.nsi create mode 100644 tests/auto/blackbox/testdata/nsis/hello.qbs create mode 100644 tests/auto/blackbox/testdata/nsisDependencies/hello.nsi create mode 100644 tests/auto/blackbox/testdata/nsisDependencies/main.c create mode 100644 tests/auto/blackbox/testdata/nsisDependencies/nsisDependencies.qbs create mode 100644 tests/auto/blackbox/testdata/out-of-date-marking/main.c create mode 100644 tests/auto/blackbox/testdata/out-of-date-marking/out-of-date-marking.qbs create mode 100644 tests/auto/blackbox/testdata/output-artifact-auto-tagging/broken.cpp.in create mode 100644 tests/auto/blackbox/testdata/output-artifact-auto-tagging/main.cpp.in create mode 100644 tests/auto/blackbox/testdata/output-artifact-auto-tagging/output-artifact-auto-tagging.qbs create mode 100644 tests/auto/blackbox/testdata/output-redirection/input.bin create mode 100644 tests/auto/blackbox/testdata/output-redirection/input.txt create mode 100644 tests/auto/blackbox/testdata/output-redirection/output-redirection.qbs create mode 100644 tests/auto/blackbox/testdata/output-redirection/output.bin create mode 100644 tests/auto/blackbox/testdata/output-redirection/output.txt create mode 100644 tests/auto/blackbox/testdata/overrideProjectProperties/helper_lib.qbs create mode 100644 tests/auto/blackbox/testdata/overrideProjectProperties/helperlib.cpp create mode 100644 tests/auto/blackbox/testdata/overrideProjectProperties/main.cpp create mode 100644 tests/auto/blackbox/testdata/overrideProjectProperties/main2.cpp create mode 100644 tests/auto/blackbox/testdata/overrideProjectProperties/overrideProjectProperties.qbs create mode 100644 tests/auto/blackbox/testdata/overrideProjectProperties/project_using_helper_lib.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/BaseApp.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/bin/super-tool.1 create mode 100644 tests/auto/blackbox/testdata/path-probe/bin/tool create mode 100644 tests/auto/blackbox/testdata/path-probe/bin/tool.1 create mode 100644 tests/auto/blackbox/testdata/path-probe/bin/tool.2 create mode 100644 tests/auto/blackbox/testdata/path-probe/bin/tool.3 create mode 100644 tests/auto/blackbox/testdata/path-probe/bin/tool.4 create mode 100644 tests/auto/blackbox/testdata/path-probe/candidate-filter.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/environment-paths.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/main.cpp create mode 100644 tests/auto/blackbox/testdata/path-probe/mult-files-common-suffixes.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/mult-files-mult-suffixes.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/mult-files-mult-variants.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/mult-files-suffixes.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/mult-files.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/name-filter.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/non-existent-selector.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/non-existent.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/single-file-mult-variants.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/single-file-selector-array.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/single-file-selector.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/single-file-suffixes.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/single-file.qbs create mode 100644 tests/auto/blackbox/testdata/path-probe/usr/bin/tool create mode 100644 tests/auto/blackbox/testdata/pch-change-tracking/header1.h create mode 100644 tests/auto/blackbox/testdata/pch-change-tracking/header2.cpp create mode 100644 tests/auto/blackbox/testdata/pch-change-tracking/header2.h create mode 100644 tests/auto/blackbox/testdata/pch-change-tracking/main.cpp create mode 100644 tests/auto/blackbox/testdata/pch-change-tracking/pch-change-tracking.qbs create mode 100644 tests/auto/blackbox/testdata/pch-change-tracking/pch.h create mode 100644 tests/auto/blackbox/testdata/per-group-define-in-export-item/main.cpp create mode 100644 tests/auto/blackbox/testdata/per-group-define-in-export-item/per-group-define-in-export-item.qbs create mode 100644 tests/auto/blackbox/testdata/pkg-config-probe-sysroot/modules/themodule/themodule.qbs create mode 100644 tests/auto/blackbox/testdata/pkg-config-probe-sysroot/pkg-config.qbs create mode 100644 tests/auto/blackbox/testdata/pkg-config-probe-sysroot/sysroot1/usr/share/pkgconfig/dummy.pc create mode 100644 tests/auto/blackbox/testdata/pkg-config-probe-sysroot/sysroot2/usr/share/pkgconfig/dummy.pc create mode 100644 tests/auto/blackbox/testdata/pkg-config-probe/dummy1/dummy1.pc create mode 100644 tests/auto/blackbox/testdata/pkg-config-probe/dummy2/dummy2.pc create mode 100644 tests/auto/blackbox/testdata/pkg-config-probe/modules/themodule/themodule.qbs create mode 100644 tests/auto/blackbox/testdata/pkg-config-probe/pkg-config.qbs create mode 100644 tests/auto/blackbox/testdata/plugin-dependency/helper1.cpp create mode 100644 tests/auto/blackbox/testdata/plugin-dependency/helper2.cpp create mode 100644 tests/auto/blackbox/testdata/plugin-dependency/main.cpp create mode 100644 tests/auto/blackbox/testdata/plugin-dependency/plugin-dependency.qbs create mode 100644 tests/auto/blackbox/testdata/plugin-dependency/plugin1.cpp create mode 100644 tests/auto/blackbox/testdata/plugin-dependency/plugin2.cpp create mode 100644 tests/auto/blackbox/testdata/plugin-dependency/plugin3.cpp create mode 100644 tests/auto/blackbox/testdata/plugin-dependency/plugin4.cpp create mode 100644 tests/auto/blackbox/testdata/precompiled-and-prefix-headers/main.cpp create mode 100644 tests/auto/blackbox/testdata/precompiled-and-prefix-headers/pch.h create mode 100644 tests/auto/blackbox/testdata/precompiled-and-prefix-headers/precompiled-and-prefix-headers.qbs create mode 100644 tests/auto/blackbox/testdata/precompiled-and-prefix-headers/prefix.h create mode 100644 tests/auto/blackbox/testdata/precompiled-headers-and-redefine/file.cpp create mode 100644 tests/auto/blackbox/testdata/precompiled-headers-and-redefine/main.cpp create mode 100644 tests/auto/blackbox/testdata/precompiled-headers-and-redefine/pch.h create mode 100644 tests/auto/blackbox/testdata/precompiled-headers-and-redefine/precompiled-headers-and-redefine.qbs create mode 100644 tests/auto/blackbox/testdata/prevent-floating-point-values/prevent-floating-point-values.qbs create mode 100644 tests/auto/blackbox/testdata/probe-change-tracking/probe-change-tracking.qbs create mode 100644 tests/auto/blackbox/testdata/probe-in-exported-module/dependee.qbs create mode 100644 tests/auto/blackbox/testdata/probe-in-exported-module/dependency.qbs create mode 100644 tests/auto/blackbox/testdata/probe-in-exported-module/modules/depmodule/depmodule.qbs create mode 100644 tests/auto/blackbox/testdata/probe-in-exported-module/modules/mymodule/mymodule.qbs create mode 100644 tests/auto/blackbox/testdata/probe-in-exported-module/modules/myothermodule/myothermodule.qbs create mode 100644 tests/auto/blackbox/testdata/probe-in-exported-module/probe-in-exported-module.qbs create mode 100644 tests/auto/blackbox/testdata/probe-in-exported-module/test.in create mode 100644 tests/auto/blackbox/testdata/probe-in-exported-module/test2.in create mode 100644 tests/auto/blackbox/testdata/probeProperties/bin/tool create mode 100644 tests/auto/blackbox/testdata/probeProperties/main.c create mode 100644 tests/auto/blackbox/testdata/probeProperties/probeProperties.qbs create mode 100644 tests/auto/blackbox/testdata/probes-and-array-properties/modules/mymodule/mymodule.qbs create mode 100644 tests/auto/blackbox/testdata/probes-and-array-properties/probes-and-array-properties.qbs create mode 100644 tests/auto/blackbox/testdata/probes-and-shadow-products/probes-and-shadow-products.qbs create mode 100644 tests/auto/blackbox/testdata/probes-in-nested-modules/modules/inner/inner.qbs create mode 100644 tests/auto/blackbox/testdata/probes-in-nested-modules/modules/outer/outer.qbs create mode 100644 tests/auto/blackbox/testdata/probes-in-nested-modules/probes-in-nested-modules.qbs create mode 100644 tests/auto/blackbox/testdata/product-dependencies-by-type/main.cpp create mode 100644 tests/auto/blackbox/testdata/product-dependencies-by-type/modules/myconfig/myconfig.qbs create mode 100644 tests/auto/blackbox/testdata/product-dependencies-by-type/product-dependencies-by-type.qbs create mode 100644 tests/auto/blackbox/testdata/product-in-exported-module/modules/m/m.qbs create mode 100644 tests/auto/blackbox/testdata/product-in-exported-module/product-in-exported-module.qbs create mode 100644 tests/auto/blackbox/testdata/productproperties/app.qbs create mode 100644 tests/auto/blackbox/testdata/productproperties/blubb_header.h.in create mode 100644 tests/auto/blackbox/testdata/productproperties/header.qbs create mode 100644 tests/auto/blackbox/testdata/productproperties/main.cpp create mode 100644 tests/auto/blackbox/testdata/productproperties/productproperties.qbs create mode 100644 tests/auto/blackbox/testdata/project_filepath_check/main.cpp create mode 100644 tests/auto/blackbox/testdata/project_filepath_check/main2.cpp create mode 100644 tests/auto/blackbox/testdata/project_filepath_check/project1.qbs create mode 100644 tests/auto/blackbox/testdata/project_filepath_check/project2.qbs create mode 100644 tests/auto/blackbox/testdata/proper quoting/main.cpp create mode 100644 tests/auto/blackbox/testdata/proper quoting/my static lib helper.cpp create mode 100644 tests/auto/blackbox/testdata/proper quoting/my static lib.cpp create mode 100644 tests/auto/blackbox/testdata/proper quoting/proper quoting.qbs create mode 100644 tests/auto/blackbox/testdata/proper quoting/some helper/some helper.cpp create mode 100644 tests/auto/blackbox/testdata/proper quoting/some helper/some helper.h create mode 100644 tests/auto/blackbox/testdata/properties-in-export-items/main1.cpp create mode 100644 tests/auto/blackbox/testdata/properties-in-export-items/main2.cpp create mode 100644 tests/auto/blackbox/testdata/properties-in-export-items/properties-in-export-items.qbs create mode 100644 tests/auto/blackbox/testdata/property-assignment-in-failed-module/main.cpp create mode 100644 tests/auto/blackbox/testdata/property-assignment-in-failed-module/modules/m/m.qbs create mode 100644 tests/auto/blackbox/testdata/property-assignment-in-failed-module/property-assignment-in-failed-module.qbs create mode 100644 tests/auto/blackbox/testdata/property-assignment-on-non-present-module/property-assignment-on-non-present-module.qbs create mode 100644 tests/auto/blackbox/testdata/property-evaluation-context/modules/base/base.qbs create mode 100644 tests/auto/blackbox/testdata/property-evaluation-context/modules/top/top.qbs create mode 100644 tests/auto/blackbox/testdata/property-evaluation-context/property-evaluation-context.qbs create mode 100644 tests/auto/blackbox/testdata/property-precedence/dep.qbs create mode 100644 tests/auto/blackbox/testdata/property-precedence/dummy.txt create mode 100644 tests/auto/blackbox/testdata/property-precedence/modules/leaf/leaf.qbs create mode 100644 tests/auto/blackbox/testdata/property-precedence/modules/nonleaf/nonleaf.qbs create mode 100644 tests/auto/blackbox/testdata/property-precedence/property-precedence.qbs create mode 100644 tests/auto/blackbox/testdata/propertyChanges/lib.cpp create mode 100644 tests/auto/blackbox/testdata/propertyChanges/modules/TestModule/module.qbs create mode 100644 tests/auto/blackbox/testdata/propertyChanges/propertyChanges.qbs create mode 100644 tests/auto/blackbox/testdata/propertyChanges/ruletest.qbs create mode 100644 tests/auto/blackbox/testdata/propertyChanges/source1.cpp create mode 100644 tests/auto/blackbox/testdata/propertyChanges/source2.cpp create mode 100644 tests/auto/blackbox/testdata/propertyChanges/source3.cpp create mode 100644 tests/auto/blackbox/testdata/propertyChanges/test.in create mode 100644 tests/auto/blackbox/testdata/protobuf/addressbook.proto create mode 100644 tests/auto/blackbox/testdata/protobuf/addressbook_cpp.qbs create mode 100644 tests/auto/blackbox/testdata/protobuf/addressbook_objc.qbs create mode 100644 tests/auto/blackbox/testdata/protobuf/import-main.cpp create mode 100644 tests/auto/blackbox/testdata/protobuf/import.proto create mode 100644 tests/auto/blackbox/testdata/protobuf/import.qbs create mode 100644 tests/auto/blackbox/testdata/protobuf/main.cpp create mode 100644 tests/auto/blackbox/testdata/protobuf/main.m create mode 100644 tests/auto/blackbox/testdata/protobuf/needs-import-dir-main.cpp create mode 100644 tests/auto/blackbox/testdata/protobuf/needs-import-dir.proto create mode 100644 tests/auto/blackbox/testdata/protobuf/needs-import-dir.qbs create mode 100644 tests/auto/blackbox/testdata/protobuf/subdir/myenum.proto create mode 100644 tests/auto/blackbox/testdata/pseudo-multiplexing/pseudo-multiplexing.qbs create mode 100644 tests/auto/blackbox/testdata/qbs-session/file1.cpp create mode 100644 tests/auto/blackbox/testdata/qbs-session/file2.cpp create mode 100644 tests/auto/blackbox/testdata/qbs-session/lib.cpp create mode 100644 tests/auto/blackbox/testdata/qbs-session/lib.h create mode 100644 tests/auto/blackbox/testdata/qbs-session/main.cpp create mode 100644 tests/auto/blackbox/testdata/qbs-session/modules/mymodule/mymodule.qbs create mode 100644 tests/auto/blackbox/testdata/qbs-session/qbs-session.qbs create mode 100644 tests/auto/blackbox/testdata/qbsVersion/qbs-version.qbs create mode 100644 tests/auto/blackbox/testdata/rad-after-incomplete-build/dummy.txt create mode 100644 tests/auto/blackbox/testdata/rad-after-incomplete-build/project_with_rule.qbs create mode 100644 tests/auto/blackbox/testdata/recursive_renaming/dir/subdir/blubb.txt create mode 100644 tests/auto/blackbox/testdata/recursive_renaming/dir/wasser.txt create mode 100644 tests/auto/blackbox/testdata/recursive_renaming/recursive_renaming.qbs create mode 100644 tests/auto/blackbox/testdata/recursive_wildcards/dir/file1.txt create mode 100644 tests/auto/blackbox/testdata/recursive_wildcards/dir/subdir/file2.txt create mode 100644 tests/auto/blackbox/testdata/recursive_wildcards/recursive_wildcards.qbs create mode 100644 tests/auto/blackbox/testdata/referenceErrorInExport/main.c create mode 100644 tests/auto/blackbox/testdata/referenceErrorInExport/referenceErrorInExport.qbs create mode 100644 tests/auto/blackbox/testdata/remove-duplicate-libs/MyStaticLib.qbs create mode 100644 tests/auto/blackbox/testdata/remove-duplicate-libs/main.c create mode 100644 tests/auto/blackbox/testdata/remove-duplicate-libs/provider.c create mode 100644 tests/auto/blackbox/testdata/remove-duplicate-libs/provider2.c create mode 100644 tests/auto/blackbox/testdata/remove-duplicate-libs/remove-duplicate-libs.qbs create mode 100644 tests/auto/blackbox/testdata/remove-duplicate-libs/requestor1.c create mode 100644 tests/auto/blackbox/testdata/remove-duplicate-libs/requestor2.c create mode 100644 tests/auto/blackbox/testdata/renameDependency/after/lib2.cpp create mode 100644 tests/auto/blackbox/testdata/renameDependency/after/lib2.h create mode 100644 tests/auto/blackbox/testdata/renameDependency/before/lib.cpp create mode 100644 tests/auto/blackbox/testdata/renameDependency/before/lib.h create mode 100644 tests/auto/blackbox/testdata/renameDependency/before/main.cpp create mode 100644 tests/auto/blackbox/testdata/renameDependency/before/renameDependency.qbs create mode 100644 tests/auto/blackbox/testdata/reproducible-build/file1.cpp create mode 100644 tests/auto/blackbox/testdata/reproducible-build/file2.cpp create mode 100644 tests/auto/blackbox/testdata/reproducible-build/main.cpp create mode 100644 tests/auto/blackbox/testdata/reproducible-build/reproducible-build.qbs create mode 100644 tests/auto/blackbox/testdata/require-deprecated/blubb.js create mode 100644 tests/auto/blackbox/testdata/require-deprecated/require.qbs create mode 100644 tests/auto/blackbox/testdata/require-deprecated/zort.js create mode 100644 tests/auto/blackbox/testdata/require/blubb.js create mode 100644 tests/auto/blackbox/testdata/require/require.qbs create mode 100644 tests/auto/blackbox/testdata/require/zort.js create mode 100644 tests/auto/blackbox/testdata/rescue-transformer-data/main.cpp create mode 100644 tests/auto/blackbox/testdata/rescue-transformer-data/modules/m/m.qbs create mode 100644 tests/auto/blackbox/testdata/rescue-transformer-data/transformer-data-rescue.qbs create mode 100644 tests/auto/blackbox/testdata/response-files/cat-response-file.cpp create mode 100644 tests/auto/blackbox/testdata/response-files/response-files.qbs create mode 100644 tests/auto/blackbox/testdata/retagged-output-artifact/retagged-output-artifact.qbs create mode 100644 tests/auto/blackbox/testdata/rule-connection-with-excluded-inputs/rule-connection-with-excluded-inputs.qbs create mode 100644 tests/auto/blackbox/testdata/rule-with-no-inputs/rule-with-no-inputs.qbs create mode 100644 tests/auto/blackbox/testdata/rule-with-non-required-inputs/a.inp create mode 100644 tests/auto/blackbox/testdata/rule-with-non-required-inputs/b.inp create mode 100644 tests/auto/blackbox/testdata/rule-with-non-required-inputs/c.inp create mode 100644 tests/auto/blackbox/testdata/rule-with-non-required-inputs/rule-with-non-required-inputs.qbs create mode 100644 tests/auto/blackbox/testdata/ruleConditions/foo.narf create mode 100644 tests/auto/blackbox/testdata/ruleConditions/main.cpp create mode 100644 tests/auto/blackbox/testdata/ruleConditions/modules/narfzort/narfzort.qbs create mode 100644 tests/auto/blackbox/testdata/ruleConditions/ruleConditions.qbs create mode 100644 tests/auto/blackbox/testdata/ruleConditions/templates/zorduct.qbs create mode 100644 tests/auto/blackbox/testdata/ruleCycle/happy.grass create mode 100644 tests/auto/blackbox/testdata/ruleCycle/ruleCycle.qbs create mode 100644 tests/auto/blackbox/testdata/sanitizer/sanitizer.cpp create mode 100644 tests/auto/blackbox/testdata/sanitizer/sanitizer.qbs create mode 100644 tests/auto/blackbox/testdata/scan-result-in-non-dependency/app/app.h create mode 100644 tests/auto/blackbox/testdata/scan-result-in-non-dependency/app/app.qbs create mode 100644 tests/auto/blackbox/testdata/scan-result-in-non-dependency/app/main.cpp create mode 100644 tests/auto/blackbox/testdata/scan-result-in-non-dependency/lib/lib.h create mode 100644 tests/auto/blackbox/testdata/scan-result-in-non-dependency/other/other.qbs create mode 100644 tests/auto/blackbox/testdata/scan-result-in-non-dependency/p.qbs create mode 100644 tests/auto/blackbox/testdata/scan-result-in-other-product/app/app.h create mode 100644 tests/auto/blackbox/testdata/scan-result-in-other-product/app/app.qbs create mode 100644 tests/auto/blackbox/testdata/scan-result-in-other-product/app/main.cpp create mode 100644 tests/auto/blackbox/testdata/scan-result-in-other-product/lib/lib.h create mode 100644 tests/auto/blackbox/testdata/scan-result-in-other-product/lib/lib.qbs create mode 100644 tests/auto/blackbox/testdata/scan-result-in-other-product/other/other.qbs create mode 100644 tests/auto/blackbox/testdata/scan-result-in-other-product/p.qbs create mode 100644 tests/auto/blackbox/testdata/scanner-item/modules/m/m.qbs create mode 100644 tests/auto/blackbox/testdata/scanner-item/scanner-item.qbs create mode 100644 tests/auto/blackbox/testdata/scanner-item/subdir1/file.inc create mode 100644 tests/auto/blackbox/testdata/scanner-item/subdir1/file1.in create mode 100644 tests/auto/blackbox/testdata/scanner-item/subdir2/file.inc create mode 100644 tests/auto/blackbox/testdata/scanner-item/subdir2/file2.in create mode 100644 tests/auto/blackbox/testdata/separate-debug-info/foo.cpp create mode 100644 tests/auto/blackbox/testdata/separate-debug-info/main.cpp create mode 100644 tests/auto/blackbox/testdata/separate-debug-info/separate-debug-info.qbs create mode 100644 tests/auto/blackbox/testdata/setup-build-environment/modules/buildenv/buildenv.qbs create mode 100644 tests/auto/blackbox/testdata/setup-build-environment/modules/m/m.qbs create mode 100644 tests/auto/blackbox/testdata/setup-build-environment/setup-build-environment.qbs create mode 100644 tests/auto/blackbox/testdata/setup-run-environment/lib1.cpp create mode 100644 tests/auto/blackbox/testdata/setup-run-environment/lib2.cpp create mode 100644 tests/auto/blackbox/testdata/setup-run-environment/lib3.cpp create mode 100644 tests/auto/blackbox/testdata/setup-run-environment/lib4.cpp create mode 100644 tests/auto/blackbox/testdata/setup-run-environment/lib5.cpp create mode 100644 tests/auto/blackbox/testdata/setup-run-environment/main.cpp create mode 100644 tests/auto/blackbox/testdata/setup-run-environment/setup-run-environment.qbs create mode 100644 tests/auto/blackbox/testdata/smart-relinking/lib.cpp create mode 100644 tests/auto/blackbox/testdata/smart-relinking/main.cpp create mode 100644 tests/auto/blackbox/testdata/smart-relinking/smart-relinking.qbs create mode 100644 tests/auto/blackbox/testdata/smart-relinking/staticlib.cpp create mode 100644 tests/auto/blackbox/testdata/source-artifact-changes/modules/module_with_files/main.cpp create mode 100644 tests/auto/blackbox/testdata/source-artifact-changes/modules/module_with_files/module_with_files.qbs create mode 100644 tests/auto/blackbox/testdata/source-artifact-changes/source-artifact-changes.qbs create mode 100644 tests/auto/blackbox/testdata/source-artifact-in-inputs-from-dependencies/header.h create mode 100644 tests/auto/blackbox/testdata/source-artifact-in-inputs-from-dependencies/source-artifact-in-inputs-from-dependencies.qbs create mode 100644 tests/auto/blackbox/testdata/soversion/lib.cpp create mode 100644 tests/auto/blackbox/testdata/soversion/soversion.qbs create mode 100644 tests/auto/blackbox/testdata/static-lib-without-sources/lib.cpp create mode 100644 tests/auto/blackbox/testdata/static-lib-without-sources/static-lib-without-sources.qbs create mode 100644 tests/auto/blackbox/testdata/subprofile-change-tracking/main1.cpp create mode 100644 tests/auto/blackbox/testdata/subprofile-change-tracking/main2.cpp create mode 100644 tests/auto/blackbox/testdata/subprofile-change-tracking/subprofile-change-tracking.qbs create mode 100644 tests/auto/blackbox/testdata/successive-changes/input.in create mode 100644 tests/auto/blackbox/testdata/successive-changes/successive-changes.qbs create mode 100644 tests/auto/blackbox/testdata/suspicious-calls/copy-command.qbs create mode 100644 tests/auto/blackbox/testdata/suspicious-calls/copy-eval.qbs create mode 100644 tests/auto/blackbox/testdata/suspicious-calls/copy-prepare.qbs create mode 100644 tests/auto/blackbox/testdata/suspicious-calls/copy-probe.qbs create mode 100644 tests/auto/blackbox/testdata/suspicious-calls/direntries-command.qbs create mode 100644 tests/auto/blackbox/testdata/suspicious-calls/direntries-eval.qbs create mode 100644 tests/auto/blackbox/testdata/suspicious-calls/direntries-prepare.qbs create mode 100644 tests/auto/blackbox/testdata/suspicious-calls/direntries-probe.qbs create mode 100644 tests/auto/blackbox/testdata/suspicious-calls/test.txt create mode 100644 tests/auto/blackbox/testdata/symbolLinkMode/indirect.cpp create mode 100644 tests/auto/blackbox/testdata/symbolLinkMode/lib.cpp create mode 100644 tests/auto/blackbox/testdata/symbolLinkMode/main.cpp create mode 100644 tests/auto/blackbox/testdata/symbolLinkMode/symbolLinkMode.qbs create mode 100644 tests/auto/blackbox/testdata/symlink-removal/symlink-removal.qbs create mode 100644 tests/auto/blackbox/testdata/system-include-paths/main.cpp create mode 100644 tests/auto/blackbox/testdata/system-include-paths/subdir/gagagugu.h create mode 100644 tests/auto/blackbox/testdata/system-include-paths/system-include-paths.qbs create mode 100644 tests/auto/blackbox/testdata/system-run-paths/lib.cpp create mode 100644 tests/auto/blackbox/testdata/system-run-paths/main.cpp create mode 100644 tests/auto/blackbox/testdata/system-run-paths/system-run-paths.qbs create mode 100644 tests/auto/blackbox/testdata/texttemplate/cdefgabc.txt.in create mode 100644 tests/auto/blackbox/testdata/texttemplate/expected/lalala.txt create mode 100644 tests/auto/blackbox/testdata/texttemplate/expected/output.txt create mode 100644 tests/auto/blackbox/testdata/texttemplate/output.txt.in create mode 100644 tests/auto/blackbox/testdata/texttemplate/texttemplatetest.qbs create mode 100644 tests/auto/blackbox/testdata/toplevel-searchpath/qbs-resources/imports/MyProduct.qbs create mode 100644 tests/auto/blackbox/testdata/toplevel-searchpath/toplevel-searchpath.qbs create mode 100644 tests/auto/blackbox/testdata/trackAddFile/after/main.cpp create mode 100644 tests/auto/blackbox/testdata/trackAddFile/after/trackAddFile.qbs create mode 100644 tests/auto/blackbox/testdata/trackAddFile/after/zort.cpp create mode 100644 tests/auto/blackbox/testdata/trackAddFile/after/zort.h create mode 100644 tests/auto/blackbox/testdata/trackAddFile/before/main.cpp create mode 100644 tests/auto/blackbox/testdata/trackAddFile/before/narf.cpp create mode 100644 tests/auto/blackbox/testdata/trackAddFile/before/narf.h create mode 100644 tests/auto/blackbox/testdata/trackAddFile/before/trackAddFile.qbs create mode 100644 tests/auto/blackbox/testdata/trackExternalProductChanges/environmentChange.cpp create mode 100644 tests/auto/blackbox/testdata/trackExternalProductChanges/fileList.js create mode 100644 tests/auto/blackbox/testdata/trackExternalProductChanges/hidden/hiddenheaderqbs.h create mode 100644 tests/auto/blackbox/testdata/trackExternalProductChanges/including.cpp create mode 100644 tests/auto/blackbox/testdata/trackExternalProductChanges/jsFileChange.cpp create mode 100644 tests/auto/blackbox/testdata/trackExternalProductChanges/main.cpp create mode 100644 tests/auto/blackbox/testdata/trackExternalProductChanges/trackExternalProductChanges.qbs create mode 100644 tests/auto/blackbox/testdata/trackFileTags/after/main.cpp create mode 100644 tests/auto/blackbox/testdata/trackFileTags/after/trackFileTags.qbs create mode 100644 tests/auto/blackbox/testdata/trackFileTags/before/main.cpp create mode 100644 tests/auto/blackbox/testdata/trackFileTags/before/trackFileTags.qbs create mode 100644 tests/auto/blackbox/testdata/trackProducts/after/product3.qbs create mode 100644 tests/auto/blackbox/testdata/trackProducts/after/trackProducts.qbs create mode 100644 tests/auto/blackbox/testdata/trackProducts/after/zoo.cpp create mode 100644 tests/auto/blackbox/testdata/trackProducts/before/bar.cpp create mode 100644 tests/auto/blackbox/testdata/trackProducts/before/foo.cpp create mode 100644 tests/auto/blackbox/testdata/trackProducts/before/product1.qbs create mode 100644 tests/auto/blackbox/testdata/trackProducts/before/product2.qbs create mode 100644 tests/auto/blackbox/testdata/trackProducts/before/trackProducts.qbs create mode 100644 tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/a/a.qbs create mode 100644 tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/b/b.qbs create mode 100644 tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/c/c.qbs create mode 100644 tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/d/d.qbs create mode 100644 tests/auto/blackbox/testdata/transitive-invalid-dependencies/transitive-invalid-dependencies.qbs create mode 100644 tests/auto/blackbox/testdata/transitive-optional-dependencies/modules/a/a.qbs create mode 100644 tests/auto/blackbox/testdata/transitive-optional-dependencies/modules/b/b.qbs create mode 100644 tests/auto/blackbox/testdata/transitive-optional-dependencies/transitive-optional-dependencies.qbs create mode 100644 tests/auto/blackbox/testdata/typescript/animals.ts create mode 100644 tests/auto/blackbox/testdata/typescript/extra.js create mode 100644 tests/auto/blackbox/testdata/typescript/foo.ts create mode 100644 tests/auto/blackbox/testdata/typescript/foo2.ts create mode 100644 tests/auto/blackbox/testdata/typescript/hello.ts create mode 100644 tests/auto/blackbox/testdata/typescript/main.ts create mode 100644 tests/auto/blackbox/testdata/typescript/typescript.qbs create mode 100644 tests/auto/blackbox/testdata/typescript/woosh/extra.ts create mode 100644 tests/auto/blackbox/testdata/undefined-target-platform/undefined-target-platform.qbs create mode 100644 tests/auto/blackbox/testdata/usings-as-sole-inputs-non-multiplexed/custom1.in create mode 100644 tests/auto/blackbox/testdata/usings-as-sole-inputs-non-multiplexed/custom2.in create mode 100644 tests/auto/blackbox/testdata/usings-as-sole-inputs-non-multiplexed/usings-as-sole-inputs-non-multiplexed.qbs create mode 100644 tests/auto/blackbox/testdata/variant-suffix/lib.cpp create mode 100644 tests/auto/blackbox/testdata/variant-suffix/variant-suffix.qbs create mode 100644 tests/auto/blackbox/testdata/vcs/main.cpp create mode 100644 tests/auto/blackbox/testdata/vcs/vcstest.qbs create mode 100644 tests/auto/blackbox/testdata/versioncheck/modules/higher/higher.qbs create mode 100644 tests/auto/blackbox/testdata/versioncheck/modules/lower/lower.qbs create mode 100644 tests/auto/blackbox/testdata/versioncheck/versioncheck.qbs create mode 100644 tests/auto/blackbox/testdata/versionscript/testlib.c create mode 100644 tests/auto/blackbox/testdata/versionscript/versionscript create mode 100644 tests/auto/blackbox/testdata/versionscript/versionscript.qbs create mode 100644 tests/auto/blackbox/testdata/whole-archive/dynamiclib.cpp create mode 100644 tests/auto/blackbox/testdata/whole-archive/main1.cpp create mode 100644 tests/auto/blackbox/testdata/whole-archive/main2.cpp create mode 100644 tests/auto/blackbox/testdata/whole-archive/main3.cpp create mode 100644 tests/auto/blackbox/testdata/whole-archive/main4.cpp create mode 100644 tests/auto/blackbox/testdata/whole-archive/unused1.cpp create mode 100644 tests/auto/blackbox/testdata/whole-archive/unused2.cpp create mode 100644 tests/auto/blackbox/testdata/whole-archive/unused3.cpp create mode 100644 tests/auto/blackbox/testdata/whole-archive/unused4.cpp create mode 100644 tests/auto/blackbox/testdata/whole-archive/used.cpp create mode 100644 tests/auto/blackbox/testdata/whole-archive/whole-archive.qbs create mode 100644 tests/auto/blackbox/testdata/wildcard_renaming/pioniere.txt create mode 100644 tests/auto/blackbox/testdata/wildcard_renaming/wildcard_renaming.qbs create mode 100644 tests/auto/blackbox/testdata/wildcards-and-rules/input1.inp create mode 100644 tests/auto/blackbox/testdata/wildcards-and-rules/wildcards-and-rules.qbs create mode 100644 tests/auto/blackbox/testdata/wix/ExampleScript.bat create mode 100644 tests/auto/blackbox/testdata/wix/QbsBootstrapper.wxs create mode 100644 tests/auto/blackbox/testdata/wix/QbsSetup.wxs create mode 100644 tests/auto/blackbox/testdata/wix/Qt.wxs create mode 100644 tests/auto/blackbox/testdata/wix/WiXInstallers.qbs create mode 100644 tests/auto/blackbox/testdata/wix/de.wxl create mode 100644 tests/auto/blackbox/testdata/wixDependencies/QbsSetup.wxs create mode 100644 tests/auto/blackbox/testdata/wixDependencies/main.c create mode 100644 tests/auto/blackbox/testdata/wixDependencies/wixDependencies.qbs create mode 100644 tests/auto/blackbox/tst_blackbox.cpp create mode 100644 tests/auto/blackbox/tst_blackbox.h create mode 100644 tests/auto/blackbox/tst_blackboxandroid.cpp create mode 100644 tests/auto/blackbox/tst_blackboxandroid.h create mode 100644 tests/auto/blackbox/tst_blackboxapple.cpp create mode 100644 tests/auto/blackbox/tst_blackboxapple.h create mode 100644 tests/auto/blackbox/tst_blackboxbaremetal.cpp create mode 100644 tests/auto/blackbox/tst_blackboxbaremetal.h create mode 100644 tests/auto/blackbox/tst_blackboxbase.cpp create mode 100644 tests/auto/blackbox/tst_blackboxbase.h create mode 100644 tests/auto/blackbox/tst_blackboxexamples.cpp create mode 100644 tests/auto/blackbox/tst_blackboxexamples.h create mode 100644 tests/auto/blackbox/tst_blackboxjava.cpp create mode 100644 tests/auto/blackbox/tst_blackboxjava.h create mode 100644 tests/auto/blackbox/tst_blackboxjoblimits.cpp create mode 100644 tests/auto/blackbox/tst_blackboxqt.cpp create mode 100644 tests/auto/blackbox/tst_blackboxqt.h create mode 100644 tests/auto/blackbox/tst_clangdb.cpp create mode 100644 tests/auto/blackbox/tst_clangdb.h create mode 100644 tests/auto/buildgraph/buildgraph.pro create mode 100644 tests/auto/buildgraph/buildgraph.qbs create mode 100644 tests/auto/buildgraph/tst_buildgraph.cpp create mode 100644 tests/auto/buildgraph/tst_buildgraph.h create mode 100644 tests/auto/cmdlineparser/cmdlineparser.pro create mode 100644 tests/auto/cmdlineparser/cmdlineparser.qbs create mode 100644 tests/auto/cmdlineparser/tst_cmdlineparser.cpp create mode 100644 tests/auto/dllexport.h create mode 100644 tests/auto/language/language.pro create mode 100644 tests/auto/language/language.qbs create mode 100644 tests/auto/language/testdata/Banana create mode 100644 tests/auto/language/testdata/MyProperties.qbs create mode 100644 tests/auto/language/testdata/ParentWithExport.qbs create mode 100644 tests/auto/language/testdata/aboutdialog.cpp create mode 100644 tests/auto/language/testdata/additional-product-types.qbs create mode 100644 tests/auto/language/testdata/base-validate/base-validate.qbs create mode 100644 tests/auto/language/testdata/base-validate/modules/m/MParent.qbs create mode 100644 tests/auto/language/testdata/base-validate/modules/m/m.qbs create mode 100644 tests/auto/language/testdata/baseproperty.qbs create mode 100644 tests/auto/language/testdata/baseproperty_base.qbs create mode 100644 tests/auto/language/testdata/broken-dependency-cycle1.qbs create mode 100644 tests/auto/language/testdata/broken-dependency-cycle2.qbs create mode 100644 tests/auto/language/testdata/buildconfigstringlistsyntax.qbs create mode 100644 tests/auto/language/testdata/builtinFunctionInSearchPathsProperty.qbs create mode 100644 tests/auto/language/testdata/canonicalArchitecture.qbs create mode 100644 tests/auto/language/testdata/chained-probes/chained-probes.qbs create mode 100644 tests/auto/language/testdata/chained-probes/modules/m/m.qbs create mode 100644 tests/auto/language/testdata/conditionaldepends.qbs create mode 100644 tests/auto/language/testdata/conditionaldepends_base.qbs create mode 100644 tests/auto/language/testdata/defaultvalue/egon.qbs create mode 100644 tests/auto/language/testdata/defaultvalue/modules/higher/higher.qbs create mode 100644 tests/auto/language/testdata/defaultvalue/modules/lower/lower.qbs create mode 100644 tests/auto/language/testdata/defaultvalue/test.txt create mode 100644 tests/auto/language/testdata/delayed-error/modules/m/m.qbs create mode 100644 tests/auto/language/testdata/delayed-error/nonexisting.qbs create mode 100644 tests/auto/language/testdata/delayed-error/validation.qbs create mode 100644 tests/auto/language/testdata/dependencyOnAllProfiles.qbs create mode 100644 tests/auto/language/testdata/derived-sub-project/DerivedSubProject.qbs create mode 100644 tests/auto/language/testdata/derived-sub-project/project.qbs create mode 100644 tests/auto/language/testdata/derived-sub-project/subproject.qbs create mode 100644 tests/auto/language/testdata/dirwithmultipleprojects/project.qbs create mode 100644 tests/auto/language/testdata/dirwithmultipleprojects/project2.qbs create mode 100644 tests/auto/language/testdata/dirwithnoprojects/.gitignore create mode 100644 tests/auto/language/testdata/dirwithoneproject/project.qbs create mode 100644 tests/auto/language/testdata/disabled-subproject.qbs create mode 100644 tests/auto/language/testdata/dotted-names/dotted-names.qbs create mode 100644 tests/auto/language/testdata/dotted-names/modules/x/y/xy.qbs create mode 100644 tests/auto/language/testdata/drawline.asm create mode 100644 tests/auto/language/testdata/dummy.txt create mode 100644 tests/auto/language/testdata/empty-js-file.js create mode 100644 tests/auto/language/testdata/empty-js-file.qbs create mode 100644 tests/auto/language/testdata/enum-project-props.qbs create mode 100644 tests/auto/language/testdata/environmentvariable.qbs create mode 100644 tests/auto/language/testdata/erroneous/ParentItem.qbs create mode 100644 tests/auto/language/testdata/erroneous/ParentWithExport.qbs create mode 100644 tests/auto/language/testdata/erroneous/ambiguous-multiplex-dependency.qbs create mode 100644 tests/auto/language/testdata/erroneous/conflicting-module-instances.qbs create mode 100644 tests/auto/language/testdata/erroneous/conflicting-properties-in-export-items.qbs create mode 100644 tests/auto/language/testdata/erroneous/conflicting_fileTagsFilter.qbs create mode 100644 tests/auto/language/testdata/erroneous/dependency-profile-mismatch-2.qbs create mode 100644 tests/auto/language/testdata/erroneous/dependency-profile-mismatch.qbs create mode 100644 tests/auto/language/testdata/erroneous/dependency_cycle.qbs create mode 100644 tests/auto/language/testdata/erroneous/dependency_cycle2.qbs create mode 100644 tests/auto/language/testdata/erroneous/dependency_cycle3.qbs create mode 100644 tests/auto/language/testdata/erroneous/dependency_cycle4.qbs create mode 100644 tests/auto/language/testdata/erroneous/duplicate-multiplex-value.qbs create mode 100644 tests/auto/language/testdata/erroneous/duplicate-multiplex-value2.qbs create mode 100644 tests/auto/language/testdata/erroneous/duplicate_sources.qbs create mode 100644 tests/auto/language/testdata/erroneous/duplicate_sources_wildcards.qbs create mode 100644 tests/auto/language/testdata/erroneous/importloop1.qbs create mode 100644 tests/auto/language/testdata/erroneous/importloop2.qbs create mode 100644 tests/auto/language/testdata/erroneous/invalid-parameter-rhs.qbs create mode 100644 tests/auto/language/testdata/erroneous/invalid-parameter-type.qbs create mode 100644 tests/auto/language/testdata/erroneous/invalid-property-option.qbs create mode 100644 tests/auto/language/testdata/erroneous/invalid-references.qbs create mode 100644 tests/auto/language/testdata/erroneous/invalid_child_item_type.qbs create mode 100644 tests/auto/language/testdata/erroneous/invalid_file.qbs create mode 100644 tests/auto/language/testdata/erroneous/invalid_property_type.qbs create mode 100644 tests/auto/language/testdata/erroneous/invalid_stringlist_element.qbs create mode 100644 tests/auto/language/testdata/erroneous/main.cpp create mode 100644 tests/auto/language/testdata/erroneous/mismatching-multiplex-dependency.qbs create mode 100644 tests/auto/language/testdata/erroneous/missing-colon.qbs create mode 100644 tests/auto/language/testdata/erroneous/misused-inherited-property.qbs create mode 100644 tests/auto/language/testdata/erroneous/module-depends-on-product.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/conflicting-instances/conflicting-instance1.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/conflicting-instances/conflicting-instance2.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/module-a/module-a.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/module-b/module-b.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/module-with-invalid-original/module-with-invalid-original.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/module-with-product-dependency/module-with-product-dependency.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/module-with-wrong-property-option/m.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/module_with_parameters/module_with_parameters.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/no_such_property/no-such-property.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/prefix1/prefix1.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/prefix1/suffix/suffix.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/prefix2/prefix2.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/prefix2/suffix/suffix.qbs create mode 100644 tests/auto/language/testdata/erroneous/modules/readonly/readonly.qbs create mode 100644 tests/auto/language/testdata/erroneous/multiple_exports.qbs create mode 100644 tests/auto/language/testdata/erroneous/multiple_properties_in_subproject.qbs create mode 100644 tests/auto/language/testdata/erroneous/no-configure-in-probe.qbs create mode 100644 tests/auto/language/testdata/erroneous/nonexistentouter.qbs create mode 100644 tests/auto/language/testdata/erroneous/oldQbsVersion.qbs create mode 100644 tests/auto/language/testdata/erroneous/original-in-export-item.qbs create mode 100644 tests/auto/language/testdata/erroneous/original-in-export-item2.qbs create mode 100644 tests/auto/language/testdata/erroneous/original-in-export-item3.qbs create mode 100644 tests/auto/language/testdata/erroneous/original-in-module-prototype.qbs create mode 100644 tests/auto/language/testdata/erroneous/original-in-product-property.qbs create mode 100644 tests/auto/language/testdata/erroneous/overwrite-inherited-readonly-property.qbs create mode 100644 tests/auto/language/testdata/erroneous/overwrite-readonly-module-property.qbs create mode 100644 tests/auto/language/testdata/erroneous/properties-item-with-invalid-condition.qbs create mode 100644 tests/auto/language/testdata/erroneous/references_cycle.qbs create mode 100644 tests/auto/language/testdata/erroneous/references_cycle2.qbs create mode 100644 tests/auto/language/testdata/erroneous/references_cycle3.qbs create mode 100644 tests/auto/language/testdata/erroneous/reserved_name_in_import.qbs create mode 100644 tests/auto/language/testdata/erroneous/rule-without-output-tags.qbs create mode 100644 tests/auto/language/testdata/erroneous/same-module-prefix1.qbs create mode 100644 tests/auto/language/testdata/erroneous/same-module-prefix2.qbs create mode 100644 tests/auto/language/testdata/erroneous/subproject_cycle.qbs create mode 100644 tests/auto/language/testdata/erroneous/subproject_cycle2.qbs create mode 100644 tests/auto/language/testdata/erroneous/subproject_cycle3.qbs create mode 100644 tests/auto/language/testdata/erroneous/syntax-error-in-probe.qbs create mode 100644 tests/auto/language/testdata/erroneous/throw_in_property_binding.qbs create mode 100644 tests/auto/language/testdata/erroneous/undeclared-parameter1.qbs create mode 100644 tests/auto/language/testdata/erroneous/undeclared-parameter2.qbs create mode 100644 tests/auto/language/testdata/erroneous/undeclared_item.qbs create mode 100644 tests/auto/language/testdata/erroneous/undeclared_module_property_in_module.qbs create mode 100644 tests/auto/language/testdata/erroneous/undeclared_property.qbs create mode 100644 tests/auto/language/testdata/erroneous/undeclared_property_in_Properties_item.qbs create mode 100644 tests/auto/language/testdata/erroneous/undeclared_property_in_export_item.qbs create mode 100644 tests/auto/language/testdata/erroneous/undeclared_property_in_export_item2.qbs create mode 100644 tests/auto/language/testdata/erroneous/undeclared_property_in_export_item3.qbs create mode 100644 tests/auto/language/testdata/erroneous/undeclared_property_wrapper.qbs create mode 100644 tests/auto/language/testdata/erroneous/undefined_stringlist_element.qbs create mode 100644 tests/auto/language/testdata/erroneous/undefined_stringlist_element_in_probe.qbs create mode 100644 tests/auto/language/testdata/erroneous/unknown_item_type.qbs create mode 100644 tests/auto/language/testdata/erroneous/unknown_module.qbs create mode 100644 tests/auto/language/testdata/erroneous/wrong-toplevel-item.qbs create mode 100644 tests/auto/language/testdata/erroneous/wrongQbsVersionFormat.qbs create mode 100644 tests/auto/language/testdata/error-in-disabled-product.qbs create mode 100644 tests/auto/language/testdata/eval-error-in-non-present-module.qbs create mode 100644 tests/auto/language/testdata/exports.qbs create mode 100644 tests/auto/language/testdata/exports_product.qbs create mode 100644 tests/auto/language/testdata/file-in-product-and-module.qbs create mode 100644 tests/auto/language/testdata/filecontextproperties.qbs create mode 100644 tests/auto/language/testdata/filetags.qbs create mode 100644 tests/auto/language/testdata/getNativeSetting.qbs create mode 100644 tests/auto/language/testdata/groupconditions.qbs create mode 100644 tests/auto/language/testdata/groupname.qbs create mode 100644 tests/auto/language/testdata/homeDirectory.qbs create mode 100644 tests/auto/language/testdata/id-uniqueness.qbs create mode 100644 tests/auto/language/testdata/idusage.qbs create mode 100644 tests/auto/language/testdata/idusage_group.qbs create mode 100644 tests/auto/language/testdata/idusage_group2.qbs create mode 100644 tests/auto/language/testdata/idusagebase.qbs create mode 100644 tests/auto/language/testdata/idusagebasebase.qbs create mode 100644 tests/auto/language/testdata/import-collection/collection/file1.js create mode 100644 tests/auto/language/testdata/import-collection/collection/file2.js create mode 100644 tests/auto/language/testdata/import-collection/imports/Collection/file1.js create mode 100644 tests/auto/language/testdata/import-collection/imports/Collection/file2.js create mode 100644 tests/auto/language/testdata/import-collection/product.qbs create mode 100644 tests/auto/language/testdata/import-collection/project.qbs create mode 100644 tests/auto/language/testdata/inherited-properties-items/imports/DebugName.qbs create mode 100644 tests/auto/language/testdata/inherited-properties-items/imports/ReleaseName.qbs create mode 100644 tests/auto/language/testdata/inherited-properties-items/inherited-properties-items-product.qbs create mode 100644 tests/auto/language/testdata/inherited-properties-items/inherited-properties-items.qbs create mode 100644 tests/auto/language/testdata/invalid-overrides.qbs create mode 100644 tests/auto/language/testdata/invalidBindingInDisabledItem.qbs create mode 100644 tests/auto/language/testdata/jsextensions.js create mode 100644 tests/auto/language/testdata/jsimportsinmultiplescopes.js create mode 100644 tests/auto/language/testdata/jsimportsinmultiplescopes.qbs create mode 100644 tests/auto/language/testdata/main.cpp create mode 100644 tests/auto/language/testdata/module-merging-variant-values/module-merging-variant-values.qbs create mode 100644 tests/auto/language/testdata/module-merging-variant-values/modules/m1/m1.qbs create mode 100644 tests/auto/language/testdata/module-merging-variant-values/modules/m2/m2.qbs create mode 100644 tests/auto/language/testdata/module-prioritization-by-search-path/bar/modules/conflicting-instances/bar.qbs create mode 100644 tests/auto/language/testdata/module-prioritization-by-search-path/foo/modules/conflicting-instances/foo.qbs create mode 100644 tests/auto/language/testdata/module-prioritization-by-search-path/product.qbs create mode 100644 tests/auto/language/testdata/module-prioritization-by-search-path/project.qbs create mode 100644 tests/auto/language/testdata/module-property-overrides-per-product.qbs create mode 100644 tests/auto/language/testdata/moduleproperties.qbs create mode 100644 tests/auto/language/testdata/modulepropertiesingroups.qbs create mode 100644 tests/auto/language/testdata/modules.qbs create mode 100644 tests/auto/language/testdata/modules/broken/broken.qbs create mode 100644 tests/auto/language/testdata/modules/deepdummy/deep/moat/dummydeepmoat.qbs create mode 100644 tests/auto/language/testdata/modules/dummy/dummy.qbs create mode 100644 tests/auto/language/testdata/modules/dummy/dummy_base.qbs create mode 100644 tests/auto/language/testdata/modules/dummy2/dummy2.qbs create mode 100644 tests/auto/language/testdata/modules/dummy3/dummy3.qbs create mode 100644 tests/auto/language/testdata/modules/dummy3_loader/dummy3_loader.qbs create mode 100644 tests/auto/language/testdata/modules/dummyqt/core/dummycore.qbs create mode 100644 tests/auto/language/testdata/modules/dummyqt/gui/dummygui.qbs create mode 100644 tests/auto/language/testdata/modules/dummyqt/network/dummynetwork.qbs create mode 100644 tests/auto/language/testdata/modules/gmod/gmod1/gmod1.qbs create mode 100644 tests/auto/language/testdata/modules/gmod2/gmod2.qbs create mode 100644 tests/auto/language/testdata/modules/gmod3/qmod3.qbs create mode 100644 tests/auto/language/testdata/modules/gmod4/gmod4.qbs create mode 100644 tests/auto/language/testdata/modules/module-with-properties-item/module-with-properties-item.qbs create mode 100644 tests/auto/language/testdata/modules/module_with_file/module-with-file.qbs create mode 100644 tests/auto/language/testdata/modules/multiple_backends/backend1.qbs create mode 100644 tests/auto/language/testdata/modules/multiple_backends/backend2.qbs create mode 100644 tests/auto/language/testdata/modules/multiple_backends/backend3.qbs create mode 100644 tests/auto/language/testdata/modules/scopemod/scopemod.qbs create mode 100644 tests/auto/language/testdata/modulescope.qbs create mode 100644 tests/auto/language/testdata/modulescope_base.qbs create mode 100644 tests/auto/language/testdata/multiplexed-exports.qbs create mode 100644 tests/auto/language/testdata/multiplexing-by-profile/p1.qbs create mode 100644 tests/auto/language/testdata/multiplexing-by-profile/p2.qbs create mode 100644 tests/auto/language/testdata/multiplexing-by-profile/p3.qbs create mode 100644 tests/auto/language/testdata/multiplexing-by-profile/p4.qbs create mode 100644 tests/auto/language/testdata/narf create mode 100644 tests/auto/language/testdata/narf.zort create mode 100644 tests/auto/language/testdata/nativesettings.ini create mode 100644 tests/auto/language/testdata/non-applicable-module-property-in-profile.qbs create mode 100644 tests/auto/language/testdata/non-required-products.qbs create mode 100644 tests/auto/language/testdata/outerInGroup.qbs create mode 100644 tests/auto/language/testdata/overridden-properties-and-prototypes.qbs create mode 100644 tests/auto/language/testdata/overridden-variant-property.qbs create mode 100644 tests/auto/language/testdata/parameter-types.qbs create mode 100644 tests/auto/language/testdata/pathproperties.qbs create mode 100644 tests/auto/language/testdata/productconditions.qbs create mode 100644 tests/auto/language/testdata/productdirectories.qbs create mode 100644 tests/auto/language/testdata/profilevaluesandoverriddenvalues.qbs create mode 100644 tests/auto/language/testdata/properties-block-in-group.qbs create mode 100644 tests/auto/language/testdata/properties-item-in-module.qbs create mode 100644 tests/auto/language/testdata/propertiesblocks.qbs create mode 100644 tests/auto/language/testdata/propertiesblocks_base.qbs create mode 100644 tests/auto/language/testdata/property-assignment-in-exported-group.qbs create mode 100644 tests/auto/language/testdata/qbs-properties-in-project-condition.qbs create mode 100644 tests/auto/language/testdata/qbs-property-convenience-override.qbs create mode 100644 tests/auto/language/testdata/qbs1275.qbs create mode 100644 tests/auto/language/testdata/recursive-dependencies/recursive-dependencies.qbs create mode 100644 tests/auto/language/testdata/relaxed-error-mode/file1.txt create mode 100644 tests/auto/language/testdata/relaxed-error-mode/file2.txt create mode 100644 tests/auto/language/testdata/relaxed-error-mode/relaxed-error-mode.qbs create mode 100644 tests/auto/language/testdata/required-and-nonrequired-dependencies/complicated.qbs create mode 100644 tests/auto/language/testdata/required-and-nonrequired-dependencies/dependency-via-export.qbs create mode 100644 tests/auto/language/testdata/required-and-nonrequired-dependencies/dependency-via-module.qbs create mode 100644 tests/auto/language/testdata/required-and-nonrequired-dependencies/direct-dependencies.qbs create mode 100644 tests/auto/language/testdata/required-and-nonrequired-dependencies/modules/failing-validation-indirect/failing-validation-indirect.qbs create mode 100644 tests/auto/language/testdata/required-and-nonrequired-dependencies/modules/failing-validation/failing-validation.qbs create mode 100644 tests/auto/language/testdata/required-and-nonrequired-dependencies/required-chain-export-indirect.qbs create mode 100644 tests/auto/language/testdata/required-and-nonrequired-dependencies/required-chain-export.qbs create mode 100644 tests/auto/language/testdata/required-and-nonrequired-dependencies/required-chain-module.qbs create mode 100644 tests/auto/language/testdata/rfc1034identifier.qbs create mode 100644 tests/auto/language/testdata/subdir/exports-mylib.qbs create mode 100644 tests/auto/language/testdata/subdir/pathproperties_base.qbs create mode 100644 tests/auto/language/testdata/subdir2/exports-mylib2.qbs create mode 100644 tests/auto/language/testdata/suppressed-and-non-suppressed-errors.qbs create mode 100644 tests/auto/language/testdata/throwing-probe.qbs create mode 100644 tests/auto/language/testdata/use-internal-profile.qbs create mode 100644 tests/auto/language/testdata/versionCompare.qbs create mode 100644 tests/auto/language/testdata/zort create mode 100644 tests/auto/language/tst_language.cpp create mode 100644 tests/auto/language/tst_language.h create mode 100644 tests/auto/shared.h create mode 100644 tests/auto/tools/tools.pro create mode 100644 tests/auto/tools/tools.qbs create mode 100644 tests/auto/tools/tst_tools.cpp create mode 100644 tests/auto/tools/tst_tools.h create mode 100644 tests/benchmarker/activities.h create mode 100644 tests/benchmarker/benchmarker-main.cpp create mode 100644 tests/benchmarker/benchmarker.cpp create mode 100644 tests/benchmarker/benchmarker.h create mode 100644 tests/benchmarker/benchmarker.pro create mode 100644 tests/benchmarker/benchmarker.qbs create mode 100644 tests/benchmarker/commandlineparser.cpp create mode 100644 tests/benchmarker/commandlineparser.h create mode 100644 tests/benchmarker/exception.h create mode 100644 tests/benchmarker/runsupport.cpp create mode 100644 tests/benchmarker/runsupport.h create mode 100644 tests/benchmarker/valgrindrunner.cpp create mode 100644 tests/benchmarker/valgrindrunner.h create mode 100644 tests/fuzzy-test/commandlineparser.cpp create mode 100644 tests/fuzzy-test/commandlineparser.h create mode 100644 tests/fuzzy-test/fuzzy-test.pro create mode 100644 tests/fuzzy-test/fuzzy-test.qbs create mode 100644 tests/fuzzy-test/fuzzytester.cpp create mode 100644 tests/fuzzy-test/fuzzytester.h create mode 100644 tests/fuzzy-test/main.cpp create mode 100644 tests/tests.pro create mode 100644 tests/tests.qbs diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..081f0e9a --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,272 @@ +--- +Checks: > + -*, + bugprone-*, + cppcoreguidelines-interfaces-global-init, + cppcoreguidelines-pro-type-cstyle-cast, + cppcoreguidelines-pro-type-member-init, + cppcoreguidelines-slicing, + fuchsia-virtual-inheritance, + google-build-explicit-make-pair, + google-build-namespaces, + google-global-names-in-headers, + google-objc-*, + google-readability-casting, + google-readability-namespace-comments, + google-runtime-operator, + misc-definitions-in-headers, + misc-misplaced-const, + misc-new-delete-overloads, + misc-non-copyable-objects, + misc-redundant-expression, + misc-static-assert, + misc-uniqueptr-reset-release, + misc-unused-*, + modernize-avoid-bind, + modernize-deprecated-headers, + modernize-loop-convert, + modernize-make-*, + modernize-pass-by-value, + modernize-redundant-void-arg, + modernize-replace-*, + modernize-return-braced-init-list, + modernize-shrink-to-fit, + modernize-unary-static-assert, + modernize-use-auto, + modernize-use-bool-literals, + modernize-use-emplace, + modernize-use-equals-*, + modernize-use-noexcept, + modernize-use-override, + modernize-use-transparent-functors, + modernize-use-using, + performance-*, + readability-avoid-const-params-in-decls, + readability-container-size-empty, + readability-delete-null-pointer, + readability-deleted-default, + readability-identifier-naming, + readability-misleading-indentation, + readability-misplaced-array-index, + readability-non-const-parameter, + readability-redundant-*, + -readability-redundant-member-init, + readability-simplify-boolean-expr, + readability-static-definition-in-anonymous-namespace, + readability-uniqueptr-delete-release + +WarningsAsErrors: > + bugprone-*, + cppcoreguidelines-*, + google-*, + misc-unused-*, + modernize-*, + performance-*, + readability-*, + -readability-container-size-empty + +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +CheckOptions: + - key: bugprone-argument-comment.StrictMode + value: '0' + - key: bugprone-assert-side-effect.AssertMacros + value: assert + - key: bugprone-assert-side-effect.CheckFunctionCalls + value: '0' + - key: bugprone-dangling-handle.HandleClasses + value: 'std::basic_string_view;std::experimental::basic_string_view' + - key: bugprone-exception-escape.FunctionsThatShouldNotThrow + value: '' + - key: bugprone-exception-escape.IgnoredExceptions + value: '' + - key: bugprone-misplaced-widening-cast.CheckImplicitCasts + value: '0' + - key: bugprone-sizeof-expression.WarnOnSizeOfCompareToConstant + value: '1' + - key: bugprone-sizeof-expression.WarnOnSizeOfConstant + value: '1' + - key: bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression + value: '0' + - key: bugprone-sizeof-expression.WarnOnSizeOfThis + value: '1' + - key: bugprone-string-constructor.LargeLengthThreshold + value: '8388608' + - key: bugprone-string-constructor.WarnOnLargeLength + value: '1' + - key: bugprone-suspicious-enum-usage.StrictMode + value: '0' + - key: bugprone-suspicious-missing-comma.MaxConcatenatedTokens + value: '5' + - key: bugprone-suspicious-missing-comma.RatioThreshold + value: '0.200000' + - key: bugprone-suspicious-missing-comma.SizeThreshold + value: '5' + - key: bugprone-suspicious-string-compare.StringCompareLikeFunctions + value: '' + - key: bugprone-suspicious-string-compare.WarnOnImplicitComparison + value: '1' + - key: bugprone-suspicious-string-compare.WarnOnLogicalNotComparison + value: '0' + - key: bugprone-unused-return-value.CheckedFunctions + value: '::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty' + - key: cert-dcl16-c.NewSuffixes + value: 'L;LL;LU;LLU' + - key: cppcoreguidelines-no-malloc.Allocations + value: '::malloc;::calloc' + - key: cppcoreguidelines-no-malloc.Deallocations + value: '::free' + - key: cppcoreguidelines-no-malloc.Reallocations + value: '::realloc' + - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: '1' + - key: cppcoreguidelines-pro-bounds-constant-array-index.GslHeader + value: '' + - key: cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle + value: '0' + - key: cppcoreguidelines-pro-type-member-init.IgnoreArrays + value: '0' + - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions + value: '0' + - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor + value: '0' + - key: google-build-namespaces.HeaderFileExtensions + value: ',h,hh,hpp,hxx' + - key: google-global-names-in-headers.HeaderFileExtensions + value: ',h,hh,hpp,hxx' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.BranchThreshold + value: '4294967295' + - key: google-readability-function-size.LineThreshold + value: '4294967295' + - key: google-readability-function-size.NestingThreshold + value: '4294967295' + - key: google-readability-function-size.ParameterThreshold + value: '4294967295' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-function-size.VariableThreshold + value: '4294967295' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: google-runtime-int.SignedTypePrefix + value: int + - key: google-runtime-int.TypeSuffix + value: '' + - key: google-runtime-int.UnsignedTypePrefix + value: uint + - key: misc-definitions-in-headers.HeaderFileExtensions + value: ',h,hh,hpp,hxx' + - key: misc-definitions-in-headers.UseHeaderFileExtension + value: '1' + - key: misc-unused-parameters.StrictMode + value: '0' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-make-shared.IgnoreMacros + value: '1' + - key: modernize-make-shared.IncludeStyle + value: '0' + - key: modernize-make-shared.MakeSmartPtrFunction + value: 'std::make_shared' + - key: modernize-make-shared.MakeSmartPtrFunctionHeader + value: memory + - key: modernize-make-unique.IgnoreMacros + value: '1' + - key: modernize-make-unique.IncludeStyle + value: '0' + - key: modernize-make-unique.MakeSmartPtrFunction + value: 'std::make_unique' + - key: modernize-make-unique.MakeSmartPtrFunctionHeader + value: memory + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-pass-by-value.ValuesOnly + value: '0' + - key: modernize-raw-string-literal.ReplaceShorterLiterals + value: '0' + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-replace-random-shuffle.IncludeStyle + value: llvm + - key: modernize-use-auto.MinTypeNameLength + value: '5' + - key: modernize-use-auto.RemoveStars + value: '0' + - key: modernize-use-emplace.ContainersWithPushBack + value: '::std::vector;::std::list;::std::deque' + - key: modernize-use-emplace.SmartPointers + value: '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr' + - key: modernize-use-emplace.TupleMakeFunctions + value: '::std::make_pair;::std::make_tuple' + - key: modernize-use-emplace.TupleTypes + value: '::std::pair;::std::tuple' + - key: modernize-use-equals-default.IgnoreMacros + value: '1' + - key: modernize-use-equals-delete.IgnoreMacros + value: '1' + - key: modernize-use-noexcept.ReplacementString + value: '' + - key: modernize-use-noexcept.UseNoexceptFalse + value: '1' + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + - key: modernize-use-transparent-functors.SafeMode + value: '0' + - key: modernize-use-using.IgnoreMacros + value: '1' + - key: performance-faster-string-find.StringLikeClasses + value: 'std::basic_string' + - key: performance-for-range-copy.AllowedTypes + value: '' + - key: performance-for-range-copy.WarnOnAllAutoCopies + value: '0' + - key: performance-inefficient-string-concatenation.StrictMode + value: '0' + - key: performance-inefficient-vector-operation.VectorLikeClasses + value: '::std::vector' + - key: performance-move-const-arg.CheckTriviallyCopyableMove + value: '1' + - key: performance-move-constructor-init.IncludeStyle + value: llvm + - key: performance-type-promotion-in-math-fn.IncludeStyle + value: llvm + - key: performance-unnecessary-copy-initialization.AllowedTypes + value: '' + - key: performance-unnecessary-value-param.AllowedTypes + value: '' + - key: performance-unnecessary-value-param.IncludeStyle + value: llvm + - key: readability-function-size.BranchThreshold + value: '4294967295' + - key: readability-function-size.LineThreshold + value: '4294967295' + - key: readability-function-size.NestingThreshold + value: '4294967295' + - key: readability-function-size.ParameterThreshold + value: '4294967295' + - key: readability-function-size.StatementThreshold + value: '800' + - key: readability-function-size.VariableThreshold + value: '4294967295' + - key: readability-identifier-naming.IgnoreFailedSplit + value: '0' + - key: readability-inconsistent-declaration-parameter-name.IgnoreMacros + value: '1' + - key: readability-inconsistent-declaration-parameter-name.Strict + value: '0' + - key: readability-redundant-smartptr-get.IgnoreMacros + value: '1' + - key: readability-simplify-boolean-expr.ChainedConditionalAssignment + value: '0' + - key: readability-simplify-boolean-expr.ChainedConditionalReturn + value: '0' +... + diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..a949bb5a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +* +!docker/* +!scripts/* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..f2f854c1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "qtscript"] + path = src/shared/qtscript + url = ../../qt/qtscript.git diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000..dc306c9f --- /dev/null +++ b/.mailmap @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..4a8d9905 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,230 @@ +# +# Required environment variables in the travis config +# +# DOCKER_USERNAME +# +language: cpp + +git: + submodules: false + +env: + global: + - QT_INSTALL_DIR=~/Qt + - QT_VERSION=5.12.8 + - QTCREATOR_VERSION=4.11.2 + +cache: + directories: + - ${QT_INSTALL_DIR} + - ~/.ccache + +stages: + - name: Build Qbs and and run autotests + +jobs: + include: + - &build-on-bionic + stage: Build Qbs and and run autotests + name: With Qbs on Ubuntu bionic (linux_gcc64) + env: + BUILD_OPTIONS="modules.cpp.compilerWrapper:ccache modules.qbs.debugInformation:true" + services: + - docker + before_script: + - docker-compose pull bionic + - ccache -M 5G + - ccache -s + after_script: + - ccache -s + script: + - docker-compose run bionic scripts/build-qbs-with-qbs.sh + + - <<: *build-on-bionic + name: With QMake on Ubuntu bionic (linux_gcc64) + env: + BUILD_OPTIONS="CONFIG+=ccache" + script: + - docker-compose run bionic scripts/build-qbs-with-qmake.sh + + - <<: *build-on-bionic + name: With Qbs on Ubuntu bionic (mingw32_w64) + env: + BUILD_OPTIONS="profile:qt-mingw32_w64 modules.cpp.compilerWrapper:ccache modules.cpp.treatWarningsAsErrors:true config:release" + script: + - docker-compose run bionic qbs build ${BUILD_OPTIONS} + + - <<: *build-on-bionic + name: With Qbs on Ubuntu bionic (linux_clang64 & clang_tidy) + env: + BUILD_OPTIONS="profile:qt-clang_64 modules.cpp.compilerWrapper:ccache" + services: + - docker + script: + - docker-compose run --rm bionic scripts/run-analyzer.sh + + - <<: *build-on-bionic + name: With Qbs and with Qt 5.13 for Android + before_install: + - docker-compose pull bionic + - docker-compose pull bionic-android-513 + script: + - docker-compose run bionic qbs build modules.cpp.compilerWrapper:ccache modules.qbsbuildconfig.enableBundledQt:true config:release + - docker-compose run bionic-android-513 scripts/test-qt-for-android.sh release/install-root/usr/local/bin + + - <<: *build-on-bionic + name: With Qbs and with Qt 5.14 for Android + before_install: + - docker-compose pull bionic + - docker-compose pull bionic-android-514 + script: + - docker-compose run bionic qbs build modules.cpp.compilerWrapper:ccache modules.qbsbuildconfig.enableBundledQt:true config:release + - docker-compose run bionic-android-514 scripts/test-qt-for-android.sh release/install-root/usr/local/bin + + - &build-on-macos + stage: Build Qbs and and run autotests + name: With Qbs on macOS (xcode 11.3) + os: osx + osx_image: xcode11.3 + addons: + homebrew: + packages: + - capnp + - ccache + - grpc + - icoutils + - makensis + - protobuf + - python3 + - p7zip + update: true + env: + # Address sanitizer slows autotests down too much. + # We would hit the maximum build time on Travis. + BUILD_OPTIONS="modules.qbsbuildconfig.enableAddressSanitizer:false modules.cpp.compilerWrapper:ccache modules.qbs.debugInformation:true" + QMAKE_PATH=${QT_INSTALL_DIR}/${QT_VERSION}/clang_64/bin/qmake + PATH="${QT_INSTALL_DIR}/Qt Creator.app/Contents/MacOS:${PATH}" + QBS_BUILD_PROFILE=qt + before_install: + - ./scripts/install-qt.sh -d ${QT_INSTALL_DIR} --version ${QT_VERSION} qtbase qtdeclarative qttools qtscript qtscxml + - ./scripts/install-qt.sh -d ${QT_INSTALL_DIR} --version ${QTCREATOR_VERSION} qtcreator + - python3 -m pip install --user beautifulsoup4 lxml + before_script: + - ulimit -c unlimited -S # enable core dumps + script: + - ccache -s + - qbs setup-toolchains --detect + - qbs setup-qt ${QMAKE_PATH} qt + - qbs config profiles.qt.baseProfile xcode-macosx-x86_64 + - qbs config defaultProfile qt + - qbs config --list profiles + - scripts/build-qbs-with-qbs.sh + - ccache -s + # Find core dump and print traceback on failure + after_failure: + - | + for f in $(find /cores -maxdepth 1 -name 'core.*' -print); do + lldb --core $f --batch --one-line "bt" + done; + + - <<: *build-on-macos + name: With Qbs on macOS (xcode 11.3) using iOS profile + if: NOT branch =~ ^gerrit + env: + # Address sanitizer slows autotests down too much. + # We would hit the maximum build time on Travis. + BUILD_OPTIONS=modules.qbsbuildconfig.enableAddressSanitizer:false + QT_INSTALL_DIR=/Users/travis/Qt + PATH="${QT_INSTALL_DIR}/Qt Creator.app/Contents/MacOS:${PATH}" + QMAKE_PATH=${QT_INSTALL_DIR}/${QT_VERSION}/clang_64/bin/qmake + QBS_AUTOTEST_QMAKE_PATH=${QT_INSTALL_DIR}/${QT_VERSION}/ios/bin/qmake + QBS_BUILD_PROFILE=qt + QBS_AUTOTEST_BASE_PROFILE=xcode-iphoneos-arm64 + WITH_DOCS=0 + before_install: + - ./scripts/install-qt.sh -d ${QT_INSTALL_DIR} --version ${QT_VERSION} qtbase qtdeclarative qttools qtscript + - ./scripts/install-qt.sh -d ${QT_INSTALL_DIR} --version ${QT_VERSION} --target ios --toolchain ios qtbase qtdeclarative qttools qtscript + - ./scripts/install-qt.sh -d ${QT_INSTALL_DIR} --version ${QTCREATOR_VERSION} qtcreator + script: + - qbs setup-toolchains --detect + - qbs setup-qt ${QMAKE_PATH} qt + - qbs config profiles.qt.baseProfile xcode-macosx-x86_64 + - scripts/build-qbs-with-qbs.sh + + - <<: *build-on-macos + name: With Qbs on macOS (xcode 10.3) + osx_image: xcode10.3 + if: NOT branch =~ ^gerrit + + - <<: *build-on-macos + name: With Qbs on macOS (xcode 9.4) + osx_image: xcode9.4 + if: NOT branch =~ ^gerrit + + - &build-on-windows-with-docker + stage: Build Qbs and and run autotests + name: With Qbs on Windows with Docker (Visual Studio 2017) + if: NOT branch =~ ^gerrit + os: windows + services: docker + env: + CLCACHE_DIR="${HOME}/.ccache" + before-install: + - curl -sLo "/c/Program Files/Docker/docker-compose.exe" https://github.com/docker/compose/releases/download/1.25.3/docker-compose-Windows-x86_64.exe + - docker-compose pull windows + before_script: + - docker-compose run --rm windows clcache -s + script: + - > + docker-compose run --rm windows qbs build + -p dist + qbs.buildVariant:release + modules.cpp.compilerWrapper:clcache + modules.qbsbuildconfig.enableBundledQt:true + modules.qbsbuildconfig.enableProjectFileUpdates:true + modules.qbsbuildconfig.enableUnitTests:true + modules.cpp.treatWarningsAsErrors:true + project.withDocumentation:true + config:release-64 profile:qt64 + config:release profile:qt + after_script: + - docker-compose run --rm windows clcache -s + + - &build-on-windows + stage: Build Qbs and and run autotests + name: With Qbs on Windows (Visual Studio 2017) + os: windows + env: + # Need to build in release mode. Otherwise autotests would be too slow. + BUILD_OPTIONS="config:release modules.cpp.compilerWrapper:clcache" + QMAKE_PATH=${QT_INSTALL_DIR}/${QT_VERSION}/msvc2017_64/bin/qmake.exe + PATH="${QT_INSTALL_DIR}/Tools/QtCreator/bin:/c/Python38:/c/Python39:/c/Python38/Scripts:/c/Python39/Scripts:${PATH}" + QBS_BUILD_PROFILE=qt + CLCACHE_DIR="${HOME}/.ccache" + before_install: + # Install Qbs and Qt + - ./scripts/install-qt.sh -d ${QT_INSTALL_DIR} --version ${QT_VERSION} --toolchain win64_msvc2017_64 qtbase qtdeclarative qttools qtscript + - ./scripts/install-qt.sh -d ${QT_INSTALL_DIR} --version ${QTCREATOR_VERSION} qtcreator + - choco install python3 + - pip3 install conan beautifulsoup4 lxml + - pip3 install git+https://github.com/frerich/clcache.git@cae73d8255d78db8ba11e23c51fd2c9a89e7475b + before_script: + - clcache -s + after_script: + - clcache -s + script: + - qbs setup-toolchains --detect + - qbs setup-qt ${QMAKE_PATH} qt + - qbs config qt.baseProfile MSVC2017-x64 + - qbs config defaultProfile qt + - scripts/build-qbs-with-qbs.sh + + - <<: *build-on-windows + name: With Qbs on Windows (clang-cl) + if: NOT branch =~ ^gerrit + script: + - qbs setup-toolchains --detect + - qbs setup-qt ${QMAKE_PATH} qt + - qbs config profiles.qt.baseProfile clang-cl-x86_64 + - qbs config defaultProfile qt + - scripts/build-qbs-with-qbs.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..86c2de06 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,102 @@ +# Contributing to Qbs + +The main source code repository is hosted at +[codereview.qt-project.org](https://codereview.qt-project.org/q/project:qbs/qbs). + +The Qbs source code is also mirrored on [code.qt.io](https://code.qt.io/cgit/qbs/qbs.git/) +and on [GitHub](https://github.com/qbs/qbs). Please note that those mirrors +are read-only and we do not accept pull-requests on GitHub. However, we gladly +accept contributions via [Gerrit](https://codereview.qt-project.org/q/project:qbs/qbs). + +This document briefly describes steps required to be able to propose changes +to the Qbs project. + +See the [Qt Contribution Guidelines](https://wiki.qt.io/Qt_Contribution_Guidelines) +page, [Setting up Gerrit](https://wiki.qt.io/Setting_up_Gerrit) and +[Gerrit Introduction](https://wiki.qt.io/Gerrit_Introduction) for more +details about how to upload patches to Gerrit. + +## Preparations + +* [Set up](https://wiki.qt.io/Setting_up_Gerrit#How_to_get_started_-_Gerrit_registration) +a Gerrit account +* Tweak your SSH config as instructed [here](https://wiki.qt.io/Setting_up_Gerrit#Local_Setup) +* Use the recommended Git settings, defined [here](https://wiki.qt.io/Setting_up_Gerrit#Configuring_Git) +* Get the source code as described below + +## Cloning Qbs + +Clone Qbs from the [code.qt.io](https://code.qt.io/cgit/qbs/qbs.git/) mirror +``` +git clone git://code.qt.io/qbs/qbs.git +``` +Alternatively, you can clone from the mirror on the [GitHub](https://github.com/qbs/qbs) +``` +git clone https://github.com/qbs/qbs.git +``` + +Set up the Gerrit remote +``` +git remote add gerrit ssh://@codereview.qt-project.org:29418/qbs/qbs +``` + +## Setting up git hooks + +Install the hook generating Commit-Id files into your top level project directory: +``` +gitdir=$(git rev-parse --git-dir); scp -P 29418 codereview.qt-project.org:hooks/commit-msg ${gitdir}/hooks/ +``` + +This hook automatically adds a "Change-Id: …" line to the commit message. Change-Id is used +to identify new Patch Sets for existing +[Changes](https://wiki.qt.io/Gerrit_Introduction#Terminology). + +## Making changes + +Commit your changes in the usual way you do in Git. + +After making changes, you might want to ensure that Qbs can be built and autotests pass: +``` +qbs build -p autotest-runner +``` +See ["Appendix A: Building Qbs"](http://doc.qt.io/qbs/building-qbs.html) for details. + +In case your changes might significantly affect performance (in either way), the +'qbs_benchmarker' tool can be used (Linux only, requires Valgrind to be installed): +``` +qbs_benchmarker -r -o -n -a -p +``` +Use 'qbs_benchmarker --help' for details. + +## Pushing your changes to Gerrit + +After committing your changes locally, push them to Gerrit + +``` +git push gerrit HEAD:refs/for/master +``` + +Gerrit will print a URL that can be used to access the newly created Change. + +## Adding reviewers + +Use the "ADD REVIEWER" button on the Change's web page to ask people to do the +review. To find possible reviewers, you can examine the Git history with +'git log' and/or 'git blame' or ask on the mailing list: qbs@qt-project.org + +## Modifying Commits + +During the review process, it might be necessary to do some changes to the commit. +To include you changes in the last commit, use +``` +git commit --amend +``` +This will edit the last commit instead of creating a new one. Make sure to preserve the +Change-Id footer when amending commits. For details, see +[Updating a Contribution With New Code](https://wiki.qt.io/Gerrit_Introduction#Updating_a_Contribution_With_New_Code) + +## Abandoning changes + +Changes which are inherently flawed or became inapplicable should be abandoned. You can do that +on the Change's web page with the "ABANDON" button. Make sure to remove the abandoned commit +from your working copy by using '[git reset](https://git-scm.com/docs/git-reset)'. diff --git a/LGPL_EXCEPTION.txt b/LGPL_EXCEPTION.txt new file mode 100644 index 00000000..918157a3 --- /dev/null +++ b/LGPL_EXCEPTION.txt @@ -0,0 +1,22 @@ +The Qt Company LGPL Exception version 1.1 + +As an additional permission to the GNU Lesser General Public License version +2.1, the object code form of a "work that uses the Library" may incorporate +material from a header file that is part of the Library. You may distribute +such object code under terms of your choice, provided that: + (i) the header files of the Library have not been modified; and + (ii) the incorporated material is limited to numerical parameters, data + structure layouts, accessors, macros, inline functions and + templates; and + (iii) you comply with the terms of Section 6 of the GNU Lesser General + Public License version 2.1. + +Moreover, you may apply this exception to a modified version of the Library, +provided that such modification does not involve copying material from the +Library into the modified Library's header files unless such material is +limited to (i) numerical parameters; (ii) data structure layouts; +(iii) accessors; and (iv) small macros, templates and inline functions of +five lines or less in length. + +Furthermore, you are not required to apply this additional permission to a +modified version of the Library. diff --git a/LICENSE.GPL3-EXCEPT b/LICENSE.GPL3-EXCEPT new file mode 100644 index 00000000..b1cb1bec --- /dev/null +++ b/LICENSE.GPL3-EXCEPT @@ -0,0 +1,704 @@ +This is the GNU General Public License version 3, annotated with The +Qt Company GPL Exception 1.0: + +------------------------------------------------------------------------- + +The Qt Company GPL Exception 1.0 + +Exception 1: + +As a special exception you may create a larger work which contains the +output of this application and distribute that work under terms of your +choice, so long as the work is not otherwise derived from or based on +this application and so long as the work does not in itself generate +output that contains the output from this application in its original +or modified form. + +Exception 2: + +As a special exception, you have permission to combine this application +with Plugins licensed under the terms of your choice, to produce an +executable, and to copy and distribute the resulting executable under +the terms of your choice. However, the executable must be accompanied +by a prominent notice offering all users of the executable the entire +source code to this application, excluding the source code of the +independent modules, but including any changes you have made to this +application, under the terms of this license. + + +------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LICENSE.LGPLv21 b/LICENSE.LGPLv21 new file mode 100644 index 00000000..602bfc94 --- /dev/null +++ b/LICENSE.LGPLv21 @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/LICENSE.LGPLv3 b/LICENSE.LGPLv3 new file mode 100644 index 00000000..509cc641 --- /dev/null +++ b/LICENSE.LGPLv3 @@ -0,0 +1,173 @@ + GNU LESSER GENERAL PUBLIC LICENSE + + The Qt Toolkit is Copyright (C) 2015 The Qt Company Ltd. + Contact: http://www.qt.io/licensing + + You may use, distribute and copy the Qt GUI Toolkit under the terms of + GNU Lesser General Public License version 3, which is displayed below. + +------------------------------------------------------------------------- + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright © 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies of this +licensedocument, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + +0. Additional Definitions. + + As used herein, “this License” refers to version 3 of the GNU Lesser +General Public License, and the “GNU GPL” refers to version 3 of the +GNU General Public License. + + “The Library” refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An “Application” is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A “Combined Work” is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the “Linked +Version”. + + The “Minimal Corresponding Source” for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The “Corresponding Application Code” for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort + to ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this + license document. + +4. Combined Works. + + You may convey a Combined Work under terms of your choice that, taken +together, effectively do not restrict modification of the portions of +the Library contained in the Combined Work and reverse engineering for +debugging such modifications, if you also do each of the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this + license document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of + this License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with + the Library. A suitable mechanism is one that (a) uses at run + time a copy of the Library already present on the user's + computer system, and (b) will operate properly with a modified + version of the Library that is interface-compatible with the + Linked Version. + + e) Provide Installation Information, but only if you would + otherwise be required to provide such information under section 6 + of the GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the Application + with a modified version of the Linked Version. (If you use option + 4d0, the Installation Information must accompany the Minimal + Corresponding Source and Corresponding Application Code. If you + use option 4d1, you must provide the Installation Information in + the manner specified by section 6 of the GNU GPL for conveying + Corresponding Source.) + +5. Combined Libraries. + + You may place library facilities that are a work based on the Library +side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities, conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of + it is a work based on the Library, and explaining where to find + the accompanying uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +as you received it specifies that a certain numbered version of the +GNU Lesser General Public License “or any later version” applies to +it, you have the option of following the terms and conditions either +of that published version or of any later version published by the +Free Software Foundation. If the Library as you received it does not +specify a version number of the GNU Lesser General Public License, +you may choose any version of the GNU Lesser General Public License +ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the Library. + diff --git a/README.md b/README.md new file mode 100644 index 00000000..c56d8c21 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Qbs + +Qbs is a build automation tool designed to conveniently manage the build +process of software projects across multiple platforms. Qbs can be used for any +software project, regardless of programming language, toolkit, or libraries used. + +## Documentation + +Qbs product documentation is available at [doc.qt.io/qbs](http://doc.qt.io/qbs/index.html) + +The project's homepage is [wiki.qt.io/qbs](http://wiki.qt.io/qbs) + +## Supported platforms + +Qbs binaries are available for Windows, macOS, Linux, and FreeBSD. + +For more information about how to install Qbs on your platform, see the +[Installing](https://doc.qt.io/qbs/installing.html) page in the documentation. + +Qbs allows to build applications for different platforms, for the list of +supported platforms and the details about each platform, see the +[Target Platforms](https://doc.qt.io/qbs/platforms.html) page. + +## Building Qbs + +For information about building Qbs from sources, see +["Appendix A: Building Qbs"](http://doc.qt.io/qbs/building-qbs.html). + +## Reporting Bugs + +Please report any bugs in our [bug tracker](https://bugreports.qt.io/browse/QBS). + +## Contributing + +See [Contributing to Qbs](CONTRIBUTING.md) + diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..092afa15 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.17.0 diff --git a/bin/ibmsvc.xml b/bin/ibmsvc.xml new file mode 100644 index 00000000..cea5990c --- /dev/null +++ b/bin/ibmsvc.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/bin/ibqbs.bat b/bin/ibqbs.bat new file mode 100644 index 00000000..0e1cb2bd --- /dev/null +++ b/bin/ibqbs.bat @@ -0,0 +1 @@ +@xgConsole /profile=%~dp0\ibmsvc.xml /command="qbs -j 20 %*" diff --git a/changelogs/changes-1.10.0.md b/changelogs/changes-1.10.0.md new file mode 100644 index 00000000..ce4bf500 --- /dev/null +++ b/changelogs/changes-1.10.0.md @@ -0,0 +1,43 @@ +# General +* Added the `vcs` module to provide VCS repository information. + Git and Subversion are supported initially. +* Added initial support for the Universal Windows Platform. +* Improved a lot of error messages. + +# Language +* Profiles can now be defined within a project using the `Profile` item. +* Groups without a prefix now inherit the one of the parent group. +* Both the `Module` and the `FileTagger` item now have a `priority` property, allowing them to + override conflicting instances. +* It is now possible to add file tags to generated artifacts by setting the new `fileTags` property + in a group that has a `fileTagsFilter`. +* Added new open mode `TextFile.Append`. +* Added the `filePath` function to the `TextFile` class. +* `Process` and `TextFile` objects in rules, commands and configure scripts are now + closed automatically after script execution. + +# C/C++ Support +* Added the `cpufeatures` module for abstracting compiler flags related to CPU features such as SSE. +* Added property `cpp.discardUnusedData` abstracting linker options that strip unneeded symbols + or sections. +* Added property `cpp.variantSuffix` for making binary names unique when multiplexing products. +* Added property `cpp.compilerDefinesByLanguage` providing the compiler's pre-defined macros. + +# Android +* The deprecated `apkbuilder` tool is no longer used. + +# Qt +* Added support for the Qt Quick compiler. +* Added support for `qmlcachegen`. + +# Command-line interface +* Removed some non-applicable options from a number of tools. +* The `run` command can now deploy and run Android apps on devices, and deploy and run iOS and + tvOS apps on the simulator. +* Added new command `list-products`. + +# Documentation +* Added porting guide for qmake projects. +* Added in-depth descriptions of all command-line tools. +* Added "How-to" for creating modules for third-party libraries. +* Added a man page. diff --git a/changelogs/changes-1.10.1.md b/changelogs/changes-1.10.1.md new file mode 100644 index 00000000..d0f68a89 --- /dev/null +++ b/changelogs/changes-1.10.1.md @@ -0,0 +1,11 @@ +# Important bugfixes +* Fix assertion on project loading (QBS-1275). +* Fix crash when the "original" value is misused (QBS-1255). +* Fix qtquickcompiler support for qml files in subdirectories (QBS-1261). +* Fix constant rebuilding after moving an external header file (QBS-1285). +* Fix GCC support for "bare metal" systems (QBS-1263, QBS-1265). +* Fix using ids in Depends items (QBS-1264). +* Fix access to module instances in dependency parameters (QBS-1253). +* Fix race condition when creating Inno Setup, NSIS, or WiX installers. +* Fix release builds for Android with NDK r12 and above (QBS-1256). +* Fix parametrized dependencies in Export and Module items (QBS-1287). diff --git a/changelogs/changes-1.11.0.md b/changelogs/changes-1.11.0.md new file mode 100644 index 00000000..028db51d --- /dev/null +++ b/changelogs/changes-1.11.0.md @@ -0,0 +1,57 @@ +# General +* Added `qbs.targetPlatform` and `qbs.hostPlatform` properties which are scalar versions of + `qbs.targetOS` and `qbs.hostOS`. `qbs.targetPlatform` is a "write-only" property that can be used + to set the OS/platform that is being targeted, while `qbs.targetOS` and `qbs.hostOS` should + continue to be used to *read* the OS/platform that is being targeted. + `qbs.targetOS` is also now read-only. +* The "run" functionality as used by the command-line command of the same name now considers + an executable's library dependencies, that is, it adds the paths they are located in to the + respective environment variable (e.g. PATH on Windows). + +# Language +* Modules can now declare target artifacts using the new `filesAreTargets` property of the + `Group` item. +* The Module.setupRunEnvironment script now has a new parameter `config`. Users can set it via the + `--setup-run-env-config` option of the `run` command. The only value currently supported + is `ignore-lib-dependencies`, which turns off the abovementioned injection of library + dependencies' paths into the run environment. +* Module.setupBuildEnvironment and Module.setupRunEnvironment now have access to the `product` + and `project` variables. With regards to accessing module properties, these script now behave + like rules, rather than normal properties. +* Added the `BinaryFile` service for reading and writing binary data files. +* The `SubProject` item now has a condition property. + +# C/C++ Support +* Added property `cpp.rpathOrigin` which evaluates to `@loader_path` on Darwin and `$ORIGIN` + on other Unix-like platforms. +* Added the `qbs.toolchainType` property, which is a scalar version of the `qbs.toolchain` + property and is used to set the current toolchain. +* Added `cpp.driverLinkerFlags` for flags to be passed to the compiler driver only when linking. +* We now properly support `"c++17"` as a possible value of `cpp.cxxLanguageVersion`. +* The auto-detection mechanism for GCC-like compilers now considers typical mingw prefixes. + +# Qt Support +* Added the Qt.scxml.generateStateMethods property to back the --statemethods option. + +# Command-line interface +* Configuration names are now passed as "config:". +* Options do not have to precede property assignments anymore. +* Referencing a non-existing product in a property override now results in an error. + +# Documentation +* Major overhaul of the module and item reference for improved readability. +* Added a how-to on the topic of pre-compiled headers. +* Added documentation for the built-in XML support. +* Added documentation for qbs.Utilities. +* Added documentation on how to target specific platforms. + +# Important bug fixes +* Fixed some inconsistencies related to item ids (QBS-1016, QBS-1262). +* Fixed slow project resolving on macOS (QBS-1277). +* Fixed problems with qtquickcompiler support in Qt 5.11 (QBS-1299). +* Fixed race conditions in multi-configuration builds (QBS-1308). + +# Other +* The `InnoSetup`, `nsis`, and `wix` modules' rules now have a dependency on installable artifacts + of dependencies. +* Introduced the `ico` module for creating .ico and .cur files. diff --git a/changelogs/changes-1.11.1.md b/changelogs/changes-1.11.1.md new file mode 100644 index 00000000..e2851850 --- /dev/null +++ b/changelogs/changes-1.11.1.md @@ -0,0 +1,5 @@ +# Important bugfixes +* Speed up run environment setup (QTCREATORBUG-20175). +* Fix qbs command line generated by the Visual Studio project generator (QBS-1303). +* Install all required header files when building Qbs with qmake. +* Fix undefined behavior in the qbscore library where a reference to a temporary object was stored. diff --git a/changelogs/changes-1.12.0.md b/changelogs/changes-1.12.0.md new file mode 100644 index 00000000..6c88d35b --- /dev/null +++ b/changelogs/changes-1.12.0.md @@ -0,0 +1,41 @@ +# General +* Added new module `Exporter.qbs` for creating qbs modules from products. +* Added new module `Exporter.pkgconfig` for creating pkg-config metadata files. +* Introduced the concept of system-level qbs settings. +* Added a Makefile generator. +* All command descriptions now contain the product name. + +# Language +* The `explicitlyDependsOn` property of the `Rule` item no longer considers + target artifacts of product dependencies. The new property `explicitlyDependsOnFromDependencies` + can be used for that purpose. +* The `excludedAuxiliaryInputs` property of the `Rule` item has + been renamed to `excludedInputs`. The old name is now deprecated. +* Added a new property type `varList`. +* Added `FileInfo.suffix` and `FileInfo.completeSuffix`. +* The deprecated JS extensions `XmlDomDocument` and `XmlDomElement` + have been removed. Use `Xml.DomDocument` and `Xml.DomDocument` instead. + +# C/C++ Support +* For MSVC static libraries, compiler-generated PDB files are + now tagged as `debuginfo_cl` to make them installable. +* The `cxxLanguageVersion` property can now be set to different values in different modules, + and the highest value will be chosen. + +# Qt Support +* Amalgamation builds work properly now in the presence of "mocable" files. +* Fixed some redundancy on the linker command line. + +# Other modules +* Added support for `%option outfile` and `%output` to the `lex_yacc` module. +* The `vcs` module now creates the header file even if no repository is present. + +# Autotest support +* Added an `auxiliaryInputs` property to the `AutotestRunner` item for specifying run-time + dependencies of test executables. +* The `AutotestRunner` item now has a `workingDirectory` property. + By default, the respective test executable's location is used. + +# Important bug fixes +* Disabled products no longer cause their exported dependencies to get pulled into + the importing product (QBS-1250). diff --git a/changelogs/changes-1.12.1.md b/changelogs/changes-1.12.1.md new file mode 100644 index 00000000..d9f10594 --- /dev/null +++ b/changelogs/changes-1.12.1.md @@ -0,0 +1,8 @@ +# Important bugfixes +* Lifted the restriction that the -march option cannot appear in cpp.*Flags (QBS-1018). +* All required header files get installed now (QBS-1370). +* Fixed rpaths not ending up on the command line under certain circumstances (QBS-1372). +* Fixed possible crash when scanning qrc files (QBS-1375). +* Fixed spurious re-building of .pc and .qbs module files. +* Fixed possible crash on storing a build graph after re-resolving. +* Fixed possible assertion on input artifacts with alwaysUpdated == false. diff --git a/changelogs/changes-1.12.2.md b/changelogs/changes-1.12.2.md new file mode 100644 index 00000000..94ecc68e --- /dev/null +++ b/changelogs/changes-1.12.2.md @@ -0,0 +1,10 @@ +# Important bugfixes +* The Visual Studio 2017 Build Tools are properly supported now. +* Android NDK r18 is properly supported now. +* Removed invalid assertion that prevented deriving from the Properties item. +* Fixed build error on some BSD hosts (QBS-1395). +* setup-qt fixes: + * The QtWebkit module is now properly detected (QBS-1399). + * The case of the qtmain library being called "qt5main" is properly handled now (QBS-767). + * Building against a Qt that was built with sanitizing support + works out of the box now (QBS-1387). diff --git a/changelogs/changes-1.13.0.md b/changelogs/changes-1.13.0.md new file mode 100644 index 00000000..37e4da00 --- /dev/null +++ b/changelogs/changes-1.13.0.md @@ -0,0 +1,37 @@ +# General +* Added a lot more documentation. +* The `--show-progress` command line option is now supported on Windows. + +# Language +* Introduced module providers. +* The `Depends` item now falls back to `pkg-config` to locate dependencies whose names do not + correspond to a qbs module. +* Added the concept of job pools for limiting concurrent execution of commands by type. +* Added support for rules without output artifacts. +* Added `atEnd` function to the `Process` service. +* Added `canonicalPath` function to the `FileInfo` service. +* Removed the need to add "import qbs" at the head of project files. +* The `Application`, `DynamicLibrary` and `StaticLibrary` items now have properties for more + convenient installation of target binaries. + +# C/C++ Support +* Added recursive dependency scanning of GNU ld linkerscripts. +* Added new `cpp` property `linkerVariant` to force use of `gold`, `bfd` or `lld`. + +# Qt Support +* It is no longer required to call `setup-qt` before building Qt projects. +* Introduced the property `Qt.core.enableBigResources` for the creation of "big" Qt resources. +* Static builds now pull in the default set of plugins as specified by Qt, and the user can + specify the set of plugins by type. +* Files can be explicitly tagged as mocable now. + +# Other modules +* Added `protobuf` support for C++ and Objective-C. +* Introduced the `texttemplate` module, a facility similar to qmake's `SUBSTITUTES` feature. + +# Android Support +* The `AndroidApk` item was deprecated, a normal `Application` item can be used instead. +* Building Qt apps is properly supported now, by making use of the `androiddeployqt` tool. + +# Autotest support +* Introduced the `autotest` module. diff --git a/changelogs/changes-1.13.1.md b/changelogs/changes-1.13.1.md new file mode 100644 index 00000000..b2a315cd --- /dev/null +++ b/changelogs/changes-1.13.1.md @@ -0,0 +1,9 @@ +# Important bugfixes +* Qt support: Plugins are no longer linked into static libraries when building against + a static Qt (QBS-1441). +* Qt support: Fixed excessively long linker command lines (QBS-1441). +* Qt support: Host libraries are now looked up at the right location (QBS-1445). +* Qt support: Fixed failure to find Qt modules in Qt Creator when re-parsing a project that + hasn't been built yet. +* macOS: Properties in bundle.infoPlist are no longer overridden (QBS-1447). +* iOS: Fixed generation of default Info.plist (QBS-1447). diff --git a/changelogs/changes-1.14.0.md b/changelogs/changes-1.14.0.md new file mode 100644 index 00000000..860578a1 --- /dev/null +++ b/changelogs/changes-1.14.0.md @@ -0,0 +1,35 @@ +# Language +* The `PathProbe` item was extended to support looking for multiple files and filtering candidate + files. + +# C/C++ Support +* Added support for Visual Studio 2019. +* Added support for clang-cl. +* Various improvements for bare-metal toolchains, including new example projects and support for + the SDCC toolchain. + +# Qt Support +* Added the `Qt.android_support.extraLibs` property. + +# Other modules +* The `pkgconfig` module now has a `sysroot` property. +* Added gRPC support to the `protobuf.cpp` module. + +# Android Support +* Removed support for NDK < r19. +* Added new `Android.sdk` properties `versionCode` and `versionName`. + +# Infrastructure +* Added configuration files for Travis CI. +* Various fixes and improvements in the Debian Docker image; updated to to Qt 5.11.3. + +# Contributors +* BogDan Vatra +* Christian Kandeler +* Christian Stenger +* Davide Pesavento +* Denis Shienkov +* hjk +* Ivan Komissarov +* Joerg Bornemann +* Richard Weickelt diff --git a/changelogs/changes-1.14.1.md b/changelogs/changes-1.14.1.md new file mode 100644 index 00000000..695ee3c7 --- /dev/null +++ b/changelogs/changes-1.14.1.md @@ -0,0 +1,4 @@ +# Important bugfixes +* Qt support: Fix static builds on Windows (QBS-1465). +* Qt support: Fix static builds with Qt >= 5.13.1. +* Darwin: Adapt to Xcode 11. diff --git a/changelogs/changes-1.15.0.md b/changelogs/changes-1.15.0.md new file mode 100644 index 00000000..cc95011a --- /dev/null +++ b/changelogs/changes-1.15.0.md @@ -0,0 +1,44 @@ +# General +* Added a session command which offers a JSON-based API for interaction with + other tools via stdin/ stdout. This allows for proper Qbs support in IDEs that + do not use Qt or even C++. + +# Language +* Probes are now evaluated before Profile items and can be used to create + profiles on project level. +* AutotestRunner got a separate job pool. +* Added a timeout property to Command, JavaScriptCommand and AutotestRunner. + This allows to identify and kill stuck commands. + +# C/C++ Support +* Ensure proper support of Xcode 11. +* Linker map files can be generated with all toolchains. +* Bare metal toolchains can now generate listing files. +* Improve the command line output filtering of bare metal toolchains. +* Added support for clang in mingw mode on Windows. +* Added msp430 support to GCC and IAR. +* Added STM8 support to IAR and SDCC. +* Added IDE project generators for IAR Embedded Workbench for ARM, AVR, 8051, + MSP430, and STM8 architectures. +* Added IDE project generators for KEIL uVision v4 for ARM and 8051 + architectures. +* Added more bare metal project examples for various target platforms. +* The IAR, KEIL and SDCC toolchains are now found automatically in various. + locations by the setup-toolchains command and by probes if no installPath is + set in the profile. + +# Infrastructure +* Automated build and testing on Ubuntu, macOS and Windows. +* Added Ubuntu bionic Docker image which replaces Debian stretch. +* Updated Qt in the Ubuntu and Windows Docker images to 5.12. +* When building Qbs, Qt libraries can now be bundled on Linux, macOS and + Windows. + +# Contributors +* Alberto Mardegan +* Christian Kandeler +* Denis Shienkov +* Ivan Komissarov +* Jochen Ulrich +* Joerg Bornemann +* Richard Weickelt diff --git a/changelogs/changes-1.16.0.md b/changelogs/changes-1.16.0.md new file mode 100644 index 00000000..23628b90 --- /dev/null +++ b/changelogs/changes-1.16.0.md @@ -0,0 +1,126 @@ +# General + +* A new freedesktop module helps UNIX application developers to follow the + freedesktop.org guidelines. +* The Android module now allows resourcesDir, sourcesDir and assetsDir to be + specified as relative paths. +* A new ConanfileProbe allows better and more flexible integration of Qbs and + the Conan package manager. +* A new hostArchitecture property has been added to the qbs module. + + +# Language + +* List properties in modules are now merged according to inter-module + dependencies. This is important when flags like cpp.staticLibraries are + contributed by multiple modules with dependencies between each other. + (QBS-1517). +* Dependency matching of multiplexed products is now less strict and does not + require all multiplex properties to match. For instance, if product A is + multiplexed over qbs.architecture and qbs.buildVariant while product B is only + multiplexed over one of these axes, then Qbs no longer fails (QBS-1515). + + +# C/C++ Support + +* The Renesas RL78 architecture is now supported in GCC and IAR and the + toolchains are auto-detected by qbs-setup-toolchains. +* The Renesas RX as well as the RH850, V850, 78K are now supported in IAR and + the toolchains are auto-detected by qbs-setup-toolchains. +* The MPLAB X32 GCC-based toolchain is now auto-detected on Windows. +* Multiple occurrences of static libraries on the linker command line are now + pruned and the last instance always wins when using GCC or LLVM-based + toolchains. This avoids problems with excessively long linker command lines + (QBS-1273). +* Clang-cl and MSVC toolchains use the compiler frontend instead of the linker + when linking. The old behavior can be restored by setting cpp.linkerMode to + "manual". This allows to use sanitizers with clang-cl by passing + "-fsanitise=xxx" via cpp.driverFlags (QBS-1522). +* The clang-cl toolchain now uses "link.exe" as the default linker. + "lld-link.exe" can be explicitly selected by setting cpp.linkerVariant to + "lld" (QBS-1522). +* The MSVC, clang-cl and MinGW toolchains are now automatically detected if the + profile does not set an explicit installation location, for instance because + no profile was given. +* Installation of separate debug information can now be enabled and configured + by simply setting the installDebugInformation and debugInformationInstallDir + properties in the Application and Library convenience items. This works for + toolchains based upon GCC, MSVC or clang-cl. +* Xcode version 11.4 is now supported on macOS. + + +# Qt Support + +* Qbs now supports Qt 5.14 for Android which comes as a multi-architecture + package. The qbs-setup-android tool has been updated accordingly (QBS-1497). +* JSON metatype files generated by moc (Qt >= 5.15) are supported by setting + Qt.core.generateMetaTypesFile and Qt.core.metaTypesInstallDir (QBS-1531). +* Pure debug builds of Qt (>= 5.14) with MinGW are now properly supported. They + don't have the 'd' suffix (QTBUG-80792). +* The QML type declaration mechanism introduced in Qt 5.15 is now supported by + the Qt.qml module (QBS-1531). +* Generated qmltypes files are now named according to the product's targetName + property (QTBUG-82710). + + +# Documentation + +* The how-to chapter has been extended with sections about debugging Qbs files + and about building separate debug information in C++ projects. +* The item and module reference documentation has been improved for the + cpp.libraryPaths, cpp.dynamicLibraries (QBS-1516), qbs.toolchainType and + qbs.toolchain properties as well as the Export item and the Library + convenience item. +* Documentation for various path probes has been added (QBS-1187). +* The README was extended and a CONTRIBUTING file has been added which provides + useful information for potential contributors. This is important for people + looking at our github mirror. + + +# Infrastructure + +* The Debian Docker image has been removed. +* We are now using ccache and clcache in our CI pipelines to shrink the build + time. +* Clang-tidy is now used to identify potential problems in the code base and a + lot of action was taken upon a lot of findings. +* A Docker image for testing Qbs with Android and Qt has been added. + + +# Important Bug Fixes + +* Fix nullpointer access and heap-use-after-free error (QBS-1485). +* Select the right instance when Depends.profiles is used on a dependency with + an aggregator product (QBS-1513). +* Fix crash when specifying a non-existing profile in Depends.profiles + (QBS-1514). +* Try harder to detect GCC toolchains in qbs-setup-toolchains (QBS-1524). +* Code signing for Core Foundation Bundles on macOS has been fixed. +* Automatic artifact scanning now prefers artifacts from product dependencies if + multiple candidates are found. This improves dependency tracking in complex + projects (QBS-1532). +* The grpcIncludePath property in the probufcpp module has been fixed + (QBS-1542). +* Qbs does no longer crash when accessing a property of a non-existent module in + "IDE mode". + + +# Contributors + +* Alberto Mardegan +* Björn Schäpers +* BogDan Vatra +* Christian Kandeler +* Christian Stenger +* Denis Shienkov +* Ivan Komissarov +* Jochen Ulrich +* Joerg Bornemann +* Leon Buckel +* Marius Sincovici +* Maximilian Goldstein +* Mitch Curtis +* Oliver Wolff +* Orgad Shaneh +* Raphaël Cotty +* Richard Weickelt diff --git a/changelogs/changes-1.17.0.md b/changelogs/changes-1.17.0.md new file mode 100644 index 00000000..f34eebe7 --- /dev/null +++ b/changelogs/changes-1.17.0.md @@ -0,0 +1,98 @@ +# General + +* The lookup order in PathProbe changed to [environmentPaths, + searchPaths, platformEnvironmentPaths, platformSearchPaths]. +* The pathPrefix and platformPaths properties have been removed from the + PathProbe item. They were deprecated since Qbs 1.13. +* The protocBinary property in the protobuf module has been renamed to + compilerPath. +* A new module capnp for Cap'n Proto in C++ applications has been added. + Cap'n Proto is a serialization protocol similar to protobuf. +* The qbs-setup-android tool got a --system flag to install profiles + system-wide similar to qbs-setup-qt and qbs-setup-toolchains. + + +# Language + +* The product and project variables are now available on the + right-hand-side of moduleProvider expressions and the default scope is + product (QBS-1587). + + +# C/C++ Support + +* Lots of improvements have been made on toolchain support for + bare-metal devices in general. Bare-metal targets can be selected by + setting qbs.targetPlatform to 'none'. +* KEIL: The ARMCLANG, C166 and C251 toolchains are now supported. +* IAR: National's CR16, Microchip's AVR32, NXP's M68K, Renesas' + M8/16C/M32C/R32C/SuperH targets and RISC-V targets are now supported. +* GCC: National's CR16, NXP M68K, Renesas M32C/M32R/SuperH/V850 as well + as RISC-V and Xtensa targets are now supported. +* MSVC: Module definition files can now be used to provide the linker + with information about exports and attributes (QBS-571). +* MSVC: "/external:I" is now used to set system include paths (QBS-1573). +* MSVC: cpp.generateCompilerListingFiles is now supported to generate + assembler listings. +* Xcode: macOS framework paths on the command line are now automatically + deduplicated (QBS-1552). +* Xcode: Support for Xcode 12.0 has been added (QBS-1582). + + +# Qt Support + +* The Qt for Android modules have been cleaned up. Support for ARMv5, MIPS and + MIPS64 targets has been removed (QBS-1496). +* Initial support for Qt6 has been added. + + +# Android Support + +* A packageType property has been added to the Android.sdk module which + allows to create Android App bundles (aab) instead of apk packages + only. +* A aaptName property has been added to the Android.sdk module which + allows to use aapt2 (QBS-1562) since aapt has been deprecated. + + +# Documentation + +* New bare-metal examples have been added and existing examples have + been ported to more toolchains. +* A new how-to about cpp.rPaths has been added (QBS-1204). +* Various minor improvements have been made. + + +# Important Bug Fixes + +* Building Qt for Android applications as static libraries has been + fixed (QBS-1545). +* Trailing slashes are no longer removed from Visual Studio environment + variables (QBS-1551). +* The MSVC cpp module did not use the cpp.distributionIncludePaths + property (QBS-1572). +* The visual studio generator has been fixed to work with Visual Studio + 16.6 (QBS-1569). +* Fixed extraction of build information from CONFIG and QT_CONFIG + variables in Qt installations (QBS-1387). +* The version number is no longer appended to .so files on Android + (QBS-1578). +* Compiler defines are now correctly passed to moc when processing + header files (QBS-1592). + + +# Contributors + +* Alberto Mardegan +* Christian Gagneraud +* Christian Kandeler +* Christian Stenger +* Denis Shienkov +* Ivan Komissarov +* Jake Petroules +* Jochen Ulrich +* Mitch Curtis +* Oliver Wolff +* Raphaël Cotty +* Richard Weickelt +* Sergey Zhuravlev diff --git a/changelogs/changes-1.6.0 b/changelogs/changes-1.6.0 new file mode 100644 index 00000000..616be866 --- /dev/null +++ b/changelogs/changes-1.6.0 @@ -0,0 +1,16 @@ +* Added lex_yacc module. +* Introduced property cpp.systemRunPaths. +* Introduced the ability to check a module's version in a Depends item. +* Introduced cpp.driverFlags, which allows specifying flags to be + passed to the compiler driver (in any mode), but never the system linker. +* Introduced cpp.linkerMode property to allow selection of the + correct linker (C driver, C++ driver, or system linker) based on the + objects being linked. +* Added automatic escaping of arguments passed to the + cpp.linkerFlags and cpp.platformLinkerFlags properties using the -Wl + or -Xlinker syntaxes. To revert to the old behavior, + Project.minimumQbsVersion can be set to a version lower than 1.6. +* Each build configuration now requires a unique name, which + is specified on the command line in the same place that qbs.buildVariant + used to be specified. This allows building for multiple configurations + with the same variant. diff --git a/changelogs/changes-1.6.1 b/changelogs/changes-1.6.1 new file mode 100644 index 00000000..20899ade --- /dev/null +++ b/changelogs/changes-1.6.1 @@ -0,0 +1,6 @@ +Features: + * Added cpp.linkerWrapper property. +Important bug fixes: + * Fixed a number of bugs evaluating module properties (QBS-845, QBS-1005). + * Fixed x86_64 > x86 cross compiling (QBS-1028). + * Fixed dynamic rules with generated inputs (QBS-1029). diff --git a/changelogs/changes-1.7.0 b/changelogs/changes-1.7.0 new file mode 100644 index 00000000..341ebcd3 --- /dev/null +++ b/changelogs/changes-1.7.0 @@ -0,0 +1,23 @@ +Features: + * Added a generator for Visual Studio projects. + * The Group item is now nestable. + * Stricter type checking for properties. + * Added support for generating qrc files. + * Added full support for the QtScxml module. + * Introduced cpp.soVersion. + * Added support for building Inno Setup packages. + * Tentative support for Visual Studio 2017. + * We now assume UTF-8 encoding for project files. + * In Scanner items, input.fileName now contains + a filename rather than the full path. + * Warnings encountered during project resolving are now stored + and re-displayed when the project is loaded. + * Documentation was improved in several places, most + notably for the Rule item. + * Support for the deprecated Transformer item was removed. +Important bug fixes: + * Long paths on Windows are handled (QBS-1068). + * Cyclic module dependencies are detected (QBS-1044). + * The libqbscore soname now includes the minor version, + so that it will not stay the same across ABI changes + (QBS-1002). diff --git a/changelogs/changes-1.7.1 b/changelogs/changes-1.7.1 new file mode 100644 index 00000000..07262550 --- /dev/null +++ b/changelogs/changes-1.7.1 @@ -0,0 +1,5 @@ +Important bug fixes: + * Fixed race condition in qmake build (QBS-1091) + * Qt Creator no longer leaves empty build directories + behind after cancelled project loading (QTCREATORBUG-17543) + * Fixed an exception crossing the API boundary diff --git a/changelogs/changes-1.7.2 b/changelogs/changes-1.7.2 new file mode 100644 index 00000000..9f4385d7 --- /dev/null +++ b/changelogs/changes-1.7.2 @@ -0,0 +1,5 @@ +Important bug fixes: + * macOS: Fixed App Extension builds on older versions of Xcode/macOS + * Windows: Fixed handling of files on network shares + * Fixed syntax error in Qt module that occurred with static Qt builds + * Several fixes for the Visual Studio generator (QBS-1077, QBS-1100) diff --git a/changelogs/changes-1.8.0 b/changelogs/changes-1.8.0 new file mode 100644 index 00000000..5c4cec23 --- /dev/null +++ b/changelogs/changes-1.8.0 @@ -0,0 +1,33 @@ +Features: + * General: + * It is no longer strictly required to provide a profile. + * Sub-second timestamp resolutions are now supported on Unix + systems. + * Added a convenient replacement for + product.moduleProperty("module", "property"), namely + product.module.property. + * The loadFile and loadExtension functions are deprecated in + favor of the new require function, which accepts arguments of either + form accepted by the deprecated functions. + * Added new tool qbs-create-project to set up a new qbs + project from an existing source tree. + * FileTagger items can now have conditions. + * Probe items can now appear directly under a Project item. + * Cpp module: + * Added support for QNX and the QCC toolchain + * Added the cpp.useRPathLink property to control whether + to use the -rpath-link linker option. + * Provided the means to easily combine source files for the C + language family in order to support "amalgamation builds". + * Introduced cpp.treatSystemHeadersAsDependencies. + * Qt modules: + * Introduced property Qt.core.combineMocOutput. + * Introduced Qt.core.enableKeywords for simple disabling of + the "signals", "slots" and "emit" symbols. +Important bug fixes: + * Improved scalability of parallel builds on Linux by starting + Process commands via a dedicated launcher process. +Behavior changes: + * The base directory for source files changed from the product + source directory to the parent directory of the file where the files are + listed. diff --git a/changelogs/changes-1.8.1 b/changelogs/changes-1.8.1 new file mode 100644 index 00000000..aeebe92b --- /dev/null +++ b/changelogs/changes-1.8.1 @@ -0,0 +1,12 @@ +Important bug fixes: + * Qbs-specific build errors are now correctly linked + in Qt Creator's issues pane (QBS-1151). + * Fixed automatic base profile assignment for + MSVC Qt installations (QBS-1141) + * Various QNX fixes (QBS-1136, QBS-1137, QBS-1138, QBS-1139, QBS-1143). + +Behavior changes: + * Users now get early error messages if they forget + the "modules", "products" or "projects" prefix + in command line property overrides such as + "modules.cpp.enableExceptions:false". diff --git a/changelogs/changes-1.9.0.md b/changelogs/changes-1.9.0.md new file mode 100644 index 00000000..2e3cbfa0 --- /dev/null +++ b/changelogs/changes-1.9.0.md @@ -0,0 +1,58 @@ +# General +* Setting module property values from the command line can now + be done per product. +* Introduced new properties `qbs.architectures` and `qbs.buildVariants` + to allow product multiplexing by `qbs.architecture` and `qbs.buildVariant`, + respectively. +* When rebuilding a project, the environment, project file and property + values are taken from the existing build graph. + +# Language +* `Depends` items can now be parameterized to set special module parameters + for one particular product dependency. The new item type `Parameter` + is used to declare such parameters in a module. The new item type + `Parameters` is used to allow products to set default values for + such parameters in their `Export` item. +* The functions `loadExtension` and `loadFile` have been deprecated and + will be removed in a future version. Use the `require` function instead. + +# Custom Rules and Commands +* Artifacts corresponding to the `explicitlyDependsOn` property + are now available under this name in rules and commands. +* A rule's `auxiliaryInputs` and `explicitlyDependsOn` tags + are now also matched against rules of dependencies, if these rules are + creating target artifacts. +* Rules now have a property `requiresInputs`. If it is `false`, the rule will + be run even if no artifacts are present that match its input tags. +* Added a new property `relevantEnvironmentVariables` to the `Command` class. + Use it if the command runs an external tool whose behavior can be + influenced by special environment variables. + +# C/C++ Support +* Added the `cpp.link` parameter to enable library dependencies to be + excluded from linking. +* When pulling in static library products, the new `Depends` parameter + `cpp.linkWholeArchive` can now be specified to force all the library's + objects into the target binary. +* When pulling in library products, the new `Depends` parameter + `cpp.symbolLinkMode` can now be specified to control how the library + is linked into the target binary on Apple platforms: specifically, + whether the library is linked as weak, lazy, reexported, and/or + upward (see the `ld64` man page for more information). +* The property `cpp.useCxxPrecompiledHeader`, as well as the variants for the + other languages, now defaults to true. +* The property `cpp.cxxLanguageVersion` now gets mapped to MSVC's `/std` option, + if applicable. + +# Apple +* Added support for building macOS disk images. + +# Android +* Product multiplexing is no longer done via profiles, but via architecture, + employing the new `qbs.architectures` property (see above). As a result, + the `setup-android` command now sets up only one profile, rather than + one for each architecture. +* Added support for NDK Unified Headers. + +# Documentation +* Added a "How-to" section. diff --git a/changelogs/changes-1.9.1.md b/changelogs/changes-1.9.1.md new file mode 100644 index 00000000..59fc6908 --- /dev/null +++ b/changelogs/changes-1.9.1.md @@ -0,0 +1,4 @@ +# Important bugfixes +* Lower the response file threshold on Windows to fix build failures with mingw (QBS-1201). +* Fix explicitly specified build variant being ignored for Darwin targets (QBS-1202). +* Fix building for the AVR architecture (QBS-1203). diff --git a/dist/.gitignore b/dist/.gitignore new file mode 100644 index 00000000..72e8ffc0 --- /dev/null +++ b/dist/.gitignore @@ -0,0 +1 @@ +* diff --git a/doc/appendix/json-api.qdoc b/doc/appendix/json-api.qdoc new file mode 100644 index 00000000..207798dd --- /dev/null +++ b/doc/appendix/json-api.qdoc @@ -0,0 +1,812 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 + \previouspage porting-to-qbs.html + \nextpage attributions.html + \page json-api.html + + \title Appendix C: The JSON API + + This API is the recommended way to provide \QBS support to an IDE. + It is accessible via the \l{session} command. + + \section1 Packet Format + + All information is exchanged via \e packets, which have the following + structure: + \code + packet = "qbsmsg:" [] + \endcode + First comes a fixed string indentifying the start of a packet, followed + by the size of the actual data in bytes. After that, further meta data + might follow. There is none currently, but future extensions might add + some. A line feed character marks the end of the meta data section + and is followed immediately by the payload, which is a single JSON object + encoded in Base64 format. We call this object a \e message. + + \section1 Messages + + The message data is UTF8-encoded. + + Most messages are either \e requests or \e replies. Requests are messages + sent to \QBS via the session's standard input channel. Replies are messages + sent by \QBS via the session's standard output channel. A reply always + corresponds to one specific request. Every request (with the exception + of the \l{quit-message}{quit request}) expects exactly one reply. A reply implies + that the requested operation has finished. At the very least, it carries + information about whether the operation succeeded, and often contains + additional data specific to the respective request. + + Every message object has a \c type property, which is a string that uniquely + identifies the message type. + + All requests block the session for other requests, including those of the + same type. For instance, if client code wishes to restart building the + project with different parameters, it first has to send a + \l{cancel-message}{cancel} request, wait for the current build job's reply, + and only then can it request another build. The only other message beside + \l{cancel-message}{cancel} that can legally be sent while a request + is currently being handled is the \l{quit-message}{quit} message. + + A reply object may carry an \c error property, indicating that the respective + operation has failed. If this property is not present, the request was successful. + The format of the \c error property is described \l{ErrorInfo}{here}. + + In the remainder of this page, we describe the structure of all messages + that can be sent to and received from \QBS, respectively. The property + tables may have a column titled \e Mandatory, whose values indicate whether + the respective message property is always present. If this column is missing, + all properties of the respective message are mandatory, unless otherwise + noted. + + \section1 The \c hello Message + + This message is sent by \QBS exactly once, right after the session was started. + It is the only message from \QBS that is not a response to a request. + The value of the \c type property is \c "hello", the other properties are + as follows: + \table + \header \li Property \li Type + \row \li api-level \li int + \row \li api-compat-level \li int + \endtable + + The value of \c api-level is increased whenever the API is extended, for instance + by adding new messages or properties. + + The value of \c api-compat-level is increased whenever incompatible changes + are being done to this API. A tool written for API level \c n should refuse + to work with a \QBS version with an API compatibility level greater than \c n, + because it cannot guarantee proper behavior. This value will not change unless + it is absolutely necessary. + + The value of \c api-compat-level is always less than or equal to the + value of \c api-level. + + \section1 Resolving a Project + + To instruct \QBS to load a project from disk, a request of type + \c resolve-project is sent. The other properties are: + \table + \header \li Property \li Type \li Mandatory + \row \li build-root \li \l FilePath \li yes + \row \li configuration-name \li string \li no + \row \li data-mode \li \l DataMode \li no + \row \li dry-run \li bool \li no + \row \li environment \li \l Environment \li no + \row \li error-handling-mode \li string \li no + \row \li fallback-provider-enabled \li bool \li no + \row \li force-probe-execution \li bool \li no + \row \li log-time \li bool \li no + \row \li log-level \li \l LogLevel \li no + \row \li module-properties \li list of strings \li no + \row \li overridden-properties \li object \li no + \row \li project-file-path \li FilePath \li if resolving from scratch + \row \li restore-behavior \li string \li no + \row \li settings-directory \li string \li no + \row \li top-level-profile \li string \li no + \row \li wait-lock-build-graph \li bool \li no + \endtable + + The \c environment property defines the environment to be used for resolving + the project, as well as for all subsequent \QBS operations on this project. + + The \c error-handling-mode specifies how \QBS should deal with issues + in project files, such as assigning to an unknown property. The possible + values are \c "strict" and \c "relaxed". In strict mode, \QBS will + immediately abort and set the reply's \c error property accordingly. + In relaxed mode, \QBS will continue to resolve the project if possible. + A \l{warning-message}{warning message} will be emitted for every error that + was encountered, and the reply's \c error property will \e not be set. + The default error handling mode is \c "strict". + + If the \c log-time property is \c true, then \QBS will emit \l log-data messages + containing information about which part of the operation took how much time. + + The \c module-properties property lists the names of the module properties + which should be contained in the \l{ProductData}{product data} that + will be sent in the reply message. For instance, if the project to be resolved + is C++-based and the client code is interested in which C++ version the + code uses, then \c module-properties would contain \c{"cpp.cxxLanguageVersion"}. + + The \c overridden-properties property is used to override the values of + module, product or project properties. The possible ways to specify + keys are described \l{Overriding Property Values from the Command Line}{here}. + + The \c restore-behavior property specifies if and how to make use of + an existing build graph. The value \c "restore-only" indicates that + a build graph should be loaded from disk and used as-is. In this mode, + it is an error if the build graph file does not exist. + The value \c "resolve-only" indicates that the project should be resolved + from scratch and that an existing build graph should be ignored. In this mode, + it is an error if the \c "project-file-path" property is not present. + The default value is \c "restore-and-track-changes", which uses an + existing build graph if possible and re-resolves the project if no + build graph was found or if the parameters are different from the ones + used when the project was last resolved. + + The \c top-level-profile property specifies which \QBS profile to use + for resolving the project. It corresponds to the \c profile key when + using the \l resolve command. + + All other properties correspond to command line options of the \l resolve + command, and their semantics are described there. + + When the project has been resolved, \QBS will reply with a \c project-resolved + message. The possible properties are: + \table + \header \li Property \li Type \li Mandatory + \row \li error \li \l ErrorInfo \li no + \row \li project-data \li \l TopLevelProjectData \li no + \endtable + + The \c error-info property is present if and only if the operation + failed. The \c project-data property is present if and only if + the conditions stated by the request's \c data-mode property + are fulfilled. + + All other project-related requests need a resolved project to operate on. + If there is none, they will fail. + + There is at most one resolved project per session. If client code wants to + open several projects or one project in different configurations, it needs + to start additional sessions. + + \section1 Building a Project + + To build a project, a request of type \c build-project is sent. The other properties, + none of which are mandatory, are listed below: + \table + \header \li Property \li Type + \row \li active-file-tags \li string list + \row \li changed-files \li \l FilePath list + \row \li check-outputs \li bool + \row \li check-timestamps \li bool + \row \li clean-install-root \li bool + \row \li data-mode \li \l DataMode + \row \li dry-run \li bool + \row \li command-echo-mode \li string + \row \li enforce-project-job-limits \li bool + \row \li files-to-consider \li \l FilePath list + \row \li install \li bool + \row \li job-limits \li list of objects + \row \li keep-going \li bool + \row \li log-level \li \l LogLevel + \row \li log-time \li bool + \row \li max-job-count \li int + \row \li module-properties \li list of strings + \row \li products \li list of strings or \c "all" + \endtable + + All boolean properties except \c install default to \c false. + + The \c active-file-tags and \c files-to-consider are used to limit the + build to certain output tags and/or source files. + For instance, if only C/C++ object files should get built, then + \c active-file-tags would be set to \c "obj". + + The objects in a \c job-limits array consist of a string property \c pool + and an int property \c limit. + + If the \c log-time property is \c true, then \QBS will emit \l log-data messages + containing information about which part of the operation took how much time. + + If \c products is an array, the elements must correspond to the + \c full-display-name property of previously retrieved \l ProductData, + and only these products will get built. + If \c products is the string \c "all", then all products in the project will + get built. + If \c products is not present, then products whose + \l{Product::builtByDefault}{builtByDefault} property is \c false will + be skipped. + + The \c module-properties property has the same meaning as in the + \l{Resolving a Project}{resolve-project} request. + + All other properties correspond to options of the \l build command. + + When the build has finished, \QBS will reply with a \c project-built + message. The possible properties are: + \table + \header \li Property \li Type \li Mandatory + \row \li error \li \l ErrorInfo \li no + \row \li project-data \li \l TopLevelProjectData \li no + \endtable + + The \c error-info property is present if and only if the operation + failed. The \c project-data property is present if and only if + the conditions stated by the request's \c data-mode property + are fulfilled. + + Unless the \c command-echo-mode value is \c "silent", a message of type + \c command-description is emitted for every command to be executed. + It consists of two string properties \c highlight and \c message, + where \c message is the message to present to the user and \c highlight + is a hint on how to display the message. It corresponds to the + \l{Command and JavaScriptCommand}{Command} property of the same name. + + For finished process commands, a message of type \c process-result + might be emitted. The other properties are: + \table + \header \li Property \li Type + \row \li arguments \li list of strings + \row \li error \li string + \row \li executable-file-path \li \l FilePath + \row \li exit-code \li int + \row \li stderr \li list of strings + \row \li stdout \li list of strings + \row \li success \li bool + \row \li working-directory \li \l FilePath + \endtable + + The \c error string is one of \c "failed-to-start", \c "crashed", \c "timed-out", + \c "write-error", \c "read-error" and \c "unknown-error". + Its value is not meaningful unless \c success is \c false. + + The \c stdout and \c stderr properties describe the process's standard + output and standard error output, respectively, split into lines. + + The \c success property is \c true if the process finished without errors + and an exit code of zero. + + The other properties describe the exact command that was executed. + + This message is only emitted if the process failed or it has printed data + to one of the output channels. + + \section1 Cleaning a Project + + To remove a project's build artifacts, a request of type \c clean-project + is sent. The other properties are: + \table + \header \li Property \li Type + \row \li dry-run \li bool + \row \li keep-going \li bool + \row \li log-level \li \l LogLevel + \row \li log-time \li bool + \row \li products \li list of strings + \endtable + + The elements of the \c products array correspond to a \c full-display-name + of a \l ProductData. If this property is present, only the respective + products' artifacts are removed. + + If the \c log-time property is \c true, then \QBS will emit \l log-data messages + containing information about which part of the operation took how much time. + + All other properties correspond to options of the \l clean command. + + None of these properties are mandatory. + + After all artifacts have been removed, \QBS replies with a + \c project-cleaned message. If the operation was successful, this message + has no properties. Otherwise, a property \c error of type \l ErrorInfo + indicates what went wrong. + + \section1 Installing a Project + + Installing is normally part of the \l{Building a Project}{build} + process. To do it in a separate step, the \c install property + is set to \c false when building and a dedicated \c install-project + message is sent. The other properties are: + \table + \header \li Property \li Type + \row \li clean-install-root \li bool + \row \li dry-run \li bool + \row \li install-root \li \l FilePath + \row \li keep-going \li bool + \row \li log-level \li \l LogLevel + \row \li log-time \li bool + \row \li products \li list of strings + \row \li use-sysroot \li bool + \endtable + + The elements of the \c products array correspond to a \c full-display-name + of a \l ProductData. If this property is present, only the respective + products' artifacts are installed. + + If the \c log-time property is \c true, then \QBS will emit \l log-data messages + containing information about which part of the operation took how much time. + + If the \c use-sysroot property is \c true and \c install-root is not present, + then the install root will be \l{qbs::sysroot}{qbs.sysroot}. + + All other properties correspond to options of the \l install command. + + None of these properties are mandatory. + + \target cancel-message + \section1 Canceling an Operation + + Potentially long-running operations can be aborted using the \c cancel-job + request. This message does not have any properties. There is no dedicated + reply message; instead, the usual reply for the request associated with + the currently running operation will be sent, with the \c error property + set to indicate that it was canceled. + + If there is no operation in progress, this request will have no effect. + In particular, if it arrives after the operation that it was supposed to + cancel has already finished (i.e. there is a race condition), the reply + received by client code will not contain a cancellation-related error. + + \section1 Adding and Removing Source Files + + Source files can be added to and removed from \QBS project files with + the \c add-files and \c remove-files messages, respectively. These two + requests have the same set of properties: + \table + \header \li Property \li Type + \row \li files \li \l FilePath list + \row \li group \li string + \row \li product \li string + \endtable + + The \c files property specifies which files should be added or removed. + + The \c product property corresponds to the \c full-display-name of + a \l ProductData and specifies to which product to apply the operation. + + The \c group property corresponds to the \c name of a \l GroupData + and specifies to which group in the product to apply the operation. + + After the operation has finished, \QBS replies with a \c files-added + and \c files-removed message, respectively. Again, the properties are + the same: + \table + \header \li Property \li Type \li Mandatory + \row \li error \li \l ErrorInfo \li no + \row \li failed-files \li \l FilePath list \li no + \endtable + + If the \c error property is present, the operation has at least + partially failed and \c failed-files will list the files + that could not be added or removed. + + \section1 The \c get-run-environment Message + + This request retrieves the full run environment for a specific + executable product, taking into account the + \l{Module::setupRunEnvironment}{setupRunEnvironment} scripts + of all modules pulled in by the product. The properties are as follows: + \table + \header \li Property \li Type \li Mandatory + \row \li base-environment \li \l Environment \li no + \row \li config \li list of strings \li no + \row \li product \li string \li yes + \endtable + + The \c base-environment property defines the environment into which + the \QBS-specific values should be merged. + + The \c config property corresponds to the \l{--setup-run-env-config} + option of the \l run command. + + The \c product property specifies the product whose environment to + retrieve. The value must correspond to the \c full-display-name + of some \l ProductData in the project. + + \QBS will reply with a \c run-environment message. In case of failure, + it will contain a property \c error of type \l ErrorInfo, otherwise + it will contain a property \c full-environment of type \l Environment. + + \section1 The \c get-generated-files-for-sources Message + + This request allows client code to retrieve information about + which artifacts are generated from a given source file. + Its sole property is a list \c products, whose elements are objects + with the two properties \c full-display-name and \c requests. + The first identifies the product to which the requests apply, and + it must match the property of the same name in a \l ProductData + in the project. + The latter is a list of objects with the following properties: + \table + \header \li Property \li Type \li Mandatory + \row \li source-file \li \l FilePath \li yes + \row \li tags \li list of strings \li no + \row \li recursive \li bool \li no + \endtable + + The \c source-file property specifies a source file in the respective + product. + + The \c tags property constrains the possible file tags of the generated + files to be matched. This is relevant if a source files serves as input + to more than one rule or the rule generates more than one type of output. + + If the \c recursive property is \c true, files indirectly generated + from the source file will also be returned. The default is \c false. + For instance, íf this property is enabled for a C++ source file, + the final link target (e.g. a library or an application executable) + will be returned in addition to the object file. + + \QBS will reply with a \c generated-files-for-sources message, whose + structure is similar to the request. It also has a single object list + property \c products, whose elements consist of a string property + \c full-display-name and an object list property \c results. + The properties of these objects are: + \table + \header \li Property \li Type + \row \li source-file \li \l FilePath + \row \li generated-files \li \l FilePath list + \endtable + + The \c source-file property corresponds to an entry of the same name + in the request, and the \c generated-files are the files which are + generated by \QBS rules that take the source file as an input, + taking the constraints specified in the request into account. + + Source files for which the list would be empty are not listed. + Similarly, products for which the \c results list would be empty + are also omitted. + + \note The results may be incomplete if the project has not been fully built. + + \section1 Closing a Project + + A project is closed with a \c release-project message. This request has + no properties. + + \QBS will reply with a \c project-released message. If no project was open, + the reply will contain an \c error property of type \l ErrorInfo. + + \target quit-message + \section1 Closing the Session + + To close the session, a \c quit message is sent. This request has no + properties. + + \QBS will cancel all currently running operations and then close itself. + No reply will be sent. + + \section1 Progress Messages + + While a request is being handled, \QBS may emit progress information in order + to enable client code to display a progress bar. + + \target task-started + \section2 The \c task-started Message + + This is always the first progress-related message for a specific request. + It appears at most once per request. + It consists of a string property \c description, whose value can be displayed + to users, and an integer property \c max-progress that indicates which + progress value corresponds to 100 per cent. + + \target task-progress + \section2 The \c task-progress Message + + This message updates the progress via an integer property \c progress. + + \target new-max-progress + \section2 The \c new-max-progress Message + + This message is emitted if the original estimated maximum progress has + to be corrected. Its integer property \c max-progress updates the + value from a preceding \l task-started message. + + \section1 Messages for Users + + There are two types of messages that purely contain information to be + presented to users. + + \target log-data + \section2 The \c log-data Message + + This object has a string property \c message, which is the text to be + shown to the user. + + \target warning-message + \section2 The \c warning Message + + This message has a single property \c warning of type \l ErrorInfo. + + \section1 The \c protocol-error Message + + \QBS sends this message as a reply to a request with an unknown \c type. + It contains an \c error property of type \l ErrorInfo. + + \section1 Project Data + + If a request can alter the build graph data, the associated reply may contain + a \c project-data property whose value is of type \l TopLevelProjectData. + + \section2 TopLevelProjectData + + This data type represents the entire project. It has the same properties + as \l PlainProjectData. If it is part of a \c project-resolved message, + these additional properties are also present: + \table + \header \li Property \li Type + \row \li build-directory \li \l FilePath + \row \li build-graph-file-path \li \l FilePath + \row \li build-system-files \li \l FilePath list + \row \li overridden-properties \li object + \row \li profile-data \li object + \endtable + + The value of \c build-directory is the top-level build directory. + + The \c build-graph-file-path value is the path to the build graph file. + + The \c build-system-files value contains all \QBS project files, including + modules and JavaScript helper files. + + The value of \c overridden-properties is the one that was passed in when + the project was last \l{Resolving a Project}{resolved}. + + The \c profile-data property maps the names of the profiles used in the project + to the respective property maps. Unless profile multiplexing is used, this + object will contain exactly one property. + + \section2 PlainProjectData + + This data type describes a \l Project item. The properties are as follows: + \table + \header \li Property \li Type + \row \li is-enabled \li bool + \row \li location \li \l FilePath + \row \li name \li string + \row \li products \li \l ProductData list + \row \li sub-projects \li \l PlainProjectData list + \endtable + + The \c is-enabled property corresponds to the project's + \l{Project::condition}{condition}. + + The \c location property is the exact position in a \QBS project file + where the corresponding \l Project item was defined. + + The \c products and \c sub-projects are what the project has pulled in via + its \l{Project::references}{references} property. + + \section2 ProductData + + This data type describes a \l Product item. The properties are as follows: + \table + \header \li Property \li Type + \row \li build-directory \li \l FilePath + \row \li dependencies \li list of strings + \row \li full-display-name \li string + \row \li generated-artifacts \li \l ArtifactData list + \row \li groups \li \l GroupData list + \row \li is-enabled \li bool + \row \li is-multiplexed \li bool + \row \li is-runnable \li bool + \row \li location \li \l Location + \row \li module-properties \li \l ModulePropertiesData + \row \li multiplex-configuration-id \li string + \row \li name \li string + \row \li properties \li object + \row \li target-executable \li \l FilePath + \row \li target-name \li string + \row \li type \li list of strings + \row \li version \li string + \endtable + + The \c dependencies are the names of products that occur in the (enabled) + \l Depends items of this product. + + The \c generated-artifacts are files that are created by the \l{Rule}{rules} + in this product. + + The \c groups list corresponds to the \l Group items in this product. + In addition, a "pseudo-group" is created for the \l{Product::files}{files} + property of the product itself. Its name is the same as the product's. + + The \c is-enabled property corresponds to the product's + \l{Product::condition}{condition}. A product may also get disabled + if it contains errors and \QBS was was instructed to operate in relaxed mode + when the project was \l{Resolving a Project}{resolved}. + + The \c is-multiplexed property is true if and only if the product is + \l{Multiplexing}{multiplexed} over one ore more properties. + + The \c is-runnable property indicates whether one of the product's + target artifacts is an executable file. + In that case, the file is available via the \c target-executable property. + + The \c location property is the exact position in a \QBS project file + where the corresponding \l Product item was defined. + + The \c module-properties object provides the values of the module properties + that were requested when the project was \l{Resolving a Project}{resolved}. + + The \c name property is the value given in the \l{Product::name}{Product item}, + whereas \c full-display-name is a name that uniquely identifies the + product in the entire project, even in the presence of multiplexing. + In the absence of multiplexing, it is the same as \c name. In either case, + it is suitable for being presented to users. + + See the \l Product item documentation for a description of the other + properties. + + \section2 GroupData + + This data type describes a \l Group item. The properties are: + \table + \header \li Property \li Type + \row \li is-enabled \li bool + \row \li location \li \l Location + \row \li module-properties \li \l ModulePropertiesData + \row \li name \li string + \row \li prefix \li string + \row \li source-artifacts \li \l ArtifactData list + \row \li source-artifacts-from-wildcards \li \l ArtifactData list + \endtable + + The \c is-enabled property corresponds to the groups's + \l{Group::condition}{condition}. However, if the group's product + is disabled, this property will always be \c false. + + The \c location property is the exact position in a \QBS project file + where the corresponding \l Group item occurs. + + The \c module-properties object provides the values of the module properties + that were requested when the project was \l{Resolving a Project}{resolved}. + If no module properties are set on the Group level and the value would therefore + be the same as in the group's product, then this property is omitted. + + The \c source-artifacts list corresponds the the files listed verbatim + in the group's \l{Group::files}{files} property. + + The \c source-artifacts-from-wildcards list represents the the files + expanded from wildcard entries in the group's \l{Group::files}{files} property. + + See the \l Group item documentation for a description of the other + properties. + + \section2 ArtifactData + + This data type represents files that occur in the project, either as sources + or as outputs of a rules. \QBS project files, on the other hand, are not + artifacts. The properties are: + \table + \header \li Property \li Type + \row \li file-path \li \l FilePath + \row \li file-tags \li list of strings + \row \li install-data \li object + \row \li is-executable \li bool + \row \li is-generated \li bool + \row \li is-target \li bool + \row \li module-properties \li \l ModulePropertiesData + \endtable + + The \c install-data property is an object whose \c is-installable property + indicates whether the artifact gets installed. If so, then the \l FilePath + properties \c install-file-path and \c install-root provide further + information. + + The \c is-target property is true if the artifact is a target artifact + of its product, that is, \c is-generated is true and \c file-tags + intersects with the \l{Product::type}{product type}. + + The \c module-properties object provides the values of the module properties + that were requested when the project was \l{Resolving a Project}{resolved}. + This property is only present for generated artifacts. For source artifacts, + the value can be retrieved from their \l{GroupData}{group}. + + The other properties should be self-explanatory. + + \section2 ModulePropertiesData + + This data type maps fully qualified module property names to their + respective values. + + \section1 Other Custom Data Types + + There are a number of custom data types that serve as building blocks in + various messages. They are described below. + + \section2 FilePath + + A \e FilePath is a string that describes a file or directory. FilePaths are + always absolute and use forward slashes for separators, regardless of + the host operating system. + + \section2 Location + + A \e Location is an object representing a file path and possibly also a position + within the respective file. It consists of the following properties: + \table + \header \li Property \li Type \li Mandatory + \row \li file-path \li \l FilePath \li yes + \row \li line \li int \li no + \row \li column \li int \li no + \endtable + + \section2 ErrorInfo + + An \e ErrorInfo is an object representing error information. Its sole property + \c items is an array of objects with the following structure: + \table + \header \li Property \li Type \li Mandatory + \row \li \c message \li string \li yes + \row \li location \li \l Location \li no + \endtable + + \section2 DataMode + + This is the type of the \c data-mode property in a + \l{Resolving a project}{resolve} or \l{Building a project}{build} + request. It is used to indicate under which circumstances + the reply message should include the project data. The possible + values have string type and are as follows: + \list + \li \c "never": Do not attach project data to the reply. + \li \c "always": Do attach project data to the reply. + \li \c "only-if-changed": Attach project data to the reply only + if it is different from the current + project data. + \endlist + The default value is \c "never". + + \section2 LogLevel + + This is the type of the \c log-level property that can occur + in various requests. It is used to indicate whether the client would like + to receive \l log-data and/or \l{warning-message}{warning} messages. + The possible values have string type and are as follows: + \list + \li "error": Do not log anything. + \li "warning": \QBS may emit \l{warning-message}{warnings}, but no + \l log-data messages. + \li "info": In addition to warnings, \QBS may emit informational + \l log-data messages. + \li "debug": \QBS may emit debug output. No messages will be generated; + instead, the standard error output channel will be used. + \endlist + The default value is \c "info". + + \section2 Environment + + This data type describes a set of environment variables. It is an object + whose keys are names of environment variables and whose values are + the values of these environment variables. + +*/ diff --git a/doc/appendix/qbs-porting.qdoc b/doc/appendix/qbs-porting.qdoc new file mode 100644 index 00000000..ba697d7b --- /dev/null +++ b/doc/appendix/qbs-porting.qdoc @@ -0,0 +1,504 @@ +/**************************************************************************** +** +** 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 + \previouspage building-qbs.html + \page porting-to-qbs.html + \nextpage json-api.html + + \title Appendix B: Migrating from Other Build Systems + + You can use the \l{create-project}{qbs create-project} command to + automatically generate \QBS project files from an arbitrary directory + structure. This is a useful starting point when migrating from other + build tools, such as qmake or CMake. + + To use the tool, switch to the project directory and run the + \c {qbs create-project} command, which is located in the \c bin directory of + the \QBS installation directory (or the Qt Creator installation directory). + + After generating the initial .qbs file, add the missing configuration + variables and functions to it, as described in the following sections. + + \section1 Migrating from qmake + + The following sections describe the \QBS equivalents of qmake variable + values. + + \section2 CONFIG + + Specify project configuration and compiler options. + + \section3 console + + Set the \l{Product::consoleApplication}{Product.consoleApplication} property + to \c true for the \l{Application}, \l{CppApplication},or \l{QtApplication} + item. For example: + + \code + Application { + name: "helloworld" + files: "main.cpp" + Depends { name: "cpp" } + consoleApplication: true + } + \endcode + + \section3 ordered + + This qmake variable has no direct equivalent in \QBS. Instead, the build + order is determined by implicit and explicit dependencies between products. + To add an explicit dependency, add a \l{Depends} item to a + \l{Product}{product}: + + \code + CppApplication { + name: "myapp" + Depends { name: "mylib" } + } + \endcode + + The \c myapp product depends on and links to the \c mylib product, and is + therefore built after it. + + \section3 qt + + In qmake, the Qt dependency is implicit, whereas in \QBS it is not. + If \c {CONFIG -= qt}, add a \l{Depends} item to specify that + the \l{Product}{product} depends on the \l{cpp} module: + + \code + Product { + Depends { name: "cpp" } + } + \endcode + + \section2 DEFINES + + Set the \l{cpp::defines}{cpp.defines} property for the \l{Product}{product}. + + \note To reference \c cpp.defines, you must specify a dependency on the + \l{cpp} module. + + \code + Product { + Depends { name: "cpp" } + cpp.defines: ["SUPPORT_MY_FEATURES"] + } + \endcode + + \section2 DESTDIR + + We recommend that you use the \l{Installing Files}{installation mechanism} + to specify the location of the target file: + + \code + Application { + Group { + name: "Runtime resources" + files: "*.qml" + qbs.install: true + qbs.installDir: "share/myproject" + } + Group { + name: "The App itself" + fileTagsFilter: "application" + qbs.install: true + qbs.installDir: "bin" + } + } + \endcode + + If that is not possible, you can use the \l{Product::}{destinationDirectory} + property: + + \code + DynamicLibrary { + name: "mydll" + destinationDirectory: "libDir" + } + \endcode + + \section2 HEADERS, SOURCES, FORMS, RESOURCES, OTHER_FILES + + Include header, source, form, and resource files as well as any + other files as values of a \l{Product::files}{Product.files} + or \l{Group::files}{Group.files} property: + + \code + QtApplication { + name: "myapp" + files: ["myapp.h", "myapp.cpp", "myapp.ui", "myapp.qrc", "readme.txt"] + } + \endcode + + \QBS uses \l{FileTagger}{file taggers} to figure out what kind of file + it is dealing with. + + \section2 ICON + + There is no direct equivalent in \QBS. If you add a \l{Depends} {dependency} + to the \l{ib} module and add the \c .xcassets directory as a value of the + \l{Product::files}{Product.files} property, \QBS takes care of setting the + application icon automatically when building for Apple platforms: + + \code + Application { + name: "myapp" + files [".xcassets"] + Depends { name: "ib" } + } + \endcode + + Alternatively, you can set the icon name as the value of the + \l{bundle::infoPlist}{bundle.infoPlist} parameter, specify a dependency to + the \l{ib} module, and add the application \c .icns file as a value of the + \l{Product::}{files} property: + + \code + Application { + name: "myapp" + files ["myapp.icns"] + Depends { name: "ib" } + bundle.infoPlist: ({"CFBundleIconFile": "myapp"}) + \endcode + + \section2 INCLUDEPATH + + Add the paths to the include files as values of the \l{cpp::includePaths} + {cpp.includePaths} property: + + \code + CppApplication { + cpp.includePaths: ["..", "some/other/dir"] + } + \endcode + + \section2 LIBS + + For libraries that are part of the project, use \l{Depends} items. + + To pull in external libraries, use the \l{cpp::libraryPaths} + {cpp.libraryPaths} property for the Unix \c -L (library path) flags and the + \l{cpp::dynamicLibraries}{cpp.dynamicLibraries} and \l{cpp::staticLibraries} + {cpp.staticLibraries} properties for the + \c -l (library) flags. + + For example, \c {LIBS += -L/usr/local/lib -lm} would become: + + \code + CppApplication { + cpp.libraryPaths: ["/usr/local/lib"] + cpp.dynamicLibraries: ["m"] + } + \endcode + + \section2 OUT_PWD + + Use the \l{Product::buildDirectory}{Product.buildDirectory} property + to refer to the base output directory of the generated artifacts. + + \section2 PWD + + Corresponds to the the file-scope variable \c path. + + \section2 _PRO_FILE_ + + Corresponds to the file-scope variable \c filePath when used in a + \l{Project}{project} or \l{Product}{product}. + + \section2 _PRO_FILE_PWD_ + + Corresponds to the \l{Project::sourceDirectory}{Project.sourceDirectory} or + \l{Product::sourceDirectory}{Product.sourceDirectory} property. + + \section2 QMAKE_ASSET_CATALOGS + + Add a \l{Depends}{dependency} to the \l{ib} module and add the \c .xcassets + directory as a value of the \l{Product::}{files} property: + + \code + Application { + name: "myapp" + files [".xcassets"] + Depends { name: "ib" } + } + \endcode + + \section2 QMAKE_BUNDLE_DATA + + For the time being, you can manually place files in the appropriate location + using the \l{Installing Files}{installation mechanism}. Better solutions are + under development. + + \section2 QMAKE_BUNDLE_EXTENSION + + Set the \l{bundle::extension}{bundle.extension} property. + + \note Unlike qmake, \QBS automatically prepends a period (.) to the property + value. + + \section2 QMAKE_{C,CXX,OBJECTIVE}_CFLAGS{_DEBUG,_RELEASE} + + Use the \l{cpp::commonCompilerFlags}{cpp.commonCompilerFlags} property or + the properties corresponding to each compiler flags variable: + + \table + \header + \li qmake Variable + \li cpp Module Property + \row + \li \c QMAKE_CFLAGS_DEBUG + + \c QMAKE_CFLAGS_RELEASE + \li \l{cpp::cFlags}{cpp.cFlags} + \row + \li \c QMAKE_CXXFLAGS_DEBUG + + \c QMAKE_CXXFLAGS_RELEASE + \li \l{cpp::cxxFlags}{cpp.cxxFlags} + \row + \li \c QMAKE_OBJECTIVE_CFLAGS + \li \l{cpp::objcFlags}{cpp.objcFlags} + + \l{cpp::objcxxFlags}{cpp.objcxxFlags} + \endtable + + Use \l{Properties} items or simple conditionals as values of the + \l{qbs::buildVariant}{qbs.buildVariant} property to simulate the \c _DEBUG + and \c _RELEASE variants of the qmake variables. + + \section2 QMAKE_FRAMEWORK_BUNDLE_NAME + + Set the \l{bundle::bundleName}{bundle.bundleName} property (which is derived + from \l{Product::targetName}{Product.targetName}) combined with + \l{bundle::extension}{bundle.extension}. + + \section2 QMAKE_FRAMEWORK_VERSION + + Set the \l{bundle::frameworkVersion}{bundle.frameworkVersion} property. + + \section2 QMAKE_INFO_PLIST + + Include the \c info.plist file as a value of \l{Product::}{files} property + and specify a dependency to the \l{bundle} module: + + \code + Application { + name: "myapp" + files ["info.plist"] + Depends { name: "bundle" } + } + \endcode + + \QBS will automatically add any necessary properties to your \c Info.plist + file. Typically, it determines the appropriate values from the other + properties in the project, and therefore you do not need to use the + \c {Info.plist.in > Info.plist} configuration mechanism. Further, you almost + never need to embed placeholders into the source \c Info.plist file. Set the + \l{bundle::processInfoPlist}{bundle.processInfoPlist} property to \c false + to disable this behavior: + + \code + \\ ... + bundle.processInfoPlist: false + \endcode + + In addition to, or instead of, using an actual \c Info.plist file, you can + add \c Info.plist properties using the \l{bundle::infoPlist} + {bundle.infoPlist} property. For example: + + \code + \\ ... + bundle.infoPlist: ({ + "NSHumanReadableCopyright": "Copyright (c) 2017 Bob Inc", + "Some other key", "Some other value, & XML special characters are no problem! >;) 非凡!" + }) + \endcode + + \section2 QMAKE_LFLAGS + + Set the \l{cpp::linkerFlags}{cpp.linkerFlags} property for the \l{Product} + {product}. + + \section2 QMAKE_{MACOSX,IOS,TVOS,WATCHOS}_DEPLOYMENT_TARGET + + For each qmake deployment target variable, use the corresponding property of + the \l{cpp} module: + + \table + \header + \li qmake Variable + \li cpp Module Property + \row + \li \c QMAKE_MACOSX_DEPLOYMENT_TARGET + \li \l{cpp::minimumMacosVersion}{cpp.minimumMacosVersion} + \row + \li \c QMAKE_IOS_DEPLOYMENT_TARGET + \li \l{cpp::minimumIosVersion}{cpp.minimumIosVersion} + \row + \li \c QMAKE_TVOS_DEPLOYMENT_TARGET + \li \l{cpp::minimumTvosVersion}{cpp.minimumTvosVersion} + \row + \li \c QMAKE_WATCHOS_DEPLOYMENT_TARGET + \li \l{cpp::minimumWatchosVersion}{cpp.minimumWatchosVersion} + \endtable + + \section2 QMAKE_RPATHDIR + + Set the \l{cpp::rpaths}{cpp.rpaths} property for the \l{Product}{product}. + + \section2 QMAKE_SONAME_PREFIX + + Use the \l{cpp::sonamePrefix}{cpp.sonamePrefix} property for the \l{Product} + {product}. + + \section2 QML_IMPORT_PATH + + Used only for Qt Creator QML syntax highlighting. Inside a \l{Product}, + \l{Application}, \l{CppApplication}, or \l{QtApplication}, create a + \c qmlImportPaths property: + + \code + Product { + name: "myProduct" + property stringList qmlImportPaths: [sourceDirectory + "/path/to/qml/"] + } + \endcode + + \section2 QT + + Add a \l{Depends} item to the \l{Product}{product} that specifies the + dependencies to \l{Qt} modules. For example: + + \code + QtApplication { + Depends { name: "Qt.widgets" } + } + \endcode + + You could also use the following form that is equivalent to the previous + one: + + \code + QtApplication { + Depends { name: "Qt"; submodules: "widgets" } + } + \endcode + + \section2 QTPLUGIN + + Building static applications often requires linking to static QPA plugins, + such as \c qminimal. You can use the following syntax to enable \QBS to + link to the required plugins: + + \code + QtApplication { + name: "myapp" + Depends { name: "Qt"; submodules: ["core", "gui", "widgets"] } + Depends { name: "Qt.qminimal"; condition: Qt.core.staticBuild } + } + \endcode + + \section2 RC_FILE + + Add Windows resource files to the value of the \l{Product::files} + {Product.files} property. + + \section2 TARGET + + Use the \l{Product::targetName}{Product.targetName} property to specify the + base file name of target artifacts. + + \section2 TEMPLATE + + \section3 app + + Use \l{Application} or \l{CppApplication} as the \l{Product}{product}: + + \code + CppApplication { + name: "helloworld" + files: "main.cpp" + } + \endcode + + This is roughly equivalent to: + + \code + Product { + name: "helloworld" + type: "application" + files: "main.cpp" + Depends { name: "cpp" } + } + \endcode + + \section3 lib + + Use either \l{DynamicLibrary} or \l{StaticLibrary} as the \l{Product} + {product}, depending on whether the value of \c CONFIG in the .pro file is + \c shared or \c static. For example, if the value is \c shared: + + \code + DynamicLibrary { + name: "mydll" + files: ["mySourceFile.cpp"] + Depends { name: "cpp" } + } + \endcode + + \section3 subdirs + + In a \l{Project} item, specify subdirectories as values of the + \l{Project::}{references} property: + + \code + Project { + references: [ + "app/app.qbs", + "lib/lib.qbs" + ] + } + \endcode + + \section2 message(), warning(), error(), log() + + You can use the \l{Console API} to print info, warning, error, and log messages to the console. + + \code + Product { + name: { + console.info("--> now evaluating the product name"); + return "theName"; + } + Depends { name: "cpp" } + cpp.includePath: { throw "An error occurred." } + } + \endcode +*/ diff --git a/doc/classic.css b/doc/classic.css new file mode 100644 index 00000000..f97bdbea --- /dev/null +++ b/doc/classic.css @@ -0,0 +1,295 @@ +BODY,H1,H2,H3,H4,H5,H6,P,CENTER,TD,TH,UL,DL,DIV { + font-family: Arial, Geneva, Helvetica, sans-serif; +} +H1 { + text-align: center; + font-size: 160%; +} +H2 { + font-size: 120%; +} +H3 { + font-size: 100%; +} + +h3.fn,span.fn +{ + background-color: #eee; + border-width: 1px; + border-style: solid; + border-color: #ddd; + font-weight: bold; + padding: 6px 0px 6px 10px; + margin: 42px 0px 0px 0px; +} + +hr { + border: 0; + color: #a0a0a0; + background-color: #ccc; + height: 1px; + width: 100%; + text-align: left; + margin: 34px 0px 34px 0px; +} + +table.valuelist { + border-width: 1px 1px 1px 1px; + border-style: solid; + border-color: #dddddd; + border-collapse: collapse; + background-color: #f0f0f0; +} + +table.indextable { + border-width: 1px 1px 1px 1px; + border-collapse: collapse; + background-color: #f0f0f0; + border-color:#555; + font-size: 110%; +} + +table td.largeindex { + border-width: 1px 1px 1px 1px; + border-collapse: collapse; + background-color: #f0f0f0; + border-color:#555; + font-size: 120%; +} + +table.valuelist th { + border-width: 1px 1px 1px 2px; + padding: 4px; + border-style: solid; + border-color: #666; + color:white; + background-color:#666; +} + +th.titleheader { + border-width: 1px 0px 1px 0px; + padding: 4px; + border-style: solid; + border-color: #444; + color:white; + background-color:#555555; + font-size: 110%; +} + +th.largeheader { + border-width: 1px 0px 1px 0px; + padding: 4px; + border-style: solid; + border-color: #444; + color:white; + background-color:#555555; + font-size: 120%; +} + +p { + + margin-left: 4px; + margin-top: 8px; + margin-bottom: 8px; +} + +a:link +{ + color: #0046ad; + text-decoration: none +} + +a:visited +{ + color: #672967; + text-decoration: none +} + +a.obsolete +{ + color: #661100; + text-decoration: none +} + +a.compat +{ + color: #661100; + text-decoration: none +} + +a.obsolete:visited +{ + color: #995500; + text-decoration: none +} + +a.compat:visited +{ + color: #995500; + text-decoration: none +} + +body +{ + background: #ffffff; + color: black +} + +table.generic, table.annotated +{ + border-width: 1px; + border-color:#bbb; + border-style:solid; + border-collapse:collapse; +} + +table td.memItemLeft { + width: 180px; + padding: 2px 0px 0px 8px; + margin: 4px; + border-width: 1px; + border-color: #E0E0E0; + border-style: none; + font-size: 100%; + white-space: nowrap +} + +table td.memItemRight { + padding: 2px 8px 0px 8px; + margin: 4px; + border-width: 1px; + border-color: #E0E0E0; + border-style: none; + font-size: 100%; +} + +table tr.odd { + background: #f0f0f0; + color: black; +} + +table tr.even { + background: #e4e4e4; + color: black; +} + +table.annotated th { + padding: 3px; + text-align: left +} + +table.annotated td { + padding: 3px; +} + +table tr pre +{ + padding-top: 0px; + padding-bottom: 0px; + padding-left: 0px; + padding-right: 0px; + border: none; + background: none +} + +tr.qt-style +{ + background: #96E066; + color: black +} + +body pre +{ + padding: 0.2em; + border: #e7e7e7 1px solid; + background: #f1f1f1; + color: black +} + +table tr.qt-code pre +{ + padding: 0.2em; + border: #e7e7e7 1px solid; + background: #f1f1f1; + color: black +} + +span.preprocessor, span.preprocessor a +{ + color: darkblue; +} + +span.comment +{ + color: darkred; + font-style: italic +} + +span.string,span.char +{ + color: darkgreen; +} + +.title +{ + text-align: center +} + +.subtitle +{ + font-size: 0.8em +} + +.small-subtitle +{ + font-size: 0.65em +} + +.qmlitem { + padding: 0; +} + +.qmlname { + white-space: nowrap; + font-weight: bold; + font-size: 125%; +} + +.qmltype { + font-weight: bold; + font-size: 125%; +} + +.qmlproto, .qmldoc { + // border-top: 1px solid #84b0c7; +} + +.qmlproto { + padding: 0; + //background-color: #e4e4e4;//#d5e1e8; + //font-weight: bold; + //-webkit-border-top-left-radius: 8px; + //-webkit-border-top-right-radius: 8px; + //-moz-border-radius-topleft: 8px; + //-moz-border-radius-topright: 8px; +} + +.qmldoc { + border-top: 1px solid #e4e4e4; + //padding: 2px 5px; + //background-color: #eef3f5; + //border-top-width: 0; + //-webkit-border-bottom-left-radius: 8px; + //-webkit-border-bottom-right-radius: 8px; + //-moz-border-radius-bottomleft: 8px; + //-moz-border-radius-bottomright: 8px; +} + +.qmldoc p, .qmldoc dl, .qmldoc ul { + //margin: 6px 0; +} + +*.qmlitem p { + //margin-top: 0px; + //margin-bottom: 0px; +} diff --git a/doc/codeattributions.qdoc b/doc/codeattributions.qdoc new file mode 100644 index 00000000..ee4e4b90 --- /dev/null +++ b/doc/codeattributions.qdoc @@ -0,0 +1,201 @@ +/*! + +\contentspage attributions.html +\ingroup attributions-libs +\ingroup attributions-qbs +\page qbs-attribution-ds_store.html attribution +\target ds_store + +\title ds_store +\brief MIT License + +Manipulate Finder .DS_Store files from Python + +Used in the qbs dmg module for building Apple disk images. + +The sources can be found in src/3rdparty/python/lib/python2.7/site-packages/ds_store. + +\l{https://github.com/al45tair/ds_store}{Project Homepage}, upstream version: 1.1.2 + + +\badcode +Copyright (c) 2014 Alastair Houghton +\endcode + +\l{https://spdx.org/licenses/MIT.html}{MIT License}. + +\badcode +Copyright (c) 2014 Alastair Houghton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +\endcode +*/ + +/*! + +\contentspage attributions.html +\ingroup attributions-libs +\ingroup attributions-qbs +\page qbs-attribution-dmgbuild.html attribution +\target dmgbuild + +\title dmgbuild +\brief MIT License + +macOS command line utility to build disk images + +Used in the qbs dmg module for building Apple disk images. + +The sources can be found in src/3rdparty/python/lib/python2.7/site-packages/dmgbuild. + +\l{https://github.com/al45tair/dmgbuild}{Project Homepage}, upstream version: 1.3.1 + + +\badcode +Copyright (c) 2014 Alastair Houghton +\endcode + +\l{https://spdx.org/licenses/MIT.html}{MIT License}. + +\badcode +Copyright (c) 2014 Alastair Houghton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +\endcode +*/ + +/*! + +\contentspage attributions.html +\ingroup attributions-libs +\ingroup attributions-qbs +\page qbs-attribution-mac_alias.html attribution +\target mac_alias + +\title mac_alias +\brief MIT License + +Generate/parse Mac OS Alias records from Python + +Used in the qbs dmg module for building Apple disk images. + +The sources can be found in src/3rdparty/python/lib/python2.7/site-packages/mac_alias. + +\l{https://github.com/al45tair/mac_alias}{Project Homepage}, upstream version: 2.0.6 + + +\badcode +Copyright (c) 2014 Alastair Houghton +\endcode + +\l{https://spdx.org/licenses/MIT.html}{MIT License}. + +\badcode +Copyright (c) 2014 Alastair Houghton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +\endcode +*/ + +/*! + +\contentspage attributions.html +\ingroup attributions-libs +\ingroup attributions-qbs +\page qbs-attribution-biplist.html attribution +\target biplist + +\title biplist +\brief BSD 3-clause "New" or "Revised" License + +biplist is a library for reading/writing binary plists. + +Used in the qbs dmg module for building Apple disk images. + +The sources can be found in src/3rdparty/python/lib/python2.7/site-packages/biplist. + +\l{https://bitbucket.org/wooster/biplist}{Project Homepage}, upstream version: 1.0.2 + + +\badcode +Copyright (c) 2010, Andrew Wooster +\endcode + +\l{https://spdx.org/licenses/BSD-3-Clause.html}{BSD 3-clause "New" or "Revised" License}. + +\badcode +Copyright (c) 2010, Andrew Wooster +All rights reserved. + +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 biplist 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 HOLDER 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. +\endcode +*/ diff --git a/doc/config/macros.qdocconf b/doc/config/macros.qdocconf new file mode 100644 index 00000000..a8abe25e --- /dev/null +++ b/doc/config/macros.qdocconf @@ -0,0 +1,65 @@ +macro.QBS = "Qbs" +macro.qbsversion = $QBS_VERSION + +macro.defaultvalue = "Default:" +macro.nodefaultvalue = "Default: Undefined" +macro.appleproperty = "This property is specific to Apple platforms." +macro.unixproperty = "This property is specific to Unix platforms." +macro.windowsproperty = "This property is specific to Windows." +macro.baremetalproperty = "This property is specific to bare-metal platforms." +macro.funsince.HTML = "

This function was introduced in version \1.

" + +macro.aacute.HTML = "á" +macro.Aring.HTML = "Å" +macro.aring.HTML = "å" +macro.Auml.HTML = "Ä" +macro.author = "\\b{Author:}" +macro.BR.HTML = "
" +macro.copyright.HTML = "©" +macro.eacute.HTML = "é" +macro.gui = "\\b" +macro.HR.HTML = "
" +macro.iacute.HTML = "í" +macro.key = "\\b" +macro.macos = "macOS" +macro.menu = "\\b" +macro.oslash.HTML = "ø" +macro.ouml.HTML = "ö" +macro.QA = "\\e{Qt Assistant}" +macro.QD = "\\e{Qt Designer}" +macro.QL = "\\e{Qt Linguist}" +macro.QQV = "\\e{Qt QML Viewer}" +macro.param = "\\e" +macro.raisedaster.HTML = "*" +macro.rarrow.HTML = "→" +macro.reg.HTML = "®" +macro.return = "Returns" +macro.starslash = "\\c{*/}" +macro.begincomment = "\\c{/*}" +macro.endcomment = "\\c{*/}" +macro.uuml.HTML = "ü" +macro.mdash.HTML = "—" +macro.pi.HTML = "Π" +macro.beginqdoc.HTML = "/*!" +macro.endqdoc.HTML = "*/" +macro.borderedimage = "\\div {class=\"border\"} \\image \1\n\\enddiv" + +macro.beginfloatleft.HTML = "
" +macro.beginfloatright.HTML = "
" +macro.endfloat.HTML = "
" +macro.clearfloat.HTML = "
" +macro.emptyspan.HTML = "" + +# Embed YouTube content by video ID - Example: \youtube dQw4w9WgXcQ +# Also requires a .jpg thumbnail for offline docs. In .qdocconf, add: +# +# HTML.extraimages += images/dQw4w9WgXcQ.jpg +# qhp.ProjectName.extraFiles += images/dQw4w9WgXcQ.jpg +# +macro.youtube.HTML = "
\n\n" \ + "
\n" diff --git a/doc/config/qbs-project.qdocconf b/doc/config/qbs-project.qdocconf new file mode 100644 index 00000000..bab672e1 --- /dev/null +++ b/doc/config/qbs-project.qdocconf @@ -0,0 +1,37 @@ +include($QT_INSTALL_DOCS/global/qt-cpp-defines.qdocconf) +include($QT_INSTALL_DOCS/global/compat.qdocconf) +include($QT_INSTALL_DOCS/global/fileextensions.qdocconf) + +project = "Qbs" +description = "Qbs Manual" + +headerdirs = +sourcedirs = .. +imagedirs = ../images ../templates/images +exampledirs = .. + +include(macros.qdocconf) +sources.fileextensions = "*.qdoc" + + +qhp.projects = Qbs +qhp.Qbs.file = qbs.qhp +qhp.Qbs.namespace = org.qt-project.qbs.$QBS_VERSION_TAG +qhp.Qbs.virtualFolder = doc +qhp.Qbs.indexTitle = Qbs +qhp.Qbs.filterAttributes = qbs $QBS_VERSION +qhp.Qbs.customFilters.Qbs.name = Qbs $QBS_VERSION +qhp.Qbs.customFilters.Qbs.filterAttributes = qbs $QBS_VERSION +qhp.Qbs.indexRoot = + +qhp.Qbs.subprojects = manual +qhp.Qbs.subprojects.manual.title = Qbs Manual +qhp.Qbs.subprojects.manual.indexTitle = Qbs Manual +qhp.Qbs.subprojects.manual.type = manual + +# Doxygen compatibility commands +macro.see = "\\sa" +macro.function = "\\fn" + +navigation.homepage = "Qbs Manual" +buildversion = "Qbs $QBS_VERSION" diff --git a/doc/config/style/qt5-sidebar.html b/doc/config/style/qt5-sidebar.html new file mode 100644 index 00000000..3df5d1bb --- /dev/null +++ b/doc/config/style/qt5-sidebar.html @@ -0,0 +1,18 @@ +
+
+ +

Qbs Manual

+
+ diff --git a/doc/doc.pri b/doc/doc.pri new file mode 100644 index 00000000..cd05a945 --- /dev/null +++ b/doc/doc.pri @@ -0,0 +1,18 @@ +include(../src/install_prefix.pri) + +include(doc_shared.pri) + +DOC_OUTDIR_POSTFIX = /html +DOC_HTML_INSTALLDIR = $$QBS_INSTALL_PREFIX/share/doc/qbs +DOC_QCH_OUTDIR = $$OUT_PWD/doc +DOC_QCH_INSTALLDIR = $$QBS_INSTALL_PREFIX/share/doc/qbs + +include(doc_targets.pri) + +fixnavi.commands = \ + cd $$shell_path($$PWD) && \ + 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 new file mode 100644 index 00000000..8d7da8d0 --- /dev/null +++ b/doc/doc.qbs @@ -0,0 +1,111 @@ +import qbs 1.0 +import qbs.File +import qbs.FileInfo +import qbs.Probes + +Project { + references: ["man/man.qbs"] + + Product { + name: "qbs documentation" + builtByDefault: false + type: Qt.core.config.contains("cross_compile") ? + ["qbsdoc.qdoc-html-fixed"] : [ "qbsdoc.qdoc-html-fixed", "qch"] + + property string fixedHtmlDir: FileInfo.joinPaths(buildDirectory, "qdoc-html-fixed") + Depends { name: "Qt.core" } + Depends { name: "qbsbuildconfig" } + Depends { name: "qbsversion" } + + Probes.BinaryProbe { + id: pythonProbe + names: ["python3", "python"] // on Windows, there's no python3 + } + property string _pythonExe: pythonProbe.found ? pythonProbe.filePath : undefined + + files: [ + "../README.md", + "../CONTRIBUTING.md", + "classic.css", + "external-resources.qdoc", + "fixnavi.pl", + "howtos.qdoc", + "qbs.qdoc", + "qbs-online.qdocconf", + "config/*.qdocconf", + "config/style/qt5-sidebar.html", + "appendix/**/*", + "reference/**/*", + "templates/**/*", + "images/**", + "targets/**", + ] + Group { + name: "main qdocconf file" + files: "qbs.qdocconf" + fileTags: "qdocconf-main" + } + Group { + name: "fix-imports script" + files: ["fix-qmlimports.py"] + fileTags: ["qbsdoc.fiximports"] + } + + property string versionTag: qbsversion.version.replace(/\.|-/g, "") + Qt.core.qdocEnvironment: [ + "QBS_VERSION=" + qbsversion.version, + "SRCDIR=" + path, + "QT_INSTALL_DOCS=" + Qt.core.docPath, + "QBS_VERSION_TAG=" + versionTag + ] + + Rule { + inputs: ["qdoc-png"] + explicitlyDependsOn: ["qbsdoc.fiximports"] + multiplex: true + outputFileTags: ["qdoc-html", "qbsdoc.dummy"] // TODO: Hack. Rule injection to the rescue? + outputArtifacts: [{filePath: "dummy", fileTags: ["qbsdoc.dummy"]}] + prepare: { + if (!product._pythonExe) + throw "Python executable was not found"; + var scriptPath = explicitlyDependsOn["qbsdoc.fiximports"][0].filePath; + var htmlDir = FileInfo.path(FileInfo.path(inputs["qdoc-png"][0].filePath)); + var fixCmd = new Command(product._pythonExe, [scriptPath, htmlDir]); + fixCmd.description = "fixing bogus QML import statements"; + return [fixCmd]; + } + } + + Rule { + inputs: ["qdoc-html"] + Artifact { + filePath: FileInfo.joinPaths(product.fixedHtmlDir, input.fileName) + fileTags: ["qbsdoc.qdoc-html-fixed"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); } + return [cmd]; + } + } + + Group { + fileTagsFilter: ["qbsdoc.qdoc-html-fixed"] + qbs.install: qbsbuildconfig.installHtml + qbs.installDir: qbsbuildconfig.docInstallDir + qbs.installSourceBase: product.fixedHtmlDir + } + Group { + fileTagsFilter: ["qdoc-css", "qdoc-png"] + qbs.install: qbsbuildconfig.installHtml + qbs.installDir: qbsbuildconfig.docInstallDir + qbs.installSourceBase: Qt.core.qdocOutputDir + } + Group { + fileTagsFilter: ["qch"] + qbs.install: qbsbuildconfig.installQch + qbs.installDir: qbsbuildconfig.docInstallDir + } + } +} diff --git a/doc/doc_shared.pri b/doc/doc_shared.pri new file mode 100644 index 00000000..3e4eccf4 --- /dev/null +++ b/doc/doc_shared.pri @@ -0,0 +1,14 @@ +include(../qbs_version.pri) + +qbsdoc_version.name = QBS_VERSION +qbsdoc_version.value = $$QBS_VERSION +qbsdoc_versiontag.name = QBS_VERSION_TAG +qbsdoc_versiontag.value = $$replace(QBS_VERSION, "[-.]", ) +qbsdoc_qtdocs.name = QT_INSTALL_DOCS +qbsdoc_qtdocs.value = $$[QT_INSTALL_DOCS/src] +QDOC_ENV += qbsdoc_version qbsdoc_versiontag qbsdoc_qtdocs + +build_online_docs: \ + DOC_FILES += $$PWD/qbs-online.qdocconf +else: \ + DOC_FILES += $$PWD/qbs.qdocconf diff --git a/doc/doc_targets.pri b/doc/doc_targets.pri new file mode 100644 index 00000000..0636be2b --- /dev/null +++ b/doc/doc_targets.pri @@ -0,0 +1,86 @@ +# Creates targets for building documentation +# (adapted from qt_docs.prf) +# +# Usage: Define variables (details below) and include this pri file afterwards. +# +# QDOC_ENV - environment variables to set for the qdoc call (see example below) +# DOC_INDEX_PATHS - list of paths where qdoc should search for index files of dependent +# modules (Qt index path is included by default) +# DOC_FILES - list of qdocconf files +# DOC_OUTDIR_POSTFIX - html is generated in $$OUT_PWD/$$DOC_OUTDIR_POSTFIX +# DOC_HTML_INSTALLDIR - path were to install the directory of html files +# DOC_QCH_OUTDIR - path where to generated the qch files +# DOC_QCH_INSTALLDIR - path where to install the qch files +# DOC_TARGET_PREFIX - prefix for generated target names +# +# Example for QDOC_ENV: +# ver.name = VERSION +# ver.value = 1.0.2 +# foo.name = FOO +# foo.value = foo +# QDOC_ENV = ver foo + +isEmpty(DOC_FILES): error("Set DOC_FILES before including doc_targets.pri") +isEmpty(DOC_HTML_INSTALLDIR): error("Set DOC_HTML_INSTALLDIR before including doc_targets.pri") +isEmpty(DOC_QCH_OUTDIR): error("Set DOC_QCH_OUTDIR before including doc_targets.pri") +isEmpty(DOC_QCH_INSTALLDIR): error("Set DOC_QCH_INSTALLDIR before including doc_targets.pri") + +QT_TOOL_ENV = $$QDOC_ENV +qtPrepareTool(QDOC, qdoc) +QT_TOOL_ENV = + +!build_online_docs: qtPrepareTool(QHELPGENERATOR, qhelpgenerator) + +DOCS_BASE_OUTDIR = $$OUT_PWD/doc +DOC_INDEXES += -indexdir $$shell_quote($$[QT_INSTALL_DOCS]) +for (index_path, DOC_INDEX_PATHS): \ + DOC_INDEXES += -indexdir $$shell_quote($$index_path) + +DTP = $$DOC_TARGET_PREFIX +for (doc_file, DOC_FILES) { + !exists($$doc_file): error("Cannot find documentation specification file $$doc_file") + DOC_TARGET = $$replace(doc_file, ^(.*/)?(.*)\\.qdocconf$, \\2) + DOC_TARGETDIR = $$DOC_TARGET + DOC_OUTPUTDIR = $${DOCS_BASE_OUTDIR}/$${DOC_TARGETDIR}$${DOC_OUTDIR_POSTFIX} + + $${DTP}html_docs_$${DOC_TARGET}.commands = $$QDOC -outputdir $$shell_quote($$DOC_OUTPUTDIR) $$doc_file $$DOC_INDEXES + QMAKE_EXTRA_TARGETS += $${DTP}html_docs_$${DOC_TARGET} + + !isEmpty($${DTP}html_docs.commands): $${DTP}html_docs.commands += && + $${DTP}html_docs.commands += $$eval($${DTP}html_docs_$${DOC_TARGET}.commands) + + $${DTP}inst_html_docs.files += $$DOC_OUTPUTDIR + + !build_online_docs { + $${DTP}qch_docs_$${DOC_TARGET}.commands = $$QHELPGENERATOR $$shell_quote($$DOC_OUTPUTDIR/$${DOC_TARGET}.qhp) -o $$shell_quote($$DOC_QCH_OUTDIR/$${DOC_TARGET}.qch) + $${DTP}qch_docs_$${DOC_TARGET}.depends = $${DTP}html_docs_$${DOC_TARGET} + QMAKE_EXTRA_TARGETS += $${DTP}qch_docs_$${DOC_TARGET} + + !isEmpty($${DTP}qch_docs.commands): $${DTP}qch_docs.commands += && + $${DTP}qch_docs.commands += $$eval($${DTP}qch_docs_$${DOC_TARGET}.commands) + + $${DTP}inst_qch_docs.files += $$DOC_QCH_OUTDIR/$${DOC_TARGET}.qch + } +} + +!build_online_docs { + $${DTP}qch_docs.depends = $${DTP}html_docs + $${DTP}inst_qch_docs.path = $$DOC_QCH_INSTALLDIR + $${DTP}inst_qch_docs.CONFIG += no_check_exist no_default_install no_build + install_$${DTP}docs.depends = install_$${DTP}inst_qch_docs + $${DTP}docs.depends = $${DTP}qch_docs + INSTALLS += $${DTP}inst_qch_docs + QMAKE_EXTRA_TARGETS += $${DTP}qch_docs install_$${DTP}docs +} else { + $${DTP}docs.depends = $${DTP}html_docs +} + +$${DTP}inst_html_docs.path = $$DOC_HTML_INSTALLDIR +$${DTP}inst_html_docs.CONFIG += no_check_exist no_default_install directory +INSTALLS += $${DTP}inst_html_docs +install_$${DTP}docs.depends += install_$${DTP}inst_html_docs + +QMAKE_EXTRA_TARGETS += $${DTP}html_docs $${DTP}docs + +unset(DTP) + diff --git a/doc/external-resources.qdoc b/doc/external-resources.qdoc new file mode 100644 index 00000000..00b7bf97 --- /dev/null +++ b/doc/external-resources.qdoc @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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$ +** +****************************************************************************/ + +/*! + \externalpage https://developer.apple.com/library/content/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html#//apple_ref/doc/uid/TP40012302-CH7-SW28 + \title Adopt the @2x Naming Convention +*/ + +/*! + \externalpage https://login.qt.io/ + \title Qt Account +*/ + +/*! + \externalpage https://www.qt.io/ide/ + \title Qt Creator +*/ + +/*! + \externalpage https://www.qt.io/download/ + \title Qt SDK +*/ + +/*! + \externalpage https://doc.qt.io/qt-5/licensing.html + \title Qt Licensing +*/ + +/*! + \externalpage https://www.gnu.org/licenses/gpl-2.0.html + \title GNU General Public License, version 2 +*/ + +/*! + \externalpage http://www.linfo.org/bsdlicense.html + \title BSD +*/ + +/*! + \externalpage https://chocolatey.org/packages/qbs + \title Chocolatey +*/ + +/*! + \externalpage https://www.macports.org/ports.php?by=name&substr=qbs + \title MacPorts +*/ + +/*! + \externalpage https://brew.sh/ + \title Homebrew +*/ + +/*! + \externalpage http://www.typescriptlang.org + \title TypeScript +*/ + +/*! + \externalpage http://www.typescriptlang.org/docs/handbook/compiler-options.html + \title Compiler Options +*/ + +/*! + \externalpage https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dyld.1.html + \title DYLD documentation +*/ + +/*! + \externalpage https://dmgbuild.readthedocs.io/en/latest/settings.html#background + \title dmgbuild - Settings +*/ + +/*! + \externalpage http://www.jrsoftware.org/isinfo.php + \title Inno Setup +*/ + +/*! + \externalpage http://nodejs.org + \title Node.js +*/ + +/*! + \externalpage http://wixtoolset.org + \title Windows Installer XML Toolset +*/ + +/*! + \externalpage https://clang.llvm.org/docs/JSONCompilationDatabase.html + \title JSON Compilation Database Format Specification +*/ + +/*! + \externalpage https://github.com/protocolbuffers/protobuf + \title protoc +*/ + +/*! + \externalpage nolink + \title macOS + \internal +*/ + +/*! + \externalpage https://ccache.samba.org/ + \title ccache +*/ + +/*! + \externalpage https://ccache.samba.org/manual.html#_precompiled_headers + \title ccache documentation about precompiled headers +*/ diff --git a/doc/fix-qmlimports.py b/doc/fix-qmlimports.py new file mode 100755 index 00000000..bb022d25 --- /dev/null +++ b/doc/fix-qmlimports.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +############################################################################# +## +## 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$ +## +############################################################################# +from __future__ import print_function +import os +import glob +import errno +import sys +import argparse +import shutil +import urllib +from subprocess import Popen, PIPE +import re +from collections import Counter + +import platform +useShell = (platform.system() == 'Windows') + +gotSoup = True +try: + from bs4 import BeautifulSoup +except ImportError: + print('Warning: Failed to import BeautifulSoup. Some functionality is disabled.', + file=sys.stderr) + gotSoup = False + +qmlTypeString = ' QML Type' + +# Modifies a QML Type reference page to look like a generic +# JavaScript reference - Removes the 'QML Type' strings from +# the titles as well as the import statement information. +# This is used in the Installer Framework docs, which contain +# JS reference documentation generated using commands specific +# to documenting QML code. +# +# Parameters: a Beautiful Soup object constructed with an opened +# html file to process. +# +# Returns True if the element tree was modified, False otherwise +def modifyQMLReference(soup): + pageTitle = soup.head.title.string + soup.head.title.string = pageTitle.replace(qmlTypeString, '') + for t in soup.find_all('h1', class_='title'): + t.string = t.string.replace(qmlTypeString, '') + + for table in soup.find_all('table'): + td = table.find('td') + if td and td.string: + if 'Import Statement:' in td.string: + td.parent.extract() + return True + return False + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description = """Removes bogus import statements from the offline docs""") + parser.add_argument('outputdir', + help = 'output directory of the generated html files') + args = parser.parse_args() + + if not gotSoup: + print('Error: This script requires the Beautiful Soup library.', file=sys.stderr) + sys.exit(1) + + if not os.path.isdir(args.outputdir): + print('Error: No such directory:', args.outputdir, file=sys.stderr) + sys.exit(1) + + # compile a list of all html files in the outputdir + htmlFiles = [] + for f in os.listdir(args.outputdir): + fullPath = os.path.join(args.outputdir, f) + if os.path.isdir(fullPath): + continue + if os.path.splitext(f)[1] == '.html': + htmlFiles.append(fullPath) + + sys.stdout.flush() + modified = {} + pre_blocks = {} + fileCount = 0 + progStep = max(16, len(htmlFiles)) / 16 + for html in htmlFiles: + fileCount += 1 + if not (fileCount % progStep): + print('.', end='') + sys.stdout.flush() + with open(html, 'r+', encoding='utf8') as file_: + try: + soup = BeautifulSoup(file_, 'lxml') + actions = [] + val = 0 + if modifyQMLReference(soup): + actions.append('Removed QML type info') + for a in actions: + modified[a] = modified.get(a, 0) + 1 + if actions: + file_.seek(0) + file_.write(str(soup)) + file_.truncate() + file_.close() + except (AttributeError, KeyError): + print('\nFailed to parse', html, ':', + sys.exc_info()[0], file=sys.stderr) + except IOError as e: + print('\nError:', e, file=sys.stderr) + except ValueError as e: + print('\nError:', e, file=sys.stderr) + if 'lxml' in str(e): + print('(If using pip, try \"pip install lxml\")', file=sys.stderr) + quit(1) + for k, v in modified.items(): + print ('\n\t', k, 'in %d files' % v, end='') + pb = pre_blocks.get(k, 0) + if pb: + print (' (', pb, '
 blocks)', sep='', end='')
+    print('\n')
diff --git a/doc/fixnavi.pl b/doc/fixnavi.pl
new file mode 100644
index 00000000..3c4dc134
--- /dev/null
+++ b/doc/fixnavi.pl
@@ -0,0 +1,182 @@
+#! /usr/bin/perl -w
+
+#############################################################################
+##
+## 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$
+##
+#############################################################################
+use strict;
+
+my @files = ();
+my %defines = ();
+for (@ARGV) {
+    if (/^-D(.*)$/) {
+        $defines{$1} = 1;
+    } elsif (/^-/) {
+        printf STDERR "Unknown option '".$_."'\n";
+        exit 1;
+    } else {
+        push @files, $_;
+    }
+}
+
+int(@files) or die "usage: $0 [-D]... \n";
+
+my @toc = ();
+my %title2page = ();
+my $doctitle = "";
+my $curpage = "";
+my $intoc = 0;
+my %prev_skips = ();
+my %next_skips = ();
+my %define_skips = ();
+my %polarity_skips = ();
+my $prev_skip = "";
+my $next_skip = "";
+my $define_skip = "";
+my $polarity_skip = 0;
+for my $file (@files) {
+    my $skipping = 0; # no nested ifs!
+    open FILE, $file or die "File $file cannot be opened.\n";
+    while () {
+        if (/^\h*\\if\h+defined\h*\(\h*(\H+)\h*\)/) {
+            $skipping = !defined($defines{$1});
+            if (!$intoc) {
+                $define_skip = $1;
+                $polarity_skip = $skipping;
+            }
+        } elsif (/^\h*\\else/) {
+            $skipping = 1 - $skipping;
+        } elsif (/^\h*\\endif/) {
+            $skipping = 0;
+        } elsif (keys(%title2page) == 1 && /^\h*\\list/) {
+            $intoc++;
+        } elsif (!$intoc) {
+            if ($skipping && /^\h*\\previouspage\h+(\H+)/) {
+                $prev_skip = $1;
+            } elsif ($skipping && /^\h*\\nextpage\h+(\H+)/) {
+                $next_skip = $1;
+            } elsif (/^\h*\\page\h+(\H+)/) {
+                $curpage = $1;
+            } elsif (/^\h*\\title\h+(.+)$/) {
+                if ($curpage eq "") {
+                    die "Title '$1' appears in no \\page.\n";
+                }
+                if (length($define_skip)) {
+                     $define_skips{$1} = $define_skip;
+                     $polarity_skips{$1} = $polarity_skip;
+                     $prev_skips{$1} = $prev_skip;
+                     $next_skips{$1} = $next_skip;
+                     $define_skip = $prev_skip = $next_skip = "";
+                }
+                $title2page{$1} = $curpage;
+                $doctitle = $1 if (!$doctitle);
+                $curpage = "";
+            }
+        } else {
+            if (/^\h*\\endlist/) {
+                $intoc--;
+            } elsif (!$skipping && /^\h*\\(?:o|li)\h+\\l\h*{(.*)}$/) {
+                push @toc, $1;
+            }
+        }
+    }
+    close FILE;
+}
+
+my %prev = ();
+my %next = ();
+my $last = $doctitle;
+for my $title (@toc) {
+    $next{$last} = $title2page{$title};
+    $prev{$title} = $title2page{$last};
+    $last = $title;
+}
+
+for my $file (@files) {
+    open IN, $file or die "File $file cannot be opened a second time?!\n";
+    open OUT, '>'.$file.".out" or die "File $file.out cannot be created.\n";
+    my $cutting = 0;
+    while () {
+        if (!$cutting) {
+            if (/^\h*\\contentspage/) {
+                $cutting = 1;
+            }
+        } else {
+            if (/^\h*\\title\h+(.+)$/) {
+                if (defined($define_skips{$1})) {
+                    print OUT "    \\if defined(".$define_skips{$1}.")\n";
+                    if ($polarity_skips{$1}) {
+                        print OUT "    \\previouspage ".$prev_skips{$1} if ($prev_skips{$1});
+                        print OUT "    \\else\n";
+                    }
+                }
+                print OUT "    \\previouspage ".$prev{$1} if ($prev{$1});
+                if (defined($define_skips{$1})) {
+                    if (!$polarity_skips{$1}) {
+                        print OUT "    \\else\n";
+                        print OUT "    \\previouspage ".$prev_skips{$1} if ($prev_skips{$1});
+                    }
+                    print OUT "    \\endif\n";
+                }
+                print OUT "    \\page ".$title2page{$1};
+                if (defined($define_skips{$1})) {
+                    print OUT "    \\if defined(".$define_skips{$1}.")\n";
+                    if ($polarity_skips{$1}) {
+                        print OUT "    \\nextpage ".$next_skips{$1} if ($next_skips{$1});
+                        print OUT "    \\else\n";
+                    }
+                }
+                print OUT "    \\nextpage ".$next{$1} if ($next{$1});
+                if (defined($define_skips{$1})) {
+                    if (!$polarity_skips{$1}) {
+                        print OUT "    \\else\n";
+                        print OUT "    \\nextpage ".$next_skips{$1} if ($next_skips{$1});
+                    }
+                    print OUT "    \\endif\n";
+                }
+                print OUT "\n";
+                $cutting = 0;
+            } else {
+                next;
+            }
+        }
+        print OUT $_;
+    }
+    close OUT;
+    close IN;
+    rename($file.".out", $file) or die "Cannot replace $file with new version.\n";
+}
diff --git a/doc/howtos.qdoc b/doc/howtos.qdoc
new file mode 100644
index 00000000..d216ea00
--- /dev/null
+++ b/doc/howtos.qdoc
@@ -0,0 +1,748 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 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
+    \previouspage custom-modules.html
+    \nextpage reference.html
+    \page howtos.html
+
+    \title How-tos
+
+    This page provides concrete instructions for common scenarios.
+
+    \list
+    \li \l{How do I build a Qt-based project?}
+    \li \l{How do I make my app build against my library?}
+    \li \l{How do I build release with debug information?}
+    \li \l{How do I separate and install debugging symbols?}
+    \li \l{How do I use precompiled headers?}
+    \li \l{How do I make use of rpaths?}
+    \li \l{How do I make sure my generated sources are getting compiled?}
+    \li \l{How do I run my autotests?}
+    \li \l{How do I use ccache?}
+    \li \l{How do I create a module for a third-party library?}
+    \li \l{How do I build against libraries that provide pkg-config files?}
+    \li \l{How do I create application bundles and frameworks on iOS, macOS, tvOS, and watchOS?}
+    \li \l{How do I apply C/C++ preprocessor macros to only a subset of the files in my product?}
+    \li \l{How do I make the state of my Git repository available to my source files?}
+    \li \l{How do I limit the number of concurrent jobs for the linker only?}
+    \li \l{How do I add QML files to a project?}
+    \li \l{How do I define a reusable Group of files that can be included in other \QBS files?}
+    \li \l{How do I print the value of a property?}
+    \li \l{How do I debug \QBS scripts?}
+    \endlist
+
+    \section1 How do I build a Qt-based project?
+
+    First of all, your project files need to declare \l{Depends}{dependencies}
+    on \l{Qt} modules.
+
+    To build the project, you need a matching \e profile. The following commands
+    set up and use a Qt-specific profile:
+    \code
+    $ qbs setup-qt /usr/bin/qmake qt
+    $ cd my_project
+    $ qbs profile:qt
+    \endcode
+    If you plan to use this profile a lot, consider making it the default one:
+    \code
+    $ qbs config defaultProfile qt
+    $ cd my_project
+    $ qbs
+    \endcode
+    See \l{Managing Qt Versions} for more details.
+    \note These instructions are only relevant for building from the command line.
+    If you use Qt Creator, profiles are set up automatically from the information in the Kit.
+
+    \section1 How do I make my app build against my library?
+
+    This is achieved by introducing a \e dependency between the two products using the
+    \l{Depends} item. Here is a simple, but complete example:
+    \code
+    Project {
+        CppApplication {
+            name : "the-app"
+            files : [ "main.cpp" ]
+            Depends { name: "the-lib" }
+        }
+        DynamicLibrary {
+            name: "the-lib"
+            Depends { name: "cpp" }
+            files: [
+                "lib.cpp",
+                "lib.h",
+            ]
+            Export {
+                Depends { name: "cpp" }
+                cpp.includePaths: [product.sourceDirectory]
+           }
+        }
+    }
+    \endcode
+
+    The product \c the-lib is a dynamic library. It expects other products to build against it, and
+    for that purpose, it exports an include path (via an \l{Export} item), so that the
+    source files in these products can include the library's header file.
+
+    The product \c the-app is an application that expresses its intent to link against \c the-lib
+    by declaring a dependency on it. Now \c main.cpp can include \c lib.h (because of the exported
+    include path) and the application binary will link against the library (because the linker
+    \l{Rule}{rule} in the \l{cpp} module considers library dependencies as inputs).
+    \note In a non-trivial project, the two products would not be defined in the same file.
+          Instead, you would put them into files of their own and use the
+          \l{Project::references}{Project.references} property to pull them into the project.
+          The product definitions would stay exactly the same. In particular, their location
+          in the project tree is irrelevant to the relationship between them.
+
+    \section2 Choosing Between Dynamic and Statically-built Qt Projects
+
+    To build \c "the-lib" as either a dynamic or static library, depending on
+    how Qt was built, you can use the following code:
+
+    \code
+    Product {
+        name: "the-lib"
+        type: Qt.core.staticBuild ? "staticlibrary" : "dynamiclibrary"
+
+        Depends { name: "Qt.core" }
+        // ...
+    }
+    \endcode
+
+    \section1 How do I build release with debug information?
+
+    You can simply use the \c{"profiling"} \l{qbs::buildVariant}{qbs.buildVariant}:
+    \code
+    qbs build qbs.buildVariant:profiling
+    \endcode
+
+    \section1 How do I separate and install debugging symbols?
+
+    First, you need to set the \l{cpp::debugInformation}{cpp.debugInformation} and
+    \l{cpp::separateDebugInformation}{cpp.separateDebugInformation}
+    properties to \c true or use some conditional expression in your product:
+    \code
+    CppApplication {
+        // ...
+        cpp.debugInformation: qbs.buildVariant !== "release"
+        cpp.separateDebugInformation: true
+    }
+    \endcode
+
+    Now, you can install your \l{Application}{application}, \l{DynamicLibrary}{dynamic library}
+    or \l{LoadableModule}{loadable module} among with its debugging symbols as follows:
+    \code
+    CppApplication {
+        // ...
+        install: true
+        installDir: "bin"
+        installDebugInformation: true
+        debugInformationInstallDir: "bin"
+    }
+    \endcode
+
+    If you are not using \l{List of Convenience Items}{convenience items},
+    you can install debug symbols manually using the \l{Group} item. If the
+    \l{cpp::separateDebugInformation}{cpp.separateDebugInformation} property is set to \c true,
+    \QBS will create debugging symbols with the corresponding file tags
+    \c "debuginfo_app" (for an application), \c "debuginfo_dll" (for a dynamic library),
+    or \c "debuginfo_loadablemodule" (for a macOS plugin).
+
+    \code
+    Product {
+        type: "application"
+        Depends { name: "cpp" }
+        cpp.debugInformation: qbs.buildVariant !== "release"
+        cpp.separateDebugInformation: true
+        Group {
+            fileTagsFilter: cpp.separateDebugInformation ? ["debuginfo_app"] : []
+            qbs.install: true
+            qbs.installDir: "bin"
+            qbs.installSourceBase: buildDirectory
+        }
+    }
+    \endcode
+
+    If you're building a shared library, you need to use the \c "debuginfo_dll" tag instead:
+    \code
+    Product {
+        type: "dynamic_library"
+        // ...
+        Group {
+            fileTagsFilter: cpp.separateDebugInformation ? ["debuginfo_dll"] : []
+            qbs.install: true
+            qbs.installDir: "lib"
+            qbs.installSourceBase: buildDirectory
+        }
+    }
+    \endcode
+
+    If you're building a macOS plugin, you need to use the \c "debuginfo_loadablemodule"
+    tag instead:
+    \code
+    Product {
+        type: "loadablemodule"
+        // ...
+        Group {
+            fileTagsFilter: cpp.separateDebugInformation ? ["debuginfo_loadablemodule"] : []
+            qbs.install: true
+            qbs.installDir: "PlugIns"
+            qbs.installSourceBase: buildDirectory
+        }
+    }
+    \endcode
+
+    \section1 How do I use precompiled headers?
+
+    If you use a \l Group item to add a precompiled header file to a product
+    and mark it with the \l{filetags-cpp}{relevant file tag} (\c c_pch_src,
+    \c cpp_pch_src, \c objc_pch_src, or \c objcpp_pch_src), it is used
+    automatically.
+
+    Only one precompiled header is allowed per product and language.
+
+    For example:
+
+    \code
+    CppApplication {
+        name: "the-app"
+        files: ["main.cpp"]
+
+        Group {
+            files: ["precompiled-header.pch"]
+            fileTags: ["cpp_pch_src"]
+        }
+    }
+    \endcode
+
+    \section1 How do I make use of rpaths?
+
+    rpath designates the run-time search path used by the dynamic linker when loading
+    libraries on UNIX platforms. This concept does not apply to Windows.
+
+    Suppose you have a project with two dynamic library products \c LibraryA and \c LibraryB
+    and one dependent application product. Also, \c LibraryB depends on \c LibraryA. The
+    application is installed to the \c bin folder and the libraries are installed to the
+    \c lib folder next to the \c bin folder. You want the application to be able to find the
+    dependent libraries relative to its own location. This can be achieved by usage of the
+    \l{cpp::rpaths}{cpp.rpaths} property.
+
+    First, you need to set \l{cpp::rpaths}{cpp.rpaths} in your libraries so they can
+    find dependent libraries in the same folder where they are located. This can be
+    done as follows:
+
+    \snippet ../examples/rpaths/rpaths.qbs 0
+
+    We are setting \l{cpp::rpaths}{cpp.rpaths} to \l{cpp::rpathOrigin}{cpp.rpathOrigin} which
+    expands to \c "$ORIGIN" on Linux and to \c "@loader_path" on macOS.
+
+    On macOS you also need to set \l{cpp::sonamePrefix}{cpp.sonamePrefix} to \c "@rpath" to
+    tell the dynamic linker to use RPATHs when loading this library.
+
+    \c LibraryB looks exactly the same:
+
+    \snippet ../examples/rpaths/rpaths.qbs 1
+
+    In a real project, it might be a good idea to move common properties to some base item
+    and inherit it in library items.
+
+    The application item is a bit different. It sets \l{cpp::rpaths}{cpp.rpaths} to the
+    \c "lib" folder which is located one level up from the \c bin folder:
+
+    \snippet ../examples/rpaths/rpaths.qbs 2
+
+    \section1 How do I make sure my generated sources are getting compiled?
+
+    The rules in a \QBS project do not care whether its inputs are actual source files
+    listed on the right-hand side of a \l{Product::files}{files} property or artifacts
+    that were generated by another rule. For instance, the C++ compiler rule considers
+    all input files of type "cpp", no matter how they got into the product. The following
+    example project demonstrates this. One of its source files exists in the repository,
+    the other one is generated at build time. Both are getting compiled the same way.
+    \note Do not try to add the generated files to a \c files property. Declaring them
+    as rule outputs is all that is needed to make \QBS know about them.
+    \code
+    import qbs.TextFile
+    CppApplication {
+        files: ["impl.cpp", "impl.h"]
+        cpp.includePaths: sourceDirectory
+        Rule {
+            multiplex: true
+            Artifact { filePath: "main.cpp"; fileTags: "cpp" }
+            prepare: {
+                var cmd = new JavaScriptCommand();
+                cmd.description = "generating " + output.fileName;
+                cmd.sourceCode = function() {
+                    var f = new TextFile(output.filePath, TextFile.WriteOnly);
+                    f.writeLine("#include ");
+                    f.writeLine("int main()");
+                    f.writeLine("{");
+                    f.writeLine("    return functionFromImpl();");
+                    f.writeLine("}");
+                    f.close();
+                };
+                return cmd;
+            }
+        }
+    }
+    \endcode
+
+    \section1 How do I run my autotests?
+
+    There are two simple things you need to do in your project. Firstly, you
+    mark your test executables as such. This is done by adding the tag \c{"autotest"}
+    to the product type:
+    \code
+    CppApplication {
+        name: "test1"
+        type: base.concat("autotest")
+        // ...
+    }
+    \endcode
+    The second step is to instantiate an \l AutotestRunner product in your project:
+    \code
+    Project {
+        // ...
+        AutotestRunner { name: "run_my_tests" }
+    }
+    \endcode
+    Building an AutotestRunner product does not produce artifacts, but triggers execution of all
+    applications whose products are tagged as autotests:
+    \code
+    $ qbs -p run_my_tests
+    test1: PASS
+    test2: PASS
+    test3: FAIL
+    ...
+    \endcode
+    See the \l{AutotestRunner}{AutotestRunner documentation} for how to fine-tune the behavior.
+
+    \section1 How do I use ccache?
+
+    \l ccache is a popular C/C++ compiler cache on Unix to speed up compiling the
+    same content multiple times.
+
+    \QBS excels at tracking dependencies and avoiding needless recompilations, so
+    for linear development of one project and configuration using ccache
+    has little benefit. But if you switch between revisions of a project,
+    or build the same project with different configurations, a global cache like
+    ccache can speed up compilations significantly.
+
+    ccache can be used by setting up symbolic links to compiler executables
+    (such as \c g++, \c gcc) in the file system. In this setup, the use of ccache is
+    transparent to \QBS. If you prefer to call ccache explicitly, you should
+    set \l{cpp::compilerWrapper}{cpp.compilerWrapper} to \c ccache.
+
+    \note Using precompiled headers might prevent ccache from actually
+    using cached results. To work around this, you can set
+    \c{sloppiness=pch_defines,time_macros} in your local ccache options.
+    See the \l{ccache documentation about precompiled headers} for further details.
+
+    \section1 How do I create a module for a third-party library?
+
+    If you have pre-built binary files in your source tree, you can create
+    modules for them and then introduce dependencies between your project and
+    the modules to pull in the functionality of a third-party library.
+
+    Create the following folder structure to store the module files:
+
+    \code
+    $projectroot/modules/ThirdParty
+    \endcode
+
+    Then create a file in the directory that specifies the module properties
+    for each supported toolchain. The filename must have the \c .qbs extension.
+    The module will be pulled in if a product declares a dependency on it.
+
+    In the following example, \c lib1.dylib is a multi-architecture library
+    containing both 32-bit and 64-bit code.
+
+    \code
+    ---ThirdParty.qbs---
+
+    Module {
+        Depends { name: "cpp" }
+        cpp.includePaths: ["/somewhere/include"]
+        Properties {
+            condition: qbs.targetOS.contains("android")
+            cpp.dynamicLibraries: ["/somewhere/android/" + Android.ndk.abi + "/lib1.so"]
+        }
+        Properties {
+            condition: qbs.targetOS.contains("macos")
+            cpp.dynamicLibraries: ["/somewhere/macos/lib1.dylib"]
+        }
+        Properties {
+            condition: qbs.targetOS.contains("windows") && qbs.architecture === "x86"
+            cpp.dynamicLibraries: ["/somewhere/windows_x86/lib1.lib"]
+        }
+        Properties {
+            condition: qbs.targetOS.contains("windows") && qbs.architecture === "x86_64"
+            cpp.dynamicLibraries: ["/somewhere/windows_x86_64/lib1.lib"]
+        }
+    }
+    \endcode
+
+    Finally, declare dependencies on \c ThirdParty in your project:
+
+    \code
+    CppApplication {
+        name: "the-app"
+        files: ["main.cpp"]
+        Depends { name: "ThirdParty" }
+    }
+    \endcode
+
+    \section1 How do I create application bundles and frameworks on iOS, macOS, tvOS, and watchOS?
+
+    Creating an application bundle or framework is achieved by introducing a
+    dependency on the \l{bundle} module and setting the \l{bundle::isBundle}
+    {bundle.isBundle} property to \c true.
+
+    Here is a simple example for an application:
+
+    \code
+    Application {
+        Depends { name: "cpp" }
+        Depends { name: "bundle" }
+        bundle.isBundle: true
+        name: "the-app"
+        files: ["main.cpp"]
+    }
+    \endcode
+
+    and for a framework:
+
+    \code
+    DynamicLibrary {
+        Depends { name: "cpp" }
+        Depends { name: "bundle" }
+        bundle.isBundle: true
+        name: "the-lib"
+        files: ["lib.cpp", "lib.h"]
+    }
+    \endcode
+
+    \QBS also supports building static frameworks. You can create one by
+    replacing the \l{DynamicLibrary} item with a \l{StaticLibrary} item in the
+    example above.
+
+    \note When using the \l{Application} item (or convenience items, such as
+    \l{CppApplication}, \l{DynamicLibrary}, and \l{StaticLibrary}), your
+    products will be built as bundles on Apple platforms by default (this
+    behavior is subject to change in a future release).
+
+    To explicitly control whether your product is built as a bundle, set the \c bundle.isBundle
+    property. Setting the \l{Product::}{consoleApplication} property of your
+    product will also influence whether your product is built as a bundle.
+
+    Building your application against your framework is the same as linking a normal dynamic or
+    static library; see the \l{How do I make my app build against my library?} section for an
+    example.
+
+    \section1 How do I build against libraries that provide pkg-config files?
+
+    Just add a \l Depends item that matches the name of the pkg-config module, and \QBS
+    will automatically employ \l{https://www.freedesktop.org/wiki/Software/pkg-config}{pkg-config}
+    to find the headers and libraries if no matching \QBS module can be found. For instance,
+    to build against the OpenSSL library, you would write this:
+    \code
+    Depends { name: "openssl" }
+    \endcode
+    That's it. The pkg-config behavior can be fine-tuned via the \l pkgconfig module,
+    but normally you will not need to pull it in explicitly.
+
+    Internally, this functionality is implemented via \l {Module Providers}
+
+    \section1 How do I apply C/C++ preprocessor macros to only a subset of the files in my product?
+
+    Use a \l{Group} item to define a subset of project files. To add
+    macros within the group, you need to use the \c outer.concat property,
+    because you are adding macros to those specified in the outer scope.
+
+    In the following example, \c MACRO_EVERYWHERE is defined for all files in
+    the \l{Product} unless a Group overrides the macro, whereas
+    \c MACRO_GROUP is only defined for \c groupFile.cpp.
+
+    \code
+    Product {
+        Depends { name: "cpp" }
+        cpp.defines: ["MACRO_EVERYWHERE"]
+        Group {
+            cpp.defines: outer.concat("MACRO_GROUP")
+            files: "groupFile.cpp"
+        }
+    }
+    \endcode
+
+    The \c cpp.defines statements inside a \c Group only apply to the files in
+    that \c Group, and therefore you cannot use a \c Group to include a bunch of
+    files and globally visible macros. The macros must be specified in a
+    \l{Properties} item at the same level as the \c Group if
+    they need to be visible to files outside the \c Group:
+
+    \code
+    Product {
+        Depends { name: "cpp" }
+        Group {
+            condition: project.supportMyFeature
+            files: "myFile.cpp"
+        }
+
+        property stringList commonDefines: ["ONE", "TWO"]
+
+        Properties {
+            condition: project.supportMyFeature
+            cpp.defines: commonDefines.concat("MYFEATURE_SUPPORTED")
+        }
+    }
+    \endcode
+
+    \section1 How do I make the state of my Git repository available to my source files?
+
+    Add a dependency to the \l{vcs} module to your product:
+    \code
+    CppApplication {
+        // ...
+        Depends { name: "vcs" }
+        // ...
+    }
+    \endcode
+    Your source files will now have access to a macro whose value is a string representing the
+    current Git or Subversion HEAD:
+    \code
+    #include 
+    #include 
+
+    int main()
+    {
+        std::cout << "I was built from " << VCS_REPO_STATE << std::endl;
+    }
+    \endcode
+
+    This value is also available via the \l{vcs::repoState}{vcs.repoState}
+    property.
+
+    \section1 How do I limit the number of concurrent jobs for the linker only?
+    \target job-pool-howto
+
+    While it is usually desirable to run as many compiler jobs as there are CPU cores,
+    the same is not true for linker jobs. The reason is that linkers are typically
+    I/O bound rather than CPU bound. When building large libraries, they also tend
+    to use up enormous amounts of memory. Therefore, we'd like to make sure that
+    only a few linkers are running at the same time without limiting other types
+    of jobs. In \QBS, this is achieved via \e{job pools}. There are several ways
+    to make use of them.
+
+    Firstly, you can provide a limit via the command line:
+    \code
+    $ qbs --job-limits linker:4
+    \endcode
+    The above call instructs \QBS to run at most four linker instances at the same
+    time, while leaving the general number of concurrent jobs at the default
+    value, which is derived from the number of CPU cores.
+    The \c linker string on the command line refers to the job pool of the same
+    name, which the \l{cpp-job-pools}{cpp module} assigns to all its commands that
+    invoke a linker.
+
+    Secondly, you can set a limit via the settings, either generally
+    or for a specific profile:
+    \code
+    $ qbs config preferences.jobLimit.linker 4
+    $ qbs config profiles.myprofile.preferences.jobLimit.linker 2
+    \endcode
+
+    And finally, you can also set the limit per project or per product, using a
+    \l JobLimit item:
+    \code
+    Product {
+        name: "my_huge_library"
+        JobLimit {
+            jobPool: "linker"
+            jobCount: 1
+        }
+        // ...
+    }
+    \endcode
+    The above construct ensures that this specific library is never linked at
+    the same time as any other binary in the project.
+
+    Job limits set on the command line override those from the settings, which in turn
+    override the ones defined within a project. Use the \c{--enforce-project-job-limits}
+    option to give the job limits defined via \c JobLimit items maximum precedence.
+
+    \section1 How do I add QML files to a project?
+
+    The simplest way to add QML files to a project is to add them to a
+    \l {The Qt Resource System}{resource file}:
+
+    \code
+    QtGuiApplication {
+        // ...
+
+        files: "main.cpp"
+
+        Group {
+            prefix: "qml/"
+            files: ["main.qml", "HomePage.qml"]
+            fileTags: ["qt.qml.qml", "qt.core.resource_data"]
+        }
+    }
+    \endcode
+
+    In the example above, we declare each QML file as having the
+    \l {filetags-qtcore}{"qt.core.resource_data"} file tag. This ensures
+    that it is added to a generated resource file.
+
+    \section1 How do I define a reusable Group of files that can be included in other \QBS files?
+
+    Suppose you have an application and tests for that application, and that
+    the project is structured in the following way:
+
+    \badcode
+    ├── app
+    │   ├── app.qbs
+    │   ├── ...
+    │   └── qml
+    │       └── ui
+    │           ├── AboutPopup.qml
+    │           └── ...
+    ├── my-project.qbs
+    └── tests
+        ├── tst_app.cpp
+        ├── ...
+        └── tests.qbs
+    \endcode
+
+    Both projects need access to the QML files used by the
+    application. To demonstrate how this can be done, we'll create a file
+    named \c qml-ui.qbs and put it in the \c app/qml/ui directory:
+
+    \code
+    Group {
+        prefix: path + "/"
+        fileTags: ["qt.qml.qml", "qt.core.resource_data"]
+        files: [
+            "AboutPopup.qml",
+            // ...
+        ]
+    }
+    \endcode
+
+    This Group is a variation of the one in the
+    \l {How do I add QML files to a project?}{section above}.
+
+    If no prefix is specified, the file names listed in the \c files property
+    are resolved relative to the \e importing product's (e.g. \c app.qbs)
+    directory. For that reason, we set the prefix to inform \QBS that the file
+    names should be resolved relative to the \e imported item instead:
+    \c qml-ui.qbs. Conveniently, this also means that we don't need to specify
+    the path prefix for each file.
+
+    The application can then import the file like so:
+
+    \code
+    import "qml/ui/qml-ui.qbs" as QmlUiFiles
+
+    QtGuiApplication {
+        // ...
+
+        files: "main.cpp"
+
+        QmlUiFiles {}
+    }
+    \endcode
+
+    The tests can use a relative path to import the file:
+
+    \code
+    import "../app/qml/ui/qml-ui.qbs" as QmlUiFiles
+
+    QtGuiApplication {
+        // ...
+
+        files: "tst_app.cpp"
+
+        QmlUiFiles {}
+    }
+    \endcode
+
+    \section1 How do I print the value of a property?
+
+    Use the \l {Console API}{console API}. For example, suppose your project
+    is not built the way you expect it to be, and you suspect that
+    \c qbs.targetOS has the wrong value:
+
+    \code
+    readonly property bool unix: qbs.targetOS.contains("unix")
+    \endcode
+
+    To find out the value of \c qbs.targetOS, use \c {console.info()}:
+
+    \code
+    readonly property bool unix: {
+        console.info("qbs.targetOS: " + qbs.targetOS)
+        return qbs.targetOS.contains("unix")
+    }
+    \endcode
+
+    It is also possible to throw an exception with the text saying what is wrong - this might
+    be useful if the property contains invalid or unsupported value:
+    \code
+    readonly property bool unix: {
+        if (qbs.targetOS.contains("darwin"))
+            throw "Apple platforms are not supported";
+        return qbs.targetOS.contains("unix")
+    }
+    \endcode
+
+    \section1 How do I debug \QBS scripts?
+
+    To debug the value of a specific property, see the \l{How do I print the value of a property}
+    section.
+
+    Similar debugging techniques could be used within \l{Rule}{Rules} or \c .js files.
+
+    It is also possible to increase \QBS' logging level using the \c --more-verbose (\c -v) option
+    of the \c{qbs build} command:
+
+    \code
+    qbs build -v config:release
+    \endcode
+
+    \QBS uses the Qt Categorized Logging system which allows to configure logging categories
+    in \l{https://doc.qt.io/qt-5/qloggingcategory.html#configuring-categories}{multiple ways}. For
+    example, to enable debug logging for the \c moduleloader category, use the following command:
+    \code
+    QT_LOGGING_RULES="qbs.moduleloader.debug=true" qbs resolve
+    \endcode
+
+    To list all the files in the project directory and show whether they are known to qbs in the
+    respective configuration, use the \c{qbs status} command:
+    \code
+    qbs status config:release
+    \endcode
+*/
diff --git a/doc/images/qbs-build-process.png b/doc/images/qbs-build-process.png
new file mode 100644
index 0000000000000000000000000000000000000000..960108e146a5e7287cdf8c1b6b6c134a889f3aa2
GIT binary patch
literal 5335
zcmZu#2UJtdw#I^pfJjk55D*RtN)x08M5-iosftLKD!oeyy@*l{O`4!ILAnq?z(^4Y
zp-7P$2uNt6gkGfN#rOaBu6y76*P3(Io;kD2*|TSVdnQ&_TaE6*)e9696m$r_C{-tMr*E*w%i|WuQ%C2nw|Jtp58*cOSXDTxB0@VbYWF)u&M-Di!Q9i4c3wX
z!;)JW4BHJGCpUsZMixPOx1d
zH`xx8WSW(R}1sarngh=*jW~Tuow&ZX(t#5u1>>(4DwRO2;?k&b1U1+A9~k
zQCM9R)(thDfLbU+Ep($62&hdGibSek=|`^)cdbnftj~;X5vIo7rp6Pd7Rar8YLhUv
zNtz;&@H;C@`$XdJ?Y-mQq=f|1W*KR-o3u$Fk-AAF@|y%;s@o_im^=_FiUxj@zj9~-
zCZ(@LsrrJyP)*p1hd7^S?ZDHl-dY>wG#gtuT=thu$kz!;oYMId63Hr3#FUXqPy+^&
zRvQ@Ruhu#^GJ(O;ynsceLZ5H$uht0Chf5V19U-6WbgOUn7423!NYZ;224^BrTZUDS
zVAWSw`21ZIM;l*zW*ULvrtI5A&l=6t5k{q-eGT#2P|Oy`zO!>q5lCn~weOshqK6Q}
zpGKFvNTvcGrni4%s_^HL#wsA35y681Vu2(8{8wlA-sWn?YJ6G_2Loo@A<$r!)KedC
ze!Q3?$b?`3@2e8A_o90UHAL3O5oj;lMm4dJXQI9tIv#T!Kg0Jj3HvdkvikD}o>|q#
zp8mQ`(XxRM1)1ibG+U%io$<0uffo77Pdq?*7Z2`A*|o3210K1A{bfVDH8Hg2Z>Zm!
z9g&|6#;!do9-y!kESPum%`I#Y*bc2bz}f=GOdsekVczHZqU(zRDLa3K%@yKSdSBAE
zw&q<0AbPjSH3yptv}0okL&R5KWA8V$W8e9RKo3G#%I}^Av-<_j?cLk3fd#`?qU*&~
zJQgYMSc^P0Lp<#4Z}znFU#6T0Wg$FXG$RW7DOQ^D__w1M739WbdtJ@lrs%lLzshPG
zObCh)Mp#sUm&r$Us6-}&fv@U+JMS+~GLod^*BB5IH>kLpXOJoCU?J1`lq0MJnBWh?
zkIuNmejym1F@*TIBx-|qX#5;5oDnTnaK>RI?b#+O3&x1B-quvmXXY&Y7W#b-K3p_y
z>98>^%NDE%FtxdK!&ll(Ut28bN6cTVHo|}yQvb_}f>byN<3X7GvBFygtYt*#sr~&W
z9u6Au{I$~MD&Su{sl}t{t15bmKZpzLU&=X?Qna9>0BmKXlo3%f;-eny@Y9&K@3RgS
zrd81e#lG$zjMf2?3-7I2To0N1TFQhuTT=h{X{EdhU|h6(+V8_FMvS3D$$isXIVv87
zH2%iQcbxlU2|cwv{@af=c^Yqe8>7FjkG+;mGEFTt%1-~8X2^C4ArVnJaW2W!%t_;P
z9m-g6H--SF0RpIt+PshWz`>c9%K;vN0UT9@die*x&OkcxUBcvL|%-oc95^O;SkLOH8ALo8TX!Mz+PN|ytwF_rdC3%~$uznUt2h
zQ9AoZTU>sYErIKY`}|v*w9fTW7eVUcg+91;&omX0zOvICy&ztRPhpzUdV`3JfkSd`
zUR4yc-ul>wT@cB=i=4cnQL8|qANnoX>p8WAT4LZaTGLr+^9#M)w}Qz{@PiM~gy4hV{l&_DJb$3!G`BmtEzX~oqZ9QPJ%Ux5IF|D~JhyY30t{3|OPLuRpB1a2w-yjlsLwsomPd8u?_2^>yTN
zVzTL=Wt>AbVjCb{Rk7#6k5NvaNNb!!B?|R4T?F!}9#so5w{QZkIuxJl{SGn{n?-Lt
zsp335Yc=iaK3X2?ksK|-!=^o>{Bffjmb48`v6`l}Yk_}UL!OwjHK^!CRu>#M{TE*R
zW2fQN=jBtbm5w(*Glkf&_-J+cm<3<<*y`j53(houdNu}@
zpNf5Pzo~eLRW+&1C~tLQH6NnNfxYyNqVifX-+Q~+#Pa&+>C*$gVc
zy{Eu;F48aVpT(5L{$hIdo$<8m3!b&vIg4_*mFPWM%#`P*$QP>@qq)Ep>)I2bnl*n1
z8mlV&AJ|DwEeuHpyv~6J5YPak1#%~My
zrg76T6T3Wi?&61sq3{_k^45&jnejByLGHx|;%MQUOk^Z%RXu8c3<2L?Wg&L$OlA5K
zJo4w0vqKJNFL%r(Pj35i0O;4(Au=>%%pU3XugdLX&CeB*Bu)R-?CXtE`w>-_0Q*S7d}%e-Lu6}X%y
zzP}glp-(UbPO@^TC*K}Uu`o-wd}^e=Mz?#^bz7?Y?35Z01`!p1&ec!;*oLKuEs42&
z`c3}aHByZX8LMef(tkR;8r%bC@4O7a*~!-~HFFOPuEz)ZUa<*#-Pyt!^va)M&ea;v
z$Cb6yJC*>9e&YVW@sxheK>>?WtO3`fA_l|hi}tG
zp|Vt+lZS$x{LgLhr)QS^6O$k0QO1cruN9*!roT|*#lPV|{y1qic0GUpQKNFn0I%ph
z4UPfHtpPk(#_3TRx<}T;R41#=0K86{0;N7
zH||JB8Jl>pc$hC-RdTj#Dt=MG=77+jhKX-*kv+mFU;Os>67zdNluS>=hi9jS_8A4E
zMG!-IwZN~3YYN5zKK9=V;h8CrHEU-UiQU1<-*`4NhefWJy46;Gg_gHiP8X52MZKkt
z!yl0aCN>zlqo2>E+Kxmri=5C_*6vs4e)gPy`-`((fKETXyBxI-oI{8YS^4Wlb%yYM
zWF-p|WwaUFcZbHHviHQs5Jpve2ULVL2@<(Nr1d
z9AbB3UzpzH*uBO#!y6Rbow<^jW@?Tb^a?FG-wJs?+`_Cj%$3~CB)`|zrdln~`SdC!
zQ+ko1c^Mn$XY`)S
zhbvrn%6L!Rbq=QS`h=AkkPQ^T@6Ggg&*zU_BU$8m)ijCVQ9gkw{x_`f;nhlM9g!nt
z#5GIJ>vx650T~?C#MH*jq5CmvaiO&}Rxdb)3#q@GKoj657)w*~2>-ah@Kx@Wr?_it
zeRuSGkZ0AUlndLT=)jXB2es@1Vrw-ZTo4?jk)u&OK32T_r*2?OB&@Ear|uac%S`*)~YT_kl&B--Z@eS5iFu-wb`VV}h=H
z!Bzop+$VRY*?M@Hk&CXOoNFGz$~WfCzBFZ|w~9RLggEE3Vz{H?8WBqK+dm$$nL(J5
zKEqo%NE-HOY3bi-c>&@fT^)rEqb#e9r7?FkhH7w@POp?4)VIIXN?iCpz?_(!09s!t
zyzv!!>8kKGq~u8*#J`f}zK7-;asMDI>}6In-0V4#DC5i_#xl9-(stTP@kk@XppVlP
zx0;7W>b84^{Ua*d*VW^QL8r&^m*ow}!F%+bmK}*IFwZ3i^*TT?KadtLK@Qi5%%Hse
z53-$i$q^J*!QTNter&x-mNz)Y`|E?++G}Xrx;m4u@J_$I2?Jl(y1K@9(;snnAIT6f
zWSU^=XoBO8%I#cNS0=a#vs8U+sYJGc62Wt#xt~v_`1+r;
z6{qL*l9`xg(y0a2d1T2t^;07rHQG1}vV-`kx4-kN+7e
z@ewxO%Y^TIBQqNmr{3}RIF2Sy*y&%$X~EV6O_FIkhl7~tBWW74c^W2S7m6)@CXh1`
zrtt+Un~@l2-@5v6s!N}d?rpU&^6)^T=_|#2NBc-JE!r1F7&-W(@BEV9n?HUSCYs*d
z`QwMl94zw0urRmCqmrflfRcZwI+tOcg1$=1)37oLt$wE^!(gY!!Q-j0xK8r_;ee)EqWs|6(i@_J1V@n)Zt?8Gr<)u$yB
zYHy-zyYs;4=ld&{(}mPo5j9SV7a37opsRDZ93e9UDieD7M@T@|$23#Qcb|pjC;2-e
z56dZ}-gX7(Ns7@6`ZjSU7d&M^B-+3vsbrSlb(`57$8(qXe7`R@rLTT6@msP12lr%D
z9QvG{f-3x2;h>eY8!_jnpXB$gr&Y&^c=x$44Ly~U>ZUt=v}w_Sq3Ks)|3I(IEh0y_|&HlLW_C>=n~rm9i=L{2gYdvJd*%dbTkUjTJs{u
ze<`YQ?g}(;ZN?Jay`ecBo*vM}W{z9wNWDWP+}r1;xSP#FxZ_Lg_O(+iT5EOsNpr!x
zYrZ+nX}6aJ$$lt&;5T?XJ!pLeO3h~s#h5$M$Rvq>o1@aw+iWFYR2(P&>wf1y)-E=k
zkojev8{%N$T{|;x0OG>&XRHxi#^uIr4E$+vfEbOOQXb>Tp7V?cB
z45E=_))iJ_m#aWxjPFgZMxAj8ILB8aG!*b~J?2~Y`t)*I<;8{qxB#?qtQj{+-{aWI
z$o1A3Cr{}=(s$>8($P&Yq-KQuz3Uwt3e5dk4{y&zPB&Lf`0cr|QdfA|jy(
z^6vNkv2%8I_T63A^{IRD@TVG7Ko%eY0Rfekrm6t}0bv&Y#YRqy|Kyhwu$O><=p3$#
zPzzg1INU${_U&8fLL729%zG%ry~qD$x3Ax5gy-A9fN|8TzF@Dupyj3I$*~FCM_frs
zNoiA=Yn#vN%BrHP%I@|q{s+Sr-W|Me>TQnNNO?JR8NHF}H-idY_!G62`u=FN
z=4-R(^p!s@s(GjL!|8bJ*Nh-s%+k_w+g?xbQf$!2v-<6}z=as~Nt5u!>*J&En;To+
zGrwVzhQ70ZR+m>s&!^w+4nCc-y1TmzoWIr_H|zgC9x{9NVdz8mm%h;DxRkY=#P~$$
zxK8fYzt?O3Z2J!yn%jatCi;(mcbt4RKRY))ITf;i@*er#-#5@SQUtd!@fh4|Tki>5
zib>8!Ml5Adj*SN6E}VwGKkwNcJ)J6_?;H5i^=dT8?Y(d0O5DoN_4hxgI=g${tu{Kh
zXXTcKxj4Hte;y9Tg;Z8lTr1
z;>OKK3)Gu##X*sB5+=
zKd-{;qlg_LHYEV)Rf>DOlnp8$&
z>GZWd{pQbmi!-gZC|m1EHjXdNeVRKgC^$|`
zK=y}!trYDJWoOK-{0I(o#d@1GC6|($(BP|<32E@u@ZZ1oe+LI!
zTL)VQoAY0`zj0mk6gjR*jf9VulZSu&(kK;eqY42U>u~g1m=lgAre>@j&r8M9T?V$AvxT66w(m~*0=$nb1|HM$
z^jSO!^Sa#-HOmWs9>&(}w=iE={FXMsa>KFay;L>5;wS@LhhHEr;>|~%jPccFuByl3
z8c(T>*&k?55_lg$h$V4_`cz=CAMqlBY3^}}mSGdaXCD?I6L68{@l_-%Un5M!r9p*~t9
zY$h8-6_ScM*GOHus?SbJYyBf?E)#!-CO%x?u(}*gsoD|!S}*mH&3_}jr`++Z=84|l
zNW{Bl`r6`b{tI+p)x_dlNcP8A9?#9T4+=2BN_`m<*1%iQm7Ni=6C}X9qxC&i)LoM@
zg}Mfl@sjf~?aAfrzSeTm18J`>(No3Ov9GeJlzA&pz9^iw|2G*>cVm^FRFUAEClhPM
zA(1z2Bpe{WG{0aR_#|<@$+Tx8+Ohlq_JJ_A2<)*y-Qr#G3Lu!+Uogcl@Mxp-PtM`J
zOIx3L%%aPcTI@etjUO)U8l-E?GI@S&Zl<{;``w1ILQr1={^@iZmH?II%bQDRmP+^Y
z7SE}y{)BZH&Bzam!p<&R&ZH@Y(d<0C=U`Wav#nNz_%I_Wlj~Cyv<7sz$;;|tKr#n;
zrQ!8@!atH*twDZnC*vhkAYmE%SNq4I@`9ozY$C~3bj_SrrExQ4iu-wTzT
zy9b6y<;u8vS7rf4qsKu*oB@-nUzM>5PHZ9R7cQ=Koy8AH#2p0%E~+mM?cgJa)sYq0
z@;co-`wUs-$>HhuGDOAZ;7WTN-2tBX!6VgZv2W@F+$5&})S5@8n=HcB~S=9Y#TL^H-0utf#K*tKh
zblG
zEtKW17MbXtdYfRi@zS{Zk0tzH?IYT&8Q*Y{7{0luNZqCH7Xe`m9*iwcv%#r6fyWZ@
z#cA_uGHz4T&KWgo20cozOqX>nPvEhKP4GS_*W0`Wm>^uaoAnJ;9d@A-isOFh`|$gt
z_VU@4SEVQNjwHz^d(+;FNRmCD;Z0id^vpj``(Mr~O>BsQ0G}gmG16)EW6c8NpQi;|
zCTmD~_Is}Sb>9^3&cIo6KR5R)l;*UBJrN)n{$i=rm3MtqF2Ze<_t3>aD6IYiO)y~d$vzfP#hUqD#FB1Ffgoi}x;BbdMw`~DyBZ>r_$oT-
z6)U#^aDPBu4aZ?`!xhb3m2qOfU=L|{XNnW&BNCa=%>eEaIVM)UOK&q4l+6Y{pb77wW1(y|N7FdL@0RXFx{mD
zO<(fJR>j4gX|n^<`%td>S-wSP@u5=i(iVkVwI}=&`GRDk+cODCLrP#}_m!#7g;slf
zuTf0w5!i&mz=d_8j@47<`@z+}1LAp79ZQW&)tJ6;f*i{${IYY5TJ~(~4@Z9kIaZ57Jtz
z{l53jxAPE6qed721N;PB&K!?_D36IKOhln_Q2u>)#NA89t;@N3ZQv=;Adf-$4>@YoZ6flZ0IBtFl88KS*5FBRhi0S$4WKyhDtO*2O6%qKt6d_^lNlU~EWm_w)r(_jn`@G*GgjtO~sc=#CG4u{VC
z%d8UXyT^k1U^)kGWSrA5G{h;I*ftcIlTi&T)r?q}-_MZl|;fiLBR
zav5uELK&`e0eZ3|Peo|6=5s%mPaeNnY0LOi2Eg6}GPLt7x_VSD1wYsFS$QcEQbmQ>
zyvL6@(P)@1Zkt%k?7lHjngm|=^W*9UL|=GDSqXIMm1_We(oTl{nJZDe0E9@kq7HA#
z8YYZcW@^2jNLgv^!30UipPLpTXl5#_wIxab$th1TUofHY<#OekFHCEGYo`_!zH@nd
z@6tz<*fQ6JF-kE|hZ=?O`P9JLss#b}S?PI#_JfbVcue@0IE{aejyWE3=Qs$%*FOTU
z8^LuL>A5o0D1Z5#HDQVlfUw7;Y47a%_4(9ruw!r(xU&ZH`JSqT{@r^sMxXJr
zWMGO#*lvD`g;p}S?gikvvFM#S;RErk#vhY^e|Ua4?fQ8rP=%!Jg0Xem(_~6yw&w|H
zEmImbE(Q;v&}r|X(ps)+c^W&rm1CZ_v}szlkt?+fu+}0LY)R~vCR_I?0izf;^6;sr
zos`Q_l+>KXBe1rNx6l-TWmBtj>LNirM|1A=5!MDY34F>Q{Jb;jq}vJq9_aGwEyAWQ
z!tqZ9YTCUUAsQa~d@KCR#lWW;GXUGdnNH3JNxOJGsIzw(>JdD$b7of(s$Pg3be<}SjUM`jj?h|NG
zq*~jtTW{v~?N8Pd=ilG8e(-30yLWD`wD^vwNb9e&IW1GN9GD>1!ghlT(yr>!sOc=A!2Uh>LV5wQvO0+w7mO(AQ@`{BVMc_)2M)L%2wE4~Uc!iE`jhK$?Y21CX
ze|O=zC(IcRgl;Bp^|#C|CC%iSyM8UPZk<2{1|*8QJz7!2OG-ORCA>SnfW3F9DPN-|
z-JT6Qqo>l`-)bk4S6_41xdanftK-1KU%2P-Cuadlhtvl
zDYhR^B&<{hKhddbnttP~b12A#QaA9!@z)!}j~_etl}AbGuykLR*Ln2WZnuam-J_P{
zm-&xhMv;Hu2#9MTN(MfCfvZJ9#15v6-UzI`0Q^e@vUYeq3kpdz3jEekLFD_*W1okI
zZ^hM%*JM4dmZ&jEcC~saGgGi;h9Nr6*tY1pQ7H%GbSq+Q7*W(C(r3gv5|G{SwLs#4
z_S;b@^!npd$L(@fWDbwx=^+~Eao=}Gc{g3=kxazvNq2Ve=cnz9=qC$2$#%~aOl&&M
z?{X4PwSLy1a$-9gfMG_0Pqr>+EbYdJo+l>ebA&HyxBp)Q96DbcPQ?K3(MFQLLSib*Ai5
z_G0U9T+!Vh%65
ztuOP!#dQs>0#M!tF1G(AN6FcKd|H6q2lYHi&ex=QKq)#W{fnZg+z$roKswmVx7uB;h$Lqq9Qa&4wNv%v!AV*M5)%Xu&BNwPs;*
z7X&!>=BfdJbZvcM9<{1R>FHw@QBDo^@Cw#90Q2oqw_Q18`*1=yB4QNiHKVqlSFH6Z
zB$)jQEThyJGB|s%WmBt3fmT=7f88tbK;MAm;T;^1#mp30Bwh_r_E&mh76%D8;57}C
z2l>~`QT@l8PN!@4t!lK3AF!=9%l9MmdvW3ij6A3qk-8uP^z1ch^4C^0!jDzRltrq@
zWpdQ^Fm!CuWxL0B?LYp{Q!$wDf8SbUh3H93tR{p+knbW`Ng{d;Xds#0^d$iUn6t}#
zy1>Q|=7om)j}}<9WDkB%3#cK@|KlYCd{dgsUI35^Mcl=*>@}^)Z+@rWZ5p%i58B{U
zeY+_Tpf>vWoloI@2`WQrry;l@7_g^CC1Y=Ta<<&v0JKsESbq}s?C(EV=tg2L%9vNi
zuHEj-GrgC)`tQjl5~h$Se^IXMD##>c0H2{XlCvC$I)c8e`ZbZZ@R*7IK*`co
z>EhM*^QKg{2NV+(^W7@D&g+d&wFLgQkhZVq2S;%;OM$F4#L>-H22FGS+6fL{ar@DlF0#w6oI6lqlGvQ
zp)nIz=U$1#k|vZtc+F*%ooUV43C~^P>jFj8Z$6Kivo|j#REE(BrP{6Bc|z%pc478}
zEU8TTPjI}|Mf=z(!qJTO6aDu0FCV#@In$Z@Ws0L+yw_)uB{`@$aNX8yKv73;z(4iD
z!zxsWW+H}w4L5$2I|}@7DZ`oOwx}Y=^#IF(iux2-t>bG
zZPFcm1wEeQg?fy~OO*s_&_|z=Tg9=!t>QA-btY7)_IWeSfR4pd9S&F3yR#;we-&Bg
z&(9)#fjqKH`c{wMp1Hz)W|3#}o_SOC{^Wdn0np-uH>rY+1)q_z2*`hn0UCrfM$~bC
z@CUT`lCICreln}vF#!U2Wg94irz&QysTOTaik>Z;
z2CA77^LD06m#QPaJf|p8!veirV0ygVX|5%QU!r)W%)O9ryuV$BOg^ePjiZJsQV7Kn
zWXD&g>`F9Bo*FyPBsxDe!1VSR+B`Z`ePfV@#l$<-Bw7M@$@?|ZQIYy#`ZlqsS0)7F
zGqEUk9RHI$eBf8`=@skA@|e0#M4x|&;qevu;RhM@BMB5X9@{g&k6k1d%5Yl+kLM^u
z<@G=56W6SNQjVJUr1ZKTCtV<7WbKK_UQQW7h~K8aZRJl|X|9lFSkv7XH`44LoS3IP
zqJ!@aS5!vsAFE#V(w(j4X5wB}5$Tz-?wbHA1ok^N`b}_64TQ)oFmIzYK!iUx%VZ$?
zY;NXeYSdKpFYZAKmQjqr5KJPXVQt0
zuz>$K;WMm|j^~)Or4+`c^!v~z1OsAqq`~;t38kYMPmdhJlN!Mt)5dAaG!dwZ(R?;&
z(;aXm!mVP~SfRope;QmZt~`yY1#jkKr
z5}R8@nm+(`cTG`0cdglU?~_T#gY+-Wvti*?u5sC4h#TX1!>T`7bH}Yk?jEWZIg_)+
zpZHTzIom;2Zmvv=`J(0EFy)MqVP&7<6}rIQO>Hf!A6|M3@)Od^Mb3*mpoPfno-+P-
zRQm)_LU*B502bBmz1yCRziAklU2GK~2mCD}Dk1;$3@};3VF^4&5Imm}>MX2$`%+2i
z7+7r0aC0hezO$T-XxJ1q{hU4UZ=*jo(NB%?1@!shV>hBh)prh<4${s~-qkQFtZ!!B
zJ(!W+YY;r}17WQt_RW4>@cD2A&nNxHUYs_0GTRPW%ylmP4iqC@K;&oEtY}3JABiKy
z5v;W|zK&jcFB+Y#4YdR+1E=1i1Ofm0fk^&PBT?4!Jas?W
zfU%SZZNx(_H0Xh2&N5-j4_P{6z4SwnDF+KaV;G{}O#dmLn=>Q{IW!6q{M;n-D;X?R
z>2Bt%x<(C1WLRhz=;x)8yUsthR9cA~sTjCXi991Z%Dm`3J8g7UKGG>sLSi?az!(0(D+SqBBE-n&gWBt&NKjzkbKmy4^)4I@G*2m
zuGtfHeja7r-(S*S`eij9`nh+z`$0h*ziF&RiJ9{PbHDVCBqVu(TjWuY1oS?FHL#))
zctQ&%4CNiO!g6eGffoWSf#w)k1?r|SD$hISKLs*58=fWN5k29_FGLm)OnwUmi!H!+?E8?(Nc9~Z%&!6>34YBV
zz|*5YB^)__`x!Dj*3W`?A;JI;F<-z_K!WT$zFhm;qF-5+FVwo9%^O8G+ALmeAv0QM
zLJB%w?aoIFzsxT0jktDaeF~k~a0-3=stqG~^7H+x?gsMG?Dpw+r;;VRGu;K#0FQX=
z4_->K*}k3zr`6doZ4GUB(UB8(M03K+_n>sjd{W7uDmq-`PdsFI+{*dZvft
z@^PzXF8>>v1^Cdzj^aP54VJ^6j2V#a{FZBW>E58Tp+5y#eY1Bj^a%ez9O9j90iiLK|(`Qr0Rk)<3ECvvMRv
z5-;wZ3;2hX8}EVUd_5hLn8OndO#H_JAO`mf_uoI{Ep__#b;ZgV(3o_Lk)-DPlE1Ne
z23yeB>vV1U`ndL^j6OY!Dw_zKa|et_KiXBVP{R?f<@}Rtfle?w@O^N*RK@Wik(%oK
zrp|F-1!&>;DN@XnjV-#zAK-4%6vGx6;|qvN(GKQ;?rdhW1I>9&*)9<9s6c#6E>_qe
ztjs#5-oTp#5lx6**04L;XKf9%#3mw|J5s!_0y1otY9C6Wx!K>dfJxCr4FEqU14@L4%8`fM%233FL#
zqxkE(E#{HASmMoKhTevO!gp>Z_WB@Q553eurHBz(|KfT>s#5iZ2-hU(i>o|m8pjuT
zZpk%ZE^-3E!Lf$D#(K(Dd^Ux%syG^+2v=R46=HYJ?acj3eV|ao`wiFsRYYIUI<7D7
zBmHew3aK5-o?JhFV)+>nknxQo805!mmwRhrjt*IuHKhTt&1uY;FJH^(?*5a02XS|o!Ot{q$_daY(w>`-2%sERK?jcw%
z6`S>&?A`+`tQ8RDpyGb&Dbd%wGdPXY89eg@48=RW>El0b_$<_TR&9g(Ws|2c!*uW0
zZ4KQ=`Dc@3s=uxCByV+9G{{P<-p3jEY(E!ImrM3$FEsz*EpqN?>h%*@`uYQr@BN6A
zc{#>lOy}(PrN^@justtTuhL=KI$634D-bn`QX3ouxrZcPa5(bw#;7|OD=!C?9t9M
zJ0adBYt$@FjCtWJv>_IQ<4DNG3d-esK`DL!6CgR6T3OIZxv#MyL6VJI}q*B+a(|lU8
zr2FRVdBeaxyo`W|0E9yM2RbYVL-@NW-!ag*_ZlN(A%smYU99W*?$Ht`1l9*Ix0Q1i
z+Vv2-_ZYxSC5=8eb-O?7(C0#yG=4uhQ!)fjd!e;kOOzq70Hat-A=WhocSbun4Xwg70k^AJ^{g-gtW+
z+E#TtM+TF2oBrgy`l(`&d6Dy~VM<=9$RV$M8bbRFjf(hLTtRZUxiE>&Du6OnDQyV8
z+Lez;>MP~{9z>Y24k+72`iP@iq84&$8y*5_$vY6d=
ze(Reri{Kt8PN`RetCNh6+Vsisxz31Jh~IZB)SoNYpxqL(!?>WCYQ8#$_|lRUTid04
zovrNEj=`2DXr-X;JbAV0N)65L_fWzu?4NP-3wX%Eq=iB~n-|L(z+l3ONA>;LT2#(F
z<`FJSsL{5yAV=hZ4sbA-f+#M}SZNXe>Ba5>BXZc;7j}?iV-m-rtN78V&zTaES~Oy3iw59v8x*(oq;F
z@=gP0A6G`kl`Bl^tfnW#b?VRLMoa$pI_|TLd5D}kiPS7)jt$Z?0HO2Ya37&*t!7r4
z(?YzjD_M*bOX6JeWj<(_#Jv@^@4@WuOW}rR|L`!mV6#0yU;arySLhRF;&W9;zAdd6
z!RSb!X^aHEaPmRKcjma#Dp->~Luixih9E6Q$gn#Z)YT%Sl$)rQc3*?Uz)CgM10?&t
z&KBwxpf+3+t+IH@eqSYfm{UOY6Hmu|90|f_B1js2Jm7RgBLrkHd@~hkh?$>;h{FlZ
zBMgLP^{x@-JEmrkk*L`mnH_^oU}L9Uxgtl4MboY*qNKSw
zT-j--h7;Ln9iE4Ij-*{QO!6A#*e3Q*HPcn5Cf$^mKEyHazdaCNvW`Ft6Q|0Q+Vz({
z2_~k(@7w{?yZ*!F*e*UUPZ(5R6$*YQzHGNK&k+gcgY7VB7n5dC78y#9i)3Tiazmah1BIN9y$iW=L
zxe*uYyo`K;CxN_&BF4oXiYAfs%`C{Eja*Ah)YMpA-h{k=>G18>!=vH4L0Wkxfmk#9
z8tSB>Sy41(mapQiunN+iTP3H&b5qbk}<#W?dnr4wq9o-A3-BRH|^`$|FCzk)mkWlL~$
z`e%g$Co0Kr(I53wE6d!%>hfog7a{Mfx27TKYVRTGotgBw)mcTHLw)0>buq<0^Zce0
zh&fd9Jf@5;l$YDIU)4*L=&vnF!57!(fMI$?mY^V#o4)UH9EA@Jw*DR7o=f-Dh&lAT
z!0Zw;>zOT&P6Bi_|In6(mGU(*_JERJk@8o;{AjTRi?*QI%?Lj#@?_2*SEA^+vgxc~I)B%$AB)w|8()ZGt`X-}7th`5wK@APoBrh3_M
z^@ufA@O@ei`ffZFTWvzV?mIoiiJ~7E(y~&OL?3b?nGFkz^iUENI7dVqvq}>zTlLp;
zdb9%zRNr*VWT#~~DGxv7_w#r0C
zPSwuFo3IF*F^q-oxac3FEviWaFkw)fc=&o>)_CUFWj
z%Zr}iYe}Rh=>lIrDmk_*iC&WEkTJFLNwqc%wT9sr;RaE3A)XMZ6mM;0uP4-M#Z(Qx
zAK}hOM!=C1l6BU)N*n(ay{8c82rxg`a#U(0oqwp1d;8MX7$${#}uNOT3
zGG)tSBu(Y2*6f>pGNf^|`FW81-HIH)y)lp&OGb5;~HEM3ttKa<4A
zA^XI%g|C-H(X5eA{|81lv8@u%UvHvIu^bf9Hv`NMjQCop0D6iH57Me=#_sM6k(aLp
zYY&|`iYTQw-!-fU2WQ4EdKrH32;xX*+8QzhLw_9%*kD5Y)w?!2lyY93$|qERtxZ`Q
zEf7$1`?+wKfZROz)
z%6kmmHAw3R!l|Vw?=r7(qt{2%ke2H2p~UPK@;%8GLbzbSP8o_%PYF;eihddUuJ3CP
zh*R4zXYlwQ`I7CDjNLa21urErF~(t#8$gy75(8(n)jz;R3>?TjWF>?DUA`O*s?bY_
z3+*EW=dq-H9ib_(&g6H!bHfsBK4OWSh(||qF^!J_pqIcEA0b{ml;r-YW7`H|2iCvR
zJNB@sXt!G!ydyesGOgT%=kG(&3Vru`lDt*9KujSHbxVQAo!+i0KzcYWX~Lp~^$^7F
zjgCn=i@BHUEL^IML0|(%DqEu<$?;<(P2+uUzZ)z!Qf5MTIGGc@gr1Afe!Pe+DSIu%
zlxzWu%K6|qLbH1XeBj$1XW~V#BwWU1PksTcd0|$;2MY$AnGps9Xs@_zK@^pf*!(dj
zvkDi`7oR2-JZ}_?sjyb~Bk>b66i&ZBO8VkC&GOUBp8Vtqvo<||GtnX%G`!=l;x^nD
zTAJQg*3l|~?ES-gj(%+9-Yd-?kg&pf3qo$!JZ`nz_pc^!XH%G!G!aD_pLTlFzWMj+
zbi`r*4Zbb^5qzwf{LL)y)?AF4#Q9sQv+Uc_Q70H>U8o+YR+m^q$W(~!c&o4X10X6Z
z@R}|S`D9K(PH}is%A5;Gj4H12U0Nc+_s4qEfu(f?CEUxiKFs+M9+3fAl`^i~kP(_j
zn@)>2V1~+}Z7wOJ_70@`WKCm`$+q408L%U=lAdY^a`sI`ZkolcC;4FM@d|mk7|YD(
zRf7B2+WG~37H876WKNj7vI?M3oJ@my%jmBs?xy@09l${yEC@hvROwYefSUmZAR_YEz#ENV&AC+gS^
zK#|c=#QK&I@fuvnG!mL6Z^UVy<_wV>%npNRag>%8<4YhW18@e=3DM;+ogf9phNL}JCIh*J!!W#
zmr#gGCW@-fhV@l{W*Jof&F$x&B!wi->f0Be&kL_dM*
z!hZ#j$a(^-348-G*HN%}jB99xMr#Cc|7Q%xDC=Z9HeW(G@P
z1J^){%JNy`#sxxN5X{QT2mCA1Uw>CC
zO9)GJ(_@9dN%}IQ`Eht+=>=K(GOy?I``eG%_JB&Zb!d2*wP6qS?Bj}>yUd7*iL!cI
z%LXFOMA5mEOxW}i5yu&|v8=W=m{_PUf{p@LlRkH!e*9NEsEwS7}5#zpXai(Dd6|DuHX5f$2R
zhm>s@$mN|3EcO#^mL=B#Bmj@A3P;k;@eXRN{S7$RiVh6TT*F_c1_S~Kbxxl_R2&Y)
zUkW;3RZ6-SP{!4v)^Uyw>$wxjTiaYjP<;Uh7M0W73@U(p%@qhr0p+N*_~6S7#;SJj
zQ91APz8Ohl`ooX#fB3WNu*W1xbra&9G|2%AZumYf6-Urhv_GvsPWKJ3VvW*-!I{E(
zfBrl%iWr`Qo8Ryk>o{K2GT?Wtxbk=C3`#)mS?V7+=>1gP>kUo?UuJya=S!Z*#E+@u
z%)6S5=|ZD(*@0So&>%3B*XLi9;RbBIfYc$gn@X%+=Wf
zf0!&P$}hVkCgQgcq&4q|D6j-0X?u5d80rP@tP~G)zJ-It`M<8+_k4Ty2#!t2S<#U=
zuxNzlP0dqFQjdEYD6&&0t*AJUDOuuV^i%sd!JZ@*v=|9k*AlI3sje}u|1R-u#JngP
zk%!gI{7RZh*26~+M;4wlvTaTkzFH<4*438396#>F
z8VVCFc+=*zqU4hY^%^mBhNc2oCs4D%p9gPg2J6I-%v>xUw4KCUdiG;foqt*II;%jE
zv{n6U^(06C&%SX{H1F#INs>1i!#%Dr)rjH|8ccwx)ys3yS(hcROkVsdx__#EcW9n4
z#LmIY#=SXTB%x7RYjC;?po6iE^PpZyinp)*m|t~79T7!=iC0D*CHT0Q5^KU?OlbQ`
zs2kO}GeYw={m0%H(INjnh<2_$VFyXR~;-{^JOw#Q%0K~%Xig=_2!`t=7cet{L
z*1cU4j6`qvMbS6^Rf>@~Q9Xa|U(fexx{mYN+34bTdlr?1Bk?98c+d(~{}v!23{uXx$<#RWCtHhzMsnQzo&R#O)iZnwiNz4k|x_~
zl1XO_bm=BvR~Uq}yepZVosbj2q32+VISErh&FT^!CZ&eR6ZEbQ0OkIiy&h#yMd64z
zb=|KLBY_~98DhoCKXK-e3<)H0YLU4jVuZg@T8p09fovfv2szg=fpHVLs-8pfh_6F9
zyr2POVbR(-iLl9rJPh4Y5WULgcTRu3wHHggu3wZpETXG}8ca%4f#l!^jRbY&C4^sY
zdJwp@cGPd9L?45NPe_opq27Ae?
zrr{sVOgKU_apfh3UMbm&XXD1nc+=|q!G0c5G`VpCT>FDFTT#5mO8MY1vqAT2Oo^(R
z4JfcnZ2J|yS3qQ>Oww*2N+wq%4XxdL^7oziytKn-9gofAt|w9wDp;`0D&~BKIr^e>
z2hRKcFJ6((_tr)}gTAtVE2g>uLDf)Yl_ig1OTJ>=DPfDzcH(-x#fd+=)*nMT9F1wz
z0ld=Y4fBxn+P(Dfdc2f*O^WZ8ijrFB!`iZ;M)73MT47>OzV+j+{iis`mj#Ftyl_;7
z>MuKP{1!IM`67~0NFQTI5YrUv@B#%er*~os
z|7rl+NZPS;NxvleRPTgUOkBAk{?ek?*y|klUIPHD5_|9lywG2Z@s<&Y>+RS9XMVB;
zHLEprFsp@0qZNLvwnv#*s*@%-hO?+hNu%R^)YRQnZpJ@md)
zfTLsndpl7J@X!^+3})EAt{d=~^-pEbcMN)V*pnoe%sE?W__E=;7KvSX98trOkN;k?
z-eb^$^_Wx}0)rrS>hfkL*1iF$fCb*fT0O`YcBE&^Fm%7@6v{H2dKkY!Wv(rAS>QH#?=eM82nm(<%
z(>rVF+q|+TH!~L~cE8TwsB1RSano{Vl|?eQyZSL}_8o025OuB-z&TMkVpSN=W1vcTU6r8R|#+h2t
zy?K(Aq|sHx#6xSnk{^VU_$h-D7iN1QD*vS=GDO@mDNr*M&%&FgO*y*QvZyToUm_Qc
zqXFs)e(JvDe9wilMbc*M0zsku-fJ@&IOk<5RqAK2A)=OQns2Hz7CqXAtU_f(_Tj=E{%1hcL}uj_J{7DZ
zs#X!ELl%sr#ggOwqR_t;r#fl(em
zJN@6}AJkt+&0V%Pv-42ZM{|9cW?`})BX;{n$}q$I%ziAgY^*bxv-2yy2BIA*UZN|5
zW+NUvlr9;|OYB7Y
z8cNyF&7T9=l9jLltQicsu@PmIghHdducR1GQH64q6T*7>nMgY$ZK86Vy$&JK+0
z51p&JNTNT-@z)#9IN5lonm)+#3oWZ=VD}F38O>|AR-m}|W)3i<@IJ7d59V)`
z%-KqG9C(RGz$IW>y_L74Wd+~(2E!+OW^HXKYB}=D4z#)^C_IuTxG;O3)j~R*S~BVU
z>WcoIB$6E)R?>cMn#3v1-o%FYD!L;ROdn{dNVJcgRek*q#4IH_{zxWszGut>-HM{y
zYOHI;9z2pjpSpko0sQZXCVfkv&&~iNN>R&_O#C=A!smdOyl=qU6-1cm@UJtYv}Vuy
zzv@xsN*=%dQ&^p^4RhDL1+!DcAJzW2hPzgz6z8
zT~A7FqCoXkV$s};Qc_{oO2&Ud|on{X1hcq
ziAUBkY510ma1V$*+LJ*usa|)Jy25EGt
z^3D|+m!Q)u^fAzn>}{Q2);i%$B$>K7FP48eV$uyxm|S7RyMg12X0=Ou&ie|4}0f!vX__=K`|
zM8P9Fqt@2{zUKB*eI7~UQptG@9Urc<#mbAa?r<`_jwJQAL{F+6AO87IS*6n!M9W8l
z+!sIoP5X>$Oe%@9flv%dY`#$^hTD9Wz$bOGOIjq%MDbX@>mJ(TD{rQjv~s?`y=LR*
z^hA9nFMCaxFy@L=Z*>V6acMyaR$v|8LARqmgJ0$V@kz=i`@ij3+3n*Ws6amBAJvmI
zi!Pu%=VqgzQez-oLFe1If7OIynXj0Q@IA-dKumSM3g6&SG|j(
z>31JkhVGbYx^qzDUKNG!mg1BfwM%~7J-ztX;jo&aMP2v_oO+obPyhGBq}h{g@daQW
z=e^EQ5%hjLq(x2G-Yrd%1!9&dy8kok>|@Bdltrul!Qy$dT%vZqgk;rwaat=)#U%f=@rbh4jfweMQ{EuUz^|9EDg`-U${Sg|A$Y
z{d+^kdCoqF-;7LyMe*G>wQb@FoH^ZH6z~Z;;0gq|)aeYQ_Xp_kv=pHHgwQt4iONcp
z06HK9=>3MWqY`BX8oYX%J!%FL19#_0p6XVuO|sm4{;=f3{99OfEI@%M`Q*DYQpk<9
z4y!8s?LVeK{p9)lKCx}_4Vxmz4uE;R?7u=*%LS|xP<9|
zb`8}8&kouf=fJsfdI0*O;WXW9g5m$8=sW}2e7i7STZ#Uy5UG_|5ri0}RE5|(_8tjp
zZ>^T9D5+h0m5SM@SyWLJwMUH_t(K})dlf~|H}9u>Oz!77=iGPBxqjE(a44?tvJ4;!
zsxt3Hviw|3JxE;*A|HkUd*XL6xiH=cqTMd3oJ}GTxSnqIP0yJy0ymVB*Sd!k9G8fn
zOHM0M=TPd4L-1q@2GyXx9$%|Qi4~2U)$link?^ngLlCephBRldQGe}*-&4vEL*QQs
zl;wKJm6m%Y%38B{QnnmuTik0vIa_txJxw>RssvGII3*oGgyyr>{}su5^ItA3iUGfg
z>YdQc6&Y$Nxg*1GfV^#5GGADgq`rVDW8u7RwjAo6WxN0sryG*YVSPWA`R7HHPmy}r
zn0aDo@p}zNS6E6y6fMm+V}Ha6ijy5Zlso(-DJ&J0uKp_e5mgDlRUhiXEEsmPkd#`gut>XHP==c(j^xr#I-K&#onD+Q34sWgQ5&42h}{b23W{7gkBK?1f8GmvFp|Br#v
z;IMqrvGE8d{CGYd@?PEgsT`Es!$-LrKlW7aasiXjOmDzW*L)DNxMVVAM6XD$2;v^>
zXpYVPsGbs#Nuq9Yy{4(n4CtCin4S`ZjN<&E}a1R{%bP3ssV&GNe%u-*wwJw
zW!Rhqmfp3b8Y)690AqdW9iKcePl;$3`$C9{ZdqqFI2%rY*!H3!#$T5}V}3t>$nZZ|
zeBbVI+agSiPzC-_l24bn1b!bzE-q6Omj|Ia@%mGeA11vJIAs}rgfX61>{_et`x5HL
zMC?G`4C<1h!vWy@l8c!g?f-L4{dv5)7ehv5a!csQ@v&VI5wW%B1HYz=+TedCG4j^<
zkr)9$qhOrFU{hdY`>Hr&Si`;c(`r86B;iGPRCUGJ|RUBOBa~wq8L9UM9-#v{A
z)Otu{9wirsWZexCMfJAn{b2LC+{edXxmR!M1{0YQG>|Tm+w$`7#OEe#L~N)rQON8P
z7@<@KVRB*(C^E1my24`%OhaV7%^Sc8%Hx9eVBqw&BQ7UzIc3a0%&+^bf9=DppAKS%*kQMa
z0^WFOeMtKmX-{tP6RR}O702NTSOoCjMb9?FqyfG@?7SxOD{#PGb;6I~<*O*D1pAkY
z$PZ!zE#-a1=954A8xHv<%Bl|35meLEKfUiudc~GRKh8X7*g3_AftAh=|896CwaWx@
z^;L@8IkGA$$zp{9tWL;f!V9ThRRV9kR|4~59u_AE_QaS-65PLE&R}fttj6r|7cHcqinOn-
zY&=9aSVLa_e08e_bHK(;RX<{1Mcw8^vX5W~
zUPN8K?zHQYX?lbqKA;&cbO5NTU1be-jt!B6z;#WUdo^xp%6iPz<=+D@f$V_d+Mb6i
z(k5Ed7*%%Nz1|dW*4`BI_loUkR-ECALG`u}t;&CL1?#-MDUaFF?6yp^OY2KPLO$N@
zAS{o;;BIfq)OP4+@}90~=i(Y+>QuweL=$&ubZmfMCkxJRzmMW(xX}m>AeMmYL3aTm
zE7GiYZJ3#Q=mKTf(XnKW!5WW2_j^QkJ#DQ{xY6r|s;)Cua!uxWn^6LZ6k3H-ARUo8D^%*qegVLaxNL45V<^;fL}xkGqJ8
z3Hn_ET4IP7gPE!l`G6xWGllF#rL#T0G%aMbzggS4p{<~t7EB;p)og_N7@S~7@TLR5
zQ%_*doqWpU%6+#6^Z$)G&m_*a7(-06{xJ-`Lq0+{{*$>cUzrNTaCb6>$Nt8ID9yIM
zD?)zP@QWf5i<21x(luD?zI|0t7oz`4;|x_8G)Z)_JEy0TQOY&o?WAK?&3QBt^rE#uVA3M}xnrj5-YZ
z>+UZBM7lV-&B3xMBG)D&`2x7+V~3sA_%U+72YOO=VD7<%N;i_u3KggjSo-HZ%xWJs
zas*q22%*HfxXnWv2EUjH0Rr)lT@Q1;eDs-i=>*L|#Aj>vYh7b1w0y?JfV8mm
z+Ee%JO4Z-rz=w07?O4!}wDXaP{Q7Ni5T3&So`hTX(Y)h$`_KpOuE3Z6P>>MnjOx@_
z3ZS{!TS2uNQq)`1#eU3MV8Ki*7R?a5=H(?oP=JEsAk2OBw9eDYW&pgEE0dJ9fMhni
z3x*C(P#CDE)yq&;hf+BRx?={gv5(|$=eZ`$d=&PiGHooOCpK%RxOa}ew06&9lfn(4`5Ie?`XLri|%>wd*c
zfo6ALXb)-=BA~`epN5gF(p^xEds23H9$szSd#E#+TlnO(o_lOFrE=}p+07#h{IWez
z|Cdn2(?*SShBEV
z0Et7c1K+_vrKRgs{+%a0;q{}EKPhFa8vrZQ&7;HY1(c@MTcLz)kH+c=W^;hG6w>pS
zECb58!DxN?EO$3kG2*-O%O?+c0}{(qVj&Q9+6>lGN^v03Y&@$;Apw$X4@B3+LP8wG
zf5#6@&Y-LT9)mcXoo04RVg#Xzoo;bBXfFY;DgmJdqDQtfGAyAF*Gy$^f`kbQM;bHN
z*w9gvWAh^sg3|w%_D>7@OKwXYr~|BJi((;y2(co|`Y)u4rdWsoZO_wDy`MyJg4E63
zi!19nv46Pm^NXjmF4YO3s20j527fyWGU0DfWJB>*n2^DM>SZqMO^GCwkE;VbJf7oQ
zJ)O*~aTYE~eH(0x7EFK{cURQh*Wuh@{y5gCeip=%@x3w(^3+sTV(6zTSd53xcKNEL
zB@QA}mZQ_Qt%343Ag2m9tJ6mfcZ(ioUCca(LvfgkJjb_@$R3n*?djFmJrTc!_kY%2
z1h$IsJ$PR7qcDH0A6QB;ZTaTmTjn$n7MNl_4zX%nf|gMh%#tK#n(lz|CU3K$?+0hq{!XAW5x4wA^#zvkF{E$O!snce485gjoJBn}FUZxt2!FszN4Q&M?vQ1DOi
z)f2vVMu;^A)qsj?lWpZb6uy?IbOwEZDmHOkv6|={M}QKvWykb-Q!EVt9=EXC^y@qr
zx;HWifs<9&o3>c`o7t)WsYtIpZ5M2pA>yg*rU`pq*=|N~>+Q?qY{!W97V9#`S7W}L
zuaE4FGyp{)dTmJyq`JsZ(h}4K8>@Q|47c`V}?mp?fRR
z0LCvDn==alK!OcXnqO%1@4G!d&wuj&)$H-Abo^!Tv#4CA<9l4G=NcwtoFylWjiq86
z*oNeo`PA2KLz8IdAcPfIHmsXinRYdxbG2z>=P<<8qV`Yc-@;3KrbfZe9ftAC_9)a8
zz<5Pm3K>oA@f0gcTK^*8US?~)>t&{*Dc%6}xiXI+g^DzQg8DPYjnrl{sJvJRxrXzP
z&=S;fAEUb?gZm;QFOLN8widotv6>J6J9M7;{{8WJz>|hYC%fn8M^=BxgAR3-XKz5o
zVWmsZyHU@egvLNzR}Zs@yq4S>ga%^?B}BtMzIarOrz?5aC#le(EwuUZ>cr01mMYia
z|A)Vs{E3CC5)gggEb=C@R5Mi=tK9@OLG`4(>x|B+yP!jq$RMbg6*j$sz8SXwc2&DT
zKr}-(#1gZj5!>OFBV^OVESxdPJFn9Aq*?Lu*!S_{4x61flco^W^R2iC>b4or_>|n;`9x9tfgC
zSJf26?`iusiq(fQ)VDbvVuNp*K5x#=w&q?D)hHUpYOlnBq@_rBh>zv}e`SdyI2&Ib
zz%}9hUvjIYJeI!Cswd?}I19PAdpANtt8V(U-|TbAmpr^zCr+0`lD}pjZ1vC1?#)+i
zN4Pp;2drjt{QOhvvcLMXp*@44gsF!HJ=4UpADyaX|FaxNW0_6PDhVCUsz$+8}_#y!FYICktBJ@vR0A%X3$zZgDT;
z|IQgwp=2@|YnT!7>1Y7=~8r|}7ZmMA!3YJG$SdtyE*unZ@NN1jBPQhnSA
zTGuSBG;9GJ=el1Xr}tuO>S3lY(zJdooGx|_W>PI~vb9T@5A!W#>ASzW5Q;i(cIY4Bk%Z&P`n}z6${d$r8NMh2)i^1`^TU3T+_47T;4i=zDXM!HM{xmB-x
z)wv~=^Sa8pxqHDevA}kKJPhvF+Wp@0=)d2)poRu<8}ZvK894Sp!$8kf<3gEL!t+3F
z-~82~_L~esX@Se#NjKHv^>kZOGMW!A3hymEyvK21bN|6b$JfVKW1r+38e~40i^3*c
z7x8yC0awtMta5KC;dDv~2mvCsMXPl6_E4|=XZL~+9_ij(g(E&C2~m0tn%S$+3q|sf
zI;j=ev-0VmCq0-Mr)*PO8TM#wI?NV!6bRtv>jX+5bJpoXVisyo>D^B<=`}An@Ie@5
zN@S7K11q^G;a5VB(oOQxq#1fOodI#WIQmcH{Tuu9wD2S!fpzKqhhC$gs404PI$Uf8
z7(Ev~)PiV2Jw@QCTf0XU@HIr`n|ltWK2qLMI+m@U3~IU1R?s4zXIOPAsL_mbUl(ZU
zLyPQN;MA;nF;j4vrTUXNS`O9@-W0DNd0|tT$x|`*_7$7p(wHk#YNm=x4%N*hNHm;y
zIuSWjCK`f^b@muy7Z-b-DiBmH@TeO*BlSd@(4(`I1PLU_@oJBN1p>o+`Hb_}yv)+_
z5LE8z%9_ho*+|2g6V8JhG+UVl_ijnHgax>{THnqz^83)-U@v!`2w|5~>XyBwSaCNs
zff`5eY>fxsS7isM*cnev_nGoS>~mF~3}>BXC-bS?RfuFv!mGB9AD5lUuK$|=KBLdb
zPKc&g^i+=}j)96-h1kuZ{+aZp2pv5d4+g#SwNPBFoO`;}%N~$4mfPV+(|nm)h{p`S
ztX~#sT{;-&`*9`-a>Ff2*nny-t_>lOG;ilH^}aXjU})5cB;+T#`@a3GTSHuo^G)6U
zBUjC>vxc%;oVeKR1g8aBAk8W*ognemt$9K%;^?{(0*l11|PAN$hi4b?%LlzS12s
zx6l@icD@BJm|=%+dYev}+ta##BzfA*TqIaOWzu`>mK2%Q$4u2U`k~9huT{x+dyR2#
zcj0kk;O`yzEf1x$!MhF?w+!ZwPkP_YJ6eRaBh^b@C*!N)3oDvE9+pvb!EVG4Q@yUr
z{0HA+ow|wkC&dP9Xx3^aJ^Z3HQxX7pCwZJaT6@2Dc6y~1U=Q_E8*D-Nv>{8A;S3`r
z5(nOB_#rM9IJF*u)Ba%n@g{nkFA<^$LLYtrqrqj!o-gzy^B6A5zLc6^nyvJ2Hzn>J&>>d@pfx!r9`W+pb-n$h>b460tR()+7{$#9R>!EP&7~*Zk4Xk
z6J#X~AIvR?d~h56i0@j+!yeJn@zz+I?&xwR#6BmMB(y4$12XPh0F)Y*0bUl+#yNb#
z-;X@uLM!$lKz&-g))gtj1F76P!|NPMQg-&)&RjlWqbkPm^a*Z~#5^yQ6Z7e30JR1z
zG4Ixcn7R6`(cIBHpT;HYk4<0AcRUW8C^`0XjIes=JThS$K+u1R>^UvK&!~LWj`}onbR3z_zViTDk)SN@U54r&nW<@A
zlG8VFXeV8$gia+Ge;v%shd*Ya#%XI0dyJUJG!kfWu^aM)(ip_YP$uoy#*a_}41?+@
zkETR7-b^aW>q*pds@bXTQ%H9EpG{RC!u~A1=fTNKzM6MDYS~Zt@2^Nr`{U)q^_zeF
zMon!>=gBJ(Mum2db?U4td_yHdxQYxkgJ{@QJQ$`nOsH5(5J}32%o+gUhok$avMxL>
z@TPsypA&O7T_K5d_vC99jP{ucAPEVS?G?A`YjdCh{x#I<7NL~Hek)5o{~H&dRHa+b
zYc~LkJ?)4eyg1h~m`ln_9VIi{l!fsFVA|3G`%02W!EOC9Co6xAVm%nosL5^G+YkTs
zK)hxbKp{?(bC@57@LPm+{g)9I@&rLM+9P@Q43CUHW?L{?nh4$V46sMB-6C8Y9nXAO
zwK-cwEMJ_tz>Y`bydLF{Powr5M;b&pi^~G27+PpiAs@HgmdG!b1c@b90KcQn%}Pb1
z=V-#WUGA8)#3Osi)RtH)pz&NDr_D`=)8+%?UF`{;*FD9@O|pN!ug|=N_ZZ)FrK>)3
zcW(+`Y|1Gz*VF6t)Nt0F1tBs%4$vJ5efU@@hc)a1F4C!3}b-PpUfU9?Y
z@on*ZT(7q3`sUc2;H7`N@6WzuNH3SACh@Z_b&E5Oelv|;KbIq-#h?0mi3E=8m?gSZ
zT5UBp)KrGBt`I;N1?w=@A}T*S0X*UZdbgy>pBu78__rCb5wbWX63RZ0#c9XEiC=Z9
zw$HlxzSMqrA92~?gPTd>l5rk5ElC$n&OO~Rld;q>L3+i0h*aU3mA3Rry$?==6nFT#
zbb%(#O)R&+By_=Mkn2Nl7Z_BMfwRtP%^LOxlNrnu7jSM>vIOJGmfFLSnkDP=U7YqB
zMu;R&aN@1c?QTs>PBr~uvM#OLK+E@e#hfig&dPPQ*;Ak2EZg0eL3ULCU@bw2o25je
z>YL%hKc73cw>&0u;@D33cs}@EMFJj4H`)4I{TVTx}a%k;vE3|C~?7Z3JI(
zQ&aNh2j^4K0-FD>^Xkj;R}(3a7PdLFRX&5*=-^0CRowIXo{x?ARx3{TNB+DLVqyR7
zT>c`CK^y+Xqhjdq
z3_{@J#TWMV5U!uiA;dm@&yLxQ>CCyS!~IjHEn%N;P2T^mTua6lz+Yh_;H)QeOD{*{
zI9_m0cG*?-r6{k=x5j6t~@eUvU
zN`g}Jry(w5_j=w??p~ZcfmoC}SzO3MN4wvQo!KX73wHmoizBeind}>3R2a;tc0%nn
zbxAC}diIFPpY2nJ9J=3mD;<1qDo1?%>GN08v7_zDxBm3xOH>mUkot;zM(EF@!O@qL
zlT)1MS^np~IYT1SmosEF!Ljhk{?WmoWvwf@8VU0aKB&~)D3IJUdXobWTAcO_Ctl(b
z{$@&u&Ny=Ub2go|z{T4pM}pqBldmsu@&1(s`8T4pfw~qW4uA
zo{~O0{}U|n>>Ku3mgHNwzqpR4`unb3rObo~wPJcs!F##x4GgA2lV`SSiKXsmCFESajn#i3DT2#j3v=
zvtKLqnbe&X7J*iyKL%aWbolv2o0xmlE<9EDIn;d`jlHRrfK}UX>)*cFr}W|eJOna=`s>TV~}e@>I0cy>~AHN*w#9>pN0SW_DAbK2X5bQE9oLe
zF6Y)2l@%eikJPJW%}T@tnJ|
zD5EtVDL73(PI`)TG0A9n{)~Pk=U;MUGb>Rh5%O!Xv(qSpEWC!$(gZtAxCU3oK9M8n
zmz(0W8H3NMj%j)qZdHkxj7ZSldMd;<66c;zM^@7&Qq9>n&mkka%3uO+veJsxfh;&`
zA38a&U{8RmHRMq-7QoedWAc(r6Vv>tpzUFVw_4N5I{rOGJf|vQDlv$=7Q#{@3MSgx
zH+qLqD62E?a8M+(`f$j~D7sTC@SAv99I3ESt5zac^4Cp{>~h|i>AwxCW}RI^n_=2@8k*Blyl^!K$I`Ca^&2+-zK2B0yLD@%y9G$
zhKY~HNJAR+LmHIEl!|kn0KA=`99eF7-&Vl;L5(yW
z$k(p*$m&;!ufYp>VW#_`akg_ZjEI_ZC
z#pc__U{sJCr!W7u)?u>7DuP(Uwf)tzk69{DMkmmTlg|V+EiD04&-Z0QW
zs$~>}4f%cY!j78GgRShEEC;EKU!eVyW|gm;)zYqBC(I?u0{S7=b%=o7@h^@6>aa`^Lv8^ZRMtsYlNYxc+Z
zxtx0+iV#6I+~sg$d;g_1_W=^nzGKt9NC`$doql?Cmv;qfVkzTkKU*7QFi>>BO+gry
zfR4I^1wWnOkiGMJ?#b_vzA7Io47Ysa4^@%D`)1!_)CN;Fnsw2Ph*6A9
z-n0#M%?EtqzF8QuTP{>eVM^~Qn}{4z5Y5k{)~SPbe-D%v
zAC6-b`y@3TsaW}k%rc~la;ZKMpt({Bi3HmwJntg922Wzo$jIx?{I1P7qu#NR+(Y@<
z!3c|iee7;wrqr$Ko~w@xxnXXcW-<_1mX?JL(ZK4MP*k?x6QZQx(#y~&{k7)L0xh)N
z3
zJV#eZ4I4{&pUD0zkmVO#-@VszP}rn(^}o`6&B@8%(I7P!re%b+K+&x&9_)Yo=!Z=AAEf{#xy-*MY{GZwKk@D5me-dpuvFC~jvvx2w^~bFpIl
z_-wVZ|B*fbDR3%%1++K%sG+Z4<9r=idepJzA3x|}d#l|JD7~Q2A@~3xJT5)^bEETq
z%b)Ja_yi9APcOu!_NDUn&GiEr$I7x)Uu0!DWyht3O|l~e(=k2FJo5Qn6|U@c@4rZ{
zujPZ(#*8#7ib@q21?*@|(MEqL;gv7DNTDT~?H%(Oh-}Kga3OX(|LvDOtUr`2dFNJi
zlHIPq=ZVOdP5ySvEOL&mX|{FW=2^pk$;LlcVC<4V%HtsM29KYEu&ixT_yj92DLA_{
z!%XtnO06q@qFQn83hIB0hapmZ
z8rkz6VkZYku$St2nx}4jPfEU9>Qug7$WrBcYYPg1d5|-ao3%76I`<;W&1og^HUcMV
z3&qB!VLi$~Afv!NFW=tU2W21I1D3<3+dT~Z=(L_)_C0*?=-W~4_as02pN9u^Pa4}*
z7S6vO`JLaKz1$x|SWTTF6~9%#yt4YRdG09u`P`{-`i~#o&#zq*#{fa#L7;>%!N~-}
zi!4Ztg$%JzTO~mXg@p;kqsL5ZzfNU=lOJB1zq@5VF!R-wyMAN#1!h1c`{$@{$d*Fe
z#yhaV^&pylt1<_naNtKlMynypi=FytkjbP(n$219xteY(YpQWpp<99|mj1}lvAaMf
z85H1>qtV06wnr*zr+|&qmiu$T3JN2lGK$#uNSH}(Zp-ht665RUht}&p$lq0V-`>?x
z>N)Vc=UamK*r7QyhZSC*Mc6N{99H2JO3O1Bzz9(Svz~Dvhw{52K}vy-u&h*QS{E>=-v7#
z&qM!*4teBtI54K#?H`h^6KHIsN!P
zHxs~I%2g-;T57$~*sUP;FzsSAtzUf5x}x|Kho`({dbyf~o%%3g@z@g_l>u7nc+6D|
zycNQVr5EsQQ%w~d1=-GgaOPDJhk`e#W_qjURBw^jnB
z_u-!_KdSw+1P+!39vW9)wo4j(3W=%7EdI-frSFZmesLtA<(&#L`7SG-yQjjHw!*Nc
z%)1*>T}smb(4RJ~e#ri9z(7`+WdCpc_IKE*S{;}T4H{EF2u^2K;I3vzr0L|_tC{Ar
zqO(j`8I*Tqi~tWcdQvp%`{rISy*G4!$P)_96c*JUcg^k8*iR}n{|=2FzJvI9rOVXN
zj(W#t*vdAtqqRDrJh#HK7PwH8tvO#81Z7;_y>7yG3!Yy00P|xnMnJ#)r|Ms`KU%BO
zYej)yG{PfxEx`S4VH^8)dxbN<`I_wA?)3C9hwpy*ngNPZazB}is3`*=AI8Hfmv0Dq
z{Vd4b{?qMzR(cYmTHnAQXLwnqB4@dZ7(3~$PGsr|%tA-bJkSILC{QQz`lmjn&P!uO
zS5^4v`P28Ov1d8O$NxpcseLOJ)criMYd-!UAWV4VT?u}6=qFSvHMOt5&NyNi
zc1xyv`%woAG(P(9B0rL%zWcx5k-N-n=z8)>(gIkq1JWNgOtAECw{8~-D)XfYtI_Om
ztM4)IV94ZlGA#zvT8IIGU$=`
z0?5eu)yecl`E4xyG!9^G8iATilRG^{rd*rlePM&8e|8OFjn$4Hw))xZ?H7>|>cOL(
zn{DGP8pB}{ZESNroT;??8=MFv%d?Xj`1u<5-e#%6|8kskC7WOcQz&jQQ=2%^dH)tw
z5+F3MaFs>dL+|}m?p{rkylxlGsB*i^1uh!mVKXaEl&X^bj=vYGs_x891+6_)Q_FfR
zqx5ZyotzhHki`ZL^3_ds|C}g%Ke3>tv##rkU5uT*-0sXKK;r<5eqEZSL;wS?xaX*|cr=?%GxO;9#O_^sVQrCuhXsYUDse_;KF9@mu8
z^jOPu+ok$PM?UfJYlXGNXacT72?I*)JBinATdp>G`J+Vck9BeU$6;GQ7KbSfc>`}5
zSB2hDzO=Y7ZclrXozYDharB3EM6=ksH
zq%G-xw$xUtLk{vf0)
zXaCY!OMF$LR{yHH?3TikCs
zy0bX)M92z%rVkkGc10ZZG>j-3#+A+7O|ld
zO}2WNGf`Yxg5h;>lbO5Rh^3ws{(LPFf_jPUYX|Gm0hI4cO2(H_up{MC7L#eQ3p60^
z6}!HqD1EIc@kk$F?O@_WR=?!Yx1HXH67(}cCQr`8UxdbZ^`s;$OCu}_w*#;PlUyKk
z@K|$VIy+-&#BVi)M4L&%Fo-7`A}Zvmb2?IWFCz6*hA^cP>(k4RUs_fNu2W&AOzNLK
z`Z7)(70rHtR2sOl1kOru_C;&wewfLp{h>79c=Ka!VG41kPlA7Y`14Xwzjw>68PxME
zy&5|*L!{keDb;RSXV;PgS4;w~o8u7(gE$6Fvy8|Ni+wT%KD))j_6+ILJzeAmxE;Sz
zAOm3Fpez(O$i`^e_e~^0@JrjRms$;ztI(LTEot?8>(cN%K&B&Kx@5)>*fX$fqN)hi
zYb--hh-cKI7HE0HGH{V1@4c9UlUUzrLlG!Tb&iFS8Pr6GYD8)*sb
zwWxpZq6RPFsaqCZ+`uEdUwSkn7>YURslCo`h92j4xnombL|hV(J!71w6jC*U)Nrz2
z0NI2fnvCO2#NOyjVSGDZ9WYFNcFg80vRw5+MMD!8s{jTwmoou(4sZ6Q9Qxt3%d5H2
zukF+=(UNQo#z_n_C(Z&&Z|y?gQDzPZjWKkC4r3mOjk^!W@4WRFW8YiW!eJcR{s%H3qpe!jqEk8~HvmZpN=`p$h&M7%W62hgLD=h)61qWf`!TQOS6rYzoU6XxjGUvQ!dZ4LYe2YOyGU>#o@B2oMHSOgVR=+
z#kI!$eDe5A^s1grV+IA75u+KneSlJK6Ho$y{Pcz9Hb45#L|#JF_j>=E%tdEYunWa0
z3`wp}Vm#J)`ik9ZQ--yPLG{rL<^z#TH3U`<2&v7aYVw!Q|y@eFUXd6eMs+fCR_R$f>TD
z9l&ZmX}wL)L%NB68JIdcOMPG3Oe%b-tJ(p%5QN(nZGZ|(KLg2A2O%hv#mZ5hIptRpGuYJ~km>FgrGM;6&+sizUKXgm7CMN(t
zBZ+zr;LbAOAI#&LG=)(`2rx$cE@0a7)W|G54kj1=W;rqLbqZ
z+BH`gY<2X5+S!1ugJ0S9L0sl><)~h+e}|XlyZ4Yiga5hJRgX0fC>lQuF*C_ZjxPz6
zB*1JaYO}0o#?yp`E---9m@EgX=|>U-nBfd6NSk7)C6-s~&238}wy3kFm$^l>&Q~vu
z^|4+Pm3?`XBD+(KhZhHW<_G)5MLdv`@KpRrxGpugaoys$#C~o$T_X~141u?lFva*6
zi|j}zeCLfaJL!w&R)YSyDt=jwe_a6#o72GAn5zFrOLR*l(`6S`NIHlu0iUD%RH
zMN)`QyRa
z&3nIQ3b4%!E3UC_1!2~~>o3MaijBu(9e^%hBTZBv!M;Rx9Czrl*|tWl77LVGG-*iG$n?_YpPmEzP?2L
zRaA*s&bsaM^3qI(w2G^m#nVTk1b@z(y(uf9nG>CLKLWW9Y0W5mWz3acqXI~ev*d0R
ztT(yOXvCg0X=x=rQCA$#$1@n~*^+TCq8Cy07pf&Pp5V7Vo$9%XetG6`m?(I|uTH;&cAHTf7XmR(lxT2|+l`%DJ%rQ3w
z;{QRi@fe8c9B)=^UNl$uYZWH;UgquFRI6!^v|NcFIMF7M*1VFx>bJA=N!Qv(HO!Q3
zdMJ-uUNW3aO&m)=To$VUG_;Lkr)EtRi=(RpIa?y*&!UbS%^hHb@cxC{96rmtU)C9{
zH+q>}kt^v_2b}E)(&{Gc{;vU7SP+**?l8!G*g9v}xR_QpPC7f4vX|r9PBvG338kzw
zzeGI5+LU+;1Bsu{T`$G}CiIWUEGiMOopw%Vje6$TFuY7WuQaTaI!|&<4;FY*xK;(+4r6h^-L{r~wC+g%_V2
z*F{H#!$T@F|5HCGdm3jC$Iz}Xz15$?p~nF_$bIqsbaNb}Q9
zIWT^DBxcx`n83|rpzcMyPRiJE7xrTPw0b|Jc=C0*#1a(Um6-6I9X1|}Ax^vSFs#}6
z8K)T0REyQUqWoK3o94Ap)H$IX4{6rweO}OBRb;pr8oBTC+2G=U#*?$AN5K={p=n1=
zn%RccET;rSEy3yk?Z#%mK6w2sYaSDjaqs%g7Qj(%J*xx_=}Cs!{o-xi_BiIt?QU+K
zUS^6%PD#&60xUnRN=oo8kyymCow~&ATCsogO%Yh}w*=S=P<4$EFo5Q@JFZjk*Bmdy
zp(*|juJ&QCy*oXa|
z_lh(-xdc|({KxrLgeW-}+OQ!n@Fy~5SZo|dxF&%i-@&q*4O@nuvSP2N2iAP~GPw-J
z04fp_!d>iKzE*u74?~vDsK8T_vtChZ_1h)$vSk|YADHbYmXFU|kvAC9{`h?t)hVT|
zga^^;NWmfS9gEj4#c5yGz65^SkulB=Ts$MwWjD|%_bKy2FAeSnA_}RhE3)O-$!@8r
zCnb0nb_t*<{iwiIeqFG_FsB+b4NI*@
zvv{uqe#*^oRNQG@{H&x*76^x#p@QK9i3tOIz(%|#a%H{@RDatV#54K0pqJUe4_c{L
z(Xx>$8IT%=SWZj`wTCL@#R+H0L9?BeTr;zg*%bE*6L@zv78s;-Uz1Ms<-(CyE$@7q4fabjiN>_fOqCI|CLXlFKi%
z^YXK&xwV^~Y)H9z-8Qfa1>xKPl9YCj&(m2Wz-Wtjxfl_Z%XX$=vYTyy@sKkB@|sD4
zb}EPoH$axuO}U2Wn=|Wu6PW`}r~X
zuuWxH*_=7@FQ-lR`ZLj2O>zJq)V+sql<#tlyJj3dG}KQ;I`UldkcB|LfAjbNQKnkG
zMpO7T)U^G}C&6Y-jII8Hg))EHh@bgrU^N9iB2#1|3{uB_zB)oYY_sLQtYaVrup!C)
zkiq~*Vy$P3gD|MW1J?5WpI$p_bVgy(!9dl*~M;|kS
z#xHFL+?oi@dDY5=W_@-w?n<(goCR@H$JVgkwg?t|$iL2*&J>5_B9ZRRkH3HT(
zdwb`M{oXH>lIc~-w@HO%4Lx7?lUKF_8p1F5R=8y_^k&u+O?l#_q9IlMfo0JsOxK-+
z0V|42_&plUDV0+JJw{%J<+iQbKDZB0;WulW}
ziykNm@^~Js_=|jxXf-7m?I}+%v*1^rjk>q`>jzsB#9?S2WRmd+o>X~one9D_$H1em
z0p&g|UZ3y_=Gs})KPG$P0@edHc{f2*SjNM+}@&x>>
z=LzrN4rE!8_6STpR!@4`^TeEA#M9w5RkgL4n6|W8-D6wwpIZ0IMV}vUUGZek?KgpD
z1!?_*!+bOo@1H_z-oVKgqAds<<$1tA98^)hMf-u1aW;#N4>>4=6n}(p>C{hF{Y0sp
z41bRFoUy)P#!Ujfd(B5A8?a4Id>@ahOaEY>XIIL;R=>en_&X9j-azTWXG(3Yjv*^9gSfc6|D$@i0
z12L{|7h#rw#I1a^0UdY2aP*2(E+mdw*IA|Sjx(Co^f=r2{<$t<0fh*kB?(BIZ3IFM
zG(92_t$$C}{&wq@MwB%xWbh>Rm=347LxzxRxPxy7UhG2_wPSkEe#O=>+wFeVon^-3
zY9zMa!q`*$9JjTSsLtw0vGS?E=g$`!rJpkl@Oqe@w$^pMOv8Sfzt)GS1cEI;V{x6P>AX^$BqyKmEB;=22WP$dvMqYFcIqbEp6YNDpcGFN<>b@@c`(1cH;gsX-`hV<@o2Suu1uE
zCOo=dBcE#!(*8VXl*iQ>T<_?kz;8L`wmUB9D+a^rZuOe1w1Z=YV66FqQokzW53)J>
z2E#)#sCvUN?$J*3P6I1sZ=rmYuA052-85Tg9Uw`uj#q2h^a?-`a&vaOmry%*jhuoI
ziLs+1p0O4E%GAxi?Wj~Ab?eqYAlqz;!nFz$lhicNELO6yz<-XJ3$HxSKwsHjI3hFu
z`n#N(f&wq+RVLcsvC!O@Bhor2Xeitk7htPo?_-Vgncf212kPrEH;pS?Tf?do|5KRz
zuU>R4sm)cm$kb~rvFQDjXW1E9=H0WghIG8ncZl}g)S`lh2R(YRTE^iSNBESKJZ?l=jo(dmmlLGE7FR=`bk`4=z+qU@;w5Q7;%Hv=XivJ>6yvm0Ercj8=s2X8D>w
z0#-hpnxLF}l2$OLy!RW7uiE@8m%}cQy3u=XEl7PrXX;B^Mv4>gk~_5XYnOcrFh?yX
z&io9&b`5`cQ*g+g^qd06d|-2wwn$gI3RWazp~ZVmS%biKSb_q>C<)
znFhO#rM8lYOr)kQwyQ?*)W}1gLwOH(QP;#aCu@>Dx-x8f$0d1RP5i!KF&02YUFAs}SzbN>
z1$!4cf<~>1sh8WxJHmD=YXZO%>=Q_wRZ>V292QN-@mnyQFX2~GV;|7hR{Fr5P!R@2
zA*zAtM6ijp{&)?bK?+Px*rZW$U3KL|`PHXc?hAUj;pk)$u1Df_H^19mna
zw^aZ${F4XpUpEn2xnWH~<{%Z^49i2rj)lTahzlry^nZ%%nU1_zD%F+&p8m-@@uWxl
z#o;fBZAZ<`CZvUhsZpi(c4`QF?ZCVOvq9Ny89N^F9!Uhjbzf2E>V3q=Mqh-a8x>t$
zrdl035l9STjtVC*8TtDz859(A@F}tvX7XFYNP661B^TxylgYlDqy_``jjGsn0EOz*
zj&&Rp9p@jQx)2v(W3d{5ApVHn&mKJg$FOF~Cgb#8SSxuaTc>33eE$2>SbB5#ABz4W~0VL^^Un)X^ZZlWfXAfvX0-N;7k_o@FapQK-kPwiyp!+
z7A5zC}%`9clRI#Lr){aNd6ZytgdMi{LV~7
zqi`!(m)jCX7y^z8o95a6y}{)=PxWjK|F9r6*D$?SHACzI?Cekn)bMpC8Y%7Au%DOX
znxxd98r^V>12BM`L|6=;%-1Z;hUE-Ct~r^ei-)fITH3J%Tt(02Q+zr~-3DON2ocS?
zNcbslYtrkbjZk>n%M&1HeAzZ*`dX
zQ<)pYQGfXW;mlemMp?G%^9iO=tlYzbNMKAr0bDSozeQevuH53~^k<(n8l8!iL(5vK
z-wu9fW@*;u%B1NoqJe=t>G4&xfAoL8;Jaa|rsM0m+EADZ>RfX%T;|beVrvsQkMXrl
z^nM|(R+ln7N9;}<#=qvlp0JnTd7RXrFz1I@7R~)JSfxW^vY7y5Xwi#HiW(2r_iF@e9mx{pq36_9t046GU~VF(hd
zArrl>b!#{<21c3an`+DC<#fd`olOV)$?QSB4#Ay1^eI^%dfVrB2nDGN?*S>()|d{b
zyg8zIg(CA2@OH2Cy+&TgC)BFS)`J^LhnEuul>xEx>_S5+jpKTd>tpw~Ycu>`lcIVdcpvLRqn>5z@BbLaMKz^D0d&vgwhT|L=?VU|G5N~P10?Jf
zi(s*hGX;z(<{&|(h>@a$!!1Hdm;V;N@DXB39P1FQ2~JwG7XBM$8Dz6nzWK|n%44C@
zXh8}4ANlt+E9m*3)G7hYX~db`9^GLD43QY^0y6d}f&U*`_V+AIVY*bX7-*LQFq{wx
zk@QKoEi`INgoNz5rc)Y@#}yE-7Y(FW@qS)XopjNO2EExus;=(Le*tjyECOgzO`_T~
zu@4igvV})Md$q+{S;7^wKN^*9q9to0<7{p;lC%Fd-*rf=37ZQLRUW_W9YZJdzFQXXW%y0^u~^85;7qKx
zq*c8UVVZ9b0xH&+HD*@tF&|t%9w;`1zY&q_nan;kW~+YxMw1-wO?kDjM%50gA3t+*
ze9sSvBevzJ>?n{IHGB4Ty^v4A
zH!54m=*mY?_D(9k3xH8UJgeV#^ckv29_OoX(R#hWZFP<=RKSK(XMyg9gL;vx#%LV=
zdnjBTx_sw0ruQg8E)z}c)+b3kgh;>C>;u#NvzvRsIfRNiYEUDhsr2qNwu6n|E6)!AQ(P77?FoPot~E>c
z0Z_2_M5FEnlIHJeIs9%j2#PNswBi*zMv4D)3JGa>X)1Y@pW9BkiX7;%-(7{RiNycs
z-#5)bd;CQb!>{0C^7A2_N&nQ+Z#Ov0+0AV
zVIZh3Uq57Dv|J^=^efSfD1+0h9Nztzo0A2rMUlL8lDRqZNI-(x#{jv9EiPdsaY`nf
zK8(2f?dFM8YnuRE*j&&h4SJrrJCS)$7B^9$h}xXA>>e65BX&6`jY_1zwpoJAI}F%_
zPj|(I+8CdIn;*3_uuBzh4>74~zUqK$*l4V$m%F4NYaFS-CtBlDE$_R3y_+m*MxA5Odp7j;h4qCa!_B$=hu&Fvpd^
z?HN^?nNa|_UiFPh(Rrndn5V>U65>drbvf-fs4pB(rYRrBw9{U@rL_ynA-?!?CI9bJqC*11U;2SHAtmNHrAvd1lxgJOte5T5w=tsbqI1
z=@X+2XA@6gU99%-ah~(nof(X>eXdYcS_3jVbK2vRUW)wS{RbZfmxJEu$^M0u3$lDH
zO9U6|1}xNaYMIk8$E3eb(9?cjK%t|=#IJ5>QnU6QCnNArA7k^-QG1Nxp+;F;>VS(U
z4#9!jyeOB&;o+HTVe4eNP9bRz-!oC>OUXAFN;fQnoAcl>bvTRb-0JSn4)SLVi
z12$G}INIsRGA+mXM95;BCkCI`M$_g!;iSOEMr+jjI+4#2${J4?aW+s*>0T%4-tTeG
zjW+yKySF?0{FqN4j!(P(LoAu?p31I4M!)8l2gXTnpd29cemIrditf=5Gx~}y-^jcO
zz&K;ylfkK?O=Up60tw)S4!A8Ndod&REBzUwe@Fk=^aS*kvs8tbZ(i#*t1NWz#`Gfk
zZqORzF$=jxv{-_kI^sA(=Z0gx6OwZX?6ERip;3CmR%hGA7N6txRsV{zbHGoPL
zwtx+y=7<-hapyHoV@pd1>K8e0jaJj5au=MgNpSBgF}i*gFPl87ez$x;=!d^8J*VtV
zXkC?DdS}T{;w6zXQX?L22)jQP?dgQ
zj*BK;E3GrKHg23uU&{>2B*D3d|7uljKaZBfgW@IC7<~CI#hps5@FhPlod7f7tS02=+hUNlysSV-7mdoare~md1duq;8qR|#JePPwHfx6E5=+UC*i_Y#6Mi*IcVdAhc#F4oldhT+F;{*wE_i<WGyX@yl$oy1-w`GrBcP{sdW5
z!^Dwn^0R?{8bWwj^h?N?rC7TIDVv6eBq--C`!5$Emhp*ExoK*%AIS`nKc>5uhoqNl
znLC!K&r8pfQz-6)Hk!F?0cse`+NVW_WOGnncf-g1ZMDPB1R_3Zh`oNE;>~zxQUD=c
zCmHfc@t(MftOkqX7)$RVe$ikmy#eX$IvciAxoSnuq7$uL{3QXSY#&b1zjXNDDYZ{!
zB}l^qw28FC}~x
zl?L&v8i4VRzuf;SxqK`gt)~<}QCLU2cA*2QWYkllt>X;8q2@gpG$kf1A?C^?Cp;TC
zDB#Z;^_uX|gt;jSE$w=d3{7t{8EyE)%>lqx>f3R`zI3#lrU9x0$pUxsAmoQot4RUXDgm=l
z2=Vj+PMWP~Uh>-&l8<<#fRMPU;#Wkw3ajwx#>bKfrRw+P-4A|785KmiJL#5%YIw?t
z_U@M)399q#_fPe?(ic<)WI@rhGhcV|RObJ!P!tE?eZ3Z0AUJt90^}|T*iywIyK0bg
ze!dR6bb(Ap3760O&)+r!G2VYSJMAes>6(EXG))a5QmTF+zCZ-Hv?#k?>%=Jn_(zk&II_5R-78-gSQ!gpAP1R
znwRvI#ey=gC&SJLzHkAVKM;;Pku1oS`_QzPeigE@pbFa>H>@JJ?QGLdQ}pRRFq6D_
z&Ejb8mw%Nf>9kL)1^A*#jS;p+(2nTuaAE6Gnna
zqzV?E-~QxljMaRMPCx(#AGQ(jn0efI77#BVuwtwblvk`;Ku;BM3_@f9XCFxA=3Gw<
zZe^)Jy-ZE}8%DUo2_GhnP&40ET_H;=%WS3u7f!0%R+k%q)
z{kzlH%R>hdv*#Zh>lN1A+VF!uG_5k$%i_Qzq9i+mRH(y9;d$F
zkAFF@mmr;~7-h(NCoPV`iOPPWf}@ypd@77EBql~5R{9aLJKrOF8NkD_$+l%QP2y+R
z4p{uIpk&%IZ0?32D-@dvG1Yi7@UN(8_@?rz8UxKGSYj4LrEy=;uL<_{$1}e|5w&{R
z+#L0loI%3Hp=z^A&sPE{Bq|Lh+iEuQV`D#0GV_Bqa0)5#KQ+i>y3S(2Ele>$ZAe~P
zm#&4?&1yhOrBcNmc4%a&zS5}n+qUXH2E{&jtFNJEHsEjj`O*0jqY_Wo-e%rc1aVGme3(^
z6I_M6gDApZVuI&Z$BI1h6cvcs6Sl}<5A^UVcM`*pMRV*hg
zt-#_jfuy>MF%Td;_L|G!=1PE2DXe*MT8sV_ivD5dFjkM0x(|@8)h@t+wxlx_QyK<5
z;E=XRyIL`ykYtnwe)3qO_NZ?u%uK?Is7q%b?3S?sEdmEXGsL6)<04}QiX|mCD#PUx
z?B@^6E7|A8a4+e+7qW5}h`Pp{xqYw1a`o4^m;
zlFMC)fUCj0k&IJejAR@qG>w~@q@
zPv@@Ox|@#nA6zKsS;d1%X@A>rF*s}KLB3Jma(-}BUi*7rc$A@7RBp;Iq4v@mB+Ju}
zfkrrD-FNm@fbdw-GClgaSr#`#ezl=5m&)b7Os%ji&$GNFv(9N>9&6)(8qe4oh
zf-310`(XsMU32sxRFi@3660jdX%_(maP6}Ot@qihYXRWmSGd9LeBo$QaG(N`40l>=
ztHr;sv-DSPyii*ccp+rB%B|op|J-h$f*$CI!|mrhj=rcmU?{oN6aViuA84djT8*ZM
zPm;O1M@eE#%~LX#`zGG6Ts=qfb8{AqrG9wpw-^I`*sOlm`=tU12t6{*oB$o1)F>Q4
zg#9TOwCQ>}&IXFV7Mlu;XeGDpfZXO;dF{mi^tZje>sWf@$dQq&fE
zo)7LEnko+bW`^*)IAAsG4$ZF7kT8%lCrY%4@@o}uO3WcESoPh(#Pl6`nsy(Wkb;JCMSF*U_FJa3xs8=I`uFh;xZEfK2R2sGQS-s&
z3|Sm6^EF?@5VTZuAg}u}&-?o`GUBz^Q-Y?8&5E$S~emKrNJJVzHm^1w&bAoZ(WPbJRPKJ~Z#
z1kCO9qu+Xld%&SK%KkanKv5-&y#=wuy6T($R|B?SwAG&#S@-W;PLJQ&dvS(5la4lh
zIJaz>)N>Hg$Don;M9W&P1khjaVE!(3U)T~`r0&0UN2jv)->17VbQ}u%ZTOv+F!rs>x++}Mu!N~s5Wiv)Bf7)X^|$v7pzr&
zJ$TWC)PODr?P0yY$h+E5{Aso()e`$&qxr%?6Q348F$6B6N+bQYF!grJ*VPDvg;?<}
ze;w8<55k?Q5>lNQ%+w1J4%U~n3C>8d%-yF?WW8y*p_vnAW9%-l($=c|Qqbk=w}4+C
z+Ou0WHa1$ajsGl-7+?Oo^ygkz=as1|tNZuwU%j%uy>#!&%Iv
z_Ks+{l`yoc3!>uCIAp{+?E~pQH2LQvdA0Tlp{b#jn
zYb7k<-)7b6&qF6J{rT+fH{E}_YirY+oML?EJ3`kO9`a6^K+art$NsL53fwY`%vXCz
zUBz~7cpGH|;x1&J##(hXyX=FSv1Z-I-e7txSU)9~q8o>;^5o5iuinVoZV@a^{mJnq
n+!2`J&TS?G}fW5%h3

literal 0
HcmV?d00001

diff --git a/doc/images/qbs-settings-gui.png b/doc/images/qbs-settings-gui.png
new file mode 100644
index 0000000000000000000000000000000000000000..197340882b7997d8384adc5bb653097cb7455741
GIT binary patch
literal 31364
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 1.9 required";
+            var qbsApp;
+            for (var i = 0; i < explicitlyDependsOn.application.length; ++i) {
+                var artifact = explicitlyDependsOn.application[i];
+                if (artifact.product.name === "qbs_app")
+                    qbsApp = ModUtils.artifactInstalledFilePath(artifact);
+            }
+            var args = [qbsApp, "-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 00000000..b11beeef
--- /dev/null
+++ b/doc/man/qbs.1
@@ -0,0 +1,79 @@
+.\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.47.7.
+.TH QBS "1" "December 2018" "qbs 1.12.3" "User Commands"
+.SH NAME
+qbs \- the Qbs build tool
+.SH SYNOPSIS
+.B qbs
+[\fI\,command\/\fR] [\fI\,command parameters\/\fR]
+.SH DESCRIPTION
+Qbs 1.12.3, 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
+show\-version
+Print the Qbs version number to stdout.
+.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
+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 .
diff --git a/doc/man/see-also.h2m b/doc/man/see-also.h2m
new file mode 100644
index 00000000..7de95809
--- /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 .
diff --git a/doc/qbs-online.qdocconf b/doc/qbs-online.qdocconf
new file mode 100644
index 00000000..fc90ebdd
--- /dev/null
+++ b/doc/qbs-online.qdocconf
@@ -0,0 +1,19 @@
+include(config/qbs-project.qdocconf)
+
+HTML.footer = \
+    "   
\n" \ + "

\n" \ + " © 2018 The Qt Company Ltd.\n" \ + " Documentation contributions included herein are the copyrights of\n" \ + " their respective owners. " \ + " The documentation provided herein is licensed under the terms of the" \ + " GNU Free Documentation" \ + " License version 1.3 as published by the Free Software Foundation. " \ + " Qt and respective logos are trademarks of The Qt Company Ltd " \ + " in Finland and/or other countries worldwide. All other trademarks are property\n" \ + " of their respective owners.

\n" + +include($QT_INSTALL_DOCS/global/qt-html-templates-online.qdocconf) + +# Add an .html file with sidebar content, used in the online style +HTML.stylesheets += config/style/qt5-sidebar.html diff --git a/doc/qbs.qdoc b/doc/qbs.qdoc new file mode 100644 index 00000000..5b9b0b47 --- /dev/null +++ b/doc/qbs.qdoc @@ -0,0 +1,2047 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// ********************************************************************** +// NOTE: the sections are not ordered by their logical order to avoid +// reshuffling the file each time the index order changes (i.e., often). +// Run the fixnavi.pl script to adjust the links to the index order. +// ********************************************************************** + + +/*! + \contentspage{index.html}{Qbs} + \page index.html + \nextpage overview.html + + \title Qbs Manual + + \section1 Version \qbsversion + + \QBS is a tool that helps simplify the build process for + developing projects across multiple platforms. \QBS can be used for any + software project, regardless of programming language, toolkit, or libraries used. + + + \note Please report bugs and suggestions to the + \l{http://bugreports.qt.io/}{Qt Bug Tracker}. + + \list + \li \l{Introduction} + \li \l{Setup} + \list + \li \l{Installing} + \li \l{Configuring Profiles and Preferences} + \li \l{Managing Qt Versions} + \endlist + \li \l{Usage} + \list + \li \l{Language Introduction} + \li \l{Building Applications} + \li \l{Running Applications} + \li \l{Installing Files} + \li \l{Target Platforms} + \li \l{Using the Shell} + \li \l{Generators} + \li \l{Multiplexing} + \li \l{Custom Modules and Items} + \li \l{Module Providers} + \endlist + \li \l{How-tos} + \li \l{Reference} + \list + \li \l{List of All Items} + \list + \li \l{List of Language Items} + \li \l{List of Convenience Items} + \li \l{List of Probes} + \endlist + \li \l{List of Built-in Services} + \li \l{Command-Line Interface} + \li \l{List of Modules} + \li \l{Command and JavaScriptCommand} + \endlist + + \li \l{Appendix A: Building Qbs} + \li \l{Appendix B: Migrating from Other Build Systems} + \li \l{Appendix C: The JSON API} + \li \l{Appendix D: Licenses and Code Attributions} + \endlist +*/ + + +/*! + \contentspage index.html + \previouspage index.html + \page overview.html + \nextpage setup.html + + \title Introduction + + \QBS is a build automation tool designed to conveniently manage the build + process of software projects across multiple platforms. + + \section1 Features + + \QBS 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 + + \section2 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. + + \section2 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}{project} that can contain + several \l{Product}{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}{rules} that \QBS + applies to produce artifacts from input files. The input files can be + divided into \l{Group}{groups} according to their type or purpose, for + example. A group can also be used to attach \l{Properties}{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{cpp} module: + + \code + Application { + name: "helloworld" + files: "main.cpp" + Depends { name: "cpp" } + } + \endcode + + For more information, see \l{Language Introduction}. + + \section2 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 \l{build}{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{cpp} module determines + that the C++ sources are compiled and linked into a binary. + + Alternatively, you could use the \l{CppApplication} + convenience item that implies a dependency on the \l{cpp} module: + + \code + CppApplication { + name: "helloworld" + files: "main.cpp" + } + \endcode + + Additionally, if the sources use Qt, you need a dependency to the + \l{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{dmg} module contains + properties and rules for building Apple Disk Images, which are typically + used to distribute applications and installers on macOS. The + \l{innosetup}, \l{nsis}, and \l{wix} modules contain properties and rules + for building installers for Windows platforms. + + \section2 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} 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. + + \section2 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 + \l{Project::}{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}. + + \section2 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}. + + \section3 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}. + + \section1 Build Process + + \image qbs-build-process.png + + The build process of a product starts by examining the \l{Product::}{type} + property of the product. It contains a list of \e {file tags} that are + similar to MIME types. + + The following example product contains one file tag, \e application: + + \code + Product { + Depends { name: "cpp" } + type: ["application"] + files: ["main.cpp", "class.cpp", "class.h"] + } + \endcode + + \QBS then searches through all \e rules available in the context, meaning + rules that are defined in the project or those that are made available + through the dependency on a module, such as the compiler and linker rules + pulled in from the \c cpp dependency in the example. + + When \QBS finds a rule that produces one or more artifacts with the relevant + file tag, it looks at the depencencies of that rule and finds out that it + produces artifacts tagged \c obj. It then finds a rule that produces \c obj + artifacts that takes \c .cpp artifacts as input. + + \code + Module { + // ... + Rule { + inputs: ["cpp"] + Artifact { + filePath: input.fileName + ".o" + fileTags: ["obj"] + } + prepare: { + // g++ -c main.cpp -o main.o ... + } + } + //... + } + \endcode + + There is no rule in the current context that produces \c .cpp files, but + we have defined \c .cpp files as inputs for the product. When we added a + dependency on the \l{cpp} module, that dependency also pulled in another \QBS + primitive called the \l{FileTagger}{file tagger}. The file tagger + looked for files matching the pattern \c *.cpp, and then applied the \c cpp + tag to those input files: + + \code + Module { + // ... + FileTagger { + patterns: "*.cpp" + fileTags: ["cpp"] + } + //... + } + \endcode + + Since the \c .cpp files are input files, they by definition have no other + dependencies, and we can go back the opposite way in the tree starting with + the compiler rule described above. + + This design works well for generated files. The \c .cpp artifacts could come + from another rule that produced them by processing some other input, either + instead of or in addition to the raw files listed in the product. + + The compiler rule will be invoked twice, once for each \c .cpp file, + producing a separate object file for each one. Then the linker rule will be + invoked. Its \c multiplex property is set to \c true, which means that + instead of producing one output per input and invoking the rule multiple + times, all input will be collected before invoking the rule only once to + produce the final application object. + + The standard versus multiplex rules map well to the compiler and linker + processes. The compiler takes one input file to produce one output file, + whereas the linker takes multiple input files to produce one output file. + + Finally, after the linker rule has been invoked, it produces an artifact + tagged \c application. Because the product's type property did not contain + other file tags, the build process is now complete. +*/ + + +/*! + \contentspage index.html + \previouspage overview.html + \page setup.html + \nextpage installing.html + + \title Setup + + \list + \li \l{Installing} + \li \l{Configuring Profiles and Preferences} + \li \l{Managing Qt Versions} + \endlist +*/ + +/*! + \contentspage index.html + \previouspage reference.html + \page building-qbs.html + \nextpage porting-to-qbs.html + + \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: + + \list + \li Qt 5.11, 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. + + \section2 Documentation + + Building the \QBS documentation requires Python 2.7 or 3.2 or above, + as well as some third party Python modules. These can be installed via \c pip: + + \code + pip install beautifulsoup4 lxml + \endcode + + Regenerating the man page requires the \c help2man tool. + + \section1 Building \QBS with QMake + + To build \QBS, enter the following command: + + \code + 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 + + \section2 QMake Configure Options + + \QBS recognizes the following qmake CONFIG options to customize the build: + + \table + \header \li Option \li Notes + \row \li qbs_enable_unit_tests \li Enable additional autotests. + \row \li qbs_disable_rpath \li Disable the use of rpath. This can be used when packaging + \QBS for distributions which do not permit the use of rpath, + 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. + \row \li qbs_use_bundled_qtscript \li Use the bundled QtScript library. + \endtable + + In addition, you can set the \c QBS_SYSTEM_SETTINGS_DIR environment variable + before running qmake to specify a custom location for \QBS to look for its + system-level settings. + + \section1 Building \QBS with \QBS + + It is also possible to build \QBS with the previously installed \QBS version. + To build \QBS, enter the following command in the source directory: + \code + qbs + \endcode + This will use the \c defaultProfile or pick up the Qt version and the toolchain from the \c PATH + if the \c defaultProfile is not set. See \l {Configuring Profiles and Preferences} for details + about profiles. + + To run automatic tests, the \c autotest-runner product should be built: + \code + qbs build -p autotest-runner + \endcode + \QBS will use an empty profile when running tests which means it will try to autodetect + toolchains, Qt versions and other things based on the system environment. + It is possible to specify which profile should be used during the test-run by passing the + \c QBS_AUTOTEST_PROFILE environment variable. + This variable should be set prior to building \QBS itself; otherwise the \c resolve command + should be used to update the environment stored in the buildgraph: + \code + export QBS_AUTOTEST_PROFILE=qt + qbs resolve + qbs build -p autotest-runner + \endcode + + It is also possible to set up a separate profile for a particular testsuite. + A profile for the \c tst_blackbox_android suite can be set up as follows: + \code + qbs setup-android pie + export QBS_AUTOTEST_PROFILE_BLACKBOX_ANDROID=pie + \endcode + + It might be useful to set up the directory with the \QBS settings to isolate the test + environment: + \code + export QBS_AUTOTEST_SETTINGS_DIR=~/path/to/qbs/settings + \endcode + + \section2 \QBS Build Options + + The \c qbsbuildconfig module can be used to customize the build. + Properties of that module can be passed using command line as follows: + \code + qbs build modules.qbsbuildconfig.enableAddressSanitizer:true + \endcode + + \QBS recognizes the following properties: + \table + \header + \li Property + \li Default value + \li Notes + \row + \li enableAddressSanitizer + \li \c false + \li Whether to use address sanitizer or not. Enabling this option will add the + -fsanitize=address flag. + \row + \li enableUnitTests + \li \c false + \li Enable additional autotests. Enabling this option will export some symbols that would + otherwise be private. + \row + \li enableProjectFileUpdates + \li \c false + \li Enable API for updating project files. This is required for an IDE and implies a + dependency to the Qt GUI module that would not be needed for the \QBS command-line tool. + \row + \li enableRPath + \li \c true + \li Use this property to disable the use of rpath. This can be used when packaging \QBS + for distributions which do not permit the use of rpath, such as Fedora. + \row + \li installApiHeaders + \li \c true + \li Holds whether to install the header files for the \QBS libraries or not. This option + is required to build against the \QBS libraries. + \row + \li enableBundledQt + \li \c false + \li Holds whether the Qt libraries that \QBS depends on will be bundled with \QBS during + the \c install step. This option is only implemented on macOS. + \row + \li useBundledQtScript + \li \c false + \li Use the bundled QtScript module instead of the one shipped with Qt. In that case, + QtScript should be checked out as a git submodule. + \row + \li libDirName + \li \c "lib" + \li Directory name used by \c libInstallDir and \c importLibInstallDir properties. + \row + \li appInstallDir + \li \c "bin" + \li Relative directory path under the install prefix path to put application binaries. + \row + \li libInstallDir + \li \c "bin" on Windows, \c libDirName otherwise + \li Relative directory path under the install prefix path where to put shared libraries + (excluding plugins, see the \c relativePluginsPath property). + \row + \li importLibInstallDir + \li \c libDirName + \li Relative directory path under the install prefix path where to put import libs. + \row + \li libexecInstallDir + \li \c appInstallDir on Windows, \c "libexec/qbs" otherwise + \li Relative directory path under the install prefix path where to put auxiliary binaries + executed by the \QBS libraries. + \row + \li systemSettingsDir + \li \c undefined + \li Directory that will be used by \QBS to store its settings. If not specified, a default + platform-dependent directory is used. + \row + \li installManPage + \li \c true on Unix, \c false otherwise + \li Whether to install man pages. + \row + \li installHtml + \li \c true + \li Whether to install HTML help pages. + \row + \li installQch + \li \c false + \li Whether to install qch files. See + \l{https://doc.qt.io/qt-5/qthelp-framework.html}{The Qt Help Framework} for details + about qch files. + \row + \li generatePkgConfigFiles + \li auto-detected + \li Whether to generate files for pkg-config. + \row + \li generateQbsModules + \li auto-detected + \li Whether to generate \QBS modules for the exported \QBS libraries. Use this when + building another product against \QBS libraries using \QBS as build system. + \row + \li docInstallDir + \li \c "share/doc/qbs/html" + \li Relative directory path under the install prefix path where to put documentation. + \row + \li pkgConfigInstallDir + \li \c libDirName + \c "/pkgconfig" + \li Relative directory path under the install prefix path where to put pkg-config files. + \row + \li qbsModulesBaseDir + \li \c libDirName + \c "/qbs/modules" + \li Relative directory path under the install prefix path where to put \QBS modules. + Applies only when \c generateQbsModules is \c true. + \row + \li relativeLibexecPath + \li \c "../" + \c libexecInstallDir + \li Path to the auxiliary binaries relative to the application binary. + \row + \li relativePluginsPath + \li \c "../" + \c libDirName + \li Path to plugin libraries relative to the application binary. + \row + \li relativeSearchPath + \li \c ".." + \li Relative path to the directory where to look for \QBS development modules and items. + \row + \li libRPaths + \li auto-detected + \li List of rpaths. + \row + \li resourcesInstallDir + \li \c "" + \li Relative directory path under the install prefix path where to put shared resources + like documentation, \QBS user modules and items. + \row + \li pluginsInstallDir + \li \c libDirName + \c "/qbs/plugins" + \li Relative path to the directory where to put plugins to. + \endtable + + \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}. + Both Windows 10 and Debian Linux container types are available. + + \note The source code for the \QBS development Docker images is located in the \c{docker/} + directory of the \QBS source tree, if you wish to build them yourself. + + \section2 Linux Containers + + The easiest way to get started is to build \QBS using a Linux container. These types of + containers are supported out of the box on all the supported host platforms: Windows, macOS, + and Linux. + + The images provide everything that is necessary to build and test \QBS: + + \list + \li Qt SDK for building \QBS with \c qmake + \li Latest stable release of \QBS for building \QBS with \QBS + \endlist + + We are using docker-compose for building and running the Docker images because it simplifies + the Docker command line and ensures that the correct image tag is used. All available images + are listed in the \c docker-compose.yml file in the project root directory. + + Run the following command to download the \QBS development image based on Ubuntu 18.04 + \e Bionic: + + \code + docker-compose pull bionic + \endcode + + You can then create a new container with the \QBS source directory mounted from your host + machine's file system, by running: + + \code + docker-compose run --rm bionic + \endcode + + You will now be in an interactive Linux shell where you can develop and build \QBS. + + \section2 Windows Containers + + To build \QBS for Windows using Windows containers, your host OS must be running Windows 10 Pro + and have Hyper-V enabled. \l{https://docs.docker.com/docker-for-windows/#switch-between-windows-and-linux-containers}{Switch your Docker environment to use Windows containers}. + + We are using docker-compose for building and running the Docker images because it simplifies + the Docker command line and ensures that the correct image tag is used. All available images + are listed in the \c docker-compose.yml file in the project root directory. + + Run the following command to download the \QBS development image based on Windows 10: + + \code + docker-compose pull windows + \endcode + + You can then create a new container with the \QBS source directory mounted from your host + machine's file system, by running: + + \code + docker-compose run --rm windows + \endcode + + If you want to use Windows containers on a macOS or Linux host, you will have to create a + virtual machine running Windows 10 and register it with \c{docker-machine}. There is at least + \l{https://github.com/StefanScherer/windows-docker-machine}{one Open Source project} + that helps to facilitate this by using using Packer, Vagrant, and VirtualBox. + + The \c{docker run} command to spawn a Windows container on a Unix host will look slightly + different (assuming \c windows is the name of the Docker machine associated with the Windows + container hosting VM): + + \code + eval $(docker-machine env windows) + docker-compose run --rm windows + \endcode + + \section2 Building Release Packages + + Release packages for \QBS for Windows can be built using the following command on Windows: + + \code + docker-compose run --rm windows cmd /c scripts\make-release-archives + \endcode + + For building release packages for Windows on macOS or Linux: + + \code + eval $(docker-machine env windows) + docker-compose run --rm windows cmd /c scripts\\make-release-archives + \endcode +*/ + + +/*! + \contentspage index.html + \previouspage setup.html + \page installing.html + \nextpage configuring.html + + \title Installing + + \QBS binaries are available for Windows, macOS, Linux, and FreeBSD. + + On all platforms, \QBS binaries are part of the \l{Qt Creator} and \l{Qt SDK} + installers. You can find the \c qbs executable in the \c bin directory of + Qt Creator, or within the application bundle's \c MacOS directory on macOS. + + \QBS can also be built locally from sources. For more information, see + \l{Appendix A: Building Qbs}. + + \section1 Windows + + The Qt Project provides prebuilt binaries for Windows (x86 and x64) at + \l{https://download.qt.io/official_releases/qbs/}. For commercial customers of + The Qt Company, the binaries are available in the \l {Qt Account}. + The binaries are packaged in a .zip folder that can be extracted to a location + of your choice. + + \QBS is also available as a \l Chocolatey package, which can be installed in + the usual way: + + \code + choco install qbs + \endcode + + The \c .nupkg file can also be downloaded directly from + \l{https://download.qt.io/official_releases/qbs/} for + \l{https://chocolatey.org/security#organizational-use-of-chocolatey}{offline installation}. + + \section1 macOS + + \QBS can be conveniently installed on macOS with the \l{MacPorts} or \l{Homebrew} + package managers: + + \code + brew install qbs + \endcode + + or + + \code + port install qbs + \endcode + + \section1 Linux and FreeBSD + + \QBS is \l{https://repology.org/metapackage/qbs/versions}{available via the + package management systems} of Linux distributions, and FreeBSD. +*/ + +/*! + \contentspage index.html + \previouspage installing.html + \page configuring.html + \nextpage qt-versions.html + + \title Configuring Profiles and Preferences + + Profiles contain properties that apply to one or more projects. They are + stored independently of the project files and are usually not shared between + build hosts. Typically, profiles contain module properties, such as + installation paths of tools or libraries on the host computer. This approach + has the following advantages, among others: + + \list + \li Team members with different computer setups can work together + smoothly because no host-specific settings end up in the project + files. + \li Different versions of a tool or library can be used to build the + same project without affecting each other. + \endlist + + For example, a profile for building C++ applications contains at least the + installation path and the type of the compiler toolchain. A profile for + building Qt applications contains the toolchain-specific properties as well + as \l{Qt-specific Module Provider Properties}{the path to the Qt installation}. + + This topic describes profiles stored in the \QBS settings. In some cases it + might be beneficial to keep profiles explicitly in the project sources. This + can be achieved with the \l{Profile} item. + + \section1 Setting Up Toolchain Profiles + + \QBS comes with a helper tool \l{setup-toolchains} that can + create profiles for many toolchains. Open a terminal window and type: + + \code + qbs setup-toolchains --detect + \endcode + + This will automatically set up a profile for each detected toolchain on your + computer. You can list the existing profiles by running: + + \code + qbs config --list profiles + \endcode + + Some toolchains, especially for bare-metal targets, may require additional + module properties. Those can be added with the \l{config} or the + \l{config-ui} tools. Now you should be ready to build your first project + with \QBS. Go into examples/helloworld-minimal and type: + + \code + qbs build profile: + \endcode + + You have successfully built your first \QBS project. If you want to build + projects that use Qt, additional steps might be necessary. Please refer to + \l{Managing Qt Versions} for more information. + + \section1 Global Preferences + + In addition to profiles, \QBS provides some global preferences such as \c + qbsSearchPaths and \c defaultProfile. + + \section1 Managing Profiles and Preferences + + You can use the \l{config} command to manage all \QBS configuration + settings, such as profiles and global preferences from the command line, + for example: + + \code + qbs config profiles..qbs.architecture arm + \endcode + + For convenience, \QBS provides a tool \l{config-ui} where you can manage the + settings in a hierarchical view. + + \image qbs-settings-gui.png + + \QBS Settings displays the keys in the specified settings directory and + their values. To expand all keys, select \uicontrol View > + \uicontrol {Expand All} (\key Ctrl+E or \key Cmd+E on macOS). To collapse + all keys, select \uicontrol {Collapse All} (\key Ctrl+C or \key Cmd+C). + + To change the value of a key, double-click it and enter the new value. + + To save your changes, select \uicontrol File > \uicontrol Save. + + To refresh the settings from the settings directory, select \uicontrol File + > \uicontrol Reload. + +*/ + +/*! + \contentspage index.html + \previouspage configuring.html + \page qt-versions.html + \nextpage usage.html + + \title Managing Qt Versions + + \section1 Introduction + + If your environment has the right \c qmake binary in its \c PATH and is also set up + properly for a matching toolchain, then you do not necessarily need a profile + to build projects with a Qt dependency. Otherwise, you should create one: + + \code + qbs setup-qt /usr/bin/qmake myqt + \endcode + + This will create the \c myqt profile which can then be used on + the command line: + + \code + qbs profile:myqt + \endcode + + \note If the \c setup-toolchains command has found more than one toolchain, you will need + to manually link your Qt profile to one of them, like this: + \code + qbs config profiles.myqt.baseProfile + \endcode + + + \section1 Multiple Qt Builds + + To support multiple Qt builds, or in fact any combination of related settings, you need to + create several profiles. The following example illustrates how to set up + three different profiles, each for a different Qt build: + + \code + qbs setup-qt ~/dev/qt/4.7/bin/qmake qt47 + qbs setup-qt ~/dev/qt/4.8/bin/qmake qt48 + qbs setup-qt ~/dev/qt/5.0/qtbase/bin/qmake qt5 + \endcode + + You can set the default Qt build like this: + + \code + qbs config defaultProfile qt5 + \endcode + + To choose a Qt build that is different from the default, use: + + \code + qbs build profile:qt48 + \endcode + + You can set other properties in a profile (not just Qt ones), in the same way + you override them from the command line. For example: + + \code + qbs setup-qt C:\Qt\5.0.0\qtbase\bin\qmake.exe qt5 + qbs config profiles.qt5.qbs.architecture x86_64 + qbs config profiles.qt5.baseProfile msvc2010 + \endcode + + The last example uses the inheritance feature of profiles. All settings in the profile + set as \c baseProfile are known in the derived profile as well. + They can of course be overridden there. +*/ + +/*! + \contentspage index.html + \previouspage qt-versions.html + \page usage.html + \nextpage language-introduction.html + + \title Usage + + \list + \li \l{Language Introduction} + \li \l{Building Applications} + \li \l{Running Applications} + \li \l{Installing Files} + \li \l{Target Platforms} + \li \l{Using the Shell} + \li \l{Generators} + \li \l{Multiplexing} + \li \l{Custom Modules and Items} + \li \l{Module Providers} + \endlist + +*/ + + +/*! + \contentspage index.html + \previouspage usage.html + \page language-introduction.html + \nextpage building-applications.html + + \title Language Introduction + + \QBS uses project files (*.qbs) to describe the contents of a project. + A project contains one or more \l{Product}{products}. A product is the target of a build + process, typically an application, library or maybe a tar ball. + \note \QBS source files are assumed to be UTF-8 encoded. + + \section1 The Obligatory Hello World Example + + \QBS project files are written using a QML dialect. + A very simple C++ hello world project looks like this: + \code + ---helloworld.qbs--- + Application { + name: "helloworld" + files: "main.cpp" + Depends { name: "cpp" } + } + \endcode + + The import statement gives us access to some built-in types and specifies the + used language version. + + \a Application describes the product we want to build. In this case, an + application. This is just a shortcut for writing + \code + Product { + type: "application" + // ... + } + \endcode + + The \a name is the name of the product. In this case it is also the + name of the produced executable (on Windows, the ".exe" extension is added by default). + + In the property \a files, we specify the source files for our product. + Unlike QML, the right-hand side can be either a string or a string list. + A single string is converted to a stringlist containing just one element. + So we could have also written + + \code + files: [ "main.cpp" ] + \endcode + + \a Depends adds the dependency to the \l{cpp} module. This is necessary to + let \QBS know that we have a C++ project and want to compile main.cpp with a + C++ compiler. For more information about \QBS modules, see \l{Modules}. + + + \section1 Reusing Project File Code + QML-like inheritance works also in \QBS. + + \code + ---CrazyProduct.qbs--- + Product { + property string craziness: "low" + } + + ---hellocrazyworld.qbs--- + import "CrazyProduct.qbs" as CrazyProduct + + CrazyProduct { + craziness: "enormous" + name: "hellocrazyworld" + // ... + } + \endcode + + You can put JS code into separate \c{.js} files and then import them. + \code + ---helpers.js--- + function planetsCorrectlyAligned() + { + // implementation + } + + ---myproject.qbs--- + import "helpers.js" as Helpers + + Product { + name: "myproject" + Group { + condition: Helpers.planetsCorrectlyAligned() + file: "magic_hack.cpp" + } + // ... + } + \endcode + + + \section1 Modules + + A \e module is a collection of properties and language items that are used for + building a product if the product depends on (or loads) the module. + + For example, the \a cpp module looks like this (simplified): + \code + Module { + name: "cpp" + property string warningLevel + property string optimization + property bool debugInformation + property pathList includePaths + // ... + FileTagger { + patterns: "*.cpp" + fileTags: ["cpp"] + } + Rule {...} // compiler + Rule {...} // application linker + Rule {...} // static lib linker + Rule {...} // dynamic lib linker + } + \endcode + + The properties that can be set for the \a cpp module are used to control the behavior of + your C++ toolchain. + In addition, you can use FileTaggers and Rules that are explained later. + + As soon as your product depends on a module, it can set the properties of the + module. You specify the optimization level for your product (and all build variants) like this: + + \code ---helloworld.qbs--- + Application { + name: "helloworld" + files: ["main.cpp"] + cpp.optimization: "ludicrousSpeed" + Depends { name: "cpp" } + } + \endcode + + A module can implicitly depend on other modules. For example, the + \l{Qt.core} module depends on the \l{cpp} module. However, to set the + properties of a module, you must make the dependency explicit. + + \code + // THIS DOES NOT WORK + Application { + name: "helloworld" + files: ["main.cpp"] + Depends { name: "Qt.core" } + cpp.optimization: "ludicrousSpeed" + // ERROR! We do not know about "cpp" here, + // though "Qt.core" depends on "cpp". + } + + // THIS WORKS + Application { + name: "helloworld" + files: ["main.cpp"] + Depends { name: "Qt.core" } + Depends { name: "cpp" } + cpp.optimization: "ludicrousSpeed" + } + \endcode + + \section2 Different Properties for a Single File + + Not only the product, but all the source files of the product can have their own + set of module properties. For example, assume you have some files that are known to crash + your compiler if you turn on optimizations. You want to turn off + optimizations for just these files and this is how you do it: + + \code + Application { + name: "helloworld" + files: "main.cpp" + Group { + files: ["bad_file.cpp", "other_bad_file.cpp"] + cpp.optimization: "none" + } + Depends { name: "cpp" } + } + \endcode + + \section2 Selecting Files by Properties + + Sometimes you have a file that is only going to be compiled on a certain platform. + This is how you do it: + \code + Group { + condition: qbs.targetOS.contains("windows") + files: [ + "harddiskdeleter_win.cpp", + "blowupmonitor_win.cpp", + "setkeyboardonfire_win.cpp" + ] + } + Group { + condition: qbs.targetOS.contains("linux") + files: [ + "harddiskdeleter_linux.cpp", + "blowupmonitor_linux.cpp", + "setkeyboardonfire_linux.cpp" + ] + } + \endcode + + In the above example, \l{qbs::targetOS}{qbs.targetOS} is a property of the + target of the the \l{qbs} module. The \c qbs module is always implicitly + loaded. Its main properties are: + + \list + \li \l{qbs::}{buildVariant} that specifies the name of the build variant + for the current build. + \li \l{qbs::}{hostOS} that is set by \QBS internally and specifies the + operating system \QBS is running on. + \li \l{qbs::}{targetOS} that specifies the operating system you want to + build the project for. + \endlist + + You can set these properties on the command line or by using a profile. + + \code + $ qbs # qbs.buildVariant:debug, profile: (or profile:none, if no default profile exists) + $ qbs config:release # qbs.buildVariant:release, profile: + $ qbs config:debug config:release # builds two configurations of the project + $ qbs profile:none # all module properties have their default values + \endcode + + To select files by build variant: + \code + Group { + condition: qbs.buildVariant == "debug" + files: "debughelper.cpp" + } + \endcode + + To set properties for a build variant: + \code + Properties { + condition: qbs.buildVariant == "debug" + cpp.debugInformation: true + cpp.optimization: "none" + } + \endcode + Or, to use a more QML-like style: + \code + cpp.debugInformation: qbs.buildVariant == "debug" ? true : false + cpp.optimization: qbs.buildVariant == "debug" ? "none" : "fast" + \endcode + + \section1 Property Types + + While properties in \QBS generally work the same way as in QML, the set of possible property + types has been adapted to reflect the specific needs of a build tool. The supported types + are as follows: + + \table + \header + \li Property type + \li Example + \li Description + \row + \li \c bool + \li \c{property bool someBoolean: false} + \li The usual boolean values. + \row + \li \c int + \li \c{property int theAnswer: 42} + \li Integral numbers. + \row + \li \c path + \li \c{property path aFile: "file.txt"} + \li File paths resolved relative to the directory the product they are associated with + is located in. + \row + \li \c pathList + \li \c{property pathList twoFiles: ["file1.txt", "./file2.txt"]} + \li A list of \c path values. + \row + \li \c string + \li \c{property string parentalAdvisory: "explicit lyrics"} + \li JavaScript strings. + \row + \li \c stringList + \li \c{property stringList realWorldExample: ["no", "not really"]} + \li A list of JavaScript strings. + \row + \li \c var + \li \c{property var aMap: ({ key1: "value1", key2: "value2" })} + \li Generic data, as in QML. + \row + \li \c varList + \li \c{property var aMapList: [{ key1: "value1", key2: "value2" }, { key1: "value3" }]} + \li A list of generic data, typically JavaScript objects. + \endtable + + + \section1 Overriding Property Values from the Command Line + + Property values set in project files or profiles can be overridden on the command line. + The syntax is \c{.:}. The following command lines + demonstrate how to set different kinds of properties: + \code + $ qbs projects.someProject.projectProperty:false # set a property of a project + $ qbs products.someProduct.productProperty:false # set a property of a product + $ qbs modules.cpp.treatWarningsAsErrors:true # set a module property for all products + $ qbs products.someProduct.cpp.treatWarningsAsErrors:true # set a module property for one product + \endcode + + Property values on the command line can also be expressed in JavaScript form, the same way + as you would write them in a project file. Make sure to take care of proper + quoting, so that the shell does not interpret any of the values itself. Properties of type + \c stringList can also be provided as comma-separated values, if none of the strings contain + special characters: + \code + $ qbs projects.someProject.listProp:'["a", "b", "c"]' + $ qbs projects.someProject.listProp:a,b,c # same as above + $ qbs projects.someProject.listProp:'["a b", "c"]' # no CSV equivalent + \endcode + + \section1 File Tags and Taggers + + \QBS itself knows nothing about C++ files or file extensions. All source files + in a product are handled equally. However, you can assign \a{file tags} to an artifact + to act as a marker or to specify a file type. + + An artifact can have multiple file tags. + For example, you can use the \a Group item to group files with the same file tags (or a set of + properties). + + \code + Product { + Group { + files: ["file1.cpp", "file2.cpp"] + fileTags: ["cpp"] + } + Group { + files: "mydsl_scanner.l" + fileTags: ["flex", "foobar"] + } + // ... + } + \endcode + + When you load the \a cpp module, you also load the following item: + \code + FileTagger { + patterns: "*.cpp" + fileTags: ["cpp"] + } + \endcode + This construct means that each source file that matches the pattern \c{*.cpp} (and + has not explicitly set a file tag) gets the file tag \c{cpp}. + + The above example can be simplified to + \code + Product { + Depends: "cpp" + files: ["file1.cpp", "file2.cpp"] + Group { + files: "mydsl_scanner.l" + fileTags: ["flex", "foobar"] + } + // ... + } + \endcode + + The \a FileTagger from the \a cpp module automatically assigns the \c cpp + file tag to the source files. Groups that just contain the \a files + property can be more simply expressed by using the \a files property of the product. + + File tags are used by \a rules to transform one type of artifact into + another. For instance, the C++ compiler rule transforms artifacts with the file tag + \c cpp to artifacts with the file tag \c{obj}. + + In addition, it is possible to use file taggers to tag files and specify custom file tags: + \code + Product { + Depends: "cpp" + Group { + overrideTags: false // The overrideTags property defaults to true. + fileTags: ["foobar"] + files: ["main.cpp"] // Gets the file tag "cpp" through a FileTagger item and + // "foobar" from this group's fileTags property. + } + // ... + } + \endcode + + \section1 Rules + + \QBS applies a \e rule to a pool of artifacts (in the beginning it is just the set of + source files of the project) and chooses the ones that match the input file + tags specified by the rule. Then it creates output artifacts in the build graph that have other + filenames and file tags. It also creates a script that transforms the input artifacts into the + output artifacts. Artifacts created by one rule can (and typically do) serve as inputs to + another rule. In this way, rules are connected to one another via their input and output + file tags. + + For examples of rules, see the share/qbs/modules directory in the \QBS + repository. + + You can define rules in your own module to be provided along with + your project. Or you can put a rule directly into your project file. + + For more information, see \l{Rule}. +*/ + + +/*! + \contentspage index.html + \previouspage language-introduction.html + \page building-applications.html + \nextpage running-applications.html + + \title Building Applications + + This section assumes that \QBS is present in \c PATH. For the details how to install \QBS, see + the \l{Installing} page. + + To build applications from the command line, enter the following commands: + + \code + cd examples/collidingmice + 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. + + You can use the \l{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 + \l{build} command. For example, to build debug and release configurations with + the \e Android profile, enter the following command: + + \code + qbs build profile:Android config:debug config:release + \endcode + + The position of the property assignment is important. In the example + above, the profile property is set for all build configurations that come + afterwards. + + To set a property just for one build configuration, place the assignment after + the build configuration name. + In the following example, the property \l{cpp::treatWarningsAsErrors} + {cpp.treatWarningsAsErrors} is set to \c true for debug only and + \l{cpp::optimization}{cpp.optimization} is set to \c small for release only. + + \code + qbs build config:debug modules.cpp.treatWarningsAsErrors:true config:release modules.cpp.optimization:small + \endcode + + Projects are built in the debug build configuration by default. +*/ + +/*! + \contentspage index.html + \previouspage running-applications.html + \page installing-files.html + \nextpage {Target Platforms} + + \title Installing Files + + To install your project, specify the necessary information in the project file: + + \code + Application { + Group { + name: "Runtime resources" + files: "*.qml" + qbs.install: true + qbs.installDir: "share/myproject" + } + Group { + name: "The App itself" + fileTagsFilter: "application" + qbs.install: true + qbs.installDir: "bin" + } + } + \endcode + + In this example, we want to install a couple of QML files and an executable. + + When building, \QBS installs artifacts into the default root folder, namely + \c{/install-root}. The \l{qbs::installPrefix}{qbs.installPrefix} and + \l{qbs::installDir}{qbs.installDir} properties are appended to the root folder. + \code + qbs build qbs.installPrefix:/usr + \endcode + In this example, the executable will be installed into the \c{/install-root/usr/bin} + folder and the QML files will be installed into the + \c{/install-root/usr/share/myproject} folder. + + To skip installation during the build, use the \c --no-install option. + + To override the default location, use the \c --install-root option of the \c{qbs install} + command: + \code + qbs build --no-install qbs.installPrefix:/usr + sudo qbs install --no-build --install-root / + \endcode + In this example, artifacts will be installed directly into the \c /usr folder. Since the + \c{qbs install} command implies \c build, we use the \c --no-build parameter to ensure that + we do not accidentally rebuild the project, thereby changing the artifacts' owner to \c root. + + Sometimes, it makes sense to install the application into a temporary root folder, keeping the + same folder structure within that root folder as in the examples above; for instance, + when building a Linux package such as \c deb or \c rmp. To install the application into the + \c /tmp/myProjectRoot folder, use the following command: + + \code + $ qbs install --install-root /tmp/myProjectRoot + \endcode + + In this example, the executable will be installed into the \c{/tmp/myProjectRoot/usr/bin} folder + and QML files will be installed into the \c{/tmp/myProjectRoot/usr/share/myproject} folder. + + To remove all files from the install root prior to installing, use the \c --clean-install-root + parameter: + + \code + qbs install --clean-install-root --install-root /tmp/myProjectRoot + \endcode + + For more information about how the installation path is constructed, see + \l {Installation Properties}. +*/ + +/*! + \contentspage index.html + \previouspage building-applications.html + \page running-applications.html + \nextpage installing-files.html + + \title Running Applications + + 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. + + For example, entering the following command runs the Qt Creator application: + + \code + qbs run --products qtcreator + \endcode + + This command also builds and installs the product, if necessary. +*/ + +/*! + \contentspage index.html + \previouspage {Target Platforms} + \page shell.html + \nextpage generators.html + + \title Using the Shell + + To use the \QBS shell, enter the following command: + + \code + qbs shell + \endcode + + This is mainly a debugging tool. It opens a shell with the same environment + that \QBS uses when building the project, so you can, for example, inspect + which environment variables will be set up. + +*/ + +/*! + \contentspage index.html + \previouspage multiplexing.html + \page custom-modules.html + \nextpage module-providers.html + + \title Custom Modules and Items + + Users of \QBS are not limited to the pre-defined \l{List of Modules}{modules} and + \l{List of Language Items}{items}, they can also create their own. Here we describe how + to set up custom modules and items so that \QBS will find them. + + \section1 File System Layout + + Items and modules are located under a common base directory, whose name and location is + completely arbitrary. We will refer to it as \c search-path here. This directory has two + subdirectories \c modules and \c imports, which contain \QBS modules and items, respectively. + + + \section1 Custom Modules + + To introduce a custom module \c mymodule, create a directory \c{search-path/modules/mymodule/}. + \note Module names are case-sensitive, and this also goes for the corresponding directory name. + + Then, put a file containing an instance of the \l{Module} in there and give it the \c{.qbs} + extension. This module will be pulled in if a + \l{Product}{product} declares a \l{Depends}{dependency} on \c mymodule. + + + \section1 Custom Items + + To introduce a custom item \c MyItem, create the file \c{search-path/imports/MyItem.qbs}. + + \note Item file names must start with a capital letter due to the fact that type names can + only start with a capital letter. Otherwise, the file will be silently ignored. + + \section1 Making \QBS Aware of Custom Modules and Items + + To be able to use your custom modules and items, you need to make them known to \QBS. You can + do this per project or globally. + + \section2 Project-specific Modules and Items + + Let's assume you have a project that is located in \c{project_dir} and you have created some + modules in \c{project_dir/custom-stuff/modules/} as well as some items in + \c{project_dir/custom-stuff/imports/} that you want to use in the project. + To achieve this, your top-level project file should look like this: + \code + // ... + Project { + // .. + qbsSearchPaths: "custom-stuff" + // .. + } + \endcode + \note For technical reasons, the custom modules and items will not be + available in the file that contains the \l{Project::qbsSearchPaths} + {Project.qbsSearchPaths} property. Any product that wants to make use of + them needs to be in a different file that is pulled in via the + \l{Project::references}{Project.references} property, for example. + This is not a serious limitation, since every well-structured project will + be split up in this manner. + + \section2 Making Custom Modules and Items Available Across Projects + + What if your modules and items are generally useful and you want to access them in several + projects? In this case, it is best to add the location to your preferences. + For example: + \code + qbs config preferences.qbsSearchPaths /usr/local/share/custom-qbs-extensions + \endcode + +*/ + +/*! + \contentspage index.html + \previouspage custom-modules.html + \page module-providers.html + \nextpage howtos.html + + \title Module Providers + + There are use cases for which a pre-defined module is not flexible enough. + For instance, the overall set of modules related to a certain task might depend + on some information present on the local platform. + + \note Module providers are an advanced concept that you will rarely need to use directly. + Reading this section is not required for most people's everyday work. + + \section1 How \QBS Uses Module Providers + + If \QBS encounters a \l Depends item whose name does not match a known module, + it checks whether such a module can be generated. This procedure works as follows: + \list 1 + \li All \l{Project::qbsSearchPaths}{search paths} are scanned for a file called + \c {module-providers//provider.qbs}, where \c is the name of the dependency + as specified in the \c Depends item. Multi-component names such as "a.b" are turned + into nested directories, and each of them is scanned, starting with the deepest path. + For instance, if the dependency's name is \c {a.b}, then \QBS will look for + \c {a/b/provider.qbs} and then \c {a/provider.qbs}. + \li If such a file is found, it needs to contain a \l ModuleProvider item. This item + is instantiated, which potentially leads to the creation of one or more modules, + and \QBS retrieves the search paths to find these modules from the item. + The details are described in the \l ModuleProvider documentation. + \li If a matching module provider was found and provided new search paths, + a second attempt will be made to locate the dependency using the new paths. + The search for a matching module provider ends as soon as one was found, regardless + of whether it created any modules or not. + \li If no matching module provider was found in any of the search paths, \QBS will fall back + to a generic module provider, which creates a module that attempts to locate the + dependency via \c pkg-config. + This fallback mechanism can be disabled in the respective + \l{Depends::enableFallback}{Depends} item or globally via the + \l{no-fallback-module-provider}{--no-fallback-module-provider} option. + \endlist + + \section1 Parameterizing Module Providers + + You can pass information to module providers from the command line, via profiles or + from within a product, in a similar way as you would do for modules. For instance, the + following invocation of \QBS passes information to two module providers \c a and \c b: + \code + $ qbs moduleProviders.a.p1:true moduleProviders.a.p2:true moduleProviders.b.p:false + \endcode + \QBS will set the properties of the respective module providers accordingly. + In the above example, module provider \c a needs to declare two boolean properties \c p1 + and \c p2, and they will be set to \c true and \c false, respectively. + +*/ + +/*! + \contentspage index.html + \previouspage shell.html + \page generators.html + \nextpage multiplexing.html + + \title Generators + + 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 Generating Microsoft Visual Studio Projects + + To generate a project for another build system, such as Microsoft Visual + Studio, use the \l{generate}{qbs generate} command and specify a generator + using the \l{generate-generator}{-g} option. For example: + + \code + # For Visual Studio + qbs generate -g visualstudio2015 + \endcode + + \QBS will then generate a series of files in the current directory, based on the generator that + 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 + to be reflected in \QBS. You must edit your \QBS project files and re-run + \l{generate}{qbs generate} in order for the changes to be reflected in your + IDE. + + \section1 Generating IAR Embedded Workbench Projects + + To generate a project for \l{https://www.iar.com/iar-embedded-workbench/} + {IAR Embedded Workbench}, use the \l{generate}{qbs generate} command and specify + a generator using the \l{generate-generator}{-g} option. For example: + + \code + # For IAREW v8xxxx + qbs generate -g iarew8 profile: + qbs generate -g iarew8 -d -f profile: + \endcode + + \note You need to specify a specific QBS profile, which is required for a generator + to fetch a target architecture to generate the project. + + \note IAR EW generator creates a native target project. + + Supported IAR EW generators are listed in a table below: + + \table + \header \li Generator \li IAR EW Version \li Target Architecture + \row \li iarew8 \li All 8.x.y versions \li ARM + \row \li iarew7 \li All 7.x.y versions \li AVR, MSP430 + \row \li iarew10 \li All 10.x.y versions \li 8051 (aka MCS51) + \row \li iarew3 \li All 3.x.y versions \li STM8 + \endtable + + \section1 KEIL uVision Projects + + To generate a project for \l{https://www2.keil.com/mdk5/uvision/} + {KEIL uVision}, use the \l{generate}{qbs generate} command and specify + a generator using the \l{generate-generator}{-g} option. For example: + + \code + # For KEIL UV5 + qbs generate -g keiluv5 profile: + qbs generate -g keiluv5 -d -f profile: + \endcode + + \note You need to specify a specific QBS profile, which is required for a generator + to fetch a target architecture to generate the project. + + \note KEIL UV generator creates a native target project. + + Supported KEIL UV generators are listed in a table below: + + \table + \header \li Generator \li KEIL UV Version \li Target Architecture + \row \li keiluv5 \li All 5.x.y versions \li 8051 (aka MCS51), ARM + \endtable + + \section1 Generating Clang Compilation Databases + + To generate a \l{JSON Compilation Database Format Specification} + {Clang compilation database (clangdb)}, use the following command: + + \code + qbs generate --generator clangdb + \endcode + + \section1 Generating Makefiles + + To generate a Makefile, use the following command: + \code + qbs generate --generator makefile + \endcode + + \section2 Targets + + The generated Makefile will contain targets for all output artifacts known to \QBS. + + In addition, the following targets are created for every product: + \list + \li \c {} to build the product + \li \c {clean-} to remove all files generated by the above target + \li \c {install-} to install the product's artifacts that have + \c{qbs.install} set + \endlist + In the above list, the placeholder \c{} stands for the product's name with + all characters that are not ASCII letters, digits, dots or underscores replaced + with underscore characters. + + The special target \c all builds all products whose \l{Product::builtByDefault}{builtByDefault} + property is enabled. This is the default target. It is complemented by \c install and \c clean. + + \note The Makefile will not be able to build artifacts created by + \l{JavaScriptCommand}{JavaScriptCommands}, because there is no command line to run for them. + + \section2 Pre-defined Variables + + The build directory and the install root are set to whatever you specified when calling the + generator. If you did not specify anything, \QBS' default values are used. You can override + these values when invoking the \c make tool by explicitly setting the \c{BUILD_ROOT} + and \c{INSTALL_ROOT} variables, respectively. For instance: + \code + $ qbs generate -g makefile config:make modules.qbs.installRoot:/opt/mydir + $ make -f make/Makefile # Will install to /opt/mydir + $ make -f make/Makefile INSTALL_ROOT=/opt/myotherdir # Will install to /opt/myotherdir + \endcode + + \section2 Spaces in Directory Names + + Due to the difficulties involved in making this work correctly, \QBS will refuse to generate + a Makefile if the source, build or install root directories contain spaces. It will + try to handle spaces in file names of output artifacts, though. + + \section2 Platform-specific Differences in Format + + \QBS assumes that the Makefile will be invoked on the current host platform, so that + platform's tools will be used for copying and removing files, and path separators will + be converted to backslashes on Windows. When dealing with spaces in artifact names, + on Unix-like systems compatibility with GNU make is assumed with regards to quoting. + + \section1 Limitations + + 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 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 + Product { + // ERROR: 'name' property cannot have different values based on the configuration + name: qbs.configuration === "debug" + ? "MyProduct_debug" + : "MyProduct" + } + \endcode + + \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): + + \code + Product { + // OK + property bool isDebug: + name: isDebug ? "MyProduct_debug" : "MyProduct" + } + \endcode + + The properties to which the limitation applies includes but is not limited to: + + \list + \li \l{Product::name}{Product.name} + \li \l{bundle::isBundle}{bundle.isBundle} + \endlist + + If a simple workaround is possible in a particular case (for example, + varying \l{Product::targetName}{Product.targetName} across configuration + instead of \l{Product::name}{Product.name}, the generator will typically + suggest it in the error message. +*/ + +/*! + \contentspage index.html + \previouspage generators.html + \page multiplexing.html + \nextpage custom-modules.html + + \title Multiplexing + + Multiplexing is an advanced \QBS feature that allows a product to be + transparently built in multiple \e passes along with an optional, final + \e aggregate pass that allows the output artifacts of the initial passes + to be combined or otherwise operated on in some way. + + The multiplexing feature is used to implement certain platform-specific + behavior: specifically, it allows applications and libraries on Apple + platforms to be compiled into \e fat binaries containing multiple CPU + architectures, the creation of Apple frameworks containing multiple + \e variants (for example, combined debug and release builds), and the + creation of Android application and library packages containing native + code built for multiple Android ABIs. + + A product can be multiplexed over the \l{qbs::architectures} + {qbs.architectures} property (which maps to \l{qbs::architecture} + {qbs.architecture}), \l{qbs::buildVariants}{qbs.buildVariants} property + (which maps to \l{qbs::buildVariant}{qbs.buildVariant}), and \l{qbs::profiles} + {qbs.profiles} (which maps to \l{Project::profile}{Project.profile}). + + For example, to build a "fat" \c iOS binary containing two architectures, use the following + command: + \code + qbs build modules.qbs.targetPlatform:ios modules.qbs.architectures:arm64,armv7a + \endcode + + \note The implementation details around multiplexing are subject to change. + + Product multiplexing works by examining the + \l{Product::multiplexByQbsProperties}{Product.multiplexByQbsProperties} + property, which can + be set to the list of properties your product should multiplex over. For + example, \c multiplexByQbsProperties might contain two strings, + \c "architectures" and \c "buildVariants". \QBS evaluates the values of + \c qbs.architectures and \c qbs.buildVariants, which in turn might contain + the values \c ["x86", "x86_64"] and \c ["debug", "release"]. \QBS will build + all the possible configurations of the product: \c {(x86, debug)}, + \c {(x86, release)}, \c {(x86_64, debug)}, and \c {(x86_64, release)}. + + If the \l{Product::aggregate}{Product.aggregate} property is \c true, the + product will also be + built a fifth time, with the values of the multiplexed properties left + undefined. The aggregate product will have an automatic dependency on the + original four instances of the product, allowing it to collect their output + artifacts and to operate on them. + + The aggregate product is used in situations where the target artifacts of + the individually multiplexed instances must be combined into one final + aggregate artifact that makes up the overall product. + Bundle products on Apple platforms use the aggregate product to create the + bundle artifacts (such as \c Info.plist and \c PkgInfo) that are independent + of a particular architecture or build variant. In addition, they use the + \c lipo tool to join together the built native code for different + architectures (such as \c x86 and \c x86_64) into the final, + multi-architecture fat binary that the app bundle contains. +*/ + +/*! + \contentspage index.html + \previouspage json-api.html + \page attributions.html + + \title Appendix D: Licenses and Code Attributions + + \section1 Licenses + + The \QBS library and tools are available under commercial licenses from + \l{Qt Licensing}{The Qt Company}. In addition, they are available under + \l{GNU Lesser General Public License, Version 3} (LGPL version 3) and + \l{GNU General Public License, version 2} (GPL version 2). + + Shared functionality, which might be pulled in by user build scripts, is + available under commercial licenses, + \l{GNU Lesser General Public License, Version 2.1} (LGPL version 2.1) with + \l{The Qt Company LGPL Exception version 1.1}, and LGPL version 3. + + Autotests are available under commercial licenses and + \l{GNU General Public License Version 3, Annotated with The Qt Company GPL Exception 1.0}. + + Examples are available under commercial licenses and \l{BSD}. + + \section2 GNU Lesser General Public License, Version 3 + + \quotefile ../LICENSE.LGPLv3 + + \section2 GNU General Public License Version 3, Annotated with The Qt Company GPL Exception 1.0 + + \quotefile ../LICENSE.GPL3-EXCEPT + + \section2 GNU Lesser General Public License, Version 2.1 + + \quotefile ../LICENSE.LGPLv21 + + \section2 The Qt Company LGPL Exception version 1.1 + + \quotefile ../LGPL_EXCEPTION.txt + + \section1 Third-Party Attibutions + + \QBS contains third-party code, which we gratefully acknowledge: + \generatelist{groupsbymodule attributions-qbs} +*/ diff --git a/doc/qbs.qdocconf b/doc/qbs.qdocconf new file mode 100644 index 00000000..9a1b2f6a --- /dev/null +++ b/doc/qbs.qdocconf @@ -0,0 +1,2 @@ +include(config/qbs-project.qdocconf) +include($QT_INSTALL_DOCS/global/qt-html-templates-offline.qdocconf) diff --git a/doc/reference/cli/builtin/cli-build.qdoc b/doc/reference/cli/builtin/cli-build.qdoc new file mode 100644 index 00000000..f9312350 --- /dev/null +++ b/doc/reference/cli/builtin/cli-build.qdoc @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-build.html + \ingroup cli + + \title build + \brief Builds a project. + + \section1 Synopsis + + \code + qbs build [options] [[config:configuration-name] [property:value] ...] + \endcode + + \section1 Description + + Builds projects in one or more configurations. + + You can specify \l{Project}{project}, \l{Product}{product}, or + \l{List of Modules}{module} properties, as well as a \l{Profile} + {profile} separately for each configuration. + + This is the default command. + + If the build directory does not exist, it will be created. + + For more information, see \l{Building Applications}. + + \section1 Options + + \target build-all-products + \include cli-options.qdocinc all-products + \include cli-options.qdocinc build-directory + \include cli-options.qdocinc changed-files + \include cli-options.qdocinc check-outputs + \include cli-options.qdocinc check-timestamps + \include cli-options.qdocinc clean_install_root + \include cli-options.qdocinc command-echo-mode + \include cli-options.qdocinc dry-run + \include cli-options.qdocinc project-file + \target build-force-probe-execution + \include cli-options.qdocinc force-probe-execution + \include cli-options.qdocinc jobs + \include cli-options.qdocinc job-limits + \include cli-options.qdocinc keep-going + \include cli-options.qdocinc less-verbose + \include cli-options.qdocinc log-level + \include cli-options.qdocinc log-time + \include cli-options.qdocinc more-verbose + \include cli-options.qdocinc no-install + \target build-products + \include cli-options.qdocinc products-specified + \include cli-options.qdocinc settings-dir + \include cli-options.qdocinc show-progress + \target no-fallback-module-provider + \include cli-options.qdocinc no-fallback-module-provider + \include cli-options.qdocinc wait-lock + + \section1 Parameters + + \include cli-parameters.qdocinc configuration-name + \include cli-parameters.qdocinc property + + \section1 Examples + + Builds the application specified by the \c .qbs file in the current + directory using the default profile: + + \code + qbs + \endcode + + Builds the application using four concurrent build jobs: + + \code + qbs -j 4 + \endcode + + Builds the default configuration of an application using the profile called + \c Android: + + \code + qbs build profile:Android + \endcode + + Builds the application using different \l{cpp} module properties + for debug and release configurations. For the debug configuration, warnings + will cause the build to fail, whereas for the release configuration, the + size of the build artifacts will be optimized. Both configurations are built + with a profile named \c{"qt"}: + + \code + qbs build profile:qt config:debug modules.cpp.treatWarningsAsErrors:true config:release modules.cpp.optimization:small + \endcode +*/ diff --git a/doc/reference/cli/builtin/cli-clean.qdoc b/doc/reference/cli/builtin/cli-clean.qdoc new file mode 100644 index 00000000..bd6ba161 --- /dev/null +++ b/doc/reference/cli/builtin/cli-clean.qdoc @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-clean.html + \ingroup cli + + \title clean + \brief Removes the files generated during a build. + + \section1 Synopsis + + \code + qbs clean [options] [config:configuration-name] + \endcode + + \section1 Description + + Removes build \l{Artifact}{artifacts} for the specified build + configuration. + + \section1 Options + + \include cli-options.qdocinc build-directory + \include cli-options.qdocinc dry-run + \include cli-options.qdocinc keep-going + \include cli-options.qdocinc less-verbose + \include cli-options.qdocinc log-level + \include cli-options.qdocinc log-time + \include cli-options.qdocinc more-verbose + \include cli-options.qdocinc products-specified + \include cli-options.qdocinc settings-dir + \include cli-options.qdocinc show-progress + + \section1 Parameters + + \include cli-parameters.qdocinc configuration-name + + \section1 Examples + + Removes the build artifacts in the current directory for the default build + configuration: + + \code + qbs clean + \endcode +*/ diff --git a/doc/reference/cli/builtin/cli-dump-nodes-tree.qdoc b/doc/reference/cli/builtin/cli-dump-nodes-tree.qdoc new file mode 100644 index 00000000..2e5485ec --- /dev/null +++ b/doc/reference/cli/builtin/cli-dump-nodes-tree.qdoc @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-dump-nodes-tree.html + \ingroup cli + + \title dump-nodes-tree + \brief Dumps the nodes in the build graph to \c stdout. + + \section1 Synopsis + + \code + qbs dump-nodes-tree [options] [config:configuration-name] + \endcode + + \section1 Description + + Dumps the nodes in the build graph to \c stdout. + + This is an internal command that is used for debugging purposes only. + + \section1 Options + + \include cli-options.qdocinc build-directory + \include cli-options.qdocinc products-specified + \include cli-options.qdocinc settings-dir + + \section1 Parameters + + \include cli-parameters.qdocinc configuration-name + + \section1 Examples + + Dumps the nodes tree into a file called \c nodes-tree.log: + + \code + qbs dump-nodes-tree >nodes-tree.log + \endcode + +*/ diff --git a/doc/reference/cli/builtin/cli-generate.qdoc b/doc/reference/cli/builtin/cli-generate.qdoc new file mode 100644 index 00000000..b6dda6f4 --- /dev/null +++ b/doc/reference/cli/builtin/cli-generate.qdoc @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-generate.html + \ingroup cli + + \title generate + \brief Invokes a project generator, for example to create project files for + another build tool. + + \section1 Synopsis + + \code + qbs generate [options] [[config:configuration-name] [property:value] ...] + \endcode + + \section1 Description + + Invokes a project generator, for example to create project files for another + build tool. + + For more information, see \l{Generators}. + + \section1 Options + + \target generate-generator + \include cli-options.qdocinc generator + \include cli-options.qdocinc build-directory + \include cli-options.qdocinc project-file + \include cli-options.qdocinc install-root + \include cli-options.qdocinc less-verbose + \include cli-options.qdocinc log-level + \include cli-options.qdocinc log-time + \include cli-options.qdocinc more-verbose + \include cli-options.qdocinc settings-dir + \include cli-options.qdocinc show-progress + + \section1 Parameters + + \include cli-parameters.qdocinc configuration-name + \include cli-parameters.qdocinc property + + \section1 Examples + + Generates a project for Microsoft Visual Studio: + + \code + qbs generate -g visualstudio2015 + \endcode + + Generates a \l{https://clang.llvm.org/docs/JSONCompilationDatabase.html} + {Clang compilation database (clangdb)}: + + \code + qbs generate --generator clangdb + \endcode +*/ diff --git a/doc/reference/cli/builtin/cli-help.qdoc b/doc/reference/cli/builtin/cli-help.qdoc new file mode 100644 index 00000000..c36d0331 --- /dev/null +++ b/doc/reference/cli/builtin/cli-help.qdoc @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-help.html + \ingroup cli + + \title help + \brief Shows general or command-specific help. + + \section1 Synopsis + + \code + qbs help [] + \endcode + + \section1 Description + + Shows a list of available commands with descriptions. Specify a command + name to list the options that the command takes with descriptions. + + \section1 Options + + This command takes no options. + + \section1 Examples + + Shows an overview of all commands: + + \code + qbs help + \endcode + + Lists the options that the \c build command takes: + + \code + qbs help build + \endcode +*/ diff --git a/doc/reference/cli/builtin/cli-install.qdoc b/doc/reference/cli/builtin/cli-install.qdoc new file mode 100644 index 00000000..b55f0529 --- /dev/null +++ b/doc/reference/cli/builtin/cli-install.qdoc @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-install.html + \ingroup cli + + \title install + \brief Installs a project. + + \section1 Synopsis + + \code + qbs install [options] [[config:configuration-name] [property:value] ...] + \endcode + + \section1 Description + + Install all files marked as installable to their respective destinations. + The project is built first, if necessary, unless the \c --no-build option is + given. + + For more information, see \l{Installing Files}. + + \section1 Options + + \include cli-options.qdocinc all-products + \include cli-options.qdocinc build-directory + \include cli-options.qdocinc changed-files + \include cli-options.qdocinc check-outputs + \include cli-options.qdocinc check-timestamps + \include cli-options.qdocinc clean_install_root + \include cli-options.qdocinc command-echo-mode + \include cli-options.qdocinc dry-run + \include cli-options.qdocinc project-file + \include cli-options.qdocinc force-probe-execution + \include cli-options.qdocinc install-root + \include cli-options.qdocinc jobs + \include cli-options.qdocinc keep-going + \include cli-options.qdocinc less-verbose + \include cli-options.qdocinc log-level + \include cli-options.qdocinc log-time + \include cli-options.qdocinc more-verbose + \include cli-options.qdocinc no-build + \include cli-options.qdocinc products-specified + \include cli-options.qdocinc settings-dir + \include cli-options.qdocinc wait-lock + + \section1 Parameters + + \include cli-parameters.qdocinc configuration-name + \include cli-parameters.qdocinc property + + \section1 Examples + +*/ diff --git a/doc/reference/cli/builtin/cli-list-products.qdoc b/doc/reference/cli/builtin/cli-list-products.qdoc new file mode 100644 index 00000000..92ef043d --- /dev/null +++ b/doc/reference/cli/builtin/cli-list-products.qdoc @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 cli.html + \page cli-list-products.html + \ingroup cli + + \title list-products + \brief Lists all products in the given project. + + \section1 Synopsis + + \code + qbs list-products [options] [config:configuration-name] + \endcode + + \section1 Description + + Lists all products that exist in a project. + + \section1 Options + + \include cli-options.qdocinc project-file + \include cli-options.qdocinc build-directory + \include cli-options.qdocinc settings-dir + + \section1 Parameters + + \include cli-parameters.qdocinc configuration-name + + \section1 Examples + + To list all products for the project in the current directory: + + \code + qbs list-products + \endcode +*/ diff --git a/doc/reference/cli/builtin/cli-resolve.qdoc b/doc/reference/cli/builtin/cli-resolve.qdoc new file mode 100644 index 00000000..b13f3de3 --- /dev/null +++ b/doc/reference/cli/builtin/cli-resolve.qdoc @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-resolve.html + \ingroup cli + + \title resolve + \brief Resolves a project without building it. + + \section1 Synopsis + + \code + qbs resolve [options] [[config:configuration-name] [property:value] ...] + \endcode + + \section1 Description + + Resolves a \l{Project}{project} in one or more configurations. Run this + command to change the properties of an existing build. + + \section1 Options + + \include cli-options.qdocinc build-directory + \include cli-options.qdocinc dry-run + \include cli-options.qdocinc project-file + \include cli-options.qdocinc force-probe-execution + \include cli-options.qdocinc less-verbose + \include cli-options.qdocinc log-level + \include cli-options.qdocinc log-time + \include cli-options.qdocinc more-verbose + \include cli-options.qdocinc settings-dir + \include cli-options.qdocinc show-progress + \include cli-options.qdocinc no-fallback-module-provider + + \section1 Parameters + + \include cli-parameters.qdocinc configuration-name + \include cli-parameters.qdocinc property + + \section1 Examples + + Resolves the default configuration of the project in the current directory: + + \code + qbs resolve + \endcode +*/ diff --git a/doc/reference/cli/builtin/cli-run.qdoc b/doc/reference/cli/builtin/cli-run.qdoc new file mode 100644 index 00000000..9f20f868 --- /dev/null +++ b/doc/reference/cli/builtin/cli-run.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 cli.html + \page cli-run.html + \ingroup cli + + \title run + \brief Runs an executable generated by building a project. + + \section1 Synopsis + + \code + qbs run [options] [config:configuration-name] [property:value] ... [ -- ] + \endcode + + \section1 Description + + Runs the specified product's executable with the specified \c . + If the project has only one product, the \c --products option may be + omitted. + + The product will be built if it is not up to date, unless the \c --no-build + option is specified. For more information, see \l{build}. + + For more information about running applications, see + \l{Running Applications}. + + \section1 Options + + \include cli-options.qdocinc all-products + \include cli-options.qdocinc build-directory + \include cli-options.qdocinc changed-files + \include cli-options.qdocinc check-outputs + \include cli-options.qdocinc check-timestamps + \include cli-options.qdocinc clean_install_root + \include cli-options.qdocinc command-echo-mode + \include cli-options.qdocinc dry-run + \include cli-options.qdocinc project-file + \include cli-options.qdocinc force-probe-execution + \include cli-options.qdocinc install-root + \include cli-options.qdocinc jobs + \include cli-options.qdocinc keep-going + \include cli-options.qdocinc less-verbose + \include cli-options.qdocinc log-level + \include cli-options.qdocinc log-time + \include cli-options.qdocinc more-verbose + \include cli-options.qdocinc no-build + \include cli-options.qdocinc products-specified + \include cli-options.qdocinc settings-dir + \include cli-options.qdocinc setup-run-env-config + \include cli-options.qdocinc wait-lock + + \section1 Parameters + + \include cli-parameters.qdocinc configuration-name + \include cli-parameters.qdocinc property + + \section1 Arguments + + \include cli-parameters.qdocinc arguments + + \section1 Examples + + Runs the Qt Creator application: + + \code + qbs run --products qtcreator + \endcode +*/ diff --git a/doc/reference/cli/builtin/cli-session.qdoc b/doc/reference/cli/builtin/cli-session.qdoc new file mode 100644 index 00000000..62999a82 --- /dev/null +++ b/doc/reference/cli/builtin/cli-session.qdoc @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 cli.html + \page cli-session.html + \ingroup cli + + \title session + \brief Starts a session for interacting with an IDE + + \section1 Synopsis + + \code + qbs session + \endcode + + \section1 Description + + Starts a session, communicating via standard input and standard output. + + In this mode, \QBS takes commands from standard input and sends replies + to standard output, using a \l{Appendix C: The JSON API}{JSON-based API}. + + This is the recommended \QBS interface for IDEs. It can be used to retrieve + information about a project and interact with it in various ways, such + as building it, collecting the list of executables, adding new source files + and so on. + +*/ diff --git a/doc/reference/cli/builtin/cli-shell.qdoc b/doc/reference/cli/builtin/cli-shell.qdoc new file mode 100644 index 00000000..221bef24 --- /dev/null +++ b/doc/reference/cli/builtin/cli-shell.qdoc @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-shell.html + \ingroup cli + + \title shell + \brief Opens a shell with a product's environment. + + \section1 Synopsis + + \code + qbs shell [options] [config:configuration-name] [property:value] + \endcode + + \section1 Description + + Opens a shell in the environment that a build with the specified parameters + would use. + + For more information, see \l{Using the Shell}. + + \section1 Options + + \include cli-options.qdocinc build-directory + \include cli-options.qdocinc project-file + \include cli-options.qdocinc products-specified + \include cli-options.qdocinc settings-dir + + \section1 Parameters + + \include cli-parameters.qdocinc configuration-name + \include cli-parameters.qdocinc property + + \section1 Examples + + Opens a shell with the same environment that \QBS uses when building the + project: + + \code + qbs shell + \endcode +*/ diff --git a/doc/reference/cli/builtin/cli-status.qdoc b/doc/reference/cli/builtin/cli-status.qdoc new file mode 100644 index 00000000..ddc8537f --- /dev/null +++ b/doc/reference/cli/builtin/cli-status.qdoc @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-status.html + \ingroup cli + + \title status + \brief Shows the status of files in the project directory. + + \section1 Synopsis + + \code + qbs status [options] [config:configuration-name] + \endcode + + \section1 Description + + Lists all the files in the project directory and shows whether they are + known to \QBS in the respective configuration. + + \section1 Options + + \include cli-options.qdocinc build-directory + \include cli-options.qdocinc settings-dir + + \section1 Parameters + + \include cli-parameters.qdocinc configuration-name + + \section1 Examples + + To list the files in the project directory and view their status: + + \code + qbs status + \endcode +*/ diff --git a/doc/reference/cli/builtin/cli-update-timestamps.qdoc b/doc/reference/cli/builtin/cli-update-timestamps.qdoc new file mode 100644 index 00000000..366f1352 --- /dev/null +++ b/doc/reference/cli/builtin/cli-update-timestamps.qdoc @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-update-timestamps.html + \ingroup cli + + \title update-timestamps + \brief Marks the build as up-to-date. + + \section1 Synopsis + + \code + qbs update-timestamps [options] [config:configuration-name] ... + \endcode + + \section1 Description + + Updates the timestamps of all build \l{Artifact}{artifacts}, causing + the next builds of the \l{Project}{project} to do nothing until source + files are updated again. + + This command is useful if you know that the current changes to source files + are irrelevant to the build. + + \note Using this command causes a discrepancy between the actual state of + source files and the information in the build graph, so be careful. + + \section1 Options + + \include cli-options.qdocinc build-directory + \include cli-options.qdocinc less-verbose + \include cli-options.qdocinc log-level + \include cli-options.qdocinc more-verbose + \include cli-options.qdocinc products-specified + \include cli-options.qdocinc settings + + \section1 Parameters + + \include cli-parameters.qdocinc configuration-name + \include cli-parameters.qdocinc property + + \section1 Examples +*/ diff --git a/doc/reference/cli/builtin/cli-version.qdoc b/doc/reference/cli/builtin/cli-version.qdoc new file mode 100644 index 00000000..38240eef --- /dev/null +++ b/doc/reference/cli/builtin/cli-version.qdoc @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-version.html + \ingroup cli + + \title show-version + \brief Prints the \QBS version. + + \section1 Synopsis + + \code + qbs show-version + \endcode + + \section1 Description + + Prints the version of \QBS to stdout. + + \section1 Options + + This command takes no options. + +*/ diff --git a/doc/reference/cli/cli-options.qdocinc b/doc/reference/cli/cli-options.qdocinc new file mode 100644 index 00000000..b02ce68e --- /dev/null +++ b/doc/reference/cli/cli-options.qdocinc @@ -0,0 +1,537 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +/*! + +//! [all-products] + + \section2 \c --all-products + + Processes all \l{Product}{products}, even if their \c builtByDefault + property is \c false. + +//! [all-products] + +//! [blacklist] + + \section2 \c {--blacklist } + + Ignores files whose names match the patterns specified by \c . + The list entries can contain wildcards and are separated by commas. + + By default, no files are ignored. + +//! [blacklist] + +//! [build-directory] + + \section2 \c {--build-directory|-d } + + Specifies a \c where build artifacts are stored. + + The default value is the current directory unless + \c preferences.defaultBuildDirectory is set. + + Relative paths will be interpreted relative to the current directory. + + You can use the following special values as placeholders: + + \list + \li \c @project is expanded to the name of the project file excluding + the extension \c .qbs. + \li \c @path is expanded to the name of the directory containing the + project file. + \endlist + +//! [build-directory] + +//! [changed-files] + + \section2 \c {--changed-files [,...]} + + Assumes that the files specified by \c , and only those files, have + changed. + +//! [changed-files] + +//! [check-outputs] + + \section2 \c --check-outputs + + Forces transformer output \l{Artifact}{artifact} checks. + + Verifies that the output artifacts declared by \l{Rule}{rules} in the + \l{Project}{project} are actually created. + +//! [check-outputs] + +//! [check-timestamps] + + \section2 \c --check-timestamps + + Forces timestamp checks. + + Retrieves the timestamps from the file system, instead of using the file + timestamps that are stored in the build graph. + +//! [check-timestamps] + +//! [clean_install_root] + + \section2 \c --clean-install-root + + Removes the installation base directory before installing. + +//! [clean_install_root] + +//! [command-echo-mode] + + \section2 \c {--command-echo-mode } + + Determines what kind of output to show when executing commands. + + Possible values of \c are: + + \list + \li \c silent + \li \c summary (default value) + \li \c command-line + \li \c command-line-with-environment + \endlist + +//! [command-echo-mode] + +//! [detect-qt-versions] + + \section2 \c --detect + + Attempts to auto-detect all known Qt versions, looking them up in the PATH + environment variable. + +//! [detect-qt-versions] + +//! [detect-toolchains] + + \section2 \c --detect + + Attempts to auto-detect all known toolchains, looking them up in the PATH + environment variable. + +//! [detect-toolchains] + +//! [dry-run] + + \section2 \c --dry-run|-n + + Performs a dry run. No commands will be executed and no permanent changes to + the build graph will be done. + +//! [dry-run] + +//! [export] + + \section2 \c {--export } + + Exports settings to the specified \c . + +//! [export] + +//! [project-file] + + \section2 \c {[--file|-f ]} + + Uses \c as the project file. If \c is a directory and it + contains a single file with the extension \c .qbs, that file will be used. + + If this option is not given at all, the behavior is the same as for + \c{-f }. + +//! [project-file] + +//! [flat] + + \section2 \c --flat + + Does not create nested project files, even if there are subdirectories and + the top-level directory does not contain any files. + +//! [flat] + +//! [force-probe-execution] + + \section2 \c --force-probe-execution + + Forces re-execution of all \l{Probe} items' configure scripts, + rather than using the cached data. + +//! [force-probe-execution] + +//! [generator] + + \section2 \c {--generator|-g } + + Uses the specified build system generator. + + Possible values of \c include: + + \list + \li \c clangdb + \li \c visualstudio2015 + \li \c iarew8 + \endlist + + The available generators are listed if you run the \c {qbs generate} command + without passing a generator to it. + +//! [generator] + +//! [help] + + \section2 \c {--help|-h|-?} + + Displays help for the command. + +//! [help] + +//! [import] + + \section2 \c {--import } + + Imports settings from the specified \c . + +//! [import] + +//! [install-root] + + \section2 \c {--install-root } + + Installs into the specified \c . If the directory does not exist, + it will be created. + + The default value is \c /install-root. + + Use the special value \c @sysroot to install into the \c sysroot. That is, + the value of the \l{qbs::sysroot}{qbs.sysroot} property. + +//! [install-root] + +//! [jobs] + + \section2 \c {--jobs|-j } + + Uses \c concurrent build jobs, where \c must be an integer greater + than zero. + + The default is the number of logical cores. + +//! [jobs] + +//! [job-limits] + + \section2 \c {--job-limits :[,:...]} + + Sets pool-specific job limits. See \l{job-pool-howto}{here} for more information on + job pools. + + \section2 \c {--enforce-project-job-limits} + + Normally, job limits defined in project files via the \l JobLimit item get overridden + by those set on the command line. If this option is passed, they get maximum priority + instead. Use it if there are product-specific limits that make more sense for + that part of the code base than the generic ones you'd like to apply globally. + +//! [job-limits] + +//! [keep-going] + + \section2 \c --keep-going|-k + + Keeps going when errors occur, if at all possible. + +//! [keep-going] + +//! [less-verbose] + + \section2 \c --less-verbose|-q + + Becomes more quiet by decreasing the log level by one. This option can be + given more than once. Excessive occurrences have no effect. + + If the option \c --log-level appears anywhere on the command line in + addition to this option, its value is taken as the base for the decrease. + +//! [less-verbose] + +//! [list-root] + + \section2 \c {--list [ ...]} + + Lists keys under the key \c or all keys if the root is not specified. + + Possible values are: + + \list + \li \c defaultProfile is the default \l{Profile}{profile} to use + if a profile is not specified + \li \c preferences accepts build preferences as second-level keys + \li \c profiles accepts profile names as second-level keys + + \endlist + +//! [list-root] + +//! [config-user] + + \section2 \c {--user} + + Causes read operations to display only the user-level settings, + while the system-level settings are ignored. + Write operations will target the user-level settings, which is also the default. + +//! [config-user] + +//! [config-system] + + \section2 \c {--system} + + Read and write operations will consider only the system-level settings. + +//! [config-system] + +//! [config-ui-system] + + \section2 \c {--system} + + Instructs the tool to work on the system-level settings. Otherwise, + the user-level settings are presented. + +//! [config-ui-system] + +//! [log-level] + + \section2 \c {--log-level } + + Uses the specified log level. + + Possible values of \c are: + + \list + \li \c error + \li \c warning + \li \c info (default value) + \li \c debug + \li \c trace + \endlist + +//! [log-level] + +//! [log-time] + + \section2 \c --log-time + + Logs the time that the operations involved in this command take. + + This option is implied in log levels \c debug and higher. + + This option is mutually exclusive with \c --show-progress. + +//! [log-time] + +//! [more-verbose] + + \section2 \c --more-verbose|-v + + Becomes more verbose by increasing the log level by one. This option can be + given more than once. Excessive occurrences have no effect. + + If the option \c --log-level appears anywhere on the command line in + addition to this option, its value is taken as the base for the increase. + +//! [more-verbose] + +//! [ndk-dir] + + \section2 \c {--ndk-dir } + + Specifies a \c that contains an Android NDK. + +//! [ndk-dir] + +//! [no-build] + + \section2 \c --no-build + + Does not re-build the project before installing or running it. + +//! [no-build] + +//! [no-install] + + \section2 \c --no-install + + Does not install any artifacts as part of the build process. + +//! [no-install] + +//! [products-specified] + + \section2 \c {--products|-p [,...]} + + Takes only the \l{Product}{products} specified by \c and their + dependencies into account. + +//! [products-specified] + +//! [qt-dir] + + \section2 \c {--qt-dir } + + Specifies a \c that contains a Qt version. + +//! [qt-dir] + +//! [sdk-dir] + + \section2 \c {--sdk-dir } + + Specifies a \c that contains an Android SDK. + +//! [sdk-dir] + +//! [settings-dir] + + \section2 \c {--settings-dir } + + Reads all settings (such as \l{Profile}{profile} information) from the + specified \c . If the directory does not exist, it will be + created. + + The default value is system-specific. For example: + + \list + \li Linux: \c $HOME/.config/QtProject/qbs + \li Windows: \c %APPDATA%\QtProject\qbs + \li macOS: \c $HOME/Library/Preferences/qbs + \endlist + +//! [settings-dir] + +//! [setup-run-env-config] + + \target --setup-run-env-config + \section2 \c --setup-run-env-config + + A comma-separated list of strings. They will show up in the \c config + parameter of all \l{Module::}{setupRunEnvironment} scripts. + +//! [setup-run-env-config] + + +//! [show-progress] + + \section2 \c --show-progress + + Shows how command execution is progressing. + + This option is mutually exclusive with \c --log-time. + +//! [show-progress] + +//! [no-fallback-module-provider] + + \section2 \c --no-fallback-module-provider + + If this option is set, then \QBS will not fall back to a pkg-config based + \l{Module Providers}{module provider} if a dependency is not found. + +//! [no-fallback-module-provider] + + +//! [setup-tools-system] + + \section2 \c {--system} + + If this option is given, the profile(s) created by this tool will end up + in the system-level settings and thus be available to all users. + Otherwise, they go into the user-level settings. + +//! [setup-tools-system] + +//! [type] + + \section2 \c {--type } + + Specifies the type of the toolchain. Needed if \QBS cannot determine the + compiler from the name of the executable file located in the specified + directory. + + Possible values include: + + \list + \li \c clang + \li \c gcc + \li \c mingw + \li \c msvc + \li \c iar + \li \c keil + \li \c sdcc + \endlist + +//! [type] + +//! [unset] + + \section2 \c {--unset } + + Removes the specified \c . + +//! [unset] + +//! [wait-lock] + + \section2 \c --wait-lock + + Waits indefinitely for other processes to release the build graph lock. + + This option is typically used by \l{Generators}{generators}, which may + re-invoke multiple \QBS processes on the same project simultaneously. + +//! [wait-lock] + +//! [whitelist] + + \section2 \c {--whitelist } + + Considers only files whose names match the patterns specified by + \c . The list entries can contain wildcards and are separated by + commas. + + By default, all files are considered. + +//! [whitelist] + +*/ diff --git a/doc/reference/cli/cli-parameters.qdocinc b/doc/reference/cli/cli-parameters.qdocinc new file mode 100644 index 00000000..36bb0864 --- /dev/null +++ b/doc/reference/cli/cli-parameters.qdocinc @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +/*! + +//! [arguments] + + \section2 \c -- + + Command-line arguments to be passed to the program to be run. + +//! [arguments] + + +//! [configuration-name] + + \section2 \c config:configuration-name + + Specifies the build configuration to use. + + \QBS can build a project for one or multiple configurations at once, each + having a different set of parameters. The \c config parameter has a + special function: with each occurrence a new configuration instance begins + and all subsequent parameters until the next \c config are assigned to this + instance. Parameter assignments before the first occurrence of \c config are + applied to all build configurations. Inside products and modules, the + current active build configuration can be retrieved via + \l{qbs::configurationName}{qbs.configurationName}. + + In the following snippet, a profile \c clang is applied to all + configurations while \c cpp.optimization is different for \c debug and + \c release: + + \code + profile:clang config:debug cpp.optimization:none config:release cpp.optimization:small + \endcode + + The value of \c config determines the name of the build folder and affects + the default value of the \l{qbs::buildVariant}{qbs.buildVariant} property. + \QBS knows the \c config values \c{"debug"} and \c{"release"}, but in + general any name can be chosen. When naming configurations created for + special purposes, follow the rules for legal names generally used in + programming languages: + + \list + \li The first character must be a letter (a-z), an underscore (_), or a + dollar sign ($). + \li Subsequent characters may be letters, digits, underscores, or dollar + signs. + \endlist + +//! [configuration-name] + +//! [key] + + \section2 \c + + Typically, a \c consists of several levels separated by periods. The + first level shows a \e root value, such as \e preferences or \e profiles. + The root determines whether the second-level keys are build preferences or + profile names. The lower level keys consist of a module name, followed by a + property name. + +//! [key] + +//! [property] + + \section2 \c {property:value} + + Property values set in project files or profiles can be overridden on the + command line. The syntax is: + + \code + .: + \endcode + + For more information, see + \l{Overriding Property Values from the Command Line}. + +//! [property] +*/ diff --git a/doc/reference/cli/cli.qdoc b/doc/reference/cli/cli.qdoc new file mode 100644 index 00000000..c93b51cd --- /dev/null +++ b/doc/reference/cli/cli.qdoc @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** 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 reference.html + \group cli + + \title Command-Line Interface + \brief A list of available command-line commands. + + The \c qbs command-line commands can be invoked as \c{qbs }. + + This reference summarizes the command-line commands. The authoritative + source of up-to-date information is the respective help screen, which you + get by calling \c{qbs help }. + + Some commands are described in context in the \l{Usage} section of the + manual. +*/ diff --git a/doc/reference/cli/tools/cli-config-ui.qdoc b/doc/reference/cli/tools/cli-config-ui.qdoc new file mode 100644 index 00000000..d900841d --- /dev/null +++ b/doc/reference/cli/tools/cli-config-ui.qdoc @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-config-ui.html + \ingroup cli + + \title config-ui + \brief Opens a GUI application for managing \QBS settings, such as + preferences and profiles. + + \section1 Synopsis + + \code + qbs config-ui [--settings-dir ] [--system] + \endcode + + \section1 Description + + Opens the \QBS Settings application for managing \QBS settings, such as + preferences and profiles, in a hierarchical view. This makes it easier to + manage a large number of settings than using the \l{config} command from + the command line. + + \image qbs-settings-gui.png + + For more information, see \l{Configuring Profiles and Preferences}. + + \section1 Options + + \include cli-options.qdocinc settings-dir + \include cli-options.qdocinc config-ui-system + \include cli-options.qdocinc help + + \section1 Examples + + Opens \QBS Settings: + + \code + qbs config-ui + \endcode + +*/ diff --git a/doc/reference/cli/tools/cli-config.qdoc b/doc/reference/cli/tools/cli-config.qdoc new file mode 100644 index 00000000..2b78ff8e --- /dev/null +++ b/doc/reference/cli/tools/cli-config.qdoc @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-config.html + \ingroup cli + + \title config + \brief Manages \QBS settings, such as preferences and profiles. + + \section1 Synopsis + + \code + qbs config [--settings-dir ] [--user|--system] [--list [ ...]] + [--unset ] [--export ] [--import ] + qbs config [--settings-dir ] [--user|--system] + qbs config [--settings-dir ] [--user|--system] + \endcode + + \section1 Description + + Manages \QBS settings, such as preferences and profiles. You can list all + keys and remove the specified key, as well as import settings from a file + or export them to a file. + + The second form of this command displays the specified \c . + + The third form sets the specified \c with the specified \c . + + There are two sets of settings: The system-level settings affect all users, + while the user-level settings are specific to the current user. + By default, the read operations consider both sources. If the same key is + present in both settings, then for list values, the system value is + appended to the user value, while for other types of values the user-level + one takes precedence. Write operations go to the user-level settings by default. + Use the \c {--user} and \c {--system} options to change this behavior. + \note It is conceivable that the default system value of the + \c {preferences.qbsSearchPaths} setting could pull in unwanted \QBS modules, + in particular when doing cross-builds. In such a case, you can set + \c {preferences.ignoreSystemSearchPaths} to exclude the search paths coming + from the system settings. You'll typically do this for a specific profile: + \code + $ qbs config profiles.myprofile.preferences.ignoreSystemSearchPaths true + \endcode + + You can use the \l{config-ui} command to open the Qbs Settings tool for + managing settings in a hierarchical view. + + For more information, see \l{Configuring Profiles and Preferences}, \l{Managing Qt Versions}, + \l{Modules}, \l {List of Modules}, and \l{Custom Modules and Items}. + + \section1 Options + + \include cli-options.qdocinc settings-dir + \include cli-options.qdocinc list-root + \include cli-options.qdocinc unset + \include cli-options.qdocinc export + \include cli-options.qdocinc import + \include cli-options.qdocinc config-user + \include cli-options.qdocinc config-system + \include cli-options.qdocinc help + + \section1 Parameters + + \include cli-parameters.qdocinc key + + \section1 Examples + + Lists the existing profiles: + + \code + qbs config --list profiles + \endcode + + Shows the default profile: + + \code + qbs config defaultProfile + \endcode + + \note If no output is shown, the default profile used is the built-in + profile \c none, which sets no properties. + + Sets the profile called \c gcc as the base profile of the Qt profile called + \c myqt: + + \code + qbs config profiles.myqt.baseProfile gcc + \endcode + + Adds the location of a custom module to your preferences, to make the module + accessible in several projects: + + \code + qbs config preferences.qbsSearchPaths /usr/local/share/custom-qbs-extensions + \endcode +*/ diff --git a/doc/reference/cli/tools/cli-create-project.qdoc b/doc/reference/cli/tools/cli-create-project.qdoc new file mode 100644 index 00000000..db3af228 --- /dev/null +++ b/doc/reference/cli/tools/cli-create-project.qdoc @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-create-project.html + \ingroup cli + + \title create-project + \brief Creates a \QBS project from an existing set of source files. + + \section1 Synopsis + + \code + qbs create-project [--blacklist ] [--whitelist ] + [--flat] + \endcode + + \section1 Description + + Automatically generates \QBS project files from an arbitrary directory + structure. You can filter the files to add to the project by blacklisting + or whitelisting them. The command creates nested project files, unless you + use the \c --flat option. + + This is a useful starting point when migrating from other build tools, such + as qmake or CMake. + + \note Run this command from the project directory. + + After generating the initial .qbs files, add the missing configuration + variables and functions to them. + + \section1 Options + + \include cli-options.qdocinc blacklist + \include cli-options.qdocinc whitelist + \include cli-options.qdocinc flat + \include cli-options.qdocinc help + + \section1 Examples + + Creates \c .qbs files in the project directory when run from there: + + \code + qbs create-project + \endcode +*/ diff --git a/doc/reference/cli/tools/cli-setup-android.qdoc b/doc/reference/cli/tools/cli-setup-android.qdoc new file mode 100644 index 00000000..5421de89 --- /dev/null +++ b/doc/reference/cli/tools/cli-setup-android.qdoc @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-setup-android.html + \ingroup cli + + \title setup-android + \brief Creates \QBS profiles for Android SDK and NDK installations. + + \section1 Synopsis + + \code + qbs setup-android [--settings-dir ] [--ndk-dir ] + [--sdk-dir ] [--qt-dir ] + + \endcode + + \section1 Description + + Creates \QBS profiles for Android SDK and NDK installations. + + If a Qt path is given, these additional profiles will be suitable for + building Qt binaries for the respective architecture, if the Qt + installation has support for it. + + \section1 Options + + \include cli-options.qdocinc settings-dir + \include cli-options.qdocinc ndk-dir + \include cli-options.qdocinc sdk-dir + \include cli-options.qdocinc qt-dir + \include cli-options.qdocinc help + + \section1 Examples + + Creates a profile with the name \c oreo that specifies the target platforms' + \l{qbs::}{architectures} and operating systems, as well as the + toolchains available: + + \code + qbs setup-android oreo + \endcode +*/ diff --git a/doc/reference/cli/tools/cli-setup-qt.qdoc b/doc/reference/cli/tools/cli-setup-qt.qdoc new file mode 100644 index 00000000..1cf961d0 --- /dev/null +++ b/doc/reference/cli/tools/cli-setup-qt.qdoc @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-setup-qt.html + \ingroup cli + + \title setup-qt + \brief Creates \QBS profiles for Qt installations. + + \section1 Synopsis + + \code + qbs setup-qt [--settings-dir ] [--system] --detect + qbs setup-qt [--settings-dir ] [--system] + \endcode + + \section1 Description + + Creates \QBS profiles for Qt installations. + + The first form tries to auto-detect all known Qt versions, looking them up + via the PATH variable. + + The second form creates a profile with the name \c {} for the + Qt version located at \c {}. + + For more information, see \l{Managing Qt Versions}. + + \section1 Options + + \include cli-options.qdocinc detect-qt-versions + \include cli-options.qdocinc settings-dir + \include cli-options.qdocinc setup-tools-system + \include cli-options.qdocinc help + + \section1 Examples + + Looks up Qt versions in the PATH and creates profiles for them: + + \code + qbs setup-qt --detect + \endcode + + Creates a profile called \c myqt for the Qt version located in + \c /usr/bin/qmake: + + \code + qbs setup-qt /usr/bin/qmake myqt + \endcode +*/ diff --git a/doc/reference/cli/tools/cli-setup-toolchains.qdoc b/doc/reference/cli/tools/cli-setup-toolchains.qdoc new file mode 100644 index 00000000..70cb0c23 --- /dev/null +++ b/doc/reference/cli/tools/cli-setup-toolchains.qdoc @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 cli.html + \page cli-setup-toolchains.html + \ingroup cli + + \title setup-toolchains + \brief Creates \QBS profiles for toolchains, such as GCC or MSVC. + + \section1 Synopsis + + \code + qbs setup-toolchains [--settings-dir ] [--system] --detect + qbs setup-toolchains [--settings-dir ] [--system] [--type ] + + \endcode + + \section1 Description + + The first form tries to auto-detect all known toolchains, looking them up + via the PATH variable. + + The second form creates a profile with the name \c {} for the + toolchain located at \c {}. It will attempt to + determine the toolchain type automatically based on the file name or the + compiler executable. If that fails, you need to provide the compiler type as + a value of the \c --type option. + + For more information, see \l{Configuring Profiles and Preferences}. + + \section1 Options + + \include cli-options.qdocinc settings-dir + \include cli-options.qdocinc setup-tools-system + \include cli-options.qdocinc detect-toolchains + \include cli-options.qdocinc type + \include cli-options.qdocinc help + + \section1 Examples + + Looks up toolchains via the PATH variable and creates profiles for them: + + \code + qbs setup-toolchains --detect + \endcode + + Creates a profile called \c mingw for the toolchain located in + \c C:\mingw530_32\bin on Windows: + + \code + qbs setup-toolchains C:\mingw530_32\bin\g++.exe mingw + \endcode + + Creates a profile called \c g++-mingw-w64 for the toolchain located in + \c /usr/bin/x86_64-w64-mingw32-g++ g++-mingw-w64 on Ubuntu: + + \code + qbs setup-toolchains /usr/bin/x86_64-w64-mingw32-g++ g++-mingw-w64 + \endcode +*/ diff --git a/doc/reference/commands.qdoc b/doc/reference/commands.qdoc new file mode 100644 index 00000000..655412c0 --- /dev/null +++ b/doc/reference/commands.qdoc @@ -0,0 +1,243 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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$ +** +****************************************************************************/ + +// TODO: "\c" markup is used for all properties in table due to QTBUG-35505. + +/*! + \contentspage reference.html + \page commands.html + + \title Command and JavaScriptCommand + \brief Types of commands to be used in rules + + A \e command is what \QBS executes at build time. It is represented in the language by an object + of type \c Command, which runs a process, or \c JavaScriptCommand, which executes arbitrary + JavaScript code. A command is always created in the prepare script of a \c Rule. + + \section1 Command + + A \c Command represents a process that will be invoked at build time. Its constructor + arguments are the binary to run and a list of command-line arguments. For instance: + \code + var insaneCommand = new Command("rm", ["-r", "/"]); + \endcode + The \l{Rule} item documentation shows a \c Command in context. + + \section1 JavaScriptCommand + + A \c JavaScriptCommand represents a chunk of JavaScript code that is run at build time. + For instance: + \code + var cmd = new JavaScriptCommand(); + cmd.apology = "Sorry."; + cmd.sourceCode = function() { + console.info("I'm a rather pointless command."); + console.info(apology); + }; + \endcode + + Within the source code, the special identifiers \c project and \c product + (giving access to project and product properties, respectively) as well as \c inputs and + \c outputs are available. As the example shows, arbitrary properties can be set on the command + object and then used within the source code. This technique is typically used to forward values + from the prepare script to the command. + The \l{Rule} item documentation shows a \c JavaScriptCommand in context. + + \section1 Properties + + \section2 Common Properties + The following properties are available in both \c Command and \c JavaScriptCommand. + + \table + \header + \li Property + \li Type + \li Default + \li Description + \row + \li \c description + \li string + \li empty + \li A message that is displayed when the command is executed. + \row + \li \c extendedDescription + \li string + \li empty + \li A detailed description that is displayed when the command is executed. + \row + \li \c highlight + \li string + \li empty + \li A tag that can be used to influence how the \c description is displayed. In principle, + the values are arbitrary. The \QBS command-line tool understands the following values and + maps them to different colors if the output device is a terminal: + \list + \li "compiler" indicates that the command processes source code + \li "linker" indicates that the command links objects + \li "codegen" indicates that the command generates source code + \li "filegen" indicates that the command creates arbitrary files + \endlist + All other values are mapped to the default color. + \row + \li \c jobPool + \li string + \li empty + \li Determines which job pool the command will use. An empty + string, which is the default, stands for the global job pool. + See \l{JobLimit}{here} and \l{job-pool-howto}{here} for more information on job pools. + \row + \li \c silent + \li bool + \li false + \li A flag that controls whether the \c description is printed. Set it to \c true for commands that + users need not know about. \note If this property is \c false, then \c description must + not be empty. + \row + \li \c timeout + \li int + \li -1 + \li Time limit for the command execution in seconds. If the command does not finish within + the timeout, it is cancelled. In case of a \c Command, the process is requested to + terminate. If it does not terminate within three seconds, it is killed. A value below + or equal to 0 means no timeout. \br + This property was introduced in Qbs 1.15. + \endtable + + + \section2 Command Properties + + \table + \header + \li Property + \li Type + \li Default + \li Description + \row + \li \c arguments + \li stringList + \li empty + \li The list of arguments to invoke the command with. Explicitly setting this property + overrides an argument list provided when instantiating the object. + \row + \li \c environment + \li stringList + \li empty + \li A list of environment variables that are added to the common build environment. + They are provided as a list of strings in the form "varName=value". + \row + \li \c maxExitCode + \li int + \li 0 + \li The maximum exit code from the process to interpret as success. Setting this should + rarely be necessary, as all well-behaved applications use values other than zero + to indicate failure. + \row + \li \c program + \li string + \li undefined + \li The binary to invoke. Explicitly setting this property overrides a path provided when + instantiating the object. + \row + \li \c relevantEnvironmentVariables + \li stringList + \li undefined + \li Names of environment variables that the invoked binary considers. + If one of these variables changes in the build environment, the command will be + re-run even if the input files are still up to date. + \row + \li \c responseFileThreshold + \li int + \li 32000 on Windows, -1 elsewhere + \li If this value is greater than zero and less than the length of the full command line, + and if \c responseFileUsagePrefix is not empty, the contents of the command line are + moved to a temporary file, whose path becomes the entire contents of the + argument list. The program is then supposed to read the full argument list from that + file. This mechanism is mainly useful to work around Windows limitations regarding + the maximum length of the command line and will only work with programs that explicitly + support it. + \row + \li \c responseFileArgumentIndex + \li int + \li 0 + \li Index of the first argument to include in the response file. For example this may be + used in conjunction with a compiler wrapper where the first argument (the path to the + compiler) must be included on the raw command line. + \row + \li \c responseFileUsagePrefix + \li string + \li empty + \li The prefix that informs \c program that the rest of the argument + is a path to a file containing the actual command line. + \row + \li \c stderrFilterFunction + \li function + \li undefined + \li A function that takes as input the command's actual standard error output and returns a string + that is presented to the user as the command's standard error output. + If it is not set, the output is shown unfiltered. + \row + \li \c stdoutFilterFunction + \li function + \li undefined + \li A function that takes as input the command's actual standard output and returns a string + that is presented to the user as the command's standard output. + If it is not set, the output is shown unfiltered. + \row + \li \c workingDirectory + \li string + \li empty + \li The program's working directory. + \row + \li \c stdoutFilePath + \li string + \li undefined + \li Redirects the filtered standard output content to \c stdoutFilePath. If \c stdoutFilePath is undefined, + the filtered standard output is forwarded to \QBS, possibly to be printed to the console. + \row + \li \c stderrFilePath + \li string + \li undefined + \li Redirects the filtered standard error output content to \c stderrFilePath. If \c stderrFilePath is undefined, + the filtered standard error output is forwarded to \QBS, possibly to be printed to the console. + \endtable + + \section2 JavaScriptCommand Properties + + \table + \header + \li Property + \li Type + \li Default + \li Description + \row + \li \c sourceCode + \li function + \li undefined + \li The JavaScript function to execute. + \endtable +*/ diff --git a/doc/reference/items/convenience/appleapplicationdiskimage.qdoc b/doc/reference/items/convenience/appleapplicationdiskimage.qdoc new file mode 100644 index 00000000..69a10c79 --- /dev/null +++ b/doc/reference/items/convenience/appleapplicationdiskimage.qdoc @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** 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-convenience-items.html + \nextpage AppleDiskImage + \qmltype AppleApplicationDiskImage + \since Qbs 1.9 + \inherits AppleDiskImage + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.AppleApplicationDiskImage + + \brief Apple application drag 'n' drop disk image installer. + + An AppleApplicationDiskImage item is a \l{Product}{product} of the + \l{Product::}{type} \c{"dmg.dmg"} that has a dependency on the + \l{dmg} module. In addition, it has rules and properties + specific to building drag 'n' drop disk image installers with an application + bundle and symbolic link to the \c /Applications directory. + + Any artifacts of product dependencies that are tagged \c{"installable"} will + be copied into the disk image, provided their file paths are relative to the + path specified by the \l{sourceBase} property (that is, are located in that + directory). Any artifacts tagged \c{"installable"} that are not relative to + \l{sourceBase} will be ignored. + + Here is what the project file could look like for a simple DMG installer: + \code + AppleApplicationDiskImage { + Depends { name: "myapp" } + name: "My App" + dmg.volumeName: name + dmg.iconSize: 128 + dmg.windowWidth: 640 + dmg.windowHeight: 280 + dmg.iconPositions: [ + {"path": "Applications", "x": 128, "y": 128}, + {"path": "My App.app", "x": 256, "y": 128} + ] + files: ["background.tiff", "volume-icon.icns"] + Group { + files: ["*.lproj/**"] // licenses + fileTags: ["dmg.license.input"] + } + } + \endcode + + For plain disk images whose contents are not a single application bundle, consider the base + \l{AppleDiskImage} item instead. +*/ + +/*! + \qmlproperty string AppleApplicationDiskImage::sourceBase + + The base directory from which artifacts installed into the disk image will be copied. + This directory is always considered to be relative to \l{qbs::installRoot} + {qbs.installRoot}. + For example, if the application Example.app exists at + \c{qbs.installRoot/Applications/Example.app}, and the value of this property + is \c{"/Applications"}, the application will be located at\c{/Example.app} + relative to the disk image root. Therefore, its full path when the disk + image is mounted would be something like \c{/Volumes/Example-1.0/Example.app}. + + \defaultvalue \c{"/Applications"} +*/ + +/*! + \qmlproperty stringList AppleApplicationDiskImage::symlinks + + List of symlinks to create in the disk image. This is specified as a list of strings, + each string containing two file paths separated by a colon. The first path is the + symlink target, and the second path is the name of the symlink relative to the root of + the disk image. + + \defaultvalue \c{["/Applications:Applications"]} +*/ diff --git a/doc/reference/items/convenience/applediskimage.qdoc b/doc/reference/items/convenience/applediskimage.qdoc new file mode 100644 index 00000000..78772db5 --- /dev/null +++ b/doc/reference/items/convenience/applediskimage.qdoc @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** 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-convenience-items.html + \previouspage AppleApplicationDiskImage + \nextpage Application + \qmltype AppleDiskImage + \inherits Product + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.AppleDiskImage + + \brief Apple disk image. + + An AppleDiskImage item is a is a \l{Product}{product} of the \l{Product::} + {type} \c{"dmg.dmg"} that has a dependency on the \l{dmg} module. + + For single-application drag 'n' drop disk image installers, you will + probably want to use the \l{AppleApplicationDiskImage} item instead. +*/ diff --git a/doc/reference/items/convenience/application.qdoc b/doc/reference/items/convenience/application.qdoc new file mode 100644 index 00000000..ca77e153 --- /dev/null +++ b/doc/reference/items/convenience/application.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-convenience-items.html + \previouspage AppleDiskImage + \nextpage ApplicationExtension + \qmltype Application + \inherits Product + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.Application + + \brief Product of the type application. + + An Application item is a \l{Product} representing an application. + + The target artifact of this type of product is usually an executable binary + tagged \c "application". + However, on Android, unless you set \l{Product::}{consoleApplication} to \c true, + the application target will be an APK or an AAB package tagged \c "android.package" + according to the \l{Android.sdk}{packageType} property. A dependency to the \l{Android.sdk} + module is automatically added to the product. +*/ + +/*! + \qmlproperty bool Application::install + + If \c{true}, the executable that is produced when building the application will be installed + to \l{Application::installDir}{installDir}. + + \defaultvalue \c false + \since Qbs 1.13 +*/ + +/*! + \qmlproperty string Application::installDir + + Where to install the executable that is produced when building the application, if + \l{Application::install}{install} is enabled. + + The value is appended to \l{qbs::installPrefix}{qbs.installPrefix} + when constructing the actual installation directory. + + \defaultvalue \c Applications if the app is a \l{bundle::isBundle}{bundle}, \c bin otherwise. + \since Qbs 1.13 +*/ + +/*! + \qmlproperty string Application::installDebugInformation + + If \c{true}, the debug information will be installed to + \l{Application::debugInformationInstallDir}{debugInformationInstallDir}. + + \defaultvalue \c false + \since Qbs 1.16 + \sa{How do I separate and install debugging symbols?} +*/ + +/*! + \qmlproperty string Application::debugInformationInstallDir + + Where to install the debug information if \l installDebugInformation is enabled. + + The value is appended to \l{qbs::installPrefix}{qbs.installPrefix} + when constructing the actual installation directory. + + \defaultvalue \l installDir. + \since Qbs 1.16 + + \sa{How do I separate and install debugging symbols?} +*/ diff --git a/doc/reference/items/convenience/applicationextension.qdoc b/doc/reference/items/convenience/applicationextension.qdoc new file mode 100644 index 00000000..bc65c544 --- /dev/null +++ b/doc/reference/items/convenience/applicationextension.qdoc @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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-convenience-items.html + \previouspage Application + \nextpage AutotestRunner + \qmltype ApplicationExtension + \inherits XPCService + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.ApplicationExtension + + \brief Application Extension for iOS, macOS, tvOS, or watchOS. + + An ApplicationExtension item is a \l{Product} based on the \l{XPCService} + item that sets some properties required for iOS, macOS, tvOS, or watchOS + Application Extensions. +*/ diff --git a/doc/reference/items/convenience/autotestrunner.qdoc b/doc/reference/items/convenience/autotestrunner.qdoc new file mode 100644 index 00000000..616d6459 --- /dev/null +++ b/doc/reference/items/convenience/autotestrunner.qdoc @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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-convenience-items.html + \previouspage ApplicationExtension + \nextpage CppApplication + \qmltype AutotestRunner + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.AutotestRunner + + \brief Product that runs all autotests in the project. + + An AutotestRunner has a dependency on all \l{Product}{products} with the + \l{Product::}{type} \c "autotest". Building the AutotestRunner product + will then run the respective executables. The \l{Product::}{builtByDefault} + property is set to \c false by default, so running the autotests + has to be explicitly requested. The default name of the product is + \c "autotest-runner". + + To use this feature: + + \list 1 + \li Attach the \c "autotest" type to your autotests: + \code + CppApplication { + name: "tst_mytest" + type: ["application", "autotest"] + // ... + } + \endcode + \li Instantiate exactly one AutotestRunner in your project, typically at + the top level: + \code + Project { + // ... + AutotestRunner { } + // ... + } + \endcode + \li Trigger the autotest execution by building the product: + \code + qbs build -p autotest-runner + \endcode + \endlist + + + \section2 Setting Properties for individual Tests + \target autotestrunner-autotest-module + + To control the behavior of individual tests, some properties of the \c AutotestRunner + can be overridden by depending on the \l autotest module and setting its properties: + + \code + CppApplication { + name: "tst_mytest" + type: ["application", "autotest"] + + Depends { name: "autotest" } + + autotest.timeout: 60 + + // ... + } + \endcode + + + \section2 Relevant Job Pools + \target autotestrunner-job-pools + + \table + \header + \li Pool + \li Since + \li Description + \row + \li \c{"autotest-runner"} + \li 1.15 + \li The job pool used to run the tests. + \endtable + +*/ + +/*! + \qmlproperty stringList AutotestRunner::arguments + + The list of arguments to invoke the autotest with. + A test can override this by setting the \l{autotest::arguments}{arguments} property + of the \l autotest module. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty stringList AutotestRunner::auxiliaryInputs + + This property can contain arbitrary file tags. The AutotestRunner will get dependencies + on all products whose type matches at least one of these tags, and invocation of the + test executables will happen only after all of the respective artifacts have been built. + Set this property if your test executables have run-time dependencies on other products. + + \nodefaultvalue + \since Qbs 1.12 +*/ + +/*! + \qmlproperty stringList AutotestRunner::environment + + A list of environment variables that are added to the run environment. + They are provided as a list of strings in the form \c "varName=value". + + \defaultvalue DYLD_LIBRARY_PATH, DYLD_FRAMEWORK_PATH, and DYLD_ROOT_PATH + are set on macOS, or an empty list for other platforms. +*/ + +/*! + \qmlproperty bool AutotestRunner::limitToSubProject + + By default, only those autotests are considered that are in the same sub-project that + AutotestRunner was instantiated in. If you want to run all autotests regardless + of their location in the project hierarchy, set this property to \c false. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty string AutotestRunner::workingDir + + If this property is set, it will be the working directory for all invoked test executables. + Otherwise, the working directory will the the parent directory of the respective executable. + A test can override this by setting the \l{autotest::workingDir}{workingDir} property + of the \l autotest module. + + \nodefaultvalue + \since Qbs 1.12 +*/ + +/*! + \qmlproperty stringList AutotestRunner::wrapper + + Wrapper binary and its arguments for wrapping autotest calls. + This is useful for tools like Valgrind and alike. + + \defaultvalue empty +*/ + +/*! + \qmlproperty int AutotestRunner::timeout + + Time limit for the execution of the individual tests. If a test does not finish within + the time limit, the test is cancelled and considered failed. A value below or equal to 0 + means no timeout. + A test can override this by setting the \l{autotest::timeout}{timeout} property + of the \l autotest module. + + \defaultvalue -1 + \since Qbs 1.15 +*/ diff --git a/doc/reference/items/convenience/cppapplication.qdoc b/doc/reference/items/convenience/cppapplication.qdoc new file mode 100644 index 00000000..7e71eec4 --- /dev/null +++ b/doc/reference/items/convenience/cppapplication.qdoc @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** 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-convenience-items.html + \previouspage AutotestRunner + \nextpage DynamicLibrary + \qmltype CppApplication + \inherits Application + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.CppApplication + + \brief C++ application. + + A CppApplication is an \l{Application}{application} that has a dependency on the + \l{cpp} module. It is entirely equivalent to the following: + + \code + Application { + Depends { name: "cpp" } + } + \endcode +*/ diff --git a/doc/reference/items/convenience/dynamiclibrary.qdoc b/doc/reference/items/convenience/dynamiclibrary.qdoc new file mode 100644 index 00000000..18a11bf1 --- /dev/null +++ b/doc/reference/items/convenience/dynamiclibrary.qdoc @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** 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-convenience-items.html + \previouspage CppApplication + \nextpage InnoSetup + \qmltype DynamicLibrary + \inherits Library + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.DynamicLibrary + + + \brief Dynamic library. + + A DynamicLibrary item is a \l{Library}{library} of the \l{Product::}{type} + \c "dynamiclibrary". + + For Android targets, the following applies: + \list + \li The \l{Product::type}{Product.type} property value contains + \c "android.nativelibrary" in addition to \c "dynamiclibrary". + \li There is a dependency on the \l{cpp} and \l{Android.ndk} modules. + \endlist +*/ diff --git a/doc/reference/items/convenience/innosetup.qdoc b/doc/reference/items/convenience/innosetup.qdoc new file mode 100644 index 00000000..f4b52daf --- /dev/null +++ b/doc/reference/items/convenience/innosetup.qdoc @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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-convenience-items.html + \previouspage DynamicLibrary + \nextpage InstallPackage + \qmltype InnoSetup + \inherits Product + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.InnoSetup + + \brief Inno Setup installer executable. + + A InnoSetup item is a \l{Product}{product} of the \l{Product::}{type} + \c{"innosetup.exe"} that has a dependency on the \l{innosetup} module. +*/ diff --git a/doc/reference/items/convenience/installpackage.qdoc b/doc/reference/items/convenience/installpackage.qdoc new file mode 100644 index 00000000..4f82f71b --- /dev/null +++ b/doc/reference/items/convenience/installpackage.qdoc @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** 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-convenience-items.html + \previouspage InnoSetup + \nextpage JavaClassCollection + \qmltype InstallPackage + \inherits Product + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.InstallPackage + + \brief Archive of an installed project. + + An InstallPackage item is a \l{Product}{product} of the \l{Product::}{type} + \c{archiver.archive} that has a \l{Depends}{dependency} on the \l{archiver} + module. It is used to produce an archive from a set of installable files. + + Consider the following example project: + + \code + Project { + CppApplication { + name: "myapp" + Depends { name: "mylib" } + files: ["main.cpp"] + Group { + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: "bin" + } + } + DynamicLibrary { + name: "mylib" + files: ["mylib.cpp"] + Group { + name: "public headers" + files: ["mylib.h"] + qbs.install: true + qbs.installDir: "include" + } + Group { + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: "lib" + } + } + + InstallPackage { + archiver.type: "tar" + name: "tar-package" + Depends { name: "myapp" } + Depends { name: "mylib" } + } + } + \endcode + + Building the product \c "tar-package" on a Unix system will result in a tar + file with these contents: + + \code + include/mylib.h + lib/libmylib.so + bin/myapp + \endcode +*/ diff --git a/doc/reference/items/convenience/javaclasscollection.qdoc b/doc/reference/items/convenience/javaclasscollection.qdoc new file mode 100644 index 00000000..fb93eb29 --- /dev/null +++ b/doc/reference/items/convenience/javaclasscollection.qdoc @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** 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-convenience-items.html + \previouspage InstallPackage + \nextpage JavaJarFile + \qmltype JavaClassCollection + \inherits Product + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.JavaClassCollection + + \brief Collection of Java class files not bundled in a jar file. + + A JavaClassCollection item is a \l{Product}{product} of the \l{Product::} + {type} \c{"java.class"} that has a dependency on the \l{java} module. + If the files should end up in a jar file, you should use \l{JavaJarFile} + instead. +*/ diff --git a/doc/reference/items/convenience/javajarfile.qdoc b/doc/reference/items/convenience/javajarfile.qdoc new file mode 100644 index 00000000..cda3e4ba --- /dev/null +++ b/doc/reference/items/convenience/javajarfile.qdoc @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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-convenience-items.html + \previouspage JavaClassCollection + \nextpage Library + \qmltype JavaJarFile + \inherits Product + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.JavaJarFile + + \brief Collection of Java class files bundled in a jar file. + + A JavaJarFile item is a \l{Product}{product} of the \l{Product::}{type} + \c{java.jar}. It is used to produce a jar archive from a set of Java + sources. +*/ + +/*! + \qmlproperty string JavaJarFile::entryPoint + + The entry point for an executable jar file. + + \nodefaultvalue +*/ diff --git a/doc/reference/items/convenience/library.qdoc b/doc/reference/items/convenience/library.qdoc new file mode 100644 index 00000000..fc3a7564 --- /dev/null +++ b/doc/reference/items/convenience/library.qdoc @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** 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-convenience-items.html + \previouspage JavaJarFile + \nextpage LoadableModule + \qmltype Library + \since Qbs 1.4 + \inherits Product + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.NativeBinary + + \brief Generic library. + + A Library item is a base item for native libraries and can have \l{Product::}{type} set to + one of the following values: \c "dynamiclibrary", \c "staticlibrary", \c "loadablemodule". + + The default \l{Product::}{type} value is \c "dynamiclibrary" except for iOS prior to + version 8, in which case the default value is \c "staticlibrary". + + This item can automatically install the library target (and library symlinks on Unix) and + separated debug information. + + For Android targets, the following applies: + \list + \li The \l{Product::type}{Product.type} property value contains + \c "android.nativelibrary" in addition to \c "dynamiclibrary". + \li There is a dependency on the \l{cpp} and \l{Android.ndk} modules. + \endlist +*/ + +/*! + \qmlproperty bool Library::install + + If \c{true}, the library will be installed to \l{Library::installDir}{installDir}. + + \defaultvalue \c false + \since Qbs 1.13 +*/ + +/*! + \qmlproperty string Library::installDir + + Where to install the library, if \l{Library::install}{install} is enabled. On Unix, + the symbolic links are also installed to this location. + + The value is appended to \l{qbs::installPrefix}{qbs.installPrefix} + when constructing the actual installation directory. + + \defaultvalue \c Library/Frameworks if the library is a \l{bundle::isBundle}{bundle}, + otherwise \c bin for Windows and \c lib for Unix-like targets. + \since Qbs 1.13 +*/ + +/*! + \qmlproperty string Library::installDebugInformation + + If \c{true}, the debug information will be installed to + \l{Library::debugInformationInstallDir}{debugInformationInstallDir}. + + \defaultvalue \c false + \since Qbs 1.16 + \sa{How do I separate and install debugging symbols?} +*/ + +/*! + \qmlproperty string Library::debugInformationInstallDir + + Where to install the debug information if \l installDebugInformation is enabled. + + The value is appended to \l{qbs::installPrefix}{qbs.installPrefix} + when constructing the actual installation directory. + + \defaultvalue \l installDir. + \since Qbs 1.16 + + \sa{How do I separate and install debugging symbols?} +*/ + +/*! + \qmlproperty bool Library::installImportLib + + If \c{true}, the import library will be installed to \l importLibInstallDir. + This property is only relevant for Windows targets. + Enable it if you want to create a development package. + + \defaultvalue \c false + \since Qbs 1.13 +*/ + +/*! + \qmlproperty string Library::importLibInstallDir + + Where to install the import library, if \l installImportLib is enabled. + + The value is appended to \l{qbs::installPrefix}{qbs.installPrefix} + when constructing the actual installation directory. + + This property is only relevant for Windows targets. + + \defaultvalue \c lib + \since Qbs 1.13 +*/ + diff --git a/doc/reference/items/convenience/loadablemodule.qdoc b/doc/reference/items/convenience/loadablemodule.qdoc new file mode 100644 index 00000000..76022350 --- /dev/null +++ b/doc/reference/items/convenience/loadablemodule.qdoc @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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-convenience-items.html + \previouspage Library + \nextpage QtApplication + \qmltype LoadableModule + \inherits Library + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.LoadableModule + + \brief Loadable module. + + A LoadableModule item is a \l{Product}{product} of the \l{Product::}{type} + \c{"loadablemodule"}. It exists for the convenience of project file authors. +*/ diff --git a/doc/reference/items/convenience/qtapplication.qdoc b/doc/reference/items/convenience/qtapplication.qdoc new file mode 100644 index 00000000..21041c4c --- /dev/null +++ b/doc/reference/items/convenience/qtapplication.qdoc @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** 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-convenience-items.html + \previouspage LoadableModule + \nextpage QtGuiApplication + \qmltype QtApplication + \inherits CppApplication + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.QtApplication + + \brief Application with a dependency on the Qt Core module. + + A QtApplication is a \l{CppApplication}{C++ application} that has a + \l{Depends}{dependency} on the \l{Qt.core} module. It is entirely + equivalent to the following: + + \code + CppApplication { + Depends { name: "Qt.core" } + } + \endcode + +*/ diff --git a/doc/reference/items/convenience/qtguiapplication.qdoc b/doc/reference/items/convenience/qtguiapplication.qdoc new file mode 100644 index 00000000..e69b9214 --- /dev/null +++ b/doc/reference/items/convenience/qtguiapplication.qdoc @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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-convenience-items.html + \previouspage QtApplication + \nextpage StaticLibrary + \qmltype QtGuiApplication + \inherits QtApplication + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.QtGuiApplication + + \brief Application with a dependency on the Qt GUI module. + + A QtGuiApplication is an application that extends the \l{QtApplication} item + by loading the \l{Qt.gui} module. +*/ diff --git a/doc/reference/items/convenience/staticlibrary.qdoc b/doc/reference/items/convenience/staticlibrary.qdoc new file mode 100644 index 00000000..eeaba063 --- /dev/null +++ b/doc/reference/items/convenience/staticlibrary.qdoc @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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-convenience-items.html + \previouspage QtGuiApplication + \nextpage XPCService + \qmltype StaticLibrary + \inherits Library + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.StaticLibrary + + \brief Static library. + + A StaticLibrary item is a \l{Library}{library} of the \l{Product::}{type} + \c "staticlibrary". +*/ diff --git a/doc/reference/items/convenience/xpcservice.qdoc b/doc/reference/items/convenience/xpcservice.qdoc new file mode 100644 index 00000000..f786cf9a --- /dev/null +++ b/doc/reference/items/convenience/xpcservice.qdoc @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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-convenience-items.html + \previouspage StaticLibrary + \qmltype XPCService + \inherits Application + \inqmlmodule QbsConvenienceItems + \ingroup list-of-items + \keyword QML.XPCService + + \brief XPC service for macOS, iOS, tvOS, or watchOS. + + An XPCService item is a convenience item based on \l Application that + sets some properties required for macOS, iOS, tvOS, or watchOS XPC services. +*/ diff --git a/doc/reference/items/language/artifact.qdoc b/doc/reference/items/language/artifact.qdoc new file mode 100644 index 00000000..f64e2f0c --- /dev/null +++ b/doc/reference/items/language/artifact.qdoc @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 + \nextpage Depends + \ingroup list-of-language-items + \qmltype Artifact + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.Artifact + + \brief Describes a file produced by a Rule. + + An Artifact represents a single file produced by a \l{Rule}. + + For example, if a rule produces three files, it needs to contain three + Artifact items. + + In addition to Artifact properties, you can set module properties within an + Artifact item, as follows: + + \code + Artifact { + filePath: "somefile.cpp" + fileTags: ["cpp"] + cpp.cxxLanguageVersion: "c++11" + // ... + } + \endcode + + \note The code on the right-hand side of Artifact properties has access to + the set of input artifacts. That is, it can refer to the \c inputs map and, + if the rule is not a multiplex rule, the \c input variable. +*/ + +/*! + \qmlproperty bool Artifact::alwaysUpdated + + Setting this property to \c false means the file is not necessarily always + written to by any command run by the rule. + If all artifacts of a rule have this property set to \c false, the commands + of the rule are only executed if all of them are out of date compared to the inputs. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty string Artifact::filePath + + The file path of the target artifact. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList Artifact::fileTags + + A list of tags to attach to the target file. These can then be matched by a + \l{Rule}. + + \defaultvalue An empty list +*/ diff --git a/doc/reference/items/language/depends.qdoc b/doc/reference/items/language/depends.qdoc new file mode 100644 index 00000000..8a3e23ba --- /dev/null +++ b/doc/reference/items/language/depends.qdoc @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** 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 Artifact + \nextpage Export + \qmltype Depends + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.Depends + + \brief Represents dependencies between products and modules. + + A Depends item can appear inside a \l{Product} or \l{Module} item. + + For example, the following product will load the \l{cpp} module. In + addition, it will try to load modules that may or may not exist, and + pass this information on to the compiler. + + \code + Product { + Depends { name: "cpp" } + Depends { + name: "optional_module" + versionAtLeast: "2.0" + required: false + } + + Properties { + condition: optional_module.present + cpp.defines: "HAS_OPTIONAL_MODULE" + } + + // ... + } + \endcode + + + \section1 Dependency Parameters + + Sometimes it is desirable to have certain dependencies handled differently + than others. For example, one might want to depend on a dynamic library + without linking it. + + This can be done by setting the \l{dependency-parameters-cpp}{cpp.link} + dependency parameter to \c{true} in the dynamic library dependency: + + \code + Product { + Depends { name: "cpp" } + Depends { name: "some_dynamic_lib"; cpp.link: false } + + // ... + } + \endcode + + Dependency parameters are a special type of property that can only be set + within Depends and \l [QML] {Parameters} items. Dependency parameters are + declared in the modules they belong to. + + In the example above, the \l{cpp} module declares the parameter \c{link}. + The Depends item for \c{some_dynamic_lib} sets \c{cpp.link} to + \c{false}, which tells the linker rule to ignore this particular dependency. +*/ + +/*! + \qmlproperty bool Depends::condition + + Determines whether the dependency will actually be applied. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty string Depends::versionAtLeast + + The minimum value that the dependency's \c version property needs to have. + If the actual version is lower than that, loading the dependency will fail. + The value consists of integers separated by dots. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string Depends::versionBelow + + A value that the dependency's \c version property must be lower than. If the + actual version is equal to or higher than that, loading the dependency will + fail. The value consists of integers separated by dots. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList Depends::productTypes + + A list of product types. Any enabled product in the project that has a + matching type will become a dependency of the product containing the Depends + item. + + This property is mutually exclusive with the \l name and \l submodules + properties. + + The \l required and \l profiles properties are ignored if this property is + set. + + Product types attached via Module::additionalProductTypes are not considered. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool Depends::required + + Setting this property to \c false creates a \e{soft dependency}, meaning + that it is not considered an error if the given module cannot be found. + In such a case, an instance of the respective module will be created, but + only the \l{Module::present}{Module.present} property will be available for + querying, and it will be set to \c false. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty string Depends::name + + The name of the dependent product or module. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList Depends::profiles + + A list of profiles. + + If the dependency is on a product and that product is going to be built for + more than one profile, you can specify here which instance of the product + the dependency is on. + + See the \l{qbs::profiles}{qbs.profiles} property for more information. + An empty list means a dependency on all instances of the product with the given name, + regardless of their profile. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool Depends::limitToSubProject + + If \l productTypes is set and this property is \c true, only products that + are in the same sub-project as the product containing the Depends item are + considered. + + \defaultvalue \c false +*/ + +/*! + \qmlproperty stringList Depends::submodules + + A list of submodules of the module to depend on, if applicable. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool Depends::enableFallback + + Whether to fall back to a pkg-config based \l{Module Providers}{module provider} + if the dependency is not found. + + \defaultvalue \c true +*/ diff --git a/doc/reference/items/language/export.qdoc b/doc/reference/items/language/export.qdoc new file mode 100644 index 00000000..433b4ca8 --- /dev/null +++ b/doc/reference/items/language/export.qdoc @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** 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 Depends + \nextpage FileTagger + \qmltype Export + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.Export + + \brief Exports dependencies and properties to other products. + + An Export item can appear inside a \l{Product} item. It defines a \l{Module} + with the product's name that can be depended on by other products. + The properties attached to the Export item will take effect in all products + that depend on the product inside which the Export item is defined. + As an example, consider these two products: + \code + Product { + name: "A" + Export { + Depends { name: "cpp" } + cpp.includePaths: product.sourceDirectory + cpp.defines: ["USING_" + product.name.toUpperCase()] + } + } + + Product { + name: "B" + Depends { name: "A" } + } + \endcode + + The sources in product B will be able to use headers from product A without specifying + the full path to them, because the include path has been made known to the compiler via + A's Export item. Additionally, product B will be compiled with the define \c{USING_A}. + + The dependent \l{Product}'s modules are not exported unless explicitly specified within the + \l{Export} item: + \code + Product { + name: "B-Exporting-A" + Depends { name: "A" } + Export { + Depends { name: "A" } + } + } + \endcode + + The relationship of the exported dependencies is transitive. A product C depending on a + product B-Exporting-A will also get a direct dependency on A and thus inherit include + paths and preprocessor macros exported by A. + + \code + Product { + name: "C" + Depends { name: "B-Exporting-A" } + } + \endcode + + In contrast to Module items, \c{product} within Export items refers to the product which defines + the Export item. Use the \c{importingProduct} variable to refer to the product that + pulls in the resulting module: + \code + Product { + name: "D" + Export { + Depends { name: "cpp" } + cpp.includePaths: [product.sourceDirectory, importingProduct.buildDirectory] + } + } + \endcode +*/ + +/*! + \qmlproperty var Export::prefixMapping + + This property allows to provide a translation of exported values between non-deployed and + deployed contexts. It is an array of objects with properties \c prefix and \c replacement. + The array's elements get applied to all other properties set in this item such that if the + property's value start with \c prefix, that prefix gets replaced with \c replacement. + It is typically used for C/C++ include paths. For instance, in a library that provides + header files for inclusion both directly from its source directory (when building it + as part of a bigger project) and from some installed location (when building an unrelated + project against it), you would write something like the following: + \code + Export { + Depends { name: cpp" } + cpp.includePaths: [product.sourceDirectory] + prefixMapping: [{ + prefix: product.sourceDirectory, + replacement: FileInfo.joinPaths(qbs.installPrefix, "include") + }] + } + \endcode + + \defaultvalue \c undefined + \see Exporter.qbs + \see Exporter.pkgconfig + \since 1.12 +*/ diff --git a/doc/reference/items/language/filetagger.qdoc b/doc/reference/items/language/filetagger.qdoc new file mode 100644 index 00000000..1f01037b --- /dev/null +++ b/doc/reference/items/language/filetagger.qdoc @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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 Export + \nextpage Group + \qmltype FileTagger + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.FileTagger + + \brief Assigns file tags to files. + + This item assigns file tags to files. + The FileTagger item can appear in \l{Product} items or \l{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 + \l{cpp} module of \QBS has, among others, the following file tagger: + + \code + FileTagger { + patterns: "*.cpp" + fileTags: ["cpp"] + } + \endcode + + As a result, the \c "cpp" tag is automatically attached to all files ending + with \c ".cpp" in products depending on the \c cpp module. This causes them + to be compiled, because a C++ compiler rule has \c "cpp" in its list of + matching input tags. + + File taggers are disabled if file tags are set explicitly in a \l{Product} + {product} or \l{Group}{group}. For example, the \c "cpp" tag is not attached + to the \c .cpp files in the following product: + + \code + Product { + Depends { name: "cpp" } + Group { + files: "*.cpp" + fileTags: "other" + } + } + \endcode +*/ + +/*! + \qmlproperty stringList FileTagger::patterns + \since Qbs 1.0 + + A list of patterns to match against. Supports the usual wildcards '*', '?' + and '[]'. + + Neither the list itself nor any of its elements may be empty. +*/ + +/*! + \qmlproperty list FileTagger::fileTags + \since Qbs 1.0 + + Tags to attach to a product's files. These can then be matched by a rule. + + \defaultvalue An empty list +*/ + +/*! + \qmlproperty int FileTagger::priority + \since Qbs 1.10 + + The priority of the FileTagger. A higher numerical value means a higher + priority. + + \defaultvalue 0 +*/ diff --git a/doc/reference/items/language/group.qbs b/doc/reference/items/language/group.qbs new file mode 100644 index 00000000..752916c0 --- /dev/null +++ b/doc/reference/items/language/group.qbs @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +Project { + Product { +//! [0] + Group { + name: "Word processing documents" + files: ["*.doc", "*.rtf"] + prefix: "**/" + qbs.install: true + qbs.installDir: "share" + excludeFiles: "do_not_install_this_file.*" + } +//! [0] + } +} diff --git a/doc/reference/items/language/group.qdoc b/doc/reference/items/language/group.qdoc new file mode 100644 index 00000000..caeffcfa --- /dev/null +++ b/doc/reference/items/language/group.qdoc @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** 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 FileTagger + \nextpage JobLimit + \qmltype Group + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.Group + + \brief Groups files in a product. + + This item is attached to a \l{Product}{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 + + 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 "/bin". + \code + Application { + Group { + fileTagsFilter: "application" + qbs.install: true + qbs.installDir: "bin" + } + } + \endcode + + Groups may also appear in \l{Module}{modules}, which causes the respective + sources to be added to the products depending on the said module, unless + the \l{filesAreTargets} property is set. + + Groups can be nested. In this case, child groups inherit the module + properties and \l{FileTagger}{file tags} as well as the prefix of their + parent group. The \l{condition} of a child group gets logically + ANDed with the one of its parent group. +*/ + +/*! + \qmlproperty string Group::name + + The name of the group. Not used internally; mainly useful for IDEs. + + \defaultvalue "Group x", where x is a unique number among all the + groups in the product. +*/ + +/*! + \qmlproperty list Group::files + + The files in the group. Mutually exclusive with \l{fileTagsFilter}. + Relative paths are resolved using the parent directory of the file + that contains the Group item. However, if the \l{prefix} property is set to + an absolute path, then that one becomes the base directory. + + \defaultvalue An empty list +*/ + +/*! + \qmlproperty bool Group::filesAreTargets + + If this property is \c true and the group is in a \l{Module}, the files in the + group will not become source artifacts of the product that depends on the module. + Instead, they are treated like target artifacts of products. That is, they will be + matched against the \l{Rule::inputsFromDependencies}{inputsFromDependencies} + file tag list of \l{Rule}{rules} in products that depend on the module. + + \defaultvalue \c false +*/ + +/*! + \qmlproperty string Group::prefix + + A string to prepend to all files. Slashes are allowed and have directory + semantics. + + \defaultvalue The prefix of the parent group if one exists, otherwise empty. +*/ + +/*! + \qmlproperty list Group::fileTagsFilter + + List of \l{Artifact::fileTags}{artifact.fileTags} to match. Any properties + set in this group will be applied to the product's artifacts whose file tags + match the ones listed here. + + The file tags that the group's \l{fileTags} property specifies will + be added to the matching artifacts. + + This property is mutually exclusive with \l{files}. + + \defaultvalue An empty list +*/ + +/*! + \qmlproperty bool Group::condition + + Determines whether the files in the group are actually considered part of + the project. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty list Group::fileTags + + A list of file tags to attach to the group's files. These can then be + matched by a \l{Rule}{rule}. + + \note \l{FileTagger}{File taggers} are never applied to a file that has this + property set. + + \defaultvalue An empty list +*/ + +/*! + \qmlproperty bool Group::overrideTags + + Determines how tags on files that are listed both at the top level of + a product (or the parent group, if there is one) and a group are handled. + If this property is \c true, then the \l{FileTagger}{file tags} set via the + group replace the ones set via the product or parent group. + If it is \c false, the \e {group tags} are added to the \e {parent tags}. + + This property is ignored if \l{fileTagsFilter} is set. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty list Group::excludeFiles + + A list of files that are \e subtracted from the files list. Useful when + using wildcards. + + \defaultvalue An empty list +*/ diff --git a/doc/reference/items/language/joblimit.qdoc b/doc/reference/items/language/joblimit.qdoc new file mode 100644 index 00000000..cd68bae3 --- /dev/null +++ b/doc/reference/items/language/joblimit.qdoc @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 Group + \nextpage Module + \qmltype JobLimit + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.JobLimit + + \brief Restricts concurrent execution of jobs in a given pool. + + In addition to the global limit on concurrently running commands, a project might + want to restrict concurrent execution of certain types of commands even further, + for instance because they are not well-suited to share certain types of resources. + + In the following example, we define a rule that runs a tool of which at most one + instance can be running for the same project at any given time: + \code + Rule { + // ... + prepare: { + var cmd = new Command("my-exclusive-tool", [project.buildDirectory]); + cmd.description = "running the exclusive tool"; + cmd.jobPool = "exclusive_tool"; + return cmd; + } + } + JobLimit { + jobPool: "exclusive_tool" + jobCount: 1 + } + \endcode + + \c JobLimit items can appear inside \l Product, \l Project and \l Module items. + In the case of collisions, that is, items matching the same job pool but setting + different values, the ones defined inside products have the highest precedence, + and the ones inside modules have the lowest. Items defined in sub-projects have + higher precedence than those defined in parent projects. For items with the same + precedence level, the most restrictive one is chosen, that is, the one with the + lowest job number greater than zero. + + \see {How do I limit the number of concurrent jobs for the linker only?} +*/ + +/*! + \qmlproperty bool JobLimit::condition + + Determines whether the job limit is active. + + If this property is set to \c false, the job limit is ignored. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty int JobLimit::jobCount + + The maximum number of commands in the given \l{jobPool}{job pool} that can run + concurrently. + + A value of zero means "unlimited", negative values are not allowed. + + \note The global job limit always applies: For instance, if you set this + property to 100 for some job pool, and "-j 8" was given on the + command line, then no more than eight instances of commands from + the respective job pool will run at any time. + + This property must always be set. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string JobLimit::jobPool + + The job pool to which apply the limit. + + This property must always be set to a non-empty value. + + \nodefaultvalue +*/ diff --git a/doc/reference/items/language/module.qdoc b/doc/reference/items/language/module.qdoc new file mode 100644 index 00000000..18706b4b --- /dev/null +++ b/doc/reference/items/language/module.qdoc @@ -0,0 +1,357 @@ +/**************************************************************************** +** +** 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 JobLimit + \nextpage ModuleProvider + \qmltype Module + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.Module + + \brief Represents a collection of properties and items that can be loaded into a product. + + A Module item is a collection of properties and language items. It + contributes to building a product if the product has a + \l{Depends}{dependency} on the module. Modules may contain the following + items: + + \list + \li \l{Depends} + \li \l{FileTagger} + \li \l{Group} + \li \l{JobLimit} + \li \l{Parameter} + \li \l{Probe} + \li \l{PropertyOptions} + \li \l{Rule} + \li \l{Scanner} + \endlist + + When a product expresses a dependency on a module, \QBS will create an + instance of the module item in the scope of the product. The product can + then read and write properties from and to the loaded module, respectively. + + Modules in different products are isolated from each other, just as products + cannot access each other's properties. However, products can use the + \l{Export} item to pass dependencies and properties of modules to other + dependent products. + + The following (somewhat artificial) module pre-processes text files by removing certain + characters from them. The module's name is \c{txt_processor}. + + \qml + import qbs.FileInfo + import qbs.TextFile + + Module { + property stringList unwantedCharacters: [] + + FileTagger { + patterns: ["*.raw"] + fileTags: ["raw-txt"] + } + + Rule { + inputs: ["raw-txt"] + + Artifact { + filePath: FileInfo.relativePath(input.filePath, product.sourceDirectory) + + "/" + input.fileName + ".processed" + fileTags: ["processed-txt"] + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Processing " + input.fileName; + cmd.sourceCode = function() { + var inFile = new TextFile(input.filePath, TextFile.ReadOnly); + var content = inFile.readAll(); + inFile.close(); + var unwantedChars = input.txt_processor.unwantedCharacters; + for (var c in unwantedChars) + content = content.replace(unwantedChars[c], ""); + var outFile = new TextFile(output.filePath, TextFile.WriteOnly); + outFile.write(content); + outFile.close(); + }; + return cmd; + } + } + } + \endqml + + And this is how a \l{Product} would use the module: + + \qml + Product { + type: "processed-txt" + Depends { name: "txt_processor" } + txt_processor.unwantedCharacters: ["\r"] + files: [ + "file1.raw", + "file2.raw" + ] + } + \endqml + + The resulting files are tagged with \c{processed-txt} and might be consumed + by a rule in another module. That is possible if another rule has + \c{processed-txt} in its \l{Rule::inputs}{inputs} property. + + For more information about how you make your own modules available to \QBS, + see \l{Custom Modules and Items}. + + \section1 Accessing Product and Module Properties + + When defining a property in a module item, the right-hand side expression is + a binding. Bindings may reference other properties of: + + \list + \li the current module + \li other modules that this module depends on + \li the dependent product + \endlist + + Please note that this applies to bindings in modules only. Property access + in rules and other nested items is different. + + \section2 Accessing Properties of the Current Module + + Sibling properties in the same module can be accessed directly by their name: + + \qml + Module { + property stringList windowsDefaults: ["\r"] + property stringList unwantedCharacters: windowsDefaults + } + \endqml + + \section2 Properties of the Dependent Modules + + When a module loads another module through a \l{Depends} element, it can + access properties of the other module through its name. Assuming there was a + module \c OtherModule with a property \c otherProperty, such an access would + look like this: + + \qml + Module { + Depends { name: "OtherModule" } + property string myProperty: "something-" + OtherModule.otherProperty + } + \endqml + + \section2 Accessing Properties of the Dependent Product + + \qml + Module { + property bool featureEnabled: + (product.type.contains("application")) ? true : false + } + \endqml + + \section1 Special Property Values + + For every property defined in a module, \QBS provides the following special + built-in values: + + \list + \li \l base + \li \l original + \li \l outer + \endlist + + \section2 \c base + This value is useful when making use of inheritance. It stands for the value of the respective + property in the item one level up in the inheritance chain. For instance: + \code + Product { // defined in MyProduct.qbs + Depends { name: "mymodule" } + mymodule.someProperty: ["value1"] + } + ------ some other file ------ + MyProduct { + mymodule.someProperty: base.concat(["value2"]) // => ["value1", "value2"] + } + \endcode + + \section2 \c original + This is the value of the property in the module itself (possibly overridden from a profile or + the command line). Use it to set a module property conditionally: + \code + Module { // This is mymodule + property string aProperty: "z" + } + ---------- + Product { + Depends { name: "mymodule" } + Depends { name: "myothermodule" } + mymodule.aProperty: myothermodule.anotherProperty === "x" ? "y" : original // => "y" if myothermodule.anotherProperty is "x", "z" otherwise + \endcode + + \section2 \c outer + This value is used in nested items, where it refers to the value of the respective property + in the surrounding item. It is only valid in \l{Group} and \l{Properties} items: + \code + Product { + Depends { name: "mymodule" } + mymodule.someProperty: ["value1"] + Group { + name: "special files" + files: ["somefile1", "somefile2"] + mymodule.someProperty: outer.concat(["value"]) // => ["value1", "value2"] + } + } + \endcode + + + \section1 Dependency Parameters + + Modules can declare dependency parameters. Those parameters can be set + within \l{Depends} items. \l{Rule}{Rules} of the module can read the + parameters of dependencies and act accordingly. + + In the following example, the module \e{foo} declares the parameter + \c{ignore}. A dependency to \c{bar} then sets the parameter \c{foo.ignore} + to \c{true}. A rule in \c{foo} ignores all dependencies that have + \c{foo.ignore} set to true. + + \code + Module { // Definition of module 'foo'. + Parameter { property bool ignore } + Rule { + ... + prepare: { + for (i in product.dependencies) { + var dep = product.dependencies[i]; + if (dep.foo.ignore) + continue; + // Do something with the dependency. + } + } + } + ... + } + ---------- + Product { + Depends { name: "foo" } + Depends { name: "bar"; foo.ignore: true } + } + \endcode +*/ + +/*! + \qmlproperty stringList Module::additionalProductTypes + + A list of elements that will be added to the \l{Product::type}{type} + property of a product that has a dependency on the module. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty bool Module::condition + + Whether the module is enabled. If this property is \c false, the + surrounding Module item will not be considered in the module look-up. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty bool Module::present + \readonly + + This property is \c false if and only if the respective \l{Depends} item had + its \l{Depends::required}{required} property set to \c false and the module + was not found. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty int Module::priority + + The priority of this module instance. If there is more than one module + instance available for a module name, the module with the highest priority + is chosen. + + \defaultvalue 0 +*/ + +/*! + \qmlproperty script Module::setupBuildEnvironment + + A script for setting up the environment in which a product is built. + + The code in this script is treated as a function with the signature + \c{function(project, product)}. + + Use the \l{Environment Service}{Environment} functions to alter the + environment. + + The return value of this script is ignored. + + \nodefaultvalue +*/ + +/*! + \qmlproperty script Module::setupRunEnvironment + + A script for setting up the environment in which a product is run. + + The code in this script is treated as a function with the signature + \c{function(project, product, config)}. + + The \c config parameter is a list of arbitrary strings that can be passed + via the \l{run} command. The values supported by specific modules are + listed in their respective documentation. + + Use the \l{Environment Service}{Environment} functions to alter the + environment. + + The return value of this script is ignored. + + \nodefaultvalue +*/ + +/*! + \qmlproperty script Module::validate + + A script that is run after the module is loaded. It can be used to check + property values and throw errors in unexpected cases. The return value is + ignored. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string Module::version + + The module's version. It consists of integer values separated by dots. You + can check for specific values of this property in a \l{Depends} item. +*/ diff --git a/doc/reference/items/language/moduleprovider.qdoc b/doc/reference/items/language/moduleprovider.qdoc new file mode 100644 index 00000000..ddbc2595 --- /dev/null +++ b/doc/reference/items/language/moduleprovider.qdoc @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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 Module + \nextpage Parameter + \qmltype ModuleProvider + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.ModuleProvider + + \brief Creates modules on demand. + + The \c ModuleProvider item implements the module creation part of the procedure described + in the \l {Module Providers} overview. It is always located in a file called \c provider.qbs. + + The actual module creation is done on the right-hand side of the + \l{ModuleProvider::relativeSearchPaths}{relativeSearchPaths} property. + + Here is a complete minimal example of a module provider. It just creates an empty module. + If you put this item into the file \c {module-providers/mymodule/provider.qbs} + in your project source directory, you will be able to successfully build a product which + contains a dependency on the module \c mymodule. + \code + import qbs.File + import qbs.FileInfo + import qbs.TextFile + + ModuleProvider { + relativeSearchPaths: { + var moduleDir = FileInfo.joinPaths(outputBaseDir, "modules", name); + File.makePath(moduleDir); + var moduleFilePath = FileInfo.joinPaths(moduleDir, name + ".qbs"); + var moduleFile = new TextFile(moduleFilePath, TextFile.WriteOnly); + moduleFile.writeLine("Module {"); + moduleFile.writeLine("}"); + moduleFile.close(); + return ""; + } + } + \endcode +*/ + +/*! + \qmlproperty string ModuleProvider::name + + The name of the module provider. + + This property is set by \QBS. For simple dependency names, it is the name of the dependency + as specified in the \l Depends item. If the dependency name consists of multiple components, + the value is the name up until (and including) the component that corresponds to the directory + the provider was found in. For instance, if the dependency is \c {x.m1} and the provider was + found in \c {module-providers/x/m1/provider.qbs}, then \c name is \c {x.m1}. + If the provider was found in \c {module-providers/x/provider.qbs}, then \c name is \c x. +*/ + +/*! + \qmlproperty string ModuleProvider::outputBaseDir + + The path under which the new modules should be created when \l relativeSearchPaths + is evaluated. The path is unique for the current provider in the given configuration. + + This property is set by \QBS. +*/ + +/*! + \qmlproperty stringList ModuleProvider::relativeSearchPaths + + This property gets evaluated by \QBS to retrieve new search paths with which + to re-attempt the module look-up. + + It is here where you need to put the code that creates the new module files. + Use the directory structure explained in \l {Custom Modules and Items}. + That is, the file for a module called \c m will be located in a directory \c {modules/m/}, + anchored at \l outputBaseDir. + + The return value is the list of search paths required to find the new module, + relative to \l outputBaseDir. In most cases, only a single search path will be required, + in which case a single-element list containing an empty string should be returned + (or just the empty string, because of \QBS' auto-conversion feature). + + The returned list can also be empty, which means that the module provider was not able + to generate any modules in this environment. +*/ diff --git a/doc/reference/items/language/parameter.qdoc b/doc/reference/items/language/parameter.qdoc new file mode 100644 index 00000000..c544af2d --- /dev/null +++ b/doc/reference/items/language/parameter.qdoc @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** 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 Module + \nextpage Parameters + \qmltype Parameter + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.Parameter + + \brief Declares a dependency parameter. + + The Parameter item declares a dependency parameter. It can appear only + within \l{Module} and \l{Export} items. + + The Parameter item contains exactly one property declaration. + + Example: + \code + Module { + Parameter { property string extra } + } + \endcode + + For more information about dependency parameters, see + \l{Module#dependency-parameters}{Module - Dependency Parameters}. +*/ diff --git a/doc/reference/items/language/parameters.qdoc b/doc/reference/items/language/parameters.qdoc new file mode 100644 index 00000000..bbccb222 --- /dev/null +++ b/doc/reference/items/language/parameters.qdoc @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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 Parameter + \nextpage Probe + \qmltype Parameters + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.Parameters + + \brief Defines default values for dependency parameters within Export items. + + The Parameters item defines default values for dependency parameters within + \l{Export} items. + + Example: + \code + DynamicLibrary { + name: "foo" + ... + Export { + ... + Parameters { + cpp.link: false + } + } + } + \endcode + + Every dependency on \e{foo} has the parameter \l{dependency-parameters-cpp} + {cpp.link} set to \c{false} by default. + + This can be overridden explicitly: + \code + Depends { name: "foo"; cpp.link: true } + \endcode + + The Parameters item contains a number of property bindings where + each property must be a properly declared dependency parameter. + For more information, see \l{Parameter} and + \l{Module#dependency-parameters}{Module - Dependency Parameters}. +*/ diff --git a/doc/reference/items/language/probe.qdoc b/doc/reference/items/language/probe.qdoc new file mode 100644 index 00000000..4ebfae31 --- /dev/null +++ b/doc/reference/items/language/probe.qdoc @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 Parameters + \nextpage Product + \qmltype Probe + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \ingroup list-of-probes + \keyword QML.Probe + + \brief Locates files outside the project. + + Probe items can appear inside \l{Product} and \l{Project} items. + They are run prior to building, for instance to locate dependent headers, libraries, and other + files outside the project directory whose locations are not known ahead of time. + Probes can be parameterized via their properties and typically store results in + properties as well. These results are then retrieved via the Probe's id, which is mandatory: + \code + Product { + Probe { + id: valueCalculator + property string parameter: "whatever" + property int value + configure: { + value = Utils.calculateValue(parameter); // Expensive operation + found = true; + } + } + property int theValue: valueCalculator.value + } + \endcode + + \note Because Probes often invoke external processes, which is relatively expensive compared + to evaluating normal properties, their results are cached. To force re-evaluation + of a Probe, you can supply the \l{build-force-probe-execution} + {--force-probe-execution} command-line option to the \l{build} command. +*/ + +/*! + \qmlproperty bool Probe::condition + + Determines whether the probe will actually be run. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty bool Probe::found + + Indicates whether the probe was run successfully. Set by \l{configure}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty script Probe::configure + + A script that is executed when the probe is run. +*/ diff --git a/doc/reference/items/language/product.qdoc b/doc/reference/items/language/product.qdoc new file mode 100644 index 00000000..f3b51f0b --- /dev/null +++ b/doc/reference/items/language/product.qdoc @@ -0,0 +1,286 @@ +/**************************************************************************** +** +** 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 Probe + \nextpage Profile + \qmltype Product + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.Product + + \brief Represents the result of a build process. + + A Product typically represents the result of a build process. It specifies a set of + input and output files and a way to transform the former into the latter. For example, the + following product sets up a very simple C++ application: + \code + Product { + name: "helloworld" + type: "application" + files: "main.cpp" + Depends { name: "cpp" } + } + \endcode + The \l{type} property specifies what will be built (an executable). The + \l{files} property specifies the input files (one C++ source file), and the + \l{Depends} item pulls in the logic from the \l{cpp} module + about how to do the necessary transformations. + For some often-used types of products, \QBS pre-defines special derived items that save + users some typing. These are: + \list + \li \l{Application} + \li \l{CppApplication} + \li \l{DynamicLibrary} + \li \l{StaticLibrary} + \endlist + Therefore, the above example could also be written like this: + \code + CppApplication { + name: "helloworld" + files: "main.cpp" + } + \endcode + Any property \c prop attached to this item is available in sub-items as \c product.prop, as + well as in modules that are loaded from this product. + + \section1 Multiplexing Properties + + The following properties are relevant for product multiplexing only. + Unless multiplexing is desired, they can be left at their default values: + + \list + \li \l{aggregate} + \li \l{multiplexedType} + \li \l{multiplexByQbsProperties} + \endlist + + \note We do not promise backwards compatibility for multiplexing properties + as they are likely to change in future \QBS versions. + + \section1 Read-Only Properties + + The following properties are automatically set by \QBS and cannot be + changed by the user: + + \list + \li \l{buildDirectory} + \li \l{sourceDirectory} + \endlist +*/ + +/*! + \qmlproperty bool Product::builtByDefault + + Determines whether the product will be built. + + If \c false, the product will only be built if this is explicitly requested, + either by listing the product name as an argument to the + \l{build-products}{--products} option or by using the + \l{build-all-products}{--all-products} option of the \l{build} command. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty bool Product::condition + + Determines whether the product will be built. + + If \c false, the product will not be built. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty string Product::name + + The name of the product. Used to identify the product in a \l{Depends} item, for + example. The value of this property must be a simple JavaScript expression that + does not depend on module properties or values that are non-local to this product. + + \code + CppApplication { + name: "hello" + "world" + // valid + } + CppApplication { + name: "app_" + qbs.targetOS.join("_") + // invalid + } + \endcode + + To change the name of your product's target artifact, modify \l{targetName} + instead. + + \defaultvalue An empty string +*/ + +/*! + \qmlproperty stringList Product::type + + The file tags matching the product's target artifacts. + + \defaultvalue An empty list +*/ + +/*! + \qmlproperty string Product::targetName + + The base file name of the product's target artifacts. + + \defaultvalue The value of \l{name} with illegal file name characters + replaced by underscores. +*/ + +/*! + \qmlproperty string Product::destinationDirectory + + The directory where the target artifacts will be located. If a relative path + is given, the base directory will be \l{Project::buildDirectory} + {project.buildDirectory}. + + \defaultvalue \c{product.buildDirectory} +*/ + +/*! + \qmlproperty stringList Product::files + + A list of source files. Syntactic sugar to save a \l{Group} item for simple + products. + + Relative paths are resolved using the parent directory of the project file + that sets the property. + + \defaultvalue An empty list +*/ + +/*! + \qmlproperty stringList Product::excludeFiles + + A list of source files not to include. Useful with wildcards. + For more information, see \l{Group}. + + \defaultvalue An empty list +*/ + +/*! + \qmlproperty bool Product::consoleApplication + + On Windows, determines whether a console or GUI application is generated. + + If \c true, a console application is generated. If \c false, a GUI + application is generated. + + On Apple platforms, influences the default application type. + + If \c true, a normal executable is generated. + If \c false, an application bundle is generated. + + \defaultvalue Linker-dependent +*/ + +/*! + \qmlproperty stringList Product::qbsSearchPaths + + A list of paths that are searched for imports, modules and module providers. + + The value set here will be merged with the value of + \l{Project::qbsSearchPaths}{project.qbsSearchPaths}. + + For the details about how to add custom items, see the \l{Custom Modules and Items} page. + + \defaultvalue \l{Project::qbsSearchPaths}{project.qbsSearchPaths} +*/ + +/*! + \qmlproperty string Product::version + + The version number of the product. Used in shared library filenames and generated + Info.plist files in Apple application and framework bundles, for example. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool Product::aggregate + + If \c{true}, an aggregate product will be created that has dependencies on all + multiplex instances of this product. + + \note If you do not want to do multiplexing, you can use the default value + \c{undefined}. +*/ + +/*! + \qmlproperty stringList Product::multiplexedType + + Specifies the product type for the multiplexed product instances. + + \note If you do not want to do multiplexing, you can use the default value + \c{undefined}. +*/ + +/*! + \qmlproperty stringList Product::multiplexByQbsProperties + + Specifies which properties of the \l{qbs} module will be used for product + multiplexing: + + \list + \li \l{qbs::architectures}{architectures} + \li \l{qbs::buildVariants}{buildVariants} + \li \l{qbs::profiles}{profiles} + \endlist + + The value must be a subset of the above values. + + \note If you do not want to do multiplexing, you can use the default value + \c{["profiles"]}. +*/ + +/*! + \qmlproperty path Product::buildDirectory + \readonly + + The build directory for this product. This is the directory where generated + files are placed. + + The value of this property is automatically set by \QBS and cannot be + changed by the user. +*/ + +/*! + \qmlproperty path Product::sourceDirectory + \readonly + + The source directory for this product. This is the directory of the file + where this product is defined. + + The value of this property is automatically set by \QBS and cannot be + changed by the user. +*/ diff --git a/doc/reference/items/language/profile.qdoc b/doc/reference/items/language/profile.qdoc new file mode 100644 index 00000000..b0eccc80 --- /dev/null +++ b/doc/reference/items/language/profile.qdoc @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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 + \nextpage Project + \qmltype Profile + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.Profile + + \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 \l{Configuring Profiles and Preferences} 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.toolchainType: "gcc" + qbs.targetPlatform: "linux" + 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} and \l{Project} items. +*/ + +/*! + \qmlproperty string Profile::baseProfile + + 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. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool Profile::condition + + Determines whether this profile can be used. + + If this property is set to \c false, the profile cannot be used. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty string Profile::name + + 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. + + \nodefaultvalue +*/ diff --git a/doc/reference/items/language/project.qdoc b/doc/reference/items/language/project.qdoc new file mode 100644 index 00000000..b0411649 --- /dev/null +++ b/doc/reference/items/language/project.qdoc @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** 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 Profile + \nextpage Properties + \qmltype Project + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.Project + + \brief Represents a collection of products and properties. + + A Project item represents a collection of of products. In a + non-trivial project, these products are typically defined in their own files and + referenced in the main project file: + \code + Project { + references: [ + "product1/product1.qbs", + "product2/product2.qbs" + ] + } + \endcode + Any property \c prop attached to this item is available in sub-items as \c project.prop. + + While the root of the item hierarchy is always a Project, this kind of item + can also appear further down the hierarchy. Such sub-projects are usually + introduced to group products. See \l{SubProject} for details. + + \note If your project consists of only one product, the Project item can be + omitted. +*/ + +/*! + \qmlproperty path Project::buildDirectory + \readonly + + The build directory of the top-level project. +*/ + +/*! + \qmlproperty string Project::name + + The project name. Only relevant when displaying a project tree in an IDE, + for example. + + \defaultvalue The basename of the file that defines the project. +*/ + +/*! + \qmlproperty string Project::profile + \readonly + + The top-level \l{Profile}{profile} for building the project. This property + is set by \QBS when the project is being set up. +*/ + +/*! + \qmlproperty bool Project::condition + + Whether the project is enabled. If \c false, no \l{Product}{products} or + sub-projects will be collected. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty stringList Project::qbsSearchPaths + + A list of paths that are searched for imports, modules and module providers in addition to the + ones listed in \c{preferences.qbsSearchPaths}. The value set here is merged with the value + inherited from the parent project, if there is one. The result is inherited by + all products in the project. + + For the details about how to add custom items, see the \l{Custom Modules and Items} page. + + \defaultvalue An empty list +*/ + +/*! + \qmlproperty pathList Project::references + + A list of files from which to import products. This is equivalent to defining + the respective \l{Product} items directly under this Project item. + + \defaultvalue An empty list +*/ + +/*! + \qmlproperty path Project::sourceDirectory + \readonly + + The directory where the file containing the top-level Project item is + located. +*/ + +/*! + \qmlproperty string Project::minimumQbsVersion + + The minimum version of \QBS that is needed to build this project. + + \defaultvalue \c "1.3.0" +*/ diff --git a/doc/reference/items/language/properties.qdoc b/doc/reference/items/language/properties.qdoc new file mode 100644 index 00000000..a4d0d46a --- /dev/null +++ b/doc/reference/items/language/properties.qdoc @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** 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 Project + \nextpage PropertyOptions + \qmltype Properties + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.Properties + + \brief Provides conditional setting of properties. + + \note This topic documents the Properties item in the context of \l{Product} + {products}. For more information about using it in sub-projects, see + \l{SubProject}. + + The Properties item is an auxiliary item for setting multiple property values conditionally. + + In the following example, two properties are set if the project is built for Windows: + \code + Product { + Properties { + condition: qbs.targetOS.contains("windows") + cpp.defines: ["ON_WINDOWS"] + cpp.includePaths: ["extraWindowsIncludes"] + } + } + \endcode + + Multiple Properties items can be specified to set properties dependent on different + conditions. The order of appearance is important. Semantics are similar to if-else-chains. + The following example + \code + Product { + Properties { + condition: qbs.targetOS.contains("windows") + cpp.defines: ["ON_WINDOWS"] + cpp.includePaths: ["myWindowsIncludes"] + } + Properties { + condition: qbs.targetOS.contains("linux") + cpp.defines: ["ON_LINUX"] + cpp.includePaths: ["myLinuxIncludes"] + } + cpp.defines: ["ON_UNKNOWN_PLATFORM"] + } + \endcode + is equivalent to + \code + Product { + cpp.defines: { + if (qbs.targetOS.contains("windows")) + return ["ON_WINDOWS"]; + if (qbs.targetOS.contains("linux")) + return ["ON_LINUX"]; + return ["ON_UNKNOWN_PLATFORM"]; + } + cpp.includePaths: { + if (qbs.targetOS.contains("windows")) + return ["myWindowsIncludes"]; + if (qbs.targetOS.contains("linux")) + return ["myLinuxIncludes"]; + return base; + } + } + \endcode + + In Properties items, one can access the \l{Module#outer}{outer value} of a + property. + \code + Product { + Properties { + condition: qbs.targetOS.contains("windows") + cpp.defines: outer.concat("ON_WINDOWS") // === ["FOO", "ON_WINDOWS"] + } + Properties { + condition: qbs.targetOS.contains("linux") + cpp.defines: ["ON_LINUX"] // === ["ON_LINUX"] + } + cpp.defines: ["FOO"] + } + \endcode + + We suggest to use the Properties item for mutually exclusive conditions only. It is + especially useful if there are several properties to set, based on the same condition. +*/ + +/*! + \qmlproperty bool Properties::condition + + The condition to be used for the other bindings in this item. + + This is a mandatory property that has no default value. +*/ + +/*! + \qmlproperty bool Properties::overrideListProperties + + List properties set within this item will override the values coming from + modules, rather than getting merged with them, which is the default behavior. + Use this in the rare case that a module you depend on inserts a value into + a list property that is problematic for some product. + + \defaultvalue \c false +*/ diff --git a/doc/reference/items/language/propertyoptions.qdoc b/doc/reference/items/language/propertyoptions.qdoc new file mode 100644 index 00000000..a953489a --- /dev/null +++ b/doc/reference/items/language/propertyoptions.qdoc @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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 Properties + \nextpage Rule + \inqmlmodule QbsLanguageItems + \qmltype PropertyOptions + \ingroup list-of-items + \keyword QML.PropertyOptions + + \brief Provides inline documentation for properties within product and module items. + + A PropertyOptions item can appear inside a \l{Product} or \l{Module} item to + provide inline documentation for properties. +*/ + +/*! + \qmlproperty stringList PropertyOptions::allowedValues + + A list of the values permitted by the property. + + The default value, \c{undefined}, indicates that any value is permitted. +*/ + +/*! + \qmlproperty string PropertyOptions::description + + A brief description of the property. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string PropertyOptions::name + + The name of the property to document. + + \nodefaultvalue +*/ diff --git a/doc/reference/items/language/rule.qdoc b/doc/reference/items/language/rule.qdoc new file mode 100644 index 00000000..62a32caf --- /dev/null +++ b/doc/reference/items/language/rule.qdoc @@ -0,0 +1,349 @@ +/**************************************************************************** +** +** 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 PropertyOptions + \nextpage Scanner + \qmltype Rule + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.Rule + + \brief Creates transformers for input tags. + + In \QBS, rules create \e transformers that produce output files from input files. + The term \e transformer refers to a list of \l{Command and JavaScriptCommand}{commands}. + These commands are created in a rule's \l{prepare} script. They do the actual work, either + directly or by executing external commands. + + \section1 A Simple Example + + The following rule takes text files and replaces Windows-style line endings with their + Unix-style counterparts. We will look at it one piece at a time. + + \quotefromfile ../examples/rule/rule.qbs + + \skipto Rule + \printuntil multiplex: false + + A \e {multiplex rule} creates one transformer that takes all input artifacts with the + matching input file tag and creates one or more output artifacts. We are setting the + respective property to \c false here, indicating that we want to create one transformer + per input file. + \note This is actually the default, so the above assignment is not required. + + \printuntil inputs: ["txt_input"] + + Here we are specifying that our rule is interested in input files that have the tag + \c "txt_input". Such files could be source files, in which case you would tag them + using a \l{Group}. Or they could in turn get generated by a different rule, + in which case that rule would assign the file tag. + The files matching the tag will be available in the \l{prepare} script under the name + \c inputs (see \l{inputs and outputs}{The inputs and outputs Variables}). + + \printuntil } + + Here we are specifying that for every input file, we want to create one output file + whose name is the same as the input file, but with an additional extension. Because we are + giving a relative path, \QBS will prepend that path by the product's build directory. + + In addition, we tell \QBS that the output files should get the file tag \c "txt_output". This + enables other rules to use these files as inputs. You must always assign suitable file tags + to your output artifacts, or the rule will not be run. + See \l{Rules and Product Types} for details. + + If you want to create more than one output file per input file, you simply provide multiple + \l Artifact items. The set of output artifacts will be available in the prepare script + under the name \c outputs (see \l{inputs and outputs}{The inputs and outputs Variables}). + + \printuntil /^\s{4}\}/ + + The prepare script shown above puts everything together by creating the command that does + the actual transformation of the file contents, employing the help of the + \l{TextFile Service}{TextFile} class. + + As you can see, the return value is an array, meaning you can provide several commands to + implement the rule's functionality. For instance, if we had provided two \c Artifact items, + we might have also provided two commands, each of them creating one output file. + + For the \c input and \c output variables used in the code, see the next section. + + \target inputs and outputs + \section1 The \c inputs and \c outputs Variables + + We already mentioned that the input and output artifacts are available in the prepare script + via the variables \c inputs and \c outputs, respectively. These variables are JavaScript + objects whose property keys are file tags and whose property values are lists of objects + representing the artifacts matching these tags. In our example, the \c inputs variable + has a single property \c txt_input, whose value is a list with one element. Similarly, the + \c outputs variable also has one single property \c txt_output, again with a list containing + one element. + + The actual artifact objects have the following properties: + \table + \header + \li Property + \li Description + \row + \li \c baseName + \li The file name without any extension. + \row + \li \c completeBaseName + \li The file name without the last extension. + \row + \li \c fileName + \li The name of the file (that is, \c filePath without any directory components). + \row + \li \c filePath + \li The full file path. + \row + \li \c fileTags + \li The list of the artifact's file tags. + \endtable + + The artifact object contains a property for every module that is used in the product. That can + be used to access the module's properties. For instance, for an artifact in a C++ product, + \c{artifact.cpp.defines} is the list of defines that will be passed when compiling the + respective file. + + But what about the variables \c input and \c output that appeared in our example? These + are simply convenience variables which are available in the case that the \c inputs + and \c outputs variables contain only one artifact, respectively. So in our example, instead + of \c input we also could have written \c {inputs.txt_input[0]}, which is considerably + more verbose. + + \section1 Rules and Product Types + + It is important to know that 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 instance, consider this simple C++ project: + \code + Product { + type: ["application"] + Depends { name: "cpp" } + files: ["main.cpp"] + } + \endcode + Here's how this product is built: + \list 1 + \li \QBS looks for a rule that can produce artifacts with the file tag + \c{"application"}. Such a rule is found in the \l{cpp} module + (namely, the rule that invokes the linker). + \li Since the rule found in the previous step takes inputs of type \c{"obj"}, \QBS now + looks for a rule that produces artifacts of that type. Again, such a rule is found in + the \c cpp module (the rule that runs the compiler). + \li The rule found in the previous step takes inputs of type \c{"cpp"}. No rule is found + that creates such artifacts, but we do have a source file with a matching type (because + the \c cpp module contains a \l{FileTagger} which attached that type + to \c{"main.cpp"} due to its file extension). + \li Now that there is a chain of rules leading from a source file tag to the product type, + the commands of these rules are executed one after the other until we end up with + our executable. + \endlist + + \section1 A Complete Example + + The following code snippet shows a single \l{Rule} within a \l{Product} and summarizes the + previous sections. + + \note The product's type is set up to the \c "txt_output" file tag to tell \QBS that the + product depends on output artifacts produced by the custom rule. Otherwise the rule would not + be executed. + + \quotefile ../examples/rule/rule.qbs +*/ + +/*! + \qmlproperty bool Rule::multiplex + + Determines whether this is a multiplex rule. + + \defaultvalue \c false +*/ + +/*! + \qmlproperty stringList Rule::inputs + + A list of file tags the input artifacts must match. + + All output artifacts will depend on all artifacts in the product with + the given input file tags. Also, these artifacts are available in the + \c inputs variable of the \l{prepare} script. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList Rule::auxiliaryInputs + + A list of file tags. This rule will be dependent on every other rule that + produces artifacts that are compatible with the value of this property. + + Unlike \l{inputs}, this property has no effect on the content of the + \c inputs variable of the \l{prepare} script. + + All rules in this product and rules of product dependencies that produce + target artifacts are considered. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList Rule::excludedInputs + + A list of file tags. Connections to rules that produce these file tags are + prevented. + + \nodefaultvalue + \since Qbs 1.12 +*/ + +/*! + \qmlproperty stringList Rule::inputsFromDependencies + + A list of file tags that the artifacts of product dependencies must match. + + For example, the product \a foo might appear as follows in the current + product: + + \code + Depends { + name: "foo" + } + \endcode + + All artifacts of \a foo that match the given file tags will appear in the + \c inputs variable of the \l{prepare} script. + + \nodefaultvalue +*/ + +/*! + \qmlproperty var Rule::outputArtifacts + + An array of output artifacts, specified as JavaScript objects. + + For example: + + \code + outputArtifacts: [{ + filePath: "myfile.cpp", + fileTags: ["cpp"], + cpp: { cxxLanguageVersion: "c++11" } + }] + \endcode + + For a description of the possible properties, see the documentation of the + \l{Artifact} item. + + Output artifacts can be specified either by this property or by \l{Artifact} + items. Use this property if the set of outputs is not fixed but depends the + input's content. If no file tags are provided, \QBS will apply all + \l{FileTagger}{file taggers} known in the current context to the output file + name. + + The user may set the property \c{explicitlyDependsOn} on artifact objects, which is + similar to \l{Rule::explicitlyDependsOn}{Rule.explicitlyDependsOn}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList Rule::outputFileTags + + If output artifacts are specified by \l{outputArtifacts}, this property must + specify a list of file tags that the rule potentially produces. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool Rule::condition + + If \c true, the rule is enabled, otherwise it does nothing. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty stringList Rule::explicitlyDependsOn + + A list of file tags. Each artifact that matches the file tags is added to + the dependencies of each output node. All artifacts in the current product + are considered. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList Rule::explicitlyDependsOnFromDependencies + + A list of file tags. Each artifact that matches the file tags is added to + the dependencies of each output node. Only target artifacts of products that this product + depends on are considered. + + \nodefaultvalue + \since Qbs 1.12 +*/ + +/*! + \qmlproperty script Rule::prepare + + A script that prepares the commands to transform the inputs to outputs. + + The code in this script is treated as a function with the signature + \c{function(project, product, inputs, outputs, input, output, explicitlyDependsOn)}. + + The argument \c{input} is \c{undefined} if there's more than one input + artifact for this rule. Similarly, \c{output} is only defined if there is + exactly one output artifact. + + \nodefaultvalue + +*/ + +/*! + \qmlproperty bool Rule::requiresInputs + + Specifies whether a rule's commands should be created even if no inputs are + available. + + Enabling this property can be useful if you are not sure whether input + files exist, and you want \QBS to create an output file even if they do not. + + Set to \c true if the rule declares any inputs, \c false otherwise. +*/ + +/*! + \qmlproperty bool Rule::alwaysRun + + If \c true, the rule's commands are always executed, even if all output + artifacts are up to date. + + \defaultvalue \c false +*/ diff --git a/doc/reference/items/language/scanner.qdoc b/doc/reference/items/language/scanner.qdoc new file mode 100644 index 00000000..9ec57568 --- /dev/null +++ b/doc/reference/items/language/scanner.qdoc @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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 Rule + \nextpage SubProject + \qmltype Scanner + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.Scanner + + \brief Creates custom dependency scanners in modules. + + A Scanner item can appear inside a \l{Module} item, and allows to extract + dependencies for artifacts from the artifacts' file contents. + For example, this is what a scanner for "qrc" files might look like: + \code + import qbs.Xml + + Module { + Scanner { + inputs: 'qrc' + scan: { + var xml = new Xml.DomDocument(input.filePath); + dependencies = []; + // retrieve entries from the XML document + return dependencies; + } + } + } + \endcode +*/ + +/*! + \qmlproperty bool Scanner::condition + + If \c true, the scanner is enabled, otherwise it does nothing. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty stringList Scanner::inputs + + A list of \l{FileTagger}{file tags} the input artifacts must match. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool Scanner::recursive + + Determines whether to scan the returned dependencies using + the same scanner. + + \defaultvalue \c false +*/ + +/*! + \qmlproperty script Scanner::searchPaths + + A script that returns the paths where to look for dependencies. + + The code in this script is treated as a function with the signature + \c{function(project, product, input)}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty script Scanner::scan + + A script that reads the input artifact and returns a string list with + dependencies. + + The code in this script is treated as a function with the signature + \c{function(project, product, input, filePath)}, where \c input is + the artifact at which the scan originated, and \c filePath is the + currently scanned file. For non-recursive scans, \c filePath is + always equal to \c{input.filePath}. + + + \nodefaultvalue +*/ diff --git a/doc/reference/items/language/subproject.qdoc b/doc/reference/items/language/subproject.qdoc new file mode 100644 index 00000000..955e4cd4 --- /dev/null +++ b/doc/reference/items/language/subproject.qdoc @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** 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 Scanner + \qmltype SubProject + \inqmlmodule QbsLanguageItems + \ingroup list-of-items + \keyword QML.SubProject + + \brief Adds a project from a different file. + + A SubProject item is used to add a sub-project that is defined in + a separate file. Additionally, properties of the sub-project can + be set without modifying the separate project file. + + The following example adds a sub-project defined in + \c{subdir/project.qbs} and overrides its name. + + \code + Project { + SubProject { + filePath: "subdir/project.qbs" + Properties { + name: "A sub-project" + } + } + ... + } + \endcode + + A typical use case for SubProject items is to conditionally + include sub-projects. The following example pulls in the \e{tests} + sub-project if and only if the \c{withTests} property is \c{true}. + + \code + Project { + property bool withTests: false + SubProject { + filePath: "tests/tests.qbs" + Properties { + condition: parent.withTests + } + } + ... + } + \endcode + + If you do not need to set any properties on the sub-project, you can also + use the \l{Project::references}{Project.references} property, the same way + you would for a product. + + \code + Project { + references: "subdir/project.qbs" + } + \endcode + + is equivalent with + + \code + Project { + SubProject { + filePath: "subdir/project.qbs" + } + } + \endcode + + It is also possible to nest \l{Project} items directly in the same file. +*/ + +/*! + \qmlproperty bool SubProject::condition + + Whether the sub-project is added. If \c false, the sub-project is not + included. + + Setting this property has the same effect as setting the \c{condition} + property within a \l{Properties} item. If both this property and the + \c{condition} property within a \l{Properties} item are defined, the + sub-project is included only if both properties evaluate to \c{true}. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty path SubProject::filePath + + The file path of the project to add as a sub-project. If the top-level item + in this file is a \l{Product}, it gets wrapped automatically in a new project. + + \defaultvalue empty +*/ + +/*! + \qmlproperty bool SubProject::inheritProperties + + Determines whether the sub-project should inherit the properties of the + surrounding \l{Project}. You can use this feature to share global + settings between projects and sub-projects. + + \defaultvalue \c true +*/ diff --git a/doc/reference/items/probe/binary-probe.qdoc b/doc/reference/items/probe/binary-probe.qdoc new file mode 100644 index 00000000..97eacb63 --- /dev/null +++ b/doc/reference/items/probe/binary-probe.qdoc @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +** 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-probes.html + \qmltype BinaryProbe + \inqmlmodule QbsProbes + \ingroup list-of-items + \keyword QML.BinaryProbe + \inherits PathProbe + + \brief Locates executable files outside the project. + + Finds executable files that have the specified file names. + + BinaryProbe searches for executable files within directories specified by the PATH environment + variable. + + \note On Unix, also searches in the \c /usr/bin and \c /usr/local/bin directories by default. + Override \l {PathProbe::platformSearchPaths}{PathProbe.platformSearchPaths} to change this + behavior. + + \note On Windows, only files that have \e .com, \e .exe, \e .bat, \e .cmd extensions are + considered \e executables. Override \l {PathProbe::nameSuffixes}{PathProbe.nameSuffixes} to + change this behavior. + + For example, BinaryProbe can be used to search for a protobuf compiler executable as follows: + + \code + import qbs.File + import qbs.Probes + + Module { + // search for a protoc executable + Probes.BinaryProbe { + id: protocProbe + names: "protoc" + } + property string executableFilePath: protocProbe.filePath + + validate: { + if (!File.exists(executableFilePath)) + throw "The executable '" + executableFilePath + "' does not exist."; + } + + // use the found executable + Rule { + // rule input/outputs here... + + // run executable + prepare: { + var args = // initialize arguments... + var cmd = new Command(executableFilePath, args); + cmd.highlight = "codegen"; + cmd.description = "generating protobuf files for " + input.fileName; + return [cmd]; + } + } + } + \endcode +*/ diff --git a/doc/reference/items/probe/conanfile-probe.qdoc b/doc/reference/items/probe/conanfile-probe.qdoc new file mode 100644 index 00000000..e47fe15e --- /dev/null +++ b/doc/reference/items/probe/conanfile-probe.qdoc @@ -0,0 +1,249 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Richard Weickelt +** 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-probes.html + \qmltype ConanfileProbe + \inqmlmodule QbsProbes + \ingroup list-of-items + \keyword QML.ConanfileProbe + + \brief Extracts information about dependencies from a Conan recipe file. + + The \c ConanfileProbe interfaces \QBS to the \l{https://conan.io/}{Conan + package manager}. It runs + \l{https://docs.conan.io/en/latest/reference/commands/consumer/install.html}{conan + install} on a Conan recipe file such as \c conanfile.py or \c conanfile.txt + and extracts all available meta information about package dependencies using + the \l{https://docs.conan.io/en/latest/reference/generators/json.html}{json + generator}. The output may be used to set up \l{Profile} items or module + properties in products. \c ConanfileProbe can also be used to run other + Conan generators and to retrieve their output. + + \section1 Examples + + In the following examples we assume that our project contains a \c conanfile.py. + This file describes all dependencies of our project. The dependency packages are + expected to export meta information to be consumed by our project. + + \section2 Including Files Generated by Conan + + Conan has a built-in + \l{https://docs.conan.io/en/latest/reference/generators/qbs.html}{qbs + generator} that creates a project file containing dummy products. This is + the easiest way to access dependencies, but also the least flexible one. It + requires each Conan package to export correct meta information and works only + if the dependency is a library. + + \qml + import qbs.Probes + + Project { + Probes.ConanfileProbe { + id: conan + conanfilePath: project.sourceDirectory + "/conanfile.py" + generators: "qbs" + } + + references: conan.generatedFilesPath + "/conanbuildinfo.qbs" + + CppApplication { + type: "application" + files: "main.cpp" + Depends { name: "mylib" } + } + } + \endqml + + \section2 Setting Module Properties in Products + + When a product depends on a Conan package that does not have a + dedicated \l{List of Modules}{module}, package meta information may be + directly fed into the \l{cpp} module. + + This approach is very flexible. + + \qml + import qbs.Probes + + CppApplication { + Probes.ConanfileProbe { + id: conan + conanfilePath: product.sourceDirectory + "/conanfile.py" + options: ({opt1: "True"; opt2: "TheValue"}) + } + cpp.includePaths: conan.dependencies["myLib"].include_paths + cpp.libraryPaths: conan.dependencies["myLib"].lib_paths + cpp.dynamicLibraries: conan.dependencies["mylib"].libs + } + \endqml + + \section2 Setting Up a Profile + + When multiple products depend on one or more Conan packages, the dependency + information may be combined in a \l{Profile}. This is especially useful when + \QBS modules are available for some of the packages, but some of their + properties need to be initialized. Otherwise, it would have to be done + manually in global profiles. + + \qml + import qbs.Probes + + Project { + Probes.ConanfileProbe { + id: conan + conanfilePath: project.sourceDirectory + "/conanfile.py" + } + Profile { + name: "arm-gcc" + cpp.toolchainInstallPath: conan.dependencies["arm-none-eabi-gcc"].rootpath + "/bin" + cpp.toolchainPrefix: "arm-linux-gnueabi-" + qbs.toolchainType: "gcc" + } + } + \endqml + + This allows fully automated dependency management, including compiler + toolchains and is very useful when teams work in heterougeneous + environments. + +*/ + +/*! + \qmlproperty stringList ConanfileProbe::additionalArguments + + Additional command line arguments that are appended to the \c{conan install} + command. + + \defaultvalue [] +*/ + +/*! + \qmlproperty path ConanfileProbe::conanfilePath + + Path to a \c conanfile.py or \c conanfile.txt that is used by this probe. + + This property cannot be set at the same time as \l{ConanfileProbe::}{packageReference}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty var ConanfileProbe::dependencies + + This property contains the same information as + \l{ConanfileProbe::}{json}.dependencies, but instead of an array, \c + dependencies is a map with package names as keys for convenient access. + + \readonly + \nodefaultvalue +*/ + +/*! + \qmlproperty path ConanfileProbe::executable + + The name of or the path to the Conan executable. + + \defaultvalue "conan.exe" on Windows, "conan" otherwise +*/ + +/*! + \qmlproperty path ConanfileProbe::generatedFilesPath + + The path of the folder where Conan generators store their files. Each + instance of this probe creates a unique folder under + \l{Project::buildDirectory}{Project.buildDirectory}. The folder name is a + hash of the arguments supplied to \c{conan install}. + + \readonly + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList ConanfileProbe::generators + + Conan generators to be executed by this probe. The + \l{https://docs.conan.io/en/latest/reference/generators/json.html}{JSON + generator} is always enabled. Generated files are written to the + \l{ConanfileProbe::generatedFilesPath}{generatedFilesPath} folder. + + \sa {https://docs.conan.io/en/latest/reference/generators.html}{Available + generators} + + \defaultvalue ["json"] +*/ + +/*! + \qmlproperty var ConanfileProbe::json + + The parsed output of Conan's + \l{https://docs.conan.io/en/latest/reference/generators/json.html}{JSON + generator} as a JavaScript object. + + \readonly + \nodefaultvalue +*/ + +/*! + \qmlproperty var ConanfileProbe::options + + Options applied to \c{conan install} via the \c{-o} flag. + This property is an object in the form \c{key:value}. + + Example: + \qml + options: ({someOpt: "True", someOtherOpt: "TheValue"}) + \endqml + + \nodefaultvalue +*/ + +/*! + \qmlproperty string ConanfileProbe::packageReference + + Reference of a Conan package in the form \c{name/version@user/channel}. + Use this property if you want to probe an existing package in the local + cache or on a remote. + + This property cannot be set at the same time as \l{ConanfileProbe::}{conanfilePath}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty var ConanfileProbe::settings + + Settings applied to \c{conan install} via the \c{-s} flag. + This property is an object in the form \c{key:value}. + + Example: + \qml + settings: ({os: "Linux", compiler: "gcc"}) + \endqml + + \nodefaultvalue +*/ diff --git a/doc/reference/items/probe/framework-probe.qdoc b/doc/reference/items/probe/framework-probe.qdoc new file mode 100644 index 00000000..fd519efb --- /dev/null +++ b/doc/reference/items/probe/framework-probe.qdoc @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +** 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-probes.html + \qmltype FrameworkProbe + \inqmlmodule QbsProbes + \ingroup list-of-items + \keyword QML.FrameworkProbe + \inherits PathProbe + + \brief Locates \macos frameworks outside the project. + + Finds \macos frameworks that have the specified file names. + + By default, FrameworkProbe searches for frameworks in \c ~/Library/Frameworks, + \c /usr/local/lib, \c /Library/Frameworks, and in \c /System/Library/Frameworks. + + Also, if \l {qbs::sysroot}{qbs.sysroot} is specified (for example, when compiling using XCode + SDK), the probe searches in the \c sysroot/System/Library/Frameworks folder first. + + For example, a simple FrameworkProbe that searches for the Foundation framework can be used as + follows: + + \code + import qbs.Probes + Product { + Depends { name: "cpp"; } + Probes.FrameworkProbe { + id: foundationProbe + names: ["Foundation"] + } + cpp.frameworkPaths: foundationProbe.found ? [foundationProbe.path] : [] + } + \endcode +*/ diff --git a/doc/reference/items/probe/iar-probe.qdoc b/doc/reference/items/probe/iar-probe.qdoc new file mode 100644 index 00000000..51a09cfb --- /dev/null +++ b/doc/reference/items/probe/iar-probe.qdoc @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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-probes.html + \qmltype IarProbe + \inqmlmodule QbsProbes + \ingroup list-of-items + \keyword QML.IarProbe + \inherits PathProbe + \brief Collects IAR toolchain compiler information. + \since Qbs 1.13 + \internal + + Detects the version, supported architecture and the platform + endianness of the specified compiler executable file from the + \l{https://www.iar.com/}{IAR} toolchain. +*/ + +/*! + \qmlproperty string IarProbe::compilerFilePath + + An input property which is a full path to the IAR compiler executable file. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string IarProbe::architecture + + Detected architecture of the target platform's processor. + + The possible values are \c "arm", \c "mcs51" and \c "avr". + + \nodefaultvalue +*/ + +/*! + \qmlproperty string IarProbe::endianness + + Detected endianness of the target platform's processor architecture. + + The possible values are \c "big" or \c "little". + + \nodefaultvalue +*/ + +/*! + \qmlproperty int IarProbe::versionMajor + + Detected major compiler version. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int IarProbe::versionMinor + + Detected minor compiler version. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int IarProbe::versionPatch + + Detected patch compiler version. + + \nodefaultvalue +*/ diff --git a/doc/reference/items/probe/include-probe.qdoc b/doc/reference/items/probe/include-probe.qdoc new file mode 100644 index 00000000..1a513486 --- /dev/null +++ b/doc/reference/items/probe/include-probe.qdoc @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +** 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-probes.html + \qmltype IncludeProbe + \inqmlmodule QbsProbes + \ingroup list-of-items + \keyword QML.IncludeProbe + \inherits PathProbe + + \brief Locates header files outside the project. + + This is the convenience item that searches for files in "include" directories. + + For example, IncludeProbe can be used to search for a zlib header as follows: + + \code + import qbs + import qbs.Probes + + CppApplication { + Probes.IncludeProbe { + id: zlibProbe + names: "zlib.h" + } + cpp.includePaths: zlibProbe.found ? [zlibProbe.path] : [] + files: 'main.cpp' + } + \endcode +*/ diff --git a/doc/reference/items/probe/keil-probe.qdoc b/doc/reference/items/probe/keil-probe.qdoc new file mode 100644 index 00000000..3b70cddf --- /dev/null +++ b/doc/reference/items/probe/keil-probe.qdoc @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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-probes.html + \qmltype KeilProbe + \inqmlmodule QbsProbes + \ingroup list-of-items + \keyword QML.KeilProbe + \inherits PathProbe + \brief Collects KEIL toolchain compiler information. + \since Qbs 1.13 + \internal + + Detects the version, supported architecture and the platform + endianness of the specified compiler executable file from the + \l{https://www.keil.com/}{KEIL} toolchain. +*/ + +/*! + \qmlproperty string KeilProbe::compilerFilePath + + An input property which is a full path to the KEIL compiler executable file. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string KeilProbe::architecture + + Detected architecture of the target platform's processor. + + The possible values are \c "arm" and \c "mcs51". + + \nodefaultvalue +*/ + +/*! + \qmlproperty string KeilProbe::endianness + + Detected endianness of the target platform's processor architecture. + + The possible values are \c "big" or \c "little". + + \nodefaultvalue +*/ + +/*! + \qmlproperty int KeilProbe::versionMajor + + Detected major compiler version. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int KeilProbe::versionMinor + + Detected minor compiler version. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int KeilProbe::versionPatch + + Detected patch compiler version. + + \nodefaultvalue +*/ diff --git a/doc/reference/items/probe/library-probe.qdoc b/doc/reference/items/probe/library-probe.qdoc new file mode 100644 index 00000000..72b13726 --- /dev/null +++ b/doc/reference/items/probe/library-probe.qdoc @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** 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-probes.html + \qmltype LibraryProbe + \inqmlmodule QbsProbes + \ingroup list-of-items + \keyword QML.IncludeProbe + \inherits PathProbe + + \brief Locates library files outside the project. + + On Windows, searches for library files within directories specified by the \c PATH environment + variable. + + On Unix, searches for library files within directories specified by the \c LIBRARY_PATH + environment variable, as well as in \c "/usr/lib" and \c "/usr/local/lib". + + On Linux, also searches in platform-specific directories, such as \c "/usr/lib64" and + \c "/usr/lib/x86_64-linux-gnu". + + For example, LibraryProbe can be used to search for a \c zlib library as follows: + + \code + import qbs.Probes + + CppApplication { + Probes.LibraryProbe { + id: zlibProbe + names: "z" + } + cpp.libraryPaths: zlibProbe.found ? [zlibProbe.path] : [] + cpp.dynamicLibraries: zlibProbe.found ? [zlibProbe.names] : [] + files: 'main.cpp' + } + \endcode +*/ diff --git a/doc/reference/items/probe/path-probe.qdoc b/doc/reference/items/probe/path-probe.qdoc new file mode 100644 index 00000000..b7749f09 --- /dev/null +++ b/doc/reference/items/probe/path-probe.qdoc @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ivan Komissarov (abbapoh@gmail.com) +** 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-probes.html + \qmltype PathProbe + \inqmlmodule QbsProbes + \ingroup list-of-items + \keyword QML.PathProbe + + \brief Locates files outside the project. + + Finds files that have the specified file name suffix, such as \e .exe, from the specified + locations. + + PathProbe takes as input lists of paths to search files from and file name patterns. The paths + are specified by using the environmentPaths, searchPaths, platformEnvironmentPaths and + platformSearchPaths properties; the path are searched in the same order as listed. The file + name patterns are specified by the \l names and nameSuffixes properties. Returns the first file + that matches the file name patterns. If no such file is found, the + \l {Probe::found}{probe.found} property is set to \c false. + + For example, a simple PathProbe that searches for the stdio.h header can be used as follows: + + \code + Product { + Depends { name: "cpp" } + PathProbe { + id: probe + pathSuffixes: ["include"] + names: ["stdio.h"] + } + cpp.includePaths: probe.found ? [probe.path] : [] + } + \endcode +*/ + +/*! + \qmlproperty varList PathProbe::allResults + + This property contains the list of objects, each object representing a single found file: + \code + { + found: true, + candidatePaths: ["path1/to/file", "path2/to/file", ...] + filePath: "path/to/file" + fileName: "file" + path: "path/to" + } + \endcode + \sa {PathProbe::filePath}{filePath}, {PathProbe::fileName}{fileName}, + {PathProbe::path}{path} +*/ + +/*! + \qmlproperty var PathProbe::candidateFilter + + This property holds the function that can be used to filter out unsuitable candidates. + For example, when searching for a library, it might be necessary to check its architecture: + \code + PathProbe { + pathSuffixes: [".so", ".dll", ".dylib"] + candidateFilter: { + function getLibraryArchitecture(file) { ... } + return function(file) { + return Utilities.isSharedLibrary(file) + && getLibraryArchitecture(file) == qbs.architecture; + } + } + } + \endcode +*/ + +/*! + \qmlproperty stringList PathProbe::names + + The list of file names to search for. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PathProbe::nameSuffixes + + The list of file suffixes to search for. These suffixes are appended to every file name passed + via the \l names property. + + \nodefaultvalue +*/ + +/*! + \qmlproperty script PathProbe::nameFilter + + A script that is executed for each file name before prepending file suffixes. Can be used to + transform file names. +*/ + +/*! + \qmlproperty pathList PathProbe::searchPaths + + The list of paths where to search files. + + \nodefaultvalue +*/ + +/*! + \qmlproperty pathList PathProbe::platformSearchPaths + + The list of platform paths where to search files. + + \defaultvalue \c {['/usr', '/usr/local']} on Unix hosts, empty otherwise +*/ + +/*! + \qmlproperty stringList PathProbe::pathSuffixes + + A list of relative paths that are appended to each path where PathProbe searches for files. + That is, the file should be located within one of the subfolders passed using this + property. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PathProbe::environmentPaths + + The list of environment variables that contains paths where to search files. Paths in the + environment variable should be separated using + \l{qbs::pathListSeparator}{qbs.pathListSeparator}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PathProbe::platformEnvironmentPaths + + The list of platform environment variables that contains paths where to search files. Paths in + the environment variable should be separated using + \l{qbs::pathListSeparator}{qbs.pathListSeparator}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PathProbe::candidatePaths + + This property contains the result list of all paths that were checked before a file was found + (including the file names). + + This property is set even if the Probe didn't find anything and can be used to give the user + a hint what went wrong: + \code + Module { + Probes.BinaryProbe { + id: pythonProbe + names: "python" + } + validate: { + if (!pythonProbe.found) { + throw ModUtils.ModuleError( + "Could not find python binary at any of the following locations:\n\t" + + pythonProbe.candidatePaths.join("\n\t")); + } + } + } + \endcode + + \nodefaultvalue +*/ + +/*! + \qmlproperty string PathProbe::path + + This property contains the full path where the found file is located + (that is, the file directory). + + \nodefaultvalue +*/ + +/*! + \qmlproperty string PathProbe::filePath + + This property contains the full path to the found file, including the file name. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string PathProbe::fileName + + This property contains the found file's name (excluding the path). + + \nodefaultvalue +*/ + +/*! + \qmlproperty varList PathProbe::selectors + + This property should be used to search for multiple files. It contains the list of selectors + each of which describes a single file to search for. A selector can be either a string, a + stringList, or a dictionary. + + The following example searches for three files and illustrates different ways to specify + selectors: + \code + selectors: [ + // 1st file with a single name + "header.h", + // 2nd file with possible name variants + ["config.h", "foo-config.h", "bar-config.h"], + // 3rd file with possible name and suffix variants + {names: ["footk", "footk-version"], nameSuffixes: [".h", ".hpp"]} + ] + \endcode +*/ diff --git a/doc/reference/items/probe/pkgconfig-probe.qdoc b/doc/reference/items/probe/pkgconfig-probe.qdoc new file mode 100644 index 00000000..e2856905 --- /dev/null +++ b/doc/reference/items/probe/pkgconfig-probe.qdoc @@ -0,0 +1,237 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +** 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-probes.html + \qmltype PkgConfigProbe + \inqmlmodule QbsProbes + \ingroup list-of-items + \keyword QML.PkgConfigProbe + + \brief Retrieves the information about installed packages using the pkg-config binary. + + This probe takes the package name or the list of package names as input and returns the + information that is required to compile and link using those packages. + + Usually, you can simply use a Depends item as described in + \l{How do I build against libraries that provide pkg-config files?}. + + Alternatively, the probe can be used directly as follows: + + \code + CppApplication { + name: project.name + Probes.PkgConfigProbe { + id: pkgConfig + name: "QtCore" + minVersion: '4.0.0' + maxVersion: '5.99.99' + } + files: 'main.cpp' + cpp.cxxFlags: pkgConfig.cflags + cpp.linkerFlags: pkgConfig.libs + } + \endcode +*/ + +/*! + \qmlproperty string PkgConfigProbe::sysroot + + This property sets the value of the \c PKG_CONFIG_SYSROOT_DIR environment variable passed to + the \c pkg-config binary. This variable modifies -I and -L flags to use the directories located + in target sysroot. + + This property is useful when cross-compiling packages that use \c pkg-config to determine CFLAGS + and LDFLAGS. For example, if \c sysroot is set to \c /var/target, a \c -I/usr/include/libfoo + will become \c -I/var/target/usr/include/libfoo. + + \defaultvalue \c qbs.sysroot +*/ + +/*! + \qmlproperty string PkgConfigProbe::executable + + The name of or the path to the pkg-config executable. + + \defaultvalue "pkg-config" +*/ + +/*! + \qmlproperty string PkgConfigProbe::name + + The name this probe. This property is used as + \l{PkgConfigProbe::packageNames}{PkgConfigProbe.packageNames} value by default. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PkgConfigProbe::packageNames + + The list of package names to pass to the pkg-config executable. For each package, pkg-config + will return the information required to compile and link to this package. + + \defaultvalue [\l{PkgConfigProbe::name}{PkgConfigProbe.name}] +*/ + +/*! + \qmlproperty string PkgConfigProbe::minVersion + + The minimum version of the required package. If set, pkg-config will ignore packages with + version less than the value of this property. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string PkgConfigProbe::exactVersion + + The exact version of the required package. If set, pkg-config will ignore packages with + version greater than the value of this property. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string PkgConfigProbe::maxVersion + + The maximum version of the required package. If set, pkg-config will ignore packages with + version that is not equal to the value of this property. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool PkgConfigProbe::forStaticBuild + + If \c true, pkg-config will return linker flags for a static linking instead of dynamic. + + \defaultvalue \c false +*/ + +/*! + \qmlproperty stringList PkgConfigProbe::libDirs + + List of full, non-sysrooted paths where pkg-config should search for .pc files. This overrides + the built-in path (which is usually /usr/lib/pkgconfig). + + This property sets the value of the \c PKG_CONFIG_LIBDIR environment variable passed to + the \c pkg-config binary. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PkgConfigProbe::cflags + + \internal + + This property contains the unparsed output from "pkg-config --cflags" call. Usually, you should + use \l{PkgConfigProbe::defines}{PkgConfigProbe.defines}, + \l{PkgConfigProbe::includePaths}{PkgConfigProbe.includePaths} and + \l{PkgConfigProbe::compilerFlags}{PkgConfigProbe.compilerFlags} properties instead. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PkgConfigProbe::libs + + \internal + + This property contains the unparsed output from "pkg-config --libs" call. Usually, you should + use \l{PkgConfigProbe::libraries}{PkgConfigProbe.libraries}, + \l{PkgConfigProbe::libraryPaths}{PkgConfigProbe.libraryPaths} and + \l{PkgConfigProbe::linkerFlags}{PkgConfigProbe.linkerFlags} properties instead. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PkgConfigProbe::defines + + This output property contains the list of defines that should be passed to a compiler when + using requested package. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PkgConfigProbe::libraries + + This output property contains the list of library names that should be passed to a linker when + using requested package. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PkgConfigProbe::libraryPaths + + This output property contains the list of library paths that should be passed to a linker when + using requested package. + + This property sets the value of the PKG_CONFIG_LIBDIR environment variable passed to + the \c pkg-config binary. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PkgConfigProbe::includePaths + + This output property contains the list of include paths that should be passed to a compiler when + using requested package. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PkgConfigProbe::compilerFlags + + This output property contains the list of flags that should be passed to a compiler when + using requested package. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PkgConfigProbe::linkerFlags + + This output property contains the list of flags that should be passed to a linker when + using requested package. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList PkgConfigProbe::modversion + + This output property contains the version of the found package. + + \nodefaultvalue +*/ diff --git a/doc/reference/items/probe/sdcc-probe.qdoc b/doc/reference/items/probe/sdcc-probe.qdoc new file mode 100644 index 00000000..6165c0aa --- /dev/null +++ b/doc/reference/items/probe/sdcc-probe.qdoc @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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-probes.html + \qmltype SdccProbe + \inqmlmodule QbsProbes + \ingroup list-of-items + \keyword QML.SdccProbe + \inherits PathProbe + \brief Collects SDCC toolchain compiler information. + \since Qbs 1.14 + \internal + + Detects the version, supported architecture and the platform + endianness of the specified compiler executable file from the + \l{http://sdcc.sourceforge.net/}{SDCC} toolchain. +*/ + +/*! + \qmlproperty string SdccProbe::compilerFilePath + + An input property which is a full path to the SDCC compiler executable file. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string SdccProbe::architecture + + Detected architecture of the target platform's processor. + + The possible values are \c "mcs51". + + \nodefaultvalue +*/ + +/*! + \qmlproperty string SdccProbe::endianness + + Detected endianness of the target platform's processor architecture. + + The possible values are \c "little". + + \nodefaultvalue +*/ + +/*! + \qmlproperty int SdccProbe::versionMajor + + Detected major compiler version. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int SdccProbe::versionMinor + + Detected minor compiler version. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int SdccProbe::versionPatch + + Detected patch compiler version. + + \nodefaultvalue +*/ diff --git a/doc/reference/jsextensions/jsextension-binaryfile.qdoc b/doc/reference/jsextensions/jsextension-binaryfile.qdoc new file mode 100644 index 00000000..59def1af --- /dev/null +++ b/doc/reference/jsextensions/jsextension-binaryfile.qdoc @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Sergey Belyashov +** Copyright (C) 2017 Denis Shienkov +** 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 jsextension-binaryfile.html + \ingroup list-of-builtin-services + + \title BinaryFile Service + \brief Provides read and write operations on binary files. + + The \c BinaryFile service allows you to read from and write into binary files. + + \section1 Related Declarations + + \section2 BinaryFile.OpenMode + \code + enum BinaryFile.OpenMode { ReadOnly, WriteOnly, ReadWrite } + \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 + \code + BinaryFile(filePath: string, openMode: OpenMode = BinaryFile.ReadOnly) + \endcode + Opens the file at \c filePath in the given mode and returns the object representing the file. + \note The mode influences which of the operations listed below can actually be used on the file. + + \section2 atEof + \code + atEof(): boolean + \endcode + Returns \c{true} if no more data can be read from the file, \c{false} otherwise. + + \section2 close + \code + close(): void + \endcode + 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 size + \code + size(): number + \endcode + Returns the size of the file (in bytes). + + \section2 resize + \code + resize(size: number): void + \endcode + Sets the file \c size (in bytes). If \c size is larger than the file currently is, the new + bytes will be set to 0; if \c size is smaller, the file is truncated. + + \section2 pos + \code + pos(): number + \endcode + Returns the position that data is written to or read from. + + \section2 seek + \code + seek(pos: number): void + \endcode + Sets the current position to \c pos. + + \section2 read + \code + read(size: number): number[] + \endcode + Reads at most \c size bytes of data from the file and returns it as an array. + + \section2 write + \code + write(data: number[]): void + \endcode + Writes \c data into the file at the current position. +*/ diff --git a/doc/reference/jsextensions/jsextension-environment.qdoc b/doc/reference/jsextensions/jsextension-environment.qdoc new file mode 100644 index 00000000..8579ecee --- /dev/null +++ b/doc/reference/jsextensions/jsextension-environment.qdoc @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 jsextension-environment.html + \ingroup list-of-builtin-services + + \title Environment Service + \brief Provides operations on the system environment. + + The \c Environment service offers access to the system environment or process environment. + + \section1 Available Operations + + \section2 currentEnv + \code + Environment.currentEnv(): { [key: string]: string; } + \endcode + Returns the environment of \QBS in the current context as an object whose properties are + the environment variables. + + \section2 getEnv + \code + Environment.getEnv(key: string): string + \endcode + Tries to find a variable with the given name in the current context's environment and returns + its value. If no such variable could be found, \c undefined is returned. + + \section2 putEnv + \code + Environment.putEnv(key: string, value: string): void + \endcode + Sets the value of the environment variable with the given name in the build or run environment. + This method is only available in the \c Module.setupBuildEnvironment and + \c Module.setupRunEnvironment scripts. + + \section2 unsetEnv + \code + Environment.unsetEnv(key: string): void + \endcode + Unsets the environment variable with the given name from the build or run environment. + This method is only available in the \c Module.setupBuildEnvironment and + \c Module.setupRunEnvironment scripts. +*/ diff --git a/doc/reference/jsextensions/jsextension-file.qdoc b/doc/reference/jsextensions/jsextension-file.qdoc new file mode 100644 index 00000000..c12fcc4e --- /dev/null +++ b/doc/reference/jsextensions/jsextension-file.qdoc @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 jsextension-file.html + \ingroup list-of-builtin-services + + \title File Service + \brief Provides operations on the file system. + + The \c File service offers limited access to the file system for operations such as copying + or removing files. + + \section1 Available Operations + + \section2 copy + \code + File.copy(sourceFilePath: string, targetFilePath: string): boolean + \endcode + Copies \c sourceFilePath to \c targetFilePath. Any directory components in \c targetFilePath + that do not yet exist will be created. If \c sourceFilePath is a directory, a recursive + copy will be made. If an error occurs, a JavaScript exception will be thrown. + \note \c targetFilePath must be the counterpart of \c sourceFilePath at the new location, + \b{not} the new parent directory. This allows the copy to have a different name and is true + even if \c sourceFilePath is a directory. + + \section2 exists + \code + File.exists(filePath: string): boolean + \endcode + Returns true if and only if there is a file at \c filePath. + + \section2 directoryEntries + \code + File.directoryEntries(path: string, filter: File.Filter): string[] + \endcode + Returns a sorted list of the directory \c{path}'s contents non-recursively, + filtered by \c filter. The values of \c filter are equivalent to Qt's \c QDir::Filter. + + \section2 lastModified + \code + File.lastModified(filePath: string): number + \endcode + Returns the time of last modification for the file at \c filePath. The concrete semantics of the + returned value are platform-specific. You should only rely on the property that a smaller value + indicates an older timestamp. + + \section2 makePath + \code + File.makePath(path: string): boolean + \endcode + Makes the directory at \c path, creating intermediate directories if necessary. + Conceptually equivalent to \c{mkdir -p} + + \section2 move + \code + File.move(oldPath: string, newPath: string, overwrite: boolean = true): boolean + \endcode + Renames the file \c oldPath to \c newPath. + Returns \c true if successful; otherwise returns \c false. + If a file with the name \c newPath already exists, and \c overwrite is \c false, + \c move() returns \c false (that is, the file will not be overwritten). + + \section2 remove + \code + File.remove(filePath: string): boolean + \endcode + Removes the file at \c filePath. In case of a directory, it will be removed recursively. +*/ diff --git a/doc/reference/jsextensions/jsextension-fileinfo.qdoc b/doc/reference/jsextensions/jsextension-fileinfo.qdoc new file mode 100644 index 00000000..967606ea --- /dev/null +++ b/doc/reference/jsextensions/jsextension-fileinfo.qdoc @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 jsextension-fileinfo.html + \ingroup list-of-builtin-services + + \title FileInfo Service + \brief Provides operations on file paths. + + The \c FileInfo service offers various operations on file paths, such as turning absolute + paths into relative ones, splitting a path into its components, and so on. + + \section1 Available Operations + + \section2 baseName + \code + FileInfo.baseName(filePath: string): string + \endcode + Returns the file name of \c filePath up to (but not including) the first '.' character. + + \section2 canonicalPath + \code + FileInfo.canonicalPath(filePath: string): string + \endcode + Returns a canonicalized \c filePath, i.e. an absolute path without symbolic + links or redundant "." or ".." elements. + On Windows, drive substitutions are also resolved. + + It is recommended to use \c{canonicalPath} in only those cases where + canonical paths are really necessary. In most cases, \c{cleanPath} should + be enough. + + \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 + \endcode + Returns the file name of \c filePath up to (but not including) the last '.' character. + + \section2 completeSuffix + \code + FileInfo.completeSuffix(filePath: string): string + \endcode + Returns the file suffix of \c filePath from (but not including) the last '.' character. + \funsince 1.12 + + \section2 fileName + \code + FileInfo.fileName(filePath: string): string + \endcode + Returns the last component of \c filePath, that is, everything after the + last '/' character. + + \section2 fromNativeSeparators + \code + FileInfo.fromNativeSeparators(filePath: string): string + \endcode + On Windows hosts, this function behaves the same as \l fromWindowsSeparators. On other + operating systems, it returns the input unmodified. + + \section2 fromWindowsSeparators + \code + FileInfo.fromWindowsSeparators(filePath: string): string + \endcode + Returns \c filePath with all '\\' characters replaced by '/'. + + \section2 isAbsolutePath + \code + FileInfo.isAbsolutePath(filePath: string, hostOS?: string[]): boolean + \endcode + Returns true if \c filePath is an absolute path and false if it is a relative one. + If \c hostOS is specified, treats \c filePath as a file path of the kind found on that platform. + This parameter defaults to the host OS on which \QBS is running and should normally be omitted. + + \section2 joinPaths + \code + FileInfo.joinPaths(...paths: string[]): string + \endcode + Concatenates the given paths using the '/' character. + + \section2 path + \code + FileInfo.path(filePath: string, hostOS?: string[]): string + \endcode + Returns the part of \c filePath that is not the file name, that is, + everything up to + (but not including) the last '/' character. If \c filePath is just a file name, then '.' + is returned. If \c filePath ends with a '/' character, then the file name is assumed to be empty + for the purpose of the above definition. + If \c hostOS is specified, treats \c filePath as a file path of the kind found on that platform. + This parameter defaults to the host OS on which \QBS is running and should normally be omitted. + + \section2 relativePath + \code + FileInfo.relativePath(dirPath: string, filePath: string): string + \endcode + Returns a relative path so that joining \c dirPath and the returned path results in \c filePath. + If necessary, '..' components are inserted. + The function assumes \c dirPath and \c filePath to be absolute paths and \c dirPath to + be a directory. + + \section2 suffix + \code + FileInfo.suffix(filePath: string): string + \endcode + Returns the file suffix of \c filePath from (but not including) the first '.' character. + \funsince 1.12 + + \section2 toNativeSeparators + \code + FileInfo.toNativeSeparators(filePath: string): string + \endcode + On Windows hosts, this function behaves the same as \l toWindowsSeparators. On other + operating systems, it returns the input unmodified. + + \section2 toWindowsSeparators + \code + FileInfo.toWindowsSeparators(filePath: string): string + \endcode + Returns \c filePath with all '/' characters replaced by '\\'. +*/ diff --git a/doc/reference/jsextensions/jsextension-process.qdoc b/doc/reference/jsextensions/jsextension-process.qdoc new file mode 100644 index 00000000..dd942ecf --- /dev/null +++ b/doc/reference/jsextensions/jsextension-process.qdoc @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 jsextension-process.html + \ingroup list-of-builtin-services + + \title Process Service + \brief Allows you to start external processes. + + The \c Process service allows you to start processes, track their output, + and so on. + + \section1 Available Operations + + \section2 Constructor + \code + Process() + \endcode + Allocates and returns a new Process object. + + \section2 close + \code + close(): void + \endcode + Frees the resources associated with the process. It is recommended to always call this function as + soon as you are finished with the process. + + \section2 closeWriteChannel + \code + closeWriteChannel(): void + \endcode + Schedules the stdin channel of process to be closed. The channel will close once all data has been + written to the process. After calling this function, any attempts to write to the process will do + nothing. See \c QProcess::closeWriteChannel() for more details. + + \section2 exec + \code + exec(filePath: string, arguments: string[], throwOnError: boolean): number + \endcode + Executes the program at \c filePath with the given argument list and blocks until the + process is finished. If an error occurs (for example, there is no executable + file at \c filePath) + and \c throwOnError is true, then a JavaScript exception will be thrown. Otherwise + (the default), -1 will be returned in case of an error. The normal return code is the exit code + of the process. + + \section2 exitCode + \code + exitCode(): number + \endcode + Returns the exit code of the process. This is needed for retrieving the exit code from + processes started via \c start(), rather than \c exec(). + + \section2 getEnv + \code + getEnv(varName: string): string + \endcode + Returns the value of the variable \c varName in the process' environment. + + \section2 kill + \code + kill(): void + \endcode + Kills the process, causing it to exit immediately. + + \section2 readLine + \code + readLine(): string + \endcode + Reads and returns one line of text from the process output, without the newline character(s). + + \section2 atEnd + \code + atEnd(): boolean + \endcode + Returns true if there is no more data to be read from the process output, otherwise + returns false. + + \section2 readStdErr + \code + readStdErr(): string + \endcode + Reads and returns all data from the process' standard error channel. + + \section2 readStdOut + \code + readStdOut(): string + \endcode + Reads and returns all data from the process' standard output channel. + + \section2 setCodec + \code + setCodec(codec) + \endcode + Sets the text codec to \c codec. The codec is used for reading and writing from and to + the process, respectively. The supported codecs are the same as for \c QTextCodec, for example: + "UTF-8", "UTF-16", and "ISO 8859-1". + + \section2 setEnv + \code + setEnv(varName: string, varValue: string): string + \endcode + Sets the value of variable \c varName to \c varValue in the process environment. + This only has an effect if called before the process is started. + + \section2 setWorkingDirectory + \code + setWorkingDirectory(path: string): void + \endcode + Sets the directory the process will be started in. + This only has an effect if called before the process is started. + + \section2 start + \code + start(filePath: string, arguments: string[]): boolean + \endcode + Starts the program at \c filePath with the given list of arguments. Returns \c{true} if the + process could be started and \c{false} otherwise. + \note This call returns right after starting the process and should be used only if you need + to interact with the process while it is running. Most of the time, you want to use \c exec() + instead. + + \section2 terminate + \code + terminate(): void + \endcode + Tries to terminate the process. This is not guaranteed to make the process exit immediately; + if you need that, use \c kill(). + + \section2 waitForFinished + \code + waitForFinished(timeout: number): boolean + \endcode + Blocks until the process has finished or \c timeout milliseconds have passed (default is 30000). + Returns true if the process has finished and false if the operation has timed out. + Calling this function only makes sense for processes started via \c start() (as opposed to + \c exec()). + + \section2 workingDirectory + \code + workingDirectory(): string + \endcode + Returns the directory the process will be started in. + + \section2 write + \code + write(data: string): void + \endcode + Writes \c data into the process' input channel. + + \section2 writeLine + \code + writeLine(data: string): void + \endcode + Writes \c data, followed by the newline character(s), into the process' input channel. +*/ diff --git a/doc/reference/jsextensions/jsextension-propertylist.qdoc b/doc/reference/jsextensions/jsextension-propertylist.qdoc new file mode 100644 index 00000000..869873ae --- /dev/null +++ b/doc/reference/jsextensions/jsextension-propertylist.qdoc @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 jsextension-propertylist.html + \ingroup list-of-builtin-services + + \title PropertyList Service + \brief Provides read, write and convert operations on property list files. + + The \c PropertyList service allows you to read and write property list files in + all formats supported by the Core Foundation API: XML, binary, JSON, and OpenStep (read-only). + + This service is only available on Darwin platforms such as iOS, macOS, tvOS, and watchOS. + + \section1 Available operations + + \section2 Constructor + \code + PropertyList() + \endcode + Allocates and returns a new PropertyList object. + + \section2 clear + \code + clear(): void + \endcode + Voids the property list by deleting its internal object reference. + + \section2 isEmpty + \code + isEmpty(): boolean + \endcode + Returns true if the property list has no internal object reference set, otherwise false. + + \section2 format + \code + format(): string + \endcode + Returns the data format that the property list data was deserialized from. This property is set + after calling \c readFromString or \c readFromFile. + Possible return values include: \c "binary1", \c "json", \c "openstep", and \c "xml1". + If the property list object is empty or the input format could not be determined, + returns \c undefined. + + \section2 readFromFile + \code + readFromFile(filePath: string): void + \endcode + Parses the file and stores the result in the property list. + Throws an exception if an I/O error occurs or the input is in an invalid format. + + \section2 readFromObject + \code + readFromObject(obj: any): void + \endcode + Sets the given object as the property list's internal object. + \c format() will return \c undefined as this method does not deserialize a storage format. + + \section2 readFromString + \code + readFromString(input: string): void + \endcode + Parses \c input and stores the result in the property list. + This is most useful for initializing a property list object from the result of a + \c JSON.stringify call. + Throws an exception if the input is in an invalid format. + + \section2 toObject + \code + toObject(): any + \endcode + Returns an object representing the property list. + + \section2 toJSON + \code + toJSON(style: string = "compact"): string + \endcode + Returns a string representation of the property list in JSON format. + Possible values for \c style include \c "pretty" and \c "compact". The default is compact. + + \section2 toString + \code + toString(format: string): string + \endcode + Returns a string representation of the property list in the specified format. + Possible values for \c format include: \c "json" (compact), \c "json-compact", \c "json-pretty", + and \c "xml1". Currently, the OpenStep format is not supported. + Throws an exception if the object cannot be written in the given format. + + \section2 toXMLString + \code + toXMLString(): string + \endcode + Returns a string representation of the property list in XML format. + This function is a synonym for \c toString("xml1"). + + \section2 writeToFile + \code + writeToFile(filePath: string, format: string): void + \endcode + Writes the property list to the file in the given format. + Possible values for \c format include: \c "binary1", \c "json" (compact), \c "json-compact", + \c "json-pretty", and \c "xml1". Currently, the OpenStep format is not supported for writing. + Throws an exception if an I/O error occurs or the object cannot be written in the given format. +*/ diff --git a/doc/reference/jsextensions/jsextension-temporarydir.qdoc b/doc/reference/jsextensions/jsextension-temporarydir.qdoc new file mode 100644 index 00000000..75065314 --- /dev/null +++ b/doc/reference/jsextensions/jsextension-temporarydir.qdoc @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jake Petroules. +** 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 jsextension-temporarydir.html + \ingroup list-of-builtin-services + + \title TemporaryDir Service + \brief Creates a unique directory for temporary use. + + The \c TemporaryDir service creates a unique directory for temporary use. + + \section1 Available Operations + + \section2 Constructor + \code + TemporaryDir() + \endcode + Allocates and returns a new TemporaryDir object. + This method creates the temporary directory. + + \section2 isValid + \code + isValid(): boolean + \endcode + Returns \c true if the temporary directory was created successfully. + + \section2 path + \code + path(): string + \endcode + Returns the path to the temporary directory. + Empty if the temporary directory could not be created. + + \section2 remove + \code + remove(): boolean + \endcode + Removes the temporary directory, including all its contents. + Returns \c true if removing was successful. + It is recommended to always call this function as soon as you are finished with the temporary + directory. The directory will not be removed automatically. +*/ diff --git a/doc/reference/jsextensions/jsextension-textfile.qdoc b/doc/reference/jsextensions/jsextension-textfile.qdoc new file mode 100644 index 00000000..65758b3b --- /dev/null +++ b/doc/reference/jsextensions/jsextension-textfile.qdoc @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 jsextension-textfile.html + \ingroup list-of-builtin-services + + \title TextFile Service + \brief Provides read and write operations on text files. + + The \c TextFile service allows you to read from and write into text files. + + \section1 Related Declarations + + \section2 TextFile.OpenMode + \code + 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 + \code + TextFile(filePath: string, openMode: OpenMode = TextFile.ReadOnly) + \endcode + Opens the file at \c filePath in the given mode and returns the object representing the file. + \note The mode influences which of the operations listed below can actually be used on the file. + + \section2 atEof + \code + atEof(): boolean + \endcode + Returns \c{true} if no more data can be read from the file, \c{false} otherwise. + + \section2 close + \code + close(): void + \endcode + 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 + \endcode + Reads all data from the file and returns it. + + \section2 readLine + \code + readLine(): string + \endcode + Reads one line of text from the file and returns it. The returned string does not contain + the newline characters. + + \section2 setCodec + \code + setCodec(codec: string): void + \endcode + Sets the text codec to \c codec. The supported codecs are the same as for \c QTextCodec, + for example: "UTF-8", "UTF-16", and "ISO 8859-1". + + \section2 truncate + \code + truncate(): void + \endcode + Truncates the file, that is, gives it the size of zero, removing all content. + + \section2 write + \code + write(data: string): void + \endcode + Writes \c data into the file at the current position. + + \section2 writeLine + \code + writeLine(data: string): void + \endcode + Writes \c data into the file at the current position and appends the newline character(s). +*/ diff --git a/doc/reference/jsextensions/jsextension-utilities.qdoc b/doc/reference/jsextensions/jsextension-utilities.qdoc new file mode 100644 index 00000000..79358199 --- /dev/null +++ b/doc/reference/jsextensions/jsextension-utilities.qdoc @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 jsextension-utilities.html + \ingroup list-of-builtin-services + + \title Utilities Service + \brief Provides miscellaneous operations. + + The \c Utilities service offers miscellaneous operations. + + \section1 Available Operations + + \section2 cStringQuote + + \badcode + Utilities.cStringQuote(str: string): string + \endcode + + Takes a string and escapes special characters in a way that the result is + suitable for use as a C/C++ string literal. This function is typically used + to specify values for \l{cpp::defines}{cpp.defines}. + + \section2 getHash + + \badcode + Utilities.getHash(key: string): string + \endcode + + Calculates a 16-byte hash of the input and returns it. + Rules in modules should use this function to find unique locations for output + artifacts in the build directory without duplicating the whole directory structure of + the respective input file (to deal with the case of two files with the same name in different + subdirectories of the same product). + + \section2 rfc1034Identifier + + \badcode + Utilities.rfc1034Identifier(str: string): string + \endcode + + Returns an RFC-1034 compliant identifier based on the given string by replacing each character + that is not Latin alphanumeric or \c{.} with \c{-}. + + \section2 versionCompare + + \badcode + Utilities.versionCompare(version1: string, version2: string): number + \endcode + + Interprets the two arguments as version numbers and returns a number that + is smaller than, equal to, or greater than zero if \c version1 is smaller + than, equal to, or greater than \c version2, respectively. + + The version strings consist of up to three numbers separated by dots. +*/ diff --git a/doc/reference/jsextensions/jsextension-xml.qdoc b/doc/reference/jsextensions/jsextension-xml.qdoc new file mode 100644 index 00000000..b5d3891a --- /dev/null +++ b/doc/reference/jsextensions/jsextension-xml.qdoc @@ -0,0 +1,342 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 jsextension-xml.html + \ingroup list-of-builtin-services + + \title Xml Service + \brief Provides a DOM parser and generator to JavaScript. + + The \c Xml service enables you to access and manipulate XML Document + Object Model (DOM) documents. The entire document is a \e {document node}, + each XML element is an \e {element node}, the text paragraphs in the XML + elements are \e {text nodes}, and each attribute is an \e {attribute node}. + + XML DOM presents documents as tree structures. The contents of the nodes + can be accessed in the tree. They can be modified or deleted, and new + nodes can be created. + + The nodes in the node tree have a hierarchical relationship to each other. + The top node is called the \e root. Each node, except the root, has exactly + one \e parent node, while it can have any number of \e children. Nodes with + the same parent are called \e siblings. + + \section1 XML DOM Document Node Operations + + A document node represents an entire document. That is, the root of the DOM + tree. + + \section2 Constructor + + \badcode + Xml.DomDocument() + \endcode + + Creates an XML DOM root node that can contain one element. + + \section2 createCDATASection + + \badcode + Xml.DomDocument.createCDATASection(value: string) + \endcode + + Creates a CDATA section that is not parsed by a parser. It can be used to + include XML fragments without having to escape the delimiters, for example. + Tags inside the section are not treated as markup nor are entities expanded. + + \section2 createElement + + \badcode + Xml.DomDocument.createElement(tagName: string) + \endcode + + Creates an element that can contain other elements, CDATA sections, and text + nodes. + + \section2 createTextNode + + \badcode + Xml.DomDocument.createTextNode(value: string) + \endcode + + Creates a text node that represents textual content in an element or + attribute. + + \section2 documentElement + + \badcode + Xml.DomDocument.documentElement() + \endcode + + Returns the document element. + + \section2 load + + \badcode + Xml.DomDocument.load(filePath: string): void + \endcode + + Loads the document specified by \c filePath. + + \section2 save + + \badcode + Xml.DomDocument.save(filePath: string, indentation: int): void + \endcode + + Saves the document at the location specified by \c filePath with the + indentation specified by \c int. + + \section2 setContent + + \badcode + Xml.DomDocument.setContent(content: string) + \endcode + + Returns the content of the document. + + \section2 toString + + \badcode + Xml.DomDocument.toString(indentation: int) + \endcode + + Converts the document to a string with the indentation specified by \c int. + + \section1 XML DOM Node Operations + + A node represents a single node in the document tree. There are several + different types of nodes, such as element, attribute, and text nodes. + + All objects inherit the node properties for handling parents and children, + even if they cannot have parents or children. For example, attempting to add + children to text nodes results in a DOM error. + + \section2 Constructor + + \badcode + Xml.DomNode() + \endcode + + Creates an XML DOM node. + + \section2 appendChild + + \badcode + Xml.DomNode.appendChild(tagName: string) + \endcode + + Appends a new child node to the end of the list of children of a node. + + \section2 attribute + + \badcode + Xml.DomNode.attribute(name: string, defaultValue: string) + \endcode + + Returns the name and default value of the attribute. + + \section2 clear + + \badcode + Xml.DomNode.clear() + \endcode + + Clears the contents of the node. + + \section2 data + + \badcode + Xml.DomNode.data() + \endcode + + Returns the contents of the text node, CDATA section, or character data + node. + + \section2 firstChild + + \badcode + Xml.DomNode.firstChild(tagName: string) + \endcode + + Returns the first child of a node. + + \section2 hasAttribute + + \badcode + Xml.DomNode.hasAttribute(name: string) boolean + \endcode + + Returns \c true if the node has the specified attribute. + + \section2 hasAttributes + + \badcode + Xml.DomNode.hasAttributes() boolean + \endcode + + Returns \c true if the node has attributes. + + \section2 hasChildNodes + + \badcode + Xml.DomNode.hasChildNodes() boolean + \endcode + + Returns \c true if the node has children. + + \section2 insertAfter + + \badcode + Xml.DomNode.insertAfter(newChild: tagName, refChild: tagName) + \endcode + + Inserts a new child node after the child node specified by \c refChild. + + \section2 insertBefore + + \badcode + Xml.DomNode.insertBefore(newChild: tagName, refChild: tagName) + \endcode + + Inserts a new child node before the child node specified by \c refChild. + + \section2 isCDATASection + + \badcode + Xml.DomNode.isCDATASection() boolean + \endcode + + Returns \c true if this is a CDATA section. + + \section2 isElement + + \badcode + Xml.DomNode.isElement() boolean + \endcode + + Returns \c true if this is an element. + + \section2 isText + + \badcode + Xml.DomNode.isText() boolean + \endcode + + Returns \c true if this is a text node. + + \section2 lastChild + + \badcode + Xml.DomNode.lastChild(tagName: string) + \endcode + + Returns the last child of a node. + + \section2 nextSibling + + \badcode + Xml.DomNode.nextSibling(tagName: string) + \endcode + + Returns the node immediately following a node. + + \section2 parentNode + + \badcode + Xml.DomNode.parentNode() + \endcode + + Returns the parent of the node. + + \section2 previousSibling + + \badcode + Xml.DomNode.previousSibling(tagName: string) + \endcode + + Returns the node before a node. + + \section2 removeChild + + \badcode + Xml.DomNode.removeChild(tagName: string) + \endcode + + Removes the child node. + + \section2 replaceChild + + \badcode + Xml.DomNode.replaceChild(newChild: tagName, oldChild: tagName) + \endcode + + Replaces a child node with another one. + + \section2 setAttribute + + \badcode + Xml.DomNode.setAttribute(name: string, value: string) + \endcode + + Sets the name and value of an attribute. + + \section2 setData + + \badcode + Xml.DomNode.setData(value: string): void + \endcode + + Sets the data of the node to a text node, CDATA section, or character data + node. + + \section2 setTagName + + \badcode + Xml.DomNode.setTagName(tagName: string) + \endcode + + Sets the tag name of the node. + + \section2 tagName + + \badcode + Xml.DomNode.tagName() + \endcode + + Returns the tag name of the node. + + \section2 text + + \badcode + Xml.DomNode.text() + \endcode + + Returns the text of the node. +*/ diff --git a/doc/reference/jsextensions/jsextensions-general.qdoc b/doc/reference/jsextensions/jsextensions-general.qdoc new file mode 100644 index 00000000..383f7b2b --- /dev/null +++ b/doc/reference/jsextensions/jsextensions-general.qdoc @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Petroules Corporation. +** 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 jsextensions-general.html + \ingroup list-of-builtin-services + + \title General Services + \brief Provides various operations. + + These are operations that do not fit into any of the other categories. + They are automatically available in any \QBS project file or JavaScript file. + + \section1 Available Operations + + \section2 require + \code + require(identifier: string): any + \endcode + Loads an extension and returns an object representing the extension. + If \a identifier is a relative or absolute file path, this function will load a JavaScript file + and return an object containing the evaluated context of that file. Otherwise, loads a \QBS + extension named \a identifier and returns an object that contains the extension's context. + This function is only available in JavaScript files and is designed to behave similarly to the + CommonJS/RequireJS/Node.js module resolution systems. + \code + var MyFunctions = require("./myfunctions.js"); + MyFunctions.doSomething(); + var FileInfo = require("qbs.FileInfo"); + var fileName = FileInfo.fileName(filePath); + \endcode + + + \section1 Extensions to JavaScript Built-in Objects + + \section2 Array.contains + \code + Array.contains(e: any): boolean + \endcode + Returns \c{true} if the array contains the element \c{e}. Returns \c{false} otherwise. + + \section2 Array.containsAll + \code + Array.containsAll(other: any[]): boolean + \endcode + Returns \c{true} if the array contains every element in the \c{other} array. + Returns \c{false} otherwise. + + \section2 Array.containsAny + \code + Array.containsAny(other: any[]): boolean + \endcode + Returns \c{true} if the array contains some element(s) in the \c{other} array. + Returns \c{false} otherwise. + + \section2 Array.uniqueConcat + \code + Array.uniqueConcat(other: any[]): any[] + \endcode + Returns a copy of this array joined with the array \c{other}. + Duplicates that would originate from the concatenation are removed. + The order of elements is preserved. + + \section2 String.contains + \code + String.contains(s: string): boolean + \endcode + Returns \c{true} if the string contains the substring \c{s}. Returns \c{false} otherwise. + + \section2 startsWith + \code + String.startsWith(s: string): boolean + \endcode + Returns \c{true} if the string starts with the substring \c{s}. Returns \c{false} otherwise. + + \section2 endsWith + \code + String.endsWith(s: string): boolean + \endcode + Returns \c{true} if the string ends with the substring \c{s}. Returns \c{false} otherwise. + + + \section1 Console API + + \QBS provides a subset of the non-standard Console API available in most ECMAScript runtimes. + + The output of each of these functions will only be displayed if the logging level is at least + the level which the function outputs at. Logging levels from lowest to highest are: + 'error', 'warning', 'info', 'debug', and 'trace'. The default is 'info'. + + \warning The contents of this section are subject to change in order to align with future + \l{https://www.w3.org/2011/08/browser-testing-charter.html}{standardization} + \l{https://github.com/DeveloperToolsWG/console-object/blob/master/api.md}{processes}. + + \section2 console.debug + \code + console.debug(s: string): void + \endcode + This method is an alias for \c{console.log()}. + + \section2 console.error + \code + console.error(s: string): void + \endcode + Logs an \c{error} level message. + Outputs to stderr when the logger output is a terminal. + The string will be prefixed with \c{"ERROR: "} and colored red when the logger output is a + color-capable terminal. + + \section2 console.info + \code + console.info(s: string): void + \endcode + Logs an \c{info} level message. + Outputs to stdout when the logger output is a terminal. + + \section2 console.log + \code + console.log(s: string): void + \endcode + Logs a \c{debug} level message. + Outputs to stderr when the logger output is a terminal. + + \section2 console.warn + \code + console.warn(s: string): void + \endcode + Logs a \c{warning} level message. + Outputs to stderr when the logger output is a terminal. + The string will be prefixed with \c{"WARNING: "} and colored yellow when the logger output is a + color-capable terminal. +*/ diff --git a/doc/reference/modules/android-ndk-module.qdoc b/doc/reference/modules/android-ndk-module.qdoc new file mode 100644 index 00000000..e3a7ca22 --- /dev/null +++ b/doc/reference/modules/android-ndk-module.qdoc @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** 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 + \qmltype Android.ndk + \inqmlmodule QbsModules + \since Qbs 1.4 + + \brief Provides support for building native Android libraries. + + The \c Android.ndk module contains the properties and rules to create native libraries + for use in Android applications. + + Normally, you will not use this module directly, but instead work + with the \l{DynamicLibrary}, \l{StaticLibrary} and \l Application items + that \QBS provides. + + Here is what the project file for the \c hello-jni example that comes with + the NDK could look like: + + \code + CppApplication { + name: "HelloJni" + Android.sdk.packageName: "com.example.hellojni" + qbs.architectures: ["arm", "x86"] + files: "app/src/main/jni/hello-jni.c" + } + \endcode + + \section2 Relevant File Tags + \target filetags-android-ndk + + \table + \header + \li Tag + \li Since + \li Description + \row + \li \c{"android.nativelibrary"} + \li 1.4.0 + \li Attached to dynamic libraries that will end up in APK packages. + You do not normally need to use the tag explicitly, as it is the + default type of the \l{DynamicLibrary} item for Android targets. + \endtable +*/ + +/*! + \qmlproperty string Android.ndk::abi + + The ABI name as it appears under \c "lib/" in the application package. + Corresponds to \c APP_ABI in \c Android.mk. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string Android.ndk::appStl + + The library to use for C++. The possible values are: + + \list + \li \c "c++_shared" + \li \c "c++_static" + \endlist + + \defaultvalue \c{"c++_shared"} +*/ + +/*! + \qmlproperty path Android.ndk::ndkDir + + The NDK base directory. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string Android.ndk::platform + + The versioned platform name. + + \defaultvalue \c{"android-16"} for 32 bit arm ABIs + and \c{"android-21"} for all 64 bit ABIs and x86. + x86 ABI has broken wstring support in android-16 to android-19. +*/ diff --git a/doc/reference/modules/android-sdk-module.qdoc b/doc/reference/modules/android-sdk-module.qdoc new file mode 100644 index 00000000..676eaa82 --- /dev/null +++ b/doc/reference/modules/android-sdk-module.qdoc @@ -0,0 +1,252 @@ +/**************************************************************************** +** +** 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 + \qmltype Android.sdk + \inqmlmodule QbsModules + \since Qbs 1.4 + + \brief Provides support for building Android packages. + + The Android.sdk module contains the properties and rules to create Android + application packages from Java sources, resources, and so on. + It is usually pulled in indirectly by declaring an \l Application product. + + \section2 Relevant File Tags + \target filetags-android-sdk + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"android.aidl"} + \li \c{*.aidl} + \li 1.4.0 + \li Attached to Android AIDL files. One Java source file will be + generated for each such file. + \row + \li \c{"android.assets"} + \li - + \li 1.4.0 + \li Attached to Android assets, which are typically located in an + \c{assets/} subdirectory. These files are tagged automatically + if the \l automaticSources property is enabled. + \row + \li \c{"android.manifest"} + \li \c{AndroidManifest.xml} + \li 1.4.0 + \li Attached to the Android manifest. There must be one such file for + every Android app. + \row + \li \c{"android.resources"} + \li - + \li 1.4.0 + \li Attached to Android resources, which are typically located in a + \c{res/} subdirectory. These files are tagged automatically + if the \l automaticSources property is enabled. + \endtable +*/ + +/*! + \qmlproperty string Android.sdk::buildToolsVersion + + The version of the build tools such as \c aapt and \c dx. + + \defaultvalue Highest build tools version version available in the SDK. +*/ + +/*! + \qmlproperty string Android.sdk::ndkDir + + The NDK base directory, if an NDK is present. + + \defaultvalue Determined automatically based on standard search paths. +*/ + +/*! + \qmlproperty string Android.sdk::platform + + The versioned platform name (for example, \c "android-21"). + + \defaultvalue Highest build tools version version available in the SDK. +*/ + +/*! + \qmlproperty string Android.sdk::sdkDir + + The SDK base directory. + + \defaultvalue Determined automatically based on standard search paths. +*/ + +/*! + \qmlproperty bool Android.sdk::embedJar + \since Qbs 1.10 + + 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. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty path Android.sdk::assetsDir + + The base directory for Android assets in the respective product. + + \note Android requires that the file name of this directory is always + \c "assets". + + \defaultvalue \c "src/main/assets" in the product source directory +*/ + +/*! + \qmlproperty bool Android.sdk::automaticSources + + If \c true, Java sources as well as Android resources, assets, and the + manifest file will be automatically included in the respective product + via wildcards. Set this property to \c false if you want to specify + these files manually. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty string Android.sdk::manifestFile + + The file path to the Android manifest file. + This property is only relevant if \l automaticSources is enabled. + + \note Android requires that the file name is always "AndroidManifest.xml". + + \defaultvalue \c "src/main/AndroidManifest.xml" in the product source directory +*/ + +/*! + \qmlproperty string Android.sdk::packageName + + The package name of the respective product. The \c package attribute in the manifest file + will be set to this value automatically. + + \defaultvalue \c name +*/ + +/*! + \qmlproperty string Android.sdk::versionCode + + The Android Manifest version code of the respective product. The \c android:versionCode + attribute in the manifest file will be set to this value if not undefined. + + In the following example we provide an architecture-specific value + for \c android:versionCode: + + \code + // ... + property int _version: 1 + property int _patchVersion: 0 + Android.sdk.versionCode: { + switch (Android.ndk.abi) { + case "armeabi-v7a": + return 132000000 | _version * 10 + _patchVersion; + case "arm64-v8a": + return 164000000 | _version * 10 + _patchVersion; + case "x86": + return 232000000 | _version * 10 + _patchVersion; + case "x86_64": + return 264000000 | _version * 10 + _patchVersion; + } + throw "Unknown architecture"; + } + \endcode + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty string Android.sdk::versionName + + The Android Manifest version name of the respective product. The \c android:versionName + attribute in the manifest file will be set to this value if not undefined. + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty path Android.sdk::resourcesDir + + The base directory for Android resources in the respective product. + + \note Android requires that the file name of this directory is always + \c "res". + + \defaultvalue \c "src/main/res" in the product source directory +*/ + +/*! + \qmlproperty path Android.sdk::sourcesDir + + The base directory for Java sources. This property is only relevant if + \l automaticSources is enabled. + + \defaultvalue \c "src/main/java" in the product source directory +*/ + +/*! + \qmlproperty string Android.sdk::apkBaseName + + The base name of the APK file to to be built, that is, the file name + without the ".apk" extension. + + \defaultvalue \l packageName +*/ + +/*! + \qmlproperty stringList Android.sdk::aidlSearchPaths + Search paths for import statements to pass to the \c aidl tool via the \c{-I} option. +*/ + +/*! + \qmlproperty string Android.sdk::aaptName + + Name of the aapt binary. Allowed options: "aapt" and "aapt2". + + \defaultvalue \c "aapt" +*/ + +/*! + \qmlproperty string Android.sdk::packageType + + Type of the package. Allowed options: "apk" and "aab". + Type "apk" generates a runnable package whereas "aab" generates a package for Google Play. + + \defaultvalue \c "apk" +*/ diff --git a/doc/reference/modules/archiver-module.qdoc b/doc/reference/modules/archiver-module.qdoc new file mode 100644 index 00000000..8bec4993 --- /dev/null +++ b/doc/reference/modules/archiver-module.qdoc @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** 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 + \qmltype archiver + \inqmlmodule QbsModules + \since Qbs 1.4 + + \brief Provides support for building archives. + + The \c archiver module contains the properties and rules for creating (compressed) archives. + The output artifact has the file tag \c "archiver.archive". The sole input artifact is a text file + containing the list of files to package, with one file path per line. The paths can be + relative, in which case they will be looked for in \l{archiver::}{workingDirectory}. The file tag + of this input artifact is \c "archiver.input-list". +*/ + +/*! + \qmlproperty stringList archiver::flags + + Custom options not covered by any of the other properties. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty string archiver::archiveBaseName + + The base name of the archive file. That is, the file name without any + extensions. + + \defaultvalue \l{Product::targetName}{product.targetName} +*/ + +/*! + \qmlproperty string archiver::compressionLevel + + How much effort to put into the compression of a \c 7-Zip or \c zip archive. + + Possible values for zip are: + + \list + \li \c undefined + \li \c "0" + \li \c "1" + \li \c "2" + \li \c "3" + \li \c "4" + \li \c "5" + \li \c "6" + \li \c "7" + \li \c "8" + \li \c "9" + \endlist + + 7-Zip only supports 0 and the odd numbers above. + + Higher numbers result in a smaller archive, but the compression process will + take more time. + + If the value is left undefined, the default compression level is used. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string archiver::compressionType + + How to compress a \c tar or \c zip archive. + + Possible options are: + + \list + \li \c "bz2" + \li \c "deflate" + \li \c "gz" + \li \c "none" + \li \c "store" + \li \c undefined, which uses the archiver's default compression type. + \li \c "xz" + \li \c "Z" + \endlist + + \defaultvalue \c{"gz"} for \c tar archives, otherwise \c undefined. +*/ + +/*! + \qmlproperty string archiver::outputDirectory + + Where to put the archive file. + + \defaultvalue \l{Product::destinationDirectory} + {product.destinationDirectory} +*/ + +/*! + \qmlproperty string archiver::type + + Which kind of archiver to use. + + The currently supported values are: + + \list + \li \c "7zip" + \li \c "tar" + \li \c "zip" + \endlist + + \nodefaultvalue +*/ + +/*! + \qmlproperty string archiver::workingDirectory + + The directory in which to execute the archiver tool specified by + \l{archiver::}{command}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string archiver::command + + The path to the executable used to create the archive. + + This is usually the native tool corresponding to the archive type being + produced, but may fall back to another tool also capable of producing that + archive type if the native tool is not installed on the host system. + This behavior is especially useful on platforms such as Windows, where the + native tools for producing \c tar and \c zip archives in particular are + much less likely to be installed. + + The following table lists the supported \l{archiver::type}{archive types} + and the tools capable of producing them, listed in search order from left to + right: + + \table + \header + \li Type + \li Supported tools + \row + \li 7zip + \li 7z + \row + \li tar + \li tar, 7z + \row + \li zip + \li zip \e (Info-Zip), 7z, jar \e (from Java JDK) + \endtable + + \defaultvalue Depends on \l{archiver::}{type}. +*/ diff --git a/doc/reference/modules/autotest-module.qdoc b/doc/reference/modules/autotest-module.qdoc new file mode 100644 index 00000000..e25be621 --- /dev/null +++ b/doc/reference/modules/autotest-module.qdoc @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + \qmltype autotest + \inqmlmodule QbsModules + \since Qbs 1.13 + + \brief Allows to fine-tune autotest execution. + + The \c autotest module provides properties that allow autotest applications to specify + how exactly they should be run. +*/ + +/*! + \qmlproperty bool autotest::allowFailure + + Autotests for which this property is \c true can return a non-zero exit code without + causing the entire \l AutotestRunner to fail. Use this for tests that are known + to be unreliable. + + \defaultvalue \c false +*/ + +/*! + \qmlproperty stringList autotest::arguments + + The list of arguments to invoke the autotest with. If not specified, then + the \l{AutotestRunner::arguments}{arguments} property of the + \l AutotestRunner that invokes the autotest is used. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string autotest::workingDir + + The working directory for running the autotest. If not specified, then + the \l{AutotestRunner::workingDir}{workingDir} property of the + \l AutotestRunner that invokes the autotest is used. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int autotest::timeout + + The time limit for the execution of the autotest. If not specified, the + \l{AutotestRunner::timeout}{timeout} property of the \l AutotestRunner + that invokes the autotest is used. + + \nodefaultvalue + \since Qbs 1.15 +*/ + diff --git a/doc/reference/modules/bundle-module.qdoc b/doc/reference/modules/bundle-module.qdoc new file mode 100644 index 00000000..ec0e1083 --- /dev/null +++ b/doc/reference/modules/bundle-module.qdoc @@ -0,0 +1,476 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jake Petroules. +** 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 + \qmltype bundle + \inqmlmodule QbsModules + \since Qbs 1.4 + + \brief Provides Core Foundation bundle support. + + The \c bundle module contains properties and rules for building and working with + Core Foundation bundles on Apple platforms (commonly known as CFBundles or simply \e bundles), + directories with a standardized hierarchical structure that hold executable code and resources. + Examples include applications, frameworks, and plugins. + + This module is available on all platforms but is currently only useful on Apple platforms. + + \note Core Foundation bundles are not to be confused with Mach-O loadable modules, which are + also referred to as (loadable) \e bundles in Apple parlance. + In \QBS, Core Foundation bundles are referred to as \e bundles, + while Mach-O loadable bundles are referred to as \e {loadable modules}. + + \section2 Relevant File Tags + \target filetags-bundle + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"bundle.content"} + \li - + \li 1.8 + \li Attached to the output artifacts of the rule that produces the + bundle. + \row + \li \c{"infoplist"} + \li \c{Info.plist}, \c{*-Info.plist} + \li 1.5 + \li Source files with this tag are Info property lists files or fragments that are merged + into the bundle's final \c Info.plist. + \endtable +*/ + +/*! + \qmlproperty bool bundle::isBundle + + Whether the product should actually be packaged as a bundle as opposed to a + flat file. This allows a product indirectly dependent on the \c{bundle} + module to retain control of whether it should actually be built as a bundle. + + \defaultvalue \c{true} for applications and dynamic libraries on Apple + platforms, \c{false} otherwise. +*/ + +/*! + \qmlproperty bool bundle::isShallow + \readonly + + Whether the bundle directory tree is \e shallow. That is, whether it lacks a + \c Contents subdirectory. This is the default on all platforms other than + macOS. + + \defaultvalue \c{false} on macOS, otherwise \c{true}. +*/ + +/*! + \qmlproperty string bundle::identifierPrefix + + A prefix for the product's bundle identifier. If \l{bundle::}{identifier} + is left unset, the bundle identifier will be a concatenation of this value + and the \l{bundle::}{identifier} property, separated by a period (.). This + corresponds to the organization identifier in Xcode. + + \defaultvalue \c{org.example} +*/ + +/*! + \qmlproperty string bundle::identifier + + The bundle's identifier. If left unset, the bundle identifier will be a + concatenation of this value and the \l{bundle::}{identifierPrefix} property, + separated by a period (.). + + \defaultvalue A combination of \l{bundle::}{identifierPrefix} and the + product's target name formatted as an RFC-1034 identifier. +*/ + +/*! + \qmlproperty string bundle::extension + + The extension of the bundle's wrapper directory, without the leading period + (.). + + This property should not normally need to be set unless creating a custom + bundle type. + + \defaultvalue \c{"app"} for \c{"APPL"} packages, \c{"framework"} for + \c{"FMWK"} packages, and \c{"bundle"} for \c{"BNDL"} and custom packages. +*/ + +/*! + \qmlproperty string bundle::packageType + + The four-letter file type code of the bundle, specified in the bundle's + \c PkgInfo file and in the bundle's \c Info.plist as the value for the + \c CFBundlePackageType key. + + This property should almost never need to be changed, even though specifying + an alternative package type for custom bundles is allowed. + + \defaultvalue \c{"APPL"} for applications, \c{"FMWK"} for frameworks, and + \c{"BNDL"} for custom bundles. +*/ + +/*! + \qmlproperty bool bundle::generatePackageInfo + \since Qbs 1.5 + + Whether to generate a \c PkgInfo file for the bundle. + + This property should almost never need to be changed, even though enabling + it when specifying an alternative package type for custom bundles using + \l{bundle::}{packageType} is allowed. + + \defaultvalue \c{true} for applications, otherwise \c{false}. +*/ + +/*! + \qmlproperty string bundle::signature + + The four-letter signature specific to the bundle, also known as the creator + code, specified in the bundle's \c PkgInfo file and in the bundle's + \c Info.plist as the value for the \c CFBundleSignature key. + + This property should normally never need to be set. + + \defaultvalue \c{"????"} +*/ + +/*! + \qmlproperty string bundle::bundleName + + The file name of the bundle's wrapper directory. + + This property should not normally need to be changed. + + \defaultvalue A combination of the product's \l{Product::}{targetName} and + bundle's \l{bundle::}{extension}. +*/ + +/*! + \qmlproperty string bundle::frameworkVersion + + For framework bundles, the version of the framework. Not used for other + package types. + + \defaultvalue \c{"A"} +*/ + +/*! + \qmlproperty pathList bundle::publicHeaders + + A list of public header files to copy to a framework bundle's \c Headers + subdirectory. + + \nodefaultvalue +*/ + +/*! + \qmlproperty pathList bundle::privateHeaders + + A list of private header files to copy to a framework bundle's + \c PrivateHeaders subdirectory + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool bundle::resources + + A list of resources to copy to a bundle's \c Resources subdirectory. Files + will automatically be copied into \c lproj subdirectories corresponding to + the input files' paths. + + \nodefaultvalue +*/ + +/*! + \qmlproperty var bundle::infoPlist + + A dictionary of key-value pairs to add to the bundle's \c Info.plist. + + The contents of this property will be aggregated with the values from any + \c plist files. If this property and any \c plist files contain the same + key, this property will take precedence. However, it might be overridden + during postprocessing (see \l{bundle::}{processInfoPlist}). + + If \c undefined, will not be taken into account. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool bundle::processInfoPlist + + Whether to perform post-processing on the aggregated \c Info.plist contents. + + If this property is \c{true}, various post-processing operations will be + applied to the bundle's property list dictionary after it has been + aggregated from the contents of any \c plist files on disk, and the + \l{bundle::}{infoPlist} property. + + First, values from a list of defaults will be added to the dictionary if + they were not already present. Second, values from the \c AdditionalInfo key + of the platform SDK's \c Info.plist file will be added to the dictionary if + they were not already present, as well as some other miscellaneous keys, + such as \c BuildMachineOSBuild and \c UIDeviceFamily (on iOS). Finally, + variable expansions will be performed such that substrings of the form + \c $(VAR) or \c ${VAR} will be replaced with their corresponding environment + variables. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty bool bundle::embedInfoPlist + + Whether to create a \c{__TEXT} section in the product's executable + containing the processed \c Info.plist. + + Only applies to command line applications. + + \defaultvalue \c{true} if the product is a command line tool, otherwise + \c{false}. +*/ + +/*! + \qmlproperty string bundle::infoPlistFormat + + The file format to write the product's resulting \c Info.plist in. + + Possible values are: + + \list + \li \c{"binary1"} + \li \c{"json"} + \li \c{"same-as-input"} + \li \c{"xml1"} + \endlist + + \defaultvalue \c{"binary1"} for iOS, \c{"same-as-input"} or \c{"xml1"} + for macOS depending on whether a \c plist file is used, and \c{undefined} + for all other operating systems. +*/ + +/*! + \qmlproperty string bundle::infoPlistPath + \readonly + + The path that the \c Info.plist file will be written to. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::infoStringsPath + \readonly + \since Qbs 1.5 + + The path that the \c InfoPlist.strings file will be written to. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::pkgInfoPath + \readonly + + The path that the \c PkgInfo file will be written to. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::versionPlistPath + \readonly + + The path that the \c version.plist file will be written to. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::executablePath + \readonly + + The path that the main executable file will be written to. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::contentsFolderPath + \readonly + + The path of the bundle's \c Contents subdirectory. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::documentationFolderPath + \readonly + \since Qbs 1.5 + + The path of the directory where documentation will be written. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::executableFolderPath + \readonly + + The path of the directory where the main exectuable will be written. + + The path is relative to the directory that contains the bundle. + + Not to be confused with \l{bundle::}{executablesFolderPath}. +*/ + +/*! + \qmlproperty string bundle::executablesFolderPath + \readonly + + The path of the directory where auxiliary executables will be copied. + + The path is relative to the directory that contains the bundle. + + Not to be confused with \l{bundle::}{executableFolderPath}. +*/ + +/*! + \qmlproperty string bundle::frameworksFolderPath + \readonly + + The path of the directory where internal frameworks will be copied. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::javaFolderPath + \readonly + \since Qbs 1.5 + + The path of the directory where Java content will be written. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::localizedResourcesFolderPath + \readonly + \since Qbs 1.5 + + The path of the directory where localized resource files will be copied. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::pluginsFolderPath + \readonly + + The path of the directory where plugins will be copied. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::privateHeadersFolderPath + \readonly + + The path of the directory where private header files will be copied. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::publicHeadersFolderPath + \readonly + + The path of the directory where public headers files will be copied. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::scriptsFolderPath + \readonly + + The path of the directory where script files will be copied. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::sharedFrameworksFolderPath + \readonly + + The path of the directory where shared frameworks will be copied. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::sharedSupportFolderPath + \readonly + + The path of the directory where shared support files will be copied. + + The path is relative to the directory that contains the bundle. +*/ + +/*! + \qmlproperty string bundle::unlocalizedResourcesFolderPath + \readonly + + The path of the directory where non-localized resource files will be copied. + + The path is relative to the directory that contains the bundle. + + This is the same as the base resources path. +*/ + +/*! + \qmlproperty string bundle::versionsFolderPath + \readonly + \since Qbs 1.5 + + The path of the bundle's \c Versions subdirectory. + + The path is relative to the directory that contains the bundle. + + This is only relevant for (non-shallow) framework bundles. +*/ diff --git a/doc/reference/modules/capnprotocpp-module.qdoc b/doc/reference/modules/capnprotocpp-module.qdoc new file mode 100644 index 00000000..b041670a --- /dev/null +++ b/doc/reference/modules/capnprotocpp-module.qdoc @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** 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 + \qmltype capnproto.cpp + \inqmlmodule QbsModules + \since Qbs 1.17 + + \brief Provides support for Cap'n Proto for the C++ language. + + The \c capnproto.cpp module provides support for generating C++ headers + and sources from proto definition files using the \c capnpc tool. + + A simple qbs file that uses Cap'n Proto can be written as follows: + \code + CppApplication { + Depends { name: "capnproto.cpp" } + files: ["foo.capnp", "main.cpp"] + } + \endcode + A generated header now can be included in the C++ sources: + \code + #include + + int main(int argc, char* argv[]) { + ::capnp::MallocMessageBuilder message; + + auto foo = message.initRoot(); + foo.setAnswer(42); + return 0; + } + \endcode + + \section2 Relevant File Tags + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"capnproto.input"} + \li \c{*.capnp} + \li 1.17.0 + \li Source files with this tag are considered inputs to the \c capnpc compiler. + \endtable + + \section2 Dependencies + This module depends on the \c capnp module and on the \c capnp-rpc module if + \l{capnproto.cpp::useRpc}{useRpc} property is \c true. These modules are created by the + \l{Module Providers} via the \c pkg-config tool. +*/ + +/*! + \qmlproperty string capnproto.cpp::compilerName + + The name of the capnp binary. + + \defaultvalue \c "capnpc" +*/ + +/*! + \qmlproperty string capnproto.cpp::compilerPath + + The path to the protoc binary. + + Use this property to override the auto-detected location. + + \defaultvalue \c auto-detected +*/ + +/*! + \qmlproperty pathList capnproto.cpp::importPaths + + The list of import paths that are passed to the \c capnpc tool via the \c --import-path option. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty bool capnproto.cpp::useRpc + + Use this property to enable support for the RPC framework. + + A simple qbs file that uses rpc can be written as follows: + + \quotefile ../examples/capnproto/calculator_cpp/calculator_cpp.qbs + + \defaultvalue \c false +*/ diff --git a/doc/reference/modules/cpp-module.qdoc b/doc/reference/modules/cpp-module.qdoc new file mode 100644 index 00000000..da9996b8 --- /dev/null +++ b/doc/reference/modules/cpp-module.qdoc @@ -0,0 +1,1872 @@ +/**************************************************************************** +** +** 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 + \qmltype cpp + \inqmlmodule QbsModules + \since Qbs 1.0 + + \brief Provides C/C++ support. + + The \c cpp module contains the properties and rules for toolchains of the C/C++ family. + On Apple platforms, this includes support for Objective-C/C++. + + \section2 Setting Up the Run Environment + + When running an application that has dependencies on dynamic libraries, the + script specified by \l{Module::setupRunEnvironment}{Module.setupRunEnvironment} + automatically adds the locations of the libraries to the environment. + That is, to \c PATH on Windows, \c DYLD_LIBRARY_PATH on macOS, + and \c LD_LIBRARY_PATH on Linux and other Unix platforms. If the value + \c{"ignore-lib-dependencies"} shows up in the \c config array, this behavior + is disabled. Users can set the value via the \l run command. + + \target dependency-parameters-cpp + \section2 Dependency Parameters + + \table + \header + \li Parameter + \li Type + \li Since + \li Default + \li Description + \row + \li \c{link} + \li \c{bool} + \li 1.9 + \li undefined + \li If \c{false}, the dependency will not be linked, even if + it is a valid input for a linker rule. This property + affects library dependencies only. + \row + \li \c{linkWholeArchive} + \li \c{bool} + \li 1.9 + \li undefined + \li If \c{true}, then if the dependency is a static library, all of its objects + will be pulled into target binary, even if their symbols do not appear to be used. + This parameter is mainly useful when creating a dynamic library from static libraries. + \row + \li \c{symbolLinkMode} + \li \c{string} + \li 1.9 + \li undefined + \li Attribute specifying how the library or framework will be linked. + May contain the values: "weak", "lazy", "reexport", and "upward"; refer to the Apple + ld64 man page for full details. \c{undefined} uses normal linking. + Currently only applies when linking for Apple platforms. + \endtable + + \section2 Relevant File Tags + \target filetags-cpp + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"application"} + \li n/a + \li 1.0.1 + \li The rule that creates executable files (typically via a linker) attaches this tag + to its output artifact. + \row + \li \c{"asm"} + \li \c{*.s} (for GCC-like toolchains), \c{*.asm} (for MSVC) + \li 1.1.0 + \li Source files with this tag serve as inputs to a rule invoking the toolchain's + assembler. One object file is generated for each such file. + \row + \li \c{"asm_cpp"} + \li \c{*.S}, \c{*.sx} + \li 1.1.0 + \li Like \c{"asm"}, but for source files that need preprocessing. This tag only has an + effect with GCC-like toolchains. + \row + \li \c{"c"} + \li \c{*.c} (if \c combineCSources is not enabled) + \li 1.0.1 + \li Source files with this tag serve as inputs to a rule invoking the toolchain's + C compiler. One object file is generated for each such file. + \row + \li \c{"c.combine"} + \li \c{*.c} (if \c combineCSources is enabled) + \li 1.8 + \li Source files with this tag serve as inputs to a rule combining them into + a single C file, which will then be compiled. + \row + \li \c{"cpp"} + \li \c{*.C}, \c{*.cpp}, \c{*.cxx}, \c{*.c++}, \c{*.cc} + (if \c combineCxxSources is not enabled) + \li 1.0.1 + \li Source files with this tag serve as inputs to a rule invoking the toolchain's + C++ compiler. One object file is generated for each such file. + \row + \li \c{"cpp.combine"} + \li \c{*.C}, \c{*.cpp}, \c{*.cxx}, \c{*.c++}, \c{*.cc} + (if \c combineCxxSources is enabled) + \li 1.8 + \li Source files with this tag serve as inputs to a rule combining them into + a single C++ file, which will then be compiled. + \row + \li \c{"c_pch_src"}, \c{"cpp_pch_src"}, \c{"objc_pch_src"}, \c{"objcpp_pch_src"} + \li - + \li 1.5 + \li Files with this tag will be turned into precompiled headers for C, C++, Objective-C + and Objective-C++, respectively. There can be only one such file per product and + language. + \row + \li \c{"def"} + \li - + \li 1.17.0 + \li This tag is used for a module-definition file (.def) to be passed to the linker. + Such a file provides the linker with information about exports, attributes, + and other information about the program to be linked. This file tag only has + an effect with the MSVC toolchain. + \row + \li \c{"dynamiclibrary"} + \li n/a + \li 1.0.1 + \li The rule that creates dynamic libraries (typically via a linker) attaches this tag + to its output artifact. + \row + \li \c{"dynamiclibrary_import"} + \li n/a + \li 1.0.0 + \li This tag is used for import libraries of dynamic libraries. For + example, the MSVC linker rule creates a \c{dynamiclibrary_import} + artifact \c{foo.lib} in addition to a \c{dynamiclibrary} artifact + \c{foo.dll}. + \row + \li \c{"hpp"} + \li \c{*.h}, \c{*.H}, \c{*.hpp}, \c{*.hxx}, \c{*.h++} + \li 1.0.1 + \li This tag is used for header files (C, C++, Objective-C and Objective-C++). No rule + in this module generates output artifacts from such files directly, but the compiler + rule will have a dependency on all rules that create such files. + \row + \li \c{"linkerscript"} + \li - + \li 1.5.0 + \li This tag is used for \c ld linker scripts. You can provide such a file if you need + to replace the default linker script. + This file tag only has an effect with GCC-like toolchains. The linker needs to be + \c{ld}-compatible. + \row + \li \c{"obj"} + \li n/a + \li 1.0.1 + \li The rule that creates object files (typically via a compiler) attaches this tag + to its output artifacts. Such files are usually intermediate artifacts of the build + process and rarely need to be referenced in project files. + \row + \li \c{"objc"} + \li \c{*.m} (if \c combineObjcSources is not enabled) + \li 1.1.0 + \li Source files with this tag serve as inputs to a rule invoking the toolchain's + Objective-C compiler. One object file is generated for each such file. + \row + \li \c{"objc.combine"} + \li \c{*.m} (if \c combineObjcSources is enabled) + \li 1.8 + \li Source files with this tag serve as inputs to a rule combining them into + a single Objective-C file, which will then be compiled. + \row + \li \c{"objcpp"} + \li \c{*.mm} (if \c combineObjcxxSources is not enabled) + \li 1.1.0 + \li Source files with this tag serve as inputs to a rule invoking the toolchain's + Objective-C++ compiler. One object file is generated for each such file. + \row + \li \c{"objcpp.combine"} + \li \c{*.mm} (if \c combineObjcxxSources is enabled) + \li 1.8 + \li Source files with this tag serve as inputs to a rule combining them into + a single Objective-C++ file, which will then be compiled. + \row + \li \c{"rc"} + \li \c{*.rc} + \li 1.1.0 + \li Files with this tag serve as inputs to the Windows resource compiler. One object file + is generated for each such file. The tag has no effect on target platforms other than + Windows. + \row + \li \c{"staticlibrary"} + \li n/a + \li 1.0.1 + \li The rule that creates static libraries (typically via a linker) attaches this tag + to its output artifact. + \row + \li \c{"versionscript"} + \li - + \li 1.5.0 + \li This tag is used for \c ld linker scripts. You can provide such a file if you need + fine-grained control over the symbols present in a shared library. + This file tag only has an effect with GCC-like toolchains. The linker needs to be + \c{ld}-compatible. + \endtable + + \section2 Relevant Job Pools + \target cpp-job-pools + + \table + \header + \li Pool + \li Since + \li Description + \row + \li \c{"assembler"} + \li 1.13 + \li The job pool used by rules that run the toolchain's assembler. This is only + relevant for direct invocations of the assembler binary, not for running it + indirectly via the compiler. + \row + \li \c{"compiler"} + \li 1.13 + \li The job pool used by rules that run a compiler. All language variants use + the same pool. + \row + \li \c{"linker"} + \li 1.13 + \li The job pool used by rules that run a linker. + \endtable +*/ + +/*! + \qmlproperty bool cpp::allowUnresolvedSymbols + \since Qbs 1.2 + + Set to \c true 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 not present during linking. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::architecture + + The target architecture. + + \defaultvalue \l{qbs::architecture}{qbs.architecture} +*/ + +/*! + \qmlproperty string cpp::endianness + \since Qbs 1.9 + + Specifies the endianness of the target platform's processor architecture + (\c{"big"} or \c{"little"}). + + The value is automatically detected from the compiler's default values, but + can also be manually set in order to select a specific endianness when + targeting bi-endian architectures like MIPS and PowerPC. + + \defaultvalue Compiler default value. +*/ + +/*! + \qmlproperty bool cpp::debugInformation + + Whether to generate debug information. + + \defaultvalue \l{qbs::debugInformation}{qbs.debugInformation} +*/ + +/*! + \qmlproperty bool cpp::combineCSources + \since Qbs 1.8 + + Enabling this property on a \l{Product}{product} instructs the + \l{FileTagger}{file tagger} to attach the tag \c{"c.combine"} to C sources, + rather than \c{"c"}. As a result, all C sources of the product will be + combined into a single file, which is then compiled. + + This can speed up initial compilation significantly, but is of course + detrimental in the context of incremental builds. Also, perfectly legal code + may fail to compile with this option due to the merging of translation + units. + + To enable this property in a product that has sources that cannot be merged, + put the sources into a dedicated \l{Group} and set their \l{Group::} + {fileTags} property to \c{"c"}, overriding the file tagger. + + \note Module properties set on specific source files (that is, at the Group + level) will not be taken into account when building the combined file. You + either need to set these properties at the product level or prevent the + respective files from getting combined via the mechanism described above. + + \defaultvalue \c false +*/ + +/*! + \qmlproperty bool cpp::combineCxxSources + \since Qbs 1.8 + + Like \l{cpp::}{combineCSources}, but for C++. The \l{filetags-cpp} + {relevant file tags} are \c{"cpp"} and \c{"cpp.combine"}. + + \defaultvalue \c false + + \sa combineCSources +*/ + +/*! + \qmlproperty bool cpp::combineObjcSources + \since Qbs 1.8 + + Like \l{cpp::}{combineCSources}, but for Objective-C. The \l{filetags-cpp} + {relevant file tags} are \c{"objc"} and \c{"objc.combine"}. + + \defaultvalue \c false + + \sa combineCSources +*/ + +/*! + \qmlproperty bool cpp::combineObjcxxSources + \since Qbs 1.8 + + Like \l{cpp::}{combineCSources}, but for Objective-C++. The \l{filetags-cpp} + {relevant file tags} are \c{"objcpp"} and \c{"objcpp.combine"}. + + \defaultvalue \c false + + \sa combineCSources +*/ + +/*! + \qmlproperty bool cpp::createSymlinks + \unixproperty + + Whether to create version alias symlinks when building a dynamic library. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty bool cpp::discardUnusedData + \since Qbs 1.10 + + 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. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpp::separateDebugInformation + \since Qbs 1.4 + + Whether to store debug information in an external file or bundle instead of + within the binary. + + The type of files that will be generated when this property is \c{true} is + dependent on the compiler and target platform: + + \list + \li With MSVC, this property controls the generation of + \c{.pdb} (Program Debug Database) files. + \li On Apple platforms with GCC or Clang, this property controls the + generation of \c{.dSYM} (DWARF Symbol) bundles or, if + \l{cpp::}{dsymutilFlags} contains the \c{-f} or \c{--flat} flags, + \c{.dwarf} symbol files. + \li On other platforms with GCC or Clang (including Windows/MinGW), this + property controls the generation of \c{.debug} symbol files. + \endlist + + \defaultvalue \c{true} for MSVC and with GCC or Clang on Apple platforms, + otherwise \c{false}. + + \sa debugInformation, {How do I separate and install debugging symbols?} +*/ + +/*! + \qmlproperty stringList cpp::defines + + A list of preprocessor macros that gets passed to the compiler. + + To set macro values, use the following syntax: + + \code + cpp.defines: ["USE_COLORS=1", 'COLOR_STR="blanched almond"'] + \endcode + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::platformDefines + + A list of preprocessor macros that are used for all projects that are built + for the current target platform. User project files usually do not set this + property. + + \nodefaultvalue +*/ + +/*! + \qmlproperty pathList cpp::includePaths + + A list of include paths. + + Relative paths are considered to be relative to the \c .qbs product file + they are used in. + + \nodefaultvalue +*/ + +/*! + \qmlproperty pathList cpp::systemIncludePaths + + A list of include paths that are passed as system include paths to the + compiler. + + For header files in those paths, warnings will be ignored. + + Relative paths are considered to be relative to the \c .qbs product file + they are used in. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::systemRunPaths + \since Qbs 1.6 + + The paths the dynamic linker uses on process start-up to locate dynamic + libraries. + + \defaultvalue Auto-detected for host builds on Linux via \c ldconfig, + \c{["/lib", "/usr/lib"]} otherwise on Unix, \c [] on Windows. +*/ + +/*! + \qmlproperty pathList cpp::libraryPaths + + A list of library search paths. + + The paths will be passed to the linker in the appropriate way. For + example, the \c {-L} argument will be used when using \c {ld}-like + linkers. + + Relative paths are considered to be relative to the \c .qbs product file + they are used in. + + \nodefaultvalue + + \sa dynamicLibraries, {How do I create a module for a third-party library?} +*/ + +/*! + \qmlproperty stringList cpp::dynamicLibraries + + A List of dynamic libraries to be linked. + + If the library is part of your project, consider using a \l{Depends} item + instead. + + Each library can be specified in one of two ways: + + \list + \li An absolute file path (for example, \c "/foo/bar.lib"), in which case + it is passed directly to the linker, ignoring \l libraryPaths. + \li A library basename (for example, \c "bar"), in which case + the directory that contains the library must be provided via + \l libraryPaths. + \endlist + + Relative paths (\c "foo/bar.lib") are not allowed. + + \nodefaultvalue + + \sa libraryPaths, {How do I create a module for a third-party library?} +*/ + +/*! + \qmlproperty stringList cpp::staticLibraries + + A list of static libraries to be linked. + + If the library is part of your project, consider using a \l{Depends} item + instead. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::executablePrefix + + A string to prepend to the executable file \l{Product::targetName}{name}. + + \defaultvalue \c "" +*/ + +/*! + \qmlproperty string cpp::dynamicLibraryPrefix + + A string to prepend to the dynamic library file \l{Product::targetName}{name}. + + \defaultvalue \l{qbs::toolchain}{toolchain}-dependent, typical values are \c "" or \c "lib" +*/ + +/*! + \qmlproperty string cpp::loadableModulePrefix + \appleproperty + + A string to prepend to the Darwin loadable module file \l{Product::targetName}{name}. + + \defaultvalue \c "" +*/ + +/*! + \qmlproperty string cpp::staticLibraryPrefix + + A string to prepend to the static library file \l{Product::targetName}{name}. + + \defaultvalue \l{qbs::toolchain}{toolchain}-dependent, typical values are \c "" or \c "lib" +*/ + +/*! + \qmlproperty string cpp::executableSuffix + + A string to append to the executable file \l{Product::targetName}{name}. + + \defaultvalue \l{qbs::toolchain}{toolchain}-dependent, typical values are \c "" or \c ".exe" +*/ + +/*! + \qmlproperty string cpp::dynamicLibrarySuffix + + A string to append to the dynamic library file \l{Product::targetName}{name}. + + \defaultvalue \l{qbs::toolchain}{toolchain}-dependent, typical values are \c ".so", \c ".dll" + or \c "dylib" +*/ + +/*! + \qmlproperty string cpp::dynamicLibraryImportSuffix + \windowsproperty + + A string to append to the dynamic library import file \l{Product::targetName}{name}. + + \defaultvalue \c ".lib" +*/ + +/*! + \qmlproperty string cpp::loadableModuleSuffix + \appleproperty + + A string to append to the Darwin loadable module file \l{Product::targetName}{name}. + + \defaultvalue \c ".bundle" +*/ + +/*! + \qmlproperty string cpp::staticLibrarySuffix + + A string to append to the executable file \l{Product::targetName}{name}. + + \defaultvalue \l{qbs::toolchain}{toolchain}-dependent, typical values are \c ".a" or \c ".lib" +*/ + +/*! + \qmlproperty string cpp::debugInfoSuffix + + A string to append to the debug information file name. + + \defaultvalue \l{qbs::toolchain}{toolchain}-dependent, typical values are \c ".debug", + \c ".pdb" or \c ".dwarf" +*/ + +/*! + \qmlproperty string cpp::debugInfoBundleSuffix + \appleproperty + + A string to append to the debug information bundle name. + + \defaultvalue \c ".dSYM" +*/ + +/*! + \qmlproperty pathList cpp::prefixHeaders + \since Qbs 1.0.1 + + A list of files to automatically include at the beginning of each source + file in the \l{Product}{product}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::optimization + + The optimization level. + + \defaultvalue \l{qbs::optimization}{qbs.optimization} +*/ + +/*! + \qmlproperty bool cpp::treatWarningsAsErrors + + Whether warnings will be handled as errors and cause the build to fail. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty bool cpp::useCPrecompiledHeader + \since Qbs 1.5 + + Whether to use a precompiled header for compiling C sources if one is + present. + + Set this property to \c false in a \l Group item to disable precompiled + headers for some sources even though a precompiled header is present in the + product. + + See \l{filetags-cpp}{Relevant File Tags} for the associated file tags. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty bool cpp::useCxxPrecompiledHeader + \since Qbs 1.5 + + Like \l useCPrecompiledHeader, but for C++. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty bool cpp::useObjcPrecompiledHeader + \since Qbs 1.5 + + Like \l useCPrecompiledHeader, but for Objective-C. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty bool cpp::useObjcxxPrecompiledHeader + \since Qbs 1.5 + + Like \l useCPrecompiledHeader, but for Objective-C++. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty string cpp::warningLevel + + The warning level for the compiler: \c{"none"} or \c{"all"}. + + \defaultvalue \c{"all"} +*/ + +/*! + \qmlproperty stringList cpp::driverFlags + \since Qbs 1.6 + + A list of flags that are added to all compilation and linking commands + performed by the compiler driver, independently of the language. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::driverLinkerFlags + \since Qbs 1.11 + + A list of flags that are added to all linking commands performed by the + compiler driver, independently of the language. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::commonCompilerFlags + \since Qbs 1.0.1 + + A list of flags that are added to all compilation commands independently of + the language. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::compilerVersion + + Compiler version string consisting of major, minor and patch numbers, + separated by a dot. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int cpp::compilerVersionMajor + \since Qbs 1.4 + + The major version of the compiler. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int cpp::compilerVersionMinor + \since Qbs 1.4 + + The minor version of the compiler. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int cpp::compilerVersionPatch + \since Qbs 1.4 + + The patch level component of the compiler version. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::assemblerFlags + \since Qbs 1.5 + + A list of additional flags for the assembler. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::cppFlags + + A list of additional flags for the C preprocessor. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::cFlags + + A list of additional flags for the C compiler. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::cxxFlags + + A list of additional flags for the C++ compiler. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::cLanguageVersion + \since Qbs 1.4 + + The version of the C standard with which the code must comply. + + If this property is set, the corresponding compiler and linker flags will be + added, depending on the toolchain. + + If the value is left undefined, the compiler default will be used. + If the list contains more than one value, the highest version is chosen. + + Possible values include: \c{"c89"}, \c{"c99"}, \c{"c11"}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::cxxLanguageVersion + \since Qbs 1.4 + + The version of the C++ standard with which the code must comply. + + If this property is set, the corresponding compiler and linker flags will be + added, depending on the toolchain. + + If the value is left undefined, the compiler default will be used. + If the list contains more than one value, the highest version is chosen. + + Possible values include: \c{"c++98"}, \c{"c++11"}, \c{"c++14"}, \c{"c++17"}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpp::useLanguageVersionFallback + \since Qbs 1.11 + + Whether to explicitly use the language standard version fallback values in + compiler command line invocations. + + By default, \QBS will automatically substitute fallback values for the C and + C++ language standard versions specified by the \l cLanguageVersion and + \l cxxLanguageVersion properties, which are passed to the \c{-std=} compiler + command line option with GNU-compatible toolchains, if it detects that you + are using an older toolchain which does not support the standard values. The + substitutions are made as follows: + + \table + \header + \li Value + \li Substitute + \row + \li c++11 + \li c++0x + \row + \li c11 + \li c1x + \row + \li c++14 + \li c++1y + \row + \li c++17 + \li c++1z + \endtable + + If this property is explicitly set to \c true, \QBS will always use the + fallback values. + + If this property is explicitly set to \c false, \QBS will never use the + fallback values. + + This property has no effect with the Microsoft Visual C++ compiler. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::cxxStandardLibrary + \since Qbs 1.4 + + The C++ standard library to link to. + + If this property is set, the corresponding compiler and linker flags will be + added, assuming the value is valid for the current toolchain. + + If the value is left undefined, the compiler default will be used. + + Possible values include: \c{"libstdc++"}, \c{"libc++"}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::objcFlags + + A list of additional flags for the Objective-C compiler. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::objcxxFlags + + A list of additional flags for the Objective-C++ compiler. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::linkerFlags + + A list of additional flags for the linker. + + These flags should \e not be escaped using the \c -Wl or \c -Xlinker + syntaxes, as \QBS will do this automatically based on the linker being used. + + \sa linkerMode + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::assemblerName + \since Qbs 1.5 + + The name of the assembler binary. This property is set in the build profile. + + \defaultvalue Determined by \l{setup-toolchains}{qbs setup-toolchains}. +*/ + +/*! + \qmlproperty string cpp::toolchainInstallPath + + The directory path where the toolchain is installed. This property is set + in the build profile. + + This is usually the base property from which all compiler and tool paths + are automatically derived. + + \defaultvalue Determined by \l{setup-toolchains}{qbs setup-toolchains}. +*/ + +/*! + \qmlproperty string cpp::assemblerPath + \since Qbs 1.5 + + The full path of the assembler binary. This property is set in the build + profile. + + \defaultvalue Determined by \l{setup-toolchains}{qbs setup-toolchains}. +*/ + +/*! + \qmlproperty string cpp::compilerName + + The name of the main compiler binary. This property is set in the build profile. + + \defaultvalue Determined by \l{setup-toolchains}{qbs setup-toolchains}. +*/ + +/*! + \qmlproperty string cpp::compilerPath + + The full path of the main compiler binary. This property is set in the build profile. + + If the toolchain provides different compilers for different languages, + \l{cpp::}{compilerPathByLanguage} is used. + + \defaultvalue Determined by \l{setup-toolchains}{qbs setup-toolchains}. +*/ + +/*! + \qmlproperty var cpp::compilerPathByLanguage + \since Qbs 1.3 + + A \c{string} to \c{string} map that maps file tags to full paths of compiler + binaries. This property is set in the build profile. + + \defaultvalue Determined by \l{setup-toolchains}{qbs setup-toolchains}. +*/ + +/*! + \qmlproperty stringList cpp::compilerWrapper + \since Qbs 1.1 + + A wrapper binary and its arguments for wrapping compiler calls. + This is useful for compiler wrappers, such as \c ccache. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::linkerName + \since Qbs 1.1.1 + + The name of the linker binary. This property is set in the build profile. + + \defaultvalue Determined by \l{setup-toolchains}{qbs setup-toolchains}. +*/ + +/*! + \qmlproperty string cpp::linkerPath + \since Qbs 1.1.1 + + The full path of the linker binary. This property is set in the build profile. + + \defaultvalue Determined by \l{setup-toolchains}{qbs setup-toolchains}. +*/ + +/*! + \qmlproperty stringList cpp::linkerWrapper + \since Qbs 1.6.2 + + A wrapper binary and its arguments for wrapping linker calls. + This is useful for linker wrappers as needed by Bullseye Coverage, for + example. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::entryPoint + \since Qbs 1.3 + + The name of the entry point of an executable or dynamic library. + + If this property is left undefined, the toolchain's default is used. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::runtimeLibrary + \since Qbs 1.3.3 + + The type of the runtime library used. + + Accepted values are \c{"static"} and \c{"dynamic"}. + + If this property is set to \c{undefined}, the default runtime library of the + toolchain is used. + + \note This property is only functional for MSVC. + + \defaultvalue \c{"dynamic"} for MSVC, \c{undefined} for other compilers. +*/ + +/*! + \qmlproperty bool cpp::enableExceptions + \since Qbs 1.5 + + Whether to enable exceptions in C++ code. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty bool cpp::enableSuspiciousLinkerFlagWarnings + \since Qbs 1.8 + + Whether to print warnings about escaped linker flags (such as \c -Xlinker and \c -Wl). + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty string cpp::exceptionHandlingModel + \since Qbs 1.5 + + The exception handling model to use. + + For MSVC, this can be \c{"default"}, \c{"seh"} or \c{"externc"}. For all + other compilers, \c{"default"} indicates the default or the only exception + handling model. + + \defaultvalue \c{"default"} +*/ + +/*! + \qmlproperty bool cpp::enableRtti + \since Qbs 1.5 + + Whether to enable runtime type information in C++ code. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpp::enableReproducibleBuilds + \since Qbs 1.5 + + Whether the compiler should try to generate reproducible object files. + + Some compilers (notably GCC) use random numbers for generating symbol names + that have to be different in every compilation unit. This is avoided by + setting this property to \c{true}. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty bool cpp::treatSystemHeadersAsDependencies + \since Qbs 1.8 + + Whether included header files found via \l{cpp::}{systemIncludePaths}, + \l{cpp::}{distributionIncludePaths}, or \l{cpp::}{compilerIncludePaths} + will be added to the dependencies of the respective object file. + + This means that modification of such header files (or any of the headers + they include) will cause recompilation. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty stringList cpp::dsymutilFlags + \since Qbs 1.4.1 + + \appleproperty + + Additional flags for the \c dsymutil tool. + + \note If this property contains the \c{-f} or \c{--flat} options, this will + cause \QBS to generate "flat" (single-file) \c{.dwarf} debug symbol files + instead of \c{.dSYM} bundles (directories) when + \l{cpp::}{separateDebugInformation} is set to \c{true}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::dsymutilPath + \since Qbs 1.4 + + \appleproperty + + The full path of the \c dsymutil binary. This property is set in the build + profile. + + \defaultvalue Determined by \l{setup-toolchains}{qbs setup-toolchains}. +*/ + +/*! + \qmlproperty string cpp::lipoPath + \since Qbs 1.9 + + \appleproperty + + The full path of the \c lipo binary. + + \defaultvalue Determined automatically. +*/ + +/*! + \qmlproperty pathList cpp::frameworkPaths + + \appleproperty + + A list of framework search paths. + + Relative paths are considered to be relative to the \c .qbs product file + they are used in. + + \nodefaultvalue +*/ + +/*! + \qmlproperty pathList cpp::systemFrameworkPaths + + \appleproperty + + A list of framework search paths. + + Relative paths are considered to be relative to the \c .qbs product file + they are used in. Header files in frameworks in those paths will not cause + warnings. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::frameworks + + \appleproperty + + A list of frameworks to be linked. + + If the framework is part of your project, consider using a \l{Depends} item + instead. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::weakFrameworks + + \appleproperty + + A list of frameworks to be weakly linked. + + If the framework is part of your project, consider using a \l{Depends} item + instead. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpp::automaticReferenceCounting + \since Qbs 1.4 + + \appleproperty + + Whether to enable Automatic Reference Counting (ARC) for Objective-C and + Objective-C++ source code. + + If left undefined, uses the compiler default (probably \c{false}). + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpp::requireAppExtensionSafeApi + \since Qbs 1.4 + + \appleproperty + + Whether to enforce the use of only app-extension-safe APIs on Apple + platforms. This is necessary for building Application Extensions in OS X + Yosemite and iOS 8 and above. + + If left undefined, uses the compiler and linker defaults (probably + \c{false}). + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::minimumIosVersion + + \appleproperty + + A version number in the format \c{[major].[minor]} indicating the earliest + version of iOS that the product should run on. + + Passes \c{-miphoneos-version-min=} to the compiler. + + If set to undefined, compiler defaults will be used. + + \note \QBS sets minimum version to \c "6.0" for \c armv7a because earlier iOS versions are + broken in recent XCode installations. + + \defaultvalue \c "6.0" for \c armv7a, \c undefined otherwise +*/ + +/*! + \qmlproperty string cpp::minimumOsxVersion + \obsolete + + \appleproperty + + Deprecated in \QBS 1.5.2. Use \l{cpp::minimumMacosVersion} instead. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::minimumMacosVersion + \since Qbs 1.5.2 + + \appleproperty + + A version number in the format \c{[major].[minor]}indicating the earliest + version of macOS that the product should run on. + + Passes \c{-mmacosx-version-min=} to the compiler. + + If left undefined, compiler defaults will be used. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::minimumWatchosVersion + + \appleproperty + + A version number in the format \c{[major].[minor]} indicating the earliest + version of Apple watchOS that the product should run on. + + If left undefined, compiler defaults will be used. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::minimumTvosVersion + \since Qbs 1.5 + + \appleproperty + + A version number in the format \c{[major].[minor]} indicating the earliest + version of Apple tvOS that the product should run on. + + If left undefined, compiler defaults will be used. + + \note \QBS sets the minimum version to \c "6.0", because earlier tvOS + versions are not supported by recent XCode installations by default. + + \defaultvalue \c "6.0 +*/ + +/*! + \qmlproperty string cpp::archiverName + + \unixproperty + + The name of the archiver binary. This property is set in the build profile. + + \defaultvalue \c{"ar"} +*/ + +/*! + \qmlproperty string cpp::archiverPath + + \unixproperty + + The full path of the archiver binary. This property is set in the build + profile. + + \defaultvalue Determined by \l{setup-toolchains}{qbs setup-toolchains}. +*/ + +/*! + \qmlproperty string cpp::exportedSymbolsCheckMode + \since Qbs 1.4.1 + + \unixproperty + + Controls how \QBS determines whether an updated dynamic library causes + relinking of dependents. + + The default value is \c "ignore-undefined", which means that undefined + symbols being added or removed do not cause any relinking. If that should + happen, for example because dependent products are linked with an option + such as \c "--no-undefined", this property can be set to \c "strict". + + \defaultvalue \c "ignore-undefined" +*/ + +/*! + \qmlproperty string cpp::linkerMode + \since Qbs 1.6 + + Controls whether to automatically use an appropriate compiler frontend + instead of the system linker when linking binaries. + + The default is \c{"automatic"}, which chooses either the C++ compiler, C + compiler, or system linker specified by the \l{cpp::}{linkerName} and + \l{cpp::}{linkerPath} properties, depending on the type of object files + present on the linker command line. + + Set this property to \c{"manual"} to explicitly specify the linker using the + \l{cpp::}{linkerName} and \l{cpp::}{linkerPath} properties. + + \defaultvalue \c "automatic" +*/ + +/*! + \qmlproperty string cpp::linkerVariant + \since Qbs 1.13 + + Set this property to force the use of a specific linker. A non-empty value + will result in the \c {-fuse-ld} option being emitted when linking with \c gcc, + \c clang or \c clang-cl. + + The possible values for \c clang and \c gcc are \c "bfd", \c "gold" and \c "lld", + the possible values for \c clang-cl are \c "link" and \c "lld". + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::nmName + \since Qbs 1.2 + + \unixproperty + + The name of the \c nm binary. This property is set in the build profile. + + \defaultvalue \c{"nm"} +*/ + +/*! + \qmlproperty string cpp::nmPath + \since Qbs 1.2 + + \unixproperty + + The full path of the \c nm binary. This property is set in the build + profile. + + \defaultvalue Determined by \l{setup-toolchains}{qbs setup-toolchains}. +*/ + +/*! + \qmlproperty string cpp::objcopyName + \since Qbs 1.4 + + \unixproperty + + The name of the \c objcopy binary. This property is set in the build + profile. + + \defaultvalue \c{"objcopy"} +*/ + +/*! + \qmlproperty string cpp::objcopyPath + \since Qbs 1.4 + + \unixproperty + + The full path of the \c objcopy binary. This property is set in the build + profile. + + \defaultvalue Determined by \l{setup-toolchains}{qbs setup-toolchains}. +*/ + +/*! + \qmlproperty string cpp::stripName + \since Qbs 1.4 + + \unixproperty + + The name of the \c strip binary. This property is set in the build profile. + + \defaultvalue \c{"strip"} +*/ + +/*! + \qmlproperty string cpp::stripPath + \since Qbs 1.4 + + \unixproperty + + The full path of the \c strip binary. This property is set in the build + profile. + + \defaultvalue Determined by \l{setup-toolchains}{qbs setup-toolchains}. +*/ + +/*! + \qmlproperty bool cpp::positionIndependentCode + + \unixproperty + + Whether to generate position-independent code. + + If this property is left undefined, position-independent code is generated + for libraries, but not for applications. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::rpathOrigin + \since Qbs 1.11 + + \unixproperty + + A placeholder value used in an \c rpath string to refer to the directory + containing the referring executable or shared library. + + \defaultvalue \c{"@loader_path"} on Mach-O based platforms (macOS, iOS, + tvOS, watchOS); \c{"$ORIGIN"} on ELF based platforms (Linux and most other + Unix-like platforms). +*/ + +/*! + \qmlproperty stringList cpp::rpaths + + \unixproperty + + A list of \c rpaths that are passed to the linker. Paths that also appear in + \l{cpp::}{systemRunPaths} are ignored. + + \nodefaultvalue + + \sa{How do I make use of rpaths?} +*/ + +/*! + \qmlproperty string cpp::sonamePrefix + \since Qbs 1.5 + + \unixproperty + + A library name or full library path. + + If defined, the value of this property is used as a path to be prepended to + the built shared library's \c SONAME identifier. The \c SONAME + (\c LC_ID_DYLIB on Apple platforms, \c DT_SONAME on other Unix-like + platforms) is the identifier that the dynamic linker will later use to + reference the library. + + On Apple platforms, the path may contain the following placeholders: + + \list + \li \c @rpath - + Expands to paths defined by \c LC_RPATH Mach-O commands in + the current process executable or the referring libraries. + \li \c @executable_path - + Expands to the current process executable location. + \li \c @loader_path - + Expands to the referring executable or library location. + \endlist + + In most cases, using \c @rpath is sufficient and recommended. + However, the prefix may be also specified using different placeholders, or + an absolute path. + + For more information, see the \l{DYLD documentation} on dynamic library + install names. + + \nodefaultvalue + + \sa{How do I make use of rpaths?} +*/ + +/*! + \qmlproperty string cpp::soVersion + \since Qbs 1.7 + + \unixproperty + + The version number to be appended to the soname in ELF shared libraries. + + \defaultvalue The major part of \l{Product::version}{product.version} if a + version is set, otherwise \c []. +*/ + +/*! + \qmlproperty bool cpp::useRPaths + \since Qbs 1.3 + + \unixproperty + + If \c{false}, prevents the linker from writing \c rpaths to the binary. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty bool cpp::useRPathLink + \since Qbs 1.8 + + \unixproperty + + Whether to use the \c{-rpath-link} linker option for transitive shared + objects. + + \defaultvalue \c{true} on non-Darwin Unix platforms or when targeting + macOS 10.4.x and older. +*/ + +/*! + \qmlproperty string cpp::rpathLinkFlag + \since Qbs 1.9 + + The rpath link flag used by the linker. + + \defaultvalue \l{qbs::toolchain}{toolchain}-dependent, typical values are \c "-rpath-link=" + or \c "-L" +*/ + +/*! + \qmlproperty string cpp::variantSuffix + \since Qbs 1.10 + + A suffix to add to a product's target name if that product is of the + \l{Product::type}{type} \c staticlibrary or \c dynamiclibrary. + + On Darwin platforms, applications and loadable modules are also affected. + + By default, this property is left empty on all platforms unless the product + is multiplexed over the \l{qbs::buildVariants}{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 \c []. + + For example, building a dynamic library called \c MyLib that is multiplexed + over the \l{qbs::buildVariants}{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). + + \defaultvalue Platform-specific. +*/ + +/*! + \qmlproperty string cpp::visibility + + \unixproperty + + The visibility level for exported symbols. + + Possible values include: \c{"default"}, \c{"hidden"}, \c{"hiddenInlines"}, + and \c{"minimal"}, which combines \c{"hidden"} and \c{"hiddenInlines"}. + + \defaultvalue \c{"default"} +*/ + +/*! + \qmlproperty bool cpp::generateManifestFile + \since Qbs 1.5.0 + + \windowsproperty + + Whether to auto-generate a manifest file and include it in the binary. + + Set this property to \c false if you provide your own \c rc file. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty string cpp::windowsApiCharacterSet + \since Qbs 1.0.1 + + \windowsproperty + + + The character set used in the Win32 API. + + The value \c "unicode" defines the preprocessor symbols \c UNICODE and + \c _UNICODE, \c "mbcs" defines \c _MBCS, and setting the value to + \c undefined will use the default character set. + + \defaultvalue \c{"unicode"} +*/ + +/*! + \qmlproperty string cpp::windowsApiFamily + \since Qbs 1.10 + + \windowsproperty + + + The Windows API family that the application or library is targeting. + + This value 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. + + \defaultvalue Undefined, which lets the Windows SDK headers determine the + default. +*/ + +/*! + \qmlproperty stringList cpp::windowsApiAdditionalPartitions + \since Qbs 1.10 + + \windowsproperty + + A list of additional Windows API partitions to enable in addition to the + ones implicitly enabled by the value of \l{cpp::}{windowsApiFamily} + (\c WINAPI_FAMILY). + + This value is used when building Windows Store applications. For example, + setting \l{cpp::}{windowsApiFamily} to \c{"pc"} and this property to + \c{["phone"]} will allow you to create Windows Store applications that + target all Universal Windows Platform device families (\c "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. + + \defaultvalue Undefined, which lets the Windows SDK headers determine + the default partitions based on the value of \c WINAPI_FAMILY. + + \sa windowsApiFamily +*/ + +/*! + \qmlproperty bool cpp::requireAppContainer + \since Qbs 1.10 + + \windowsproperty + + Whether the generated executable or dynamic-link library \e requires an + AppContainer execution environment. Set to \c true when creating + Universal Windows Platform applications. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string cpp::minimumWindowsVersion + + \windowsproperty + + A version number in the format \c{[major].[minor]} indicating the earliest + version of Windows that the product should run on. + + Defines \c WINVER, \c _WIN32_WINNT, and \c _WIN32_WINDOWS, and applies a + version number to the linker flags \c /SUBSYSTEM and \c /OSVERSION for + MSVC or \c --major-subsystem-version, \c --minor-subsystem-version, + \c --major-os-version, and \c --minor-os-version for MinGW. + + If left undefined, compiler defaults will be used. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpp::alwaysUseLipo + \since Qbs 1.9 + + Whether to always use \c lipo when combining Mach-O output files on + Apple platforms, even if there is only one CPU architecture. This value + should not normally need to be changed. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty var cpp::compilerDefinesByLanguage + \since Qbs 1.10 + + A \c{string} to \c{string} to \c{string} 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 This property will not be usable without also setting + \l{cpp::}{enableCompilerDefinesByLanguage}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty pathList cpp::compilerIncludePaths + \since Qbs 1.6 + + A list of \c #include search paths that are used for all projects that are + using the current toolchain. + + User project files usually do not set this property. + + \defaultvalue Determined automatically by probing the compiler. +*/ + +/*! + \qmlproperty pathList cpp::compilerFrameworkPaths + \since Qbs 1.6 + + A list of framework search paths that are used for all projects that are + using the current toolchain. + + User project files usually do not set this property. + + \defaultvalue Determined automatically by probing the compiler. +*/ + +/*! + \qmlproperty pathList cpp::compilerLibraryPaths + \since Qbs 1.6 + + A list of library search paths that are used for all projects that are using + the current toolchain. + + User project files usually do not set this property. + + \defaultvalue Determined automatically by probing the compiler. +*/ + +/*! + \qmlproperty pathList cpp::distributionFrameworkPaths + \since Qbs 1.8 + + A list of distribution-specific framework search paths, prioritized after + \l{cpp::}{systemFrameworkPaths}. + + Intended for use by module authors implementing support for new operating + systems or distributions. + + User project files should not set this property. + + \nodefaultvalue +*/ + +/*! + \qmlproperty pathList cpp::distributionIncludePaths + \since Qbs 1.8 + + A list of distribution-specific include paths that are passed as system + include paths to the compiler, prioritized after + \l{cpp::}{systemIncludePaths}. + + Intended for use by module authors implementing support for new operating + systems or distributions. + + User project files should not set this property. + + \nodefaultvalue +*/ + +/*! + \qmlproperty pathList cpp::distributionLibraryPaths + \since Qbs 1.8 + + A list of distribution-specific library search paths. + + Intended for use by module authors implementing support for new operating + systems or distributions. + + User project files should not set this property. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList cpp::enableCompilerDefinesByLanguage + \since Qbs 1.10 + + A 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 + \l{cpp::}{compilerDefinesByLanguage}. + + Because this has performance implications, no languages are enabled by + default. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpp::generateLinkerMapFile + \since Qbs 1.13 + + Whether to auto-generate a linker map file. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty bool cpp::generateCompilerListingFiles + \since Qbs 1.15 + + Whether to auto-generate compiler listing files. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty bool cpp::generateAssemblerListingFiles + \since Qbs 1.15 + + \baremetalproperty + + Whether to auto-generate an assembler listing files. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty bool cpp::removeDuplicateLibraries + \since Qbs 1.16 + + Whether the list of the objects and libraries is reduced to a + list of unique values before being passed to the linker. + + \defaultvalue \c{true} +*/ diff --git a/doc/reference/modules/cpufeatures-module.qdoc b/doc/reference/modules/cpufeatures-module.qdoc new file mode 100644 index 00000000..8dcd117f --- /dev/null +++ b/doc/reference/modules/cpufeatures-module.qdoc @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** 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 + \qmltype cpufeatures + \inqmlmodule QbsModules + \since Qbs 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{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 +*/ + +/*! + \qmlproperty bool cpufeatures::arm_neon + + Whether to use NEON instructions in ARM binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::arm_vfpv4 + + Whether to use VFPv4 instructions in ARM binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::mips_dsp + + Whether to use DSP instructions in MIPS binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::mips_dspr2 + + Whether to use DSPr2 instructions in MIPS binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_avx + + Whether to use AVX instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_avx2 + + Whether to use AVX2 instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_avx512bw + + Whether to use AVX-512-BW instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_avx512cd + + Whether to use AVX-512-CD instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_avx512dq + + Whether to use AVX-512-DQ instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_avx512er + + Whether to use AVX-512-ER instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_avx512f + + Whether to use AVX-512 instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_avx512ifma + + Whether to use AVX-512-IFMA instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_avx512pf + + Whether to use AVX-512-PF instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_avx512vbmi + + Whether to use AVX-512-VBMI instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_avx512vl + + Whether to use AVX-512-VL instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_f16c + + Whether to use F16C instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_sse2 + + Whether to use SSE2 instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_sse3 + + Whether to use SSE3 instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_ssse3 + + Whether to use SSSE3 instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_sse4_1 + + Whether to use SSE4.1 instructions in x86 binaries. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool cpufeatures::x86_sse4_2 + + Whether to use SSE4.2 instructions in x86 binaries. + + \nodefaultvalue +*/ diff --git a/doc/reference/modules/dmg-module.qdoc b/doc/reference/modules/dmg-module.qdoc new file mode 100644 index 00000000..56fd6596 --- /dev/null +++ b/doc/reference/modules/dmg-module.qdoc @@ -0,0 +1,422 @@ +/**************************************************************************** +** +** 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 + \qmltype dmg + \inqmlmodule QbsModules + \since Qbs 1.9 + + \brief Provides support for building Apple Disk Images. + + The \c dmg module contains properties and rules for building Apple Disk Images, which are + typically used to distribute applications and installers on macOS. + + This module is only available on Apple platforms. + + \QBS enables you to build macOS disk images with custom backgrounds and icon + layouts. Most applications on macOS are installed via a \c .dmg file, which + is usually customized by using a custom image background and icon layout. + Unfortunately, it is challenging to construct such DMG files correctly, + because the layout relies on several undocumented proprietary file formats, + some of which date back to the Mac OS Classic days, and which are even + nested within one another. + + Most of the existing tools to create DMG files do so by using AppleScript to + manipulate the Finder graphically to generate the appropriate icon layout, + which is both unstable and incompatible with headless build servers, because + the necessary OS services to launch graphical applications may not be + running at all. Many projects create the primary \c .DS_Store + file manually, and commit the result to their source repository. This binary + blob is difficult to inspect and edit, and might not be backwards compatible + with older versions of the OS, depending on how it was generated. + + \QBS takes a different approach. It generates the necessary files + programmatically, in an entirely reproducible manner. There are no external + dependencies that need to be separately installed nor do any binary blobs + need to be committed to your source repository. + + \section2 Appearance Properties + + Appearance properties are used to control the contents of the .DS_Store file + and its embedded Alias and Bookmark records, that will be generated by \QBS + in order to control the appearance of the disk image when mounted in Finder. + + \section2 License Properties + + License properties are used to control the content and appearance of the + license prompt displayed when a user attempts to mount the resulting disk + image via Finder. + + \section2 Relevant File Tags + \target filetags-dmg + + The file tags determine how the tagged files are handled. + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"dmg.input"} + \li n/a + \li 1.9 + \li Tagged files are copied into the disk image. The + \l{dmg::}{sourceBase} property controls the destination directory + and hierarchy of copied files within the disk image. + \row + \li \c{"dmg.license.input"} + \li \c{*.txt}, \c{*.rtf}, \c{*.html}, \c{*.doc}, \c{*.docx}, \c{*.odt}, \c{*.xml}, + \c{*.webarchive} + \li 1.9 + \li Tagged files are converted into rich text and used for the license + prompt when mounting the DMG. + \row + \li \c{"icns"} + \li \c{*.icns} + \li 1.3 + \li The tagged file is added as the Apple Disk Image volume icon, which + will show up in the Finder as an overlay on the file icon. + \row + \li \c{"tiff"} + \li \c{*.tif}, \c{*.tiff} + \li 1.9 + \li The tagged file is used as the background image of the directory as + shown in Finder when the DMG file is mounted. + \endtable +*/ + +/*! + \qmlproperty string dmg::volumeName + + The name of the disk image that is displayed in Finder when the DMG is + mounted. + + \defaultvalue \l{Product::targetName}{product.targetName} +*/ + +/*! + \qmlproperty bool dmg::badgeVolumeIcon + + Whether to render the user-supplied icon (\l{filetags-dmg}{"icns"}) on top + of the default volume icon instead of using it directly. This generally + gives the disk image icon a better and more consistent appearance. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty string dmg::format + + The format to create the disk image in. + + Allowed values include but are not limited to \c{"UDZO"}, \c{"UDBZ"}, and + \c{"ULFO"}. + + \defaultvalue \c{"UDBZ"} +*/ + +/*! + \qmlproperty int dmg::compressionLevel + + The \c zlib, \c bzip2, or \c lzfse compression level for UDZO, UDBZ, or ULFO + disk images. + + \defaultvalue \c 9 in release mode, otherwise \c undefined. +*/ + +/*! + \qmlproperty string dmg::sourceBase + + The base directory of the files that are going to be embedded in the DMG + (\l{filetags-dmg}{"dmg.input"}). The source base directory + is omitted from the target directory path of the DMG directory. + + \defaultvalue The directory of the current file to be embedded, relative to + the product's source directory. +*/ + +/*! + \qmlproperty string dmg::backgroundColor + + The background color of the disk image as seen when mounted in Finder. + + For the full list of supported color names and formats, see the + \l{dmgbuild - Settings} documentation. + + For more information about how to use an image for the background instead, + see \l{filetags-dmg}{"tiff"}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int dmg::iconSize + + The width and height of the file icons as seen when the disk image is + mounted in Finder. + + \defaultvalue \c{128} +*/ + +/*! + \qmlproperty int dmg::windowX + + The x position of the Finder window that displays the disk image contents + when it is mounted. + + \defaultvalue \c{100} +*/ + +/*! + \qmlproperty int dmg::windowY + + The y position of the Finder window that displays the disk image contents + when it is mounted. + + \defaultvalue \c{100} +*/ + +/*! + \qmlproperty int dmg::windowWidth + + The width of the Finder window that displays the disk image contents + when it is mounted. + + \defaultvalue \c{640} +*/ + +/*! + \qmlproperty int dmg::windowHeight + + The height of the Finder window that displays the disk image contents when + it is mounted. + + \note The window height includes the height of the standard macOS title bar + (22 points). + + \defaultvalue \c{480} +*/ + +/*! + \qmlproperty list dmg::iconPositions + + A list of objects containing the \c{path}, \c{x}, and \c{y} properties, + which correspond to disk image-relative file paths and visual coordinates of + file icons in the disk image as seen when it is mounted in Finder. + + For example: + + \code + dmg.iconPositions: [ + {"path": "Applications", "x": 128, "y": 128}, + {"path": "Foo Bar.app", "x": 256, "y": 128} + ] + \endcode + + This property is useful for specifying the positions of files where you do + not have direct control over the corresponding \QBS artifact, or there is no + corresponding \QBS artifact (for example, "Foo Bar.app" is a directory, + which has no equivalent artifact in the build graph). + + For files to which you are directly applying the \l{filetags-dmg}{dmg.input} + file tag, you should use the \l{dmg::}{iconX} and \l{dmg::}{iconY} + properties instead. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int dmg::iconX + + The x position of the file icon in the Finder window that displayed the disk + image contents when it is mounted. + + This property is only useful with artifacts tagged \l{filetags-dmg} + {dmg.input}. It cannot be used at the product level to affect all files. + + If you do not have access to the artifact corresponding to the file whose + position you want to set, use the \l{dmg::}{iconPositions} property instead. + + \defaultvalue \c{windowWidth / 2} +*/ + +/*! + \qmlproperty int dmg::iconY + + The y position of the file icon in the Finder window that displayed the disk + image contents when it is mounted. + + This property is only useful with artifacts tagged \l{filetags-dmg} + {dmg.input}. It cannot be used at the product level to affect all files. + + If you do not have access to the artifact corresponding to the file whose + position you want to set, use the \l{dmg::}{iconPositions} property instead. + + \defaultvalue \c{windowHeight / 2} +*/ + +/*! + \qmlproperty string dmg::defaultLicenseLocale + + The locale of the default license to display when there is no license whose + locale matches the system locale. + + \defaultvalue \c{"en_US"} +*/ + +/*! + \qmlproperty string dmg::licenseLocale + + The locale of the license file. + + Defaults to a value guessed from the file path, specifically the base name + of any \c .lproj directory found in the file's path. If the locale could not + be determined from the file path and this property is not set, an error will + be emitted. + + This property is only useful with artifacts tagged \l{filetags-dmg} + {dmg.license.input}. It cannot be used at the product level to affect all + files. + + \defaultvalue Determined automatically. +*/ + +/*! + \qmlproperty string dmg::licenseLanguageName + + The name of the language associated with the license file, localized to that + language. + + This property is only useful with artifacts tagged \l{filetags-dmg} + {dmg.license.input}. It cannot be used at the product level to affect all + files. + + \defaultvalue \c{"English"} +*/ + +/*! + \qmlproperty string dmg::licenseAgreeButtonText + + The text shown on the \e Agree button associated with the license file, + localized to the value of \l{dmg::}{licenseLanguageName}. + + This property is only useful with artifacts tagged \l{filetags-dmg} + {dmg.license.input}. It cannot be used at the product level to affect all + files. + + \defaultvalue \c{"Agree"} +*/ + +/*! + \qmlproperty string dmg::licenseDisagreeButtonText + + The text shown on the \c Disagree button associated with the license file, + localized to the value of \l{dmg::}{licenseLanguageName}. + + This property is only useful with artifacts tagged \l{filetags-dmg} + {dmg.license.input}. It cannot be used at the product level to affect all + files. + + \defaultvalue \c{"Disagree"} +*/ + +/*! + \qmlproperty string dmg::licensePrintButtonText + + The text shown on the \c Print button associated with the license file, + localized to the value of \l{dmg::}{licenseLanguageName}. + + This property is only useful with artifacts tagged \l{filetags-dmg} + {dmg.license.input}. It cannot be used at the product level to affect all + files. + + \defaultvalue \c{"Print"} +*/ + +/*! + \qmlproperty string dmg::licenseSaveButtonText + + The text shown on the \e Save button associated with the license file, + localized to the value of \l{dmg::}{licenseLanguageName}. + + This property is only useful with artifacts tagged \l{filetags-dmg} + {dmg.license.input}. It cannot be used at the product level to affect all + files. + + \defaultvalue \c{"Save"} +*/ + +/*! + \qmlproperty string dmg::licenseInstructionText + + An instruction text associated with the license file that will be shown on + the license dialog, localized to the value of + \l{dmg::}{licenseLanguageName}. + + This property is only useful with artifacts tagged \l{filetags-dmg} + {dmg.license.input}. It cannot be used at the product level to affect all + files. + + \defaultvalue \c{"If you agree with the terms of this license, press + \"Agree\" to install the software. If you do not agree, press + \"Disagree\"."} +*/ + +/*! + \qmlproperty string dmg::dmgSuffix + + The file extension for disk images. + + This should not normally need to be changed. + + \defaultvalue \c{".dmg"} +*/ + +/*! + \qmlproperty string dmg::hdiutilPath + + The path to the \c hdiutil binary used to perform disk image related + operations. + + This should not normally need to be changed. + + \defaultvalue \c{"/usr/bin/hdiutil"} +*/ + +/*! + \qmlproperty string dmg::textutilPath + + The path to the \c textutil binary used to convert license agreement files + to rich text format. + + This should not normally need to be changed. + + \defaultvalue \c{"/usr/bin/textutil"} +*/ diff --git a/doc/reference/modules/exporter-pkgconfig-module.qdoc b/doc/reference/modules/exporter-pkgconfig-module.qdoc new file mode 100644 index 00000000..9489b462 --- /dev/null +++ b/doc/reference/modules/exporter-pkgconfig-module.qdoc @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + \qmltype Exporter.pkgconfig + \inqmlmodule QbsModules + \since Qbs 1.12 + + \brief Provides support for generating pkg-config files. + + The \c Exporter.pkgconfig module contains the properties and rules to create a + \l{https://www.freedesktop.org/wiki/Software/pkg-config}{pkg-config} + metadata (\c{.pc}) file for a \l Product. + + By default, \QBS will attempt to derive some of the \c {.pc} file's contents from + the product's \l Export item. This behavior can be suppressed by setting the + \l autoDetect property to \c false. + + \section2 Relevant File Tags + \target filetags-exporter-pkgconfig + + \table + \header + \li Tag + \li Since + \li Description + \row + \li \c{"Exporter.pkgconfig.pc"} + \li 1.12.0 + \li This tag is attached to the generated \c{.pc} file. + \endtable +*/ + +/*! + \qmlproperty bool Exporter.pkgconfig::autoDetect + + If this property is enabled, then \QBS will try to derive various \c {.pc} file + entries from the contents of the product's \l Export item, including the dependencies + declared therein. Values for these fields can still be explicitly provided via the + respective properties, in which case they will be concatenated with the auto-detected ones. + If an exported dependency is known to correspond to a pkg-config module (either by + pulling in the \c{Exporter.pkgconfig} module or by appearing in the \l requiresEntry + property), it will end up in the \c Requires field of the \c{.pc} file, otherwise + its exported \l cpp properties will be collected for use in the \c Cflags and \c Libs fields. + The \l excludedDependencies property can be used to ignore specific products altogether. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty stringList Exporter.pkgconfig::cflagsEntry + + The value of the \c Cflags field in the \c {.pc} file. + + If \l autoDetect is enabled, then this value will be appended to the flags derived from + the product's \l Export item and the dependencies declared therein. + + \defaultvalue \c {[]} +*/ + +/*! + \qmlproperty stringList Exporter.pkgconfig::conflictsEntry + + The value of the \c Conflicts field in the \c {.pc} file. + + \defaultvalue \c {[]} +*/ + +/*! + \qmlproperty stringList Exporter.pkgconfig::excludedDependencies + + If \l autoDetect is enabled, the entries of this property will be matched against + the product's exported dependencies. In case such a dependency's name is present in the array, + \QBS will not traverse that dependency to collect entries for the + \c Cflags, \c Libs, \c Requires and \c {Requires.private} fields. + + This list must not contain any values that are present in \l requiresEntry. + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty string Exporter.pkgconfig::fileName + + The file name of the generated pkg-config metadata file. + + \defaultvalue \c {product.targetName + ".pc"} +*/ + +/*! + \qmlproperty stringList Exporter.pkgconfig::libsEntry + + The value of the \c Libs field in the \c {.pc} file. + + If \l autoDetect is enabled, then this value will be appended to the flags derived from + the product's \l Export item and the dependencies declared therein. + + \defaultvalue \c {[]} +*/ + +/*! + \qmlproperty stringList Exporter.pkgconfig::libsPrivateEntry + + The value of the \c Libs.Private field in the \c {.pc} file. + + \defaultvalue \c {[]} +*/ + +/*! + \qmlproperty string Exporter.pkgconfig::nameEntry + + The value of the \c Name field in the \c {.pc} file. + + \defaultvalue \c {product.name} +*/ + + +/*! + \qmlproperty stringList Exporter.pkgconfig::requiresEntry + + The value of the \c Requires field in the \c {.pc} file. + + If \l autoDetect is enabled, then those of the product's exported dependencies + that pull in the \c {Exporter.pkgconfig} module will also end up in the + \c Requires field, provided they are not listed in \l excludedDependencies. + + If an exported dependency matches an entry of this array, \QBS will not traverse that + dependency to gather \l cpp properties for use in the \l cflagsEntry and \l libsEntry values, + as pkg-config takes care of that itself. + + \defaultvalue \c {[]} +*/ + +/*! + \qmlproperty stringList Exporter.pkgconfig::requiresPrivateEntry + + The value of the \c Requires.private field in the \c {.pc} file. + + If \l autoDetect is enabled, then those of the product's non-exported dependencies + that pull in the \c {Exporter.pkgconfig} module will also end up in the + \c Requires.private field, provided they are not listed in \l excludedDependencies. + + \defaultvalue \c {[]} +*/ + +/*! + \qmlproperty var Exporter.pkgconfig::transformFunction + + A function with the signature + \c {function(product, moduleName, propertyName, value)}. + This can be useful to "fine-tune" property values if \l autoDetect is enabled, in case + they need amending for the purpose of pkg-config. + The \c product parameter represents the exporting product, the remaining parameters describe + the module property. The modified value of the module property shall be returned. + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty string Exporter.pkgconfig::urlEntry + + The value of the \c URL field in the \c {.pc} file. + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty string Exporter.pkgconfig::versionEntry + + The value of the \c Version field in the \c {.pc} file. + + \defaultvalue \c {product.version} +*/ + +/*! + \qmlproperty string Exporter.pkgconfig::customVariables + + Use this property to add arbitrary variable assignments into the \c .pc file. + The property is a map that will produce one assignment per entry. + The keys and values of the map represent the left-hand sides and right-hand + sides of these assignments, respectively. The values are strings that will + be written into the file verbatim. + + \nodefaultvalue +*/ diff --git a/doc/reference/modules/exporter-qbs-module.qdoc b/doc/reference/modules/exporter-qbs-module.qdoc new file mode 100644 index 00000000..610b02da --- /dev/null +++ b/doc/reference/modules/exporter-qbs-module.qdoc @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + \qmltype Exporter.qbs + \inqmlmodule QbsModules + \since Qbs 1.12 + + \brief Provides support for generating \QBS modules from products. + + The Exporter.qbs module contains the properties and rules to create a \QBS module from + the \l Export item of a \l Product. + + Such a module acts as your product's interface to other projects written in \QBS. + For instance, suppose you are creating a library. To allow other products both within + and outside of your project to make use of it, you would write something like the following: + \code + DynamicLibrary { + name: "mylibrary" + qbs.installPrefix: "/opt/mylibrary" + Depends { name: "Exporter.qbs" } + property string headersInstallDir: "include" + // ... + Group { + name: "API headers" + files: ["mylib.h"] + qbs.install: true + qbs.installDir: headersInstallDir + } + Group { + fileTagsFilter: ["Exporter.qbs.module"] + qbs.installDir: "qbs/modules/mylibrary" + } + Export { + Depends { name: "cpp" } + cpp.includePaths: [product.sourceDirectory] + prefixMapping: [{ + prefix: product.sourceDirectory, + replacement: FileInfo.joinPaths(qbs.installPrefix, product.headersInstallDir) + }] + } + } + \endcode + To build against this library, from within your project or any other one, you simply + declare a \l{Depends}{dependency}: + \code + Depends { name: "mylibrary" } + \endcode + + \section2 Relevant File Tags + \target filetags-exporter-qbs + + \table + \header + \li Tag + \li Since + \li Description + \row + \li \c{"Exporter.qbs.module"} + \li 1.12.0 + \li This tag is attached to the generated module file. + \endtable +*/ + +/*! + \qmlproperty stringList Exporter.qbs::artifactTypes + + Artifacts that match these tags will become \l{Group::filesAreTargets}{target artifacts} + of the generated module, so they can get picked up by the rules of depending products. + + If you do not specify anything here, all installed artifacts of the product are considered. + \note You can only limit the default set of artifacts by setting this property, but you + cannot extend it, because only artifacts that are to be installed are considered. + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty string Exporter.qbs::additionalContent + + The value of this property will be copied verbatim into the generated module. + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty stringList Exporter.qbs::excludedDependencies + + Normally, all \l Depends items in the \l Export item are copied into the generated + module. However, if there are any exported dependencies that only make sense for + products in the same project, then you can enter their names into this array, and they + will get filtered out. + \note You should strive to structure your projects in such a way that you do not need to set + this property. + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty string Exporter.qbs::fileName + + The name of the generated module file. + + \defaultvalue \c {product.targetName + ".qbs"} +*/ diff --git a/doc/reference/modules/freedesktop-module.qdoc b/doc/reference/modules/freedesktop-module.qdoc new file mode 100644 index 00000000..48c95a0e --- /dev/null +++ b/doc/reference/modules/freedesktop-module.qdoc @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Alberto Mardegan +** 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 + \qmltype freedesktop + \inqmlmodule QbsModules + \since Qbs 1.16 + + \brief Provides support for some freedesktop.org specifications. + + The \c freedesktop module contains properties and rules for building and working with + applications compliant to freedesktop.org specifications on UNIX platforms. + The areas in which this module can be of use include: + + \list + \li Creation or post-processing of + \l{https://specifications.freedesktop.org/desktop-entry-spec/latest/}{\c{.desktop} + files} + \li Installation of \l{https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html}{AppStream} metadata + \li Defining the location for + \l{https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#directory_layout} + {application icons} + \endlist + + This module is available on all platforms but is currently only useful on UNIX platforms. + + \section2 Example usage + \target example-freedesktop + + \code + Application { + ... + Depends { name: "freedesktop" } + + Group { + files: [ + ... + + // Declare the desktop and appstream files + "data/my-app.desktop", + "data/my-app.metainfo.xml", + ] + } + + // Add/change some fields in the desktop file + freedesktop.desktopKeys: ({ + 'Exec': FileInfo.joinPaths(qbs.installPrefix, + product.installDir, + product.targetName) + ' --argument', + 'X-Application-Version': product.version, + }) + + // Declare the application icon + Group { + files: "icons/my-application.svg" + fileTags: "freedesktop.appIcon" + } + } + \endcode + + \section2 Relevant File Tags + \target filetags-freedesktop + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"freedesktop.desktopfile_source"} + \li \c{*.desktop} + \li 1.16 + \li A source file with this tag is a \c{.desktop} file or fragment that + will be merged into the application's final \c{.desktop} file. + \row + \li \c{"freedesktop.desktopfile"} + \li - + \li 1.16 + \li Attached to the output artifacts of the rule that produces the + merged \c{.desktop} file. + \row + \li \c{"freedesktop.appstream"} + \li \c{*.metainfo.xml}, \c{*.appdata.xml} + \li 1.16 + \li Source files with this tag are AppStream metadata files which will + be installed under \l{qbs::}{installPrefix}\c{/share/metainfo} + \row + \li \c{"freedesktop.appIcon"} + \li - + \li 1.16 + \li Source files with this tag are application icons and will be + installed under \l{qbs::}{installPrefix}\c{/share/icons/hicolor/scalable/apps} + \endtable +*/ + +/*! + \qmlproperty string freedesktop::name + + The display name of the application which will be stored in the \c{.desktop} file. + + \defaultvalue \l{Product::name}{product.name} +*/ + +/*! + \qmlproperty var freedesktop::desktopKeys + + A dictionary of key-value pairs to add to the application's \c{.desktop} + file. + + The contents of this property will be aggregated with the values from any + \c{.desktop} file. If this property and any \c{.desktop} files contain the + same key, this property will take precedence. + + \nodefaultvalue +*/ diff --git a/doc/reference/modules/ib-module.qdoc b/doc/reference/modules/ib-module.qdoc new file mode 100644 index 00000000..d6f8d324 --- /dev/null +++ b/doc/reference/modules/ib-module.qdoc @@ -0,0 +1,282 @@ +/**************************************************************************** +** +** 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 + \qmltype ib + \inqmlmodule QbsModules + \since Qbs 1.1 + + \brief Provides support for Apple Interface Builder and related tools and file types. + + The \c ib module contains properties and rules for building Interface Builder documents, + storyboards, asset catalogs, and icon sets. + + This module is only available on Apple platforms. + + \section2 Relevant File Tags + \target filetags-ib + + The file tags determine how the tagged directories and files are handled. + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"assetcatalog"} + \li \c{*.xcassets} + \li 1.3 + \li Tagged directories are compiled into compiled asset catalog + archives (\c .car files) on supported platforms, or a collection of + loose resource files. The resulting files will be automatically + tagged \l{filetags-bundle}{bundle.input}. If the current product is + a bundle, the files will be included in the bundle's resources + directory. + \row + \li \c{"iconset"} + \li \c{*.iconset} + \li 1.3 + \li Tagged directories are compiled into Apple ICNS files. The resulting + files will be automatically tagged \l{filetags-bundle} + {bundle.input}. If the current product is a bundle, the files will + be included in the bundle's resources directory. + \row + \li \c{"nib"} + \li \c{*.nib}, \c{*.xib} + \li 1.1 + \li Tagged "NIB" and "XIB" files will be compiled. The output may be a + flat file or a bundle (directory structure). The resulting + files will be automatically tagged \l{filetags-bundle} + {bundle.input}. If the current product is a bundle, the files will + be included in the bundle's resources directory. + \row + \li \c{"png"} + \li \c{*.png} + \li 1.9 + \li Tagged PNG files may be converted into multi-resolution TIFFs. + Source files should adopt the \l{Adopt the @2x Naming Convention} + {@2x naming convention} in order to be properly grouped. The + resulting files will be given the \l{filetags-dmg}{tiff} file tag. + \row + \li \c{"storyboard"} + \li \c{*.storyboard} + \li 1.3 + \li Tagged storyboard files will be compiled. The output may be a flat + file or a bundle (directory structure). The resulting + files will be automatically tagged \l{filetags-bundle} + {bundle.input}. If the current product is a bundle, the files will + be included in the bundle's resources directory. + \endtable +*/ + +/*! + \qmlproperty bool ib::warnings + + Whether to print warnings when compiling. + + Does not apply to icon sets. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty bool ib::errors + + Whether to print errors when compiling. + + Does not apply to icon sets. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty bool ib::notices + + Whether to print notifications when compiling. + + Does not apply to icon sets. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty stringList ib::flags + + Additional flags to pass to the underlying tool (\c ibtool, \c actool, + \c iconutil). + + \nodefaultvalue + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty string ib::ibtoolName + \since Qbs 1.3 + + The name of the \c ibtool binary used to compile NIBs and storyboards. + + This property should not normally need to be changed. + + \defaultvalue \c{"ibtool"} +*/ + +/*! + \qmlproperty string ib::ibtoolPath + \since Qbs 1.3 + + The directory where the \c ibtool binary is located. + + This property should not normally need to be changed. + + \defaultvalue \c{ibtoolName} +*/ + +/*! + \qmlproperty bool ib::flatten + + Compiles XIBs and storyboards into flattened (non-wrapper) files. + + Set to \c{false} to preserve editability of the resulting NIB and storyboard + bundles in Interface Builder. + + This property should not normally need to be changed. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty string ib::module + \since Qbs 1.3 + + Sets the name of the module that the NIB or storyboard is a part of. + + Requires Xcode 6 or newer. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool ib::autoActivateCustomFonts + \since Qbs 1.3 + + Instructs the \c ibtool compiler to add custom fonts to the application's + \c Info.plist when compiling XIBs and storyboards, which will cause the + fonts to activate upon application launch. + + Requires Xcode 6 or newer. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty string ib::actoolName + \since Qbs 1.3 + + The name of the \c actool binary used to compile asset catalogs. + + This property should not normally need to be changed. + + \defaultvalue \c{"actool"} +*/ + +/*! + \qmlproperty string ib::actoolPath + \since Qbs 1.3 + + The directory where the \c actool binary is located. + + This property should not normally need to be changed. + + \defaultvalue \c{actoolName} +*/ + +/*! + \qmlproperty string ib::appIconName + \since Qbs 1.3 + + The name of the resource in the asset catalog that will be used as the + application's icon. + + Used to generate the partial \c Info.plist which will be merged into the + resulting app. + + If this property is \c{undefined}, no application icon will be specified. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string ib::launchImageName + \since Qbs 1.3 + + The name of the resource in the asset catalog that will be used as the + application's launch image. + + Used to generate the partial \c Info.plist which will be merged into the + resulting app. + + If this property is \c{undefined}, no launch image will be specified. + + This property is specific to iOS. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool ib::compressPngs + \since Qbs 1.3 + + Whether to compress PNG image files when building asset catalogs. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty string ib::iconutilName + \since Qbs 1.3 + + The name of the \c iconutil binary used to compile icon sets. + + This property should not normally need to be changed. + + \defaultvalue \c{"iconutil"} +*/ + +/*! + \qmlproperty string ib::iconutilPath + \since Qbs 1.3 + + The directory where the \c iconutil binary is located. + + This property should not normally need to be changed. + + \defaultvalue \c{iconutilName} +*/ diff --git a/doc/reference/modules/ico-module.qdoc b/doc/reference/modules/ico-module.qdoc new file mode 100644 index 00000000..ccff608e --- /dev/null +++ b/doc/reference/modules/ico-module.qdoc @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** 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 + \qmltype ico + \inqmlmodule QbsModules + \since Qbs 1.11 + + \brief Provides support for building ICO (.ico) and CUR (.cur) files. + + The \c ico module contains properties and rules for building + Microsoft Windows Icon (.ico) and Microsoft Windows Cursor (.cur) files. + + \note This module relies on the \c icotool command-line tool from the + \c icotools package, which must be installed separately and found in the + \c PATH or an appropriate system binaries directory for your system (for + example, \c /usr/bin or \c /usr/local/bin). + + \section2 Relevant File Tags + \target filetags-ico + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"cur"} + \li n/a + \li 1.11 + \li The rule that creates cursor (.cur) files + attaches this tag to its output artifact. + \row + \li \c{"ico"} + \li n/a + \li 1.11 + \li The rule that creates icon (.ico) files + attaches this tag to its output artifact. + \row + \li \c{"png"} + \li \c{"*.png"} + \li 1.11 + \li Source files with this tag indicate PNG files + which are used as inputs to create icon or cursor files. + \endtable +*/ + +/*! + \qmlproperty int ico::cursorHotspotX + + The cursor hotspot x coordinate. Only relevant when building .cur files. + + This property must be set individually for each input PNG file. + + \note \c icoutils version 0.32 or greater is required to set the + hotspot in cursor files with multiple images. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int ico::cursorHotspotY + + The cursor hotspot y coordinate. Only relevant when building .cur files. + + This property must be set individually for each input PNG file. + + \note \c icoutils version 0.32 or greater is required to set the + hotspot in cursor files with multiple images. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool ico::raw + + Whether to store the input file as a raw PNG, as supported in Windows Vista. + + Only relevant when building .ico files. + + This property must be set individually for each input PNG file. + + It is recommended to set this value only for icon sizes larger than or equal + to 256x256. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty string ico::icotoolFilePath + + The full path of the \c icotool binary. + + This property should not normally need to be changed if \QBS was able to + automatically detect it. + + \defaultvalue Determined automatically. +*/ diff --git a/doc/reference/modules/innosetup-module.qdoc b/doc/reference/modules/innosetup-module.qdoc new file mode 100644 index 00000000..0f61275f --- /dev/null +++ b/doc/reference/modules/innosetup-module.qdoc @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** 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 + \qmltype innosetup + \inqmlmodule QbsModules + \since Qbs 1.7 + + \brief Provides Inno Setup support. + + The \c innosetup module contains properties and rules for building EXE + setup packages with \l{Inno Setup}. Inno Setup 5 and above are supported. + + \note A typical Inno Setup Script includes an \c OutputBaseFilename command + to set the filename of the generated installer executable. However, \QBS + overrides any \c OutputBaseFilename commands found in the script by passing + the \c /F option to the ISCC compiler, and therefore, you must use + the \l{Product::targetName}{Product.targetName} property to set the + filename. \QBS also overrides any \c Output commands by passing the \c /O + option to the ISCC compiler. + + \section2 Relevant File Tags + \target filetags-innosetup + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"innosetup.iss"} + \li \c{"*.iss"} + \li 1.7 + \li Source files with this tag identify Inno Setup Script files, which serve as inputs + to a rule invoking the Inno Setup Script Compiler. + \row + \li \c{"innosetup.exe"} + \li n/a + \li 1.7 + \li The rule that creates Inno Setup executable files attaches this tag + (as well as the \l{filetags-cpp}{"application"} tag) to its output + artifact. + \endtable +*/ + +/*! + \qmlproperty stringList innosetup::defines + + A list of preprocessor macros that get passed to the compiler. + + To set macro values, use the following syntax: + + \badcode + innosetup.defines: ["USE_COLORS=1", 'COLOR_STR="blanched almond"'] + \endcode + + \nodefaultvalue +*/ + +/*! + \qmlproperty pathList innosetup::includePaths + + A list of include paths. + + Relative paths are considered to be relative to the \c .qbs product file + they are used in. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool innosetup::verboseOutput + + Whether to display verbose output from the Inno Setup compiler. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty stringList innosetup::compilerFlags + + A list of additional flags for the Inno Setup compiler. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string innosetup::version + + The Inno Setup version. + + Consists of three numbers separated by dots, for instance \c {"5.5.9"}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty int innosetup::versionMajor + + The Inno Setup major version. + + \defaultvalue \c{versionParts[0]} +*/ + +/*! + \qmlproperty int innosetup::versionMinor + + The Inno Setup minor version. + + \defaultvalue \c{versionParts[1]} +*/ + +/*! + \qmlproperty list innosetup::versionParts + + The Inno Setup version as a list. + + For instance, Inno Setup version 5.5.9 would correspond to a value of + \c[5, 5, 9]. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty int innosetup::versionPatch + + The Inno Setup patch level. + + \defaultvalue \c{versionParts[2]} +*/ + +/*! + \qmlproperty path innosetup::toolchainInstallPath + + The Inno Setup installation directory. + + Determined by searching the registry for the latest version. + + This property should not normally need to be changed. + + \defaultvalue Determined automatically. +*/ + +/*! + \qmlproperty string innosetup::compilerName + + The name of the compiler binary. + + This property should not normally need to be changed. + + \defaultvalue \c{"ISCC.exe"} +*/ + +/*! + \qmlproperty string innosetup::compilerPath + + The full path of the compiler binary. + + This property should not normally need to be changed. + + \defaultvalue \c{compilerName} +*/ diff --git a/doc/reference/modules/java-module.qdoc b/doc/reference/modules/java-module.qdoc new file mode 100644 index 00000000..28057e08 --- /dev/null +++ b/doc/reference/modules/java-module.qdoc @@ -0,0 +1,245 @@ +/**************************************************************************** +** +** 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 + \qmltype java + \inqmlmodule QbsModules + \since Qbs 1.4 + + \brief Provides Java support. + + The \c java module contains the properties and rules for building Java projects. + + \section2 Relevant File Tags + \target filetags-java + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"java.class"} + \li - + \li 1.4 + \li This tag is attached to the output artifacts of the rule that runs the + \c javac tool. + \row + \li \c{"java.jar"} + \li - + \li 1.4 + \li This tag is attached to the output artifacts of the rule that runs the + \c jar tool. + \row + \li \c{"java.java"} + \li \c{*.java} + \li 1.4 + \li Source files with this tag serve as inputs to the rule running the \c javac tool. + \row + \li \c{"java.manifest"} + \li \c{*.mf} + \li 1.8 + \li The contents of files with this tag will be aggregated with the + values in \l{java::}{manifest}. + \endtable +*/ + +/*! + \qmlproperty stringList java::additionalClassPaths + + The locations beside this product's class output path to consider when + compiling. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList java::additionalCompilerFlags + + A list of compiler flags not covered by any of the properties in this + module. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList java::additionalJarFlags + + A list of archiver flags not covered by any of the properties in this + module. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList java::bootClassPaths + + A list of non-standard bootstrap class files. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string java::compilerFilePath + + The command to invoke when compiling Java sources. + + \defaultvalue \l compilerName, prefixed by \l jdkPath if it is defined. +*/ + +/*! + \qmlproperty string java::compilerName + + The file name of the Java compiler. + + \defaultvalue \c{"javac"} +*/ + +/*! + \qmlproperty bool java::enableWarnings + + Whether warnings are emitted when compiling Java sources. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty string java::interpreterFilePath + + The command to invoke when executing Java code. + + \defaultvalue \l interpreterName, prefixed by \l jdkPath if it is defined. +*/ + +/*! + \qmlproperty string java::interpreterName + + The file name of the Java interpreter. + + \defaultvalue \c{"java"} +*/ + +/*! + \qmlproperty string java::jarFilePath + + The command to run when creating or extracting \c jar files. + + \defaultvalue \l jarName, prefixed by \l jdkPath if it is defined. +*/ + +/*! + \qmlproperty string java::jarName + + The file name of the \c jar tool. + + \defaultvalue \c{"jar"} +*/ + +/*! + \qmlproperty pathList java::jdkIncludePaths + \since Qbs 1.4.1 + + A list of include paths for native header files. + + Applications using JNI to interface with native code should add these paths + to \l{cpp::includePaths}{cpp.includePaths}. + + \defaultvalue Determined automatically. +*/ + +/*! + \qmlproperty string java::jdkPath + + The base path of the Java Development Kit (JDK). + + This is equivalent to the \c JAVA_HOME environment variable, and by default + will be determined automatically from one of the following: + + \list + \li \c JAVA_HOME environment variable (all platforms) + \li Registry (Windows) + \li \c java_home tool (macOS) + \li Known JDK paths (other Unix platforms) + \endlist + + \defaultvalue Determined automatically. +*/ + +/*! + \qmlproperty string java::languageVersion + + The Java language version to interpret source code as. + + If left undefined, the compiler will use its default. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string java::runtimeVersion + + The version of the Java runtime to generate compatible bytecode for. + + If left undefined, the compiler will use its default. + + \nodefaultvalue +*/ + +/*! + \qmlproperty var java::manifest + \since Qbs 1.4.2 + + The properties to add to the manifest file when building a JAR. + + The contents of this property will be aggregated with the values from any + files tagged \l{filetags-java}{"java.manifest"}. + + If this property and a manifest file contain the same key, this property + will take precedence. If left undefined, this property will not be taken + into account. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList java::manifestClassPath + + A list of entries to add to the manifest's Class-Path when building a JAR. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool java::warningsAsErrors + + If this property is \c true, the compiler will abort where it would normally + emit a warning. + + \defaultvalue \c false +*/ diff --git a/doc/reference/modules/lexyacc-module.qdoc b/doc/reference/modules/lexyacc-module.qdoc new file mode 100644 index 00000000..2d511764 --- /dev/null +++ b/doc/reference/modules/lexyacc-module.qdoc @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** 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 + \qmltype lex_yacc + \inqmlmodule QbsModules + \since Qbs 1.6 + + \brief Provides support for the \c lex and \c yacc tools. + + The \c lex_yacc module allows you to create scanners and parsers via the POSIX tools \c lex + and \c yacc, respectively. These tools are closely related and share a number of properties, + which is why they are represented by a single module. + + \section2 Relevant File Tags + \target filetags-lexyacc + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"lex.input"} + \li \c{*.l} + \li 1.6 + \li Source files with this tag serve as inputs to the \c lex tool. + \row + \li \c{"yacc.input"} + \li \c{*.y} + \li 1.6 + \li Source files with this tag serve as inputs to the \c yacc tool. + \endtable +*/ + +/*! + \qmlproperty bool lex_yacc::enableCompilerWarnings + \since Qbs 1.8 + + Whether compiler warnings are displayed. + + Because \c lex and \c yacc are known to produce files that will trigger + compiler warnings, such warnings are suppressed by default. Set this + property to \c true if you want to see them. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty string lex_yacc::lexBinary + + The file path of the \c lex tool. + + \defaultvalue \c{"lex"} +*/ + +/*! + \qmlproperty stringList lex_yacc::lexFlags + + Additional command-line options for the \c lex tool. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty string lex_yacc::lexOutputFilePath + + The output file for the \c lex tool. + + This corresponds to \c{%option outfile} in the .l file. + If \c{%option outfile} is set in the .l file then this property is ignored. + + \defaultvalue \c undefined + \since 1.12 +*/ + +/*! + \qmlproperty string lex_yacc::outputTag + + The file tag for the generated scanner and parser sources. + + Use \l{filetags-cpp}{"cpp"} if you want to use a C++ compiler on them. + + \defaultvalue \l{filetags-cpp}{"c"} +*/ + +/*! + \qmlproperty bool lex_yacc::uniqueSymbolPrefix + + If this property is \c true, the prefix \c yy normally used for the + generated lexer and parser functions is replaced by the base name of the + file provided as input to \c lex and \c yacc, respectively. + + Enable this property if you want to use more than one lexer or parser in a + single product. + + \note Enabling this property requires that the associated lexer and scanner + source files have the same base name. It also assumes a variant of \c lex + that supports the non-POSIX option \c{-P}, such as \c flex. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty string lex_yacc::yaccBinary + + The file path of the \c yacc tool. + + \defaultvalue \c{"yacc"} +*/ + +/*! + \qmlproperty stringList lex_yacc::yaccFlags + + Additional command-line options for the \c yacc tool. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty string lex_yacc::yaccOutputFilePath + + Main output file for the \c yacc tool. + + This corresponds to \c{%output} in the .y file. + If \c{%output} is set in the .y file then this property is ignored. + + \defaultvalue \c undefined + \since 1.12 +*/ diff --git a/doc/reference/modules/nodejs-module.qdoc b/doc/reference/modules/nodejs-module.qdoc new file mode 100644 index 00000000..0b56ba63 --- /dev/null +++ b/doc/reference/modules/nodejs-module.qdoc @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** 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 + \qmltype nodejs + \inqmlmodule QbsModules + \since Qbs 1.3 + + \brief Provides Node.js support. + + The \c nodejs module contains support for running \l{Node.js} applications + from \QBS. +*/ + +/*! + \qmlproperty path Node.js::applicationFile + + The input JavaScript file whose corresponding output will be executed when + running the Node.js application. If this property is \c{undefined}, the + product will not be runnable. + + \nodefaultvalue +*/ diff --git a/doc/reference/modules/nsis-module.qdoc b/doc/reference/modules/nsis-module.qdoc new file mode 100644 index 00000000..359fec46 --- /dev/null +++ b/doc/reference/modules/nsis-module.qdoc @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** 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 + \qmltype nsis + \inqmlmodule QbsModules + \since Qbs 1.2 + + \brief Provides Nullsoft Scriptable Install System support. + + The \c nsis module contains properties and rules for building EXE installers + for Windows using the Nullsoft Scriptable Install System (NSIS). + + This module is available on all platforms. + + \note A typical NSIS script includes an \c OutFile command to set the + filename of the generated installer executable. However, \QBS overrides any + \c OutFile commands found in the script, and therefore, you must use the + \l{Product::targetName}{product.targetName} property to set the filename. + + \section2 Relevant File Tags + \target filetags-nsis + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"nsh"} + \li \c{*.nsh} + \li 1.2 + \li This tag is attached to NSIS header files. + \row + \li \c{"nsi"} + \li \c{*.nsi} + \li 1.2 + \li This tag is attached to NSIS script files. + \row + \li \c{"nsissetup"} + \li - + \li 1.2 + \li The rule that creates the NSIS setup executable attaches this tag to its output + artifact. + \endtable +*/ + +/*! + \qmlproperty stringList nsis::defines + + A list of preprocessor macros that get passed to the compiler. + + To set macro values, use the following syntax: + + \badcode + cpp.defines: ["USE_COLORS=1", 'COLOR_STR="blanched almond"'] + \endcode + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool nsis::disableConfig + + Whether to exclude \c nsisconf.nsh. + + Generally, you do not need to set this property. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty bool nsis::enableQbsDefines + + Whether to define preprocessor macros corresponding to the values from the + \l{Project}{project} and \l{Product}{product} objects. + + When building a 64-bit package, the preprocessor variable \c{Win64} will + also be defined. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty string nsis::warningLevel + + The severity of the warnings to emit. The higher the level, the more + warnings will be shown. + + The levels \c{none}, \c{errors}, \c{warnings}, \c{info}, and \c{all} + correspond to the NSIS verbosity levels 0 through 4, inclusive. \c{normal} + corresponds to the default level. + + \defaultvalue \c{"normal"} +*/ + +/*! + \qmlproperty stringList nsis::compilerFlags + + A list of additional flags for the NSIS compiler. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string nsis::compressor + + The compression algorithm used to compress files and data in the installer. + + Setting this property overrides any \c SetCompressor command in the NSI file + being compiled. + + Possible values include: \c{"default"}, \c{"zlib"}, \c{"zlib-solid"}, + \c{"bzip2"}, \c{"bzip2-solid"}, \c{"lzma"}, \c{"lzma-solid"}. + + \defaultvalue \c{"default"} +*/ + +/*! + \qmlproperty string nsis::version + + The NSIS version. Consists of four numbers separated by dots. For example, + \c "2.46.0.0". + + \nodefaultvalue +*/ + +/*! + \qmlproperty int nsis::versionMajor + + The NSIS major version. + + \defaultvalue \c{versionParts[0]} +*/ + +/*! + \qmlproperty int nsis::versionMinor + + The NSIS minor version. + + \defaultvalue \c{versionParts[1]} +*/ + +/*! + \qmlproperty list nsis::versionParts + + The NSIS version as a list. + + For example, the NSIS version 2.46.0.0 would correspond to a value of + \c[2, 46, 0, 0]. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty int nsis::versionPatch + + The NSIS patch level. + + \defaultvalue \c{versionParts[2]} +*/ + +/*! + \qmlproperty int nsis::versionBuild + + The fourth NSIS version number component. + + \defaultvalue \c{versionParts[3]} +*/ + +/*! + \qmlproperty path nsis::toolchainInstallPath + + The NSIS installation directory. + + Determined by searching from the known registry keys and known installation + paths until a match is found. + + This property should not normally need to be changed. + + \defaultvalue Determined automatically. +*/ + +/*! + \qmlproperty string nsis::compilerName + + The name of the compiler binary. + + This property should not normally need to be changed. + + \defaultvalue \c{"makensis"} +*/ + +/*! + \qmlproperty string nsis::compilerPath + + The directory where the compiler binary is located. + + This property should not normally need to be changed. + + \defaultvalue \l{nsis::}{compilerName} +*/ diff --git a/doc/reference/modules/pkgconfig-module.qdoc b/doc/reference/modules/pkgconfig-module.qdoc new file mode 100644 index 00000000..f3030889 --- /dev/null +++ b/doc/reference/modules/pkgconfig-module.qdoc @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 + \qmltype pkgconfig + \inqmlmodule QbsModules + \since 1.13 + + \brief Allows to configure the pkg-config tool. + + The \c pkgconfig module is used to fine-tune the behavior of the \c {pkg-config} tool, + which is + \l{How do I build against libraries that provide pkg-config files?}{potentially employed} + when looking up dependencies. +*/ + +/*! + \qmlproperty string pkgconfig::executableFilePath + + The path to the \c {pkg-config} executable. + + \defaultvalue auto-detected +*/ + +/*! + \qmlproperty stringList pkgconfig::libDirs + + Set this if you need to overwrite the default search directories. The values + given here will be forwarded to the tool via the \c PKG_CONFIG_LIBDIR environment + variable. + \note You do not need to set this for cross-compilation in order to point + \c {pkg-config} to the sysroot. \QBS does that for you. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool pkgconfig::staticMode + + If this property is \c true, then calls to \c{pkg-config} will include the + \c{--static} option. Set this if your product is to be linked statically. + + \defaultvalue \c false +*/ + +/*! + \qmlproperty path pkgconfig::sysroot + + This property controls the value of the \l{PkgConfigProbe::sysroot}{PkgConfigProbe.sysroot} + property. + + Set this property if you need to overwrite the default search sysroot path used by + \c pkg-config. + + This can be useful if \c pkg-config files are located in the directory other than qbs.sysroot. + This is the case on macOS platform - all XCode profiles are sysrooted to the SDK + directory, but \c pkg-config is typically intalled using Brew and resides in the + \c /usr/local directory. + + Setting this property to \c undefined or empty (\c "") value will use pkg-config's default + search paths: + \code + qbs build modules.pkgconfig.sysroot:undefined + \endcode + + \defaultvalue \c "" on macOS, \c qbs.sysroot on other platforms +*/ diff --git a/doc/reference/modules/protobufcpp-module.qdoc b/doc/reference/modules/protobufcpp-module.qdoc new file mode 100644 index 00000000..146bb293 --- /dev/null +++ b/doc/reference/modules/protobufcpp-module.qdoc @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + \qmltype protobuf.cpp + \inqmlmodule QbsModules + \since Qbs 1.13 + + \brief Provides support for protocol buffers for the C++ language. + + The \c protobuf.cpp module provides support for generating C++ headers + and sources from proto definition files using the \l protoc tool. + + A simple qbs file that uses protobuf can be written as follows: + \code + CppApplication { + Depends { name: "protobuf.cpp" } + files: ["foo.proto", "main.cpp"] + } + \endcode + A generated header now can be included in the C++ sources: + \code + #include + + int main(int argc, char* argv[]) { + Foo bar; + bar.set_answer(42); + google::protobuf::ShutdownProtobufLibrary(); + return 0; + } + \endcode + + \section2 Relevant File Tags + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"protobuf.input"} + \li \c{*.proto} + \li 1.13.0 + \li Source files with this tag are considered inputs to the \c protoc compiler. + \row + \li \c{"protobuf.grpc"} + \li + \li 1.14.0 + \li Source files with this tag are considered as gRPC files. + \endtable +*/ + +/*! + \qmlproperty string protobuf.cpp::compilerName + + The name of the protoc binary. + + \since Qbs 1.17 + \defaultvalue \c "protoc" +*/ + +/*! + \qmlproperty string protobuf.cpp::compilerPath + + The path to the protoc binary. + + Use this property to override the auto-detected location. + + \since Qbs 1.17 + \defaultvalue \c auto-detected +*/ + +/*! + \qmlproperty string protobuf.cpp::grpcIncludePath + + The path where grpc++ headers are located. Set this property to override the + default location. + + \defaultvalue \c auto-detected +*/ + +/*! + \qmlproperty string protobuf.cpp::grpcLibraryPath + + The path where the grpc++ library is located. Set this property to override the + default location. + + \defaultvalue \c auto-detected +*/ + +/*! + \qmlproperty pathList protobuf.cpp::importPaths + + The list of imports that are passed to the \c protoc tool via the \c --proto_path option. + These imports should contain the proto files. They are used to determine + the relative structure of the generated files. + \note The paths are passed to \c protoc in the same order as specified in this property and + \c protoc output may differ depending on that order. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty string protobuf.cpp::includePath + + The path where protobuf C++ headers are located. Set this property to override the + default location. + + \defaultvalue \c auto-detected +*/ + +/*! + \qmlproperty string protobuf.cpp::libraryPath + + The path where the protobuf C++ library is located. Set this property to override the + default location. + + \defaultvalue \c auto-detected +*/ + +/*! + \qmlproperty bool protobuf.cpp::useGrpc + + Whether to use gRPC framework. + + Use this property to enable support for the modern open source high performance RPC + framework by Google, gRPC (\l{https://www.grpc.io}). + + A simple qbs file that uses grpc can be written as follows: + \code + CppApplication { + Depends { name: "protobuf.cpp" } + protobuf.cpp.useGrpc: true + files: ["main.cpp"] + Group { + files: "grpc.proto" + fileTags: "protobuf.grpc" + } + } + \endcode + + \note that \c protobuf.grpc tag should be assigned manually because gRPC uses same \c *.proto + files and \QBS can't detect whether to generate gRPC or \c protobuf. + + The following \c grpc.proto file... + \code + syntax = "proto3"; + + package Qbs; + + message Request { + string name = 1; + } + + message Response { + string name = 1; + } + + service Grpc { + rpc doWork(Request) returns (Response) {} + } + \endcode + + ...can be used in the C++ sources as follows: + \code + #include + + class Service final : public Qbs::Grpc::Service + { + grpc::Status doWork( + grpc::ServerContext* context, + const Qbs::Request* request, + Qbs::Response* reply) override + { + (void)context; + reply->set_name(request->name()); + return grpc::Status::OK; + } + }; + \endcode + + \defaultvalue \c false +*/ diff --git a/doc/reference/modules/protobufobjc-module.qdoc b/doc/reference/modules/protobufobjc-module.qdoc new file mode 100644 index 00000000..235118e6 --- /dev/null +++ b/doc/reference/modules/protobufobjc-module.qdoc @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + \qmltype protobuf.objc + \inqmlmodule QbsModules + \since Qbs 1.13 + + \brief Provides support for protocol buffers for the Objective-C language. + + The \c protobuf.objc module provides support for generating Objective-C + headers and sources from proto definition files using the \l protoc tool. + + \section2 Relevant File Tags + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"protobuf.input"} + \li \c{*.proto} + \li 1.13.0 + \li Source files with this tag are considered inputs to the \c protoc compiler. + \endtable +*/ + +/*! + \qmlproperty string protobuf.objc::compilerName + + The name of the protoc binary. + + \since Qbs 1.17 + \defaultvalue \c "protoc" +*/ + +/*! + \qmlproperty string protobuf.objc::compilerPath + + The path to the protoc binary. + + Use this property to override the auto-detected location. + + \since Qbs 1.17 + \defaultvalue \c auto-detected +*/ + +/*! + \qmlproperty pathList protobuf.objc::importPaths + + The list of imports that are passed to the \c protoc tool via the \c --proto_path option. + These imports should contain the proto files. They are used to determine + the relative structure of the generated files. + \note The paths are passed to \c protoc in the same order as specified in this property and + \c protoc output may differ depending on that order. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty string protobuf.objc::includePath + + The path where protobuf Objective-C headers are located. Set this property + to override the default location. + + \note If frameworkPath is specified, this property has no effect. + + \defaultvalue \c auto-detected +*/ + +/*! + \qmlproperty string protobuf.objc::libraryPath + + \note If frameworkPath is specified, this property has no effect. + + \defaultvalue \c auto-detected +*/ + +/*! + \qmlproperty string protobuf.objc::frameworkPath + + The path where \c Protobuf.framework is located. Set this property to override the + default location. + + \defaultvalue \c auto-detected +*/ diff --git a/doc/reference/modules/qbs-module.qdoc b/doc/reference/modules/qbs-module.qdoc new file mode 100644 index 00000000..874e006e --- /dev/null +++ b/doc/reference/modules/qbs-module.qdoc @@ -0,0 +1,903 @@ +/**************************************************************************** +** +** 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-modules.html + \qmltype qbs + \inqmlmodule QbsModules + \since Qbs 1.0 + + \brief Comprises general properties. + + The \c qbs module is implicitly loaded in every product. It contains properties of the current + build environment, independent of the used programming languages and toolchains. + + \section2 Installation Properties + + Typically, you use \l{Group} items to specify the target directories for + installing files. To install a group of files, set the \l{qbs::install} + {qbs.install} property of the Group to \c true. The value of + \l{qbs::installDir}{qbs.installDir} specifies the path to the directory + where the files will be installed. You can specify a base directory for all + installation directories as the value of \l{qbs::installPrefix} + {qbs.installPrefix}. + + For example, the following properties specify where a set of QML files and + an application executable are installed on Windows and Linux: + + \badcode + Application { + name: "myapp" + Group { + name: "Runtime resources" + files: "*.qml" + qbs.install: true + qbs.installDir: condition: qbs.targetOS.contains("unix") + ? "share/myapp" : "resources" + } + Group { + name: "The App itself" + fileTagsFilter: "application" + qbs.install: true + qbs.installDir: "bin" + } + qbs.installPrefix: condition: qbs.targetOS.contains("unix") + ? "usr/local" : "MyApp" + } + \endcode + + On Windows, the QML files will ultimately get installed in + \c{MyApp\resources} and the application executable in \c MyApp\bin, + for instance under \c{C:\Program Files}. On Linux, the QML files will be + installed in \c /usr/local/share/myapp and the executable in + \c /usr/local/bin. + + By default, \l{qbs::installRoot}{qbs.installRoot} creates the + \c install-root directory in the build directory for packaging the binaries + before installation. It is a temporary directory that is usually not + available when the application is run, and it should therefore not be set in + the project files. You can override the default value from the command line, + as described in \l{Installing Files}. + + \section2 Multiplexing Properties + + The following properties are specific to \l{Multiplexing} + {product multiplexing}: + + \list + \li \l{qbs::}{architectures} + \li \l{qbs::}{buildVariants} + \li \l{qbs::}{profiles} + \endlist +*/ + +/*! + \qmlproperty string qbs::configurationName + \since Qbs 1.6 + \readonly + + The name of the current build configuration. + + The build configuration is set via the command line parameter \c + config. For more information, see \l{build}{build}. + + \defaultvalue \c{"default"} +*/ + +/*! + \qmlproperty string qbs::buildVariant + + The name of the build variant for the current build. + + Possible values are \c{"debug"}, \c{"release"} and \c{"profiling"}. + A debug build usually contains additional debug symbols that are needed for + debugging the application and has optimizations turned off. A profiling + build usually contains debug symbols and has optimizations turned on. This + is useful for profiling tools or when you need to retain debug symbols + in a release build. A release build is a build without debug information and + with optimizations enabled. + + \defaultvalue \c{"release"} if + \l{qbs::configurationName}{qbs.configurationName} is \c{"release"}. Otherwise + \c{"debug"} +*/ + +/*! + \qmlproperty bool qbs::debugInformation + + Whether to generate debug information. + + \defaultvalue \c{true} if \l{qbs::buildVariant}{qbs.buildVariant} is \c{"debug"} or + \c{"profiling"}. Otherwise \c{false}. +*/ + +/*! + \qmlproperty bool qbs::enableDebugCode + + Whether to enable debug functionality in the product. Not to be confused + with generation of debug symbols or the code optimization level. + + The property changes the following things when enabled: + \list + \li Passes a flag to the Windows linker to link against a debug + Windows CRT (common runtime) library + (for example /MTd instead of /MT) + \endlist + + The property changes the following things when disabled: + \list + \li Passes the \c{NDEBUG} define to the compiler + \endlist + + Typically, this property is enabled for debug builds and disabled for + release or profiling builds. + + \defaultvalue \c{true} for debug builds, \c{false} otherwise. +*/ + +/*! + \qmlproperty string qbs::optimization + + The general type of optimization that should be performed by all toolchains. + + Allowed values are: + + \list + \li \c{"fast"} + \li \c{"none"} + \li \c{"small"} + \endlist + + \defaultvalue \c{"none"} if \l{qbs::buildVariant}{qbs.buildVariant} is \c{"debug"}. + Otherwise \c{"fast"}. +*/ + +/*! + \qmlproperty string qbs::targetPlatform + \since 1.11 + + The OS you want to build the project for. + + This property is typically set in a profile or for a particular product + where the target OS is always known (such as an Apple Watch app written in + native code). + + For example, a profile used for building for the iOS Simulator platform will have this + property set to the \c ios-simulator value: + \code + profiles.xcode-iphonesimulator.qbs.targetPlatform: "ios-simulator" + \endcode + + You should generally treat this property as \e{write-only} and avoid using + it to test for the current target OS. Instead, use the \l{qbs::}{targetOS} + property for conditionals. + + \section2 Relation between targetPlatform and targetOS + + This table describes the possible values and matching between the \c targetPlatform + and the \l{qbs::}{targetOS} properties: + \table + \header + \li Target Platform + \li Target OS + \row + \li \c{"aix"} + \li \c{["aix", "unix"]} + \row + \li \c{"android"} + \li \c{["android", "linux", "unix"]} + \row + \li \c{"freebsd"} + \li \c{["freebsd", "bsd", "unix"]} + \row + \li \c{"haiku"} + \li \c{["haiku"]} + \row + \li \c{"hpux"} + \li \c{["hpux", "unix"]} + \row + \li \c{"hurd"} + \li \c{["hurd", "unix"]} + \row + \li \c{"integrity"} + \li \c{["integrity", "unix"]} + \row + \li \c{"ios"} + \li \c{["ios", "darwin", "bsd", "unix"]} + \row + \li \c{"ios-simulator"} + \li \c{["ios-simulator", "ios", "darwin", "bsd", "unix"]} + \row + \li \c{"linux"} + \li \c{["linux", "unix"]} + \row + \li \c{"lynx"} + \li \c{["lynx"]} + \row + \li \c{"macos"} + \li \c{["macos", "darwin", "bsd", "unix"]} + \row + \li \c{"netbsd"} + \li \c{["netbsd", "bsd", "unix"]} + \row + \li \c{"openbsd"} + \li \c{["openbsd", "bsd", "unix"]} + \row + \li \c{"qnx"} + \li \c{["qnx", "unix"]} + \row + \li \c{"solaris"} + \li \c{["solaris", "unix"]} + \row + \li \c{"tvos"} + \li \c{["tvos", "darwin", "bsd", "unix"]} + \row + \li \c{"tvos-simulator"} + \li \c{["tvos-simulator", "tvos", "darwin", "bsd", "unix"]} + \row + \li \c{"unix"} + \li \c{["unix"]} + \row + \li \c{"vxworks"} + \li \c{["vxworks"]} + \row + \li \c{"watchos"} + \li \c{["watchos", "darwin", "bsd", "unix"]} + \row + \li \c{"watchos-simulator"} + \li \c{["watchos-simulator", "watchos", "darwin", "bsd", "unix"]} + \row + \li \c{"windows"} + \li \c{["windows"]} + \row + \li \c{"none"} + \li \c{["none"]} + \row + \li \c{undefined} + \li \c{[]} + \endtable + + \note The "none" value is usually used for a bare-metal platforms. + + \sa {Target Platforms} + + \defaultvalue \l{qbs::hostPlatform}{hostPlatform} +*/ + +/*! + \qmlproperty string qbs::architecture + + The target platform's processor architecture. + + \c{undefined} indicates that the target platform is architecture-independent + (for example the CLR or JVM). + + This property is typically set in a profile. + + Commonly used values are: \c{"x86"}, \c{"x86_64"}, and \c{"arm"}. + + \section2 Supported Processor Architectures + + This table describes the possible values of the \l{qbs::}{architecture} property: + \table + \header + \li Architecture + \li Description + \row + \li \c{"78k"} + \li 16- and 8-bit accumulator-based register-bank CISC architecture + microcontroller family manufactured by Renesas Electronics + \row + \li \c{"arm"} + \li 32-bit RISC architecture for computer processors + developed by Acorn RISC Machine + \note There are a lot of sub-variants of the ARM architecture. + Some specialized \QBS modules differentiate between them, + making use of values such as \c "armv7a". Please consult the + respective module-specific documentation for information + on what kind of value to use. + \row + \li \c{"arm64"} + \li 64-bit RISC architecture for computer processors + developed by Acorn RISC Machine + \row + \li \c{"avr"} + \li 8-bit modified Harvard RISC architecture microcontroller + family manufactured by Microchip Technology + \row + \li \c{"avr32"} + \li 32-bit RISC architecture microcontroller family developed by Atmel + \row + \li \c{"cr16"} + \li 16-bit compact RISC architecture microcontroller family + developed by National Semiconductor + \row + \li \c{"ia64"} + \li 64-bit ISA architecture of the Itanium family processors + developed by Intel + \row + \li \c{"m16c"} + \li 16-bit CISC microcontrollers featuring high ROM code + efficiency manufactured by Renesas Electronics + \row + \li \c{"m32c"} + \li 32- and 16-bit CISC microcontrollers featuring high ROM code + efficiency manufactured by Renesas Electronics + \row + \li \c{"m32r"} + \li 32-bit RISC microcontrollers for general industrial and + car-mounted systems, digital AV equipment, digital imaging + equipment manufactured by Renesas Electronics + \row + \li \c{"m68k"} + \li 16- and 32-bit CISC microprocessor, developed by Motorola + Semiconductor Products Sector, and further improved as ColdFire + architecture developed by NXP + \row + \li \c{"mcs51"} + \li 8-bit Harvard architecture microcontroller family developed by Intel + \row + \li \c{"mips"} + \li 32-bit RISC microprocessor without interlocked pipelined stages + architecture developed by MIPS Computer Systems + \row + \li \c{"mips64"} + \li 64-bit RISC microprocessor without interlocked pipelined stages + architecture developed by MIPS Computer Systems + \row + \li \c{"msp430"} + \li 16-bit mixed-signal microcontroller family manufactured + by Texas Instruments + \row + \li \c{"ppc"} + \li 32-bit RISC architecture processor family developed by + Apple–IBM–Motorola alliance + \row + \li \c{"ppc64"} + \li 64-bit RISC architecture processor family developed by + Apple–IBM–Motorola alliance + \row + \li \c{"r32c"} + \li 32-bit CISC microcontrollers with improved code efficiency + and processing performance manufactured by Renesas Electronics + \row + \li \c{"rh850"} + \li 32-bit automotive microcontroller family manufactured + by Renesas Electronics + \row + \li \c{"riscv"} + \li Open and free standard instruction set architecture based on established + RISC principles + \row + \li \c{"rl78"} + \li 16- and 8-bit accumulator-based register-bank CISC architecture + with 3-stage instruction pipelining microcontroller family manufactured + by Renesas Electronics + \row + \li \c{"rx"} + \li High performance 32-bit CISC microcontroller family manufactured + by Renesas Electronics + \row + \li \c{"s390x"} + \li 64- and 32-bit System/390 processor architecture developed by IBM + \row + \li \c{"sh"} + \li 32-bit RISC architecture processor family developed by Hitachi and + currently manufactured by Renesas Electronics + \row + \li \c{"sparc"} + \li 32-bit RISC architecture processor family developed by + Sun Microsystems and Fujitsu + \row + \li \c{"sparc64"} + \li 64-bit RISC architecture processor family developed by + Sun Microsystems and Fujitsu + \row + \li \c{"stm8"} + \li 8-bit microcontroller family manufactured by STMicroelectronics + \row + \li \c{"v850"} + \li 32-bit RISC microcontroller family manufactured by Renesas Electronics + \row + \li \c{"x86"} + \li 32-bit ISA architecture processor family developed by Intel + \row + \li \c{"x86_64"} + \li 64-bit ISA architecture processor family developed by AMD + \row + \li \c{"xtensa"} + \li 32-bit architecture with a compact 16- and 24-bit instruction set + developed by Tensilica + \endtable + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList qbs::toolchain + + Contains the list of string values describing the toolchain and toolchain + family that is used to build a project. + + This property is deduced from the \l{qbs::}{toolchainType} property and is typically + used to test for a particular toolchain or toolchain family in conditionals: + + \code + Properties { + // flags for GCC + condition: qbs.toolchain.contains("gcc") + cpp.commonCompilerFlags: ... + } + Properties { + // flags for MSVC + condition: qbs.toolchain.contains("msvc") + cpp.commonCompilerFlags: ... + } + \endcode + + Unlike \l{qbs::}{toolchainType}, which contains a single value, \c qbs.toolchain + is a string list which also contains the toolchain family. This allows to make + conditions and checks simpler. For example, instead of: + + \code + (qbs.toolchainType === "xcode" || qbs.toolchainType === "clang" || qbs.toolchainType === "gcc") + \endcode + + use: + + \code + qbs.toolchain.contains("gcc") + \endcode + + since XCode, GCC and Clang belong to the \c "gcc" family. + + \section2 Relation between toolchainType and toolchain + + This table describes the possible values and matching between the \l{qbs::}{toolchainType} + and the \c toolchain properties: + \table + \header + \li Toolchain Type + \li Toolchain + \row + \li \c{"clang"} + \li \c{["clang", "llvm", "gcc"]} + \row + \li \c{"clang-cl"} + \li \c{["clang-cl", "msvc"]} + \row + \li \c{"gcc"} + \li \c{["gcc"]} + \row + \li \c{"iar"} + \li \c{["iar"]} + \row + \li \c{"keil"} + \li \c{["keil"]} + \row + \li \c{"llvm"} + \li \c{["llvm", "gcc"]} + \row + \li \c{"mingw"} + \li \c{["mingw", "gcc"]} + \row + \li \c{"msvc"} + \li \c{["msvc"]} + \row + \li \c{"sdcc"} + \li \c{["sdcc"]} + \row + \li \c{"xcode"} + \li \c{["xcode", "clang", "llvm", "gcc"]} + \endtable + + \nodefaultvalue +*/ + +/*! + \qmlproperty string qbs::toolchainType + \since Qbs 1.11 + + The toolchain that is going to be used for this build. + + For example, to build a project using the \c "clang" toolchain, simply do + \code + qbs build qbs.toolchainType:clang + \endcode + + You should generally treat this property as \e{write-only} and avoid using + it to test for the current toolchain. Instead, use the \l{qbs::}{toolchain} + property for conditionals. + + Typical values include: \c{"gcc"}, \c{"clang"}, \c{"clang-cl"}, \c{"mingw"}, + \c{"msvc"}, and \c{"xcode"}. Also see \l{Relation between toolchainType and toolchain}. + + By default, \c qbs.toolchainType is automatically detected based on the + \l{qbs::}{targetOS} property: + \table + \header + \li Target OS + \li Toolchain + \row + \li \c{"darwin"} + \li \c{"xcode"} + \row + \li \c{"freebsd"} + \li \c{"clang"} + \row + \li \c{"haiku"} + \li \c{"gcc"} + \row + \li \c{"qnx"} + \li \c{"qcc"} + \row + \li \c{"unix"} + \li \c{"gcc"} + \row + \li \c{"vxworks"} + \li \c{"gcc"} + \row + \li \c{"windows"} + \li \c{"msvc"} + \endtable + + \defaultvalue Determined automatically. +*/ + +/*! + \qmlproperty string qbs::sysroot + + The \c sysroot of the target platform. + + This property is typically set in a profile for cross-compiling. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string qbs::pathListSeparator + + The platform-specific separator for a path list that is used in environment + variables or other contexts. + + \defaultvalue \c{";"} on Windows, \c{":"} on Unix. +*/ + +/*! + \qmlproperty string qbs::nullDevice + \since Qbs 1.4.2 + + The platform-specific file path corresponding to the null device. + + \defaultvalue \c{"NUL"} on Windows, \c{"/dev/null"} on Unix. +*/ + +/*! + \qmlproperty path qbs::shellPath + \since Qbs 1.5 + + The platform-specific file path corresponding to the command line + interpreter. + + On Windows, this is the path to \c{cmd.exe}, which is held in the + \c{COMSPEC} environment variable (typically, + \c{C:/Windows/System32/cmd.exe}), + On Unix-like platforms, this is \c{/bin/sh}. + + \defaultvalue \c{"%COMSPEC%"} on Windows, \c{"/bin/sh"} on Unix +*/ + +/*! + \qmlproperty string qbs::hostArchitecture + \since Qbs 1.16 + + Contains the host OS architecture. + + \defaultvalue Determined automatically. +*/ + +/*! + \qmlproperty stringList qbs::hostOS + + This property is set by \QBS internally and specifies the OS \QBS is running + on. + + The possible values for this property are the values of \l{qbs::}{targetOS}, + even though some of them may not be supported. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string qbs::hostOSVersion + \readonly + \since Qbs 1.2 + + The host operating system version. Currently, only defined for Windows and + Apple platforms. + + Consists of two or three numbers separated by dots. For example, \c "10.9" + or \c "6.3.9600". + + \nodefaultvalue +*/ + +/*! + \qmlproperty string qbs::hostOSBuildVersion + \readonly + \since Qbs 1.2 + + The host operating system's build version. Currently, only defined for + Windows and Apple platforms. + + On Windows, this is the 4 or 5 digit Windows build number and is equivalent + to \l{qbs::}{versionPatch}. On Apple platforms, this is a standard build + number in the Apple versioning scheme. For example, \c "13C64". + + \nodefaultvalue +*/ + +/*! + \qmlproperty int qbs::hostOSVersionMajor + \readonly + \since Qbs 1.2 + + The host operating system major version. + + \defaultvalue \c{hostOSVersionParts[0]} +*/ + +/*! + \qmlproperty int qbs::hostOSVersionMinor + \since Qbs 1.2 + + The host operating system minor version. + + \defaultvalue \c{hostOSVersionParts[1]} +*/ + +/*! + \qmlproperty list qbs::hostOSVersionParts + \readonly + \since Qbs 1.2 + + The host operating system version as a list. + + For example, Windows 8.1 (version 6.3.9600) would correspond to a value of + \c[6, 3, 9600]. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty int qbs::hostOSVersionPatch + \readonly + \since Qbs 1.2 + + The host operating system patch level. + + \defaultvalue \c{hostOSVersionParts[2]} +*/ + +/*! + \qmlproperty string qbs::hostPlatform + \since Qbs 1.11 + + Contains the host OS platform. + + \sa {qbs::hostArchitecture}{hostArchitecture} + + \defaultvalue Determined automatically. +*/ + +/*! + \qmlproperty stringList qbs::targetOS + \readonly + + Contains the list of string values describing the OS and OS family that is + used to build a project. + + This property is calculated based on the \l{qbs::}{targetPlatform} property and is typically + used to test for a particular OS or OS family in conditionals: + \code + Group { + // Includes all Unix-like platforms, such as: Linux, BSD, Apple platforms and others. + condition: qbs.targetOS.contains("unix") + files: ... + } + Group { + // Includes all Apple platforms, such as macOS, iOS, and iOS Simulator. + condition: qbs.targetOS.contains("darwin") + files: ... + } + Group { + // Includes only macOS + condition: qbs.targetOS.contains("macos") + files: ... + } + \endcode + Avoid using \l{qbs::}{targetPlatform} for this purpose. For example, instead of: + + \code + qbs.targetPlatform === "macos" || qbs.targetPlatform === "ios" || qbs.targetPlatform === "tvos" || qbs.targetPlatform === "watchos" + \endcode + + use + + \code + qbs.targetOS.contains("darwin") + + \endcode + + However, in some cases using \l{qbs::}{targetPlatform} would be acceptable, such as + when the resulting condition would be simpler while still being correct: + + \code + qbs.targetPlatform === "linux" + \endcode + + versus + \code + qbs.targetOS.contains("linux") && !qbs.targetOS.contains("android") + \endcode + + For the complete list of possible values, see + \l{Relation between targetPlatform and targetOS}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string qbs::version + \readonly + \since Qbs 1.4.1 + + The version number of \QBS as a string. For example, \c "1.4.1". +*/ + +/*! + \qmlproperty int qbs::versionMajor + \readonly + \since Qbs 1.4.1 + + The major version number of \QBS. +*/ + +/*! + \qmlproperty int qbs::versionMinor + \readonly + \since Qbs 1.4.1 + + The minor version number of \QBS. +*/ + +/*! + \qmlproperty int qbs::versionPatch + \readonly + \since Qbs 1.4.1 + + The patch version number of \QBS. +*/ + +/*! + \qmlproperty bool qbs::install + + Whether to install a certain set of files. + + This property is typically set in a \l{Group} item to mark a number of files + as installable. + + \note Artifacts for which this property is enabled automatically receive the + file tag \c "installable". This is useful for writing packaging-related + rules. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty string qbs::installSourceBase + \since Qbs 1.4 + + The base directory of the local files that are going to be installed. The + source base directory is omitted from the target directory path specified in + \l{qbs::}{installDir}. + + \defaultvalue The directory of the current file to be installed, relative to + the product's source directory. +*/ + +/*! + \qmlproperty string qbs::installDir + + The installation directory for the files of a \l{Product}{product} or a + \l{Group}. + + The value of this property is a path that is relative to \l{qbs::} + {installPrefix}. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string qbs::installPrefix + \since Qbs 1.1 + + The global installation prefix. It is implicitly prepended to all values of + \l{qbs::}{installDir}. + + The value of this property itself is relative to the \l{qbs::}{installRoot} + in the context of installation. + + \defaultvalue \c "/usr/local" on Unix, \c "" otherwise +*/ + +/*! + \qmlproperty string qbs::installRoot + \since Qbs 1.4 + + The global installation root. It is implicitly prepended to all values + of \l{qbs::}{installPrefix} in the context of installation. + + \note This property is fundamentally different from \l{qbs::}{installDir} + and \l{qbs::}{installPrefix} in that it must not be visible to the code + being built. In fact, the install root is often just a temporary location + used to package the binaries, which should therefore not assume they will be + in that location at run-time. For the same reason, this property + is usually not set from within project files. + + \defaultvalue \c{/install-root} +*/ + +/*! + \qmlproperty stringList qbs::architectures + \since Qbs 1.9 + + The architectures the product will be built for. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList qbs::buildVariants + \since Qbs 1.9 + + The build variants the product will be built for. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList qbs::profiles + \since Qbs 1.9 + + The profiles for which the product should be built. + + For each profile listed here, one instance of the product will be built + according to the properties set in the respective profile. + + \defaultvalue \l{Project::profile}{[project.profile]} +*/ diff --git a/doc/reference/modules/qnx-module.qdoc b/doc/reference/modules/qnx-module.qdoc new file mode 100644 index 00000000..d14f44b8 --- /dev/null +++ b/doc/reference/modules/qnx-module.qdoc @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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 + \qmltype qnx + \inqmlmodule QbsModules + \since Qbs 1.8 + + \brief Provides support for building QNX applications using the QNX SDK. + + The \c qnx module contains properties and rules for QNX development. + It has been tested with the QNX 6.5, 6.6, and 7.0 SDKs. +*/ + +/*! + \qmlproperty string qnx::sdkDir + + The SDK base directory. + + \defaultvalue Determined automatically if the QNX SDK is installed at one of + the standard locations, such as \c ~/qnx700, \c /opt/qnx700, or + \c C:\\qnx700. +*/ + +/*! + \qmlproperty string qnx::configurationDir + + The QNX configuration directory. + + Equivalent to the \c{QNX_CONFIGURATION} environment variable. + + \defaultvalue \c{~/.qnx} +*/ + +/*! + \qmlproperty string qnx::hostDir + + The QNX host directory. + + Equivalent to the \c{QNX_HOST} environment variable. + + You should not normally need to set this property as it will be set to an + appropriate value based on your host operating system and the installed + QNX SDK in \l{qnx::}{sdkDir}. + + \defaultvalue Determined automatically. +*/ + +/*! + \qmlproperty string qnx::targetDir + + The QNX target directory. + + Equivalent to the \c{QNX_TARGET} environment variable. + + You should not normally need to set this property as it will be set to an + appropriate value based on the installed QNX SDK in \l{qnx::}{sdkDir}. + + \defaultvalue Determined automatically. +*/ diff --git a/doc/reference/modules/qt-android_support-module.qdoc b/doc/reference/modules/qt-android_support-module.qdoc new file mode 100644 index 00000000..995f73ba --- /dev/null +++ b/doc/reference/modules/qt-android_support-module.qdoc @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + \qmltype Qt.android_support + \inqmlmodule QbsModules + \brief Provides Qt support for the Android platform. + + The \c Qt.android_support module provides the glue for \QBS' + Qt and Android support. + It is automatically pulled in by \c Qt.core, so you do not need to + add an explicit dependency to it in your product, unless you want + to set one of its properties. +*/ + +/*! + \qmlproperty bool Qt.android_support::useMinistro + + Whether or not to use the Ministro service. If this property is enabled, then + the Qt libraries required by your application as well as some other resources + will not be packaged into the APK file, but are expected to be present on the + device at run time. + + \defaultvalue \c false +*/ + +/*! + \qmlproperty string Qt.android_support::qmlRootDir + + The root directory of the product's QML files. This information is passed to + the \c androiddeployqt tool, which will use it to decide which resources to + include in the APK file. + + \defaultvalue \c product.sourceDirectory +*/ + +/*! + \qmlproperty stringList Qt.android_support::deploymentDependencies + + Use this property to completely override the Qt deployment dependencies + of your app. Corresponds to qmake's ANDROID_DEPLOYMENT_DEPENDENCIES. + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty stringList Qt.android_support::extraPlugins + + Additional non-asset files to be packaged. Corresponds to qmake's ANDROID_EXTRA_PLUGINS. + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty stringList Qt.android_support::extraLibs + + Additional libs to be packaged and loaded on start-up (mind the order). + Corresponds to qmake's ANDROID_EXTRA_LIBS. + + \defaultvalue \c undefined +*/ + +/*! + \qmlproperty bool Qt.android_support::verboseAndroidDeployQt + + Enable this property if you want verbose output from the + \c androiddeployqt tool. + + \defaultvalue \c false +*/ diff --git a/doc/reference/modules/qt-core-module.qdoc b/doc/reference/modules/qt-core-module.qdoc new file mode 100644 index 00000000..35190c68 --- /dev/null +++ b/doc/reference/modules/qt-core-module.qdoc @@ -0,0 +1,471 @@ +/**************************************************************************** +** +** 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 + \qmltype Qt.core + \inqmlmodule QbsModules + \brief Provides Qt Core support. + + All other Qt modules have a dependency on the Qt.core module, and therefore + you do not need to list it in your dependencies if you depend on at least + one other \l{Qt Modules}{Qt module}. + + Some of the Qt.core properties only need to be defined if the respective + installation of Qt was built in some unusual way, for instance by setting + non-default \c configure flags. + + \section2 Relevant File Tags + \target filetags-qtcore + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"qch"} + \li n/a + \li 1.1 + \li This tag is attached to the output artifacts of the rule that runs the + \c qhelpgenerator tool. + \row + \li \c{"qdoc"} + \li \c{*.qdoc} + \li 1.1 + \li Source files with this tag trigger a re-execution of the rule running the \c qdoc + tool when their timestamp changes. + \row + \li \c{"qdocconf"} + \li \c{*.qdocconf} + \li 1.1 + \li Source files with this tag trigger a re-execution of the rule running the \c qdoc + tool when their timestamp changes. + \row + \li \c{"qdocconf-main"} + \li - + \li 1.1 + \li Source files with this tag serve as inputs to the rule running the \c qdoc tool. + \row + \li \c{"qdoc-output"} + \li n/a + \li 1.5 + \li Use this tag to match all \c qdoc outputs, for instance in a \l{Group} + using the \l{Group::fileTagsFilter}{group.fileTagsFilter} property. + \row + \li \c{"qhp"} + \li \c{*.qhp} + \li 1.1 + \li Files with this tag serve as inputs to the rule running the \c qhelpgenerator tool. + Such files are created by \c qdoc, but can also appear as source files. + \row + \li \c{"qm"} + \li n/a + \li 1.1 + \li This tag is attached to the output artifacts of the rule that runs the \c lrelease tool. + \row + \li \c{"qrc"} + \li \c{*.qrc} + \li 1.0 + \li Files with this tag serve as inputs to the rule running the \c rcc tool. + \row + \li \c{"qt_plugin_metadata"} + \li - + \li 1.0 + \li Source files with this tag trigger a re-execution of the rule running the \c moc + tool when their timestamp changes. + \row + \li \c{"qt.core.metatypes"} + \li n/a + \li 1.16 + \li This tag is attached to the JSON files that are potentially created if + \l{Qt.core::generateMetaTypesFile}{generateMetaTypesFile} is enabled. + \row + \li \c{"qt.core.resource_data"} + \li - + \li 1.7 + \li Source files with this tag serve as inputs to the rule creating \c qrc files. + \row + \li \c{"ts"} + \li \c{*.ts} + \li 1.0 + \li Files with this tag serve as inputs to the rule running the \c lrelease tool. + \row + \li \c{"mocable"} + \li - + \li 1.13 + \li Use this tag to force \QBS to run \c moc on the respective files, even though + they do not contain \c Q_OBJECT or a related macro. + \row + \li \c{"unmocable"} + \li - + \li 1.2 + \li Use this tag for files that \QBS should not run \c moc on, even though they contain + \c Q_OBJECT or a related macro. + \endtable +*/ + +/*! + \qmlproperty stringList Qt.core::availableBuildVariants + + The build variants that this Qt installation offers. + + \defaultvalue Set by \l{setup-qt}. +*/ + +/*! + \qmlproperty path Qt.core::binPath + + The path in which Qt tools such as \c qmake, \c moc, and so on are located. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool Qt.core::combineMocOutput + + Whether the C++ sources created by \c moc are combined into a single C++ + file per product. For projects where many header files are to be handled by + \c moc, this can speed up compilation considerably. However, side effects + may occur as a result of merging translation units. + + \defaultvalue \l{cpp::combineCxxSources}{cpp.combineCxxSources} +*/ + +/*! + \qmlproperty bool Qt.core::enableBigResources + + Whether the Qt resource compiler is run in a two-pass fashion that supports + the creation of big resources. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty stringList Qt.core::config + + Corresponds to the default value of qmake's \c CONFIG variable. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty path Qt.core::docPath + + The path in which the Qt documentation is located. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool Qt.core::enableKeywords + + Set this property to \c false if you do not want Qt to define the symbols + \c {signals}, \c {slots}, and \c {emit}. This can be necessary if your + project interacts with code that also defines such symbols. + + \defaultvalue \c true +*/ + +/*! + \qmlproperty bool Qt.core::frameworkBuild + + Whether Qt was built as a framework. This is only relevant for Darwin + systems. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool Qt.core::generateMetaTypesFile + + If this property is enabled, a JSON file tagged as \c "qt.core.metatypes" will potentially + be generated from metatype information collected by \c moc. + + \nodefaultvalue + \since Qbs 1.16 +*/ + +/*! + \qmlproperty bool Qt.core::metaTypesInstallDir + + The directory to install the metatypes file into. If this property is empty or undefined, + the metatypes file will not be installed. If the + \l{Qt.core::generateMetaTypesFile}{generateMetaTypesFile} property is not \c true, then + this property has no effect. + + \nodefaultvalue + \since Qbs 1.16 +*/ + +/*! + \qmlproperty path Qt.core::incPath + + The base path of the Qt headers. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string Qt.core::libInfix + + The library infix can be set at Qt build time to change the name of Qt's + libraries. + + For example, if the infix is \c "Test", on Unix systems, the Qt Core library + will be in a file called \c{libQt5CoreTest.so} instead of the default + \c{libQt5Core.so}. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty path Qt.core::libPath + + The path in which the Qt libraries are located. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool Qt.core::lreleaseMultiplexMode + + If this property is \c true, \c lrelease will merge all \c .ts files into + one \c .qm file. Otherwise, one \c .qm file will be created for each \c .ts + file. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty string Qt.core::lreleaseName + + The base name of the \c lrelease tool. Set this if your system uses a name + such as \c "lrelease-qt4". + + \defaultvalue \c{"lrelease"} +*/ + +/*! + \qmlproperty path Qt.core::mkspecPath + + The path in which the Qt \c mkspecs are located. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList Qt.core::mocFlags + + A list of additional flags to \c moc. + + You will rarely need to set this property. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty string Qt.core::mocName + + The base name of the \c moc tool. + + Set this if your system uses a name such as \c "moc-qt4". + + \defaultvalue \c{"moc"} +*/ + +/*! + \qmlproperty string Qt.core::namespace + + The Qt namespace that can be set at build time via the \c configure script. + + By default, Qt is not built in a namespace. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList Qt.core::pluginMetaData + + A list of additional plugin metadata. + + The elements of the list are key-value pairs separated by the \c = + character. + + A possible use case is to set the plugin URI when building a static QML + plugin: + + \badcode + Qt.core.pluginMetaData: ["uri=thePlugin"] + \endcode + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty path Qt.core::pluginPath + + The path in which the Qt plugins are located. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList Qt.core::qdocEnvironment + + The environment for calls to \c qdoc. Typically, you will need to set some + variables here when running \c qdoc to build your project documentation. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string Qt.core::qdocName + + The base name of the \c qdoc tool. + + \defaultvalue \c{"qdoc3"} for Qt 4, \c{"qdoc"} otherwise. +*/ + +/*! + \qmlproperty string Qt.core::qmBaseName + + The base name of the \c .qm file to be built from the \c .ts files in the + product. + + This property is ignored if \l{Qt.core::}{lreleaseMultiplexMode} is \c false. + + \defaultvalue \l{Product::targetName}{product.targetName} +*/ + +/*! + \qmlproperty string Qt.core::qtBuildVariant + + Specifies the type of Qt libraries to build against: \c "debug" or + \c "release". + + \note On some systems, it is not possible to link code built in debug mode + against libraries built in release mode and vice versa. + + \defaultvalue The build variant of the code linking against Qt. If Qt does + not offer that build variant, the build variant offered by Qt is chosen + instead. +*/ + +/*! + \qmlproperty stringList Qt.core::qtConfig + + Corresponds to the default value of qmake's \c QT_CONFIG variable. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty path Qt.core::resourceSourceBase + + For files tagged as \l{filetags-qtcore}{qt.core.resource_data}, this + property determines which part of their path will end up in the generated + \c .qrc file. If this property is set to \c undefined, only the file name is + used. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string Qt.core::resourcePrefix + + For files tagged as \l{filetags-qtcore}{qt.core.resource_data}, this + property determines the prefix under which they will be available in the + generated \c .qrc file. + + \defaultvalue \c{"/"} +*/ + +/*! + \qmlproperty string Qt.core::resourceFileBaseName + + For files tagged as \l{filetags-qtcore}{qt.core.resource_data}, this + property determines the base name of the generated \c .qrc file. + + If this property needs to be changed, it must be set in the corresponding + \l{Product}{product} rather than in a \l{Group}. + + \defaultvalue \l{Product::targetName}{product.targetName} +*/ + +/*! + \qmlproperty bool Qt.core::staticBuild + + Whether Qt was built statically. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string Qt.core::version + + The Qt version. + + Consists of three numbers separated by dots. For example "5.9.1". + + \nodefaultvalue +*/ + +/*! + \qmlproperty int Qt.core::versionMajor + + The Qt major version. + + \defaultvalue \c{versionParts[0]} +*/ + +/*! + \qmlproperty int Qt.core::versionMinor + + The Qt minor version. + + \defaultvalue \c{versionParts[1]} +*/ + +/*! + \qmlproperty list Qt.core::versionParts + + The Qt version as a list. + + For example, Qt version 5.9.1 would correspond to a value of \c[5, 9, 1]. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty int Qt.core::versionPatch + + The Qt patch level. + + \defaultvalue \c{versionParts[2]} +*/ diff --git a/doc/reference/modules/qt-dbus-module.qdoc b/doc/reference/modules/qt-dbus-module.qdoc new file mode 100644 index 00000000..dacf3f09 --- /dev/null +++ b/doc/reference/modules/qt-dbus-module.qdoc @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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 + \qmltype Qt.dbus + \inqmlmodule QbsModules + \brief Provides Qt D-Bus support. + + The Qt.dbus module provides support for the Qt D-Bus module, which contains + classes for inter-process communication over the D-Bus protocol. + + \section2 Relevant File Tags + \target filetags-debus + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"qt.dbus.adaptor"} + \li - + \li 1.5 + \li Source files with this tag serve as inputs to the rule running the + \c qdbusxml2cpp tool, which will create an adaptor class. + \row + \li \c{"qt.dbus.interface"} + \li - + \li 1.5 + \li Source files with this tag serve as inputs to the rule running the + \c qdbusxml2cpp tool, which will create an interface class. + \endtable +*/ + +/*! + \qmlproperty string Qt.dbus::xml2cppName + + The base name of the \c qdbusxml2cpp tool. + + Set this property if your system uses a name different from the default + value. + + \defaultvalue \c{"qdbusxml2cpp"} +*/ + +/*! + \qmlproperty stringList Qt.dbus::xml2CppHeaderFlags + + A list of additional flags when running the \c qdbusxml2cpp tool to create + header files. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty stringList Qt.dbus::xml2CppSourceFlags + + A list of additional flags when running the \c qdbusxml2cpp tool to create + source files. + + \defaultvalue \c [] +*/ diff --git a/doc/reference/modules/qt-declarative-module.qdoc b/doc/reference/modules/qt-declarative-module.qdoc new file mode 100644 index 00000000..f7996257 --- /dev/null +++ b/doc/reference/modules/qt-declarative-module.qdoc @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** 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 + \qmltype Qt.declarative + \inqmlmodule QbsModules + \brief Provides Qt Quick 1 support. + + The Qt.declarative module provides support for Qt Quick 1. + + \sa Qt.quick, Qt.qml +*/ + +/*! + \qmlproperty bool Qt.declarative::qmlDebugging + + Whether QML debugging support is compiled into your binaries. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty string Qt.declarative::qmlImportsPath + + The absolute path to the directory where Qt's QML imports are installed. + + \defaultvalue Determined by \l{setup-qt}. +*/ + +/*! + \qmlproperty string Qt.declarative::qmlPath + + The absolute path to the directory where Qt's QML files are installed. + This property is left undefined for Qt 4. + + \defaultvalue Determined by \l{setup-qt}. +*/ diff --git a/doc/reference/modules/qt-gui-module.qdoc b/doc/reference/modules/qt-gui-module.qdoc new file mode 100644 index 00000000..16d8cf34 --- /dev/null +++ b/doc/reference/modules/qt-gui-module.qdoc @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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 + \qmltype Qt.gui + \inqmlmodule QbsModules + \brief Provides Qt GUI support. + + The Qt.gui module provides support for the Qt GUI module, which contains + base classes for graphical user interface (GUI) components. + + \section2 Relevant File Tags + \target filetags-gui + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"ui"} + \li \c{*.ui} + \li 1.0 + \li Source files with this tag serve as inputs to the rule running the + \c uic tool. + \endtable +*/ + +/*! + \qmlproperty string Qt.gui::uicName + + The base name of the \c uic tool. + + Set this property if your system uses a name such as \c uic-qt4. + + \defaultvalue \c{"uic"} +*/ diff --git a/doc/reference/modules/qt-modules.qdoc b/doc/reference/modules/qt-modules.qdoc new file mode 100644 index 00000000..099f4e3c --- /dev/null +++ b/doc/reference/modules/qt-modules.qdoc @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** 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 qt-modules.html + \inmodule qbs-modules + + \title Qt Modules + + \brief Provides Qt support. + + The \c{Qt.*} modules contain properties and rules for Qt. + + \section1 Creating Dependencies to Qt Modules + + The Qt modules are grouped using the prefix \c Qt. If your product depends + on the Qt.core and Qt.network modules, you could write: + + \code + Depends { name: "Qt.core" } + Depends { name: "Qt.network" } + \endcode + + Or, alternatively: + + \code + Depends { name: "Qt"; submodules: [ "core", "network" ] } + \endcode + + The Qt modules that have properties and relevant file tags are described in + separate topics. + + \section1 Qt-specific Module Provider Properties + + Looking up a Qt installation happens via a \l{Module Providers}{module provider}. + By default, if a dependency to a Qt module is encountered, \QBS collects all Qt installations + it can find. This lookup happens by searching for \c qmake executables in the \c PATH + environment variable. Alternatively, you can explicitly tell \QBS which Qt + installations it should consider by setting the \c Qt.qmakeFilePaths + \l{Parameterizing Module Providers}{module provider property}. In that case, + the environment will be ignored. For instance, with the following Linux command line, + \QBS will build the project against a custom Qt instead of the standard one in \c{/usr/bin}: + \code + $ qbs moduleProviders.Qt.qmakeFilePaths:/opt/myqt/bin/qmake + \endcode + You can also set the module provider property in a profile. The simplest way to do + this is via the \l setup-qt tool. + + \section1 List of Submodules + + \table + \header + \li Submodule Name + \li Qt Module Name + \li Notes + \row + \li axcontainer + \li QAxContainer + \li This module is only available on Windows. + \row + \li axserver + \li QAxServer + \li This module is only available on Windows. + \row + \li concurrent + \li Qt Concurrent + \li + \row + \li \l{Qt.core}{core} + \li Qt Core + \li For more information, see \l{Qt.core}. + \row + \li \l{Qt.dbus}{dbus} + \li Qt D-Bus + \li For more information, see \l{Qt.dbus}. + \row + \li \l{Qt.declarative}{declarative} + \li Qt Quick 1 + \li Provides the \c{Qt Quick 1} module. For more information, see + \l{Qt.declarative}. + \row + \li designer + \li Qt Designer + \li + \row + \li enginio + \li Qt Enginio + \li + \row + \li \l{Qt.gui}{gui} + \li Qt GUI + \li For more information, see \l {Qt.gui}. + \row + \li help + \li Qt Help + \li You do not need this module for building \c qdoc documentation, + because that functionality is part of the Qt.core module. This + module is for using Qt classes such as \c QHelpEngine. + \row + \li multimedia + \li Qt Multimedia + \li + \row + \li multimediawidgets + \li Qt Multimedia Widgets + \li + \row + \li network + \li Qt Network + \li + \row + \li opengl + \li Qt OpenGL + \li + \row + \li phonon + \li Phonon (Qt 4 only) + \li + \row + \li printsupport + \li Qt Print Support + \li + \row + \li \l{Qt.quick}{quick} + \li Qt Quick 2 + \li Provides the \c{Qt Quick} module (Qt Quick 2). For more information, + see \l{Qt.quick}. + \row + \li quickcontrols2 + \li Qt Quick Controls 2 + \row + \li \l{Qt.qml}{qml} + \li Qt QML + \li For more information, see \l{Qt.qml}. + \row + \li qmltest + \li Qt Quick Test + \li + \row + \li script + \li Qt Script + \li + \row + \li \l{Qt.scxml}{scxml} + \li Qt Scxml + \li For more information, see \l {Qt.scxml}. + \row + \li sql + \li Qt SQL + \li + \row + \li svg + \li Qt SVG + \li + \row + \li testlib + \li Qt Test + \li + \row + \li webkit + \li Qt WebKit + \li + \row + \li webkitwidgets + \li Qt WebKit Widgets + \li + \row + \li widgets + \li Qt Widgets + \li + \row + \li xml + \li Qt XML + \li You do not need this module for the \c QXmlStreamReader and + \c QXmlStreamWriter classes, because those classes are a part of the + \c Qt.core module. This module provides the deprecated DOM and SAX + classes. + \row + \li xmlpatterns + \li Qt XML Patterns + \li + \li + \endtable +*/ diff --git a/doc/reference/modules/qt-plugin_support-module.qdoc b/doc/reference/modules/qt-plugin_support-module.qdoc new file mode 100644 index 00000000..2f41e1cc --- /dev/null +++ b/doc/reference/modules/qt-plugin_support-module.qdoc @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + \qmltype Qt.plugin_support + \inqmlmodule QbsModules + \since Qbs 1.13.0 + \brief Allows to fine-tune which Qt plugins get pulled in. + + The \c Qt.plugin_support module provides properties that allow users to control + which Qt plugins to pull into a product. + This is mostly relevant if Qt was built statically, in which case the respective + plugins are static libraries that get linked into your application or library. +*/ + +/*! + \qmlproperty varList Qt.plugin_support::pluginsByType + + Set this property if you want to override the set of plugins for a certain + plugin type. For instance, to disable all image plugins except the JPEG + one: + \code + Qt.plugin_support.pluginsByType: ({imageformats: "qjpeg"}) + \endcode + For plugin types that are not specifically overridden like this, the value in + defaultPluginsByType is used. + + \nodefaultvalue +*/ + +/*! + \qmlproperty var Qt.plugin_support::allPluginsByType + + Provides the complete set of plugins in a statically built Qt. + The value is a map. The keys are the plugin types, and the values + are lists of plugin names. + + \readonly +*/ + +/*! + \qmlproperty var Qt.plugin_support::defaultPluginsByType + + Provides the set of plugins that your application or library will + link to if you do not set pluginsByType. + The value is a map. The keys are the plugin types, and the values + are lists of plugin names. + The value depends on the Qt modules your product pulls in. + + \readonly +*/ + +/*! + \qmlproperty bool Qt.plugin_support::linkPlugins + + Controls whether plugins of a statically built Qt should be linked into the product. + + \defaultvalue \c true if the product is an application or shared library, \c false otherwise. + +*/ diff --git a/doc/reference/modules/qt-qml-module.qdoc b/doc/reference/modules/qt-qml-module.qdoc new file mode 100644 index 00000000..ba8dddf2 --- /dev/null +++ b/doc/reference/modules/qt-qml-module.qdoc @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** 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 + \qmltype Qt.qml + \inqmlmodule QbsModules + \brief Provides Qt QML support. + + The Qt.qml module provides support for the Qt QML module, which contains + classes for QML and JavaScript languages. + + \note If the current value of \l{qbs::architecture}{qbs.architecture} is not + supported by \c qmlcachegen, the QML cache file generator rule is disabled. + + \sa Qt.quick, Qt.declarative + + \section2 Relevant File Tags + \target filetags-qml + + \table + \header + \li Tag + \li Auto-tagged File Names + \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. + \row + \li \c{"qt.qml.types"} + \li n/a + \li 1.16 + \li This tag is attached to the files created by the \c qmltyperegistrar + tool if the \l importName property is set. + \endtable +*/ + +/*! + \qmlproperty string Qt.qml::importName + + Setting this value triggers QML type registration via the \c qmltyperegistrar tool, + which results in the creation of a file with the tag \c{"qt.qml.types"}. + The given string is the name under which the registered types can be imported + by QML code that wants to use them. + + \note This functionality is only available with Qt 5.15 or later. + + \since Qbs 1.16 + \nodefaultvalue +*/ + +/*! + \qmlproperty string Qt.qml::importVersion + + Specifies the version of the types to be registered. + Values consist of a major and an optional minor number, separated by dots. + This property has no effect if \l importName is not set. + + \since Qbs 1.16 + \defaultvalue \l {Product::version}{The product version} +*/ + +/*! + \qmlproperty string Qt.qml::typesFileName + + Specifies the name of the file that declares the types registered for this product. + For applications, the default value is \c{.qmltypes}, + where \c{} is the product's \l{Product::targetName}{target name}. + Otherwise, the default value is "plugins.qmltypes". + \note The naming conventions are still in flux. + When in doubt, consult the Qt documentation. + + This property has no effect if \l importName is not set. + + \since Qbs 1.16 +*/ + +/*! + \qmlproperty string Qt.qml::typesInstallDir + + The directory to install the qmltypes file into. If this property is empty or undefined, + the file will not be installed. + This property has no effect if \l importName is not set. + + \since Qbs 1.16 + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList Qt.qml::extraMetaTypesFiles + + Specifies extra metatypes files to pass to the \c qmltyperegistrar tool + via the \c{--foreign-types} option when registering QML types. + \note This property is only needed for external libraries, not products or modules + pulled via \c Depends items. In particular, you don't need to (and should not) + use it to collect the metatypes files of Qt modules. These are found automatically. + This property has no effect if \l importName is not set. + + \since Qbs 1.16 + \nodefaultvalue +*/ + +/*! + \qmlproperty string Qt.qml::qmlImportScannerName + + The base name of the QML import scanner. + + Set this value if your system uses a name different from the default value. + + \defaultvalue \c{"qmlimportscanner"} +*/ + +/*! + \qmlproperty string Qt.qml::qmlPath + + The absolute path to the directory where Qt's QML files are installed. + + \defaultvalue Determined by \l{setup-qt}. +*/ + +/*! + \qmlproperty bool Qt.qml::generateCacheFiles + + Whether QML cache files are generated. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty bool Qt.qml::cachingEnabled + \readonly + + This property is \c true if \l{Qt.qml::}{generateCacheFiles} is \c{true} + and the platform supports QML cache generation. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty string Qt.qml::qmlCacheGenPath + + The absolute path to the \c qmlcachegen executable. + + \defaultvalue Determined by \l{setup-qt}. +*/ + +/*! + \qmlproperty string Qt.qml::cacheFilesInstallDir + + The path to the directory where the cache files are installed. + + If this property is set, QML cache files are automatically installed. + + \nodefaultvalue +*/ diff --git a/doc/reference/modules/qt-quick-module.qdoc b/doc/reference/modules/qt-quick-module.qdoc new file mode 100644 index 00000000..8a39b92f --- /dev/null +++ b/doc/reference/modules/qt-quick-module.qdoc @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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 + \qmltype Qt.quick + \inqmlmodule QbsModules + \brief Provides Qt Quick 2 support. + + The Qt.quick module provides support for Qt Quick 2. + + \sa Qt.qml, Qt.declarative + + \section2 Relevant File Tags + \target filetags-quick + + \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 +*/ + +/*! + \qmlproperty bool Qt.quick::compilerAvailable + + Whether the Qt installation contains the Qt Quick compiler. + + \defaultvalue auto-detected + \since 1.10 +*/ + +/*! + \qmlproperty bool Qt.quick::useCompiler + + Whether to make use of the Qt Quick compiler. + + \defaultvalue \l compilerAvailable + \since 1.11 +*/ + +/*! + \qmlproperty bool Qt.quick::qmlDebugging + + Whether QML debugging support should be compiled into your binaries. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty string Qt.quick::qmlImportsPath + + The absolute path to the directory where Qt's QML imports are installed. + + \defaultvalue Determined by \l{setup-qt}. +*/ + +/*! + \qmlproperty string Qt.quick::qmlPath + + The absolute path to the directory where Qt's QML files are installed. + + This property is left undefined for Qt 4. + + \defaultvalue Determined by \l{setup-qt}. +*/ diff --git a/doc/reference/modules/qt-scxml-module.qdoc b/doc/reference/modules/qt-scxml-module.qdoc new file mode 100644 index 00000000..34f0461d --- /dev/null +++ b/doc/reference/modules/qt-scxml-module.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 index.html + \qmltype Qt.scxml + \inqmlmodule QbsModules + \brief Provides Qt SCXML support. + + The Qt.scxml module provides support for the Qt SCXML module, which enables + creating state machines from SCXML files. + + \section2 Relevant File Tags + \target filetags-scxml + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"qt.scxml.compilable"} + \li - + \li 1.7 + \li Source files with this tag serve as inputs to the rule running the + Qt SCXML compiler, which will create a C++ class representing a + state machine. + \endtable +*/ + +/*! + \qmlproperty string Qt.scxml::className + + The class name of the generated state machine. + + By default, the compiler will use the \c name attribute of the input file's + \c{} tag. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string Qt.scxml::generateStateMethods + + If this property is \c true, the Qt SCXML compiler will generate read and notify methods + for states. + + \defaultvalue \c false + \since 1.11 +*/ + +/*! + \qmlproperty string Qt.scxml::namespace + + The C++ namespace in which to put the generated class. + + By default, the compiler will place the class in the global namespace. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string Qt.scxml::qscxmlcName + + The base name of the Qt SCXML compiler. + + Set this value if your system uses a name different from the default value. + + \defaultvalue \c{"qscxmlc"} +*/ diff --git a/doc/reference/modules/texttemplate-module.qdoc b/doc/reference/modules/texttemplate-module.qdoc new file mode 100644 index 00000000..652b9286 --- /dev/null +++ b/doc/reference/modules/texttemplate-module.qdoc @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + \qmltype texttemplate + \inqmlmodule QbsModules + \since Qbs 1.13 + + \brief Provides support for text template files. + + The \c texttemplate module provides support for text template files. + + \section2 Example + + Consider the following text file \e{greeting.txt.in}. + + \code + ${greeting} ${name}! + \endcode + + This can be used in a project like this: + + \code + Product { + type: ["text"] + files: ["greeting.txt.in"] + Depends { name: "texttemplate" } + texttemplate.dict: ({ + greeting: "Hello", + name: "World" + }) + } + \endcode + + Which will create the file \e{greeting.txt}. + + \code + Hello World! + \endcode + + + \section2 Placeholder Syntax + + A placeholder \c{${foo}} is replaced by its corresponding value in + \e{texttemplate.dict}. + Placeholder names consist of alphanumeric characters only. + + The placeholder \c{${$}} is always replaced with \c{$}. + If you need a literal \c{${foo}} in your template, use \c{${$}{foo}}. + + Placeholders that are not defined in the dictionary will produce an error. + + + \section2 Relevant File Tags + \target filetags-texttemplate + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"texttemplate.input"} + \li \c{*.in} + \li 1.13.0 + \li Source files with this tag serve as inputs for the text template rule. + \endtable +*/ + +/*! + \qmlproperty var texttemplate::dict + + The dictionary containing values for all keys used in the template file. + + \defaultvalue \c{{}} +*/ + +/*! + \qmlproperty string texttemplate::outputFileName + + The output file name that is assigned to produced artifacts. + + \defaultvalue Complete base name of the input file +*/ + +/*! + \qmlproperty string texttemplate::outputTag + + The output tag that is assigned to produced artifacts. + + \defaultvalue \c{"text"} +*/ diff --git a/doc/reference/modules/typescript-module.qdoc b/doc/reference/modules/typescript-module.qdoc new file mode 100644 index 00000000..4c65c227 --- /dev/null +++ b/doc/reference/modules/typescript-module.qdoc @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** 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 + \qmltype typescript + \inqmlmodule QbsModules + \since Qbs 1.3 + + \brief Provides TypeScript support. + + The \c typescript module contains properties and rules for building + \l{TypeScript} applications and may be used in combination with the + \l{nodejs} module to run the applications directly from \QBS. +*/ + +/*! + \qmlproperty string typescript::warningLevel + + The severity of warnings to emit. The higher the level, the more warnings + will be shown. + + \c{pedantic} causes the TypeScript to emit warnings on expressions and + declarations with an implied \e any type. + + \defaultvalue \c{"normal"} +*/ + +/*! + \qmlproperty string typescript::targetVersion + + The ECMAScript target version for generated JavaScript code. + + If left undefined, the TypeScript \l{Compiler Options}{compiler default} is + used. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string typescript::moduleLoader + + If TypeScript modules are being used, the JavaScript module loading + mechanism to use in the generated JavaScript code. + + If left undefined, modules are not used. + + See \l{Compiler Options} for a list of possible values. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool typescript::stripComments + + Whether to remove comments from the generated JavaScript files. + + \defaultvalue \l{qbs::debugInformation}{!qbs.debugInformation} +*/ + +/*! + \qmlproperty bool typescript::generateDeclarations + + Whether to generate the corresponding \c .d.ts files during compilation. + These are TypeScript's equivalent of header files. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty bool typescript::generateSourceMaps + + Whether to generate the corresponding \c .map files during compilation. + + \defaultvalue \l{qbs::debugInformation}{qbs.debugInformation} +*/ + +/*! + \qmlproperty stringList typescript::compilerFlags + + A list of additional flags for the TypeScript compiler. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool typescript::singleFile + + Whether to compile all TypeScript source files to a single JavaScript output + file. + + The default is to compile each TypeScript file to a corresponding JavaScript + file. This property is incompatible with \l{typescript}{moduleLoader}. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty string typescript::version + + The TypeScript version. + + Consists of four numbers separated by dots. For example, "1.0.0.0". + + \nodefaultvalue +*/ + +/*! + \qmlproperty int typescript::versionMajor + + The TypeScript major version. + + \defaultvalue \c{versionParts[0]} +*/ + +/*! + \qmlproperty int typescript::versionMinor + + The TypeScript minor version. + + \defaultvalue \c{versionParts[1]} +*/ + +/*! + \qmlproperty list typescript::versionParts + + The TypeScript version as a list. + + For example, TypeScript version 1.0 would correspond to a value of + \c[1, 0, 0, 0]. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty int typescript::versionPatch + + The TypeScript patch level. + + \defaultvalue \c{versionParts[2]} +*/ + +/*! + \qmlproperty int typescript::versionBuild + + The fourth TypeScript version number component. + + \defaultvalue \c{versionParts[3]} +*/ + +/*! + \qmlproperty path typescript::toolchainInstallPath + + The TypeScript installation directory. + + This property should not normally need to be changed if \c{tsc} is available + by searching the PATH environment variable. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string typescript::compilerName + + The name of the compiler binary. + + This property should not normally need to be changed. + + \defaultvalue \c{"tsc"} +*/ + +/*! + \qmlproperty string typescript::compilerPath + + The directory where the compiler binary is located. + + This property should not normally need to be changed. + + \defaultvalue \c{compilerName} +*/ diff --git a/doc/reference/modules/vcs-module.qdoc b/doc/reference/modules/vcs-module.qdoc new file mode 100644 index 00000000..d8ae45b8 --- /dev/null +++ b/doc/reference/modules/vcs-module.qdoc @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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 + \qmltype vcs + \inqmlmodule QbsModules + \since Qbs 1.10 + + \brief Provides support for version control systems. + + The \c vcs module provides the current state of the project's repository via + the \l{vcs::}{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 example: + + \code + #include + #include + + int main() + { + std::cout << "I was built from " << VCS_REPO_STATE << std::endl; + } + \endcode + + Above, 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. +*/ + +/*! + \qmlproperty string vcs::headerFileName + + 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. + + \defaultvalue \c{"vcs-repo-state.h"} +*/ + +/*! + \qmlproperty string vcs::repoDir + + The root directory of the repository. + + \defaultvalue The top-level project directory (\l{Project::sourceDirectory} + {project.sourceDirectory}). +*/ + +/*! + \qmlproperty string vcs::repoState + + The current state of the repository. + + For example, in Git this is the commit hash of the current HEAD. + + \nodefaultvalue +*/ + +/*! + \qmlproperty string vcs::toolFilePath + + Set this property 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. + + \defaultvalue The file name of the version control tool corresponding to + \l{vcs::type}{type}. +*/ + +/*! + \qmlproperty string vcs::type + + The version control system used in the project. + + Currently, the supported values are \c{"git"} and \c{"svn"}. + + \defaultvalue auto-detected +*/ diff --git a/doc/reference/modules/wix-module.qdoc b/doc/reference/modules/wix-module.qdoc new file mode 100644 index 00000000..a0911969 --- /dev/null +++ b/doc/reference/modules/wix-module.qdoc @@ -0,0 +1,343 @@ +/**************************************************************************** +** +** 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 + \qmltype wix + \inqmlmodule QbsModules + \since Qbs 1.2 + + \brief Provides Windows Installer XML Toolset support. + + The \c wix module contains properties and rules for building MSI and + EXE setup packages with the \l{Windows Installer XML Toolset}. + + This module is available on all platforms. + + \section2 Relevant File Tags + \target filetags-wix + + \table + \header + \li Tag + \li Auto-tagged File Names + \li Since + \li Description + \row + \li \c{"msi"} + \li - + \li 1.2 + \li The rule that creates the Microsoft Installer setup file attaches this tag to its output + artifact. + \row + \li \c{"wixpdb"} + \li - + \li 1.2 + \li The rule that creates the Microsoft Installer setup file or WiX setup executable + attaches this tag to the associated debug symbol file. + \row + \li \c{"wixsetup"} + \li - + \li 1.2 + \li The rule that creates the WiX setup executable attaches this tag to its output artifact. + \row + \li \c{"wxi"} + \li \c{*.wxi} + \li 1.2 + \li This tag is attached to WiX include files. + \row + \li \c{"wxl"} + \li \c{*.wxl} + \li 1.2 + \li This tag is attached to WiX localization files. + \row + \li \c{"wxs"} + \li \c{*.wxs} + \li 1.2 + \li This tag is attached to WiX source files. + Each source file will be compiled into one WiX object file. + \endtable +*/ + +/*! + \qmlproperty bool wix::debugInformation + + Whether to generate debug information. + + \sa {qbs::debugInformation}{qbs.debugInformation} + + \defaultvalue \l{qbs::debugInformation}{qbs.debugInformation} +*/ + +/*! + \qmlproperty stringList wix::defines + + A list of preprocessor macros that get passed to the compiler. + + To set macro values, use the following syntax: + + \badcode + wix.defines: ["USE_COLORS=1", 'COLOR_STR="blanched almond"'] + \endcode + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool wix::enableQbsDefines + + Whether to define preprocessor macros corresponding to values from the + project and product objects. + + When building a 64-bit package, the preprocessor variable \c{Win64} will + also be defined. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty bool wix::visualStudioCompatibility + + Whether to pass most of the same preprocessor macros to the compiler as + Visual Studio does. + + This allows easier authoring of WiX files that are compatible with both + \QBS and MSBuild. + + \defaultvalue \c{true} +*/ + +/*! + \qmlproperty pathList wix::includePaths + + A list of include paths. + + Relative paths are considered to be relative to the \c .qbs product file + they are used in. + + \nodefaultvalue +*/ + +/*! + \qmlproperty bool wix::treatWarningsAsErrors + + Whether warnings will be handled as errors and cause the build to fail. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty string wix::warningLevel + + The severity of warnings to emit. + + The higher the level, the more warnings will be shown. + + Possible values include: \c{"none"}, \c{"normal"}, \c{"pedantic"} + + \defaultvalue \c{"normal"} +*/ + +/*! + \qmlproperty bool wix::verboseOutput + + Whether to display verbose output from the compiler and linker. + + \defaultvalue \c{false} +*/ + +/*! + \qmlproperty stringList wix::compilerFlags + + A list of additional flags for the Candle compiler. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList wix::linkerFlags + + A list of additional flags for the Light linker. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList wix::cultures + + A list of localizations to include in the MSI. + + If left undefined, includes all localizations. + + \nodefaultvalue +*/ + +/*! + \qmlproperty stringList wix::extensions + + A list of extension assemblies to link into the output. + + Possible values include: + + \list + \li custom assemblies + \li \c{"WixBalExtension"} + \li \c{"WixComPlusExtension"} + \li \c{"WixDependencyExtension"} + \li \c{"WixDifxAppExtension"} + \li \c{"WixDirectXExtension"} + \li \c{"WixFirewallExtension"} + \li \c{"WixGamingExtension"} + \li \c{"WixIisExtension"} + \li \c{"WixMsmqExtension"} + \li \c{"WixNetFxExtension"} + \li \c{"WixPSExtension"} + \li \c{"WixSqlExtension"} + \li \c{"WixTagExtension"} + \li \c{"WixUIExtension"} + \li \c{"WixUtilExtension"} + \li \c{"WixVSExtension"} + \endlist + + \defaultvalue \c{["WixBalExtension"]} if the product type is an EXE setup + application, otherwise \c{undefined}. +*/ + +/*! + \qmlproperty string wix::version + + The WiX version. + + Consists of four numbers separated by dots. For example, "3.7.1224.0". + + \nodefaultvalue +*/ + +/*! + \qmlproperty int wix::versionMajor + + The WiX major version. + + \defaultvalue \c{versionParts[0]} +*/ + +/*! + \qmlproperty int wix::versionMinor + + The WiX minor version. + + \defaultvalue \c{versionParts[1]} +*/ + +/*! + \qmlproperty list wix::versionParts + + The WiX version as a list. + + For example, WiX version 3.7.1224.0 would correspond to a value of + \c[3, 7, 1224, 0]. + + \defaultvalue \c [] +*/ + +/*! + \qmlproperty int wix::versionPatch + + The WiX patch level. + + \defaultvalue \c{versionParts[2]} +*/ + +/*! + \qmlproperty int wix::versionBuild + + The fourth WiX version number component. + + \defaultvalue \c{versionParts[3]} +*/ + +/*! + \qmlproperty path wix::toolchainInstallPath + + The WiX installation directory. + + This property should not normally need to be changed. + + \defaultvalue Determined automatically by searching the registry for the + latest version. +*/ + +/*! + \qmlproperty path wix::toolchainInstallRoot + + The WiX binaries directory. + + This property should not normally need to be changed. + + \defaultvalue Determined automatically by searching the registry for the + latest version. +*/ + +/*! + \qmlproperty string wix::compilerName + + The name of the compiler binary. + + This property should not normally need to be changed. + + \defaultvalue \c{"candle.exe"} +*/ + +/*! + \qmlproperty string wix::compilerPath + + The directory where the compiler binary is located. + + This property should not normally need to be changed. + + \defaultvalue \l{wix::}{compilerName} +*/ + +/*! + \qmlproperty string wix::linkerName + + The name of the linker binary. + + This property should not normally need to be changed. + + \defaultvalue \c{"light.exe"} +*/ + +/*! + \qmlproperty string wix::linkerPath + + The directory where the linker binary is located. + + This property should not normally need to be changed. + + \defaultvalue \l{wix::}{linkerName} +*/ diff --git a/doc/reference/modules/xcode-module.qdoc b/doc/reference/modules/xcode-module.qdoc new file mode 100644 index 00000000..bf38feea --- /dev/null +++ b/doc/reference/modules/xcode-module.qdoc @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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 + \qmltype xcode + \inqmlmodule QbsModules + \since Qbs 1.5 + + \brief Provides Xcode support. + + The \c xcode module contains properties and rules for Xcode-based development. + This module provides the foundation for several other modules on Apple + platforms, including the \l{cpp} and \l{ib} modules. +*/ + +/*! + \qmlproperty path xcode::developerPath + + The developer directory of the Xcode installation. + + Corresponds to the \c DEVELOPER_DIR environment variable. + + \defaultvalue The developer directory of the Xcode installation at its + default location in \c /Applications. For example, + \c{"/Applications/Xcode.app/Contents/Developer"}. + +*/ + +/*! + \qmlproperty string xcode::sdk + + The version of the Xcode SDK used to build products. + + This can be specified as a full canonical SDK name (\c{"macosx10.10"}), a + platform version number (\c{"10.10"}), or a platform identifier + (\c{"macosx"}), in which case the latest SDK available for that platform + will be used. + + \defaultvalue The latest SDK available in the Xcode installation for the + current platform. Determined by \l{qbs::targetOS}{qbs.targetOS}. +*/ + +/*! + \qmlproperty stringList xcode::targetDevices + + A list of the Apple devices targeted by this product. + + For macOS, watchOS, and tvOS, this should always be \c "mac", \c "watch", + and \c "tv", respectively. For iOS, this can be one or both of \c "iphone" + and \c "ipad". + + \defaultvalue The list of all device types supported by the current + platform. Determined by \l{qbs::targetOS}{qbs.targetOS}. +*/ + +/*! + \qmlproperty string xcode::sdkName + \readonly + + The canonical name of the SDK used to build products. + + For example, \c macosx10.9. + + \defaultvalue \l{xcode::}{sdk} +*/ + +/*! + \qmlproperty string xcode::sdkVersion + \readonly + + The version number of the SDK used to build products. + + For example, 10.9. + + \defaultvalue \l{xcode::}{sdk} +*/ + +/*! + \qmlproperty string xcode::latestSdkName + \readonly + + The canonical name of the latest SDK available in the Xcode installation. + + For example, \c {macosx10.10}. + + \defaultvalue \l{xcode::}{developerPath} +*/ + +/*! + \qmlproperty string xcode::latestSdkVersion + \readonly + + The version number of the latest SDK available in the Xcode installation. + + For example, 10.10. + + \defaultvalue \l{xcode::}{developerPath} +*/ + +/*! + \qmlproperty stringList xcode::availableSdkNames + \readonly + + The canonical names of all SDKs available in the Xcode installation for the + current platform. + + For example, \c {[macosx10.9, macosx10.10]}. + + \defaultvalue \l{xcode::}{developerPath} +*/ + +/*! + \qmlproperty stringList xcode::availableSdkVersions + \readonly + + The version numbers of all SDK available in the Xcode installation for the + current platform. + + For example, \c {[10.9, 10.10]}. + + \defaultvalue \l{xcode::}{developerPath} +*/ + +/*! + \qmlproperty path xcode::platformPath + \readonly + + The path of the platform directory containing \l{xcode::}{sdkPath}. + + \defaultvalue \l{xcode::}{developerPath} +*/ + +/*! + \qmlproperty path xcode::sdkPath + \readonly + + The path of the SDK used to build products. Equivalent to + \l{qbs::sysroot}{qbs.sysroot}. + + \defaultvalue Determined by \l{xcode::}{developerPath} and \l{xcode::}{sdk}. +*/ diff --git a/doc/reference/reference.qdoc b/doc/reference/reference.qdoc new file mode 100644 index 00000000..b5520193 --- /dev/null +++ b/doc/reference/reference.qdoc @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + \previouspage howtos.html + \page reference.html + \nextpage building-qbs.html + + \title Reference + + \list + \li \l{List of All Items} + \list + \li \l{List of Language Items} + \li \l{List of Convenience Items} + \li \l{List of Probes} + \endlist + \li \l{List of Built-in Services} + \li \l{Command-Line Interface} + \li \l{List of Modules} + \li \l{Command and JavaScriptCommand} + \endlist +*/ + +/*! + \contentspage reference.html + \group list-of-builtin-services + \title List of Built-in Services + \QBS provides the following built-in JavaScript extensions to simplify operations that + are expected to be needed often in project files. + + To gain access to the operations provided by a particular Service - for example, + the File service - use the following statement at the top of your \QBS project file: + \code + import qbs.File + \endcode + + If you instead need to access the service from a JavaScript file, import it using the following + statement at the top of your JavaScript file: + \code + var File = require("qbs.File"); + \endcode +*/ + +/*! + \contentspage reference.html + \qmlmodule QbsModules + \title List of Modules + These are the modules shipped with \QBS. + + For a list of \QBS modules that provide support for Qt modules, see + \l{Qt Modules}. Only the Qt modules that have properties and relevant file + tags are described in separate topics. +*/ + +/*! + \contentspage reference.html + \group list-of-items + \title List of All Items + + \QBS provides the following \l{List of Language Items}{built-in}, + \l{List of Convenience Items}{convenience}, and \l{List of Probes}{probe} + items to define projects. +*/ + +/*! + \contentspage reference.html + \qmlmodule QbsLanguageItems + \title List of Language Items + + \QBS provides the following built-in QML items to define projects. + These are the primitives upon which all other \QBS items are built. +*/ + +/*! + \contentspage reference.html + \qmlmodule QbsConvenienceItems + \title List of Convenience Items + + \QBS provides the following QML items for convenience. +*/ + +/*! + \contentspage reference.html + \qmlmodule QbsProbes + \title List of Probes + + These are the probes shipped with \QBS. + + Probes are used to retrieve information from the system prior to building. Typically, probes + call external processes to get the required information. For details, see \l{Probe}{Probe} page. +*/ diff --git a/doc/targets/qbs-target-android.qdoc b/doc/targets/qbs-target-android.qdoc new file mode 100644 index 00000000..46081db0 --- /dev/null +++ b/doc/targets/qbs-target-android.qdoc @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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 qbs-target-android.html + \ingroup platforms + + \title Building for Android + \brief Platform notes for Android. + + To develop applications for Android, you need development tools provided by + the Android SDK from Google, and (optionally) a C/C++ toolchain provided by + the Android NDK. + + \note \QBS does not yet support the Kotlin programming language. + + \section1 Creating Android Application Packages + + On Android, applications are distributed in a specially structured type of + ZIP package called an APK. The following files should be created and bundled + into an APK: + + \list + \li Android assets. + \li Android resource files. + \li AndroidManifest.xml, which provides meta-information about your + application. + \li Compiled Java code, which serves as the entry point into your + application and that automatically executes the native code in your + application (if there is any). + \li Shared libraries containing native code. + \endlist + + You can use the \l{Application} item to build application + packages for Android. + + If the \l{qbs::targetPlatform}{target platform} is \c{"android"}, then the Application item has + a dependency on the \l{Android.sdk} module, which + contains the properties and rules to create Android application packages + from source files. + + You can use the \l{DynamicLibrary} item to build native + Android libraries that are bundled into the APK. The \c qbs.architectures + property specifies the architectures to build for, with the default value + \c armv7a. + If you have only one native library, you can simply list its sources + within the main Application item, and it will get built and packaged + automatically. + + The \l{DynamicLibrary} item, as well as the \l CppApplication item, + has a dependency on the \l{Android.ndk} module, + and contains the properties and rules to create native libraries. +*/ diff --git a/doc/targets/qbs-target-apple-common.qdocinc b/doc/targets/qbs-target-apple-common.qdocinc new file mode 100644 index 00000000..9b49d155 --- /dev/null +++ b/doc/targets/qbs-target-apple-common.qdocinc @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +/*! + +//! [xcode] + + \note \QBS does not yet support the Swift programming language. + + The \l{xcode} module contains properties and rules for Xcode-based + development. + +//! [xcode] + +//! [building user interfaces] + + \section1 Building User Interfaces + + The \l{ib} module contains properties and rules for building + Interface Builder documents, storyboards, asset catalogs, and icon sets. + +//! [building user interfaces] + +//! [creating app bundles] + + \section1 Creating App Bundles + + The \l{bundle} module contains properties and rules for + building and working with Core Foundation bundles (application bundles and + frameworks) on Apple platforms. + + To build an application product as a bundle, or a dynamic or static library + product as a framework, add a dependency on the bundle module and set the + \l{bundle::isBundle}{bundle.isBundle} property to \c true: + + \code + Depends { name: "bundle" } + bundle.isBundle: true + \endcode + + \QBS also provides a number of powerful features to assist in creating the + Info.plist file that is part of your bundle. In fact, you do not need to + provide an Info.plist file at all. Instead, \QBS will generate one + automatically with the necessary keys, based on the values of module + properties set in the product. + + If you do specify an Info.plist file, \QBS may still inject additional keys + into the final output from other sources. One notable source of Info.plist + keys are \e partial Info.plist files which are generated as a result of + compiling other resources like asset catalogs, XIBs/NIBs, and storyboards. + + You may also use the \c bundle.infoPlist property to apply a set of + key-value pairs to be added to the final Info.plist. This can be used + instead of or in addition to an actual Info.plist file on disk. + +//! [creating app bundles] + +//! [architectures and variants] + + \section1 Multiple Architectures and Build Variants + + \QBS uses \l{Multiplexing}{multiplexing} to create multi-architecture + \e fat binaries and multi-variant frameworks, where a single framework can + contain both a release and debug build of a library on Apple platforms. + + You can set the \c qbs.architectures property to a list of CPU architectures + (such as \c x86, \c x86_64, \c armv7a, \c armv7k, and \c arm64), and the + \c qbs.buildVariants property to a list of build variants (such as \c debug + and \c release), and \QBS will transparently perform the necessary steps to + produce the various artifacts and combine them into a single bundle. + + Since the individual build configurations are completely independent of one + another, you can continue to use conditional branches in your projects such + as the following: + + \code + Properties { + condition: qbs.buildVariant === "release" + cpp.optimization: "small" + } + \endcode + +//! [architectures and variants] + +*/ diff --git a/doc/targets/qbs-target-integrity.qdoc b/doc/targets/qbs-target-integrity.qdoc new file mode 100644 index 00000000..52418a86 --- /dev/null +++ b/doc/targets/qbs-target-integrity.qdoc @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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 qbs-target-integrity.html + \ingroup platforms + + \title Building for INTEGRITY + \brief Platform notes for INTEGRITY. + + The Green Hills INTEGRITY RTOS is not yet supported but is planned for a + future release. + + For more information about developing applications for the Green Hills + INTEGRITY RTOS, see the \l{https://www.ghs.com/products/rtos/integrity.html} + {INTEGRITY Product Documentation}. +*/ diff --git a/doc/targets/qbs-target-ios.qdoc b/doc/targets/qbs-target-ios.qdoc new file mode 100644 index 00000000..1b55a7d0 --- /dev/null +++ b/doc/targets/qbs-target-ios.qdoc @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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 qbs-target-ios.html + \ingroup platforms + + \title Building for iOS + \brief Platform notes for iOS. + + This topic describes the \QBS features specific to iOS. + + \include qbs-target-apple-common.qdocinc xcode + \include qbs-target-apple-common.qdocinc building user interfaces + \include qbs-target-apple-common.qdocinc creating app bundles + \include qbs-target-apple-common.qdocinc architectures and variants +*/ diff --git a/doc/targets/qbs-target-linux.qdoc b/doc/targets/qbs-target-linux.qdoc new file mode 100644 index 00000000..8b15da44 --- /dev/null +++ b/doc/targets/qbs-target-linux.qdoc @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 qbs-target-linux.html + \ingroup platforms + + \title Building for Linux + \brief Platform notes for Linux. + + To develop applications for Linux, you need a GCC or Clang toolchain + installed on your host machine. +*/ diff --git a/doc/targets/qbs-target-macos.qdoc b/doc/targets/qbs-target-macos.qdoc new file mode 100644 index 00000000..8d956fc6 --- /dev/null +++ b/doc/targets/qbs-target-macos.qdoc @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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 qbs-target-macos.html + \ingroup platforms + + \title Building for macOS + \brief Platform notes for macOS. + + This topic describes the \QBS features specific to macOS. + + \include qbs-target-apple-common.qdocinc xcode + \include qbs-target-apple-common.qdocinc building user interfaces + \include qbs-target-apple-common.qdocinc creating app bundles + \include qbs-target-apple-common.qdocinc architectures and variants + + \section1 Building macOS Disk Images + + The \l{AppleDiskImage} and \l{AppleApplicationDiskImage} items have a + dependency on the \l{dmg} module. The former represents a + product that is a basic Apple disk image, while the latter extends the + former to create a drag 'n' drop disk image installer used for installing + single application bundles. + + For example, the following code snippet creates a macOS disk image with a + custom background and icon layout: + + \code + AppleApplicationDiskImage { + targetName: "cocoa-application-" + version + version: "1.0" + + files: [ + "CocoaApplication/dmg.iconset", + "CocoaApplication/en_US.lproj/LICENSE", + // comment out the following line to use a solid-color background + // (see dmg.backgroundColor below) + "CocoaApplication/background*" + ] + + dmg.backgroundColor: "#41cd52" + dmg.badgeVolumeIcon: true + dmg.iconPositions: [ + {"x": 200, "y": 200, "path": "Cocoa Application.app"}, + {"x": 400, "y": 200, "path": "Applications"} + ] + dmg.windowX: 420 + dmg.windowY: 250 + dmg.windowWidth: 600 + dmg.windowHeight: 422 // this includes the macOS title bar height of 22 + dmg.iconSize: 64 + } + \endcode + + \image qbs-dmg.png + + In addition, \QBS supports multi-language license agreement prompts that + appear when the DMG is opened, with full Unicode and rich-text formatting + support. +*/ diff --git a/doc/targets/qbs-target-platforms.qdoc b/doc/targets/qbs-target-platforms.qdoc new file mode 100644 index 00000000..7e556aea --- /dev/null +++ b/doc/targets/qbs-target-platforms.qdoc @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** 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 + \previouspage installing-files.html + \group platforms + \nextpage shell.html + + \title Target Platforms + + \QBS has built-in support for building applications for all major platforms. + Platform support is implemented as a set of \l{List of Modules}{modules} + that products depend on. The following topics describe the features specific + to a particular platform, point out things to look out for, and provide tips + for fully benefiting from the \QBS functions. + + In addition to the platforms explicitly listed below, \QBS should generally + work on other UNIX and Unix-like platforms but these are not regularly + tested or officially supported. + + \QBS recognizes the existence of at least AIX, HP-UX, Solaris, FreeBSD, + NetBSD, OpenBSD, GNU Hurd, and Haiku, but provides no explicit support + (except some minimal support for FreeBSD). + + For instructions on how to setup the target platform, see the + \l {qbs::targetPlatform}{qbs.targetPlatform} property. +*/ diff --git a/doc/targets/qbs-target-qnx.qdoc b/doc/targets/qbs-target-qnx.qdoc new file mode 100644 index 00000000..c7e28d90 --- /dev/null +++ b/doc/targets/qbs-target-qnx.qdoc @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 qbs-target-qnx.html + \ingroup platforms + + \title Building for QNX + \brief Platform notes for QNX. + + To develop applications for the QNX Neutrino RTOS, you need to install the + QNX Software Development Platform (SDP) on a Linux, macOS, or Windows + development host. You can deploy the QNX Neutrino RTOS on a target system, + such as embedded hardware, a virtual machine, or a PC. + + \QBS automatically determines the location of the SDP base directory if the + SDP is installed at one of the standard locations, such as \c ~/qnx700, + \c /opt/qnx700, or \c C:\qnx700. In addition, \QBS uses the SDP and the + information it has about the host operating system to determine the location + of the QNX host and target directories. + + If the QNX SDP path could not be determined automatically, you must add a + dependency to the \l{qnx} module to your application and set the + \l{qnx::sdkDir}{qnx.sdkDir} property: + + \code + Application { + name: "helloworld" + files: "main.cpp" + Depends { name: "cpp" } + + Depends { name: "qnx" } + qnx.sdkDir: "/path/to/qnx700" + } + \endcode + + Alternatively, you can set the \c qnx.sdkDir property in a profile or on + the command line. + + \QBS supports QNX SDP version 6.5 and above. + + For more information about developing applications for the QNX Neutrino + RTOS, see the \l{http://www.qnx.com/developers/docs/} + {QNX Product Documentation}. +*/ diff --git a/doc/targets/qbs-target-tvos.qdoc b/doc/targets/qbs-target-tvos.qdoc new file mode 100644 index 00000000..76744637 --- /dev/null +++ b/doc/targets/qbs-target-tvos.qdoc @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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 qbs-target-tvos.html + \ingroup platforms + + \title Building for tvOS + \brief Platform notes for tvOS. + + This topic describes the \QBS features specific to tvOS. + + \include qbs-target-apple-common.qdocinc xcode + \include qbs-target-apple-common.qdocinc building user interfaces + \include qbs-target-apple-common.qdocinc creating app bundles + \include qbs-target-apple-common.qdocinc architectures and variants +*/ diff --git a/doc/targets/qbs-target-vxworks.qdoc b/doc/targets/qbs-target-vxworks.qdoc new file mode 100644 index 00000000..100ecfcc --- /dev/null +++ b/doc/targets/qbs-target-vxworks.qdoc @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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 qbs-target-vxworks.html + \ingroup platforms + + \title Building for VxWorks + \brief Platform notes for VxWorks. + + The Wind River VxWorks RTOS is not yet supported but is planned for a + future release. + + For more information about developing applications for the WindRiver VxWorks + RTOS, see the \l{https://www.windriver.com/products/vxworks/} + {VxWorks Product Documentation}. +*/ diff --git a/doc/targets/qbs-target-watchos.qdoc b/doc/targets/qbs-target-watchos.qdoc new file mode 100644 index 00000000..9afec3f4 --- /dev/null +++ b/doc/targets/qbs-target-watchos.qdoc @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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 qbs-target-watchos.html + \ingroup platforms + + \title Building for watchOS + \brief Platform notes for watchOS. + + This topic describes the \QBS features specific to watchOS. + + \include qbs-target-apple-common.qdocinc xcode + \include qbs-target-apple-common.qdocinc building user interfaces + \include qbs-target-apple-common.qdocinc creating app bundles + \include qbs-target-apple-common.qdocinc architectures and variants +*/ diff --git a/doc/targets/qbs-target-windows.qdoc b/doc/targets/qbs-target-windows.qdoc new file mode 100644 index 00000000..258a5b44 --- /dev/null +++ b/doc/targets/qbs-target-windows.qdoc @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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 qbs-target-windows.html + \ingroup platforms + + \title Building for Windows + \brief Platform notes for Windows. + + This topic describes the \QBS features specific to Windows. + + \note \QBS does not currently support building applications using .NET + technologies and languages such as C#, F#, and Visual Basic. At this time we + recommend that you use MSBuild and the tools shipped with the various + implementations of the .NET platform. + + \section1 Windows Resources + + The \l{ico} module contains rules and properties for building + Windows icon (.ico) and cursor (.cur) files from a set of raw PNGs. + + \section1 Universal Windows Platform + + Building applications for the Universal Windows Platform is currently only + partially supported. Notably, support for building APPX packages is missing, + but will be added in a future release. + + Relevant properties include: + + \list + \li \l{cpp::windowsApiFamily}{cpp.windowsApiFamily} + \li \l{cpp::windowsApiAdditionalPartitions} + {cpp.windowsApiAdditionalPartitions} + \li \l{cpp::requireAppContainer}{cpp.requireAppContainer} + \endlist + + See the \l{cpp} module for more information. + + \note \QBS does not (and will not) support building Windows Runtime + applications targeting Windows 8 or Windows 8.1. We encourage users to + instead build desktop applications for older versions of Windows, or migrate + to Windows 10 and the Universal Windows Platform. + + \section1 Building Windows Installers + + The following modules contain properties and rules for building Windows + installers using a number of different technologies: + + \list + \li \l{innosetup} - Inno Setup + \li \l{nsis} - Nullsoft Scriptable Install System + \li \l{wix} - Windows Installer XML Toolset + \endlist +*/ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..b8b4528c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,61 @@ +version: "3.7" + +x-default-service: &linux + working_dir: /qbs + environment: + - BUILD_OPTIONS + volumes: + - .:/qbs + - ~/.ccache:/home/devel/.ccache + network_mode: bridge + cap_add: + - SYS_PTRACE + +services: + bionic: + << : *linux + hostname: bionic + image: ${DOCKER_USER:-qbsbuild}/qbsdev:bionic-5.12.8_1.15.1-0 + build: + dockerfile: docker/bionic/Dockerfile + context: . + args: + QT_VERSION: 5.12.8 + QTCREATOR_VERSION: 4.11.2 + + bionic-android-513: + << : *linux + hostname: bionic-android + image: ${DOCKER_USER:-qbsbuild}/qbsdev:bionic-android-5.13.2-3 + build: + dockerfile: docker/bionic/test-android.Dockerfile + context: . + args: + QT_VERSION: 5.13.2 + + bionic-android-514: + << : *linux + hostname: bionic-android + image: ${DOCKER_USER:-qbsbuild}/qbsdev:bionic-android-5.14.0-3 + build: + dockerfile: docker/bionic/test-android.Dockerfile + context: . + args: + QT_VERSION: 5.14.0 + + windows: + image: ${DOCKER_USER:-qbsbuild}/qbsdev:windowsservercore-5.12.7_1.15.0-0 + build: + dockerfile: docker/windowsservercore/Dockerfile + context: . + args: + QT_VERSION: 5.12.7 + QBS_VERSION: 1.15.0 + working_dir: 'C:/qbs' + environment: + - BUILD_OPTIONS + - WITH_DOCS + volumes: + - .:C:\qbs + - ~/.ccache:C:\.ccache + network_mode: nat diff --git a/docker/bionic/Dockerfile b/docker/bionic/Dockerfile new file mode 100644 index 00000000..204c2760 --- /dev/null +++ b/docker/bionic/Dockerfile @@ -0,0 +1,207 @@ +# +# Downloads and builds Qt from source. We do it in a +# separate stage to keep the number of dependencies low in +# the final Docker image. +# +FROM ubuntu:bionic as build-qt-mingw32_w64 +ARG QT_VERSION + +RUN apt-get update -qq && \ + apt-get install -qq -y --no-install-recommends \ + build-essential \ + ca-certificates \ + libclang-3.9-dev \ + libgl1-mesa-dev \ + mingw-w64 \ + python \ + xz-utils \ + wget + +ENV LLVM_INSTALL_DIR=/usr/lib/llvm-3.9 + +RUN mkdir -p /qt/source && \ + wget -nv --continue --tries=20 --waitretry=10 --retry-connrefused \ + --no-dns-cache --timeout 300 -qO- \ + https://download.qt.io/official_releases/qt/${QT_VERSION%??}/${QT_VERSION}/single/qt-everywhere-src-${QT_VERSION}.tar.xz \ + | tar --strip-components=1 -C /qt/source -xJf- + +RUN mkdir -p qt/build && \ + cd qt/build && \ + ../source/configure \ + -prefix /opt/Qt/${QT_VERSION}/mingw32_w64 \ + -release \ + -shared \ + -opensource \ + -confirm-license \ + -nomake examples \ + -nomake tests \ + -xplatform win32-g++ \ + -opengl desktop \ + -device-option CROSS_COMPILE=/usr/bin/x86_64-w64-mingw32- \ + -qt-sqlite -qt-libpng \ + -no-cups -no-dbus -no-pch \ + -no-feature-accessibility \ + -skip qtactiveqt \ + -skip qt3d \ + -skip qtcanvas3d \ + -skip qtcharts \ + -skip qtconnectivity \ + -skip qtdatavis3d \ + -skip qtdeclarative \ + -skip qtdoc \ + -skip qtgamepad \ + -skip qtgraphicaleffects \ + -skip qtimageformats \ + -skip qtlocation \ + -skip qtmultimedia \ + -skip qtnetworkauth \ + -skip qtquickcontrols \ + -skip qtquickcontrols2 \ + -skip qtpurchasing \ + -skip qtremoteobjects \ + -skip qtscxml \ + -skip qtsensors \ + -skip qtserialbus \ + -skip qtserialport \ + -skip qtspeech \ + -skip qtsvg \ + -skip qttranslations \ + -skip qtwayland \ + -skip qtvirtualkeyboard \ + -skip qtwebchannel \ + -skip qtwebengine \ + -skip qtwebsockets \ + -skip qtwebview \ + -skip qtwinextras \ + -skip qtxmlpatterns \ + -skip qtx11extras + +# Build and transform stdout into . to reduce the noise +RUN cd qt/build && \ + make -j $(nproc --all) | stdbuf -o0 tr -cd '\n' | stdbuf -o0 tr '\n' '.' && \ + make install + +# +# Install Qt and Qbs for Linux and combine that with Qt for Windows from the +# previous stage +# +FROM ubuntu:bionic +LABEL Description="Ubuntu development environment for Qbs with Qt and various dependencies for testing Qbs modules and functionality" +ARG QT_VERSION +ARG QTCREATOR_VERSION + +# Allow colored output on command line. +ENV TERM=xterm-color + +# +# Make it possible to change UID/GID in the entrypoint script. The docker +# container usually runs as root user on Linux hosts. When the Docker container +# mounts a folder on the host and creates files there, those files would be +# owned by root instead of the current user. Thus we create a user here who's +# UID will be changed in the entrypoint script to match the UID of the current +# host user. +# +ARG USER_UID=1000 +ARG USER_NAME=devel +RUN apt-get update -qq && \ + apt-get install -qq -y \ + ca-certificates \ + gosu \ + sudo && \ + groupadd -g ${USER_UID} ${USER_NAME} && \ + useradd -s /bin/bash -u ${USER_UID} -g ${USER_NAME} -o -c "" -m ${USER_NAME} && \ + usermod -a -G sudo ${USER_NAME} && \ + echo "%devel ALL = (ALL) NOPASSWD: ALL" >> /etc/sudoers + +COPY docker/bionic/entrypoint.sh /sbin/entrypoint.sh +ENTRYPOINT ["/sbin/entrypoint.sh"] + +# Qbs build dependencies +RUN apt-get update -qq && \ + apt-get install -qq -y --no-install-recommends \ + bison \ + build-essential \ + ca-certificates \ + ccache \ + clang-8 \ + clang-tidy-8 \ + curl \ + flex \ + git \ + help2man \ + icoutils \ + libclang-3.9-dev \ + libdbus-1-3 \ + libfreetype6 \ + libfontconfig1 \ + libgl1-mesa-dev \ + libgl1-mesa-glx \ + libprotobuf-dev \ + libgrpc++-dev \ + nsis \ + pkg-config \ + protobuf-compiler \ + protobuf-compiler-grpc \ + psmisc \ + python3-pip \ + python3-setuptools \ + p7zip-full \ + subversion \ + unzip \ + zip && \ + update-alternatives --install /usr/bin/clang clang /usr/bin/clang-8 100 && \ + update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-8 100 && \ + update-alternatives --install /usr/bin/clang-check clang-check /usr/bin/clang-check-8 100 && \ + update-alternatives --install /usr/bin/python python /usr/bin/python3 100 && \ + update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 100 && \ + pip install beautifulsoup4 lxml pyyaml + +ENV LLVM_INSTALL_DIR=/usr/lib/llvm-8 + + +# +# Install Qt and Qbs for Linux from qt.io +# +COPY scripts/install-qt.sh install-qt.sh + +RUN ./install-qt.sh --version ${QT_VERSION} qtbase qtdeclarative qtscript qttools qtx11extras qtscxml icu && \ + ./install-qt.sh --version ${QTCREATOR_VERSION} qtcreator && \ + echo "export PATH=/opt/Qt/${QT_VERSION}/gcc_64/bin:/opt/Qt/Tools/QtCreator/bin:\${PATH}" > /etc/profile.d/qt.sh + +ENV PATH=/opt/Qt/${QT_VERSION}/gcc_64/bin:/opt/Qt/Tools/QtCreator/bin:${PATH} + + +# +# Install Qt installation from build stage +# +COPY --from=build-qt-mingw32_w64 /opt/Qt/${QT_VERSION}/mingw32_w64 /opt/Qt/${QT_VERSION}/mingw32_w64 + +# +# Install mingw toolchain to cross build for Windows and select +# POSIX API to make use of threading support in the stl. That +# is required by Qbs. +# +RUN apt-get install -qq -y --no-install-recommends \ + mingw-w64 && \ + printf "1\n" | update-alternatives --config x86_64-w64-mingw32-g++ + + +# Configure Qbs +USER $USER_NAME +RUN qbs-setup-toolchains /usr/bin/g++ gcc && \ + qbs-setup-toolchains /usr/bin/clang clang && \ + qbs-setup-qt /opt/Qt/${QT_VERSION}/gcc_64/bin/qmake qt-gcc_64 && \ + qbs config profiles.qt-gcc_64.baseProfile gcc && \ + qbs-setup-qt /opt/Qt/${QT_VERSION}/gcc_64/bin/qmake qt-clang_64 && \ + qbs config profiles.qt-clang_64.baseProfile clang && \ + qbs config defaultProfile qt-gcc_64 && \ + \ + qbs-setup-toolchains /usr/bin/x86_64-w64-mingw32-g++ mingw && \ + qbs-setup-qt /opt/Qt/${QT_VERSION}/mingw32_w64/bin/qmake qt-mingw32_w64 && \ + qbs config profiles.qt-mingw32_w64.baseProfile mingw + +# Switch back to root user for the entrypoint script. +USER root + +# Work-around for QTBUG-79020 +RUN echo "export QT_NO_GLIB=1" >> /etc/profile.d/qt.sh diff --git a/docker/bionic/entrypoint.sh b/docker/bionic/entrypoint.sh new file mode 100755 index 00000000..40bc5acb --- /dev/null +++ b/docker/bionic/entrypoint.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +set -e + +############################################################################# +## +## Copyright (C) 2019 Richard Weickelt +## 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$ +## +############################################################################# + +# +# Entrypoint script when starting the container. The script checks the current +# working directory and changes the uid/gid of developer/users to match whatever +# is found in the working directory. This is useful to match the user and group +# of mounted volumes into the container + +# +# If not root, re-run script as root to fix ids +# +if [ "$(id -u)" != "0" ]; then + exec gosu root /sbin/entrypoint.sh "$@" +fi + +# +# Try to determine the uid of the working directory and adjust the current +# user's uid/gid accordingly. +# +USER_GID=${USER_GID:-$(stat -c "%g" .)} +USER_UID=${USER_UID:-$(stat -c "%u" .)} +USER_NAME=${USER_NAME:-devel} +USER_GROUP=${USER_GROUP:-devel} +EXEC="" +export HOME=/home/${USER_NAME} + +# +# This is a problem on Linux hosts when we mount a folder from the +# user file system and write artifacts into that. Thus, we downgrade +# the current user and make sure that the uid and gid matches the one +# of the mounted project folder. +# +# This work-around is not needed on Windows hosts as Windows doesn't +# have such a concept. +# +if [ "${USER_UID}" != "0" ]; then + if [ "$(id -u ${USER_NAME})" != "${USER_UID}" ]; then + usermod -o -u ${USER_UID} ${USER_NAME} + # After changing the user's uid, all files in user's home directory + # automatically get the new uid. + fi + current_gid=$(id -g ${USER_NAME}) + if [ "$(id -g ${USER_NAME})" != "${USER_GID}" ]; then + groupmod -o -g ${USER_GID} ${USER_GROUP} + # Set the new gid on all files in the home directory that still have the + # old gid. + find /home/${USER_NAME} -gid "${current_gid}" ! -type l -exec chgrp ${USER_GID} {} \; + fi +fi +EXEC="exec gosu ${USER_NAME}:${USER_GROUP}" + +if [ -z "$1" ]; then + ${EXEC} bash -l +else + ${EXEC} bash -l -c "$*" +fi diff --git a/docker/bionic/test-android.Dockerfile b/docker/bionic/test-android.Dockerfile new file mode 100644 index 00000000..3ec8b480 --- /dev/null +++ b/docker/bionic/test-android.Dockerfile @@ -0,0 +1,111 @@ +# +# Android SDK/NDK + Qt for Android for testing Qbs +# +FROM ubuntu:bionic +LABEL Description="Ubuntu test environment for Qbs and Qt for Android" + +# Allow colored output on command line. +ENV TERM=xterm-color + +# +# Make it possible to change UID/GID in the entrypoint script. The docker +# container usually runs as root user on Linux hosts. When the Docker container +# mounts a folder on the host and creates files there, those files would be +# owned by root instead of the current user. Thus we create a user here who's +# UID will be changed in the entrypoint script to match the UID of the current +# host user. +# +ARG USER_UID=1000 +ARG USER_NAME=devel +RUN apt-get update -qq && \ + apt-get install -qq -y \ + ca-certificates \ + gosu \ + sudo && \ + groupadd -g ${USER_UID} ${USER_NAME} && \ + useradd -s /bin/bash -u ${USER_UID} -g ${USER_NAME} -o -c "" -m ${USER_NAME} && \ + usermod -a -G sudo ${USER_NAME} && \ + echo "%devel ALL = (ALL) NOPASSWD: ALL" >> /etc/sudoers + +COPY docker/bionic/entrypoint.sh /sbin/entrypoint.sh +ENTRYPOINT ["/sbin/entrypoint.sh"] + +# Qbs build dependencies +RUN apt-get update -qq && \ + apt-get install -qq -y --no-install-recommends \ + ca-certificates \ + curl \ + libglib2.0-0 \ + libgl1-mesa-glx \ + openjdk-8-jdk-headless \ + p7zip-full \ + unzip + +ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 +RUN echo "export JAVA_HOME=${JAVA_HOME}" > /etc/profile.d/android.sh && \ + echo "export PATH=${JAVA_HOME}/bin:\${PATH}" >> /etc/profile.d/android.sh + +ENV ANDROID_HOME="/home/${USER_NAME}/android" +ENV ANDROID_SDK_ROOT=${ANDROID_HOME} +ENV ANDROID_NDK_ROOT=${ANDROID_HOME}/ndk-bundle +ENV PATH="${JAVA_HOME}:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/tools/bin:$PATH" +RUN echo "export ANDROID_HOME=/home/${USER_NAME}/android" >> /etc/profile.d/android.sh && \ + echo "export ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}" >> /etc/profile.d/android.sh && \ + echo "export ANDROID_NDK_ROOT=${ANDROID_NDK_ROOT}" >> /etc/profile.d/android.sh && \ + echo "export PATH=${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/tools/bin:\$PATH" >> /etc/profile.d/android.sh + +# +# We ned to run the following steps as the target user +# +USER ${USER_NAME} +RUN mkdir ${ANDROID_HOME} + +# Get Android SDK TOOLS +ARG SDK_TOOLS_VERSION="4333796" +RUN curl -s https://dl.google.com/android/repository/sdk-tools-linux-${SDK_TOOLS_VERSION}.zip > ${ANDROID_HOME}/sdk.zip && \ + unzip ${ANDROID_HOME}/sdk.zip -d ${ANDROID_HOME} && \ + rm -v ${ANDROID_HOME}/sdk.zip + +# Accept SDK license +ARG ANDROID_PLATFORM="android-29" +ARG BUILD_TOOLS="28.0.3" +RUN yes | sdkmanager --verbose --licenses && \ + sdkmanager --update && \ + sdkmanager "platforms;${ANDROID_PLATFORM}" "build-tools;${BUILD_TOOLS}" "platform-tools" "tools" "ndk-bundle" && \ + /usr/lib/jvm/java-8-openjdk-amd64/bin/keytool -genkey -keystore /home/${USER_NAME}/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname 'CN=Android Debug,O=Android,C=US' + +# Install ndk samples in ${ANDROID_NDK_ROOT}/samples +RUN cd ${ANDROID_NDK_ROOT} && \ + curl -sLO https://github.com/android/ndk-samples/archive/master.zip && \ + unzip -q master.zip && \ + rm -v master.zip && \ + mv ndk-samples-master samples + +# Install android-BasicMediaDecoder in ${ANDROID_SDK_ROOT}/samples +RUN mkdir ${ANDROID_SDK_ROOT}/samples && \ + cd ${ANDROID_SDK_ROOT}/samples && \ + curl -sLO https://github.com/googlearchive/android-BasicMediaDecoder/archive/master.zip && \ + unzip -q master.zip && \ + rm -v master.zip && \ + mv android-BasicMediaDecoder-master android-BasicMediaDecoder + +# Download buildtool to generate aab packages in ${ANDROID_SDK_ROOT} +RUN cd ${ANDROID_SDK_ROOT} && \ + curl -sLO https://github.com/google/bundletool/releases/download/0.15.0/bundletool-all-0.15.0.jar + +USER root + +# +# Install Qt and Qbs for Linux from qt.io +# +ARG QT_VERSION +COPY scripts/install-qt.sh install-qt.sh +RUN if [ "${QT_VERSION}" \< "5.14" ]; then \ + QT_ABIS="android_armv7 android_arm64_v8a android_x86 android_x86_64"; \ + else \ + QT_ABIS="any"; \ + fi; \ + for abi in ${QT_ABIS}; do \ + ./install-qt.sh --version ${QT_VERSION} --target android --toolchain ${abi} qtbase qtdeclarative qttools qtimageformats; \ + done && \ + echo "export QT_VERSION=${QT_VERSION}" >> /etc/profile.d/qt.sh diff --git a/docker/docker.qbs b/docker/docker.qbs new file mode 100644 index 00000000..513a5fd8 --- /dev/null +++ b/docker/docker.qbs @@ -0,0 +1,8 @@ + +// This is a convenience product to be able to use Qt Creator for editing the docker files. +// For building and managing the images, use docker-compose as explained in +// https://doc.qt.io/qbs/building-qbs.html#using-docker. +Product { + name: "docker" + files: "**" +} diff --git a/docker/windowsservercore/Dockerfile b/docker/windowsservercore/Dockerfile new file mode 100644 index 00000000..be3777aa --- /dev/null +++ b/docker/windowsservercore/Dockerfile @@ -0,0 +1,51 @@ + +FROM mcr.microsoft.com/windows/servercore:1809 +LABEL Description="Windows Server Core development environment for Qbs with Qt, Chocolatey and various dependencies for testing Qbs modules and functionality" + +# Disable crash dialog for release-mode runtimes +RUN reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting" /v Disabled /t REG_DWORD /d 1 /f +RUN reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting" /v DontShowUI /t REG_DWORD /d 1 /f + +RUN powershell -NoProfile -ExecutionPolicy Bypass -Command \ + $Env:chocolateyVersion = '0.10.8' ; \ + $Env:chocolateyUseWindowsCompression = 'false' ; \ + "[Net.ServicePointManager]::SecurityProtocol = \"tls12, tls11, tls\"; iex ((New-Object System.Net.WebClient).DownloadString('http://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" + +# Wait for vs_installer.exe, vs_installerservice.exe +# or vs_installershell.exe because choco doesn't +RUN powershell -NoProfile -InputFormat None -Command \ + choco install visualcpp-build-tools --version 15.0.26228.20170424 -y; \ + Write-Host 'Waiting for Visual C++ Build Tools to finish'; \ + Wait-Process -Name vs_installer + +ARG QBS_VERSION +RUN choco install -y python && \ + choco install -y 7zip --version 19.0 && \ + choco install -y git --version 2.24.0 --params "/GitAndUnixToolsOnPath" && \ + choco install -y qbs --version %QBS_VERSION% + +# for building the documentation +RUN pip install beautifulsoup4 lxml + +# clcache for speeding up MSVC builds +ENV CLCACHE_DIR="C:/.ccache" +RUN certutil -generateSSTFromWU roots.sst && \ + certutil -addstore -f root roots.sst && \ + del roots.sst && \ + pip install --trusted-host=pypi.org \ + git+https://github.com/frerich/clcache.git@cae73d8255d78db8ba11e23c51fd2c9a89e7475b + +########### Install Qt ############# +ARG QT_VERSION +COPY scripts/install-qt.sh install-qt.sh + +RUN bash -c "./install-qt.sh -d /c/Qt --version ${QT_VERSION} --toolchain win64_msvc2017_64 qtbase qtdeclarative qttools qtscript" +ENV QTDIR64=C:\\Qt\\${QT_VERSION}\\msvc2017_64 + +RUN bash -c "./install-qt.sh -d /c/Qt --version ${QT_VERSION} --toolchain win32_msvc2017 qtbase qtdeclarative qttools qtscript" +ENV QTDIR=C:\\Qt\\${QT_VERSION}\\msvc2017 + +RUN qbs setup-toolchains --detect && \ + qbs setup-qt %QTDIR64%/bin/qmake.exe qt64 && \ + qbs setup-qt %QTDIR%/bin/qmake.exe qt && \ + qbs config defaultProfile qt64 diff --git a/examples/app-and-lib/app-and-lib.qbs b/examples/app-and-lib/app-and-lib.qbs new file mode 100644 index 00000000..baa42307 --- /dev/null +++ b/examples/app-and-lib/app-and-lib.qbs @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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 1.0 + +Project { + references: [ + "app/app.qbs", + "lib/lib.qbs" + ] +} + diff --git a/examples/app-and-lib/app/app.qbs b/examples/app-and-lib/app/app.qbs new file mode 100644 index 00000000..e9439187 --- /dev/null +++ b/examples/app-and-lib/app/app.qbs @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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 1.0 + +Application { + consoleApplication: true + files : [ "main.cpp" ] + Depends { name: "cpp" } + Depends { name: "mylib" } +} + diff --git a/examples/app-and-lib/app/main.cpp b/examples/app-and-lib/app/main.cpp new file mode 100644 index 00000000..17f4226f --- /dev/null +++ b/examples/app-and-lib/app/main.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** 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 +#include + +int main() +{ + puts("Now calling a function from mylib:"); + return bla(); +} diff --git a/examples/app-and-lib/lib/lib.cpp b/examples/app-and-lib/lib/lib.cpp new file mode 100644 index 00000000..bfa62ecf --- /dev/null +++ b/examples/app-and-lib/lib/lib.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** 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 + +#ifndef CRUCIAL_DEFINE +# error CRUCIAL_DEFINE not defined +#endif + +int bla() +{ + puts("Hello World!"); + return 2; +} diff --git a/examples/app-and-lib/lib/lib.h b/examples/app-and-lib/lib/lib.h new file mode 100644 index 00000000..21d83e35 --- /dev/null +++ b/examples/app-and-lib/lib/lib.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef LIB_H +#define LIB_H + +int bla(); + +#endif // LIB_H diff --git a/examples/app-and-lib/lib/lib.qbs b/examples/app-and-lib/lib/lib.qbs new file mode 100644 index 00000000..66ec4ecc --- /dev/null +++ b/examples/app-and-lib/lib/lib.qbs @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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 1.0 + +StaticLibrary { + name: "mylib" + files: [ + "lib.cpp", + "lib.h", + ] + Depends { name: 'cpp' } + cpp.defines: ['CRUCIAL_DEFINE'] + + Export { + Depends { name: "cpp" } + cpp.includePaths: [product.sourceDirectory] + } +} + diff --git a/examples/baremetal/at90can128olimex/at90can128olimex.qbs b/examples/baremetal/at90can128olimex/at90can128olimex.qbs new file mode 100644 index 00000000..dc1760fe --- /dev/null +++ b/examples/baremetal/at90can128olimex/at90can128olimex.qbs @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 + +Project { + name: "Examples for Olimex AVR-CAN board" + references: [ + "redblink/redblink.qbs" + ] +} diff --git a/examples/baremetal/at90can128olimex/redblink/README.md b/examples/baremetal/at90can128olimex/redblink/README.md new file mode 100644 index 00000000..cbf51898 --- /dev/null +++ b/examples/baremetal/at90can128olimex/redblink/README.md @@ -0,0 +1,9 @@ +This example demonstrates how to build a bare-metal application using +different AVR toolchains. It is designed for the OLIMEX AVR-CAN target +board (based on at90can128 chip) and simply flashes the red LED on the +board. + +The following toolchains are supported: + + * GCC + * IAR Embedded Workbench diff --git a/examples/baremetal/at90can128olimex/redblink/gpio.c b/examples/baremetal/at90can128olimex/redblink/gpio.c new file mode 100644 index 00000000..543e31a8 --- /dev/null +++ b/examples/baremetal/at90can128olimex/redblink/gpio.c @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "gpio.h" + +#if defined(__GNUC__) +#include +#elif defined (__ICCAVR__) +#include +#else +#error "Unsupported toolchain" +#endif + +// LED pin number. +#define GPIO_RED_LED_PIN_POS (4u) +// LED port direction. +#define GPIO_RED_LED_PORT_DIR (DDRE) +// LED output port. +#define GPIO_RED_LED_PORT_OUT (PORTE) + +void gpio_init_red_led(void) +{ + GPIO_RED_LED_PORT_DIR = 0xFF; +} + +void gpio_toggle_red_led(void) +{ + GPIO_RED_LED_PORT_OUT ^= (1u << GPIO_RED_LED_PIN_POS); +} diff --git a/examples/baremetal/at90can128olimex/redblink/gpio.h b/examples/baremetal/at90can128olimex/redblink/gpio.h new file mode 100644 index 00000000..246eab86 --- /dev/null +++ b/examples/baremetal/at90can128olimex/redblink/gpio.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef GPIO_H +#define GPIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +void gpio_init_red_led(void); +void gpio_toggle_red_led(void); + +#ifdef __cplusplus +} +#endif + +#endif // GPIO_H diff --git a/examples/baremetal/at90can128olimex/redblink/main.c b/examples/baremetal/at90can128olimex/redblink/main.c new file mode 100644 index 00000000..d62f9035 --- /dev/null +++ b/examples/baremetal/at90can128olimex/redblink/main.c @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "gpio.h" + +#include + +static void some_delay(uint32_t counts) +{ + for (uint32_t index = 0u; index < counts; ++index) + __asm("nop"); +} + +int main(void) +{ + gpio_init_red_led(); + + while (1) { + gpio_toggle_red_led(); + some_delay(100000u); + } +} diff --git a/examples/baremetal/at90can128olimex/redblink/redblink.qbs b/examples/baremetal/at90can128olimex/redblink/redblink.qbs new file mode 100644 index 00000000..84323e3c --- /dev/null +++ b/examples/baremetal/at90can128olimex/redblink/redblink.qbs @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 { + condition: { + if (!qbs.architecture.contains("avr")) + return false; + return qbs.toolchain.contains("gcc") + || qbs.toolchain.contains("iar") + } + name: "at90can128olimex-redblink" + cpp.cLanguageVersion: "c99" + cpp.positionIndependentCode: false + + // + // GCC-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.driverFlags: ["-mmcu=at90can128"] + } + + // Note: We implicitly use the startup file and the linker script + // which are already linked into the avr-gcc toolchain runtime. + + // + // IAR-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("iar") + cpp.driverFlags: ["--cpu=can128", "-ms"] + cpp.entryPoint: "__program_start" + cpp.driverLinkerFlags: [ + "-D_..X_HEAP_SIZE=0", + "-D_..X_NEAR_HEAP_SIZE=20", + "-D_..X_CSTACK_SIZE=20", + "-D_..X_RSTACK_SIZE=20", + "-D_..X_FLASH_CODE_END=1FFFF", + "-D_..X_FLASH_BASE=_..X_INTVEC_SIZE", + "-D_..X_EXT_SRAM_BASE=_..X_SRAM_END", + "-D_..X_EXT_ROM_BASE=_..X_SRAM_END", + "-D_..X_EXT_NV_BASE=_..X_SRAM_END", + "-D_..X_CSTACK_BASE=_..X_SRAM_BASE", + "-D_..X_CSTACK_END=_..X_SRAM_END", + "-D_..X_RSTACK_BASE=_..X_SRAM_BASE", + "-D_..X_RSTACK_END=_..X_SRAM_END", + "-h'1895'(CODE)0-(_..X_INTVEC_SIZE-1)", + ] + cpp.staticLibraries: [ + // Explicitly link with the runtime dlib library (which contains + // all required startup code and other stuff). + cpp.toolchainInstallPath + "/../lib/dlib/dlAVR-3s-ec_mul-n.r90" + ] + } + + Group { + condition: qbs.toolchain.contains("iar") + name: "IAR" + prefix: "iar/" + Group { + name: "Linker Script" + prefix: cpp.toolchainInstallPath + "/../src/template/" + fileTags: ["linkerscript"] + // Explicitly use the default linker scripts for current target. + files: ["cfg3soim.xcl", "cfgcan128.xcl"] + } + } + + // + // Common code. + // + + Group { + name: "Gpio" + files: ["gpio.c", "gpio.h"] + } + + files: ["main.c"] +} diff --git a/examples/baremetal/baremetal.qbs b/examples/baremetal/baremetal.qbs new file mode 100644 index 00000000..8cb9692f --- /dev/null +++ b/examples/baremetal/baremetal.qbs @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 + +Project { + name: "BareMetal" + references: [ + "stm32f4discovery/stm32f4discovery.qbs", + "at90can128olimex/at90can128olimex.qbs", + "cc2540usbdongle/cc2540usbdongle.qbs", + "stm8s103f3/stm8s103f3.qbs", + "msp430f5529/msp430f5529.qbs", + "cy7c68013a/cy7c68013a.qbs", + "stm32f103/stm32f103.qbs", + "pca10040/pca10040.qbs", + ] +} diff --git a/examples/baremetal/cc2540usbdongle/cc2540usbdongle.qbs b/examples/baremetal/cc2540usbdongle/cc2540usbdongle.qbs new file mode 100644 index 00000000..52e1948e --- /dev/null +++ b/examples/baremetal/cc2540usbdongle/cc2540usbdongle.qbs @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 + +Project { + name: "Examples for cc2540usbdongle board" + references: [ + "greenblink/greenblink.qbs" + ] +} diff --git a/examples/baremetal/cc2540usbdongle/greenblink/README.md b/examples/baremetal/cc2540usbdongle/greenblink/README.md new file mode 100644 index 00000000..4e90293e --- /dev/null +++ b/examples/baremetal/cc2540usbdongle/greenblink/README.md @@ -0,0 +1,9 @@ +This example demonstrates how to build a bare-metal application using +different MCS-51 toolchains. It is designed for the CC2540 usb dongle +target board (based on Texas Instruments CC2540 wireless MCU) and +simply flashes the green LED on the board. + +The following toolchains are supported: + + * IAR Embedded Workbench + * KEIL uVision diff --git a/examples/baremetal/cc2540usbdongle/greenblink/gpio.c b/examples/baremetal/cc2540usbdongle/greenblink/gpio.c new file mode 100644 index 00000000..a0947146 --- /dev/null +++ b/examples/baremetal/cc2540usbdongle/greenblink/gpio.c @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "gpio.h" +#include "system.h" + +#define GPIO_GREEN_LED_PIN_POS (1u) + +void gpio_init_green_led(void) +{ + P0SEL = 0; // Choose the port. + P0DIR = 0x01; // Set pin0 to output mode. +} + +void gpio_toggle_green_led(void) +{ + P0 ^= GPIO_GREEN_LED_PIN_POS; +} diff --git a/examples/baremetal/cc2540usbdongle/greenblink/gpio.h b/examples/baremetal/cc2540usbdongle/greenblink/gpio.h new file mode 100644 index 00000000..ddeccad0 --- /dev/null +++ b/examples/baremetal/cc2540usbdongle/greenblink/gpio.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef GPIO_H +#define GPIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +void gpio_init_green_led(void); +void gpio_toggle_green_led(void); + +#ifdef __cplusplus +} +#endif + +#endif // GPIO_H diff --git a/examples/baremetal/cc2540usbdongle/greenblink/greenblink.qbs b/examples/baremetal/cc2540usbdongle/greenblink/greenblink.qbs new file mode 100644 index 00000000..e8aa3d1f --- /dev/null +++ b/examples/baremetal/cc2540usbdongle/greenblink/greenblink.qbs @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 { + condition: { + if (!qbs.architecture.contains("mcs51")) + return false; + return qbs.toolchain.contains("iar") + || qbs.toolchain.contains("keil") + || qbs.toolchain.contains("sdcc") + } + name: "cc2540usbdongle-greenblink" + cpp.positionIndependentCode: false + + // + // IAR-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("iar") + cpp.commonCompilerFlags: ["-e"] + cpp.driverLinkerFlags: [ + "-D_IDATA_STACK_SIZE=0x40", + "-D_PDATA_STACK_SIZE=0x00", + "-D_XDATA_STACK_SIZE=0x00", + "-D_XDATA_HEAP_SIZE=0x00", + "-D_EXTENDED_STACK_SIZE=0", + "-D_EXTENDED_STACK_START=0" + ] + cpp.staticLibraries: [ + cpp.toolchainInstallPath + "/../lib/clib/cl-pli-nsid-1e16x01" + ] + } + + Group { + condition: qbs.toolchain.contains("iar") + name: "IAR" + prefix: "iar/" + Group { + name: "Linker Script" + prefix: cpp.toolchainInstallPath + "/../config/devices/_generic/" + fileTags: ["linkerscript"] + files: ["lnk51ew_8051.xcl"] + } + } + + // + // KEIL-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("keil") + cpp.driverLinkerFlags: [ + "RAMSIZE(256)", + "CODE(0x0000-0xFFFF)" + ] + } + + // + // SDCC-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("sdcc") + } + + // + // Common code. + // + + Group { + name: "Gpio" + files: ["gpio.c", "gpio.h"] + } + + Group { + name: "System" + files: ["system.h"] + } + + files: ["main.c"] +} diff --git a/examples/baremetal/cc2540usbdongle/greenblink/main.c b/examples/baremetal/cc2540usbdongle/greenblink/main.c new file mode 100644 index 00000000..88fae04a --- /dev/null +++ b/examples/baremetal/cc2540usbdongle/greenblink/main.c @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "gpio.h" +#include "system.h" + +static void some_delay(unsigned long counts) +{ + unsigned long index = 0u; + for (index = 0u; index < counts; ++index) + system_nop(); +} + +int main(void) +{ + gpio_init_green_led(); + + while (1) { + gpio_toggle_green_led(); + some_delay(20000u); + } +} diff --git a/examples/baremetal/cc2540usbdongle/greenblink/system.h b/examples/baremetal/cc2540usbdongle/greenblink/system.h new file mode 100644 index 00000000..4f105dbd --- /dev/null +++ b/examples/baremetal/cc2540usbdongle/greenblink/system.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef SYSTEM_H +#define SYSTEM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__ICC8051__) +#include +# define system_nop() __no_operation() +# define DEFINE_SFR(name,addr) __sfr __no_init volatile unsigned char name @ addr; +#elif defined (__C51__) +#include +# define system_nop() _nop_() +# define DEFINE_SFR(name, addr) sfr name = addr; +#elif defined (__SDCC_mcs51) +#include +# define DEFINE_SFR(name, addr) __sfr __at(addr) name; +# ifndef NOP +# define NOP() __asm NOP __endasm +# endif +# define system_nop() NOP() +#else +#error "Unsupported toolchain" +#endif + +DEFINE_SFR(P0 , 0x80u) // Port 0. +DEFINE_SFR(P0SEL, 0xF3u) // Port 0 function select. +DEFINE_SFR(P0DIR, 0xFDu) // Port 0 direction select. + +#ifdef __cplusplus +} +#endif + +#endif // SYSTEM_H diff --git a/examples/baremetal/cy7c68013a/cy7c68013a.qbs b/examples/baremetal/cy7c68013a/cy7c68013a.qbs new file mode 100644 index 00000000..37084472 --- /dev/null +++ b/examples/baremetal/cy7c68013a/cy7c68013a.qbs @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 + +Project { + name: "Examples for cy7c68013a board" + references: [ + "nes-gamepads/nes-gamepads.qbs" + ] +} diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/README.md b/examples/baremetal/cy7c68013a/nes-gamepads/README.md new file mode 100644 index 00000000..06d1fc77 --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/README.md @@ -0,0 +1,42 @@ +This example demonstrates how to build a bare-metal application using +different 8051 toolchains. It is designed for the target board +based on Cypress FX2 cy7c68013a chip. + +It is possible to use the official development kit from the Cypress: + +* https://www.cypress.com/documentation/development-kitsboards/cy3684-ez-usb-fx2lp-development-kit + +but, a better solution is to buy the China's analogs or replacements, +e.g. on Aliexpress. + +It implements a USB HID device that connects two 8-buttons NES +(Dendy) gamepads to a PC. The gamepads are connected to the +cy7c68013a chip as follows: + +1. CLK - it is an output clock signal which generates by chip from + the port A, pin 0 (PA0). This pin should be connected to the CLK + inputs for both gamepads. + +2. DATA1 - it is an input data signal which comes to chip on the + the port A, pin 2 (PA2). This pin should be connected to the DATA + output from the gamepad #1. + +3. DATA2 - it is an input data signal which comes to chip on the + the port A, pin 4 (PA4). This pin should be connected to the DATA + output from the gamepad #2. + +4. LATCH - it is an output clock signal which generates by chip from + the port A, pin 6 (PA6). This pin should be connected to the LATCH + inputs for both gamepads. + +Actual schematic and pinouts depends on an used gamepads (with 7, 9 +or other pins connectors) and a development boards. + +Also, do not forget to connect the +3.3V and GND wires to the gamepads. +Then it is possible to play 8-bit NES games using various PC simulators. + +The following toolchains are supported: + + * IAR Embedded Workbench + * SDCC + * KEIL C51 diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/core.c b/examples/baremetal/cy7c68013a/nes-gamepads/core.c new file mode 100644 index 00000000..17a9661e --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/core.c @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "core.h" +#include "regs.h" + +enum cpu_freq_clk { + CPU_CLK_12M = 0, + CPU_CLK_24M, + CPU_CLK_48M +}; + +#define cpu_freq_clk_get() \ + ((CPUCS & bmCLKSPD) >> 3) + +#define cpu_freq_clk_set(freq_clk) \ + CPUCS = (CPUCS & ~bmCLKSPD) | (freq_clk << 3) + +void core_init(void) +{ + // Set CPU clock to 48MHz. + cpu_freq_clk_set(CPU_CLK_48M); + sync_delay(); + // Set stretch to 0. + CKCON = ((CKCON & (~bmSTRETCH)) | bmFW_STRETCH1); + sync_delay(); + // Clear breakpoint register. + BREAKPT = 0; + sync_delay(); + // Set all 8051 interrupts to low priority + IP = 0; + sync_delay(); + // Clear interrupt enable bits. + EIE = 0; + sync_delay(); + // Clear all external interrupt flags. + EXIF = 0; + sync_delay(); +} diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/core.h b/examples/baremetal/cy7c68013a/nes-gamepads/core.h new file mode 100644 index 00000000..a53ad2a4 --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/core.h @@ -0,0 +1,298 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef FX2_HW_H +#define FX2_HW_H + +#include "defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Allowed range: 5000 - 48000. +#ifndef CORE_IFREQ +# define CORE_IFREQ 48000 // IFCLK frequency in kHz. +#endif + +// Allowed values: 48000, 24000, or 12000. +#ifndef CORE_CFREQ +# define CORE_CFREQ 48000 // CLKOUT frequency in kHz. +#endif + +#if (CORE_IFREQ < 5000) +# error "CORE_IFREQ too small! Valid range: 5000 - 48000." +#endif + +#if (CORE_IFREQ > 48000) +# error "CORE_IFREQ too large! Valid range: 5000 - 48000." +#endif + +#if (CORE_CFREQ != 48000) +# if (CORE_CFREQ != 24000) +# if (CORE_CFREQ != 12000) +# error "CORE_CFREQ invalid! Valid values: 48000, 24000, 12000." +# endif +# endif +#endif + +// Calculate synchronization delay. +#define CORE_SCYCL (3 * (CORE_CFREQ) + 5 * (CORE_IFREQ) - 1) / (2 * (CORE_IFREQ)) + +#if (CORE_SCYCL == 1) +# define sync_delay() {\ + NOP(); } +#endif + +#if (CORE_SCYCL == 2) +# define sync_delay() {\ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 3) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 4) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 5) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 6) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 7) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 8) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 9) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 10) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 11) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 12) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 13) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 14) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 15) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#if (CORE_SCYCL == 16) +# define sync_delay() {\ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); \ + NOP(); } +#endif + +#define code_all_irq_disable() (IE = 0) +#define code_all_irq_enable() (IE = 1) + +void core_init(void); + +#ifdef __cplusplus +} +#endif + +#endif // FX2_HW_H diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/defs.h b/examples/baremetal/cy7c68013a/nes-gamepads/defs.h new file mode 100644 index 00000000..7fb36fee --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/defs.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef FX2_DEFS_H +#define FX2_DEFS_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum bit_mask { + bmBIT0 = 0x01, + bmBIT1 = 0x02, + bmBIT2 = 0x04, + bmBIT3 = 0x08, + bmBIT4 = 0x10, + bmBIT5 = 0x20, + bmBIT6 = 0x40, + bmBIT7 = 0x80 +}; + +enum boolean { + FALSE = 0, + TRUE +}; + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned long DWORD; +typedef unsigned char BOOL; + +#ifndef NULL +#define NULL (void *)0 +#endif + +#if defined(__ICC8051__) + +#include + +# define NOP() __no_operation() + +# define XDATA __xdata +# define CODE __code +# define AT __at + +# define SFR __sfr +# define SBIT __bit + +# define XDATA_REG(reg_name, reg_addr) \ + XDATA __no_init volatile BYTE reg_name @ reg_addr; + +# define SPEC_FUN_REG(reg_name, reg_addr) \ + SFR __no_init volatile BYTE reg_name @ reg_addr; + +# define SPEC_FUN_REG_BIT(bit_name, reg_addr, bit_num) \ + SBIT __no_init volatile _Bool bit_name @ (reg_addr+bit_num); + +# define _PPTOSTR_(x) #x +# define _PPARAM_(address) _PPTOSTR_(vector=address * 8 + 3) +# define INTERRUPT(isr_name, vector) \ + _Pragma(_PPARAM_(vector)) __interrupt void isr_name(void) + +#elif defined (__C51__) + +# include + +# define NOP() _nop_() + +# define XDATA xdata +# define CODE code +# define AT _at_ + +# define SFR sfr +# define SBIT sbit + +# if defined(DEFINE_REGS) +# define XDATA_REG(reg_name, reg_addr) \ + XDATA volatile BYTE reg_name AT reg_addr; +# else +# define XDATA_REG(reg_name, reg_addr) \ + extern XDATA volatile BYTE reg_name; +# endif + +# define SPEC_FUN_REG(reg_name, reg_addr) \ + SFR reg_name = reg_addr; + +# define SPEC_FUN_REG_BIT(bit_name, reg_addr, bit_num) \ + sbit bit_name = reg_addr + bit_num; + +# define INTERRUPT(isr_name, num) \ + void isr_name (void) interrupt num + +#elif defined (__SDCC_mcs51) + +# define NOP() __asm nop __endasm + +# define XDATA __xdata +# define CODE __code +# define AT __at + +# define SFR __sfr +# define SBIT __sbit + +# define XDATA_REG(reg_name, reg_addr) \ + XDATA AT reg_addr volatile BYTE reg_name; + +# define SPEC_FUN_REG(reg_name, reg_addr) \ + SFR AT reg_addr reg_name; + +# define SPEC_FUN_REG_BIT(bit_name, reg_addr, bit_num) \ + SBIT AT reg_addr + bit_num bit_name; + +# define INTERRUPT(isr_name, num) \ + void isr_name (void) __interrupt num + +#else + +#error "Unsupported toolchain" + +#endif + +#ifdef __cplusplus +} +#endif + +#endif // FX2_DEFS_H diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/gpio.c b/examples/baremetal/cy7c68013a/nes-gamepads/gpio.c new file mode 100644 index 00000000..d7a74b3e --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/gpio.c @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "core.h" +#include "gpio.h" +#include "hid.h" +#include "regs.h" + +static WORD g_counter = 0; + +// We use this empirical value to make a GPIO polling +// every ~10 msec (rough). It is a simplest way, because +// instead we need to use a timers with interrupts callbacks. +enum { POLLING_COUNTER = 580 }; + +// Pins on the port A. +enum gpio_pins { + GPIO_CLK_PIN = bmBIT0, + GPIO_DATA1_PIN = bmBIT2, + GPIO_DATA2_PIN = bmBIT4, + GPIO_LATCH_PIN = bmBIT6, +}; + +static struct { + const BYTE pin; + BYTE buttons; +} g_reports[HID_REPORTS_COUNT] = { + {GPIO_DATA1_PIN, 0}, + {GPIO_DATA2_PIN, 0} +}; + +// Pulse width around ~1 usec. +static void gpio_latch_pulse(void) +{ + IOA |= GPIO_LATCH_PIN; + sync_delay(); + sync_delay(); + sync_delay(); + IOA &= ~GPIO_LATCH_PIN; + sync_delay(); +} + +// Pulse width around ~1 usec. +static void gpio_clk_pulse(void) +{ + IOA |= GPIO_CLK_PIN; + sync_delay(); + sync_delay(); + sync_delay(); + IOA &= ~GPIO_CLK_PIN; + sync_delay(); +} + +static void gpio_reports_clean(void) +{ + BYTE index = 0; + for (index = 0; index < HID_REPORTS_COUNT; ++index) + g_reports[index].buttons = 0; +} + +static void gpio_reports_send(void) +{ + BYTE index = 0; + for (index = 0; index < HID_REPORTS_COUNT; ++index) + hid_ep1_report_update(index, g_reports[index].buttons); +} + +static void gpio_poll(void) +{ + BYTE pos = 0; + + // Cleanup reports. + gpio_reports_clean(); + + // Send latch pulse. + gpio_latch_pulse(); + + for (pos = 0; pos < HID_REPORT_BITS_COUNT; ++pos) { + // TODO: Add some nops here? + + BYTE index = 0; + for (index = 0; index < HID_REPORTS_COUNT; ++index) { + // Low state means that a button is pressed. + const BOOL v = (IOA & g_reports[index].pin) ? 0 : 1; + g_reports[index].buttons |= (v << pos); + } + + // Send clock pulse. + gpio_clk_pulse(); + } + + gpio_reports_send(); +} + +void gpio_init(void) +{ + // Set interface to ports. + IFCONFIG = (IFCONFIG & (~bmIFCFG)) | bmIFPORTS; + sync_delay(); + + // Initialize the CLK and LATCH pins as output. + OEA = GPIO_CLK_PIN | GPIO_LATCH_PIN; + sync_delay(); +} + +void gpio_task(void) +{ + if (g_counter < POLLING_COUNTER) { + ++g_counter; + } else { + g_counter = 0; + gpio_poll(); + } +} diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/gpio.h b/examples/baremetal/cy7c68013a/nes-gamepads/gpio.h new file mode 100644 index 00000000..d2568fd7 --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/gpio.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef FX2_GPIO_H +#define FX2_GPIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +void gpio_init(void); +void gpio_task(void); + +#ifdef __cplusplus +} +#endif + +#endif // FX2_GPIO_H diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/hid.c b/examples/baremetal/cy7c68013a/nes-gamepads/hid.c new file mode 100644 index 00000000..cca4edab --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/hid.c @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "hid.h" +#include "core.h" +#include "usb.h" + +void hid_init(void) +{ + // Disable end point 1 output. + EP1OUTCFG = bmEP_DISABLE; + sync_delay(); + // Enable end point 1 input in interrupt mode. + EP1INCFG = bmEP_ENABLE | bmEP_INT; + sync_delay(); + // Disable end point 2. + EP2CFG = bmEP_DISABLE; + sync_delay(); + // Disable end point 4. + EP4CFG = bmEP_DISABLE; + sync_delay(); + // Disable end point 6. + EP6CFG = bmEP_DISABLE; + sync_delay(); + // Disable end point 8. + EP8CFG = bmEP_DISABLE; + sync_delay(); + + // Reset all FIFOs. + FIFORESET = bmNAKALL; // NAK all host transfers. + sync_delay(); + FIFORESET = 0x00; + sync_delay(); + + // Enable dual autopointer(s). + AUTOPTRSETUP |= bmAPTREN; + sync_delay(); +} diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/hid.h b/examples/baremetal/cy7c68013a/nes-gamepads/hid.h new file mode 100644 index 00000000..c51b8737 --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/hid.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef FX2_HID_H +#define FX2_HID_H + +#include "usb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum hid_constants { + HID_CONFIG_NUMBER = 1, // Number of valid configuration. + HID_IFACE_NUMBER = 0, // Number of valid interface. + HID_ALT_IFACE_NUMBER = 0, // Number of valid alternate interface. + HID_EP_IN = 0x81 // Active end point address. +}; + +enum hid_gamepad_id { + HID_REPORT_ID_GAMEPAD1 = 1, + HID_REPORT_ID_GAMEPAD2 = 2 +}; + +enum { + HID_REPORTS_COUNT = 2, + HID_REPORT_BITS_COUNT = 8 +}; + +void hid_init(void); + +void hid_ep0_init(void); +void hid_ep0_setup_task(void); + +const BYTE CODE *hid_ep0_std_desc_get(void); +const BYTE CODE *hid_ep0_report_desc_get(WORD *length); + +void hid_ep1_task(void); +void hid_ep1_report_update(BYTE index, BYTE buttons); + +#ifdef __cplusplus +} +#endif + +#endif // FX2_HID_H diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/hiddesc.c b/examples/baremetal/cy7c68013a/nes-gamepads/hiddesc.c new file mode 100644 index 00000000..74601f24 --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/hiddesc.c @@ -0,0 +1,345 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "hid.h" + +enum usb_bcd_version { + USB_SPEC_BCD_VERSION = 0x0200, + USB_DEVICE_BCD_VERSION = 0x1234, + USB_HID_BCD_VERSION = 0x0111 +}; + +enum usb_ids { + USB_DEVICE_VID = 0xFFFF, + USB_DEVICE_PID = 0xFFFF +}; + +enum usb_lang_id { + USB_LANG_ID = 0x0409 +}; + +enum usb_descriptor_string_index { + USB_DESC_LANGID_STRING_INDEX = 0, + USB_DESC_MFG_STRING_INDEX, + USB_DESC_PRODUCT_STRING_INDEX, + USB_DESC_SERIAL_STRING_INDEX +}; + +enum usb_descr_length { + // Standard length. + USB_DESC_DEVICE_LEN = 18, + USB_DESC_DEVICE_QUAL_LEN = 10, + USB_DESC_CONF_LEN = 9, + USB_DESC_OTHER_SPEED_CONF_LEN = 9, + USB_DESC_INTERFACE_LEN = 9, + USB_DESC_HID_LEN = 9, + USB_DESCR_HID_REP_LEN = 56, + USB_DESC_ENDPOINT_LEN = 7, + + // Total length. + USB_DESC_DEVICE_TOTAL_LEN = USB_DESC_DEVICE_LEN, + USB_DESC_DEVICE_QUAL_TOTAL_LEN = USB_DESC_DEVICE_QUAL_LEN, + + USB_DESC_CONF_TOTAL_LEN = USB_DESC_CONF_LEN + + USB_DESC_INTERFACE_LEN + + USB_DESC_HID_LEN + USB_DESC_ENDPOINT_LEN, + + USB_DESC_OTHER_SPEED_CONF_TOTAL_LEN = USB_DESC_OTHER_SPEED_CONF_LEN + + USB_DESC_INTERFACE_LEN, +}; + +enum usb_descr_attributes { + // Attributes (b7 - buspwr, b6 - selfpwr, b5 - rwu). + USB_DESC_ATTRIBUTES = 0xA0, + // 100 mA (div 2). + USB_DESC_POWER_CONSUMPTION = 50 +}; + +enum usb_descr_numbers { + USB_DESC_CONFIG_COUNT = 1 +}; + +static const BYTE CODE +g_hid_report_desc[USB_DESCR_HID_REP_LEN] = { + // Pad #1 configuration. + 0x05, 0x01, // Usage page (Generic Desktop). + 0x09, 0x05, // Usage (Game Pad). + 0xa1, 0x01, // Collection (Application). + 0xa1, 0x00, // Collection (Physical). + 0x85, HID_REPORT_ID_GAMEPAD1, // Report id (1). + 0x05, 0x09, // Usage page (Button). + 0x19, 0x01, // Usage minimum (Button 1). + 0x29, 0x08, // Usage maximum (Button 8). + 0x15, 0x00, // Logical minimum (0). + 0x25, 0x01, // Logical maximum (1). + 0x95, HID_REPORT_BITS_COUNT, // Report count (8). + 0x75, 0x01, // Report size (1). + 0x81, 0x02, // Input (Data,Var,Abs). + 0xc0, // End collection. + 0xc0, // End collection. + // Pad #2 configuration. + 0x05, 0x01, // Usage page (Generic Desktop). + 0x09, 0x05, // Usage (Game Pad). + 0xa1, 0x01, // Collection (Application). + 0xa1, 0x00, // Collection (Physical). + 0x85, HID_REPORT_ID_GAMEPAD2, // Report id (2). + 0x05, 0x09, // Usage page (Button). + 0x19, 0x01, // Usage minimum (Button 1). + 0x29, 0x08, // Usage maximum (Button 8). + 0x15, 0x00, // Logical minimum (0). + 0x25, 0x01, // Logical maximum (1). + 0x95, HID_REPORT_BITS_COUNT, // Report count (8). + 0x75, 0x01, // Report size (1). + 0x81, 0x02, // Input (Data,Var,Abs). + 0xc0, // End collection + 0xc0 // End collection. +}; + +static const BYTE CODE +g_device_desc[USB_DESC_DEVICE_TOTAL_LEN] = { + USB_DESC_DEVICE_LEN, // Descriptor length. + USB_DESC_DEVICE, // Descriptor type. + usb_word_lsb_get(USB_SPEC_BCD_VERSION), // USB BCD version, lo. + usb_word_msb_get(USB_SPEC_BCD_VERSION), // USB BCD version, hi. + 0x00, // Device class. + 0x00, // Device sub-class. + 0x00, // Device protocol. + MAX_EP0_SIZE, // Maximum packet size (ep0 size). + usb_word_lsb_get(USB_DEVICE_VID), // Vendor ID, lo. + usb_word_msb_get(USB_DEVICE_VID), // Vendor ID, hi. + usb_word_lsb_get(USB_DEVICE_PID), // Product ID, lo. + usb_word_msb_get(USB_DEVICE_PID), // Product ID, hi. + usb_word_lsb_get(USB_DEVICE_BCD_VERSION), // Device BCD version, lo. + usb_word_msb_get(USB_DEVICE_BCD_VERSION), // Device BCD version, hi. + USB_DESC_MFG_STRING_INDEX, + USB_DESC_PRODUCT_STRING_INDEX, + USB_DESC_SERIAL_STRING_INDEX, + USB_DESC_CONFIG_COUNT // Configurations count. +}; + +static const BYTE CODE +g_device_qual_desc[USB_DESC_DEVICE_QUAL_TOTAL_LEN] = { + USB_DESC_DEVICE_QUAL_LEN, // Descriptor length. + USB_DESC_DEVICE_QUAL, // Descriptor type. + usb_word_lsb_get(USB_SPEC_BCD_VERSION), // USB BCD version, lo. + usb_word_msb_get(USB_SPEC_BCD_VERSION), // USB BCD version, hi. + 0x00, // Device class. + 0x00, // Device sub-class. + 0x00, // Device protocol. + MAX_EP0_SIZE, // Maximum packet size (ep0 size). + USB_DESC_CONFIG_COUNT, // Configurations count. + 0x00 // Reserved. +}; + +static const BYTE CODE +g_config_desc[USB_DESC_CONF_TOTAL_LEN] = { + USB_DESC_CONF_LEN, // Descriptor length. + USB_DESC_CONF, // Descriptor type. + usb_word_lsb_get(USB_DESC_CONF_TOTAL_LEN), // Total length, lo. + usb_word_msb_get(USB_DESC_CONF_TOTAL_LEN), // Total length, hi. + 0x01, // Interfaces count. + HID_CONFIG_NUMBER, // Configuration number. + 0x00, // Configuration string index. + USB_DESC_ATTRIBUTES, // Attributes. + USB_DESC_POWER_CONSUMPTION, // Power consumption. + + // Interface descriptor. + USB_DESC_INTERFACE_LEN, // Descriptor length. + USB_DESC_INTERFACE, // Descriptor type. + HID_IFACE_NUMBER, // Zero-based index of this interfacce. + HID_ALT_IFACE_NUMBER, // Alternate setting. + 0x01, // End points count (ep1 in). + 0x03, // Class code (HID). + 0x00, // Subclass code (boot). + 0x00, // Protocol code (none). + 0x00, // Interface string descriptor index. + + // HID descriptor. + USB_DESC_HID_LEN, // Descriptor length. + USB_DESC_HID, // Descriptor type. + usb_word_lsb_get(USB_HID_BCD_VERSION), // HID class BCD version, lo. + usb_word_msb_get(USB_HID_BCD_VERSION), // HID class BCD version, hi. + 0x00, // Country code (HW country code). + 0x01, // Number of HID class descriptors to follow. + USB_DESC_REPORT, // Repord descriptor type (HID). + usb_word_lsb_get(USB_DESCR_HID_REP_LEN), // Report descriptor total length, lo. + usb_word_msb_get(USB_DESCR_HID_REP_LEN), // Report descriptor total length, hi. + + // End point descriptor. + USB_DESC_ENDPOINT_LEN, // Descriptor length. + USB_DESC_ENDPOINT, // Descriptor type. + HID_EP_IN, // End point address (ep1 in). + 0x03, // End point type (interrupt). + usb_word_lsb_get(MAX_EP1_SIZE), // Maximum packet size, lo (ep1 size). + usb_word_msb_get(MAX_EP1_SIZE), // Maximum packet size, hi (ep1 size). + 0x01 // Polling interval (1 ms). +}; + +static const BYTE CODE +g_other_config_desc[USB_DESC_OTHER_SPEED_CONF_TOTAL_LEN] = { + USB_DESC_OTHER_SPEED_CONF_LEN, // Descriptor length. + USB_DESC_OTHER_SPEED_CONF, // Descriptor type. + usb_word_lsb_get(USB_DESC_OTHER_SPEED_CONF_TOTAL_LEN), // Total length, lo. + usb_word_msb_get(USB_DESC_OTHER_SPEED_CONF_TOTAL_LEN), // Total length, hi. + 0x01, // Interfaces number. + 0x01, // Configuration number. + 0x00, // Configuration string index. + USB_DESC_ATTRIBUTES, // Attributes. + USB_DESC_POWER_CONSUMPTION, // Power consumption. + + // Interface descriptor. + USB_DESC_INTERFACE_LEN, // Descriptor length. + USB_DESC_INTERFACE, // Descriptor type. + 0x00, // Zero-based index of this interfacce. + 0x00, // Alternate setting. + 0x00, // End points number (only ep0). + 0x00, // Class code. + 0x00, // Subclass code.USB_DESC_OTHER_SPEED_CONFIGURATION + 0x00, // Protocol code. + 0x00 // Interface string descriptor index. +}; + +static const BYTE CODE +g_lang_id_desc[] = { + 0x04, // Descriptor length. + USB_DESC_STRING, // Descriptor type. + usb_word_lsb_get(USB_LANG_ID), // Language id, lo. + usb_word_msb_get(USB_LANG_ID) // Language id, hi. +}; + +static const BYTE CODE +g_manuf_str_desc[] = { + 0x18, // Descriptor length. + USB_DESC_STRING, // Descriptor type. + // Unicode string: + 'Q', 0, 'B', 0, 'S', 0, ' ', 0, + 'e', 0, 'x', 0, 'a', 0, 'm', 0, 'p', 0, 'l', 0, 'e', 0 +}; + +static const BYTE CODE +g_product_str_desc[] = { + 0x1A, // Descriptor length. + USB_DESC_STRING, // Descriptor type. + // Unicode string: + 'N', 0, 'E', 0, 'S', 0, ' ', 0, + 'G', 0, 'a', 0, 'm', 0, 'e', 0, + 'P', 0, 'a', 0, 'd', 0, 's', 0 +}; + +static const BYTE CODE +g_serialno_str_desc[] = { + 0x1A, // Descriptor length. + USB_DESC_STRING, // Descriptor type. + // Unicode string: + '0', 0 ,'1', 0, '0', 0, '2', 0, '0', 0, '3', 0, + '0', 0, '4', 0, '0', 0, '5', 0, '0', 0, '6', 0 +}; + +static const BYTE CODE *ep0_string_desc_get(void) +{ + switch (SETUPDAT[2]) { + case USB_DESC_LANGID_STRING_INDEX: + return g_lang_id_desc; + case USB_DESC_MFG_STRING_INDEX: + return g_manuf_str_desc; + case USB_DESC_PRODUCT_STRING_INDEX: + return g_product_str_desc; + case USB_DESC_SERIAL_STRING_INDEX: + return g_serialno_str_desc; + default: + break; + } + + return NULL; +} + +static const BYTE CODE *ep0_config_desc_get(BOOL other) +{ + if (!other) { + if (SETUPDAT[2] == 0) { + return usb_is_high_speed() ? g_config_desc + : g_other_config_desc; + } + } else { + return usb_is_high_speed() ? g_other_config_desc + : g_config_desc; + } + + return NULL; +} + +const BYTE CODE *hid_ep0_std_desc_get(void) +{ + switch (SETUPDAT[3]) { + case USB_DESC_DEVICE: + return g_device_desc; + case USB_DESC_CONF: + return ep0_config_desc_get(FALSE); + case USB_DESC_STRING: + return ep0_string_desc_get(); + case USB_DESC_DEVICE_QUAL: + return g_device_qual_desc; + case USB_DESC_OTHER_SPEED_CONF: + return ep0_config_desc_get(TRUE); + case USB_DESC_HID: + return &g_config_desc[USB_DESC_CONF_LEN + + USB_DESC_INTERFACE_LEN]; + default: + break; + } + + return NULL; +} + +const BYTE CODE *hid_ep0_report_desc_get(WORD *length) +{ + *length = sizeof(g_hid_report_desc); + return g_hid_report_desc; +} diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/hidep0.c b/examples/baremetal/cy7c68013a/nes-gamepads/hidep0.c new file mode 100644 index 00000000..8648f625 --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/hidep0.c @@ -0,0 +1,377 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "hid.h" +#include "core.h" +#include "usb.h" + +static BOOL g_rwuen = FALSE; +static BOOL g_selfpwr = FALSE; + +static void ep0_ep1in_reset(void) +{ + EP1INCS &= ~bmEPBUSY; + sync_delay(); + usp_ep_reset_toggle(HID_EP_IN); + sync_delay(); +} + +// Get status handle. + +static BYTE *ep_address_get(BYTE ep) +{ + const BYTE ep_num = ep & ~bmSETUP_DIR; + switch (ep_num) { + case 0: + return (BYTE *)&EP0CS; + case 1: + return (ep & bmSETUP_DIR) + ? (BYTE *)&EP1INCS + : (BYTE *)&EP1OUTCS; + default: + break; + } + return NULL; +} + +static BOOL ep0_dev_status_get(void) +{ + BYTE status = 0; + if (g_selfpwr) + status |= USB_STATUS_SELF_POWERED; + if (g_rwuen) + status |= USB_STATUS_REMOTE_WAKEUP; + EP0BUF[0] = status; + EP0BUF[1] = 0; + EP0BCH = 0; + EP0BCL = 2; + return TRUE; +} + +static BOOL ep0_iface_status_get(void) +{ + EP0BUF[0] = 0; + EP0BUF[1] = 0; + EP0BCH = 0; + EP0BCL = 2; + return TRUE; +} + +static BOOL ep0_ep_status_get(void) +{ + const volatile BYTE XDATA *pep = + (BYTE XDATA *)ep_address_get(SETUPDAT[4]); + if (pep) { + EP0BUF[0] = *pep & bmEPSTALL ? 1 : 0; + EP0BUF[1] = 0; + EP0BCH = 0; + EP0BCL = 2; + return TRUE; + } + + return FALSE; +} + +static BOOL ep0_get_status_proc(void) +{ + if ((SETUPDAT[0] & bmSETUP_TO_HOST) == 0) + return FALSE; + + switch (SETUPDAT[0] & bmSETUP_RECIPIENT) { + case bmSETUP_DEVICE: + return ep0_dev_status_get(); + case bmSETUP_IFACE: + return ep0_iface_status_get(); + case bmSETUP_EP: + return ep0_ep_status_get(); + default: + break; + } + + return FALSE; +} + +// Clear feature handle. + +static BOOL ep0_dev_feature_clear(void) +{ + if (SETUPDAT[2] == USB_FEATURE_REMOTE_WAKEUP) { + g_rwuen = FALSE; + return TRUE; + } + + return FALSE; +} + +static BOOL ep0_ep_feature_clear(void) +{ + if (SETUPDAT[2] == USB_FEATURE_STALL) { + volatile BYTE XDATA *pep = + (BYTE XDATA *)ep_address_get(SETUPDAT[4]); + if (!pep) + return FALSE; + *pep &= ~bmEPSTALL; + return TRUE; + } + + return FALSE; +} + +static BOOL ep0_clear_feature_proc(void) +{ + switch (SETUPDAT[0] & bmSETUP_RECIPIENT) { + case bmSETUP_DEVICE: + return ep0_dev_feature_clear(); + case bmSETUP_EP: + return ep0_ep_feature_clear(); + default: + break; + } + + return FALSE; +} + +// Set feature handle. + +static BOOL ep0_dev_feature_set(void) +{ + switch (SETUPDAT[2]) { + case USB_FEATURE_REMOTE_WAKEUP: + g_rwuen = TRUE; + return TRUE; + case USB_FEATRUE_TEST_MODE: + // This is "test mode", just return the handshake. + return TRUE; + default: + break; + } + + return FALSE; +} + +static BOOL ep0_ep_feature_set(void) +{ + if (SETUPDAT[2] == USB_FEATURE_STALL) { + volatile BYTE XDATA *pep = + (BYTE XDATA *)ep_address_get(SETUPDAT[4]); + if (!pep) + return FALSE; + *pep |= bmEPSTALL; + usp_ep_reset_toggle(SETUPDAT[4]); + return TRUE; + } + + return FALSE; +} + +static BOOL ep0_set_feature_proc(void) +{ + switch (SETUPDAT[0] & bmSETUP_RECIPIENT) { + case bmSETUP_DEVICE: + return ep0_dev_feature_set(); + case bmSETUP_EP: + return ep0_ep_feature_set(); + default: + break; + } + + return FALSE; +} + +// Get descriptor handle. + +static BOOL ep0_std_descriptor_proc(void) +{ + const BYTE XDATA *pdesc = + (BYTE XDATA *)hid_ep0_std_desc_get(); + if (pdesc) { + SUDPTRH = usb_word_msb_get(pdesc); + SUDPTRL = usb_word_lsb_get(pdesc); + return TRUE; + } + + return FALSE; +} + +static BOOL ep0_report_descriptor_proc(void) +{ + WORD i = 0; + WORD length = 0; + const BYTE XDATA *pdesc = + (BYTE XDATA *)hid_ep0_report_desc_get(&length); + if (pdesc) { + AUTOPTRH1 = usb_word_msb_get(pdesc); + AUTOPTRL1 = usb_word_lsb_get(pdesc); + + for (i = 0; i < length; ++i) + EP0BUF[i] = XAUTODAT1; + + EP0BCH = usb_word_msb_get(length); + EP0BCL = usb_word_lsb_get(length); + return TRUE; + } + + return FALSE; +} + +static BOOL ep0_get_descriptor_proc(void) +{ + switch (SETUPDAT[3]) { + case USB_DESC_DEVICE: + case USB_DESC_CONF: + case USB_DESC_STRING: + case USB_DESC_DEVICE_QUAL: + case USB_DESC_OTHER_SPEED_CONF: + case USB_DESC_HID: + return ep0_std_descriptor_proc(); + case USB_DESC_REPORT: + return ep0_report_descriptor_proc(); + } + + return FALSE; +} + +// Get configuration handle. + +static BOOL ep0_get_config_proc(void) +{ + // We only support configuration 1. + EP0BUF[0] = HID_CONFIG_NUMBER; + EP0BCH = 0; + EP0BCL = 1; + return TRUE; +} + +// Set configuration handle. + +static BOOL ep0_set_config_proc(void) +{ + // We only support configuration 1. + if (SETUPDAT[2] != HID_CONFIG_NUMBER) + return FALSE; + + return TRUE; +} + +// Get interface handle. + +static BOOL ep0_get_iface_proc(void) +{ + if (SETUPDAT[4] != HID_IFACE_NUMBER) + return FALSE; + + EP0BUF[0] = HID_ALT_IFACE_NUMBER; + EP0BCH = 0; + EP0BCL = 1; + return TRUE; +} + +// Set interface handle. + +static BOOL ep0_set_iface_proc(void) +{ + if (SETUPDAT[4] != HID_IFACE_NUMBER) + return FALSE; + + if (SETUPDAT[2] != HID_ALT_IFACE_NUMBER) + return FALSE; + + ep0_ep1in_reset(); + return TRUE; +} + +static BOOL ep0_std_proc(void) +{ + switch (SETUPDAT[1]) { + case USB_SETUP_GET_STATUS: + return ep0_get_status_proc(); + case USB_SETUP_CLEAR_FEATURE: + return ep0_clear_feature_proc(); + case USB_SETUP_SET_FEATURE: + return ep0_set_feature_proc(); + case USB_SETUP_SET_ADDRESS: + // Handles automatically by FX2. + return TRUE; + case USB_SETUP_GET_DESCRIPTOR: + return ep0_get_descriptor_proc(); + case USB_SETUP_GET_CONFIGURATION: + return ep0_get_config_proc(); + case USB_SETUP_SET_CONFIGURATION: + return ep0_set_config_proc(); + case USB_SETUP_GET_INTERFACE: + return ep0_get_iface_proc(); + case USB_SETUP_SET_INTERFACE: + return ep0_set_iface_proc(); + default: + break; + } + + return FALSE; +} + +static BOOL ep0_setup(void) +{ + switch (SETUPDAT[0] & bmSETUP_TYPE) { + case bmSETUP_STANDARD: + return ep0_std_proc(); + default: + break; + } + + return FALSE; +} + +void hid_ep0_setup_task(void) +{ + if (!ep0_setup()) + usb_ep0_stall(); + + usb_ep0_hsnack(); +} diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/hidep1.c b/examples/baremetal/cy7c68013a/nes-gamepads/hidep1.c new file mode 100644 index 00000000..79031114 --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/hidep1.c @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "hid.h" + +struct hid_report { + const BYTE id; + BYTE buttons; + BYTE ready; +}; + +static struct hid_report g_reports[HID_REPORTS_COUNT] = { + {HID_REPORT_ID_GAMEPAD1, 0, FALSE}, + {HID_REPORT_ID_GAMEPAD2, 0, FALSE} +}; + +static void ep1_report_send(BYTE index) +{ + if (!g_reports[index].ready) + return; + + if (EP1INCS & bmEPBUSY) + return; + + EP1INBUF[0] = g_reports[index].id; + EP1INBUF[1] = g_reports[index].buttons; + EP1INBC = 2; + g_reports[index].ready = FALSE; +} + +void hid_ep1_task(void) +{ + BYTE index = 0; + for (index = 0; index < HID_REPORTS_COUNT; ++index) + ep1_report_send(index); +} + +void hid_ep1_report_update(BYTE index, BYTE buttons) +{ + g_reports[index].buttons = buttons; + g_reports[index].ready = TRUE; +} diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/irqs.h b/examples/baremetal/cy7c68013a/nes-gamepads/irqs.h new file mode 100644 index 00000000..4b5f9683 --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/irqs.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef FX2_IRQS_H +#define FX2_IRQS_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Standard interrupt numbers. +#define INT0_IRQ 0 // External interrupt 0. +#define TMR0_IRQ 1 // Timer 0 interrupt. +#define INT1_IRQ 2 // External interrupt 1. +#define TMR1_IRQ 3 // Timer 1 interrupt. +#define COM0_IRQ 4 // Serial port 0 transmit or receive interrupt. +#define TMR2_IRQ 5 // Timer 2 interrupt. +#define WKUP_IRQ 6 // Resume interrupt. +#define COM1_IRQ 7 // Serial port 1 transmit or receive interrupt. +#define USB_IRQ 8 // USB interrupt. +#define I2C_IRQ 9 // I2C bus interrupt. +#define INT4_IRQ 10 // External interrupt 4. +#define INT5_IRQ 11 // External interrupt 5. +#define INT6_IRQ 12 // External interrupt 6. + +#ifdef __cplusplus +} +#endif + +#endif // FX2_IRQS_H diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/main.c b/examples/baremetal/cy7c68013a/nes-gamepads/main.c new file mode 100644 index 00000000..71d5b924 --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/main.c @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +// Allocate all registers once here (only for C51 compiler). +#define DEFINE_REGS +#include "regs.h" + +#include "core.h" +#include "gpio.h" +#include "usb.h" + +static void hw_init(void) +{ + usb_disconnect(); + code_all_irq_disable(); + + core_init(); + gpio_init(); + usb_init(); + + usb_connect(); + code_all_irq_enable(); +} + +static void hw_loop_exec(void) +{ + while (TRUE) { + gpio_task(); + usb_task(); + } +} + +int main(void) +{ + hw_init(); + hw_loop_exec(); + return 0; +} diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/nes-gamepads.qbs b/examples/baremetal/cy7c68013a/nes-gamepads/nes-gamepads.qbs new file mode 100644 index 00000000..0d799403 --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/nes-gamepads.qbs @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 { + condition: { + if (!qbs.architecture.contains("mcs51")) + return false; + return qbs.toolchain.contains("iar") + || qbs.toolchain.contains("keil") + || qbs.toolchain.contains("sdcc") + } + name: "cy7c68013a-nes-gamepads" + cpp.positionIndependentCode: false + + // + // IAR-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("iar") + cpp.commonCompilerFlags: ["-e"] + cpp.staticLibraries: [ + cpp.toolchainInstallPath + "/../lib/clib/cl-pli-nsid-1e16x01" + ] + cpp.driverLinkerFlags: [ + "-D_IDATA_STACK_SIZE=0x40", + "-D_PDATA_STACK_SIZE=0x80", + "-D_XDATA_STACK_SIZE=0xEFF", + "-D_XDATA_HEAP_SIZE=0xFF", + "-D_EXTENDED_STACK_SIZE=0", + "-D_EXTENDED_STACK_START=0" + ] + } + + Group { + name: "IAR Linker Script" + condition: qbs.toolchain.contains("iar") + prefix: cpp.toolchainInstallPath + "/../config/devices/cypress/" + fileTags: ["linkerscript"] + files: ["lnk51ew_CY7C68013A.xcl"] + } + + // + // KEIL-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("keil") + cpp.driverLinkerFlags: [ + "RAMSIZE(256)", + "CODE(0x80)", + "XDATA(0x1000)" + ] + } + + // + // SDCC-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("sdcc") + cpp.driverLinkerFlags: [ + "--code-loc", "0x80", + "--xram-loc", "0x1000" + ] + } + + // + // Common code. + // + + files: [ + "core.c", + "core.h", + "defs.h", + "gpio.c", + "gpio.h", + "hid.c", + "hid.h", + "hiddesc.c", + "hidep0.c", + "hidep1.c", + "irqs.h", + "main.c", + "regs.h", + "usb.c", + "usb.h", + ] +} diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/regs.h b/examples/baremetal/cy7c68013a/nes-gamepads/regs.h new file mode 100644 index 00000000..98e40f0a --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/regs.h @@ -0,0 +1,779 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef FX2_REGS_H +#define FX2_REGS_H + +#include "defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// +// XDATA registers. +// + +XDATA_REG(GPIF_WAVE_DATA, 0xE400) // GPIF waveform descriptor. +XDATA_REG(RES_WAVEDATA_END, 0xE480) // Reserved. + +// General configuration. + +XDATA_REG(CPUCS, 0xE600) // Control & status. +XDATA_REG(IFCONFIG, 0xE601) // Interface configuration. +XDATA_REG(PINFLAGSAB, 0xE602) // FIFO FLAGA and FLAGB assignments. +XDATA_REG(PINFLAGSCD, 0xE603) // FIFO FLAGC and FLAGD assignments. +XDATA_REG(FIFORESET, 0xE604) // Restore FIFOS to default state. +XDATA_REG(BREAKPT, 0xE605) // Breakpoint. +XDATA_REG(BPADDRH, 0xE606) // Breakpoint address H. +XDATA_REG(BPADDRL, 0xE607) // Breakpoint address L. +XDATA_REG(UART230, 0xE608) // 230 Kbaud clock for T0,T1,T2. +XDATA_REG(FIFOPINPOLAR, 0xE609) // FIFO polarities. +XDATA_REG(REVID, 0xE60A) // Chip revision. +XDATA_REG(REVCTL, 0xE60B) // Chip revision control. + +// Endpoint configuration. + +XDATA_REG(EP1OUTCFG, 0xE610) // Endpoint 1-out configuration. +XDATA_REG(EP1INCFG, 0xE611) // Endpoint 1-in configuration. +XDATA_REG(EP2CFG, 0xE612) // Endpoint 2 configuration. +XDATA_REG(EP4CFG, 0xE613) // Endpoint 4 configuration. +XDATA_REG(EP6CFG, 0xE614) // Endpoint 6 configuration. +XDATA_REG(EP8CFG, 0xE615) // Endpoint 8 configuration. +XDATA_REG(EP2FIFOCFG, 0xE618) // Endpoint 2 FIFO configuration. +XDATA_REG(EP4FIFOCFG, 0xE619) // Endpoint 4 FIFO configuration. +XDATA_REG(EP6FIFOCFG, 0xE61A) // Endpoint 6 FIFO configuration. +XDATA_REG(EP8FIFOCFG, 0xE61B) // Endpoint 8 FIFO configuration. +XDATA_REG(EP2AUTOINLENH, 0xE620) // Endpoint 2 packet length H (in only). +XDATA_REG(EP2AUTOINLENL, 0xE621) // Endpoint 2 packet length L (in only). +XDATA_REG(EP4AUTOINLENH, 0xE622) // Endpoint 4 packet length H (in only). +XDATA_REG(EP4AUTOINLENL, 0xE623) // Endpoint 4 packet length L (in only). +XDATA_REG(EP6AUTOINLENH, 0xE624) // Endpoint 6 packet length H (in only). +XDATA_REG(EP6AUTOINLENL, 0xE625) // Endpoint 6 packet length L (in only). +XDATA_REG(EP8AUTOINLENH, 0xE626) // Endpoint 8 packet length H (in only). +XDATA_REG(EP8AUTOINLENL, 0xE627) // Endpoint 8 packet length L (in only). +XDATA_REG(EP2FIFOPFH, 0xE630) // EP2 programmable flag trigger H. +XDATA_REG(EP2FIFOPFL, 0xE631) // EP2 programmable flag trigger L. +XDATA_REG(EP4FIFOPFH, 0xE632) // EP4 programmable flag trigger H. +XDATA_REG(EP4FIFOPFL, 0xE633) // EP4 programmable flag trigger L. +XDATA_REG(EP6FIFOPFH, 0xE634) // EP6 programmable flag trigger H. +XDATA_REG(EP6FIFOPFL, 0xE635) // EP6 programmable flag trigger L. +XDATA_REG(EP8FIFOPFH, 0xE636) // EP8 programmable flag trigger H. +XDATA_REG(EP8FIFOPFL, 0xE637) // EP8 programmable flag trigger L. +XDATA_REG(EP2ISOINPKTS, 0xE640) // EP2 (if ISO) in packets per frame (1-3). +XDATA_REG(EP4ISOINPKTS, 0xE641) // EP4 (if ISO) in packets per frame (1-3). +XDATA_REG(EP6ISOINPKTS, 0xE642) // EP6 (if ISO) in packets per frame (1-3). +XDATA_REG(EP8ISOINPKTS, 0xE643) // EP8 (if ISO) in packets per frame (1-3). +XDATA_REG(INPKTEND, 0xE648) // Force in packet end. +XDATA_REG(OUTPKTEND, 0xE649) // Force out packet end. + +// Interrupts. + +XDATA_REG(EP2FIFOIE, 0xE650) // Endpoint 2 flag interrupt enable. +XDATA_REG(EP2FIFOIRQ, 0xE651) // Endpoint 2 flag interrupt request. +XDATA_REG(EP4FIFOIE, 0xE652) // Endpoint 4 flag interrupt enable. +XDATA_REG(EP4FIFOIRQ, 0xE653) // Endpoint 4 flag interrupt request. +XDATA_REG(EP6FIFOIE, 0xE654) // Endpoint 6 flag interrupt enable. +XDATA_REG(EP6FIFOIRQ, 0xE655) // Endpoint 6 flag interrupt request. +XDATA_REG(EP8FIFOIE, 0xE656) // Endpoint 8 flag interrupt enable. +XDATA_REG(EP8FIFOIRQ, 0xE657) // Endpoint 8 flag interrupt request. +XDATA_REG(IBNIE, 0xE658) // IN-BULK-NAK interrupt enable. +XDATA_REG(IBNIRQ, 0xE659) // IN-BULK-NAK interrupt request. +XDATA_REG(NAKIE, 0xE65A) // Endpoint ping NAK interrupt enable. +XDATA_REG(NAKIRQ, 0xE65B) // Endpoint ping NAK interrupt request. +XDATA_REG(USBIE, 0xE65C) // USB Int enables. +XDATA_REG(USBIRQ, 0xE65D) // USB interrupt requests. +XDATA_REG(EPIE, 0xE65E) // Endpoint interrupt enables. +XDATA_REG(EPIRQ, 0xE65F) // Endpoint interrupt requests. +XDATA_REG(GPIFIE, 0xE660) // GPIF interrupt enable. +XDATA_REG(GPIFIRQ, 0xE661) // GPIF interrupt request. +XDATA_REG(USBERRIE, 0xE662) // USB error interrupt enables. +XDATA_REG(USBERRIRQ, 0xE663) // USB error interrupt requests. +XDATA_REG(ERRCNTLIM, 0xE664) // USB error counter and limit. +XDATA_REG(CLRERRCNT, 0xE665) // Clear error counter EC[3..0]. +XDATA_REG(INT2IVEC, 0xE666) // Interrupt 2 (USB) autovector. +XDATA_REG(INT4IVEC, 0xE667) // Interrupt 4 (FIFOS & GPIF) autovector. +XDATA_REG(INTSETUP, 0xE668) // interrupt 2 & 4 setup. + +// Input/Output. + +XDATA_REG(PORTACFG, 0xE670) // I/O PORTA alternate configuration. +XDATA_REG(PORTCCFG, 0xE671) // I/O PORTC alternate configuration. +XDATA_REG(PORTECFG, 0xE672) // I/O PORTE alternate configuration. +XDATA_REG(I2CS, 0xE678) // Bus control & status. +XDATA_REG(I2DAT, 0xE679) // Bus data. +XDATA_REG(I2CTL, 0xE67A) // Bus control. +XDATA_REG(XAUTODAT1, 0xE67B) // Autoptr1 movx access. +XDATA_REG(XAUTODAT2, 0xE67C) // Autoptr2 movx access. + +// USB Control. + +XDATA_REG(USBCS, 0xE680) // USB control & status. +XDATA_REG(SUSPEND, 0xE681) // Put chip into suspend. +XDATA_REG(WAKEUPCS, 0xE682) // Wakeup source and polarity. +XDATA_REG(TOGCTL, 0xE683) // Toggle control. +XDATA_REG(USBFRAMEH, 0xE684) // USB frame count H. +XDATA_REG(USBFRAMEL, 0xE685) // USB frame count L. +XDATA_REG(MICROFRAME, 0xE686) // Microframe count, 0-7. +XDATA_REG(FNADDR, 0xE687) // USB function address. + +// Endpoints. + +XDATA_REG(EP0BCH, 0xE68A) // Endpoint 0 byte count H. +XDATA_REG(EP0BCL, 0xE68B) // Endpoint 0 byte count L. +XDATA_REG(EP1OUTBC, 0xE68D) // Endpoint 1 out byte count. +XDATA_REG(EP1INBC, 0xE68F) // Endpoint 1 in byte count. +XDATA_REG(EP2BCH, 0xE690) // Endpoint 2 byte count H. +XDATA_REG(EP2BCL, 0xE691) // Endpoint 2 byte count L. +XDATA_REG(EP4BCH, 0xE694) // Endpoint 4 byte count H. +XDATA_REG(EP4BCL, 0xE695) // Endpoint 4 byte count L. +XDATA_REG(EP6BCH, 0xE698) // Endpoint 6 byte count H. +XDATA_REG(EP6BCL, 0xE699) // Endpoint 6 byte count L. +XDATA_REG(EP8BCH, 0xE69C) // Endpoint 8 byte count H. +XDATA_REG(EP8BCL, 0xE69D) // Endpoint 8 byte count L. +XDATA_REG(EP0CS, 0xE6A0) // Endpoint control and status. +XDATA_REG(EP1OUTCS, 0xE6A1) // Endpoint 1 out control and status. +XDATA_REG(EP1INCS, 0xE6A2) // Endpoint 1 in control and status. +XDATA_REG(EP2CS, 0xE6A3) // Endpoint 2 control and status. +XDATA_REG(EP4CS, 0xE6A4) // Endpoint 4 control and status. +XDATA_REG(EP6CS, 0xE6A5) // Endpoint 6 control and status. +XDATA_REG(EP8CS, 0xE6A6) // Endpoint 8 control and status. +XDATA_REG(EP2FIFOFLGS, 0xE6A7) // Endpoint 2 flags. +XDATA_REG(EP4FIFOFLGS, 0xE6A8) // Endpoint 4 flags. +XDATA_REG(EP6FIFOFLGS, 0xE6A9) // Endpoint 6 flags. +XDATA_REG(EP8FIFOFLGS, 0xE6AA) // Endpoint 8 flags. +XDATA_REG(EP2FIFOBCH, 0xE6AB) // EP2 FIFO total byte count H. +XDATA_REG(EP2FIFOBCL, 0xE6AC) // EP2 FIFO total byte count L. +XDATA_REG(EP4FIFOBCH, 0xE6AD) // EP4 FIFO total byte count H. +XDATA_REG(EP4FIFOBCL, 0xE6AE) // EP4 FIFO total byte count L. +XDATA_REG(EP6FIFOBCH, 0xE6AF) // EP6 FIFO total byte count H. +XDATA_REG(EP6FIFOBCL, 0xE6B0) // EP6 FIFO total byte count L. +XDATA_REG(EP8FIFOBCH, 0xE6B1) // EP8 FIFO total byte count H. +XDATA_REG(EP8FIFOBCL, 0xE6B2) // EP8 FIFO total byte count L. +XDATA_REG(SUDPTRH, 0xE6B3) // Setup data pointer high address byte. +XDATA_REG(SUDPTRL, 0xE6B4) // Setup data pointer low address byte. +XDATA_REG(SUDPTRCTL, 0xE6B5) // Setup data pointer auto mode. +XDATA_REG(SETUPDAT[8], 0xE6B8) // 8 bytes of setup data. + +// GPIF. + +XDATA_REG(GPIFWFSELECT, 0xE6C0) // Waveform selector. +XDATA_REG(GPIFIDLECS, 0xE6C1) // GPIF done, GPIF idle drive mode. +XDATA_REG(GPIFIDLECTL, 0xE6C2) // Inactive bus, ctl states. +XDATA_REG(GPIFCTLCFG, 0xE6C3) // CTL out pin drive. +XDATA_REG(GPIFADRH, 0xE6C4) // GPIF address H. +XDATA_REG(GPIFADRL, 0xE6C5) // GPIF address L. + +XDATA_REG(GPIFTCB3, 0xE6CE) // GPIF transaction count byte 3. +XDATA_REG(GPIFTCB2, 0xE6CF) // GPIF transaction count byte 2. +XDATA_REG(GPIFTCB1, 0xE6D0) // GPIF transaction count byte 1. +XDATA_REG(GPIFTCB0, 0xE6D1) // GPIF transaction count byte 0. + +XDATA_REG(EP2GPIFFLGSEL, 0xE6D2) // EP2 GPIF flag select. +XDATA_REG(EP2GPIFPFSTOP, 0xE6D3) // Stop GPIF EP2 transaction on prog flag. +XDATA_REG(EP2GPIFTRIG, 0xE6D4) // EP2 FIFO trigger. +XDATA_REG(EP4GPIFFLGSEL, 0xE6DA) // EP4 GPIF flag select. +XDATA_REG(EP4GPIFPFSTOP, 0xE6DB) // Stop GPIF EP4 transaction on prog flag. +XDATA_REG(EP4GPIFTRIG, 0xE6DC) // EP4 FIFO trigger. +XDATA_REG(EP6GPIFFLGSEL, 0xE6E2) // EP6 GPIF Flag select. +XDATA_REG(EP6GPIFPFSTOP, 0xE6E3) // Stop GPIF EP6 transaction on prog flag. +XDATA_REG(EP6GPIFTRIG, 0xE6E4) // EP6 FIFO trigger. +XDATA_REG(EP8GPIFFLGSEL, 0xE6EA) // EP8 GPIF flag select. +XDATA_REG(EP8GPIFPFSTOP, 0xE6EB) // Stop GPIF EP8 transaction on prog flag. +XDATA_REG(EP8GPIFTRIG, 0xE6EC) // EP8 FIFO trigger. +XDATA_REG(XGPIFSGLDATH, 0xE6F0) // GPIF data H (16-bit mode only). +XDATA_REG(XGPIFSGLDATLX, 0xE6F1) // Read/write GPIF data L & trigger transac. +XDATA_REG(XGPIFSGLDATLNOX, 0xE6F2) // Read GPIF data L, no transac trigger. +XDATA_REG(GPIFREADYCFG, 0xE6F3) // Internal RDY,Sync/Async, RDY5CFG. +XDATA_REG(GPIFREADYSTAT, 0xE6F4) // RDY pin states. +XDATA_REG(GPIFABORT, 0xE6F5) // Abort GPIF cycles. + +// UDMA. + +XDATA_REG(FLOWSTATE, 0xE6C6) // Defines GPIF flow state. +XDATA_REG(FLOWLOGIC, 0xE6C7) // Defines flow/hold decision criteria. +XDATA_REG(FLOWEQ0CTL, 0xE6C8) // CTL states during active flow state. +XDATA_REG(FLOWEQ1CTL, 0xE6C9) // CTL states during hold flow state. +XDATA_REG(FLOWHOLDOFF, 0xE6CA) // Holdoff configuration. +XDATA_REG(FLOWSTB, 0xE6CB) // CTL/RDY signal to use as master data strobe. +XDATA_REG(FLOWSTBEDGE, 0xE6CC) // Defines active master strobe edge. +XDATA_REG(FLOWSTBHPERIOD, 0xE6CD) // Half period of output master strobe. +XDATA_REG(GPIFHOLDAMOUNT, 0xE60C) // Data delay shift. +XDATA_REG(UDMACRCH, 0xE67D) // CRC upper byte. +XDATA_REG(UDMACRCL, 0xE67E) // CRC lower byte. +XDATA_REG(UDMACRCQUAL, 0xE67F) // UDMA in only, host terminated use only. + +// Endpoint buffers. +#define MAX_EP0_SIZE 64 +#define MAX_EP1_SIZE 64 +#define MAX_EP2468_SIZE 1024 + +XDATA_REG(EP0BUF[MAX_EP0_SIZE], 0xE740) // EP0 buffer (in or out). +XDATA_REG(EP1OUTBUF[MAX_EP1_SIZE], 0xE780) // EP1 buffer (out only). +XDATA_REG(EP1INBUF[MAX_EP1_SIZE], 0xE7C0) // EP1 buffer (in only). +XDATA_REG(EP2FIFOBUF[MAX_EP2468_SIZE], 0xF000) // 512/1024-byte EP2 buffer (in or out). +XDATA_REG(EP4FIFOBUF[MAX_EP2468_SIZE], 0xF400) // 512 byte EP4 buffer (in or out). +XDATA_REG(EP6FIFOBUF[MAX_EP2468_SIZE], 0xF800) // 512/1024-byte EP6 buffer (in or out). +XDATA_REG(EP8FIFOBUF[MAX_EP2468_SIZE], 0xFC00) // 512 byte EP8 buffer (in or out). + +// Error correction code (ECC) registers (FX2LP/FX1 only). + +XDATA_REG(ECCCFG, 0xE628) // ECC configuration. +XDATA_REG(ECCRESET, 0xE629) // ECC reset. +XDATA_REG(ECC1B0, 0xE62A) // ECC1 byte 0. +XDATA_REG(ECC1B1, 0xE62B) // ECC1 byte 1. +XDATA_REG(ECC1B2, 0xE62C) // ECC1 byte 2. +XDATA_REG(ECC2B0, 0xE62D) // ECC2 byte 0. +XDATA_REG(ECC2B1, 0xE62E) // ECC2 byte 1. +XDATA_REG(ECC2B2, 0xE62F) // ECC2 byte 2. + +// Feature registers(FX2LP/FX1 only). + +XDATA_REG(GPCR2, 0xE50D) // Chip features. + +// +// SFR registers. +// + +SPEC_FUN_REG(IOA, 0x80) // Port A. +SPEC_FUN_REG_BIT(PA0, 0x80, 0) +SPEC_FUN_REG_BIT(PA1, 0x80, 1) +SPEC_FUN_REG_BIT(PA2, 0x80, 2) +SPEC_FUN_REG_BIT(PA3, 0x80, 3) +SPEC_FUN_REG_BIT(PA4, 0x80, 4) +SPEC_FUN_REG_BIT(PA5, 0x80, 5) +SPEC_FUN_REG_BIT(PA6, 0x80, 6) +SPEC_FUN_REG_BIT(PA7, 0x80, 7) + +SPEC_FUN_REG(SP, 0x81) // Stack pointer. +SPEC_FUN_REG(DPL, 0x82) // Data pointer 0 L. +SPEC_FUN_REG(DPH, 0x83) // Data pointer 0 H. +SPEC_FUN_REG(DPL1, 0x84) // Data pointer 1 L. +SPEC_FUN_REG(DPH1, 0x85) // Data pointer 1 L. +SPEC_FUN_REG(DPS, 0x86) // Data pointer 0/1 select. +SPEC_FUN_REG(PCON, 0x87) // Power control. + +SPEC_FUN_REG(TCON, 0x88) // Timer/counter control. +SPEC_FUN_REG_BIT(IT0, 0x88, 0) +SPEC_FUN_REG_BIT(IE0, 0x88, 1) +SPEC_FUN_REG_BIT(IT1, 0x88, 2) +SPEC_FUN_REG_BIT(IE1, 0x88, 3) +SPEC_FUN_REG_BIT(TR0, 0x88, 4) +SPEC_FUN_REG_BIT(TF0, 0x88, 5) +SPEC_FUN_REG_BIT(TR1, 0x88, 6) +SPEC_FUN_REG_BIT(TF1, 0x88, 7) + +SPEC_FUN_REG(TMOD, 0x89) // Timer/counter mode control. +SPEC_FUN_REG(TL0, 0x8A) // Timer 0 reload L. +SPEC_FUN_REG(TL1, 0x8B) // Timer 1 reload L. +SPEC_FUN_REG(TH0, 0x8C) // Timer 0 reload H. +SPEC_FUN_REG(TH1, 0x8D) // Timer 1 reload H. +SPEC_FUN_REG(CKCON, 0x8E) // Clock control. + +SPEC_FUN_REG(IOB, 0x90) // Port B. +SPEC_FUN_REG_BIT(PB0, 0x90, 0) +SPEC_FUN_REG_BIT(PB1, 0x90, 1) +SPEC_FUN_REG_BIT(PB2, 0x90, 2) +SPEC_FUN_REG_BIT(PB3, 0x90, 3) +SPEC_FUN_REG_BIT(PB4, 0x90, 4) +SPEC_FUN_REG_BIT(PB5, 0x90, 5) +SPEC_FUN_REG_BIT(PB6, 0x90, 6) +SPEC_FUN_REG_BIT(PB7, 0x90, 7) + +SPEC_FUN_REG(EXIF, 0x91) // External interrupt flag. +SPEC_FUN_REG(MPAGE, 0x92) // Upper address page of movx. + +SPEC_FUN_REG(SCON0, 0x98) // Serial port 0 control. +SPEC_FUN_REG_BIT(RI, 0x98, 0) +SPEC_FUN_REG_BIT(TI, 0x98, 1) +SPEC_FUN_REG_BIT(RB8, 0x98, 2) +SPEC_FUN_REG_BIT(TB8, 0x98, 3) +SPEC_FUN_REG_BIT(REN, 0x98, 4) +SPEC_FUN_REG_BIT(SM2, 0x98, 5) +SPEC_FUN_REG_BIT(SM1, 0x98, 6) +SPEC_FUN_REG_BIT(SM0, 0x98, 7) + +SPEC_FUN_REG(SBUF0, 0x99) // Serial port 0 data buffer. + +SPEC_FUN_REG(AUTOPTRH1, 0x9A) // Auto-pointer 1 address H. +SPEC_FUN_REG(AUTOPTRL1, 0x9B) // Auto-pointer 1 address L. +SPEC_FUN_REG(AUTOPTRH2, 0x9D) // Auto-pointer 2 address H. +SPEC_FUN_REG(AUTOPTRL2, 0x9E) // Auto-pointer 2 address L. + +SPEC_FUN_REG(IOC, 0xA0) // Port C. +SPEC_FUN_REG_BIT(PC0, 0xA0, 0) +SPEC_FUN_REG_BIT(PC1, 0xA0, 1) +SPEC_FUN_REG_BIT(PC2, 0xA0, 2) +SPEC_FUN_REG_BIT(PC3, 0xA0, 3) +SPEC_FUN_REG_BIT(PC4, 0xA0, 4) +SPEC_FUN_REG_BIT(PC5, 0xA0, 5) +SPEC_FUN_REG_BIT(PC6, 0xA0, 6) +SPEC_FUN_REG_BIT(PC7, 0xA0, 7) + +SPEC_FUN_REG(INT2CLR, 0xA1) // Interrupt 2 clear. +SPEC_FUN_REG(INT4CLR, 0xA2) // Interrupt 4 clear. + +SPEC_FUN_REG(IE, 0xA8) // Interrupt enable. +SPEC_FUN_REG_BIT(EX0, 0xA8, 0) +SPEC_FUN_REG_BIT(ET0, 0xA8, 1) +SPEC_FUN_REG_BIT(EX1, 0xA8, 2) +SPEC_FUN_REG_BIT(ET1, 0xA8, 3) +SPEC_FUN_REG_BIT(ES0, 0xA8, 4) +SPEC_FUN_REG_BIT(ET2, 0xA8, 5) +SPEC_FUN_REG_BIT(ES1, 0xA8, 6) +SPEC_FUN_REG_BIT(EA, 0xA8, 7) + +SPEC_FUN_REG(EP2468STAT, 0xAA) // Endpoint 2,4,6,8 status flags. +SPEC_FUN_REG(EP24FIFOFLGS, 0xAB) // Endpoint 2,4 slave FIFO status flags. +SPEC_FUN_REG(EP68FIFOFLGS, 0xAC) // Endpoint 6,8 slave FIFO status flags. +SPEC_FUN_REG(AUTOPTRSETUP, 0xAF) // Auto-pointer 1,2 setup. + +SPEC_FUN_REG(IOD, 0xB0) // Port D. +SPEC_FUN_REG_BIT(PD0, 0xB0, 0) +SPEC_FUN_REG_BIT(PD1, 0xB0, 1) +SPEC_FUN_REG_BIT(PD2, 0xB0, 2) +SPEC_FUN_REG_BIT(PD3, 0xB0, 3) +SPEC_FUN_REG_BIT(PD4, 0xB0, 4) +SPEC_FUN_REG_BIT(PD5, 0xB0, 5) +SPEC_FUN_REG_BIT(PD6, 0xB0, 6) +SPEC_FUN_REG_BIT(PD7, 0xB0, 7) + +SPEC_FUN_REG(IOE, 0xB1) // Port E. + +SPEC_FUN_REG(OEA, 0xB2) // Port A out enable. +SPEC_FUN_REG(OEB, 0xB3) // Port B out enable. +SPEC_FUN_REG(OEC, 0xB4) // Port C out enable. +SPEC_FUN_REG(OED, 0xB5) // Port D out enable. +SPEC_FUN_REG(OEE, 0xB6) // Port E out enable. + +SPEC_FUN_REG(IP, 0xB8) // Interrupt prority. +SPEC_FUN_REG_BIT(PX0, 0xB8, 0) +SPEC_FUN_REG_BIT(PT0, 0xB8, 1) +SPEC_FUN_REG_BIT(PX1, 0xB8, 2) +SPEC_FUN_REG_BIT(PT1, 0xB8, 3) +SPEC_FUN_REG_BIT(PS0, 0xB8, 4) +SPEC_FUN_REG_BIT(PT2, 0xB8, 5) +SPEC_FUN_REG_BIT(PS1, 0xB8, 6) + +SPEC_FUN_REG(EP01STAT, 0xBA) // Endpoint 0,1 status. +SPEC_FUN_REG(GPIFTRIG, 0xBB) // Endpoint 2,4,6,8 GPIF slave FIFO trigger. + +SPEC_FUN_REG(GPIFSGLDATH, 0xBD) // GPIF data H. +SPEC_FUN_REG(GPIFSGLDATLX, 0xBE) // GPIF data L with trigger. +SPEC_FUN_REG(GPIFSGLDATLNOX, 0xBF) // GPIF data L without trigger. + +SPEC_FUN_REG(SCON1, 0xC0) // Serial port 1 control. +SPEC_FUN_REG_BIT(RI1, 0xC0, 0) +SPEC_FUN_REG_BIT(TI1, 0xC0, 1) +SPEC_FUN_REG_BIT(RB81, 0xC0, 2) +SPEC_FUN_REG_BIT(TB81, 0xC0, 3) +SPEC_FUN_REG_BIT(REN1, 0xC0, 4) +SPEC_FUN_REG_BIT(SM21, 0xC0, 5) +SPEC_FUN_REG_BIT(SM11, 0xC0, 6) +SPEC_FUN_REG_BIT(SM01, 0xC0, 7) + +SPEC_FUN_REG(SBUF1, 0xC1) // Serial port 1 data buffer. + +SPEC_FUN_REG(T2CON, 0xC8) // Timer/counter 2 control. +SPEC_FUN_REG_BIT(CP_RL2, 0xC8, 0) +SPEC_FUN_REG_BIT(C_T2, 0xC8, 1) +SPEC_FUN_REG_BIT(TR2, 0xC8, 2) +SPEC_FUN_REG_BIT(EXEN2, 0xC8, 3) +SPEC_FUN_REG_BIT(TCLK, 0xC8, 4) +SPEC_FUN_REG_BIT(RCLK, 0xC8, 5) +SPEC_FUN_REG_BIT(EXF2, 0xC8, 6) +SPEC_FUN_REG_BIT(TF2, 0xC8, 7) + +SPEC_FUN_REG(RCAP2L, 0xCA) // Capture for timer 2 auto-reload up-counter L. +SPEC_FUN_REG(RCAP2H, 0xCB) // Capture for timer 2 auto-reload up-counter H. +SPEC_FUN_REG(TL2, 0xCC) // Timer 2 reload L. +SPEC_FUN_REG(TH2, 0xCD) // Timer 2 reload H. + +SPEC_FUN_REG(PSW, 0xD0) // Program status word. +SPEC_FUN_REG_BIT(P, 0xD0, 0) +SPEC_FUN_REG_BIT(FL, 0xD0, 1) +SPEC_FUN_REG_BIT(OV, 0xD0, 2) +SPEC_FUN_REG_BIT(RS0, 0xD0, 3) +SPEC_FUN_REG_BIT(RS1, 0xD0, 4) +SPEC_FUN_REG_BIT(F0, 0xD0, 5) +SPEC_FUN_REG_BIT(AC, 0xD0, 6) +SPEC_FUN_REG_BIT(CY, 0xD0, 7) + +SPEC_FUN_REG(EICON, 0xD8) // External interrupt control. +SPEC_FUN_REG_BIT(INT6, 0xD8, 3) +SPEC_FUN_REG_BIT(RESI, 0xD8, 4) +SPEC_FUN_REG_BIT(ERESI, 0xD8, 5) +SPEC_FUN_REG_BIT(SMOD1, 0xD8, 7) + +SPEC_FUN_REG(ACC, 0xE0) // Accumulator. + +SPEC_FUN_REG(EIE, 0xE8) // External interrupt enable. +SPEC_FUN_REG_BIT(EUSB, 0xE8, 0) +SPEC_FUN_REG_BIT(EI2C, 0xE8, 1) +SPEC_FUN_REG_BIT(EIEX4, 0xE8, 2) +SPEC_FUN_REG_BIT(EIEX5, 0xE8, 3) +SPEC_FUN_REG_BIT(EIEX6, 0xE8, 4) + +SPEC_FUN_REG(B, 0xF0) // B + +SPEC_FUN_REG(EIP, 0xF8) // External interrupt priority control. +SPEC_FUN_REG_BIT(PUSB, 0xF8, 0) +SPEC_FUN_REG_BIT(PI2C, 0xF8, 1) +SPEC_FUN_REG_BIT(EIPX4, 0xF8, 2) +SPEC_FUN_REG_BIT(EIPX5, 0xF8, 3) +SPEC_FUN_REG_BIT(EIPX6, 0xF8, 4) + +// +// Register bit masks. +// + +// CPU control & status register (CPUCS). +enum cpucs_bits { + bmPRTCSTB = bmBIT5, + bmCLKSPD = bmBIT4 | bmBIT3, + bmCLKSPD1 = bmBIT4, + bmCLKSPD0 = bmBIT3, + bmCLKINV = bmBIT2, + bmCLKOE = bmBIT1, + bm8051RES = bmBIT0 +}; + +// Port A (PORTACFG). +enum portacfg_bits { + bmFLAGD = bmBIT7, + bmINT1 = bmBIT1, + bmINT0 = bmBIT0 +}; + +// Port C (PORTCCFG). +enum portccfg_bits { + bmGPIFA7 = bmBIT7, + bmGPIFA6 = bmBIT6, + bmGPIFA5 = bmBIT5, + bmGPIFA4 = bmBIT4, + bmGPIFA3 = bmBIT3, + bmGPIFA2 = bmBIT2, + bmGPIFA1 = bmBIT1, + bmGPIFA0 = bmBIT0 +}; + +// Port E (PORTECFG). +enum portecfg_bits { + bmGPIFA8 = bmBIT7, + bmT2EX = bmBIT6, + bmINT6 = bmBIT5, + bmRXD1OUT = bmBIT4, + bmRXD0OUT = bmBIT3, + bmT2OUT = bmBIT2, + bmT1OUT = bmBIT1, + bmT0OUT = bmBIT0 +}; + +// I2C control & status register (I2CS). +enum i2cs_bits { + bmSTART = bmBIT7, + bmSTOP = bmBIT6, + bmLASTRD = bmBIT5, + bmID = bmBIT4 | bmBIT3, + bmBERR = bmBIT2, + bmACK = bmBIT1, + bmDONE = bmBIT0 +}; + +// I2C control register (I2CTL). +enum i2ctl_bits { + bmSTOPIE = bmBIT1, + bm400KHZ = bmBIT0 +}; + +// Interrupt 2 (USB) autovector register (INT2IVEC). +enum int2ivec_bits { + bmIV4 = bmBIT6, + bmIV3 = bmBIT5, + bmIV2 = bmBIT4, + bmIV1 = bmBIT3, + bmIV0 = bmBIT2 +}; + +// USB interrupt request & enable registers (USBIE/USBIRQ). +enum usbieirq_bits { + bmEP0ACK = bmBIT6, + bmHSGRANT = bmBIT5, + bmURES = bmBIT4, + bmSUSP = bmBIT3, + bmSUTOK = bmBIT2, + bmSOF = bmBIT1, + bmSUDAV = bmBIT0 +}; + +// End point interrupt request & enable registers (EPIE/EPIRQ). +enum epieirq_bits { + bmEP8 = bmBIT7, + bmEP6 = bmBIT6, + bmEP4 = bmBIT5, + bmEP2 = bmBIT4, + bmEP1OUT = bmBIT3, + bmEP1IN = bmBIT2, + bmEP0OUT = bmBIT1, + bmEP0IN = bmBIT0 +}; + +// Breakpoint register (BREAKPT). +enum breakpt_bits { + bmBREAK = bmBIT3, + bmBPPULSE = bmBIT2, + bmBPEN = bmBIT1 +}; + +// Interrupt 2 & 4 setup (INTSETUP). +enum intsetup_bits { + bmAV2EN = bmBIT3, + bmINT4SRC = bmBIT1, + bmAV4EN = bmBIT0 +}; + +// USB control & status register (USBCS). +enum usbcs_bits { + bmHSM = bmBIT7, + bmDISCON = bmBIT3, + bmNOSYNSOF = bmBIT2, + bmRENUM = bmBIT1, + bmSIGRESUME = bmBIT0 +}; + +// Wakeup control and status register (WAKEUPCS). +enum wakeupcs_bits { + bmWU2 = bmBIT7, + bmWU = bmBIT6, + bmWU2POL = bmBIT5, + bmWUPOL = bmBIT4, + bmDPEN = bmBIT2, + bmWU2EN = bmBIT1, + bmWUEN = bmBIT0 +}; + +// End point 0 control & status register (EP0CS). +enum ep0cs_bits { + bmHSNAK = bmBIT7 +}; + +// End point 0-1 control & status registers +// (EP0CS/EP1OUTCS/EP1INCS). +enum ep01cs_bits { + bmEPBUSY = bmBIT1, + bmEPSTALL = bmBIT0 +}; + +// End point 2-8 Control & status registers +// (EP2CS/EP4CS/EP6CS/EP8CS). +enum ep2468cs_bits { + MSK_EP2468CS_NPAK = bmBIT6 | bmBIT5 | bmBIT4, + MSK_EP2468CS_EPFULL = bmBIT3, + MSK_EP2468CS_EPEMPTY = bmBIT2 +}; + +// End point status SFR bits (EP2468STAT). +enum ep2468stat_bits { + bmEP8FULL = bmBIT7, + bmEP8EMPTY = bmBIT6, + bmEP6FULL = bmBIT5, + bmEP6EMPTY = bmBIT4, + bmEP4FULL = bmBIT3, + bmEP4EMPTY = bmBIT2, + bmEP2FULL = bmBIT1, + bmEP2EMPTY = bmBIT0 +}; + +// End point 0,1 status bits (EP01STAT). +enum ep01stat_bits { + bmEP1INBSY = bmBIT2, + bmEP1OUTBSY = bmBIT1, + bmEP0BSY = bmBIT0 +}; + +// Setup data pointer auto mode (SUDPTRCTL). +enum sudptrctl_bits { + bmSDPAUTO = bmBIT0 +}; + +// End point data toggle control (TOGCTL). +enum togctl_bits { + bmQUERYTOGGLE = bmBIT7, + bmSETTOGGLE = bmBIT6, + bmRESETTOGGLE = bmBIT5, + bmTOGCTLEPMASK = bmBIT3 | bmBIT2 | bmBIT1 | bmBIT0 +}; + +// In bulk NAK enable and request bits (IBNIE/IBNIRQ). +enum ibnieirq_bits { + bmEP8IBN = bmBIT5, + bmEP6IBN = bmBIT4, + bmEP4IBN = bmBIT3, + bmEP2IBN = bmBIT2, + bmEP1IBN = bmBIT1, + bmEP0IBN = bmBIT0 +}; + +// Ping NAK enable and request bits (NAKIE/NAKIRQ). +enum nakieirq_bits { + bmEP8PING = bmBIT7, + bmEP6PING = bmBIT6, + bmEP4PING = bmBIT5, + bmEP2PING = bmBIT4, + bmEP1PING = bmBIT3, + bmEP0PING = bmBIT2, + bmIBN = bmBIT0 +}; + +// Interface configuration bits (IFCONFIG). +enum ifconfig_bits { + bmIFCLKSRC = bmBIT7, + bm3048MHZ = bmBIT6, + bmIFCLKOE = bmBIT5, + bmIFCLKPOL = bmBIT4, + bmASYNC = bmBIT3, + bmGSTATE = bmBIT2, + bmIFCFG = bmBIT0 | bmBIT1 +}; + +// IFCFG field of IFCONFIG register. +enum ifcfg_bits { + bmIFPORTS = 0, + bmIFRESERVED = bmBIT0, + bmIFGPIF = bmBIT1, + bmIFSLAVEFIFO = bmBIT0 | bmBIT1 +}; + +// End point 2,4,6,8 FIFO configuration bits +// (EP2FIFOCFG,EP4FIFOCFG,EP6FIFOCFG,EP8FIFOCFG). +enum ep2468fifocfg_bits { + bmINFM = bmBIT6, + bmOEP = bmBIT5, + bmAUTOOUT = bmBIT4, + bmAUTOIN = bmBIT3, + bmZEROLENIN = bmBIT2, + bmWORDWIDE = bmBIT0 +}; + +// Chip revision control bits (REVCTL). +enum revctl_bits { + bmNOAUTOARM = bmBIT1, + bmSKIPCOMMIT = bmBIT0 +}; + +// Fifo reset bits (FIFORESET). +enum fiforeset_bits { + bmNAKALL = bmBIT7, + bmFREP3 = bmBIT3, + bmFREP2 = bmBIT2, + bmFREP1 = bmBIT1, + bmFREP0 = bmBIT0 +}; + +// Chip feature register (GPCR2). +enum gpcr2_bits { + bmFULLSPEEDONLY = bmBIT4 +}; + +// Clock control register (CKCON). +// Note: a RevE errata states that stretch must=0 +// to set OUTxBC. +enum ckcon_bits { + bmT2M = bmBIT5, + bmT1M = bmBIT4, + bmT0M = bmBIT3, + bmSTRETCH = bmBIT0 | bmBIT1 | bmBIT2 +}; + +enum data_mem_stretch { + bmFW_STRETCH1 = 0, // Movx: 2 cycles, R/W strobe: 2 cycles... + bmFW_STRETCH2 = 1, // Movx: 3 cycles, R/W strobe: 4 cycles... + bmFW_STRETCH3 = 2, // Movx: 4 cycles, R/W strobe: 8 cycles... + bmFW_STRETCH4 = 3, // Movx: 5 cycles, R/W strobe: 12 cycles... + bmFW_STRETCH5 = 4, // Movx: 6 cycles, R/W strobe: 16 cycles... + bmFW_STRETCH6 = 5, // Movx: 7 cycles, R/W strobe: 20 cycles... + bmFW_STRETCH7 = 6, // Movx: 8 cycles, R/W strobe: 24 cycles... + bmFW_STRETCH8 = 7 // Movx: 9 cycles, R/W strobe: 28 cycles... +}; + +// External interrupt flags (EXIF). +enum exif_bits { + bmIE5 = bmBIT7, + bmIE4 = bmBIT6, + bmI2CINT = bmBIT5, + bmUSBNT = bmBIT4 +}; + +// External interrupt control (EICON). +enum eicon_bits { + bmSMOD1 = bmBIT7, + bmERESI = bmBIT5, + bmRESI = bmBIT4, + //bmINT6 = bmBIT3 +}; + +// Power control (PCON). +enum pcon_bits { + bmSMOD0 = bmBIT7, + bmIDLE = bmBIT0 +}; + +// Autopointer 1 & 2 setup (AUTOPTRSETUP). +enum autoptrsetup_bits { + bmAPTR2INC = bmBIT2, + bmAPTR1INC = bmBIT1, + bmAPTREN = bmBIT0 +}; + +#ifdef __cplusplus +} +#endif + +#endif // FX2_REGS_H diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/usb.c b/examples/baremetal/cy7c68013a/nes-gamepads/usb.c new file mode 100644 index 00000000..59a008de --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/usb.c @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "core.h" +#include "hid.h" +#include "usb.h" + +void usb_init(void) +{ + // Disable all USB interrupts. + USBIE = 0; + sync_delay(); + // Disable all end point interrupts. + EPIE = 0; + sync_delay(); + // Disable all end point ping-nak interrupts. + NAKIE = 0; + sync_delay(); + // Disable all USB error interrupts. + USBERRIE = 0; + sync_delay(); + // Disable USB && GPIF autovectoring. + INTSETUP = 0; + sync_delay(); + // Clear USB interrupt requests. + USBIRQ = bmSUDAV | bmSOF | bmSUTOK | bmSUSP + | bmURES | bmHSGRANT | bmEP0ACK; + sync_delay(); + // Clear end point interrupt requests. + EPIRQ = bmEP0IN | bmEP0OUT | bmEP1IN | bmEP1OUT + | bmEP2 | bmEP4 | bmEP6 | bmEP8; + sync_delay(); + // Set USB interrupt to high priority. + PUSB = 1; + sync_delay(); + // Enable USB interrupts. + EUSB = 1; + sync_delay(); + // As we use SOFs to detect disconnect we have + // to disable SOF synthesizing. + USBCS |= bmNOSYNSOF; + sync_delay(); + + hid_init(); + + // Disable FX2-internal enumeration support. + USBCS |= bmRENUM; + sync_delay(); +} + +void usb_task(void) +{ + if (USBIRQ & bmSUDAV) { + USBIRQ = bmSUDAV; + hid_ep0_setup_task(); + } + + if (USBIRQ & bmEP0ACK) { + USBIRQ = bmEP0ACK; + } + + if (USBIRQ & bmURES) { + USBIRQ = bmURES; + } + + if (USBIRQ & bmSUSP) { + USBIRQ = bmSUSP; + } + + hid_ep1_task(); +} diff --git a/examples/baremetal/cy7c68013a/nes-gamepads/usb.h b/examples/baremetal/cy7c68013a/nes-gamepads/usb.h new file mode 100644 index 00000000..67877a4b --- /dev/null +++ b/examples/baremetal/cy7c68013a/nes-gamepads/usb.h @@ -0,0 +1,217 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef FX2_USB_H +#define FX2_USB_H + +#include "regs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum usb_setup_bmreq_bits { + bmSETUP_DIR = bmBIT7, + bmSETUP_TYPE = bmBIT5 | bmBIT6, + bmSETUP_RECIPIENT = bmBIT0 | bmBIT1 | bmBIT2 | bmBIT3 | bmBIT4 +}; + +// Setup request direction. +enum usb_setup_req_direction_bits { + bmSETUP_TO_DEVICE = 0, // From host to sevice direction. + bmSETUP_TO_HOST = bmBIT7 // From device to host direction. +}; + +// Setup request type. +enum usb_setup_req_type_bits { + bmSETUP_STANDARD = 0, // Standard request. + bmSETUP_CLASS = bmBIT5, // Class request. + bmSETUP_VENDOR = bmBIT6, // Vendor request. +}; + +// Setup request recipient. +enum usb_setup_req_recipient_bits { + bmSETUP_DEVICE = 0, // Device recipient. + bmSETUP_IFACE = bmBIT0, // Interface recipient. + bmSETUP_EP = bmBIT1, // End point recipient. + bmSETUP_OTHER = bmBIT0 | bmBIT1 // Other recipient. +}; + +// Setup request code. +enum usb_setup_req_code { + USB_SETUP_GET_STATUS = 0x00, // Get status code. + USB_SETUP_CLEAR_FEATURE = 0x01, // Clear feature code. + USB_SETUP_RESERVED1 = 0x02, // Reserved code. + USB_SETUP_SET_FEATURE = 0x03, // Set feature code. + USB_SETUP_RESERVED2 = 0x04, // Reserved code. + USB_SETUP_SET_ADDRESS = 0x05, // Set address code. + USB_SETUP_GET_DESCRIPTOR = 0x06, // Get descriptor code. + USB_SETUP_SET_DESCRIPTOR = 0x07, // Set descriptor code. + USB_SETUP_GET_CONFIGURATION = 0x08, // Get configuration code. + USB_SETUP_SET_CONFIGURATION = 0x09, // Set configuration code. + USB_SETUP_GET_INTERFACE = 0x0A, // Get interface code. + USB_SETUP_SET_INTERFACE = 0x0B, // Set interface code. + USB_SETUP_SYNC_FRAME = 0x0C, // Sync frame code. + USB_SETUP_ANCHOR_LOAD = 0xA0 // Anchor load code. +}; + +// Standard status responses. +enum usb_setup_status_code { + USB_STATUS_SELF_POWERED = 0x01, + USB_STATUS_REMOTE_WAKEUP = 0x02 +}; + +// Standard feature selectors. +enum usb_setup_feature_selector { + USB_FEATURE_STALL = 0x00, + USB_FEATURE_REMOTE_WAKEUP = 0x01, + USB_FEATRUE_TEST_MODE = 0x02 +}; + +// Get descriptor codes. +enum usb_setup_get_descriptor_code { + USB_DESC_DEVICE = 0x01, // Device descriptor. + USB_DESC_CONF = 0x02, // Configuration descriptor. + USB_DESC_STRING = 0x03, // String descriptor. + USB_DESC_INTERFACE = 0x04, // Interface descriptor. + USB_DESC_ENDPOINT = 0x05, // End point descriptor. + USB_DESC_DEVICE_QUAL = 0x06, // Device qualifier descriptor. + USB_DESC_OTHER_SPEED_CONF = 0x07, // Other configuration descriptor. + USB_DESC_INTERFACE_POWER = 0x08, // Interface power descriptor. + USB_DESC_OTG = 0x09, // OTG descriptor. + USB_DESC_DEBUG = 0x0A, // Debug descriptor. + USB_DESC_INTERFACE_ASSOC = 0x0B, // Interface association descriptor. + USB_DESC_HID = 0x21, // Get HID descriptor. + USB_DESC_REPORT = 0x22 // Get report descriptor. +}; + +// End point configuration (EP1INCFG/EP1OUTCFG/EP2/EP4/EP6/EP8). +enum epcfg_bits { + bmEP_VALID = bmBIT7, + bmEP_DIR = bmBIT6, // Only for EP2-EP8! + bmEP_TYPE = bmBIT5 | bmBIT4, + bmEP_SIZE = bmBIT3, // Only for EP2-EP8! + bmEP_BUF = bmBIT1 | bmBIT0 // Only for EP2-EP8! +}; + +enum ep_valid_bits { + bmEP_DISABLE = 0, + bmEP_ENABLE = bmBIT7 +}; + +// Only for EP2-EP8! +enum ep_direction { + bmEP_OUT = 0, + bmEP_IN = bmBIT6 +}; + +enum ep_type { + bmEP_ISO = bmBIT4, // Only for EP2-EP8! + bmEP_BULK = bmBIT5, // Default value. + bmEP_INT = bmBIT4 | bmBIT5 +}; + +// Only for EP2-EP8! +enum ep_size { + EP_512 = 0, + EP_1024 = bmBIT3 // Except EP4/EP8. +}; + +// Only for EP2-EP8! +enum ep_buf { + EP_QUAD = 0, + EP_DOUBLE = bmBIT1, // Default value. + EP_TRIPLE = bmBIT0 | bmBIT1 +}; + +struct ep0_buf { + BYTE *dat; + WORD len; +}; + +#define usb_disconnect() (USBCS |= bmDISCON) +#define usb_connect() (USBCS &= ~bmDISCON) + +#define usb_is_high_speed() \ + (USBCS & bmHSM) + +#define usp_ep_reset_toggle(ep) \ + TOGCTL = (((ep & 0x80) >> 3) + (ep & 0x0F)); \ + TOGCTL |= bmRESETTOGGLE + +#define usb_ep0_stall() \ + EP0CS |= bmEPSTALL + +#define usb_ep0_hsnack() \ + EP0CS |= bmHSNAK + +#define usb_word_msb_get(word) \ + (BYTE)(((WORD)(word) >> 8) & 0xFF) +#define usb_word_lsb_get(word) \ + (BYTE)((WORD)(word) & 0xFF) + +#define usb_word_swap(x) \ + ((((WORD)((x) & 0x00FF)) << 8) | \ + (((WORD)((x) & 0xFF00)) >> 8)) + +#define usb_dword_swap(x) \ + ((((DWORD)((x) & 0x000000FFul)) << 24) | \ + (((DWORD)((x) & 0x0000FF00ul)) << 8) | \ + (((DWORD)((x) & 0x00FF0000ul)) >> 8) | \ + (((DWORD)((x) & 0xFF000000ul)) >> 24)) + +void usb_init(void); +void usb_task(void); + +#ifdef __cplusplus +} +#endif + +#endif // FX2_USB_H diff --git a/examples/baremetal/msp430f5529/msp430f5529.qbs b/examples/baremetal/msp430f5529/msp430f5529.qbs new file mode 100644 index 00000000..f4a6f95b --- /dev/null +++ b/examples/baremetal/msp430f5529/msp430f5529.qbs @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 + +Project { + name: "Examples for msp430f5529 board" + references: [ + "redblink/redblink.qbs", + "nes-gamepads/nes-gamepads.qbs" + ] +} diff --git a/examples/baremetal/msp430f5529/nes-gamepads/README.md b/examples/baremetal/msp430f5529/nes-gamepads/README.md new file mode 100644 index 00000000..442bb587 --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/README.md @@ -0,0 +1,36 @@ +This example demonstrates how to build a bare-metal application using +different MSP430 toolchains. It is designed for the MSP-EXP430F5529LP +target board (based on msp430f5529 chip): + +* http://www.ti.com/tool/MSP-EXP430F5529LP + +It implements a USB HID device that connects two 8-buttons NES +(Dendy) gamepads to a PC. The gamepads are connected to the +msp430f5529 chip as follows: + +1. CLK - it is an output clock signal which generates by chip from + the port 6, pin 0 (P6.0). This pin should be connected to the CLK + inputs for both gamepads. + +2. DATA1 - it is an input data signal which comes to chip on the + the port 6, pin 1 (P6.1). This pin should be connected to the DATA + output from the gamepad #1. + +3. DATA2 - it is an input data signal which comes to chip on the + the port 6, pin 2 (P6.2). This pin should be connected to the DATA + output from the gamepad #2. + +4. LATCH - it is an output clock signal which generates by chip from + the port 6, pin 3 (P6.3). This pin should be connected to the LATCH + inputs for both gamepads. + +Actual schematic and pinouts depends on an used gamepads (with 7, 9 +or other pins connectors) and a development boards. + +Also, do not forget to connect the +3.3V and GND wires to the gamepads. +Then it is possible to play 8-bit NES games using various PC simulators. + +The following toolchains are supported: + + * IAR Embedded Workbench + * GCC diff --git a/examples/baremetal/msp430f5529/nes-gamepads/gamepads.ld b/examples/baremetal/msp430f5529/nes-gamepads/gamepads.ld new file mode 100644 index 00000000..a3906a47 --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/gamepads.ld @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +g_setupdat = 0x2380; /* Setup packet block address. */ + +g_ep0 = 0x0920; /* Input ep0 configuration address. */ +g_ep0_in_buf = 0x2378; /* Input ep0 buffer address. */ + +g_ep1_in = 0x23C8; /* Input ep1 configuration address. */ +g_ep1_in_xbuf = 0x1C80; /* Ep1 X-buffer address. */ diff --git a/examples/baremetal/msp430f5529/nes-gamepads/gpio.c b/examples/baremetal/msp430f5529/nes-gamepads/gpio.c new file mode 100644 index 00000000..b5e58bc7 --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/gpio.c @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "hwdefs.h" +#include "gpio.h" + +enum { INVALID_ADDRESS = 0xFFFF }; + +static uint16_t gpio_port_address_get(enum gpio_port port) +{ + switch (port) { + case GPIO_PORT_P1: + return P1_BASE; + case GPIO_PORT_P2: + return P2_BASE; + case GPIO_PORT_P3: + return P3_BASE; + case GPIO_PORT_P4: + return P4_BASE; + case GPIO_PORT_P5: + return P5_BASE; + case GPIO_PORT_P6: + return P6_BASE; + case GPIO_PORT_P7: + return P7_BASE; + case GPIO_PORT_P8: + return P8_BASE; + case GPIO_PORT_PJ: + return PJ_BASE; + default: + break; + } + return INVALID_ADDRESS; +} + +static uint16_t gpio_pins_adjust(enum gpio_port port, uint16_t pins) +{ + if ((port & 1) ^ 1) + pins <<= 8; + return pins; +} + +void gpio_pins_set_as_out(enum gpio_port port, uint16_t pins) +{ + const uint16_t base_address = gpio_port_address_get(port); + if (base_address == INVALID_ADDRESS) + return; + pins = gpio_pins_adjust(port, pins); + HWREG16(base_address + OFS_PASEL) &= ~pins; + HWREG16(base_address + OFS_PADIR) |= pins; +} + +void gpio_pins_set_as_in(enum gpio_port port, uint16_t pins) +{ + const uint16_t base_address = gpio_port_address_get(port); + if (base_address == INVALID_ADDRESS) + return; + pins = gpio_pins_adjust(port, pins); + HWREG16(base_address + OFS_PASEL) &= ~pins; + HWREG16(base_address + OFS_PADIR) &= ~pins; + HWREG16(base_address + OFS_PAREN) &= ~pins; +} + +void gpio_pins_set_as_pf_out(enum gpio_port port, uint16_t pins) +{ + const uint16_t base_address = gpio_port_address_get(port); + if (base_address == INVALID_ADDRESS) + return; + pins = gpio_pins_adjust(port, pins); + HWREG16(base_address + OFS_PADIR) |= pins; + HWREG16(base_address + OFS_PASEL) |= pins; +} + +void gpio_pins_set_as_pf_in(enum gpio_port port, uint16_t pins) +{ + const uint16_t base_address = gpio_port_address_get(port); + if (base_address == INVALID_ADDRESS) + return; + pins = gpio_pins_adjust(port, pins); + HWREG16(base_address + OFS_PADIR) &= ~pins; + HWREG16(base_address + OFS_PASEL) |= pins; +} + +void gpio_pins_set_high(enum gpio_port port, + uint16_t pins) +{ + const uint16_t base_address = gpio_port_address_get(port); + if (base_address == INVALID_ADDRESS) + return; + pins = gpio_pins_adjust(port, pins); + HWREG16(base_address + OFS_PAOUT) |= pins; +} + +void gpio_pins_set_low(enum gpio_port port, uint16_t pins) +{ + const uint16_t base_address = gpio_port_address_get(port); + if (base_address == INVALID_ADDRESS) + return; + pins = gpio_pins_adjust(port, pins); + HWREG16(base_address + OFS_PAOUT) &= ~pins; +} + +void gpio_pins_toggle(enum gpio_port port, uint16_t pins) +{ + const uint16_t base_address = gpio_port_address_get(port); + if (base_address == INVALID_ADDRESS) + return; + pins = gpio_pins_adjust(port, pins); + HWREG16(base_address + OFS_PAOUT) ^= pins; +} + +enum gpio_pin_status gpio_pin_get(enum gpio_port port, uint16_t pins) +{ + const uint16_t base_address = gpio_port_address_get(port); + if (base_address == INVALID_ADDRESS) + return GPIO_INPUT_PIN_LOW; + pins = gpio_pins_adjust(port, pins); + const uint16_t value = HWREG16(base_address + OFS_PAIN) & pins; + return (value > 0) ? GPIO_INPUT_PIN_HIGH : GPIO_INPUT_PIN_LOW; +} diff --git a/examples/baremetal/msp430f5529/nes-gamepads/gpio.h b/examples/baremetal/msp430f5529/nes-gamepads/gpio.h new file mode 100644 index 00000000..d69a543b --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/gpio.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef MSP430_GPIO_H +#define MSP430_GPIO_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum gpio_port { + GPIO_PORT_P1 = 1, + GPIO_PORT_P2 = 2, + GPIO_PORT_P3 = 3, + GPIO_PORT_P4 = 4, + GPIO_PORT_P5 = 5, + GPIO_PORT_P6 = 6, + GPIO_PORT_P7 = 7, + GPIO_PORT_P8 = 8, + GPIO_PORT_PJ = 13, +}; + +enum gpio_pin { + GPIO_PIN0 = 0x0001, + GPIO_PIN1 = 0x0002, + GPIO_PIN2 = 0x0004, + GPIO_PIN3 = 0x0008, + GPIO_PIN4 = 0x0010, + GPIO_PIN5 = 0x0020, + GPIO_PIN6 = 0x0040, + GPIO_PIN7 = 0x0080, + GPIO_PIN8 = 0x0100, + GPIO_PIN9 = 0x0200, + GPIO_PIN10 = 0x0400, + GPIO_PIN11 = 0x0800, + GPIO_PIN12 = 0x1000, + GPIO_PIN13 = 0x2000, + GPIO_PIN14 = 0x4000, + GPIO_PIN15 = 0x8000, +}; + +enum gpio_pin_status { + GPIO_INPUT_PIN_HIGH = 0x01, + GPIO_INPUT_PIN_LOW = 0x00 +}; + +void gpio_pins_set_as_out(enum gpio_port port, uint16_t pins); +void gpio_pins_set_as_in(enum gpio_port port, uint16_t pins); +void gpio_pins_set_as_pf_out(enum gpio_port port, uint16_t pins); +void gpio_pins_set_as_pf_in(enum gpio_port port, uint16_t pins); +void gpio_pins_set_high(enum gpio_port port, uint16_t pins); +void gpio_pins_set_low(enum gpio_port port, uint16_t pins); +void gpio_pins_toggle(enum gpio_port port, uint16_t pins); + +enum gpio_pin_status gpio_pin_get(enum gpio_port port, uint16_t pins); + +#ifdef __cplusplus +} +#endif + +#endif // MSP430_GPIO_H diff --git a/examples/baremetal/msp430f5529/nes-gamepads/hid.h b/examples/baremetal/msp430f5529/nes-gamepads/hid.h new file mode 100644 index 00000000..1307301d --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/hid.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef MSP430_HID_H +#define MSP430_HID_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum hid_constants { + HID_CONFIG_NUMBER = 1, // Number of valid configuration. + HID_IFACE_NUMBER = 0, // Number of valid interface. + HID_ALT_IFACE_NUMBER = 0, // Number of valid alternate interface. + HID_EP_IN = 0x81 // Active end point address. +}; + +enum hid_gamepad_id { + HID_REPORT_ID_GAMEPAD1 = 1, + HID_REPORT_ID_GAMEPAD2 = 2 +}; + +enum { + HID_REPORTS_COUNT = 2, + HID_REPORT_BITS_COUNT = 8 +}; + +void hid_ep0_init(void); + +// Called only in interrupt context. +void hid_ep0_setup_handler(void); +void hid_ep0_in_handler(void); + +void hid_ep0_in_nak(void); +void hid_ep0_in_stall(void); +void hid_ep0_in_clear(void); +bool hid_ep0_in_is_stalled(void); + +void hid_ep0_out_nak(void); +void hid_ep0_out_stall(void); +void hid_ep0_out_clear(void); + +const uint8_t *hid_ep0_desc_get(uint8_t type, uint16_t *length); + +uint8_t hid_ep0_setup_bm_request_type_get(void); +uint8_t hid_ep0_setup_request_get(void); +uint8_t hid_ep0_setup_lvalue_get(void); +uint8_t hid_ep0_setup_hvalue_get(void); +uint8_t hid_ep0_setup_lindex_get(void); +uint8_t hid_ep0_setup_hindex_get(void); + +void hid_ep0_enumerated_set(bool enumerated); +bool hid_ep0_is_enumerated(void); + +void hid_ep1_init(void); +void hid_ep1_task(void); + +void hid_ep1_in_stall(void); +void hid_ep1_in_unstall(void); +bool hid_ep1_in_is_stalled(void); + +#ifdef __cplusplus +} +#endif + +#endif // MSP430_HID_H diff --git a/examples/baremetal/msp430f5529/nes-gamepads/hiddesc.c b/examples/baremetal/msp430f5529/nes-gamepads/hiddesc.c new file mode 100644 index 00000000..eb391d6c --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/hiddesc.c @@ -0,0 +1,303 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "hid.h" +#include "usb.h" + +#include + +#define usb_word_msb_get(word) \ + (uint8_t)(((uint16_t)(word) >> 8) & 0xFF) +#define usb_word_lsb_get(word) \ + (uint8_t)((uint16_t)(word) & 0xFF) + +enum usb_bcd_version { + SPEC_BCD_VERSION = 0x0200, + DEVICE_BCD_VERSION = 0x1234, + HID_BCD_VERSION = 0x0111 +}; + +enum usb_ids { + DEVICE_VID = 0xFFFF, + DEVICE_PID = 0xFFFF +}; + +enum usb_lang_id { + LANG_ID = 0x0409 +}; + +enum usb_descriptor_string_index { + DESC_LANGID_STRING_INDEX = 0, + DESC_MFG_STRING_INDEX, + DESC_PRODUCT_STRING_INDEX, + DESC_SERIAL_STRING_INDEX +}; + +enum usb_descr_length { + // Standard length. + DESC_DEVICE_LEN = 18, + DESC_DEVICE_QUAL_LEN = 10, + DESC_CONF_LEN = 9, + DESC_OTHER_SPEED_CONF_LEN = 9, + DESC_INTERFACE_LEN = 9, + DESC_HID_LEN = 9, + DESCR_HID_REP_LEN = 56, + DESC_ENDPOINT_LEN = 7, + + // Total length. + DESC_DEVICE_TOTAL_LEN = DESC_DEVICE_LEN, + DESC_DEVICE_QUAL_TOTAL_LEN = DESC_DEVICE_QUAL_LEN, + DESC_CONF_TOTAL_LEN = DESC_CONF_LEN + DESC_INTERFACE_LEN + DESC_HID_LEN + DESC_ENDPOINT_LEN, + DESC_OTHER_SPEED_CONF_TOTAL_LEN = DESC_OTHER_SPEED_CONF_LEN + DESC_INTERFACE_LEN, + DESCR_HID_REP_TOTAL_LEN = DESCR_HID_REP_LEN +}; + +enum usb_descr_attributes { + // Attributes (b7 - buspwr, b6 - selfpwr, b5 - rwu). + USB_DESC_ATTRIBUTES = 0xA0, + // 100 mA (div 2). + USB_DESC_POWER_CONSUMPTION = 50 +}; + +enum usb_descr_numbers { + USB_DESC_CONFIG_COUNT = 1 +}; + +static const uint8_t g_hid_report_desc[DESCR_HID_REP_TOTAL_LEN] = { + // Pad #1 configuration. + 0x05, 0x01, // Usage page (Generic Desktop). + 0x09, 0x05, // Usage (Game Pad). + 0xa1, 0x01, // Collection (Application). + 0xa1, 0x00, // Collection (Physical). + 0x85, HID_REPORT_ID_GAMEPAD1, // Report id (1). + 0x05, 0x09, // Usage page (Button). + 0x19, 0x01, // Usage minimum (Button 1). + 0x29, 0x08, // Usage maximum (Button 8). + 0x15, 0x00, // Logical minimum (0). + 0x25, 0x01, // Logical maximum (1). + 0x95, HID_REPORT_BITS_COUNT, // Report count (8). + 0x75, 0x01, // Report size (1). + 0x81, 0x02, // Input (Data,Var,Abs). + 0xc0, // End collection. + 0xc0, // End collection. + // Pad #2 configuration. + 0x05, 0x01, // Usage page (Generic Desktop). + 0x09, 0x05, // Usage (Game Pad). + 0xa1, 0x01, // Collection (Application). + 0xa1, 0x00, // Collection (Physical). + 0x85, HID_REPORT_ID_GAMEPAD2, // Report id (2). + 0x05, 0x09, // Usage page (Button). + 0x19, 0x01, // Usage minimum (Button 1). + 0x29, 0x08, // Usage maximum (Button 8). + 0x15, 0x00, // Logical minimum (0). + 0x25, 0x01, // Logical maximum (1). + 0x95, HID_REPORT_BITS_COUNT, // Report count (8). + 0x75, 0x01, // Report size (1). + 0x81, 0x02, // Input (Data,Var,Abs). + 0xc0, // End collection + 0xc0 // End collection. +}; + +static const uint8_t g_device_desc[DESC_DEVICE_TOTAL_LEN] = { + DESC_DEVICE_LEN, // Descriptor length. + DESC_DEVICE, // Descriptor type. + usb_word_lsb_get(SPEC_BCD_VERSION), // USB BCD version, lo. + usb_word_msb_get(SPEC_BCD_VERSION), // USB BCD version, hi. + 0x00, // Device class. + 0x00, // Device sub-class. + 0x00, // Device protocol. + EP0_MAX_PACKET_SIZE, // Maximum packet size (ep0 size). + usb_word_lsb_get(DEVICE_VID), // Vendor ID, lo. + usb_word_msb_get(DEVICE_VID), // Vendor ID, hi. + usb_word_lsb_get(DEVICE_PID), // Product ID, lo. + usb_word_msb_get(DEVICE_PID), // Product ID, hi. + usb_word_lsb_get(DEVICE_BCD_VERSION), // Device BCD version, lo. + usb_word_msb_get(DEVICE_BCD_VERSION), // Device BCD version, hi. + DESC_MFG_STRING_INDEX, + DESC_PRODUCT_STRING_INDEX, + DESC_SERIAL_STRING_INDEX, + USB_DESC_CONFIG_COUNT // Configurations count. +}; + +static const uint8_t g_device_qual_desc[DESC_DEVICE_QUAL_TOTAL_LEN] = { + DESC_DEVICE_QUAL_LEN, // Descriptor length. + DESC_DEVICE_QUAL, // Descriptor type. + usb_word_lsb_get(SPEC_BCD_VERSION), // USB BCD version, lo. + usb_word_msb_get(SPEC_BCD_VERSION), // USB BCD version, hi. + 0x00, // Device class. + 0x00, // Device sub-class. + 0x00, // Device protocol. + EP0_MAX_PACKET_SIZE, // Maximum packet size (ep0 size). + USB_DESC_CONFIG_COUNT, // Configurations count. + 0x00 // Reserved. +}; + +static const uint8_t g_config_desc[DESC_CONF_TOTAL_LEN] = { + DESC_CONF_LEN, // Descriptor length. + DESC_CONF, // Descriptor type. + usb_word_lsb_get(DESC_CONF_TOTAL_LEN), // Total length, lo. + usb_word_msb_get(DESC_CONF_TOTAL_LEN), // Total length, hi. + 0x01, // Interfaces count. + HID_CONFIG_NUMBER, // Configuration number. + 0x00, // Configuration string index. + USB_DESC_ATTRIBUTES, // Attributes. + USB_DESC_POWER_CONSUMPTION, // Power consumption. + + // Interface descriptor. + DESC_INTERFACE_LEN, // Descriptor length. + DESC_INTERFACE, // Descriptor type. + HID_IFACE_NUMBER, // Zero-based index of this interfacce. + HID_ALT_IFACE_NUMBER, // Alternate setting. + 0x01, // End points count (ep1 in). + 0x03, // Class code (HID). + 0x00, // Subclass code (boot). + 0x00, // Protocol code (none). + 0x00, // Interface string descriptor index. + + // HID descriptor. + DESC_HID_LEN, // Descriptor length. + DESC_HID, // Descriptor type. + usb_word_lsb_get(HID_BCD_VERSION), // HID class BCD version, lo. + usb_word_msb_get(HID_BCD_VERSION), // HID class BCD version, hi. + 0x00, // Country code (HW country code). + 0x01, // Number of HID class descriptors to follow. + DESC_REPORT, // Repord descriptor type (HID). + usb_word_lsb_get(DESCR_HID_REP_LEN), // Report descriptor total length, lo. + usb_word_msb_get(DESCR_HID_REP_LEN), // Report descriptor total length, hi. + + // End point descriptor. + DESC_ENDPOINT_LEN, // Descriptor length. + DESC_ENDPOINT, // Descriptor type. + HID_EP_IN, // End point address (ep1 in). + 0x03, // End point type (interrupt). + usb_word_lsb_get(EP_MAX_PACKET_SIZE), // Maximum packet size, lo (ep1 size). + usb_word_msb_get(EP_MAX_PACKET_SIZE), // Maximum packet size, hi (ep1 size). + 0x01 // Polling interval (1 ms). +}; + +static const uint8_t g_lang_id_desc[] = { + 0x04, // Descriptor length. + DESC_STRING, // Descriptor type. + usb_word_lsb_get(LANG_ID), // Language id, lo. + usb_word_msb_get(LANG_ID) // Language id, hi. +}; + +static const uint8_t g_manuf_str_desc[] = { + 0x18, // Descriptor length. + DESC_STRING, // Descriptor type. + // Unicode string: + 'Q', 0, 'B', 0, 'S', 0, ' ', 0, + 'e', 0, 'x', 0, 'a', 0, 'm', 0, 'p', 0, 'l', 0, 'e', 0 +}; + +static const uint8_t g_product_str_desc[] = { + 0x1A, // Descriptor length. + DESC_STRING, // Descriptor type. + // Unicode string: + 'N', 0, 'E', 0, 'S', 0, ' ', 0, + 'G', 0, 'a', 0, 'm', 0, 'e', 0, + 'P', 0, 'a', 0, 'd', 0, 's', 0 +}; + +static const uint8_t g_serialno_str_desc[] = { + 0x1A, // Descriptor length. + DESC_STRING, // Descriptor type. + // Unicode string: + '0', 0 ,'1', 0, '0', 0, '2', 0, '0', 0, '3', 0, + '0', 0, '4', 0, '0', 0, '5', 0, '0', 0, '6', 0 +}; + +static const uint8_t *ep0_string_desc_get(uint16_t *length) +{ + const uint8_t index = hid_ep0_setup_lvalue_get(); + switch (index) { + case DESC_LANGID_STRING_INDEX: + *length = sizeof(g_lang_id_desc); + return g_lang_id_desc; + case DESC_MFG_STRING_INDEX: + *length = sizeof(g_manuf_str_desc); + return g_manuf_str_desc; + case DESC_PRODUCT_STRING_INDEX: + *length = sizeof(g_product_str_desc); + return g_product_str_desc; + case DESC_SERIAL_STRING_INDEX: + *length = sizeof(g_serialno_str_desc); + return g_serialno_str_desc; + default: + break; + } + return 0; +} + +const uint8_t *hid_ep0_desc_get(uint8_t type, uint16_t *length) +{ + switch (type) { + case DESC_DEVICE: + *length = DESC_DEVICE_TOTAL_LEN; + return g_device_desc; + case DESC_CONF: + *length = DESC_CONF_TOTAL_LEN; + return g_config_desc; + case DESC_STRING: + return ep0_string_desc_get(length); + case DESC_DEVICE_QUAL: + *length = DESC_DEVICE_QUAL_TOTAL_LEN; + return g_device_qual_desc; + case DESC_HID: + *length = DESC_HID_LEN; + return &g_config_desc[DESC_CONF_LEN + DESC_INTERFACE_LEN]; + case DESC_REPORT: + *length = DESCR_HID_REP_TOTAL_LEN; + return g_hid_report_desc; + default: + break; + } + return 0; +} diff --git a/examples/baremetal/msp430f5529/nes-gamepads/hidep0.c b/examples/baremetal/msp430f5529/nes-gamepads/hidep0.c new file mode 100644 index 00000000..c4a267a5 --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/hidep0.c @@ -0,0 +1,554 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "hid.h" +#include "hwdefs.h" +#include "usb.h" + +enum usb_ep0_action { + STATUS_ACTION_NOTHING, + STATUS_ACTION_DATA_IN +}; + +struct usb_ep0 { + uint8_t IEPCNFG; // Input ep0 configuration register. + uint8_t IEPBCNT; // Input ep0 buffer byte count. + uint8_t OEPCNFG; // Output ep0 configuration register. + uint8_t OEPBCNT; // Output ep0 buffer byte count. +}; + +struct usb_ep0_data { + uint8_t data[EP_MAX_FIFO_SIZE]; + uint16_t length; + uint16_t offset; + enum usb_ep0_action action; + bool host_ask_more_data_than_available; +}; + +#if defined(__ICC430__) +#pragma location = 0x0920 // Input ep0 configuration address. +__no_init struct usb_ep0 __data16 g_ep0; +#pragma location = 0x2378 // Input ep0 buffer address. +__no_init uint8_t __data16 g_ep0_in_buf[EP0_MAX_PACKET_SIZE]; +#pragma location = 0x2380 // Setup packet block address. +__no_init uint8_t __data16 g_setupdat[EP0_MAX_PACKET_SIZE]; +#elif defined(__GNUC__) +extern struct usb_ep0 g_ep0; +extern uint8_t g_ep0_in_buf[EP0_MAX_PACKET_SIZE]; +extern uint8_t g_setupdat[EP0_MAX_PACKET_SIZE]; +#endif + +static volatile bool g_enumerated = false; +static bool g_rwuen = false; +static bool g_selfpwr = false; +static struct usb_ep0_data g_ep0_response; + +static bool ep0_in_response_create(const uint8_t *data, uint16_t length) +{ + if (sizeof(g_ep0_response.data) < length) + return false; + + for (uint16_t i = 0; i < length; ++i) + g_ep0_response.data[i] = data[i]; + + g_ep0_response.length = length; + g_ep0_response.offset = 0; + + const uint16_t setup_length = (g_setupdat[7] << 8) | g_setupdat[6]; + if (g_ep0_response.length >= setup_length) { + g_ep0_response.length = setup_length; + g_ep0_response.host_ask_more_data_than_available = false; + } else { + g_ep0_response.host_ask_more_data_than_available = true; + } + + return true; +} + +void ep0_in_frame_send(void) +{ + uint8_t frame_size = 0; + + if (g_ep0_response.length != EP_NO_MORE_DATA) { + if (g_ep0_response.length > EP0_MAX_PACKET_SIZE) { + frame_size = EP0_MAX_PACKET_SIZE; + g_ep0_response.length -= EP0_MAX_PACKET_SIZE; + g_ep0_response.action = STATUS_ACTION_DATA_IN; + } else if (g_ep0_response.length < EP0_MAX_PACKET_SIZE) { + frame_size = g_ep0_response.length; + g_ep0_response.length = EP_NO_MORE_DATA; + g_ep0_response.action = STATUS_ACTION_NOTHING; + } else { + frame_size = EP0_MAX_PACKET_SIZE; + if (g_ep0_response.host_ask_more_data_than_available) { + g_ep0_response.length = 0; + g_ep0_response.action = STATUS_ACTION_DATA_IN; + } else { + g_ep0_response.length = EP_NO_MORE_DATA; + g_ep0_response.action = STATUS_ACTION_NOTHING; + } + } + + for (uint8_t i = 0; i < frame_size; ++i) { + g_ep0_in_buf[i] = g_ep0_response.data[g_ep0_response.offset]; + ++g_ep0_response.offset; + } + + g_ep0.IEPBCNT = frame_size; + } else { + g_ep0_response.action = STATUS_ACTION_NOTHING; + } +} + +static bool ep0_dev_status_get(void) +{ + uint16_t status = 0; + if (g_selfpwr) + status |= STATUS_SELF_POWERED; + if (g_rwuen) + status |= STATUS_REMOTE_WAKEUP; + ep0_in_response_create((uint8_t *)&status, sizeof(status)); + return true; +} + +static bool ep0_iface_status_get(void) +{ + uint16_t status = 0; + ep0_in_response_create((uint8_t *)&status, sizeof(status)); + return true; +} + +static bool ep0_ep_status_get(void) +{ + uint16_t status = 0; + const uint8_t ep = hid_ep0_setup_lindex_get(); + const uint8_t ep_num = ep & ~SETUP_DIR; + if (ep_num == 0) { + status = hid_ep0_in_is_stalled() ? 1 : 0; + } else if ((ep_num == 1) && (ep & SETUP_DIR) == SETUP_INPUT) { + // We support only one input ep1 in. + status = hid_ep1_in_is_stalled() ? 1 : 0; + } else { + return false; + } + + ep0_in_response_create((uint8_t *)&status, sizeof(status)); + return true; +} + +static bool ep0_get_status_proc(void) +{ + const uint8_t bm_req_type = hid_ep0_setup_bm_request_type_get(); + if ((bm_req_type & SETUP_DIR) != SETUP_INPUT) + return false; + const uint8_t recipient = bm_req_type & SETUP_RECIPIENT; + switch (recipient) { + case SETUP_DEVICE: + return ep0_dev_status_get(); + case SETUP_IFACE: + return ep0_iface_status_get(); + case SETUP_EP: + return ep0_ep_status_get(); + default: + break; + } + return false; +} + +static bool ep0_dev_feature_clear(void) +{ + const uint8_t feature = hid_ep0_setup_lvalue_get(); + if (feature != FEATURE_REMOTE_WAKEUP) + return false; + g_rwuen = false; + return true; +} + +static bool ep0_ep_feature_clear(void) +{ + const uint8_t feature = hid_ep0_setup_lvalue_get(); + if (feature != FEATURE_STALL) + return false; + const uint8_t ep = hid_ep0_setup_lindex_get(); + const uint8_t ep_num = ep & ~SETUP_DIR; + if (ep_num == 0) { + // Do nothing. + } else if ((ep_num == 1) && (ep & SETUP_DIR) == SETUP_INPUT) { + // We support only one input ep1 in. + hid_ep1_in_unstall(); + } else { + return false; + } + + hid_ep0_in_clear(); + return true; +} + +static bool ep0_clear_feature_proc(void) +{ + const uint8_t bm_req_type = hid_ep0_setup_bm_request_type_get(); + if ((bm_req_type & SETUP_DIR) != SETUP_INPUT) + return false; + const uint8_t recipient = bm_req_type & SETUP_RECIPIENT; + switch (recipient) { + case SETUP_DEVICE: + return ep0_dev_feature_clear(); + case SETUP_EP: + return ep0_ep_feature_clear(); + default: + break; + } + return false; +} + +static bool ep0_dev_feature_set(void) +{ + const uint8_t feature = hid_ep0_setup_lvalue_get(); + switch (feature) { + case FEATURE_REMOTE_WAKEUP: + g_rwuen = true; + return true; + case FEATURE_TEST_MODE: + // This is "test mode", just return the handshake. + return true; + default: + break; + } + return false; +} + +static bool ep0_ep_feature_set(void) +{ + const uint8_t feature = hid_ep0_setup_lvalue_get(); + if (feature != FEATURE_STALL) + return false; + const uint8_t ep = hid_ep0_setup_lindex_get(); + const uint8_t ep_num = ep & ~SETUP_DIR; + if (ep_num == 0) { + // Do nothing. + } else if ((ep_num == 1) && (ep & SETUP_DIR) == SETUP_INPUT) { + // We support only one input ep1 in. + hid_ep1_in_stall(); + } else { + return false; + } + hid_ep0_in_clear(); + return true; +} + +static bool ep0_set_feature_proc(void) +{ + const uint8_t bm_req_type = hid_ep0_setup_bm_request_type_get(); + switch (bm_req_type & SETUP_RECIPIENT) { + case SETUP_DEVICE: + return ep0_dev_feature_set(); + case SETUP_EP: + return ep0_ep_feature_set(); + default: + break; + } + return false; +} + +static bool ep0_set_address_proc(void) +{ + hid_ep0_out_stall(); + const uint8_t address = hid_ep0_setup_lvalue_get(); + if (address >= 128) + return false; + USBFUNADR = address; + hid_ep0_in_clear(); + return true; +} + +static bool ep0_descriptor_proc(uint8_t type) +{ + uint16_t length = 0; + const uint8_t *pdesc = hid_ep0_desc_get(type, &length); + if (!pdesc) + return false; + ep0_in_response_create(pdesc, length); + return true; +} + +static bool ep0_get_descriptor_proc(void) +{ + const uint8_t descr_type = hid_ep0_setup_hvalue_get(); + switch (descr_type) { + case DESC_DEVICE: + case DESC_CONF: + case DESC_STRING: + case DESC_DEVICE_QUAL: + case DESC_OTHER_SPEED_CONF: + case DESC_HID: + case DESC_REPORT: + return ep0_descriptor_proc(descr_type); + } + return false; +} + +static bool ep0_get_config_proc(void) +{ + // We only support configuration 1. + const uint8_t cfg_num = HID_CONFIG_NUMBER; + ep0_in_response_create(&cfg_num, sizeof(cfg_num)); + return true; +} + +static bool ep0_set_config_proc(void) +{ + const uint8_t cfg_num = hid_ep0_setup_lvalue_get(); + // We only support configuration 1. + const bool is_valid = (cfg_num & HID_CONFIG_NUMBER); + hid_ep0_enumerated_set(is_valid); + return is_valid; +} + +static bool ep0_get_iface_proc(void) +{ + const uint8_t iface_num = hid_ep0_setup_lindex_get(); + if (iface_num != HID_IFACE_NUMBER) + return false; + ep0_in_response_create(&iface_num, sizeof(iface_num)); + return true; +} + +static bool ep0_set_iface_proc(void) +{ + const uint8_t iface_num = hid_ep0_setup_lindex_get(); + if (iface_num != HID_IFACE_NUMBER) + return false; + + const uint8_t alt_iface_num = hid_ep0_setup_lvalue_get(); + if (alt_iface_num != HID_ALT_IFACE_NUMBER) + return false; + + hid_ep0_out_stall(); + hid_ep0_in_clear(); + return true; +} + +static bool ep0_std_proc(void) +{ + const uint8_t request_code = hid_ep0_setup_request_get(); + switch (request_code) { + case SETUP_GET_STATUS: + return ep0_get_status_proc(); + case SETUP_CLEAR_FEATURE: + return ep0_clear_feature_proc(); + case SETUP_SET_FEATURE: + return ep0_set_feature_proc(); + case SETUP_SET_ADDRESS: + return ep0_set_address_proc(); + case SETUP_GET_DESCRIPTOR: + return ep0_get_descriptor_proc(); + case SETUP_GET_CONFIGURATION: + return ep0_get_config_proc(); + case SETUP_SET_CONFIGURATION: + return ep0_set_config_proc(); + case SETUP_GET_INTERFACE: + return ep0_get_iface_proc(); + case SETUP_SET_INTERFACE: + return ep0_set_iface_proc(); + default: + break; + } + return false; +} + +static bool ep0_setup(void) +{ + const uint8_t bm_req_type = hid_ep0_setup_bm_request_type_get(); + const uint8_t setup_type = bm_req_type & SETUP_TYPE; + switch (setup_type) { + case SETUP_STANDARD: + return ep0_std_proc(); + default: + break; + } + return false; +} + +static bool ep0_has_another_setup(void) +{ + if ((USBIFG & STPOWIFG) != 0) { + USBIFG &= ~(STPOWIFG | SETUPIFG); + return true; + } + return false; +} + +void hid_ep0_init(void) +{ + hid_ep0_in_nak(); + hid_ep0_out_nak(); + + g_ep0.IEPCNFG = UBME | STALL | USBIIE; + g_ep0.OEPCNFG = UBME | STALL | USBIIE; + + // Enable only ep0in interrupts. + USBIEPIE |= BIT0; +} + +void hid_ep0_setup_handler(void) +{ + USBCTL |= FRSTE; + + g_ep0_response.length = 0; + g_ep0_response.offset = 0; + g_ep0_response.action = STATUS_ACTION_NOTHING; + g_ep0_response.host_ask_more_data_than_available = false; + + for (;;) { + const uint8_t bm_req_type = hid_ep0_setup_bm_request_type_get(); + if ((bm_req_type & SETUP_INPUT) == SETUP_INPUT) + USBCTL |= DIR; + else + USBCTL &= ~DIR; + + const bool success = ep0_setup(); + if (success) { + hid_ep0_out_clear(); + ep0_in_frame_send(); + } else { + hid_ep0_in_stall(); + } + + if (!ep0_has_another_setup()) + return; + } +} + +void hid_ep0_in_handler(void) +{ + USBCTL |= FRSTE; + hid_ep0_out_clear(); + if (g_ep0_response.action == STATUS_ACTION_DATA_IN) + ep0_in_frame_send(); + else + hid_ep0_in_stall(); +} + +void hid_ep0_in_nak(void) +{ + g_ep0.IEPBCNT = NAK; +} + +void hid_ep0_in_stall(void) +{ + g_ep0.IEPCNFG |= STALL; +} + +void hid_ep0_in_clear(void) +{ + g_ep0_response.length = EP_NO_MORE_DATA; + g_ep0_response.action = STATUS_ACTION_NOTHING; + g_ep0.IEPBCNT = 0; +} + +bool hid_ep0_in_is_stalled(void) +{ + return (g_ep0.IEPCNFG & STALL); +} + +void hid_ep0_out_nak(void) +{ + g_ep0.OEPBCNT = NAK; +} + +void hid_ep0_out_stall(void) +{ + g_ep0.OEPCNFG |= STALL; +} + +void hid_ep0_out_clear(void) +{ + g_ep0.OEPBCNT = 0; +} + +uint8_t hid_ep0_setup_bm_request_type_get(void) +{ + return g_setupdat[0]; +} + +uint8_t hid_ep0_setup_request_get(void) +{ + return g_setupdat[1]; +} + +uint8_t hid_ep0_setup_lvalue_get(void) +{ + return g_setupdat[2]; +} + +uint8_t hid_ep0_setup_hvalue_get(void) +{ + return g_setupdat[3]; +} + +uint8_t hid_ep0_setup_lindex_get(void) +{ + return g_setupdat[4]; +} + +uint8_t hid_ep0_setup_hindex_get(void) +{ + return g_setupdat[5]; +} + +void hid_ep0_enumerated_set(bool enumerated) +{ + g_enumerated = enumerated; +} + +bool hid_ep0_is_enumerated(void) +{ + return g_enumerated; +} diff --git a/examples/baremetal/msp430f5529/nes-gamepads/hidep1.c b/examples/baremetal/msp430f5529/nes-gamepads/hidep1.c new file mode 100644 index 00000000..2d29490e --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/hidep1.c @@ -0,0 +1,214 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "gpio.h" +#include "hid.h" +#include "hwdefs.h" +#include "usb.h" + +// We use this empirical value to make a GPIO polling +// every ~10 msec (rough). It is a simplest way, because +// instead we need to use a timers with interrupts callbacks. +enum { POLLING_COUNTER = 888 }; + +// Pins on the port 6. +enum gpio_pins { + GPIO_CLK_PIN = BIT0, + GPIO_DATA1_PIN = BIT1, + GPIO_DATA2_PIN = BIT2, + GPIO_LATCH_PIN = BIT3, +}; + +struct usb_ep { + uint8_t EPCNF; // Endpoint configuration. + uint8_t EPBBAX; // Endpoint X buffer base address. + uint8_t EPBCTX; // Endpoint X Buffer byte count. + uint8_t SPARE0; // No used. + uint8_t SPARE1; // No used. + uint8_t EPBBAY; // Endpoint Y buffer Base address. + uint8_t EPBCTY; // Endpoint Y buffer byte count. + uint8_t EPSIZXY; // Endpoint XY buffer size. +}; + +#if defined(__ICC430__) +#pragma location = 0x23C8 // Input ep1 configuration address. +__no_init struct usb_ep __data16 g_ep1_in; + +#pragma location = 0x1C80 // Input ep1 X-buffer address. +__no_init uint8_t __data16 g_ep1_in_xbuf[EP_MAX_PACKET_SIZE]; +#elif defined(__GNUC__) +extern struct usb_ep g_ep1_in; +extern uint8_t g_ep1_in_xbuf[EP_MAX_PACKET_SIZE]; +#endif + +static struct { + const uint8_t data_pin; + const uint8_t id; + uint8_t buttons; + bool ready; +} g_reports[HID_REPORTS_COUNT] = { + {GPIO_DATA1_PIN, HID_REPORT_ID_GAMEPAD1, 0, false}, + {GPIO_DATA2_PIN, HID_REPORT_ID_GAMEPAD2, 0, false} +}; + +static uint16_t g_poll_counter = 0; + +// Pulse width around ~10 usec. +static void ep1_latch_pulse(void) +{ + gpio_pins_set_high(GPIO_PORT_P6, GPIO_LATCH_PIN); + gpio_pins_set_low(GPIO_PORT_P6, GPIO_LATCH_PIN); +} + +// Pulse width around ~10 usec. +static void ep1_clk_pulse(void) +{ + gpio_pins_set_high(GPIO_PORT_P6, GPIO_CLK_PIN); + gpio_pins_set_low(GPIO_PORT_P6, GPIO_CLK_PIN); +} + +static void ep1_reports_clean(void) +{ + for (uint8_t index = 0; index < HID_REPORTS_COUNT; ++index) { + g_reports[index].buttons = 0; + g_reports[index].ready = false; + } +} + +static void ep1_reports_update(void) +{ + for (uint8_t index = 0; index < HID_REPORTS_COUNT; ++index) + g_reports[index].ready = true; +} + +static void ep1_gamepads_poll(void) +{ + ep1_reports_clean(); + + ep1_latch_pulse(); + + for (uint8_t pos = 0; pos < HID_REPORT_BITS_COUNT; ++pos) { + // TODO: Add some nops here? + + for (uint8_t index = 0; index < HID_REPORTS_COUNT; ++index) { + const uint8_t pin = g_reports[index].data_pin; + const enum gpio_pin_status st = gpio_pin_get(GPIO_PORT_P6, pin); + // Low state means that a button is pressed. + const bool v = (st == GPIO_INPUT_PIN_LOW); + g_reports[index].buttons |= (v << pos); + } + + ep1_clk_pulse(); + } + + ep1_reports_update(); +} + +static void ep1_report_send(uint8_t report_index) +{ + if (!g_reports[report_index].ready) + return; + + if ((g_ep1_in.EPBCTX & NAK) == 0) + return; + + g_ep1_in_xbuf[0] = g_reports[report_index].id; + g_ep1_in_xbuf[1] = g_reports[report_index].buttons; + g_ep1_in.EPBCTX = 2; + + g_reports[report_index].ready = false; +} + +void hid_ep1_init(void) +{ + enum { + USBSTABUFF_ADDRESS = 0x1C00, // Start of buffer space address. + USBIEPBBAX_1_ADDRESS = 0x1C80 // Input ep1 X-buffer address. + }; + + g_ep1_in.EPCNF = UBME | USBIIE; + g_ep1_in.EPBBAX = ((USBIEPBBAX_1_ADDRESS - USBSTABUFF_ADDRESS) >> 3) & 0x00FF; + g_ep1_in.EPBCTX = NAK; + g_ep1_in.EPSIZXY = EP_MAX_PACKET_SIZE; + + gpio_pins_set_as_out(GPIO_PORT_P6, GPIO_CLK_PIN | GPIO_LATCH_PIN); + gpio_pins_set_as_in(GPIO_PORT_P6, GPIO_DATA1_PIN | GPIO_DATA2_PIN); +} + +void hid_ep1_task(void) +{ + if (!hid_ep0_is_enumerated()) + return; + + for (uint8_t index = 0; index < HID_REPORTS_COUNT; ++index) + ep1_report_send(index); + + if (g_poll_counter <= POLLING_COUNTER) { + ++g_poll_counter; + } else { + g_poll_counter = 0; + ep1_gamepads_poll(); + } +} + +void hid_ep1_in_stall(void) +{ + g_ep1_in.EPCNF |= STALL; +} + +void hid_ep1_in_unstall(void) +{ + g_ep1_in.EPCNF &= ~(STALL | TOGGLE); +} + +bool hid_ep1_in_is_stalled(void) +{ + return (g_ep1_in.EPCNF & STALL); +} diff --git a/examples/baremetal/msp430f5529/nes-gamepads/hwdefs.h b/examples/baremetal/msp430f5529/nes-gamepads/hwdefs.h new file mode 100644 index 00000000..89dbcf5a --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/hwdefs.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef MSP430_HW_DEFS_H +#define MSP430_HW_DEFS_H + +#include +#include + +#if defined(__ICC430__) +# define _PPTOSTR_(x) #x +# define _PPARAM_(address) _PPTOSTR_(vector=address) +# define INTERRUPT(isr_name, vector) \ + _Pragma(_PPARAM_(vector)) __interrupt void isr_name(void) +#elif defined(__GNUC__) +# define INTERRUPT(isr_name, vector) \ + void __attribute__ ((interrupt(vector))) isr_name(void) +#else +# error "Unsupported toolchain" +#endif + +#include +#include + +#define HWREG8(x) \ + (*((volatile uint8_t *)((uint16_t)x))) + +#define HWREG16(x) \ + (*((volatile uint16_t *)((uint16_t)x))) + +#define HWREG32(x) \ + (*((volatile uint32_t *)((uint16_t)x))) + +#endif // MSP430_HW_DEFS_H diff --git a/examples/baremetal/msp430f5529/nes-gamepads/main.c b/examples/baremetal/msp430f5529/nes-gamepads/main.c new file mode 100644 index 00000000..97c499ee --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/main.c @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "hwdefs.h" +#include "pmm.h" +#include "ucs.h" +#include "usb.h" +#include "wdt_a.h" + +static void hw_clocks_init(uint32_t mclk_freq) +{ + ucs_init(UCS_FLLREF, UCS_REFOCLK_SELECT, UCS_CLOCK_DIVIDER_1); + ucs_init(UCS_ACLK, UCS_REFOCLK_SELECT, UCS_CLOCK_DIVIDER_1); + ucs_fll_settle_init(mclk_freq / 1000, mclk_freq / 32768); + + // Use REFO for FLL and ACLK. + UCSCTL3 = (UCSCTL3 & ~SELREF_7) | SELREF__REFOCLK; + UCSCTL4 = (UCSCTL4 & ~SELA_7) | SELA__REFOCLK; +} + +static void hw_init(void) +{ + __disable_interrupt(); + + wdt_a_stop(); + pmm_voltage_init(PMM_VOLTAGE_1_85V); + hw_clocks_init(8000000); + usb_init(); + + __enable_interrupt(); +} + +static void hw_loop_exec(void) +{ + for (;;) { + usb_task(); + } +} + +int main(void) +{ + hw_init(); + hw_loop_exec(); + return 0; +} diff --git a/examples/baremetal/msp430f5529/nes-gamepads/nes-gamepads.qbs b/examples/baremetal/msp430f5529/nes-gamepads/nes-gamepads.qbs new file mode 100644 index 00000000..37dfe8ca --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/nes-gamepads.qbs @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 { + condition: { + if (!qbs.architecture.contains("msp430")) + return false; + return qbs.toolchain.contains("iar") + || qbs.toolchain.contains("gcc") + } + name: "msp430f5529-nes-gamepads" + cpp.cLanguageVersion: "c99" + cpp.positionIndependentCode: false + + // + // IAR-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("iar") + + property path dlibIncludePath: cpp.toolchainInstallPath + "/../lib/dlib/dl430xssfn.h" + property path dlibRuntimePath: cpp.toolchainInstallPath + "/../lib/dlib/dl430xssfn.r43" + + cpp.entryPoint: "__program_start" + + cpp.defines: ["__MSP430F5529__"] + + cpp.driverFlags: [ + "-e", + "--core=430X", + "--data_model=small", + "--code_model=small", + "--dlib_config", dlibIncludePath + ] + + cpp.driverLinkerFlags: [ + "-D_STACK_SIZE=A0", + "-D_DATA16_HEAP_SIZE=A0", + "-D_DATA20_HEAP_SIZE=50" + ] + + cpp.staticLibraries: [ + // Explicitly link with the runtime dlib library (which contains + // all required startup code and other stuff). + dlibRuntimePath + ] + } + + Group { + condition: qbs.toolchain.contains("iar") + name: "IAR Linker Script" + prefix: cpp.toolchainInstallPath + "/../config/linker/" + fileTags: ["linkerscript"] + // Explicitly use the default linker scripts for current target. + files: ["lnk430f5529.xcl"] + } + + // + // GCC-specific properties and soucres. + // + + Properties { + condition: qbs.toolchain.contains("gcc") + property path supportFilesPath + // A path to the MSP430 support files, which are + // provided by the Texas Instruments separately: + // e.g. 'c:/msp430-gcc-support-files/include/' + cpp.includePaths: supportFilesPath + cpp.libraryPaths: supportFilesPath + cpp.driverFlags: ["-mmcu=msp430f5529"] + } + + Group { + condition: qbs.toolchain.contains("gcc") + name: "GCC Linker Script" + fileTags: ["linkerscript"] + files: ["gamepads.ld"] + } + + files: [ + "gpio.c", + "gpio.h", + "hid.h", + "hiddesc.c", + "hidep0.c", + "hidep1.c", + "hwdefs.h", + "main.c", + "pmm.c", + "pmm.h", + "ucs.c", + "ucs.h", + "usb.c", + "usb.h", + "wdt_a.c", + "wdt_a.h", + ] +} diff --git a/examples/baremetal/msp430f5529/nes-gamepads/pmm.c b/examples/baremetal/msp430f5529/nes-gamepads/pmm.c new file mode 100644 index 00000000..b588e7ad --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/pmm.c @@ -0,0 +1,237 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "hwdefs.h" +#include "pmm.h" + +#define PMMCTL0_H_ADDRESS (PMM_BASE + OFS_PMMCTL0_H) +#define PMMCTL0_L_ADDRESS (PMM_BASE + OFS_PMMCTL0_L) +#define PMMRIE_ADDRESS (PMM_BASE + OFS_PMMRIE) +#define PMMIFG_ADDRESS (PMM_BASE + OFS_PMMIFG) +#define SVSMHCTL_ADDRESS (PMM_BASE + OFS_SVSMHCTL) +#define SVSMLCTL_ADDRESS (PMM_BASE + OFS_SVSMLCTL) + +#define PMM_OTHER_BITSL \ + (SVSLRVL0 | SVSLRVL1 | SVSMLRRL0 | SVSMLRRL1 | SVSMLRRL2) + +#define PMM_OTHER_BITSH \ + (SVSHRVL0 | SVSHRVL1 | SVSMHRRL0 | SVSMHRRL1 | SVSMHRRL2) + +#define PMM_CLEANUP_FLAGS \ + (SVMHVLRIFG | SVMHIFG | SVSMHDLYIFG | SVMLVLRIFG | SVMLIFG | SVSMLDLYIFG) + +static void pmm_write_access_allow(bool allow) +{ + HWREG8(PMMCTL0_H_ADDRESS) = allow ? PMMPW_H : 0; +} + +static void pmm_interrupt_flags_clear(uint16_t flags) +{ + HWREG16(PMMIFG_ADDRESS) &= ~flags; +} + +static void pmm_interrupt_flags_wait(uint16_t flags) +{ + while ((HWREG16(PMMIFG_ADDRESS) & flags) == 0) { + ; + } +} + +static uint8_t pmm_level_mask(enum ppm_voltage voltage) +{ + switch (voltage) { + case PMM_VOLTAGE_1_35V: + return PMMCOREV_0; + case PMM_VOLTAGE_1_55V: + return PMMCOREV_1; + case PMM_VOLTAGE_1_75V: + return PMMCOREV_2; + case PMM_VOLTAGE_1_85V: + return PMMCOREV_3; + default: + break; + } + return PMMCOREV_0; +} + +static bool pmm_voltage_set_up(uint8_t level) +{ + pmm_write_access_allow(true); + + // Disable interrupts and backup all registers. + uint16_t pmmrie_backup = HWREG16(PMMRIE_ADDRESS); + HWREG16(PMMRIE_ADDRESS) &= ~(SVMHVLRPE | SVSHPE | SVMLVLRPE | SVSLPE + | SVMHVLRIE | SVMHIE | SVSMHDLYIE | SVMLVLRIE + | SVMLIE | SVSMLDLYIE); + uint16_t svsmhctl_backup = HWREG16(SVSMHCTL_ADDRESS); + uint16_t svsmlctl_backup = HWREG16(SVSMLCTL_ADDRESS); + + // Clear all interrupt flags. + pmm_interrupt_flags_clear(0xFFFF); + + // Set SVM high side to new level and check if voltage increase is possible. + HWREG16(SVSMHCTL_ADDRESS) = SVMHE | SVSHE | (SVSMHRRL0 * level); + pmm_interrupt_flags_wait(SVSMHDLYIFG); + pmm_interrupt_flags_clear(SVSMHDLYIFG); + + // Check if a voltage increase is possible. + if ((HWREG16(PMMIFG_ADDRESS) & SVMHIFG) == SVMHIFG) { + // Vcc is too low for a voltage increase, recover the previous settings. + HWREG16(PMMIFG_ADDRESS) &= ~SVSMHDLYIFG; + HWREG16(SVSMHCTL_ADDRESS) = svsmhctl_backup; + pmm_interrupt_flags_wait(SVSMHDLYIFG); + pmm_interrupt_flags_clear(PMM_CLEANUP_FLAGS); + // Restore interrupt enable register. + HWREG16(PMMRIE_ADDRESS) = pmmrie_backup; + pmm_write_access_allow(false); + return false; + } + + // Set SVS high side to new level. + HWREG16(SVSMHCTL_ADDRESS) |= (SVSHRVL0 * level); + pmm_interrupt_flags_wait(SVSMHDLYIFG); + pmm_interrupt_flags_clear(SVSMHDLYIFG); + + // Set new voltage level. + HWREG8(PMMCTL0_L_ADDRESS) = PMMCOREV0 * level; + + // Set SVM, SVS low side to new level. + HWREG16(SVSMLCTL_ADDRESS) = SVMLE | (SVSMLRRL0 * level) + | SVSLE | (SVSLRVL0 * level); + pmm_interrupt_flags_wait(SVSMLDLYIFG); + pmm_interrupt_flags_clear(SVSMLDLYIFG); + + // Restore low side settings and clear all other bits. + HWREG16(SVSMLCTL_ADDRESS) &= PMM_OTHER_BITSL; + // Clear low side level settings in backup register and keep all other bits. + svsmlctl_backup &= ~PMM_OTHER_BITSL; + // Restore low side SVS monitor settings. + HWREG16(SVSMLCTL_ADDRESS) |= svsmlctl_backup; + + // Restore high side settings and clear all other bits. + HWREG16(SVSMHCTL_ADDRESS) &= PMM_OTHER_BITSH; + // Clear high side level settings in backup register and keep all other bits. + svsmhctl_backup &= ~PMM_OTHER_BITSH; + // Restore high side SVS monitor settings. + HWREG16(SVSMHCTL_ADDRESS) |= svsmhctl_backup; + + pmm_interrupt_flags_wait(SVSMLDLYIFG | SVSMHDLYIFG); + pmm_interrupt_flags_clear(PMM_CLEANUP_FLAGS); + + // Restore interrupt enable register. + HWREG16(PMMRIE_ADDRESS) = pmmrie_backup; + pmm_write_access_allow(false); + + return true; +} + +static uint16_t pmm_voltage_set_down(uint8_t level) +{ + pmm_write_access_allow(true); + + // Disable interrupts and backup all registers. + uint16_t pmmrie_backup = HWREG16(PMMRIE_ADDRESS); + HWREG16(PMMRIE_ADDRESS) &= ~(SVMHVLRPE | SVSHPE | SVMLVLRPE | + SVSLPE | SVMHVLRIE | SVMHIE | + SVSMHDLYIE | SVMLVLRIE | SVMLIE | + SVSMLDLYIE + ); + uint16_t svsmhctl_backup = HWREG16(SVSMHCTL_ADDRESS); + uint16_t svsmlctl_backup = HWREG16(SVSMLCTL_ADDRESS); + + pmm_interrupt_flags_clear(SVMHIFG | SVSMHDLYIFG | SVMLIFG | SVSMLDLYIFG); + + // Set SVM, SVS high & low side to new settings in normal mode. + HWREG16(SVSMHCTL_ADDRESS) = SVMHE | (SVSMHRRL0 * level) | SVSHE | (SVSHRVL0 * level); + HWREG16(SVSMLCTL_ADDRESS) = SVMLE | (SVSMLRRL0 * level) | SVSLE | (SVSLRVL0 * level); + + pmm_interrupt_flags_wait(SVSMHDLYIFG | SVSMLDLYIFG); + pmm_interrupt_flags_clear(SVSMHDLYIFG | SVSMLDLYIFG); + + // Set new voltage level. + HWREG8(PMMCTL0_L_ADDRESS) = PMMCOREV0 * level; + + // Restore low side settings and clear all other bits. + HWREG16(SVSMLCTL_ADDRESS) &= PMM_OTHER_BITSL; + // Clear low side level settings in backup register and keep all other bits. + svsmlctl_backup &= ~PMM_OTHER_BITSL; + //Restore low side SVS monitor settings. + HWREG16(SVSMLCTL_ADDRESS) |= svsmlctl_backup; + + // Restore high side settings and clear all other bits. + HWREG16(SVSMHCTL_ADDRESS) &= PMM_OTHER_BITSH; + // Clear high side level settings in backup register and keep all other bits. + svsmhctl_backup &= ~PMM_OTHER_BITSH; + // Restore high side SVS monitor settings. + HWREG16(SVSMHCTL_ADDRESS) |= svsmhctl_backup; + + pmm_interrupt_flags_wait(SVSMLDLYIFG | SVSMHDLYIFG); + pmm_interrupt_flags_clear(PMM_CLEANUP_FLAGS); + + // Restore interrupt enable register. + HWREG16(PMMRIE_ADDRESS) = pmmrie_backup; + pmm_write_access_allow(false); + + return true; +} + +bool pmm_voltage_init(enum ppm_voltage voltage) +{ + const uint8_t exp_level = pmm_level_mask(voltage) & PMMCOREV_3; + uint8_t act_level = (HWREG16(PMM_BASE + OFS_PMMCTL0) & PMMCOREV_3); + bool result = true; + while ((exp_level != act_level) && result) { + if (exp_level > act_level) + result = pmm_voltage_set_up(++act_level); + else + result = pmm_voltage_set_down(--act_level); + } + return result; +} diff --git a/examples/baremetal/msp430f5529/nes-gamepads/pmm.h b/examples/baremetal/msp430f5529/nes-gamepads/pmm.h new file mode 100644 index 00000000..049fddc6 --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/pmm.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef MSP430_PMM_H +#define MSP430_PMM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum ppm_voltage { + PMM_VOLTAGE_1_35V, + PMM_VOLTAGE_1_55V, + PMM_VOLTAGE_1_75V, + PMM_VOLTAGE_1_85V +}; + +bool pmm_voltage_init(enum ppm_voltage voltage); + +#ifdef __cplusplus +} +#endif + +#endif // MSP430_PMM_H diff --git a/examples/baremetal/msp430f5529/nes-gamepads/ucs.c b/examples/baremetal/msp430f5529/nes-gamepads/ucs.c new file mode 100644 index 00000000..a4e17b8a --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/ucs.c @@ -0,0 +1,264 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "hwdefs.h" +#include "ucs.h" + +#if defined(__GNUC__) +#define __delay_cycles(x) \ + ({ \ + for (uint16_t j = 0; j < x; j++) { \ + __no_operation(); \ + } \ + }) +#endif + +static uint16_t ucs_source_mask(enum ucs_source source) +{ + switch (source) { + case UCS_XT1CLK_SELECT: + return SELM__XT1CLK; + case UCS_VLOCLK_SELECT: + return SELM__VLOCLK; + case UCS_REFOCLK_SELECT: + return SELM__REFOCLK; + case UCS_DCOCLK_SELECT: + return SELM__DCOCLK; + case UCS_DCOCLKDIV_SELECT: + return SELM__DCOCLKDIV; + case UCS_XT2CLK_SELECT: + return SELM__XT2CLK; + default: + break; + } + return SELM__XT1CLK; +} + +static uint16_t ucs_divider_mask(enum ucs_divider divider) +{ + switch (divider) { + case UCS_CLOCK_DIVIDER_1: + return DIVM__1; + case UCS_CLOCK_DIVIDER_2: + return DIVM__2; + case UCS_CLOCK_DIVIDER_4: + return DIVM__4; + case UCS_CLOCK_DIVIDER_8: + return DIVM__8; + case UCS_CLOCK_DIVIDER_12: + return DIVM__32; + case UCS_CLOCK_DIVIDER_16: + return DIVM__16; + case UCS_CLOCK_DIVIDER_32: + return DIVM__32; + default: + break; + } + return DIVM__1; +} + +static void ucs_fll_init(uint16_t fsystem, uint16_t ratio) +{ + const uint16_t sr_reg_backup = __get_SR_register() & SCG0; + + uint16_t mode = 0; + uint16_t d = ratio; + if (fsystem > 16000) { + d >>= 1; + mode = 1; + } else { + fsystem <<= 1; + } + + uint16_t dco_div_bits = FLLD__2; + while (d > 512) { + dco_div_bits = dco_div_bits + FLLD0; + d >>= 1; + } + + // Disable FLL. + __bis_SR_register(SCG0); + // Set DCO to lowest tap. + HWREG8(UCS_BASE + OFS_UCSCTL0_H) = 0; + // Reset FN bits. + HWREG16(UCS_BASE + OFS_UCSCTL2) &= ~(0x03FF); + HWREG16(UCS_BASE + OFS_UCSCTL2) = dco_div_bits | (d - 1); + + if (fsystem <= 630) { // fsystem < 0.63MHz + HWREG8(UCS_BASE + OFS_UCSCTL1) = DCORSEL_0; + } else if (fsystem < 1250) { // 0.63MHz < fsystem < 1.25MHz + HWREG8(UCS_BASE + OFS_UCSCTL1) = DCORSEL_1; + } else if (fsystem < 2500) { // 1.25MHz < fsystem < 2.5MHz + HWREG8(UCS_BASE + OFS_UCSCTL1) = DCORSEL_2; + } else if (fsystem < 5000) { // 2.5MHz < fsystem < 5MHz + HWREG8(UCS_BASE + OFS_UCSCTL1) = DCORSEL_3; + } else if (fsystem < 10000) { // 5MHz < fsystem < 10MHz + HWREG8(UCS_BASE + OFS_UCSCTL1) = DCORSEL_4; + } else if (fsystem < 20000) { // 10MHz < fsystem < 20MHz + HWREG8(UCS_BASE + OFS_UCSCTL1) = DCORSEL_5; + } else if (fsystem < 40000) { // 20MHz < fsystem < 40MHz + HWREG8(UCS_BASE + OFS_UCSCTL1) = DCORSEL_6; + } else { + HWREG8(UCS_BASE + OFS_UCSCTL1) = DCORSEL_7; + } + + // Re-enable FLL. + __bic_SR_register(SCG0); + + while (HWREG8(UCS_BASE + OFS_UCSCTL7_L) & DCOFFG) { + // Clear OSC fault flags. + HWREG8(UCS_BASE + OFS_UCSCTL7_L) &= ~(DCOFFG); + // Clear OFIFG fault flag. + HWREG8(SFR_BASE + OFS_SFRIFG1) &= ~OFIFG; + } + + // Restore previous SCG0. + __bis_SR_register(sr_reg_backup); + + if (mode == 1) { + // Select DCOCLK because fsystem > 16000. + HWREG16(UCS_BASE + OFS_UCSCTL4) &= ~(SELM_7 + SELS_7); + HWREG16(UCS_BASE + OFS_UCSCTL4) |= SELM__DCOCLK + SELS__DCOCLK; + } else { + // Select DCODIVCLK. + HWREG16(UCS_BASE + OFS_UCSCTL4) &= ~(SELM_7 + SELS_7); + HWREG16(UCS_BASE + OFS_UCSCTL4) |= SELM__DCOCLKDIV + SELS__DCOCLKDIV; + } +} + +void ucs_init(enum ucs_signal signal, + enum ucs_source source, + enum ucs_divider divider) +{ + uint16_t source_msk = ucs_source_mask(source); + uint16_t divider_msk = ucs_divider_mask(divider); + + switch (signal) { + case UCS_ACLK: + HWREG16(UCS_BASE + OFS_UCSCTL4) &= ~SELA_7; + source_msk |= source_msk << 8; + HWREG16(UCS_BASE + OFS_UCSCTL4) |= source_msk; + HWREG16(UCS_BASE + OFS_UCSCTL5) &= ~DIVA_7; + divider_msk = divider_msk << 8; + HWREG16(UCS_BASE + OFS_UCSCTL5) |= divider_msk; + break; + case UCS_SMCLK: + HWREG16(UCS_BASE + OFS_UCSCTL4) &= ~SELS_7; + source_msk = source_msk << 4; + HWREG16(UCS_BASE + OFS_UCSCTL4) |= source_msk; + HWREG16(UCS_BASE + OFS_UCSCTL5) &= ~DIVS_7; + divider_msk = divider_msk << 4; + HWREG16(UCS_BASE + OFS_UCSCTL5) |= divider_msk; + break; + case UCS_MCLK: + HWREG16(UCS_BASE + OFS_UCSCTL4) &= ~SELM_7; + HWREG16(UCS_BASE + OFS_UCSCTL4) |= source_msk; + HWREG16(UCS_BASE + OFS_UCSCTL5) &= ~DIVM_7; + HWREG16(UCS_BASE + OFS_UCSCTL5) |= divider_msk; + break; + case UCS_FLLREF: + HWREG8(UCS_BASE + OFS_UCSCTL3) &= ~SELREF_7; + source_msk = source_msk << 4; + HWREG8(UCS_BASE + OFS_UCSCTL3) |= source_msk; + HWREG8(UCS_BASE + OFS_UCSCTL3) &= ~FLLREFDIV_7; + + switch (divider) { + case UCS_CLOCK_DIVIDER_12: + HWREG8(UCS_BASE + OFS_UCSCTL3) |= FLLREFDIV__12; + break; + case UCS_CLOCK_DIVIDER_16: + HWREG8(UCS_BASE + OFS_UCSCTL3) |= FLLREFDIV__16; + break; + default: + HWREG8(UCS_BASE + OFS_UCSCTL3) |= divider_msk; + break; + } + break; + default: + break; + } +} + +bool ucs_xt2_blocking_turn_on(uint16_t drive, uint16_t timeout) +{ + // Check if drive value is expected one. + if ((HWREG16(UCS_BASE + OFS_UCSCTL6) & XT2DRIVE_3) != drive) { + // Clear XT2 drive field. + HWREG16(UCS_BASE + OFS_UCSCTL6) &= ~XT2DRIVE_3; + HWREG16(UCS_BASE + OFS_UCSCTL6) |= drive; + } + + HWREG16(UCS_BASE + OFS_UCSCTL6) &= ~XT2BYPASS; + // Switch on XT2 oscillator. + HWREG16(UCS_BASE + OFS_UCSCTL6) &= ~XT2OFF; + + do { + // Clear OSC fault Flags. + HWREG8(UCS_BASE + OFS_UCSCTL7) &= ~XT2OFFG; + // Clear OFIFG fault flag. + HWREG8(SFR_BASE + OFS_SFRIFG1) &= ~OFIFG; + } while ((HWREG8(UCS_BASE + OFS_UCSCTL7) & XT2OFFG) && --timeout); + + return timeout > 0; +} + +void ucs_xt2_turn_off(void) +{ + HWREG16(UCS_BASE + OFS_UCSCTL6) |= XT2OFF; +} + +void ucs_fll_settle_init(uint16_t fsystem, uint16_t ratio) +{ + volatile uint16_t x = ratio * 32; + ucs_fll_init(fsystem, ratio); + while (x--) { + __delay_cycles(30); + } +} diff --git a/examples/baremetal/msp430f5529/nes-gamepads/ucs.h b/examples/baremetal/msp430f5529/nes-gamepads/ucs.h new file mode 100644 index 00000000..1741c37f --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/ucs.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef MSP430_UCS_H +#define MSP430_UCS_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum ucs_signal { + UCS_ACLK = 0x01, + UCS_MCLK = 0x02, + UCS_SMCLK = 0x04, + UCS_FLLREF = 0x08 +}; + +enum ucs_source { + UCS_XT1CLK_SELECT, + UCS_VLOCLK_SELECT, + UCS_REFOCLK_SELECT, + UCS_DCOCLK_SELECT, + UCS_DCOCLKDIV_SELECT, + UCS_XT2CLK_SELECT +}; + +enum ucs_divider { + UCS_CLOCK_DIVIDER_1, + UCS_CLOCK_DIVIDER_2, + UCS_CLOCK_DIVIDER_4, + UCS_CLOCK_DIVIDER_8, + UCS_CLOCK_DIVIDER_12, + UCS_CLOCK_DIVIDER_16, + UCS_CLOCK_DIVIDER_32 +}; + +enum ucs_fault_flag { + UCS_XT2OFFG, + UCS_XT1HFOFFG, + UCS_XT1LFOFFG, + UCS_DCOFFG +}; + +void ucs_clocks_init(void); + +void ucs_init(enum ucs_signal signal, + enum ucs_source source, + enum ucs_divider divider); + +bool ucs_xt2_blocking_turn_on(uint16_t drive, uint16_t timeout); + +void ucs_xt2_turn_off(void); +void ucs_fll_settle_init(uint16_t fsystem, uint16_t ratio); + +#ifdef __cplusplus +} +#endif + +#endif // MSP430_UCS_H diff --git a/examples/baremetal/msp430f5529/nes-gamepads/usb.c b/examples/baremetal/msp430f5529/nes-gamepads/usb.c new file mode 100644 index 00000000..2a648580 --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/usb.c @@ -0,0 +1,317 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "gpio.h" +#include "hid.h" +#include "hwdefs.h" +#include "ucs.h" +#include "usb.h" + +static uint16_t usb_khz_freq_get(void) +{ + uint16_t freq = 0; + const uint8_t selm_curr = (UCSCTL4_L & SELM_7); + if (selm_curr <= 4) { + uint16_t fll_ref_freq = 33; // It is 32.768 kHz. + if ((UCSCTL3_L & SELREF_7) >= 0x50) + fll_ref_freq = (4.0) * 1000; + + uint16_t flln_curr = (UCSCTL2 & 0x03FF) + 1; + if (selm_curr == SELM_3) { + uint16_t flld_curr = (UCSCTL2 & FLLD_7); + flld_curr >>= 12; + flln_curr <<= flld_curr; + } + + const uint8_t fll_ref_div = (UCSCTL3_L & FLLREFDIV_7); + if (fll_ref_div == 0) + freq = flln_curr * (fll_ref_freq / 1); + else if (fll_ref_div == 1) + freq = flln_curr * (fll_ref_freq / 2); + else if (fll_ref_div == 2) + freq = flln_curr * (fll_ref_freq / 4); + else if (fll_ref_div == 3) + freq = flln_curr * (fll_ref_freq / 8); + else if (fll_ref_div == 4) + freq = flln_curr * (fll_ref_freq / 12); + else if (fll_ref_div == 5) + freq = flln_curr * (fll_ref_freq / 16); + } else { + freq = (4.0) * 1000; + } + return freq >> (UCSCTL5_L & DIVM_7); +} + +static uint16_t usb_delay_250us_get(void) +{ + const uint16_t mclk_freq = usb_khz_freq_get(); + const uint16_t delay_250us = ((mclk_freq >> 6) | (mclk_freq >> 7) + | (mclk_freq >> 9)); + return delay_250us; +} + +static void usb_cfg_access_allow(bool allow) +{ + enum { ALLOW_KEY = 0x9628, DENIED_KEY = 0x9600 }; + USBKEYPID = allow ? ALLOW_KEY : DENIED_KEY; +} + +static bool usb_pll_enable(void) +{ + if (!(USBPWRCTL & USBBGVBV)) + return false; + + if ((USBCNF & USB_EN) && (USBPLLCTL & UPLLEN)) + return true; + + gpio_pins_set_as_pf_out(GPIO_PORT_P5, GPIO_PIN2); + gpio_pins_set_as_pf_out(GPIO_PORT_P5, GPIO_PIN3); + + usb_cfg_access_allow(true); + + if (!ucs_xt2_blocking_turn_on(XT2DRIVE_0, 50000)) + return false; + + USBPLLDIVB = USBPLL_SETCLK_4_0; + USBPLLCTL = UPFDEN | UPLLEN; + + const uint16_t delay_250us = usb_delay_250us_get(); + uint8_t j = 0; + do { + USBPLLIR = 0; + for (uint8_t k = 0; k < 2; ++k) { + for (uint16_t i = 0; i < delay_250us; ++i) { + __no_operation(); + } + } + + if (j++ > 10) { + usb_cfg_access_allow(false); + return false; + } + } while (USBPLLIR != 0); + + USBCNF |= USB_EN; + usb_cfg_access_allow(false); + return true; +} + +static void usb_reset(void) +{ + usb_cfg_access_allow(true); + + hid_ep0_enumerated_set(false); + + USBCTL = 0; + USBFUNADR = 0; + USBOEPIE = 0; + USBIEPIE = 0; + + hid_ep0_init(); + hid_ep1_init(); + + USBCTL = FEN; + USBIFG = 0; + USBIE = SETUPIE | RSTRIE | SUSRIE; + usb_cfg_access_allow(false); +} + +static void usb_connect(void) +{ + usb_cfg_access_allow(true); + USBCNF |= PUR_EN; + USBPWRCTL |= VBOFFIE; + usb_cfg_access_allow(false); +} + +static void usb_pwr_vbus_wait(void) +{ + const uint16_t delay_250us = usb_delay_250us_get(); + for (uint8_t j = 0; j < 4; ++j) { + for (uint16_t i = 0; i < delay_250us; ++i) { + __no_operation(); + } + } +} + +static void usb_pwr_vbus_on_handler(void) +{ + usb_pwr_vbus_wait(); + + if (USBPWRCTL & USBBGVBV) { + usb_cfg_access_allow(true); + USBPWRCTL |= VBOFFIE; + USBPWRCTL &= ~ (VBONIFG + VBOFFIFG); + usb_cfg_access_allow(false); + } +} + +static void usb_pwr_vbus_off_handler(void) +{ + usb_pwr_vbus_wait(); + + if (!(USBPWRCTL & USBBGVBV)) { + usb_cfg_access_allow(true); + USBCNF = 0; + USBPLLCTL &= ~UPLLEN; + USBPWRCTL &= ~(VBOFFIE + VBOFFIFG + SLDOEN); + usb_cfg_access_allow(false); + } +} + +void usb_suspend(void) +{ + usb_cfg_access_allow(true); + USBCTL |= FRSTE; + USBIFG &= ~SUSRIFG; + USBPLLCTL &= ~UPLLEN; + ucs_xt2_turn_off(); + USBIE = RESRIE; + usb_cfg_access_allow(false); +} + +void usb_resume(void) +{ + usb_pll_enable(); + USBIFG &= ~(RESRIFG | SUSRIFG); + USBIE = SETUPIE | RSTRIE | SUSRIE; +} + +void usb_init(void) +{ + const uint16_t gie_backup = (__get_SR_register() & GIE); + + usb_cfg_access_allow(true); + USBPHYCTL = PUSEL; + USBPWRCTL = VUSBEN | SLDOAON; + + const uint16_t delay_250us = usb_delay_250us_get(); + for (uint8_t j = 0; j < 20; ++j) { + for (uint16_t i = 0; i < delay_250us; ++i) { + __no_operation(); + } + } + + USBPWRCTL |= VBONIE; + usb_cfg_access_allow(false); + + __bis_SR_register(gie_backup); + + if (USBPWRCTL & USBBGVBV) { + if (usb_pll_enable()) { + usb_reset(); + usb_connect(); + } + } +} + +void usb_task(void) +{ + hid_ep1_task(); +} + +INTERRUPT(usb_ubm_isr, USB_UBM_VECTOR) +{ + bool wake_up = false; + + if (USBIFG & SETUPIFG) { + hid_ep0_setup_handler(); + USBIFG &= ~SETUPIFG; + } + + switch (__even_in_range(USBVECINT & 0x3F, USBVECINT_OUTPUT_ENDPOINT7)) { + case USBVECINT_PWR_DROP: + __no_operation(); + break; + case USBVECINT_PLL_RANGE: + wake_up = true; + break; + case USBVECINT_PWR_VBUSOn: + usb_pwr_vbus_on_handler(); + if (usb_pll_enable()) { + usb_reset(); + usb_connect(); + } + wake_up = true; + break; + case USBVECINT_PWR_VBUSOff: + usb_pwr_vbus_off_handler(); + ucs_xt2_turn_off(); + wake_up = true; + break; + case USBVECINT_INPUT_ENDPOINT0: + hid_ep0_in_handler(); + break; + case USBVECINT_RSTR: + usb_reset(); + wake_up = true; + break; + case USBVECINT_SUSR: + usb_suspend(); + wake_up = true; + break; + case USBVECINT_RESR: + usb_resume(); + wake_up = true; + break; + case USBVECINT_SETUP_PACKET_RECEIVED: + hid_ep0_in_nak(); + hid_ep0_out_nak(); + hid_ep0_setup_handler(); + break; + default: + break; + } + + if (wake_up) { + __bic_SR_register_on_exit(LPM3_bits); + __no_operation(); + } +} diff --git a/examples/baremetal/msp430f5529/nes-gamepads/usb.h b/examples/baremetal/msp430f5529/nes-gamepads/usb.h new file mode 100644 index 00000000..503f3cf0 --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/usb.h @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef MSP430_USB_H +#define MSP430_USB_H + +#ifdef __cplusplus +extern "C" { +#endif + +enum usb_setup_bmreq_bits { + SETUP_DIR = 0x80, + SETUP_TYPE = 0x60, + SETUP_RECIPIENT = 0x1F +}; + +// Setup request direction. +enum usb_setup_req_direction_bits { + SETUP_OUTPUT = 0, // From host to device direction. + SETUP_INPUT = 0x80 // From device to host direction. +}; + +// Setup request type. +enum usb_setup_req_type_bits { + SETUP_STANDARD = 0, // Standard request. + SETUP_CLASS = 0x20, // Class request. + SETUP_VENDOR = 0x40 // Vendor request. +}; + +// Setup request recipient. +enum usb_setup_req_recipient_bits { + SETUP_DEVICE = 0, // Device recipient. + SETUP_IFACE = 0x01, // Interface recipient. + SETUP_EP = 0x02, // End point recipient. + SETUP_OTHER = 0x03 // Other recipient. +}; + +// Setup request code. +enum usb_setup_req_code { + SETUP_GET_STATUS = 0x00, // Get status code. + SETUP_CLEAR_FEATURE = 0x01, // Clear feature code. + SETUP_RESERVED1 = 0x02, // Reserved code. + SETUP_SET_FEATURE = 0x03, // Set feature code. + SETUP_RESERVED2 = 0x04, // Reserved code. + SETUP_SET_ADDRESS = 0x05, // Set address code. + SETUP_GET_DESCRIPTOR = 0x06, // Get descriptor code. + SETUP_SET_DESCRIPTOR = 0x07, // Set descriptor code. + SETUP_GET_CONFIGURATION = 0x08, // Get configuration code. + SETUP_SET_CONFIGURATION = 0x09, // Set configuration code. + SETUP_GET_INTERFACE = 0x0A, // Get interface code. + SETUP_SET_INTERFACE = 0x0B, // Set interface code. + SETUP_SYNC_FRAME = 0x0C, // Sync frame code. + SETUP_ANCHOR_LOAD = 0xA0 // Anchor load code. +}; + +// Standard status responses. +enum usb_setup_status_code { + STATUS_SELF_POWERED = 0x01, + STATUS_REMOTE_WAKEUP = 0x02 +}; + +// Standard feature selectors. +enum usb_setup_feature_selector { + FEATURE_STALL = 0x00, + FEATURE_REMOTE_WAKEUP = 0x01, + FEATURE_TEST_MODE = 0x02 +}; + +// Get descriptor codes. +enum usb_setup_get_descriptor_code { + DESC_DEVICE = 0x01, // Device descriptor. + DESC_CONF = 0x02, // Configuration descriptor. + DESC_STRING = 0x03, // String descriptor. + DESC_INTERFACE = 0x04, // Interface descriptor. + DESC_ENDPOINT = 0x05, // End point descriptor. + DESC_DEVICE_QUAL = 0x06, // Device qualifier descriptor. + DESC_OTHER_SPEED_CONF = 0x07, // Other configuration descriptor. + DESC_INTERFACE_POWER = 0x08, // Interface power descriptor. + DESC_OTG = 0x09, // OTG descriptor. + DESC_DEBUG = 0x0A, // Debug descriptor. + DESC_INTERFACE_ASSOC = 0x0B, // Interface association descriptor. + DESC_HID = 0x21, // Get HID descriptor. + DESC_REPORT = 0x22 // Get report descriptor. +}; + + +enum usb_ep_size { + EP0_MAX_PACKET_SIZE = 8, + EP_MAX_PACKET_SIZE = 64, + EP_MAX_FIFO_SIZE = 256, + EP_NO_MORE_DATA = 0xFFFF +}; + +void usb_init(void); +void usb_task(void); + +#ifdef __cplusplus +} +#endif + +#endif // MSP430_USB_H diff --git a/examples/baremetal/msp430f5529/nes-gamepads/wdt_a.c b/examples/baremetal/msp430f5529/nes-gamepads/wdt_a.c new file mode 100644 index 00000000..5adf3c36 --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/wdt_a.c @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "hwdefs.h" +#include "wdt_a.h" + +#define WDT_CONTROL_ADDRESS (WDT_A_BASE + OFS_WDTCTL) + +void wdt_a_stop(void) +{ + const uint8_t st = (HWREG16(WDT_CONTROL_ADDRESS) & 0x00FF) | WDTHOLD; + HWREG16(WDT_CONTROL_ADDRESS) = WDTPW + st; +} diff --git a/examples/baremetal/msp430f5529/nes-gamepads/wdt_a.h b/examples/baremetal/msp430f5529/nes-gamepads/wdt_a.h new file mode 100644 index 00000000..3a5e5b40 --- /dev/null +++ b/examples/baremetal/msp430f5529/nes-gamepads/wdt_a.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef MSP430_WDT_A_H +#define MSP430_WDT_A_H + +#ifdef __cplusplus +extern "C" { +#endif + +void wdt_a_stop(void); + +#ifdef __cplusplus +} +#endif + +#endif // MSP430_WDT_A_H diff --git a/examples/baremetal/msp430f5529/redblink/README.md b/examples/baremetal/msp430f5529/redblink/README.md new file mode 100644 index 00000000..2f63b234 --- /dev/null +++ b/examples/baremetal/msp430f5529/redblink/README.md @@ -0,0 +1,9 @@ +This example demonstrates how to build a bare-metal application using +different MSP430 toolchains. It is designed for the MSP-EXP430F5529LP +target board (based on msp430f5529 chip) and simply flashes the red +LED on the board. + +The following toolchains are supported: + + * IAR Embedded Workbench + * GCC diff --git a/examples/baremetal/msp430f5529/redblink/gpio.c b/examples/baremetal/msp430f5529/redblink/gpio.c new file mode 100644 index 00000000..3e7f181a --- /dev/null +++ b/examples/baremetal/msp430f5529/redblink/gpio.c @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "gpio.h" + +#if defined(__ICC430__) +#include +#elif defined(__GNUC__) +#include +#else +#error "Unsupported toolchain" +#endif + +// LED pin number. +#define GPIO_RED_LED_PIN_POS (0u) +// LED port direction. +#define GPIO_RED_LED_PORT_DIR (P1DIR) +// LED output port. +#define GPIO_RED_LED_PORT_OUT (P1OUT) + +void gpio_init_red_led(void) +{ + GPIO_RED_LED_PORT_DIR |= (1u << GPIO_RED_LED_PIN_POS); +} + +void gpio_toggle_red_led(void) +{ + GPIO_RED_LED_PORT_OUT ^= (1u << GPIO_RED_LED_PIN_POS); +} diff --git a/examples/baremetal/msp430f5529/redblink/gpio.h b/examples/baremetal/msp430f5529/redblink/gpio.h new file mode 100644 index 00000000..246eab86 --- /dev/null +++ b/examples/baremetal/msp430f5529/redblink/gpio.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef GPIO_H +#define GPIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +void gpio_init_red_led(void); +void gpio_toggle_red_led(void); + +#ifdef __cplusplus +} +#endif + +#endif // GPIO_H diff --git a/examples/baremetal/msp430f5529/redblink/main.c b/examples/baremetal/msp430f5529/redblink/main.c new file mode 100644 index 00000000..f1ad54ad --- /dev/null +++ b/examples/baremetal/msp430f5529/redblink/main.c @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "gpio.h" +#include "system.h" + +#include + +static void some_delay(uint32_t counts) +{ + for (uint32_t index = 0u; index < counts; ++index) + __asm("nop"); +} + +int main(void) +{ + system_init(); + + gpio_init_red_led(); + + while (1) { + gpio_toggle_red_led(); + some_delay(10000u); + } +} diff --git a/examples/baremetal/msp430f5529/redblink/redblink.qbs b/examples/baremetal/msp430f5529/redblink/redblink.qbs new file mode 100644 index 00000000..0f056e10 --- /dev/null +++ b/examples/baremetal/msp430f5529/redblink/redblink.qbs @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 { + condition: { + if (!qbs.architecture.contains("msp430")) + return false; + return qbs.toolchain.contains("iar") + || qbs.toolchain.contains("gcc") + } + name: "msp430f5529-redblink" + cpp.cLanguageVersion: "c99" + cpp.positionIndependentCode: false + + // + // IAR-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("iar") + cpp.driverFlags: ["--core=430X"] + cpp.entryPoint: "__program_start" + cpp.driverLinkerFlags: [ + "-D_STACK_SIZE=A0", + "-D_DATA16_HEAP_SIZE=A0", + "-D_DATA20_HEAP_SIZE=50", + ] + cpp.staticLibraries: [ + // Explicitly link with the runtime dlib library (which contains + // all required startup code and other stuff). + cpp.toolchainInstallPath + "/../lib/dlib/dl430xlsfn.r43" + ] + } + + Group { + condition: qbs.toolchain.contains("iar") + name: "IAR" + prefix: "iar/" + Group { + name: "Linker Script" + prefix: cpp.toolchainInstallPath + "/../config/linker/" + fileTags: ["linkerscript"] + // Explicitly use the default linker scripts for current target. + files: ["lnk430f5529.xcl", "multiplier32.xcl"] + } + } + + // + // GCC-specific properties and soucres. + // + + Properties { + condition: qbs.toolchain.contains("gcc") + property path supportFilesPath + // A path to the MSP430 support files, which are + // provided by the Texas Instruments separately: + // e.g. 'c:/msp430-gcc-support-files/include/' + cpp.includePaths: supportFilesPath + cpp.libraryPaths: supportFilesPath + cpp.driverFlags: ["-mmcu=msp430f5529"] + } + + // + // Common code. + // + + Group { + name: "Gpio" + files: ["gpio.c", "gpio.h"] + } + + Group { + name: "System" + files: ["system.c", "system.h"] + } + + files: [ + "main.c", + ] +} diff --git a/examples/baremetal/msp430f5529/redblink/system.c b/examples/baremetal/msp430f5529/redblink/system.c new file mode 100644 index 00000000..0e0e6901 --- /dev/null +++ b/examples/baremetal/msp430f5529/redblink/system.c @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "system.h" + +#if defined(__ICC430__) +#include +#elif defined(__GNUC__) +#include +#else +#error "Unsupported toolchain" +#endif + +void system_init(void) +{ + // Stop watchdog timer. + WDTCTL = WDTPW + WDTHOLD; +} diff --git a/examples/baremetal/msp430f5529/redblink/system.h b/examples/baremetal/msp430f5529/redblink/system.h new file mode 100644 index 00000000..468ef108 --- /dev/null +++ b/examples/baremetal/msp430f5529/redblink/system.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef SYSTEM_H +#define SYSTEM_H + +#ifdef __cplusplus +extern "C" { +#endif + +void system_init(void); + +#ifdef __cplusplus +} +#endif + +#endif // SYSTEM_H diff --git a/examples/baremetal/pca10040/greenblink/README.md b/examples/baremetal/pca10040/greenblink/README.md new file mode 100644 index 00000000..8b08365f --- /dev/null +++ b/examples/baremetal/pca10040/greenblink/README.md @@ -0,0 +1,9 @@ +This example demonstrates how to build a bare-metal application using +different ARM toolchains. It is designed for the Nordic pca10040 +evaluation kit (based on nRF52832 MCU) and simply flashes the green +LED on the board. + +The following toolchains are supported: + + * GNU Arm Embedded Toolchain + * KEIL Microcontroller Development Kit diff --git a/examples/baremetal/pca10040/greenblink/gcc/flash.ld b/examples/baremetal/pca10040/greenblink/gcc/flash.ld new file mode 100644 index 00000000..b998618a --- /dev/null +++ b/examples/baremetal/pca10040/greenblink/gcc/flash.ld @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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$ +** +****************************************************************************/ + +ENTRY(reset_handler) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 0x80000 + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x10000 + CODE_RAM (rwx) : ORIGIN = 0x800000, LENGTH = 0x10000 +} + +SECTIONS { + .text : { + KEEP(*(.isr_vector)) + *(.text*) + KEEP(*(.init)) + KEEP(*(.fini)) + /* .ctors */ + *crtbegin.o(.ctors) + *crtbegin?.o(.ctors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) + *(SORT(.ctors.*)) + *(.ctors) + /* .dtors */ + *crtbegin.o(.dtors) + *crtbegin?.o(.dtors) + *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) + *(SORT(.dtors.*)) + *(.dtors) + *(.rodata*) + *(.eh_frame*) + . = ALIGN(4); + } > FLASH + + .ARM.extab : { + *(.ARM.extab* .gnu.linkonce.armextab.*) + . = ALIGN(4); + } > FLASH + + _start_of_exidx_section = .; + .ARM.exidx : { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + . = ALIGN(4); + } > FLASH + _end_of_exidx_section = .; + + _end_of_code_section = .; + + .data : AT (_end_of_code_section) { + _start_of_data_section = .; + *(vtable) + *(.data*) + . = ALIGN(4); + /* preinit data */ + PROVIDE_HIDDEN(__preinit_array_start = .); + *(.preinit_array) + PROVIDE_HIDDEN(__preinit_array_end = .); + . = ALIGN(4); + /* init data */ + PROVIDE_HIDDEN(__init_array_start = .); + *(SORT(.init_array.*)) + *(.init_array) + PROVIDE_HIDDEN(__init_array_end = .); + . = ALIGN(4); + /* finit data */ + PROVIDE_HIDDEN(__fini_array_start = .); + *(SORT(.fini_array.*)) + *(.fini_array) + PROVIDE_HIDDEN(__fini_array_end = .); + *(.jcr) + . = ALIGN(4); + /* All data end */ + _end_of_data_section = .; + } > RAM + + .bss : { + . = ALIGN(4); + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + __bss_end__ = .; + } > RAM + + .heap (COPY): { + __end__ = .; + end = __end__; + *(.heap*) + _heap_limit = .; + } > RAM + + .stack_dummy (COPY): { + *(.stack*) + } > RAM + + /* Set stack top to end of RAM, and stack limit move down by size of stack_dummy section. */ + _end_of_stack = ORIGIN(RAM) + LENGTH(RAM); + _stack_limit = _end_of_stack - SIZEOF(.stack_dummy); + PROVIDE(__stack = _end_of_stack); + + /* Check if data + heap + stack exceeds RAM limit */ + ASSERT(_stack_limit >= _heap_limit, "region RAM overflowed with stack") +} diff --git a/examples/baremetal/pca10040/greenblink/gcc/startup.s b/examples/baremetal/pca10040/greenblink/gcc/startup.s new file mode 100644 index 00000000..d1fe7e9d --- /dev/null +++ b/examples/baremetal/pca10040/greenblink/gcc/startup.s @@ -0,0 +1,253 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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$ +** +****************************************************************************/ + +.syntax unified +.arch armv7e-m +.thumb + +// Define the stack. +.section .stack +.align 3 +.equ _size_of_stack, 8192 +.globl _end_of_stack +.globl _stack_limit +_stack_limit: +.space _size_of_stack +.size _stack_limit, . - _stack_limit +_end_of_stack: +.size _end_of_stack, . - _end_of_stack + +// Define the heap. +.section .heap +.align 3 +.equ _size_of_heap, 8192 +.globl _end_of_heap +.globl _heap_limit +_end_of_heap: +.space _size_of_heap +.size _end_of_heap, . - _end_of_heap +_heap_limit: +.size _heap_limit, . - _heap_limit + +// Define the empty vectors table. +.section .isr_vector, "ax" +.align 2 +.globl _vectors_table +_vectors_table: + // Generic interrupts offset. + .word _end_of_stack // Initial stack pointer value. + .word reset_handler // Reset. + .word 0 // NMI. + .word 0 // Hard fault. + .word 0 // Memory management fault. + .word 0 // Bus fault. + .word 0 // Usage fault. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // SVC. + .word 0 // Debug monitor. + .word 0 // Reserved. + .word 0 // PendSV. + .word 0 // SysTick. + + // External interrupts offset. + .word 0 // POWER CLOCK. + .word 0 // RADIO. + .word 0 // UARTE0/UART0 + .word 0 // SPIM0/SPIS0/TWIM0/TWIS0/SPI0/TWI0. + .word 0 // SPIM1/SPIS1/TWIM1/TWIS1/SPI1/TWI1. + .word 0 // NFCT. + .word 0 // GPIOTE. + .word 0 // SAADC. + .word 0 // TIMER0. + .word 0 // TIMER1. + .word 0 // TIMER2. + .word 0 // RTC0. + .word 0 // TEMP. + .word 0 // RNG. + .word 0 // ECB. + .word 0 // CCM_AAR. + .word 0 // WDT. + .word 0 // RTC1. + .word 0 // QDEC. + .word 0 // COMP_LPCOMP. + .word 0 // SWI0_EGU0. + .word 0 // SWI1_EGU1. + .word 0 // SWI2_EGU2. + .word 0 // SWI3_EGU3. + .word 0 // SWI4_EGU4. + .word 0 // SWI5_EGU5. + .word 0 // TIMER3. + .word 0 // TIMER4. + .word 0 // PWM0. + .word 0 // PDM. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // MWU. + .word 0 // PWM1. + .word 0 // PWM2. + .word 0 // SPIM2/SPIS2/SPI2. + .word 0 // RTC2. + .word 0 // I2S. + .word 0 // FPU. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. +.size _vectors_table, . - _vectors_table + +// Define the 'reset_handler' function, which is an entry point. +.text +.thumb +.thumb_func +.align 1 +.globl reset_handler +.type reset_handler, %function +reset_handler: + // Loop to copy data from read only memory to RAM. + ldr r1, =_end_of_code_section + ldr r2, =_start_of_data_section + ldr r3, =__bss_start__ + subs r3, r3, r2 + ble _copy_data_init_done +_copy_data_init: + subs r3, r3, #4 + ldr r0, [r1,r3] + str r0, [r2,r3] + bgt _copy_data_init +_copy_data_init_done: + // Zero fill the bss segment. + ldr r1, =__bss_start__ + ldr r2, =__bss_end__ + movs r0, 0 + subs r2, r2, r1 + ble _loop_fill_zero_bss_done + +_loop_fill_zero_bss: + subs r2, r2, #4 + str r0, [r1, r2] + bgt _loop_fill_zero_bss + +_loop_fill_zero_bss_done: + // Call the application's entry point. + bl main + bx lr +.size reset_handler, . - reset_handler diff --git a/examples/baremetal/pca10040/greenblink/gpio.c b/examples/baremetal/pca10040/greenblink/gpio.c new file mode 100644 index 00000000..c86f6ac5 --- /dev/null +++ b/examples/baremetal/pca10040/greenblink/gpio.c @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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 "gpio.h" +#include "system.h" + +#define GPIO_GREEN_LED_PIN_POS (17u) +#define GPIO_GREEN_LED_PIN (1u << GPIO_GREEN_LED_PIN_POS) + +// Pin direction attributes. +#define GPIO_PIN_CNF_DIR_POS (0u) // Position of DIR field. +#define GPIO_PIN_CNF_DIR_OUT (1u) // Configure pin as an output pin. +#define GPIO_PIN_CNF_DIR_MSK (GPIO_PIN_CNF_DIR_OUT << GPIO_PIN_CNF_DIR_POS) + +// Pin input buffer attributes. +#define GPIO_PIN_CNF_INPUT_POS (1u) // Position of INPUT field. +#define GPIO_PIN_CNF_INPUT_OFF (1u) // Disconnect input buffer. +#define GPIO_PIN_CNF_INPUT_MSK (GPIO_PIN_CNF_INPUT_OFF << GPIO_PIN_CNF_INPUT_POS) + +// Pin pull attributes. +#define GPIO_PIN_CNF_PULL_POS (2u) // Position of PULL field. +#define GPIO_PIN_CNF_PULL_OFF (0u) // No pull. +#define GPIO_PIN_CNF_PULL_MSK (GPIO_PIN_CNF_PULL_OFF << GPIO_PIN_CNF_PULL_POS) + +// Pin drive attributes. +#define GPIO_PIN_CNF_DRIVE_POS (8u) // Position of DRIVE field. +#define GPIO_PIN_CNF_DRIVE_S0S1 (0u) // Standard '0', standard '1'. +#define GPIO_PIN_CNF_DRIVE_MSK (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_POS) + +// Pin sense attributes. +#define GPIO_PIN_CNF_SENSE_POS (16u) // Position of SENSE field. +#define GPIO_PIN_CNF_SENSE_OFF (0u) // Disabled. +#define GPIO_PIN_CNF_SENSE_MSK (GPIO_PIN_CNF_SENSE_OFF << GPIO_PIN_CNF_SENSE_POS) + +void gpio_init_green_led(void) +{ + GPIOD_REGS_MAP->PIN_CNF[GPIO_GREEN_LED_PIN_POS] = (GPIO_PIN_CNF_DIR_MSK + | GPIO_PIN_CNF_INPUT_MSK + | GPIO_PIN_CNF_PULL_MSK + | GPIO_PIN_CNF_DRIVE_MSK + | GPIO_PIN_CNF_SENSE_MSK); +} + +void gpio_toggle_green_led(void) +{ + const uint32_t gpio_state = GPIOD_REGS_MAP->OUT; + GPIOD_REGS_MAP->OUTSET = (GPIO_GREEN_LED_PIN & ~gpio_state); + GPIOD_REGS_MAP->OUTCLR = (GPIO_GREEN_LED_PIN & gpio_state); +} diff --git a/examples/baremetal/pca10040/greenblink/gpio.h b/examples/baremetal/pca10040/greenblink/gpio.h new file mode 100644 index 00000000..9e931b71 --- /dev/null +++ b/examples/baremetal/pca10040/greenblink/gpio.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef GPIO_H +#define GPIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +void gpio_init_green_led(void); +void gpio_toggle_green_led(void); + +#ifdef __cplusplus +} +#endif + +#endif // GPIO_H diff --git a/examples/baremetal/pca10040/greenblink/greenblink.qbs b/examples/baremetal/pca10040/greenblink/greenblink.qbs new file mode 100644 index 00000000..b0d7f832 --- /dev/null +++ b/examples/baremetal/pca10040/greenblink/greenblink.qbs @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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 { + condition: { + if (!qbs.architecture.startsWith("arm")) + return false; + return (qbs.toolchain.contains("gcc") + || qbs.toolchain.contains("keil")) + && !qbs.toolchain.contains("xcode") + } + name: "pca10040-greenblink" + cpp.cLanguageVersion: "c99" + cpp.positionIndependentCode: false + + // + // GCC-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.driverFlags: [ + "-mcpu=cortex-m4", + "-mfloat-abi=hard", + "-mfpu=fpv4-sp-d16", + "-specs=nosys.specs" + ] + } + + Group { + condition: qbs.toolchain.contains("gcc") + name: "GCC" + prefix: "gcc/" + Group { + name: "Startup" + fileTags: ["asm"] + files: ["startup.s"] + } + Group { + name: "Linker Script" + fileTags: ["linkerscript"] + files: ["flash.ld"] + } + } + + // + // KEIL-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("keil") + cpp.driverFlags: [ + "--cpu", "cortex-m4.fp.sp" + ] + cpp.assemblerFlags: [ + "--cpu", "cortex-m4.fp.sp" + ] + } + + Group { + condition: qbs.toolchain.contains("keil") + name: "KEIL" + prefix: "keil/" + Group { + name: "Startup" + fileTags: ["asm"] + files: ["startup.s"] + } + Group { + name: "Linker Script" + fileTags: ["linkerscript"] + files: ["flash.sct"] + } + } + + // + // Common code. + // + + Group { + name: "Gpio" + files: ["gpio.c", "gpio.h"] + } + + Group { + name: "System" + files: ["system.h"] + } + + files: ["main.c"] +} diff --git a/examples/baremetal/pca10040/greenblink/keil/flash.sct b/examples/baremetal/pca10040/greenblink/keil/flash.sct new file mode 100644 index 00000000..29843331 --- /dev/null +++ b/examples/baremetal/pca10040/greenblink/keil/flash.sct @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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$ +** +****************************************************************************/ + +;; Load region size_region. +LR_IROM1 0x00000000 0x00080000 { + ;; Load address = execution address. + ER_IROM1 0x00000000 0x00080000 { + *.o (RESET, +First) + *(InRoot$$Sections) + .ANY (+RO) + .ANY (+XO) + } + + ; RW data. + RW_IRAM1 0x20000000 0x00008000 { + .ANY (+RW +ZI) + } +} diff --git a/examples/baremetal/pca10040/greenblink/keil/startup.s b/examples/baremetal/pca10040/greenblink/keil/startup.s new file mode 100644 index 00000000..c4a197d4 --- /dev/null +++ b/examples/baremetal/pca10040/greenblink/keil/startup.s @@ -0,0 +1,342 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Copyright (C) 2020 Denis Shienkov +;; 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$ +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +_size_of_stack EQU 0x2000 +_size_of_heap EQU 0x2000 + +;; Stack configuration. + AREA STACK, NOINIT, READWRITE, ALIGN=3 +_start_of_stack SPACE _size_of_stack +_end_of_stack + +;; Heap configuration. + AREA HEAP, NOINIT, READWRITE, ALIGN=3 +_start_of_heap SPACE _size_of_heap +_end_of_heap + + PRESERVE8 + THUMB + +;; Define the empty vectors table. + AREA RESET, DATA, READONLY +_vectors_table + ;; Generic interrupts offset. + DCD _end_of_stack ; Initial stack pointer value. + DCD reset_handler ; Reset. + DCD 0 ; NMI. + DCD 0 ; Hard fault. + DCD 0 ; Memory management fault. + DCD 0 ; Bus fault. + DCD 0 ; Usage fault. + DCD 0 ; Reserved. + DCD 0 ; Reserved. + DCD 0 ; Reserved. + DCD 0 ; Reserved. + DCD 0 ; SVC. + DCD 0 ; Reserved. + DCD 0 ; Reserved. + DCD 0 ; PendSV. + DCD 0 ; SysTick. + ;; External interrupts offset. + DCD 0 ; Power clock. + DCD 0 ; Radio. + DCD 0 ; UARTE0/UART0. + DCD 0 ; SPIM0/SPIS0/TWIM0/TWIS0/SPI0/TWI0 + DCD 0 ; SPIM1/SPIS1/TWIM1/TWIS1/SPI1/TWI1 + DCD 0 ; NFCT + DCD 0 ; GPIOTE + DCD 0 ; SAADC + DCD 0 ; TIMER0 + DCD 0 ; TIMER1 + DCD 0 ; TIMER2 + DCD 0 ; RTC0 + DCD 0 ; TEMP + DCD 0 ; RNG + DCD 0 ; ECB + DCD 0 ; CCM/AAR + DCD 0 ; WDT + DCD 0 ; RTC1 + DCD 0 ; QDEC + DCD 0 ; COMP/LPCOMP + DCD 0 ; SWI0/EGU0 + DCD 0 ; SWI1/EGU1 + DCD 0 ; SWI2/EGU2 + DCD 0 ; SWI3/EGU3 + DCD 0 ; SWI4/EGU4 + DCD 0 ; SWI5/EGU5 + DCD 0 ; TIMER3 + DCD 0 ; TIMER4 + DCD 0 ; PWM0 + DCD 0 ; PDM + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; MWU + DCD 0 ; PWM1 + DCD 0 ; PWM2 + DCD 0 ; SPIM2/SPIS2/SPI2 + DCD 0 ; RTC2 + DCD 0 ; I2S + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved + DCD 0 ; Reserved +_end_of_vectors_table + +_size_of_vectors_table EQU _end_of_vectors_table - _vectors_table + + AREA |.text|, CODE, READONLY +;; Reset handler. +reset_handler PROC + EXPORT reset_handler [WEAK] + IMPORT main + LDR R0, =main + BX R0 + ENDP + ALIGN + + END diff --git a/examples/baremetal/pca10040/greenblink/main.c b/examples/baremetal/pca10040/greenblink/main.c new file mode 100644 index 00000000..89d3daeb --- /dev/null +++ b/examples/baremetal/pca10040/greenblink/main.c @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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 "gpio.h" + +#include + +static void some_delay(uint32_t counts) +{ + for (uint32_t index = 0u; index < counts; ++index) + __asm("nop"); +} + +int main(void) +{ + gpio_init_green_led(); + + while (1) { + gpio_toggle_green_led(); + some_delay(1000000u); + } +} diff --git a/examples/baremetal/pca10040/greenblink/system.h b/examples/baremetal/pca10040/greenblink/system.h new file mode 100644 index 00000000..bf9e4b35 --- /dev/null +++ b/examples/baremetal/pca10040/greenblink/system.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef SYSTEM_H +#define SYSTEM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define __IO volatile + +struct gpio_regs_map { + uint32_t RESERVED0[321u]; + __IO uint32_t OUT; + __IO uint32_t OUTSET; + __IO uint32_t OUTCLR; + __IO uint32_t IN; + __IO uint32_t DIR; + __IO uint32_t DIRSET; + __IO uint32_t DIRCLR; + __IO uint32_t LATCH; + __IO uint32_t DETECTMODE; + uint32_t RESERVED1[118u]; + __IO uint32_t PIN_CNF[32u]; +}; + +#define GPIO_REGS_ADDRESS (0x50000000u) +#define GPIOD_REGS_MAP ((struct gpio_regs_map *)GPIO_REGS_ADDRESS) + +#ifdef __cplusplus +} +#endif + +#endif // SYSTEM_H diff --git a/examples/baremetal/pca10040/pca10040.qbs b/examples/baremetal/pca10040/pca10040.qbs new file mode 100644 index 00000000..caceae0b --- /dev/null +++ b/examples/baremetal/pca10040/pca10040.qbs @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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 + +Project { + name: "Examples for pca10040 board" + references: [ + "greenblink/greenblink.qbs" + ] +} diff --git a/examples/baremetal/stm32f103/greenblink/README.md b/examples/baremetal/stm32f103/greenblink/README.md new file mode 100644 index 00000000..652ab7ec --- /dev/null +++ b/examples/baremetal/stm32f103/greenblink/README.md @@ -0,0 +1,9 @@ +This example demonstrates how to build a bare-metal application using +different ARM toolchains. It is designed for the stm32f103 "Blue Pill" +evaluation kit (based on stm32f103c8 MCU) and simply flashes the green +LED on the board. + +The following toolchains are supported: + + * GNU Arm Embedded Toolchain + * KEIL Microcontroller Development Kit diff --git a/examples/baremetal/stm32f103/greenblink/gcc/flash.ld b/examples/baremetal/stm32f103/greenblink/gcc/flash.ld new file mode 100644 index 00000000..3fe5c156 --- /dev/null +++ b/examples/baremetal/stm32f103/greenblink/gcc/flash.ld @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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$ +** +****************************************************************************/ + +/* Entry point (defined in assembled file). */ +ENTRY(reset_handler) + +/* End of RAM, it is the user mode stack pointer address. */ +_end_of_stack = 0x20005000; + +/* Generate a link error if heap and stack don't fit into RAM. */ +_size_of_heap = 0x200; /* Required amount of heap. */ +_size_of_stack = 0x400; /* Required amount of stack. */ + +MEMORY { + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K + FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 64K +} + +SECTIONS { + /* The vectors table goes into FLASH. */ + .isr_vector : { + . = ALIGN(4); + KEEP(*(.isr_vector)) /* Startup code. */ + . = ALIGN(4); + } > FLASH + + /* The program code and other data goes into FLASH. */ + .text : { + . = ALIGN(4); + *(.text) + *(.text*) + *(.glue_7) /* Glue arm to thumb code. */ + *(.glue_7t) /* Glue thumb to arm code. */ + *(.eh_frame) + KEEP(*(.init)) + KEEP(*(.fini)) + . = ALIGN(4); + _end_of_text_section = .; /* Export global symbol at end of code. */ + } > FLASH + + /* Constant data goes into FLASH. */ + .rodata : { + . = ALIGN(4); + *(.rodata) + *(.rodata*) + . = ALIGN(4); + } > FLASH + + .ARM.extab : { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + .ARM : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } > FLASH + + .preinit_array : { + PROVIDE_HIDDEN(__preinit_array_start = .); + KEEP(*(.preinit_array*)) + PROVIDE_HIDDEN(__preinit_array_end = .); + } > FLASH + + .init_array : { + PROVIDE_HIDDEN(__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array*)) + PROVIDE_HIDDEN(__init_array_end = .); + } > FLASH + + .fini_array : { + PROVIDE_HIDDEN(__fini_array_start = .); + KEEP(*(SORT(.fini_array.*))) + KEEP(*(.fini_array*)) + PROVIDE_HIDDEN(__fini_array_end = .); + } > FLASH + + _start_of_init_data_section = LOADADDR(.data); + + /* Initialized data sections goes into RAM, load LMA copy after code. */ + .data : { + . = ALIGN(4); + _start_of_data_section = .; /* Export global symbol at data start. */ + *(.data) + *(.data*) + . = ALIGN(4); + _end_of_data_section = .; /* Export global symbol at data end. */ + } > RAM AT > FLASH + + /* Uninitialized data section. */ + . = ALIGN(4); + .bss : { + /* This is used by the startup in order to initialize the .bss secion. */ + _start_of_bss_section = .; /* Export global symbol at bss start. */ + __bss_start__ = _start_of_bss_section; + *(.bss) + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_of_bss_section = .; /* Export global symbol at bss end. */ + __bss_end__ = _end_of_bss_section; + } > RAM + + /* Used to check that there is enough RAM left. */ + ._user_heap_stack : { + . = ALIGN(4); + PROVIDE(end = .); + PROVIDE(_end = .); + . = . + _size_of_heap; + . = . + _size_of_stack; + . = ALIGN(4); + } > RAM + + /* Remove information from the standard libraries. */ + /DISCARD/ : { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } + + .ARM.attributes 0 : { + *(.ARM.attributes) + } +} diff --git a/examples/baremetal/stm32f103/greenblink/gcc/startup.s b/examples/baremetal/stm32f103/greenblink/gcc/startup.s new file mode 100644 index 00000000..5bb262f1 --- /dev/null +++ b/examples/baremetal/stm32f103/greenblink/gcc/startup.s @@ -0,0 +1,177 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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$ +** +****************************************************************************/ + +.syntax unified +.cpu cortex-m3 +.fpu softvfp +.thumb + +// These symbols are exported from the linker script. +.word _start_of_init_data_section +.word _start_of_data_section +.word _end_of_data_section +.word _start_of_bss_section +.word _end_of_bss_section + +.equ _boot_ram, 0xF108F85F + +.section .text.reset_handler +.weak reset_handler +.type reset_handler, %function +reset_handler: + // Copy the data segment initializers from flash to SRAM. + movs r1, #0 + b _loop_copy_data_init +_copy_data_init: + ldr r3, =_start_of_init_data_section + ldr r3, [r3, r1] + str r3, [r0, r1] + adds r1, r1, #4 +_loop_copy_data_init: + ldr r0, =_start_of_data_section + ldr r3, =_end_of_data_section + adds r2, r0, r1 + cmp r2, r3 + bcc _copy_data_init + ldr r2, =_start_of_bss_section + b _loop_fill_zero_bss + + // Zero fill the bss segment. +_fill_zero_bss: + movs r3, #0 + str r3, [r2], #4 +_loop_fill_zero_bss: + ldr r3, = _end_of_bss_section + cmp r2, r3 + bcc _fill_zero_bss + + // Call the static constructors. + bl __libc_init_array + + // Call the application entry point. + bl main + bx lr +.size reset_handler, .-reset_handler + +// Define the empty vectors table. +.section .isr_vector,"a",%progbits +.type _vectors_table, %object +.size _vectors_table, .-_vectors_table +_vectors_table: + // Generic interrupts offset. + .word _end_of_stack // Initial stack pointer value. + .word reset_handler // Reset. + .word 0 // NMI. + .word 0 // Hard fault. + .word 0 // Memory management fault. + .word 0 // Bus fault. + .word 0 // Usage fault. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // SVC. + .word 0 // Debug monitor. + .word 0 // Reserved. + .word 0 // PendSV. + .word 0 // SysTick. + + // External interrupts offset. + .word 0 // Window watchdog. + .word 0 // PVD through EXTI Line detection. + .word 0 // Tamper and timestamps through the EXTI line. + .word 0 // RTC wakeup through the EXTI line. + .word 0 // FLASH. + .word 0 // RCC. + .word 0 // EXTI line0. + .word 0 // EXTI line1. + .word 0 // EXTI line2. + .word 0 // EXTI line3. + .word 0 // EXTI line4. + .word 0 // DMA1 stream 0. + .word 0 // DMA1 stream 1. + .word 0 // DMA1 stream 2. + .word 0 // DMA1 stream 3. + .word 0 // DMA1 stream 4. + .word 0 // DMA1 stream 5. + .word 0 // DMA1 stream 6. + .word 0 // ADC1/2. + .word 0 // USB HP/CAN1 TX. + .word 0 // USB LP / CAN1 RX0. + .word 0 // CAN1 RX1. + .word 0 // CAN1 SCE. + .word 0 // EXTI line [9:5]s. + .word 0 // TIM1 break. + .word 0 // TIM1 update. + .word 0 // TIM1 trigger and communication. + .word 0 // TIM1 capture compare. + .word 0 // TIM2. + .word 0 // TIM3. + .word 0 // TIM4. + .word 0 // I2C1 event. + .word 0 // I2C1 error. + .word 0 // I2C2 event. + .word 0 // I2C2 error. + .word 0 // SPI1. + .word 0 // SPI2. + .word 0 // USART1. + .word 0 // USART2. + .word 0 // USART3. + .word 0 // EXTI line [15:10]s. + .word 0 // RTC alarm. + .word 0 // USB wakeup. + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word 0 + .word _boot_ram // Boot RAM mode for stm32f10x medium density devices. diff --git a/examples/baremetal/stm32f103/greenblink/gpio.c b/examples/baremetal/stm32f103/greenblink/gpio.c new file mode 100644 index 00000000..84c87a2b --- /dev/null +++ b/examples/baremetal/stm32f103/greenblink/gpio.c @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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 "gpio.h" +#include "system.h" + +#define GPIO_GREEN_LED_PIN_POS (13u) +#define GPIO_GREEN_LED_PIN (1u << GPIO_GREEN_LED_PIN_POS) + +// Bit definition for RCC_APB2ENR register. +#define RCC_APB2ENR_IOPCEN_POS (4u) +#define RCC_APB2ENR_IOPCEN (0x1u << RCC_APB2ENR_IOPCEN_POS) + +// Bit definition for GPIO_CRL register. +#define GPIO_CRL_MODE_POS (0u) // MODE field position. +#define GPIO_CRL_MODE_MSK (0x3u << GPIO_CRL_MODE_POS) // MODE field mask. +#define GPIO_CRL_MODE_OUT_FREQ_LOW (0x2u << GPIO_CRL_MODE_POS) // As output with low frequency. + +#define GPIO_CRL_CNF_POS (2u) // CNF field position. +#define GPIO_CRL_CNF_MSK (0x3u << GPIO_CRL_CNF_POS) // CNF field mask. +#define GPIO_CRL_CNF_OUTPUT_PP (0x00000000u) // General purpose output push-pull. + +void gpio_init_green_led(void) +{ + // Enable RCC clock on GPIOC port. + RCC_REGS_MAP->APB2ENR |= RCC_APB2ENR_IOPCEN; + // Configure GPIOC pin #13. + const uint32_t offset = ((GPIO_GREEN_LED_PIN_POS - 8u) << 2u); + GPIOC_REGS_MAP->CRH &= ~((GPIO_CRL_MODE_MSK | GPIO_CRL_CNF_MSK) << offset); + GPIOC_REGS_MAP->CRH |= ((GPIO_CRL_MODE_OUT_FREQ_LOW | GPIO_CRL_CNF_OUTPUT_PP) << offset); +} + +void gpio_toggle_green_led(void) +{ + GPIOC_REGS_MAP->ODR ^= GPIO_GREEN_LED_PIN; +} diff --git a/examples/baremetal/stm32f103/greenblink/gpio.h b/examples/baremetal/stm32f103/greenblink/gpio.h new file mode 100644 index 00000000..9e931b71 --- /dev/null +++ b/examples/baremetal/stm32f103/greenblink/gpio.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef GPIO_H +#define GPIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +void gpio_init_green_led(void); +void gpio_toggle_green_led(void); + +#ifdef __cplusplus +} +#endif + +#endif // GPIO_H diff --git a/examples/baremetal/stm32f103/greenblink/greenblink.qbs b/examples/baremetal/stm32f103/greenblink/greenblink.qbs new file mode 100644 index 00000000..530e38fc --- /dev/null +++ b/examples/baremetal/stm32f103/greenblink/greenblink.qbs @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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 { + condition: { + if (!qbs.architecture.startsWith("arm")) + return false; + return (qbs.toolchain.contains("gcc") + || qbs.toolchain.contains("keil")) + && !qbs.toolchain.contains("xcode") + } + name: "stm32f103-greenblink" + cpp.cLanguageVersion: "c99" + cpp.positionIndependentCode: false + + // + // GCC-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.driverFlags: [ + "-mcpu=cortex-m3", + "-specs=nosys.specs" + ] + } + + Group { + condition: qbs.toolchain.contains("gcc") + name: "GCC" + prefix: "gcc/" + Group { + name: "Startup" + fileTags: ["asm"] + files: ["startup.s"] + } + Group { + name: "Linker Script" + fileTags: ["linkerscript"] + files: ["flash.ld"] + } + } + + // + // KEIL-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("keil") + cpp.driverFlags: [ + "--cpu", "cortex-m3" + ] + cpp.assemblerFlags: [ + "--cpu", "cortex-m3" + ] + } + + Group { + condition: qbs.toolchain.contains("keil") + name: "KEIL" + prefix: "keil/" + Group { + name: "Startup" + fileTags: ["asm"] + files: ["startup.s"] + } + Group { + name: "Linker Script" + fileTags: ["linkerscript"] + files: ["flash.sct"] + } + } + + // + // Common code. + // + + Group { + name: "Gpio" + files: ["gpio.c", "gpio.h"] + } + + Group { + name: "System" + files: ["system.h"] + } + + files: ["main.c"] +} diff --git a/examples/baremetal/stm32f103/greenblink/keil/flash.sct b/examples/baremetal/stm32f103/greenblink/keil/flash.sct new file mode 100644 index 00000000..3547ca2c --- /dev/null +++ b/examples/baremetal/stm32f103/greenblink/keil/flash.sct @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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$ +** +****************************************************************************/ + +;; Load region size_region. +LR_IROM1 0x08000000 0x00010000 { + ;; Load address = execution address. + ER_IROM1 0x08000000 0x00010000 { + *.o (RESET, +First) + *(InRoot$$Sections) + .ANY (+RO) + .ANY (+XO) + } + + ; RW data. + RW_IRAM1 0x20000000 0x00005000 { + .ANY (+RW +ZI) + } +} diff --git a/examples/baremetal/stm32f103/greenblink/keil/startup.s b/examples/baremetal/stm32f103/greenblink/keil/startup.s new file mode 100644 index 00000000..d80de94c --- /dev/null +++ b/examples/baremetal/stm32f103/greenblink/keil/startup.s @@ -0,0 +1,145 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Copyright (C) 2020 Denis Shienkov +;; 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$ +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +_size_of_stack EQU 0x400 +_size_of_heap EQU 0x200 + +;; Stack configuration. + AREA STACK, NOINIT, READWRITE, ALIGN=3 +_start_of_stack SPACE _size_of_stack +_end_of_stack + +;; Heap configuration. + AREA HEAP, NOINIT, READWRITE, ALIGN=3 +_start_of_heap SPACE _size_of_heap +_end_of_heap + + PRESERVE8 + THUMB + +;; Define the empty vectors table. + AREA RESET, DATA, READONLY +_vectors_table + ;; Generic interrupts offset. + DCD _end_of_stack ; Initial stack pointer value. + DCD reset_handler ; Reset. + DCD 0 ; NMI. + DCD 0 ; Hard fault. + DCD 0 ; Memory management fault. + DCD 0 ; Bus fault. + DCD 0 ; Usage fault. + DCD 0 ; Reserved. + DCD 0 ; Reserved. + DCD 0 ; Reserved. + DCD 0 ; Reserved. + DCD 0 ; SVC. + DCD 0 ; Debug monitor. + DCD 0 ; Reserved. + DCD 0 ; PendSV. + DCD 0 ; SysTick. + ;; External interrupts offset. + DCD 0 ; Window watchdog. + DCD 0 ; PVD through EXTI line detection. + DCD 0 ; Tamper and TimeStamps through the EXTI line. + DCD 0 ; RTC wakeup through the EXTI line. + DCD 0 ; FLASH. + DCD 0 ; RCC. + DCD 0 ; EXTI line0. + DCD 0 ; EXTI line1. + DCD 0 ; EXTI line2. + DCD 0 ; EXTI line3. + DCD 0 ; EXTI line4. + DCD 0 ; DMA1 channel 1. + DCD 0 ; DMA1 channel 2. + DCD 0 ; DMA1 channel 3. + DCD 0 ; DMA1 channel 4. + DCD 0 ; DMA1 channel 5. + DCD 0 ; DMA1 channel 6. + DCD 0 ; DMA1 channel 7. + DCD 0 ; ADC1/2. + DCD 0 ; USB high priority or CAN1 TX. + DCD 0 ; USB low priority or CAN1 RX0. + DCD 0 ; CAN1 RX1. + DCD 0 ; CAN1 SCE. + DCD 0 ; EXTI line 9..5. + DCD 0 ; TIM1 break. + DCD 0 ; TIM1 update. + DCD 0 ; TIM1 trigger and commutation. + DCD 0 ; IM1 capture compare. + DCD 0 ; TIM2. + DCD 0 ; TIM3. + DCD 0 ; TIM4. + DCD 0 ; I2C1 event. + DCD 0 ; I2C1 error. + DCD 0 ; I2C2 event. + DCD 0 ; I2C2 error. + DCD 0 ; SPI1. + DCD 0 ; SPI2. + DCD 0 ; USART1. + DCD 0 ; USART2. + DCD 0 ; USART3. + DCD 0 ; EXTI line 15..10. + DCD 0 ; RTC alarm through EXTI line. + DCD 0 ; USB wakeup from suspend. +_end_of_vectors_table + +_size_of_vectors_table EQU _end_of_vectors_table - _vectors_table + + AREA |.text|, CODE, READONLY +;; Reset handler. +reset_handler PROC + EXPORT reset_handler [WEAK] + IMPORT main + LDR R0, =main + BX R0 + ENDP + ALIGN + + END diff --git a/examples/baremetal/stm32f103/greenblink/main.c b/examples/baremetal/stm32f103/greenblink/main.c new file mode 100644 index 00000000..21bc5bad --- /dev/null +++ b/examples/baremetal/stm32f103/greenblink/main.c @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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 "gpio.h" + +#include + +static void some_delay(uint32_t counts) +{ + for (uint32_t index = 0u; index < counts; ++index) + __asm("nop"); +} + +int main(void) +{ + gpio_init_green_led(); + + while (1) { + gpio_toggle_green_led(); + some_delay(100000u); + } +} diff --git a/examples/baremetal/stm32f103/greenblink/system.h b/examples/baremetal/stm32f103/greenblink/system.h new file mode 100644 index 00000000..d18854be --- /dev/null +++ b/examples/baremetal/stm32f103/greenblink/system.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef SYSTEM_H +#define SYSTEM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define __IO volatile + +// General purpose input/output registers map. +struct gpio_regs_map { + __IO uint32_t CRL; + __IO uint32_t CRH; + __IO uint32_t IDR; + __IO uint32_t ODR; + __IO uint32_t BSRR; + __IO uint32_t BRR; + __IO uint32_t LCKR; +}; + +// Reset and clock control registers map. +struct rcc_regs_map { + __IO uint32_t CR; + __IO uint32_t CFGR; + __IO uint32_t CIR; + __IO uint32_t APB2RSTR; + __IO uint32_t APB1RSTR; + __IO uint32_t AHBENR; + __IO uint32_t APB2ENR; + __IO uint32_t APB1ENR; + __IO uint32_t BDCR; + __IO uint32_t CSR; +}; + +#define PERIPH_ADDRESS (0x40000000u) + +#define APB2PERIPH_ADDRESS (PERIPH_ADDRESS + 0x00010000u) +#define AHBPERIPH_ADDRESS (PERIPH_ADDRESS + 0x00020000u) + +#define GPIOC_REGS_ADDRESS (APB2PERIPH_ADDRESS + 0x00001000u) +#define RCC_REGS_ADDRESS (AHBPERIPH_ADDRESS + 0x00001000u) + +#define GPIOC_REGS_MAP ((struct gpio_regs_map *)GPIOC_REGS_ADDRESS) +#define RCC_REGS_MAP ((struct rcc_regs_map *)RCC_REGS_ADDRESS) + +#ifdef __cplusplus +} +#endif + +#endif // SYSTEM_H diff --git a/examples/baremetal/stm32f103/stm32f103.qbs b/examples/baremetal/stm32f103/stm32f103.qbs new file mode 100644 index 00000000..bb5ab33e --- /dev/null +++ b/examples/baremetal/stm32f103/stm32f103.qbs @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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 + +Project { + name: "Examples for stm32f103 board" + references: [ + "greenblink/greenblink.qbs" + ] +} diff --git a/examples/baremetal/stm32f4discovery/blueblink/README.md b/examples/baremetal/stm32f4discovery/blueblink/README.md new file mode 100644 index 00000000..d15e1251 --- /dev/null +++ b/examples/baremetal/stm32f4discovery/blueblink/README.md @@ -0,0 +1,10 @@ +This example demonstrates how to build a bare-metal application using +different ARM toolchains. It is designed for the stm32f4discovery +evaluation kit (based on stm32f407vg MCU) and simply flashes the blue +LED on the board. + +The following toolchains are supported: + + * GNU Arm Embedded Toolchain + * IAR Embedded Workbench + * KEIL Microcontroller Development Kit diff --git a/examples/baremetal/stm32f4discovery/blueblink/blueblink.qbs b/examples/baremetal/stm32f4discovery/blueblink/blueblink.qbs new file mode 100644 index 00000000..254901e0 --- /dev/null +++ b/examples/baremetal/stm32f4discovery/blueblink/blueblink.qbs @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 { + condition: { + if (!qbs.architecture.startsWith("arm")) + return false; + return (qbs.toolchain.contains("gcc") + || qbs.toolchain.contains("iar") + || qbs.toolchain.contains("keil")) + && !qbs.toolchain.contains("xcode") + } + name: "stm32f4discovery-blueblink" + cpp.cLanguageVersion: "c99" + cpp.positionIndependentCode: false + + // + // GCC-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.driverFlags: [ + "-mcpu=cortex-m4", + "-mfloat-abi=hard", + "-mfpu=fpv4-sp-d16", + "-specs=nosys.specs" + ] + } + + Group { + condition: qbs.toolchain.contains("gcc") + name: "GCC" + prefix: "gcc/" + Group { + name: "Startup" + fileTags: ["asm"] + files: ["startup.s"] + } + Group { + name: "Linker Script" + fileTags: ["linkerscript"] + files: ["flash.ld"] + } + } + + // + // IAR-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("iar") + cpp.driverFlags: [ + "--cpu", "cortex-m4", + "--fpu", "vfpv4_sp" + ] + } + + Group { + condition: qbs.toolchain.contains("iar") + name: "IAR" + prefix: "iar/" + Group { + name: "Startup" + fileTags: ["asm"] + files: ["startup.s"] + } + Group { + name: "Linker Script" + fileTags: ["linkerscript"] + files: ["flash.icf"] + } + } + + // + // KEIL-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("keil") + cpp.driverFlags: [ + "--cpu", "cortex-m4.fp" + ] + cpp.assemblerFlags: [ + "--cpu", "cortex-m4.fp" + ] + } + + Group { + condition: qbs.toolchain.contains("keil") + name: "KEIL" + prefix: "keil/" + Group { + name: "Startup" + fileTags: ["asm"] + files: ["startup.s"] + } + Group { + name: "Linker Script" + fileTags: ["linkerscript"] + files: ["flash.sct"] + } + } + + // + // Common code. + // + + Group { + name: "Gpio" + files: ["gpio.c", "gpio.h"] + } + + Group { + name: "System" + files: ["system.h"] + } + + files: ["main.c"] +} diff --git a/examples/baremetal/stm32f4discovery/blueblink/gcc/flash.ld b/examples/baremetal/stm32f4discovery/blueblink/gcc/flash.ld new file mode 100644 index 00000000..2e1896f3 --- /dev/null +++ b/examples/baremetal/stm32f4discovery/blueblink/gcc/flash.ld @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +/* Entry point (defined in assembled file). */ +ENTRY(reset_handler) + +/* End of RAM, it is the user mode stack pointer address. */ +_end_of_stack = 0x20020000; + +/* Generate a link error if heap and stack don't fit into RAM. */ +_size_of_heap = 0x200; /* Required amount of heap. */ +_size_of_stack = 0x400; /* Required amount of stack. */ + +MEMORY { + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K + CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K + FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K +} + +SECTIONS { + /* The vectors table goes into FLASH. */ + .isr_vector : { + . = ALIGN(4); + KEEP(*(.isr_vector)) /* Startup code. */ + . = ALIGN(4); + } > FLASH + + /* The program code and other data goes into FLASH. */ + .text : { + . = ALIGN(4); + *(.text) + *(.text*) + *(.glue_7) /* Glue arm to thumb code. */ + *(.glue_7t) /* Glue thumb to arm code. */ + *(.eh_frame) + KEEP(*(.init)) + KEEP(*(.fini)) + . = ALIGN(4); + _end_of_text_section = .; /* Export global symbol at end of code. */ + } > FLASH + + /* Constant data goes into FLASH. */ + .rodata : { + . = ALIGN(4); + *(.rodata) + *(.rodata*) + . = ALIGN(4); + } > FLASH + + .ARM.extab : { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + .ARM : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } > FLASH + + .preinit_array : { + PROVIDE_HIDDEN(__preinit_array_start = .); + KEEP(*(.preinit_array*)) + PROVIDE_HIDDEN(__preinit_array_end = .); + } > FLASH + + .init_array : { + PROVIDE_HIDDEN(__init_array_start = .); + KEEP(*(SORT(.init_array.*))) + KEEP(*(.init_array*)) + PROVIDE_HIDDEN(__init_array_end = .); + } > FLASH + + .fini_array : { + PROVIDE_HIDDEN(__fini_array_start = .); + KEEP(*(SORT(.fini_array.*))) + KEEP(*(.fini_array*)) + PROVIDE_HIDDEN(__fini_array_end = .); + } > FLASH + + _start_of_init_data_section = LOADADDR(.data); + + /* Initialized data sections goes into RAM, load LMA copy after code. */ + .data : { + . = ALIGN(4); + _start_of_data_section = .; /* Export global symbol at data start. */ + *(.data) + *(.data*) + . = ALIGN(4); + _end_of_data_section = .; /* Export global symbol at data end. */ + } > RAM AT > FLASH + + _start_of_iccm_ram_section = LOADADDR(.ccmram); + + /* CCM-RAM section. */ + .ccmram : { + . = ALIGN(4); + _start_of_ccmram_section = .; /* Export global symbol at ccmram start. */ + *(.ccmram) + *(.ccmram*) + . = ALIGN(4); + _end_of_ccmram_section = .; /* Export global symbol at ccmram end. */ + } > CCMRAM AT > FLASH + + /* Uninitialized data section. */ + . = ALIGN(4); + .bss : { + /* This is used by the startup in order to initialize the .bss secion. */ + _start_of_bss_section = .; /* Export global symbol at bss start. */ + __bss_start__ = _start_of_bss_section; + *(.bss) + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_of_bss_section = .; /* Export global symbol at bss end. */ + __bss_end__ = _end_of_bss_section; + } > RAM + + /* Used to check that there is enough RAM left. */ + ._user_heap_stack : { + . = ALIGN(4); + PROVIDE(end = .); + PROVIDE(_end = .); + . = . + _size_of_heap; + . = . + _size_of_stack; + . = ALIGN(4); + } > RAM + + /* Remove information from the standard libraries. */ + /DISCARD/ : { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } + + .ARM.attributes 0 : { + *(.ARM.attributes) + } +} diff --git a/examples/baremetal/stm32f4discovery/blueblink/gcc/startup.s b/examples/baremetal/stm32f4discovery/blueblink/gcc/startup.s new file mode 100644 index 00000000..d6a2104d --- /dev/null +++ b/examples/baremetal/stm32f4discovery/blueblink/gcc/startup.s @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +.syntax unified +.cpu cortex-m4 +.fpu softvfp +.thumb + +// These symbols are exported from the linker script. +.word _start_of_init_data_section +.word _start_of_data_section +.word _end_of_data_section +.word _start_of_bss_section +.word _end_of_bss_section +.word _end_of_stack + +// Define the 'reset_handler' function, which is an entry point. +.section .text.reset_handler +.weak reset_handler +.type reset_handler, %function +reset_handler: + // Set the stack pointer. + ldr sp, =_end_of_stack + + // Copy the data segment initializers from flash to SRAM. + movs r1, #0 + b _loop_copy_data_init +_copy_data_init: + ldr r3, =_start_of_init_data_section + ldr r3, [r3, r1] + str r3, [r0, r1] + adds r1, r1, #4 +_loop_copy_data_init: + ldr r0, =_start_of_data_section + ldr r3, =_end_of_data_section + adds r2, r0, r1 + cmp r2, r3 + bcc _copy_data_init + ldr r2, =_start_of_bss_section + b _loop_fill_zero_bss + + // Zero fill the bss segment. +_fill_zero_bss: + movs r3, #0 + str r3, [r2], #4 +_loop_fill_zero_bss: + ldr r3, = _end_of_bss_section + cmp r2, r3 + bcc _fill_zero_bss + + // Call the static constructors. + bl __libc_init_array + + // Call the application's entry point. + bl main + bx lr +.size reset_handler, .-reset_handler + +// Define the empty vectors table. +.section .isr_vector,"a",%progbits +.type _vectors_table, %object +.size _vectors_table, .-_vectors_table +_vectors_table: + // Generic interrupts offset. + .word _end_of_stack // Initial stack pointer value. + .word reset_handler // Reset. + .word 0 // NMI. + .word 0 // Hard fault. + .word 0 // Memory management fault. + .word 0 // Bus fault. + .word 0 // Usage fault. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // Reserved. + .word 0 // SVC. + .word 0 // Debug monitor. + .word 0 // Reserved. + .word 0 // PendSV. + .word 0 // SysTick. + + // External interrupts offset. + .word 0 // Window WatchDog. + .word 0 // PVD through EXTI Line detection. + .word 0 // Tamper and TimeStamps through the EXTI line. + .word 0 // RTC Wakeup through the EXTI line. + .word 0 // FLASH. + .word 0 // RCC. + .word 0 // EXTI Line0. + .word 0 // EXTI Line1. + .word 0 // EXTI Line2. + .word 0 // EXTI Line3. + .word 0 // EXTI Line4. + .word 0 // DMA1 Stream 0. + .word 0 // DMA1 Stream 1. + .word 0 // DMA1 Stream 2. + .word 0 // DMA1 Stream 3. + .word 0 // DMA1 Stream 4. + .word 0 // DMA1 Stream 5. + .word 0 // DMA1 Stream 6. + .word 0 // ADC1, ADC2 and ADC3s. + .word 0 // CAN1 TX. + .word 0 // CAN1 RX0. + .word 0 // CAN1 RX1. + .word 0 // CAN1 SCE. + .word 0 // External Line[9:5]s. + .word 0 // TIM1 Break and TIM9. + .word 0 // TIM1 Update and TIM10. + .word 0 // TIM1 Trigger and Commutation and TIM11. + .word 0 // TIM1 Capture Compare. + .word 0 // TIM2. + .word 0 // TIM3. + .word 0 // TIM4. + .word 0 // I2C1 Event. + .word 0 // I2C1 Error. + .word 0 // I2C2 Event. + .word 0 // I2C2 Error. + .word 0 // SPI1. + .word 0 // SPI2. + .word 0 // USART1. + .word 0 // USART2. + .word 0 // USART3. + .word 0 // External Line[15:10]s. + .word 0 // RTC Alarm (A and B) through EXTI Line. + .word 0 // USB OTG FS Wakeup through EXTI line. + .word 0 // TIM8 Break and TIM12. + .word 0 // TIM8 Update and TIM13. + .word 0 // TIM8 Trigger and Commutation and TIM14. + .word 0 // TIM8 Capture Compare. + .word 0 // DMA1 Stream7. + .word 0 // FSMC. + .word 0 // SDIO. + .word 0 // TIM5. + .word 0 // SPI3. + .word 0 // UART4. + .word 0 // UART5. + .word 0 // TIM6 and DAC1&2 underrun errors. + .word 0 // TIM7. + .word 0 // DMA2 Stream 0. + .word 0 // DMA2 Stream 1. + .word 0 // DMA2 Stream 2. + .word 0 // DMA2 Stream 3. + .word 0 // DMA2 Stream 4. + .word 0 // Ethernet. + .word 0 // Ethernet Wakeup through EXTI line. + .word 0 // CAN2 TX. + .word 0 // CAN2 RX0. + .word 0 // CAN2 RX1. + .word 0 // CAN2 SCE. + .word 0 // USB OTG FS. + .word 0 // DMA2 Stream 5. + .word 0 // DMA2 Stream 6. + .word 0 // DMA2 Stream 7. + .word 0 // USART6. + .word 0 // I2C3 event. + .word 0 // I2C3 error. + .word 0 // USB OTG HS End Point 1 Out. + .word 0 // USB OTG HS End Point 1 In. + .word 0 // USB OTG HS Wakeup through EXTI. + .word 0 // USB OTG HS. + .word 0 // DCMI. + .word 0 // CRYP crypto. + .word 0 // Hash and Rng. + .word 0 // FPU. diff --git a/examples/baremetal/stm32f4discovery/blueblink/gpio.c b/examples/baremetal/stm32f4discovery/blueblink/gpio.c new file mode 100644 index 00000000..e337f8d2 --- /dev/null +++ b/examples/baremetal/stm32f4discovery/blueblink/gpio.c @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "gpio.h" +#include "system.h" + +#define GPIO_BLUE_LED_PIN_POS (15u) +#define GPIO_BLUE_LED_PIN (1u << GPIO_BLUE_LED_PIN_POS) + +// Output push pull mode. +#define GPIO_MODE_OUTPUT_PP (0x00000001u) + +// Bit definition for RCC_AHB1ENR register. +#define RCC_AHB1ENR_GPIODEN (0x00000008u) +// Bits definition for GPIO_MODER register. +#define GPIO_MODER_MODE0 (0x00000003u) + +void gpio_init_blue_led(void) +{ + // Enable RCC clock on GPIOD port. + RCC_REGS_MAP->AHB1ENR |= RCC_AHB1ENR_GPIODEN; + // Configure GPIO pin #15 as an output. + uint32_t value = GPIOD_REGS_MAP->MODER; + value &= ~(GPIO_MODER_MODE0 << (GPIO_BLUE_LED_PIN_POS * 2u)); + value |= (GPIO_MODE_OUTPUT_PP << (GPIO_BLUE_LED_PIN_POS * 2u)); + GPIOD_REGS_MAP->MODER = value; +} + +void gpio_toggle_blue_led(void) +{ + GPIOD_REGS_MAP->ODR ^= GPIO_BLUE_LED_PIN; +} diff --git a/examples/baremetal/stm32f4discovery/blueblink/gpio.h b/examples/baremetal/stm32f4discovery/blueblink/gpio.h new file mode 100644 index 00000000..560dcbac --- /dev/null +++ b/examples/baremetal/stm32f4discovery/blueblink/gpio.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef GPIO_H +#define GPIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +void gpio_init_blue_led(void); +void gpio_toggle_blue_led(void); + +#ifdef __cplusplus +} +#endif + +#endif // GPIO_H diff --git a/examples/baremetal/stm32f4discovery/blueblink/iar/flash.icf b/examples/baremetal/stm32f4discovery/blueblink/iar/flash.icf new file mode 100644 index 00000000..04cff04c --- /dev/null +++ b/examples/baremetal/stm32f4discovery/blueblink/iar/flash.icf @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +define symbol _start_of_intvec_section = 0x08000000; + +define symbol _start_of_ram_section = 0x20000000; +define symbol _end_of_ram_section = 0x2001FFFF; +define symbol _start_of_ccmram_section = 0x10000000; +define symbol _end_of_ccmram_section = 0x1000FFFF; +define symbol _start_of_flash_section = 0x08000000; +define symbol _end_of_flash_section = 0x080FFFFF; + +define symbol _min_stack_size = 0x400; +define symbol _min_heap_size = 0x200; + +define memory mem with size = 4G; +define region flash_section = mem:[from _start_of_flash_section to _end_of_flash_section]; +define region ram_section = mem:[from _start_of_ram_section to _end_of_ram_section]; +define region ccmram_section = mem:[from _start_of_ccmram_section to _end_of_ccmram_section]; + +define block CSTACK with alignment = 8, size = _min_stack_size { }; +define block HEAP with alignment = 8, size = _min_heap_size { }; + +initialize by copy { readwrite }; +do not initialize { section .noinit }; + +place at address mem:_start_of_intvec_section { readonly section .intvec }; + +place in flash_section { readonly }; +place in ram_section { readwrite, block CSTACK, block HEAP }; diff --git a/examples/baremetal/stm32f4discovery/blueblink/iar/startup.s b/examples/baremetal/stm32f4discovery/blueblink/iar/startup.s new file mode 100644 index 00000000..fe88e6bc --- /dev/null +++ b/examples/baremetal/stm32f4discovery/blueblink/iar/startup.s @@ -0,0 +1,171 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + + MODULE ?cstartup + + SECTION CSTACK:DATA:NOROOT(3) + SECTION .intvec:CODE:NOROOT(2) + + EXTERN __iar_program_start + + PUBLIC _vectors_table + DATA +_vectors_table + ;; Generic interrupts offset. + DCD sfe(CSTACK) ; Initial stack pointer value. + DCD reset_handler ; Reset. + DCD 0 ; NMI. + DCD 0 ; Hard fault. + DCD 0 ; Memory management fault. + DCD 0 ; Bus fault. + DCD 0 ; Usage fault. + DCD 0 ; Reserved. + DCD 0 ; Reserved. + DCD 0 ; Reserved. + DCD 0 ; Reserved. + DCD 0 ; SVC. + DCD 0 ; Debug monitor. + DCD 0 ; Reserved. + DCD 0 ; PendSV. + DCD 0 ; SysTick. + ;; External interrupts offset. + DCD 0 ; Window WatchDog. + DCD 0 ; PVD through EXTI Line detection. + DCD 0 ; Tamper and TimeStamps through the EXTI line. + DCD 0 ; RTC Wakeup through the EXTI line. + DCD 0 ; FLASH. + DCD 0 ; RCC. + DCD 0 ; EXTI Line0. + DCD 0 ; EXTI Line1. + DCD 0 ; EXTI Line2. + DCD 0 ; EXTI Line3. + DCD 0 ; EXTI Line4. + DCD 0 ; DMA1 Stream 0. + DCD 0 ; DMA1 Stream 1. + DCD 0 ; DMA1 Stream 2. + DCD 0 ; DMA1 Stream 3. + DCD 0 ; DMA1 Stream 4. + DCD 0 ; DMA1 Stream 5. + DCD 0 ; DMA1 Stream 6. + DCD 0 ; ADC1, ADC2 and ADC3s. + DCD 0 ; CAN1 TX. + DCD 0 ; CAN1 RX0. + DCD 0 ; CAN1 RX1. + DCD 0 ; CAN1 SCE. + DCD 0 ; External Line[9:5]s. + DCD 0 ; TIM1 Break and TIM9. + DCD 0 ; TIM1 Update and TIM10. + DCD 0 ; TIM1 Trigger and Commutation and TIM11. + DCD 0 ; TIM1 Capture Compare. + DCD 0 ; TIM2. + DCD 0 ; TIM3. + DCD 0 ; TIM4. + DCD 0 ; I2C1 Event. + DCD 0 ; I2C1 Error. + DCD 0 ; I2C2 Event. + DCD 0 ; I2C2 Error. + DCD 0 ; SPI1. + DCD 0 ; SPI2. + DCD 0 ; USART1. + DCD 0 ; USART2. + DCD 0 ; USART3. + DCD 0 ; External Line[15:10]s. + DCD 0 ; RTC Alarm (A and B) through EXTI Line. + DCD 0 ; USB OTG FS Wakeup through EXTI line. + DCD 0 ; TIM8 Break and TIM12. + DCD 0 ; TIM8 Update and TIM13. + DCD 0 ; TIM8 Trigger and Commutation and TIM14. + DCD 0 ; TIM8 Capture Compare. + DCD 0 ; DMA1 Stream7. + DCD 0 ; FSMC. + DCD 0 ; SDIO. + DCD 0 ; TIM5. + DCD 0 ; SPI3. + DCD 0 ; UART4. + DCD 0 ; UART5. + DCD 0 ; TIM6 and DAC1&2 underrun errors. + DCD 0 ; TIM7. + DCD 0 ; DMA2 Stream 0. + DCD 0 ; DMA2 Stream 1. + DCD 0 ; DMA2 Stream 2. + DCD 0 ; DMA2 Stream 3. + DCD 0 ; DMA2 Stream 4. + DCD 0 ; Ethernet. + DCD 0 ; Ethernet Wakeup through EXTI line. + DCD 0 ; CAN2 TX. + DCD 0 ; CAN2 RX0. + DCD 0 ; CAN2 RX1. + DCD 0 ; CAN2 SCE. + DCD 0 ; USB OTG FS. + DCD 0 ; DMA2 Stream 5. + DCD 0 ; DMA2 Stream 6. + DCD 0 ; DMA2 Stream 7. + DCD 0 ; USART6. + DCD 0 ; I2C3 event. + DCD 0 ; I2C3 error. + DCD 0 ; USB OTG HS End Point 1 Out. + DCD 0 ; USB OTG HS End Point 1 In. + DCD 0 ; USB OTG HS Wakeup through EXTI. + DCD 0 ; USB OTG HS. + DCD 0 ; DCMI. + DCD 0 ; CRYP crypto. + DCD 0 ; Hash and RNG. + DCD 0 ; FPU. + + ;; Reset handler. + THUMB + PUBWEAK reset_handler + SECTION .text:CODE:REORDER:NOROOT(2) +reset_handler + BLX R0 + LDR R0, =__iar_program_start + BX R0 + + END diff --git a/examples/baremetal/stm32f4discovery/blueblink/keil/flash.sct b/examples/baremetal/stm32f4discovery/blueblink/keil/flash.sct new file mode 100644 index 00000000..7a550018 --- /dev/null +++ b/examples/baremetal/stm32f4discovery/blueblink/keil/flash.sct @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +;; Load region size_region. +LR_IROM1 0x08000000 0x00100000 { + ;; Load address = execution address. + ER_IROM1 0x08000000 0x00100000 { + *.o (RESET, +First) + *(InRoot$$Sections) + .ANY (+RO) + } + + ; RW data. + RW_IRAM1 0x20000000 0x00020000 { + .ANY (+RW +ZI) + } +} diff --git a/examples/baremetal/stm32f4discovery/blueblink/keil/startup.s b/examples/baremetal/stm32f4discovery/blueblink/keil/startup.s new file mode 100644 index 00000000..38e5b505 --- /dev/null +++ b/examples/baremetal/stm32f4discovery/blueblink/keil/startup.s @@ -0,0 +1,184 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Copyright (C) 2019 Denis Shienkov +;; 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$ +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +_size_of_stack EQU 0x400 +_size_of_heap EQU 0x200 + +;; Stack configuration. + AREA STACK, NOINIT, READWRITE, ALIGN=3 +_start_of_stack SPACE _size_of_stack +_end_of_stack + +;; Heap configuration. + AREA HEAP, NOINIT, READWRITE, ALIGN=3 +_start_of_heap SPACE _size_of_heap +_end_of_heap + + PRESERVE8 + THUMB + +;; Define the empty vectors table. + AREA RESET, DATA, READONLY +_vectors_table + ;; Generic interrupts offset. + DCD _end_of_stack ; Initial stack pointer value. + DCD reset_handler ; Reset. + DCD 0 ; NMI. + DCD 0 ; Hard fault. + DCD 0 ; Memory management fault. + DCD 0 ; Bus fault. + DCD 0 ; Usage fault. + DCD 0 ; Reserved. + DCD 0 ; Reserved. + DCD 0 ; Reserved. + DCD 0 ; Reserved. + DCD 0 ; SVC. + DCD 0 ; Debug monitor. + DCD 0 ; Reserved. + DCD 0 ; PendSV. + DCD 0 ; SysTick. + ;; External interrupts offset. + DCD 0 ; Window WatchDog. + DCD 0 ; PVD through EXTI Line detection. + DCD 0 ; Tamper and TimeStamps through the EXTI line. + DCD 0 ; RTC Wakeup through the EXTI line. + DCD 0 ; FLASH. + DCD 0 ; RCC. + DCD 0 ; EXTI Line0. + DCD 0 ; EXTI Line1. + DCD 0 ; EXTI Line2. + DCD 0 ; EXTI Line3. + DCD 0 ; EXTI Line4. + DCD 0 ; DMA1 Stream 0. + DCD 0 ; DMA1 Stream 1. + DCD 0 ; DMA1 Stream 2. + DCD 0 ; DMA1 Stream 3. + DCD 0 ; DMA1 Stream 4. + DCD 0 ; DMA1 Stream 5. + DCD 0 ; DMA1 Stream 6. + DCD 0 ; ADC1, ADC2 and ADC3s. + DCD 0 ; CAN1 TX. + DCD 0 ; CAN1 RX0. + DCD 0 ; CAN1 RX1. + DCD 0 ; CAN1 SCE. + DCD 0 ; External Line[9:5]s. + DCD 0 ; TIM1 Break and TIM9. + DCD 0 ; TIM1 Update and TIM10. + DCD 0 ; TIM1 Trigger and Commutation and TIM11. + DCD 0 ; TIM1 Capture Compare. + DCD 0 ; TIM2. + DCD 0 ; TIM3. + DCD 0 ; TIM4. + DCD 0 ; I2C1 Event. + DCD 0 ; I2C1 Error. + DCD 0 ; I2C2 Event. + DCD 0 ; I2C2 Error. + DCD 0 ; SPI1. + DCD 0 ; SPI2. + DCD 0 ; USART1. + DCD 0 ; USART2. + DCD 0 ; USART3. + DCD 0 ; External Line[15:10]s. + DCD 0 ; RTC Alarm (A and B) through EXTI Line. + DCD 0 ; USB OTG FS Wakeup through EXTI line. + DCD 0 ; TIM8 Break and TIM12. + DCD 0 ; TIM8 Update and TIM13. + DCD 0 ; TIM8 Trigger and Commutation and TIM14. + DCD 0 ; TIM8 Capture Compare. + DCD 0 ; DMA1 Stream7. + DCD 0 ; FMC. + DCD 0 ; SDIO. + DCD 0 ; TIM5. + DCD 0 ; SPI3. + DCD 0 ; UART4. + DCD 0 ; UART5. + DCD 0 ; TIM6 and DAC1&2 underrun errors. + DCD 0 ; TIM7. + DCD 0 ; DMA2 Stream 0. + DCD 0 ; DMA2 Stream 1. + DCD 0 ; DMA2 Stream 2. + DCD 0 ; DMA2 Stream 3. + DCD 0 ; DMA2 Stream 4. + DCD 0 ; Ethernet. + DCD 0 ; Ethernet Wakeup through EXTI line. + DCD 0 ; CAN2 TX. + DCD 0 ; CAN2 RX0. + DCD 0 ; CAN2 RX1. + DCD 0 ; CAN2 SCE. + DCD 0 ; USB OTG FS. + DCD 0 ; DMA2 Stream 5. + DCD 0 ; DMA2 Stream 6. + DCD 0 ; DMA2 Stream 7. + DCD 0 ; USART6. + DCD 0 ; I2C3 event. + DCD 0 ; I2C3 error. + DCD 0 ; USB OTG HS End Point 1 Out. + DCD 0 ; USB OTG HS End Point 1 In. + DCD 0 ; USB OTG HS Wakeup through EXTI. + DCD 0 ; USB OTG HS. + DCD 0 ; DCMI. + DCD 0 ; Reserved. + DCD 0 ; Hash and Rng. + DCD 0 ; FPU. +_end_of_vectors_table + +_size_of_vectors_table EQU _end_of_vectors_table - _vectors_table + + AREA |.text|, CODE, READONLY +;; Reset handler. +reset_handler PROC + EXPORT reset_handler [WEAK] + IMPORT main + LDR R0, =main + BX R0 + ENDP + ALIGN + + END diff --git a/examples/baremetal/stm32f4discovery/blueblink/main.c b/examples/baremetal/stm32f4discovery/blueblink/main.c new file mode 100644 index 00000000..ef69fc8e --- /dev/null +++ b/examples/baremetal/stm32f4discovery/blueblink/main.c @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "gpio.h" + +#include + +static void some_delay(uint32_t counts) +{ + for (uint32_t index = 0u; index < counts; ++index) + __asm("nop"); +} + +int main(void) +{ + gpio_init_blue_led(); + + while (1) { + gpio_toggle_blue_led(); + some_delay(100000u); + } +} diff --git a/examples/baremetal/stm32f4discovery/blueblink/system.h b/examples/baremetal/stm32f4discovery/blueblink/system.h new file mode 100644 index 00000000..6e719002 --- /dev/null +++ b/examples/baremetal/stm32f4discovery/blueblink/system.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef SYSTEM_H +#define SYSTEM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define __IO volatile + +// General purpose input/output registers map. +struct gpio_regs_map { + __IO uint32_t MODER; + __IO uint32_t OTYPER; + __IO uint32_t OSPEEDR; + __IO uint32_t PUPDR; + __IO uint32_t IDR; + __IO uint32_t ODR; + __IO uint32_t BSRR; + __IO uint32_t LCKR; + __IO uint32_t AFR[2u]; +}; + +// Reset and clock control registers map. +struct rcc_regs_map { + __IO uint32_t CR; + __IO uint32_t PLLCFGR; + __IO uint32_t CFGR; + __IO uint32_t CIR; + __IO uint32_t AHB1RSTR; + __IO uint32_t AHB2RSTR; + __IO uint32_t AHB3RSTR; + uint32_t RESERVED0; + __IO uint32_t APB1RSTR; + __IO uint32_t APB2RSTR; + uint32_t RESERVED1[2u]; + __IO uint32_t AHB1ENR; + __IO uint32_t AHB2ENR; + __IO uint32_t AHB3ENR; + uint32_t RESERVED2; + __IO uint32_t APB1ENR; + __IO uint32_t APB2ENR; + uint32_t RESERVED3[2u]; + __IO uint32_t AHB1LPENR; + __IO uint32_t AHB2LPENR; + __IO uint32_t AHB3LPENR; + uint32_t RESERVED4; + __IO uint32_t APB1LPENR; + __IO uint32_t APB2LPENR; + uint32_t RESERVED5[2u]; + __IO uint32_t BDCR; + __IO uint32_t CSR; + uint32_t RESERVED6[2u]; + __IO uint32_t SSCGR; + __IO uint32_t PLLI2SCFGR; +}; + +#define PERIPH_ADDRESS (0x40000000u) + +#define APB2PERIPH_ADDRESS (PERIPH_ADDRESS + 0x00010000u) +#define AHB1PERIPH_ADDRESS (PERIPH_ADDRESS + 0x00020000u) + +// APB2 peripherals. +#define SYSCFG_REGS_ADDRESS (APB2PERIPH_ADDRESS + 0x3800u) +#define EXTI_REGS_ADDRESS (APB2PERIPH_ADDRESS + 0x3C00u) + +// AHB1 peripherals. +#define GPIOD_REGS_ADDRESS (AHB1PERIPH_ADDRESS + 0x0C00u) +#define RCC_REGS_ADDRESS (AHB1PERIPH_ADDRESS + 0x3800u) + +#define GPIOD_REGS_MAP ((struct gpio_regs_map *)GPIOD_REGS_ADDRESS) +#define RCC_REGS_MAP ((struct rcc_regs_map *)RCC_REGS_ADDRESS) + +#ifdef __cplusplus +} +#endif + +#endif // SYSTEM_H diff --git a/examples/baremetal/stm32f4discovery/stm32f4discovery.qbs b/examples/baremetal/stm32f4discovery/stm32f4discovery.qbs new file mode 100644 index 00000000..a402848c --- /dev/null +++ b/examples/baremetal/stm32f4discovery/stm32f4discovery.qbs @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 + +Project { + name: "Examples for stm32f4discovery board" + references: [ + "blueblink/blueblink.qbs" + ] +} diff --git a/examples/baremetal/stm8s103f3/redblink/README.md b/examples/baremetal/stm8s103f3/redblink/README.md new file mode 100644 index 00000000..2c76e918 --- /dev/null +++ b/examples/baremetal/stm8s103f3/redblink/README.md @@ -0,0 +1,8 @@ +This example demonstrates how to build a bare-metal application using +different STM8 toolchains. It is designed for the STM8 target board +(based on STMicroelectronics stm8s103f3 MCU) and simply flashes the +red LED on the board. + +The following toolchains are supported: + + * IAR Embedded Workbench diff --git a/examples/baremetal/stm8s103f3/redblink/gpio.c b/examples/baremetal/stm8s103f3/redblink/gpio.c new file mode 100644 index 00000000..cc68c732 --- /dev/null +++ b/examples/baremetal/stm8s103f3/redblink/gpio.c @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "gpio.h" +#include "system.h" + +// A LED is connected to the pin #5 of Port B. +#define GPIO_RED_LED_PIN_POS (0x20u) + +void gpio_init_red_led(void) +{ + PB_ODR = 0x00; // Turn off all pins. + PB_DDR = GPIO_RED_LED_PIN_POS; // Configure Pin as output. + PB_CR1 = GPIO_RED_LED_PIN_POS; // Set Pin to Push-Pull. + PB_CR2 = GPIO_RED_LED_PIN_POS; // Set Pin to Push-Pull. +} + +void gpio_toggle_red_led(void) +{ + PB_ODR ^= GPIO_RED_LED_PIN_POS; +} diff --git a/examples/baremetal/stm8s103f3/redblink/gpio.h b/examples/baremetal/stm8s103f3/redblink/gpio.h new file mode 100644 index 00000000..246eab86 --- /dev/null +++ b/examples/baremetal/stm8s103f3/redblink/gpio.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef GPIO_H +#define GPIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +void gpio_init_red_led(void); +void gpio_toggle_red_led(void); + +#ifdef __cplusplus +} +#endif + +#endif // GPIO_H diff --git a/examples/baremetal/stm8s103f3/redblink/main.c b/examples/baremetal/stm8s103f3/redblink/main.c new file mode 100644 index 00000000..63c1c0ae --- /dev/null +++ b/examples/baremetal/stm8s103f3/redblink/main.c @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "gpio.h" +#include "system.h" + +static void some_delay(unsigned long counts) +{ + unsigned long index = 0u; + for (index = 0u; index < counts; ++index) + system_nop(); +} + +int main(void) +{ + gpio_init_red_led(); + + while (1) { + gpio_toggle_red_led(); + some_delay(20000u); + } +} diff --git a/examples/baremetal/stm8s103f3/redblink/redblink.qbs b/examples/baremetal/stm8s103f3/redblink/redblink.qbs new file mode 100644 index 00000000..197bae8a --- /dev/null +++ b/examples/baremetal/stm8s103f3/redblink/redblink.qbs @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 { + condition: { + if (!qbs.architecture.contains("stm8")) + return false; + return qbs.toolchain.contains("iar") + || qbs.toolchain.contains("sdcc") + } + name: "stm8s103f3-redblink" + cpp.positionIndependentCode: false + + // + // IAR-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("iar") + cpp.commonCompilerFlags: ["-e"] + cpp.driverLinkerFlags: [ + "--config_def", "_CSTACK_SIZE=0x100", + "--config_def", "_HEAP_SIZE=0x100", + ] + } + + Group { + condition: qbs.toolchain.contains("iar") + name: "IAR" + prefix: "iar/" + Group { + name: "Linker Script" + prefix: cpp.toolchainInstallPath + "/../config/" + fileTags: ["linkerscript"] + files: ["lnkstm8s103f3.icf"] + } + } + + // + // SDCC-specific properties and sources. + // + + Properties { + condition: qbs.toolchain.contains("sdcc") + } + + // + // Common code. + // + + Group { + name: "Gpio" + files: ["gpio.c", "gpio.h"] + } + + Group { + name: "System" + files: ["system.h"] + } + + files: ["main.c"] +} diff --git a/examples/baremetal/stm8s103f3/redblink/system.h b/examples/baremetal/stm8s103f3/redblink/system.h new file mode 100644 index 00000000..463123ce --- /dev/null +++ b/examples/baremetal/stm8s103f3/redblink/system.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +#ifndef SYSTEM_H +#define SYSTEM_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Define required registers of Port B (where the LED is connected): +// * PB_ODR - Output Data Register. +// * PB_DDR - Data Direction Register. +// * PB_CR1 - Control Register #1. +// * PB_CR2 - Control Register #2. + +#if defined(__ICCSTM8__) +# define system_nop() __asm("nop") +__near __no_init volatile unsigned char PB_ODR @ 0x5005; +__near __no_init volatile unsigned char PB_DDR @ 0x5007; +__near __no_init volatile unsigned char PB_CR1 @ 0x5008; +__near __no_init volatile unsigned char PB_CR2 @ 0x5009; +#elif defined (__SDCC_stm8) +# define system_nop() __asm nop __endasm +#define PB_ODR *(volatile unsigned char *)0x5005 +#define PB_DDR *(volatile unsigned char *)0x5007 +#define PB_CR1 *(volatile unsigned char *)0x5008 +#define PB_CR2 *(volatile unsigned char *)0x5009 +#else +#error "Unsupported toolchain" +#endif + +#ifdef __cplusplus +} +#endif + +#endif // SYSTEM_H diff --git a/examples/baremetal/stm8s103f3/stm8s103f3.qbs b/examples/baremetal/stm8s103f3/stm8s103f3.qbs new file mode 100644 index 00000000..922c4c1f --- /dev/null +++ b/examples/baremetal/stm8s103f3/stm8s103f3.qbs @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 + +Project { + name: "Examples for stm8s103f3 board" + references: [ + "redblink/redblink.qbs" + ] +} diff --git a/examples/capnproto/addressbook_cpp/addressbook.capnp b/examples/capnproto/addressbook_cpp/addressbook.capnp new file mode 100644 index 00000000..1a6c6093 --- /dev/null +++ b/examples/capnproto/addressbook_cpp/addressbook.capnp @@ -0,0 +1,55 @@ +# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors +# Licensed under the MIT License: +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +@0x9eb32e19f86ee174; + +using Cxx = import "/capnp/c++.capnp"; +$Cxx.namespace("addressbook"); + +struct Person { + id @0 :UInt32; + name @1 :Text; + email @2 :Text; + phones @3 :List(PhoneNumber); + + struct PhoneNumber { + number @0 :Text; + type @1 :Type; + + enum Type { + mobile @0; + home @1; + work @2; + } + } + + employment :union { + unemployed @4 :Void; + employer @5 :Text; + school @6 :Text; + selfEmployed @7 :Void; + # We assume that a person is only one of these. + } +} + +struct AddressBook { + people @0 :List(Person); +} diff --git a/examples/capnproto/addressbook_cpp/addressbook.cpp b/examples/capnproto/addressbook_cpp/addressbook.cpp new file mode 100644 index 00000000..b2bece94 --- /dev/null +++ b/examples/capnproto/addressbook_cpp/addressbook.cpp @@ -0,0 +1,288 @@ +// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors +// Licensed under the MIT License: +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// This sample code appears in the documentation for the C++ implementation. +// +// If Cap'n Proto is installed, build the sample like: +// capnp compile -oc++ addressbook.capnp +// c++ -std=c++14 -Wall addressbook.c++ addressbook.capnp.c++ `pkg-config --cflags --libs capnp` -o addressbook +// +// If Cap'n Proto is not installed, but the source is located at $SRC and has been +// compiled in $BUILD (often both are simply ".." from here), you can do: +// $BUILD/capnp compile -I$SRC/src -o$BUILD/capnpc-c++ addressbook.capnp +// c++ -std=c++14 -Wall addressbook.c++ addressbook.capnp.c++ -I$SRC/src -L$BUILD/.libs -lcapnp -lkj -o addressbook +// +// Run like: +// ./addressbook write | ./addressbook read +// Use "dwrite" and "dread" to use dynamic code instead. + +// TODO(test): Needs cleanup. + +#include "addressbook.capnp.h" +#include +#include +#include + +using addressbook::Person; +using addressbook::AddressBook; + +void writeAddressBook(int fd) { + ::capnp::MallocMessageBuilder message; + + AddressBook::Builder addressBook = message.initRoot(); + ::capnp::List::Builder people = addressBook.initPeople(2); + + Person::Builder alice = people[0]; + alice.setId(123); + alice.setName("Alice"); + alice.setEmail("alice@example.com"); + // Type shown for explanation purposes; normally you'd use auto. + ::capnp::List::Builder alicePhones = + alice.initPhones(1); + alicePhones[0].setNumber("555-1212"); + alicePhones[0].setType(Person::PhoneNumber::Type::MOBILE); + alice.getEmployment().setSchool("MIT"); + + Person::Builder bob = people[1]; + bob.setId(456); + bob.setName("Bob"); + bob.setEmail("bob@example.com"); + auto bobPhones = bob.initPhones(2); + bobPhones[0].setNumber("555-4567"); + bobPhones[0].setType(Person::PhoneNumber::Type::HOME); + bobPhones[1].setNumber("555-7654"); + bobPhones[1].setType(Person::PhoneNumber::Type::WORK); + bob.getEmployment().setUnemployed(); + + writePackedMessageToFd(fd, message); +} + +void printAddressBook(int fd) { + ::capnp::PackedFdMessageReader message(fd); + + AddressBook::Reader addressBook = message.getRoot(); + + for (Person::Reader person : addressBook.getPeople()) { + std::cout << person.getName().cStr() << ": " + << person.getEmail().cStr() << std::endl; + for (Person::PhoneNumber::Reader phone: person.getPhones()) { + const char* typeName = "UNKNOWN"; + switch (phone.getType()) { + case Person::PhoneNumber::Type::MOBILE: typeName = "mobile"; break; + case Person::PhoneNumber::Type::HOME: typeName = "home"; break; + case Person::PhoneNumber::Type::WORK: typeName = "work"; break; + } + std::cout << " " << typeName << " phone: " + << phone.getNumber().cStr() << std::endl; + } + Person::Employment::Reader employment = person.getEmployment(); + switch (employment.which()) { + case Person::Employment::UNEMPLOYED: + std::cout << " unemployed" << std::endl; + break; + case Person::Employment::EMPLOYER: + std::cout << " employer: " + << employment.getEmployer().cStr() << std::endl; + break; + case Person::Employment::SCHOOL: + std::cout << " student at: " + << employment.getSchool().cStr() << std::endl; + break; + case Person::Employment::SELF_EMPLOYED: + std::cout << " self-employed" << std::endl; + break; + } + } +} + +#if !CAPNP_LITE + +#include "addressbook.capnp.h" +#include +#include +#include +#include +#include + +using ::capnp::DynamicValue; +using ::capnp::DynamicStruct; +using ::capnp::DynamicEnum; +using ::capnp::DynamicList; +using ::capnp::List; +using ::capnp::Schema; +using ::capnp::StructSchema; +using ::capnp::EnumSchema; + +using ::capnp::Void; +using ::capnp::Text; +using ::capnp::MallocMessageBuilder; +using ::capnp::PackedFdMessageReader; + +void dynamicWriteAddressBook(int fd, StructSchema schema) { + // Write a message using the dynamic API to set each + // field by text name. This isn't something you'd + // normally want to do; it's just for illustration. + + MallocMessageBuilder message; + + // Types shown for explanation purposes; normally you'd + // use auto. + DynamicStruct::Builder addressBook = + message.initRoot(schema); + + DynamicList::Builder people = + addressBook.init("people", 2).as(); + + DynamicStruct::Builder alice = + people[0].as(); + alice.set("id", 123); + alice.set("name", "Alice"); + alice.set("email", "alice@example.com"); + auto alicePhones = alice.init("phones", 1).as(); + auto phone0 = alicePhones[0].as(); + phone0.set("number", "555-1212"); + phone0.set("type", "mobile"); + alice.get("employment").as() + .set("school", "MIT"); + + auto bob = people[1].as(); + bob.set("id", 456); + bob.set("name", "Bob"); + bob.set("email", "bob@example.com"); + + // Some magic: We can convert a dynamic sub-value back to + // the native type with as()! + List::Builder bobPhones = + bob.init("phones", 2).as>(); + bobPhones[0].setNumber("555-4567"); + bobPhones[0].setType(Person::PhoneNumber::Type::HOME); + bobPhones[1].setNumber("555-7654"); + bobPhones[1].setType(Person::PhoneNumber::Type::WORK); + bob.get("employment").as() + .set("unemployed", ::capnp::VOID); + + writePackedMessageToFd(fd, message); +} + +void dynamicPrintValue(DynamicValue::Reader value) { + // Print an arbitrary message via the dynamic API by + // iterating over the schema. Look at the handling + // of STRUCT in particular. + + switch (value.getType()) { + case DynamicValue::VOID: + std::cout << ""; + break; + case DynamicValue::BOOL: + std::cout << (value.as() ? "true" : "false"); + break; + case DynamicValue::INT: + std::cout << value.as(); + break; + case DynamicValue::UINT: + std::cout << value.as(); + break; + case DynamicValue::FLOAT: + std::cout << value.as(); + break; + case DynamicValue::TEXT: + std::cout << '\"' << value.as().cStr() << '\"'; + break; + case DynamicValue::LIST: { + std::cout << "["; + bool first = true; + for (auto element: value.as()) { + if (first) { + first = false; + } else { + std::cout << ", "; + } + dynamicPrintValue(element); + } + std::cout << "]"; + break; + } + case DynamicValue::ENUM: { + auto enumValue = value.as(); + KJ_IF_MAYBE(enumerant, enumValue.getEnumerant()) { + std::cout << + enumerant->getProto().getName().cStr(); + } else { + // Unknown enum value; output raw number. + std::cout << enumValue.getRaw(); + } + break; + } + case DynamicValue::STRUCT: { + std::cout << "("; + auto structValue = value.as(); + bool first = true; + for (auto field: structValue.getSchema().getFields()) { + if (!structValue.has(field)) continue; + if (first) { + first = false; + } else { + std::cout << ", "; + } + std::cout << field.getProto().getName().cStr() + << " = "; + dynamicPrintValue(structValue.get(field)); + } + std::cout << ")"; + break; + } + default: + // There are other types, we aren't handling them. + std::cout << "?"; + break; + } +} + +void dynamicPrintMessage(int fd, StructSchema schema) { + PackedFdMessageReader message(fd); + dynamicPrintValue(message.getRoot(schema)); + std::cout << std::endl; +} + +#endif // !CAPNP_LITE + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::cerr << "Missing arg." << std::endl; + return 1; + } else if (strcmp(argv[1], "write") == 0) { + writeAddressBook(1); + } else if (strcmp(argv[1], "read") == 0) { + printAddressBook(0); +#if !CAPNP_LITE + } else if (strcmp(argv[1], "dwrite") == 0) { + StructSchema schema = Schema::from(); + dynamicWriteAddressBook(1, schema); + } else if (strcmp(argv[1], "dread") == 0) { + StructSchema schema = Schema::from(); + dynamicPrintMessage(0, schema); +#endif + } else { + std::cerr << "Invalid arg: " << argv[1] << std::endl; + return 1; + } + return 0; +} diff --git a/examples/capnproto/addressbook_cpp/addressbook_cpp.qbs b/examples/capnproto/addressbook_cpp/addressbook_cpp.qbs new file mode 100644 index 00000000..33f78d5a --- /dev/null +++ b/examples/capnproto/addressbook_cpp/addressbook_cpp.qbs @@ -0,0 +1,11 @@ +CppApplication { + Depends { name: "capnproto.cpp"; required: false } + condition: capnproto.cpp.present && qbs.targetPlatform === qbs.hostPlatform + consoleApplication: true + cpp.minimumMacosVersion: "10.8" + + files: [ + "addressbook.capnp", + "addressbook.cpp" + ] +} diff --git a/examples/capnproto/calculator_cpp/calculator-client.cpp b/examples/capnproto/calculator_cpp/calculator-client.cpp new file mode 100644 index 00000000..5d845292 --- /dev/null +++ b/examples/capnproto/calculator_cpp/calculator-client.cpp @@ -0,0 +1,367 @@ +// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors +// Licensed under the MIT License: +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "calculator.capnp.h" +#include +#include +#include +#include + +class PowerFunction final: public Calculator::Function::Server { + // An implementation of the Function interface wrapping pow(). Note that + // we're implementing this on the client side and will pass a reference to + // the server. The server will then be able to make calls back to the client. + +public: + kj::Promise call(CallContext context) { + auto params = context.getParams().getParams(); + KJ_REQUIRE(params.size() == 2, "Wrong number of parameters."); + context.getResults().setValue(pow(params[0], params[1])); + return kj::READY_NOW; + } +}; + +int main(int argc, const char* argv[]) { + if (argc != 2) { + std::cerr << "usage: " << argv[0] << " HOST:PORT\n" + "Connects to the Calculator server at the given address and " + "does some RPCs." << std::endl; + return 1; + } + + capnp::EzRpcClient client(argv[1]); + Calculator::Client calculator = client.getMain(); + + // Keep an eye on `waitScope`. Whenever you see it used is a place where we + // stop and wait for the server to respond. If a line of code does not use + // `waitScope`, then it does not block! + auto& waitScope = client.getWaitScope(); + + { + // Make a request that just evaluates the literal value 123. + // + // What's interesting here is that evaluate() returns a "Value", which is + // another interface and therefore points back to an object living on the + // server. We then have to call read() on that object to read it. + // However, even though we are making two RPC's, this block executes in + // *one* network round trip because of promise pipelining: we do not wait + // for the first call to complete before we send the second call to the + // server. + + std::cout << "Evaluating a literal... "; + std::cout.flush(); + + // Set up the request. + auto request = calculator.evaluateRequest(); + request.getExpression().setLiteral(123); + + // Send it, which returns a promise for the result (without blocking). + auto evalPromise = request.send(); + + // Using the promise, create a pipelined request to call read() on the + // returned object, and then send that. + auto readPromise = evalPromise.getValue().readRequest().send(); + + // Now that we've sent all the requests, wait for the response. Until this + // point, we haven't waited at all! + auto response = readPromise.wait(waitScope); + KJ_ASSERT(response.getValue() == 123); + + std::cout << "PASS" << std::endl; + } + + { + // Make a request to evaluate 123 + 45 - 67. + // + // The Calculator interface requires that we first call getOperator() to + // get the addition and subtraction functions, then call evaluate() to use + // them. But, once again, we can get both functions, call evaluate(), and + // then read() the result -- four RPCs -- in the time of *one* network + // round trip, because of promise pipelining. + + std::cout << "Using add and subtract... "; + std::cout.flush(); + + Calculator::Function::Client add = nullptr; + Calculator::Function::Client subtract = nullptr; + + { + // Get the "add" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::ADD); + add = request.send().getFunc(); + } + + { + // Get the "subtract" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::SUBTRACT); + subtract = request.send().getFunc(); + } + + // Build the request to evaluate 123 + 45 - 67. + auto request = calculator.evaluateRequest(); + + auto subtractCall = request.getExpression().initCall(); + subtractCall.setFunction(subtract); + auto subtractParams = subtractCall.initParams(2); + subtractParams[1].setLiteral(67); + + auto addCall = subtractParams[0].initCall(); + addCall.setFunction(add); + auto addParams = addCall.initParams(2); + addParams[0].setLiteral(123); + addParams[1].setLiteral(45); + + // Send the evaluate() request, read() the result, and wait for read() to + // finish. + auto evalPromise = request.send(); + auto readPromise = evalPromise.getValue().readRequest().send(); + + auto response = readPromise.wait(waitScope); + KJ_ASSERT(response.getValue() == 101); + + std::cout << "PASS" << std::endl; + } + + { + // Make a request to evaluate 4 * 6, then use the result in two more + // requests that add 3 and 5. + // + // Since evaluate() returns its result wrapped in a `Value`, we can pass + // that `Value` back to the server in subsequent requests before the first + // `evaluate()` has actually returned. Thus, this example again does only + // one network round trip. + + std::cout << "Pipelining eval() calls... "; + std::cout.flush(); + + Calculator::Function::Client add = nullptr; + Calculator::Function::Client multiply = nullptr; + + { + // Get the "add" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::ADD); + add = request.send().getFunc(); + } + + { + // Get the "multiply" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::MULTIPLY); + multiply = request.send().getFunc(); + } + + // Build the request to evaluate 4 * 6 + auto request = calculator.evaluateRequest(); + + auto multiplyCall = request.getExpression().initCall(); + multiplyCall.setFunction(multiply); + auto multiplyParams = multiplyCall.initParams(2); + multiplyParams[0].setLiteral(4); + multiplyParams[1].setLiteral(6); + + auto multiplyResult = request.send().getValue(); + + // Use the result in two calls that add 3 and add 5. + + auto add3Request = calculator.evaluateRequest(); + auto add3Call = add3Request.getExpression().initCall(); + add3Call.setFunction(add); + auto add3Params = add3Call.initParams(2); + add3Params[0].setPreviousResult(multiplyResult); + add3Params[1].setLiteral(3); + auto add3Promise = add3Request.send().getValue().readRequest().send(); + + auto add5Request = calculator.evaluateRequest(); + auto add5Call = add5Request.getExpression().initCall(); + add5Call.setFunction(add); + auto add5Params = add5Call.initParams(2); + add5Params[0].setPreviousResult(multiplyResult); + add5Params[1].setLiteral(5); + auto add5Promise = add5Request.send().getValue().readRequest().send(); + + // Now wait for the results. + KJ_ASSERT(add3Promise.wait(waitScope).getValue() == 27); + KJ_ASSERT(add5Promise.wait(waitScope).getValue() == 29); + + std::cout << "PASS" << std::endl; + } + + { + // Our calculator interface supports defining functions. Here we use it + // to define two functions and then make calls to them as follows: + // + // f(x, y) = x * 100 + y + // g(x) = f(x, x + 1) * 2; + // f(12, 34) + // g(21) + // + // Once again, the whole thing takes only one network round trip. + + std::cout << "Defining functions... "; + std::cout.flush(); + + Calculator::Function::Client add = nullptr; + Calculator::Function::Client multiply = nullptr; + Calculator::Function::Client f = nullptr; + Calculator::Function::Client g = nullptr; + + { + // Get the "add" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::ADD); + add = request.send().getFunc(); + } + + { + // Get the "multiply" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::MULTIPLY); + multiply = request.send().getFunc(); + } + + { + // Define f. + auto request = calculator.defFunctionRequest(); + request.setParamCount(2); + + { + // Build the function body. + auto addCall = request.getBody().initCall(); + addCall.setFunction(add); + auto addParams = addCall.initParams(2); + addParams[1].setParameter(1); // y + + auto multiplyCall = addParams[0].initCall(); + multiplyCall.setFunction(multiply); + auto multiplyParams = multiplyCall.initParams(2); + multiplyParams[0].setParameter(0); // x + multiplyParams[1].setLiteral(100); + } + + f = request.send().getFunc(); + } + + { + // Define g. + auto request = calculator.defFunctionRequest(); + request.setParamCount(1); + + { + // Build the function body. + auto multiplyCall = request.getBody().initCall(); + multiplyCall.setFunction(multiply); + auto multiplyParams = multiplyCall.initParams(2); + multiplyParams[1].setLiteral(2); + + auto fCall = multiplyParams[0].initCall(); + fCall.setFunction(f); + auto fParams = fCall.initParams(2); + fParams[0].setParameter(0); + + auto addCall = fParams[1].initCall(); + addCall.setFunction(add); + auto addParams = addCall.initParams(2); + addParams[0].setParameter(0); + addParams[1].setLiteral(1); + } + + g = request.send().getFunc(); + } + + // OK, we've defined all our functions. Now create our eval requests. + + // f(12, 34) + auto fEvalRequest = calculator.evaluateRequest(); + auto fCall = fEvalRequest.initExpression().initCall(); + fCall.setFunction(f); + auto fParams = fCall.initParams(2); + fParams[0].setLiteral(12); + fParams[1].setLiteral(34); + auto fEvalPromise = fEvalRequest.send().getValue().readRequest().send(); + + // g(21) + auto gEvalRequest = calculator.evaluateRequest(); + auto gCall = gEvalRequest.initExpression().initCall(); + gCall.setFunction(g); + gCall.initParams(1)[0].setLiteral(21); + auto gEvalPromise = gEvalRequest.send().getValue().readRequest().send(); + + // Wait for the results. + KJ_ASSERT(fEvalPromise.wait(waitScope).getValue() == 1234); + KJ_ASSERT(gEvalPromise.wait(waitScope).getValue() == 4244); + + std::cout << "PASS" << std::endl; + } + + { + // Make a request that will call back to a function defined locally. + // + // Specifically, we will compute 2^(4 + 5). However, exponent is not + // defined by the Calculator server. So, we'll implement the Function + // interface locally and pass it to the server for it to use when + // evaluating the expression. + // + // This example requires two network round trips to complete, because the + // server calls back to the client once before finishing. In this + // particular case, this could potentially be optimized by using a tail + // call on the server side -- see CallContext::tailCall(). However, to + // keep the example simpler, we haven't implemented this optimization in + // the sample server. + + std::cout << "Using a callback... "; + std::cout.flush(); + + Calculator::Function::Client add = nullptr; + + { + // Get the "add" function from the server. + auto request = calculator.getOperatorRequest(); + request.setOp(Calculator::Operator::ADD); + add = request.send().getFunc(); + } + + // Build the eval request for 2^(4+5). + auto request = calculator.evaluateRequest(); + + auto powCall = request.getExpression().initCall(); + powCall.setFunction(kj::heap()); + auto powParams = powCall.initParams(2); + powParams[0].setLiteral(2); + + auto addCall = powParams[1].initCall(); + addCall.setFunction(add); + auto addParams = addCall.initParams(2); + addParams[0].setLiteral(4); + addParams[1].setLiteral(5); + + // Send the request and wait. + auto response = request.send().getValue().readRequest() + .send().wait(waitScope); + KJ_ASSERT(response.getValue() == 512); + + std::cout << "PASS" << std::endl; + } + + return 0; +} diff --git a/examples/capnproto/calculator_cpp/calculator-server.cpp b/examples/capnproto/calculator_cpp/calculator-server.cpp new file mode 100644 index 00000000..c2593be3 --- /dev/null +++ b/examples/capnproto/calculator_cpp/calculator-server.cpp @@ -0,0 +1,215 @@ +// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors +// Licensed under the MIT License: +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "calculator.capnp.h" +#include +#include +#include +#include + +typedef unsigned int uint; + +kj::Promise readValue(Calculator::Value::Client value) { + // Helper function to asynchronously call read() on a Calculator::Value and + // return a promise for the result. (In the future, the generated code might + // include something like this automatically.) + + return value.readRequest().send() + .then([](capnp::Response result) { + return result.getValue(); + }); +} + +kj::Promise evaluateImpl( + Calculator::Expression::Reader expression, + capnp::List::Reader params = capnp::List::Reader()) { + // Implementation of CalculatorImpl::evaluate(), also shared by + // FunctionImpl::call(). In the latter case, `params` are the parameter + // values passed to the function; in the former case, `params` is just an + // empty list. + + switch (expression.which()) { + case Calculator::Expression::LITERAL: + return expression.getLiteral(); + + case Calculator::Expression::PREVIOUS_RESULT: + return readValue(expression.getPreviousResult()); + + case Calculator::Expression::PARAMETER: { + KJ_REQUIRE(expression.getParameter() < params.size(), + "Parameter index out-of-range."); + return params[expression.getParameter()]; + } + + case Calculator::Expression::CALL: { + auto call = expression.getCall(); + auto func = call.getFunction(); + + // Evaluate each parameter. + kj::Array> paramPromises = + KJ_MAP(param, call.getParams()) { + return evaluateImpl(param, params); + }; + + // Join the array of promises into a promise for an array. + kj::Promise> joinedParams = + kj::joinPromises(kj::mv(paramPromises)); + + // When the parameters are complete, call the function. + return joinedParams.then([KJ_CPCAP(func)](kj::Array&& paramValues) mutable { + auto request = func.callRequest(); + request.setParams(paramValues); + return request.send().then( + [](capnp::Response&& result) { + return result.getValue(); + }); + }); + } + + default: + // Throw an exception. + KJ_FAIL_REQUIRE("Unknown expression type."); + } +} + +class ValueImpl final: public Calculator::Value::Server { + // Simple implementation of the Calculator.Value Cap'n Proto interface. + +public: + ValueImpl(double value): value(value) {} + + kj::Promise read(ReadContext context) { + context.getResults().setValue(value); + return kj::READY_NOW; + } + +private: + double value; +}; + +class FunctionImpl final: public Calculator::Function::Server { + // Implementation of the Calculator.Function Cap'n Proto interface, where the + // function is defined by a Calculator.Expression. + +public: + FunctionImpl(uint paramCount, Calculator::Expression::Reader body) + : paramCount(paramCount) { + this->body.setRoot(body); + } + + kj::Promise call(CallContext context) { + auto params = context.getParams().getParams(); + KJ_REQUIRE(params.size() == paramCount, "Wrong number of parameters."); + + return evaluateImpl(body.getRoot(), params) + .then([KJ_CPCAP(context)](double value) mutable { + context.getResults().setValue(value); + }); + } + +private: + uint paramCount; + // The function's arity. + + capnp::MallocMessageBuilder body; + // Stores a permanent copy of the function body. +}; + +class OperatorImpl final: public Calculator::Function::Server { + // Implementation of the Calculator.Function Cap'n Proto interface, wrapping + // basic binary arithmetic operators. + +public: + OperatorImpl(Calculator::Operator op): op(op) {} + + kj::Promise call(CallContext context) { + auto params = context.getParams().getParams(); + KJ_REQUIRE(params.size() == 2, "Wrong number of parameters."); + + double result; + switch (op) { + case Calculator::Operator::ADD: result = params[0] + params[1]; break; + case Calculator::Operator::SUBTRACT:result = params[0] - params[1]; break; + case Calculator::Operator::MULTIPLY:result = params[0] * params[1]; break; + case Calculator::Operator::DIVIDE: result = params[0] / params[1]; break; + default: + KJ_FAIL_REQUIRE("Unknown operator."); + } + + context.getResults().setValue(result); + return kj::READY_NOW; + } + +private: + Calculator::Operator op; +}; + +class CalculatorImpl final: public Calculator::Server { + // Implementation of the Calculator Cap'n Proto interface. + +public: + kj::Promise evaluate(EvaluateContext context) override { + return evaluateImpl(context.getParams().getExpression()) + .then([KJ_CPCAP(context)](double value) mutable { + context.getResults().setValue(kj::heap(value)); + }); + } + + kj::Promise defFunction(DefFunctionContext context) override { + auto params = context.getParams(); + context.getResults().setFunc(kj::heap( + params.getParamCount(), params.getBody())); + return kj::READY_NOW; + } + + kj::Promise getOperator(GetOperatorContext context) override { + context.getResults().setFunc(kj::heap( + context.getParams().getOp())); + return kj::READY_NOW; + } +}; + +int main(int argc, const char* argv[]) { + if (argc != 2) { + std::cerr << "usage: " << argv[0] << " ADDRESS[:PORT]\n" + "Runs the server bound to the given address/port.\n" + "ADDRESS may be '*' to bind to all local addresses.\n" + ":PORT may be omitted to choose a port automatically." << std::endl; + return 1; + } + + // Set up a server. + capnp::EzRpcServer server(kj::heap(), argv[1]); + + // Write the port number to stdout, in case it was chosen automatically. + auto& waitScope = server.getWaitScope(); + uint port = server.getPort().wait(waitScope); + if (port == 0) { + // The address format "unix:/path/to/socket" opens a unix domain socket, + // in which case the port will be zero. + std::cout << "Listening on Unix socket..." << std::endl; + } else { + std::cout << "Listening on port " << port << "..." << std::endl; + } + + // Run forever, accepting connections and handling requests. + kj::NEVER_DONE.wait(waitScope); +} diff --git a/examples/capnproto/calculator_cpp/calculator.capnp b/examples/capnproto/calculator_cpp/calculator.capnp new file mode 100644 index 00000000..adc8294e --- /dev/null +++ b/examples/capnproto/calculator_cpp/calculator.capnp @@ -0,0 +1,118 @@ +# Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors +# Licensed under the MIT License: +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +@0x85150b117366d14b; + +interface Calculator { + # A "simple" mathematical calculator, callable via RPC. + # + # But, to show off Cap'n Proto, we add some twists: + # + # - You can use the result from one call as the input to the next + # without a network round trip. To accomplish this, evaluate() + # returns a `Value` object wrapping the actual numeric value. + # This object may be used in a subsequent expression. With + # promise pipelining, the Value can actually be used before + # the evaluate() call that creates it returns! + # + # - You can define new functions, and then call them. This again + # shows off pipelining, but it also gives the client the + # opportunity to define a function on the client side and have + # the server call back to it. + # + # - The basic arithmetic operators are exposed as Functions, and + # you have to call getOperator() to obtain them from the server. + # This again demonstrates pipelining -- using getOperator() to + # get each operator and then using them in evaluate() still + # only takes one network round trip. + + evaluate @0 (expression :Expression) -> (value :Value); + # Evaluate the given expression and return the result. The + # result is returned wrapped in a Value interface so that you + # may pass it back to the server in a pipelined request. To + # actually get the numeric value, you must call read() on the + # Value -- but again, this can be pipelined so that it incurs + # no additional latency. + + struct Expression { + # A numeric expression. + + union { + literal @0 :Float64; + # A literal numeric value. + + previousResult @1 :Value; + # A value that was (or, will be) returned by a previous + # evaluate(). + + parameter @2 :UInt32; + # A parameter to the function (only valid in function bodies; + # see defFunction). + + call :group { + # Call a function on a list of parameters. + function @3 :Function; + params @4 :List(Expression); + } + } + } + + interface Value { + # Wraps a numeric value in an RPC object. This allows the value + # to be used in subsequent evaluate() requests without the client + # waiting for the evaluate() that returns the Value to finish. + + read @0 () -> (value :Float64); + # Read back the raw numeric value. + } + + defFunction @1 (paramCount :Int32, body :Expression) + -> (func :Function); + # Define a function that takes `paramCount` parameters and returns the + # evaluation of `body` after substituting these parameters. + + interface Function { + # An algebraic function. Can be called directly, or can be used inside + # an Expression. + # + # A client can create a Function that runs on the server side using + # `defFunction()` or `getOperator()`. Alternatively, a client can + # implement a Function on the client side and the server will call back + # to it. However, a function defined on the client side will require a + # network round trip whenever the server needs to call it, whereas + # functions defined on the server and then passed back to it are called + # locally. + + call @0 (params :List(Float64)) -> (value :Float64); + # Call the function on the given parameters. + } + + getOperator @2 (op :Operator) -> (func :Function); + # Get a Function representing an arithmetic operator, which can then be + # used in Expressions. + + enum Operator { + add @0; + subtract @1; + multiply @2; + divide @3; + } +} diff --git a/examples/capnproto/calculator_cpp/calculator_cpp.qbs b/examples/capnproto/calculator_cpp/calculator_cpp.qbs new file mode 100644 index 00000000..862a237c --- /dev/null +++ b/examples/capnproto/calculator_cpp/calculator_cpp.qbs @@ -0,0 +1,26 @@ +Project { + CppApplication { + Depends { name: "capnproto.cpp"; required: false } + name: "server" + condition: capnproto.cpp.present && qbs.targetPlatform === qbs.hostPlatform + consoleApplication: true + capnproto.cpp.useRpc: true + + files: [ + "calculator.capnp", + "calculator-server.cpp" + ] + } + CppApplication { + Depends { name: "capnproto.cpp"; required: false } + name: "client" + condition: capnproto.cpp.present && qbs.targetPlatform === qbs.hostPlatform + consoleApplication: true + capnproto.cpp.useRpc: true + + files: [ + "calculator.capnp", + "calculator-client.cpp" + ] + } +} diff --git a/examples/cocoa-application/CocoaApplication.xcodeproj/project.pbxproj b/examples/cocoa-application/CocoaApplication.xcodeproj/project.pbxproj new file mode 100644 index 00000000..a284a776 --- /dev/null +++ b/examples/cocoa-application/CocoaApplication.xcodeproj/project.pbxproj @@ -0,0 +1,309 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 14ABF7A71717761200140DA2 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14ABF7A61717761200140DA2 /* Cocoa.framework */; }; + 14ABF7B11717761200140DA2 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 14ABF7AF1717761200140DA2 /* InfoPlist.strings */; }; + 14ABF7B31717761200140DA2 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 14ABF7B21717761200140DA2 /* main.m */; }; + 14ABF7B71717761200140DA2 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 14ABF7B51717761200140DA2 /* Credits.rtf */; }; + 14ABF7BA1717761200140DA2 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 14ABF7B91717761200140DA2 /* AppDelegate.m */; }; + 14ABF7BD1717761300140DA2 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 14ABF7BB1717761300140DA2 /* MainMenu.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 14ABF7A31717761200140DA2 /* Cocoa Application.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Cocoa Application.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 14ABF7A61717761200140DA2 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 14ABF7A91717761200140DA2 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + 14ABF7AA1717761200140DA2 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; + 14ABF7AB1717761200140DA2 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 14ABF7AE1717761200140DA2 /* CocoaApplication-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CocoaApplication-Info.plist"; sourceTree = ""; }; + 14ABF7B01717761200140DA2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 14ABF7B21717761200140DA2 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 14ABF7B41717761200140DA2 /* CocoaApplication-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CocoaApplication-Prefix.pch"; sourceTree = ""; }; + 14ABF7B61717761200140DA2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; + 14ABF7B81717761200140DA2 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 14ABF7B91717761200140DA2 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 14ABF7BC1717761300140DA2 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 14ABF7A01717761200140DA2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 14ABF7A71717761200140DA2 /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 14ABF79A1717761200140DA2 = { + isa = PBXGroup; + children = ( + 14ABF7AC1717761200140DA2 /* CocoaApplication */, + 14ABF7A51717761200140DA2 /* Frameworks */, + 14ABF7A41717761200140DA2 /* Products */, + ); + sourceTree = ""; + }; + 14ABF7A41717761200140DA2 /* Products */ = { + isa = PBXGroup; + children = ( + 14ABF7A31717761200140DA2 /* Cocoa Application.app */, + ); + name = Products; + sourceTree = ""; + }; + 14ABF7A51717761200140DA2 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 14ABF7A61717761200140DA2 /* Cocoa.framework */, + 14ABF7A81717761200140DA2 /* Other Frameworks */, + ); + name = Frameworks; + sourceTree = ""; + }; + 14ABF7A81717761200140DA2 /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + 14ABF7A91717761200140DA2 /* AppKit.framework */, + 14ABF7AA1717761200140DA2 /* CoreData.framework */, + 14ABF7AB1717761200140DA2 /* Foundation.framework */, + ); + name = "Other Frameworks"; + sourceTree = ""; + }; + 14ABF7AC1717761200140DA2 /* CocoaApplication */ = { + isa = PBXGroup; + children = ( + 14ABF7B81717761200140DA2 /* AppDelegate.h */, + 14ABF7B91717761200140DA2 /* AppDelegate.m */, + 14ABF7BB1717761300140DA2 /* MainMenu.xib */, + 14ABF7AD1717761200140DA2 /* Supporting Files */, + ); + path = CocoaApplication; + sourceTree = ""; + }; + 14ABF7AD1717761200140DA2 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 14ABF7AE1717761200140DA2 /* CocoaApplication-Info.plist */, + 14ABF7AF1717761200140DA2 /* InfoPlist.strings */, + 14ABF7B21717761200140DA2 /* main.m */, + 14ABF7B41717761200140DA2 /* CocoaApplication-Prefix.pch */, + 14ABF7B51717761200140DA2 /* Credits.rtf */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 14ABF7A21717761200140DA2 /* Cocoa Application */ = { + isa = PBXNativeTarget; + buildConfigurationList = 14ABF7C01717761300140DA2 /* Build configuration list for PBXNativeTarget "Cocoa Application" */; + buildPhases = ( + 14ABF79F1717761200140DA2 /* Sources */, + 14ABF7A01717761200140DA2 /* Frameworks */, + 14ABF7A11717761200140DA2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Cocoa Application"; + productName = CocoaApplication; + productReference = 14ABF7A31717761200140DA2 /* Cocoa Application.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 14ABF79B1717761200140DA2 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0460; + ORGANIZATIONNAME = "Petroules Corporation"; + }; + buildConfigurationList = 14ABF79E1717761200140DA2 /* Build configuration list for PBXProject "CocoaApplication" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 14ABF79A1717761200140DA2; + productRefGroup = 14ABF7A41717761200140DA2 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 14ABF7A21717761200140DA2 /* Cocoa Application */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 14ABF7A11717761200140DA2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14ABF7B11717761200140DA2 /* InfoPlist.strings in Resources */, + 14ABF7B71717761200140DA2 /* Credits.rtf in Resources */, + 14ABF7BD1717761300140DA2 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 14ABF79F1717761200140DA2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14ABF7B31717761200140DA2 /* main.m in Sources */, + 14ABF7BA1717761200140DA2 /* AppDelegate.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 14ABF7AF1717761200140DA2 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 14ABF7B01717761200140DA2 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 14ABF7B51717761200140DA2 /* Credits.rtf */ = { + isa = PBXVariantGroup; + children = ( + 14ABF7B61717761200140DA2 /* en */, + ); + name = Credits.rtf; + sourceTree = ""; + }; + 14ABF7BB1717761300140DA2 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 14ABF7BC1717761300140DA2 /* en */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 14ABF7BE1717761300140DA2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 14ABF7BF1717761300140DA2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + SDKROOT = macosx; + }; + name = Release; + }; + 14ABF7C11717761300140DA2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "CocoaApplication/CocoaApplication-Prefix.pch"; + INFOPLIST_FILE = "CocoaApplication/CocoaApplication-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 14ABF7C21717761300140DA2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "CocoaApplication/CocoaApplication-Prefix.pch"; + INFOPLIST_FILE = "CocoaApplication/CocoaApplication-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 14ABF79E1717761200140DA2 /* Build configuration list for PBXProject "CocoaApplication" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 14ABF7BE1717761300140DA2 /* Debug */, + 14ABF7BF1717761300140DA2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 14ABF7C01717761300140DA2 /* Build configuration list for PBXNativeTarget "Cocoa Application" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 14ABF7C11717761300140DA2 /* Debug */, + 14ABF7C21717761300140DA2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 14ABF79B1717761200140DA2 /* Project object */; +} diff --git a/examples/cocoa-application/CocoaApplication/AppDelegate.h b/examples/cocoa-application/CocoaApplication/AppDelegate.h new file mode 100644 index 00000000..563a1b2f --- /dev/null +++ b/examples/cocoa-application/CocoaApplication/AppDelegate.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 + +@interface AppDelegate : NSObject + +@property (nonatomic, assign) IBOutlet NSWindow *window; + +@end diff --git a/examples/cocoa-application/CocoaApplication/AppDelegate.m b/examples/cocoa-application/CocoaApplication/AppDelegate.m new file mode 100644 index 00000000..8fd5c49c --- /dev/null +++ b/examples/cocoa-application/CocoaApplication/AppDelegate.m @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 "AppDelegate.h" + +@implementation AppDelegate + +@synthesize window = _window; + +- (void)dealloc +{ + [super dealloc]; +} + +- (void)applicationDidFinishLaunching:(NSNotification *) __unused aNotification +{ + // Insert code here to initialize your application +} + +@end diff --git a/examples/cocoa-application/CocoaApplication/CocoaApplication-Info.plist b/examples/cocoa-application/CocoaApplication/CocoaApplication-Info.plist new file mode 100644 index 00000000..136a7b5e --- /dev/null +++ b/examples/cocoa-application/CocoaApplication/CocoaApplication-Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + org.example.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSHumanReadableCopyright + Copyright © 2014 Petroules Corporation. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/examples/cocoa-application/CocoaApplication/CocoaApplication-Prefix.pch b/examples/cocoa-application/CocoaApplication/CocoaApplication-Prefix.pch new file mode 100644 index 00000000..d7206045 --- /dev/null +++ b/examples/cocoa-application/CocoaApplication/CocoaApplication-Prefix.pch @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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. +** +****************************************************************************/ + +#ifdef __OBJC__ + #import +#endif diff --git a/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/Contents.json b/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..7cd4f8e1 --- /dev/null +++ b/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "icon_16x16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "icon_16x16@2x.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "icon_32x32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "icon_32x32@2x.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "icon_128x128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "icon_128x128@2x.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "icon_256x256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "icon_256x256@2x.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "icon_512x512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "icon_512x512@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_128x128.png b/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..6c556a8356d0a818c45a42ca845502a8b2e460a7 GIT binary patch literal 5256 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 M?&_&ms9Hb&A3*vBzW@LL literal 0 HcmV?d00001 diff --git a/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_128x128@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4442d3eb27fa4973c50d3cab6069e933d9e7c02d GIT binary patch literal 11304 zcmd6Ng{(uE(;3IZxTG{3g1!nQe=N|iW^pc93Oswul0SON_tkI|#eUa` zL48!&CpnNSg^BNkp$oa?H|? z{YZ9|I@$iCGbEUd|3v6%9s~dI;fY|ExajjI$D;k=S~-QLpZHVMFd1DZCT6v7gYKNT z$b+ervrR$;-JW7QU1?gk3U}7$c(q2wQ->Uc8gAxM^21R_af+O=!E1{Tat|YJh6(~x zj4YCKpcb&F6khuKS&N;`7p{;gtz0TN*|wZmiG|TV*zPX=nc;UbJigf^#CxSUWLS%v z8s0NND_8;k7bplU#6yO(dm)RxIU&Eaa;cS#u718#wru1l3Uybe>9zjQt4FQ|;Dw%R z^psIKy8~p{i0u zvBfXTj$|#dB?0a|HwsQ#^weQusy(Y;AhFZItkFG~Pcha63*HItIYAlDIlc+bDW}kO zrvrl>1Z-Z;$o}8h4y>W2Tm z9mmBe?LWjA)*WwIr@o?2|8>`YSg{#uQN%ukD?K!spx^Yl~et5dy zq*7*I!+Uz)$B`E2Ws3y*CJ_Rf{=B}!x{p&QWjr|-Wjb3&tOSEfq>;K*%9&IEaedeA zSFF8l+9{#bu3XK2$mCt`?8gbyZ?5 zY}BhS!!2JSm1f?e3Q(6KNn-)1l(oW|ORX*9_uw^e?7Gj7Bq#TpuwN^$)wOmew5YDc`}Iz^cSB=~@n3RB9Ia%uqM@eQVe_!_Re91oR_R zg__u#=M2l&J04D`Az4mK`@$5IuxHYC=bUSvZOpf$xh1ZeG!GvcECjGX{~fbt#Liq* zr52B8qJ7x^!}teT@Q3}gkV;qjS6W)4aFKn}00M;|8xQ_B6f)3d=_ zdvMh)9kzJ`HFU?--DE&e*@AfgULp7FIh2w+Ip{)!@ZDh+>tWs8tyiZxdJ;^d$E(hH}Og> z>st8xYf(4ox#WF1ceu@FJ`gom3xCd?@UV2>L31c_GLrY75K0BGBa18`5`%7RLLvjK zhC@{kl#9h>2Q#5Q3{XsXU?g$aVU7th@eq@sRZs%$AZzQZyUQtc=Vd*duX> zUN;{=1q^Q}B{mQe?*vbnAV>F`2!Sn$w-|Tru^(>75grp6p*G|gXCAm;sdo)j)O1Lg~Qu`Vq_~JMW{Y#pv9Vlj?cn+|m+Om?si(?R)zyD6a z9MgM4A9|~D_KOLhY$KSUg?))R`s};NKrjoCi??n2G>mRIBo*tlg4QgRa$Q;P_K}l{ zzZ@Mu$z%hgt5GwuE-;8t;7A208H1rhZC23wjNBoKQ}7d&OU_6=0+QsT~ zAjz56m+*>b0#1{He;z|jF3`5KegRR%d1eY&x67SMfQQ{LM4U%ReY_-xzoquv6D0PB zQv%ZGTx|He%$ z%-Qw7xP;L5|c$5Gr!{ew&CdhHIeB zA1!soQD=RY(!?=hGp(ublrQ_=?`YXp)T0*}eH9iq6_$2F)lO7V%;hY<8G$EfN0DT~ z=_s`ANs!qgY(uv2lH;0ynRxKuv94GHa|q;>RWV1;N{PpRSqrVPvVuC^AU4a>5xCA2 zZKaqtK>rbvg!TlrAIHMhPSoCT&1PS=n_~SJaFP``>G4f6c#QA*!d4((rFE21XChK& z)&3S44+@+#xQMuG0Az2TS-@x6>b1%X|1PIZ6V+M`cv3i2xqwM^{F8Y#aJ#fPRv%ba zP*}lR=V+DJEHZ}?a=;7d?0uh7S_E@C8vpsiWL(>}*A-3*>VjY{co{6y}GD zZ(#I+NM1m<953XOCN6w|ykps-py;*w>&^7^wDn+h+%w=I1APY@&CG*=bfmOnUlVbz z@bZPP0LmJ8Sc&c#NEf$WiWBeHly~_j$V+B}*lR+BWnl4JEKr7!=@lFOY@y9Q%+D*3 z(oAwSkk)75ekD3E&Gsf7iPT77AIGm}y=Lt~{j9QV_4|kr7Yod^SD}Ks{%dO}rjW6s zaPCgg(%Faj@oWdn-660g3M*47KP2TTsuq8)fxggt6dqpfhE;t}^W3`3uyo)U@z)Tn z9}Q;cbn^I@&UV*gYSaX|OysyuRAcWRd)snvbAlj{T#3HeGa?L#R9DlMG!MioB_O?Up6kSbIbuWhwR);Kk13`XQKHf@gg^MZ+$~?zf z4|Rwp7; zuGE)O6I@e^H55D@#T;VO$nxTy&HD9~C(KarfW(O)@AoE*#7v!g>t3U42ljYSa*2jJ zRR~U!CSuZI-m&l1f3kF^gbM!bUVc9CmQQ%84LpK0w8F%RAdW-!j>>>|U;eg&h8*__F#bES$fW-fwrx6B@GD@X?5WkXOA#ouK!$ zm6}Rfa6#(8-4p&o5%2>AgqAt7S4_cX#(Y=8RyafZO0~D{JBrbP_022)HhSiiHKNrG zzgJcKMnD+){j4_@f4)Jc5|t>K3U6fi6XJcgp#arF&Hr`IF&Sl??JHi*tdpfb3i%Ur zf3XPjej%rdY&;s-EqQHA74*YcedDnRiCEQc;qNYbhy`J%8kWPFurwR8^o}iP5cxxQ z4L?}AO{7^2sCA|*oRdwV)ReIOZ43VPXr}ioSBXwjW`FYZ_v`AQ4lWKSFYj1C18oNA zP6NQ{b-Knb^U4%?G6dn?{y4_=qBp+gn%x<~sKT`7{c(F$YMVs&c5WP)0rd34_PrZ6 z)pqSNv|0l{Bup>R$n5 z>zs>Pve9B@xphr6M{X~_@dC2i1@Z?UTYPsGj;CMd(xz*K{QGmb@jK(Den+QIx)7+@%OPYu27cy*uhCac`-kmQqjy@pm);zs4sz`}G_Iv0dO!AoVMr#e z8ztc*>TLH?+tD}&=s{A}(omMH-mB?a^(VPXoXw#-n6Ym<^3DPh9*b~99IIH6X7o5h zRH^__dq~>T_Ni`LSx4cK_lnUXm=FN}WLxXGrT=^Ke5^|D3Xb!`oD%OA*kcyE-Vyo@ zFJ1mF06Fh3KyyQ8S6Q$N(Gl7@)##S>S;`?c>8J8zVEykGkxydgext544X%^VX2xkV z4+u!ZD!lVepoZp(XYP;IN2k*Yzh)*h9z0NK)U2Wm@_xGvt^NnBSu$=+D}9Mwp1H(qA+=}493_hggXY-8&>24i=oCjtVvF%7D7 z#+ER^oM8XuCBuQG;e8(c&jTA@_lNvrWuE0SxA&fT($45)-+wA<*f;W-u7o1u zoJtO+_iG_c(zcVdCJYxeh*H^QI7MEQuz@#%$*8cJm3YSm$k$@puW5iO{n2|R=NH%= zx>xaoOS#F%6M;G(Td1M4$+0hsmVRS;9zS~!tMY=pl^ySo2o(s!?8(-+PF69~a9ejF z)31X@iuJe$kq9Czt$PzW!D?NHILNbd!Yy87f&~kd1v!PeqUH9jQ#0(mY4T%Z+#^Rh zzb|qL-CbijOx$|dyY`uM9hmL$ay^NltSK_=VHf=}QCV4V=(9S5uxPTWUiwbjVT2wz zxj*Y23l!apvnF}GLD+GL8@~Hgd%f^Zkb^Y8cH#u7uyfXrOTscX_)zc2N(4^XUxs-yF{OCr!Rw zG%5B7Gawi?{N3h&=_r>Bi7x2$Iv5GP(muI=rL41?71otHsfMVT<~0dM?Z>f+H^$HY zIo#8Novw(jXb#g$g2oP2et;RSw67E5rT;puPL4w=$>8fdo0>I`E$vIbw5fg z@T9)SBuL*Ym#`Tdubt;OFOqYCcNp=;rdMsrD<(v_1(&#W^xt~?$rRs_J^8xbD)Cg_ zbF@f1JTzxcXg9^J(_d~C7g~pY84qkacRIH^UdR~z*!X4TI(~*Zs5vT#ssNWZP5N9_ zls#{7!KfH2+OfT3DTzP%u93KhN`&B@DrX$o#W`O7 z%@$e+!?^KA1;+NL2$+BE$d5t`9J)FT#vT>U&kq})ZTPydiT@rjBaDLRUKV313T)y@lY38LrQwUJb+R;1v%B> z=}gKDx2&91P2>!57mjB`rl4VxcY+!}?X%Xxt4#M)ZIv;T1CYs?G^} zoblj{xwAxlc^ILO<|r;ZsBYy@*K>1fDX9T{m>{`)W2IsEICd{Ybg$vZr}&Hcp$^r# zvoY1>=>zK+C)a9|tv@-6vjel5OvBXbua2Hhy-Bm4*fW=p_IUr=8;`vZ_Iym(5i%9OcP1+9;ujo6dtrWiW=BM$5hsfX8GCe6XhU-OoENlh06v^K|L%P9iu-h@;lc=Sh{aLL%@uHtsIQy?mWZ@$5~X z8Y^5iQL$jtG(q;&lnvR+s|VNZD!|am{AtZ^+iw-x`=sm)kD5cPvUnEF&M*PQ_y+Mb z<*m4}H0k4O6tSoQln|huY0UCCinHt*PM@8lR^Mqtv0$EFJcf&zk9v{Uj*g_8Msan> zHiXCkcap9v&@4(dJ8S^=lfA&bhRN>Y+%T8DySqaZkjbJHhQ)?+#j^uMxcB~C^Nuq` zXsYoB3EGbslJLiHn)rWkW|Ii&<0n*}LJ)3Ww;$4uNbKxCwRocx^s2%3v~g*+o(!%- zYUfLG7+A6ub#!w%akCwtaa;8YxRyL<`(Ud$7jKxrSY7eqVnUC3AZzz|I5D&KtjgwK z8C+eytRUe?*X3x}&hgquBi&q#*Kx#b!}Vf1y6mP2U15zgHS`X6iIg}4A0zN;QnHz% zm!9$a3bi|!MVCf`S=kjmEtVC=?o^E3E)Q3NkP`#}?c0y+=(!;rV;)H@-ZzRxg9yv1 zvT6iL#M}9q!V&V`h&cu0AckJ<7~n^Y@?Cdm3Z*i1RdUWaQ;+TfJGdtSkvC9*@8}2E zBar3Q9S@XmpggPmQcXd75+g*zO!HDy01PT=)!Sy=g(!-)JlVdZk?=uOHu0}q(YEp| z$`|Cp9N{zvb$Sy)DO!+G{H$2l_&cMsl4Zyf4L-`C$y|lili|RPFaC-3>Fi}|$XBoi ztO0;E!1BdK%W%%HkNGTq)4m^Dz7?<8i9SEvVkGU_j@@g}F$a7YDFpy`K8D8<3E17rQjL=no%c*6C+xhU8q)_l8q?L{Sz&^J(z0b7*N1IWZ>%VJ zCjT=3!lyAVw&@obd3Gh^;G#VnR7rZ%_0mg070dw^eM!8k6*k;-+fYJvct;kGx-g~V z^lPHJTT>cLi<7}M)%tAi?>?xVeemP%Xi7}MmyekBRiuSU$SYr4l+2e)6cu=qw2_nO zfZ)UQ&*NVhA@jeo^_E#j1&Udbf+C#=AM9HXUD9i)tINOi4ip zrEK_m_xTp@;ejw>P3opDW;~)fumo3~0{@x!YYWLaIpnKHzDI---e&=fG!Q-eXhkqw%IeP5++>NlBiDCz8lXmUGi8 zZmy42_qSCAP=Z}}P7zt6A~~xD=jIa8m#GO!^n|5%hz!@R7Ad^^2E#!naJqN%Pt>SY zk+5g``aL;GV->hId}cemo>6`j-)pwCzH7J0Pr1G=E>BfV-oBd-PO^=F)J;*~MH~MT%^7jNAse1BR$rvk z8g8>NvEoIOudP>0M*tPLjF;09H%a3~1T@c^DLajwlwQ}uHa2ix9MM=Q*SdfYbEdWn ze^8V|9BF@8n)8u@`oec$@)<7KrN)G9qViAH;e~*rO@-3gEA*1a`m?B;(`5XGx8Xw9 zqIXo1=zl7443VYxun43av65zsl~m);w#S1t?&0pI;mG~Y{F$Mb#{wfs;2@5c3e;Fs zpbZn9@jX;SGL^D$WJJl9W)Qt|M+6Clnf1=(y5E;07Shv)o1H@bJSVkYy6DoJt4YrZ zM-0$o7U&`qh399v@N2L;M5~ZgmI=H%k*ptc-;8KozHKlPt#UUKT7CAS1CBo1@u#do zQGJymu3~v1Lv{HXT<#?!9jnrJ_7I7{DC+*ts;2|#PrJa?4JHOfLUtC3>WjuMy zlP6^$8z!f3A2V?r++l_r(n=q)%>XMP%c!Lx zG0XyBZ}8zPTYN6c^EF{IDKQMBn1w?wN`>u?@HgbN-_h2EmH1m@&SyiTcNSQo4+mf> z!7pwZokBM^!udyl5yRRHd+MIx{?ub7?bL6LsUfDdI#G0-w=@1IB~vQbx`Ge6roQA6 zB?Jp~ite|+61lKnu8toII+G>isdjM^EMw>RYc$~Bd>kQxY^$P*US#tY$zZ}iX6)0) znIFsaqaWp&55zXpqR+X{jv4;%qC=__TXu*ZsrE19_v(?hzxI@13;xh4iZZ;*c-h(K zb&PxicAXBreZWHTtGXHVveXp*uIyw>Y8)J+5ncXoANqvWwJhp{eLVdG`gB&GbvYz_H~w#LGvyuNqxtVX3JlDNoW~xmCvdHFG9kg~9z}@mR;}b$l}3TdKtHAE`|c z{p~Zp(L|C5yyQQWQuMv{Y*)8}f@I*V5q1Nd_aEPbWEYbD zTyM62WU4#vVv)yXuv$``{{Eg}_~#L|mD|7(CAr2n`64X(w6KURIjm^r``-ItyD@D_ zyQ^>Y?SSR8lBB!eaQ5Y#X_hCi*KuPmXZ|x}tK({U-W(#*bt!G6JP!5G=T(ZV7SOtvSvD+elrHwE2fAoDX=m)cNi} z|Bm}tpWhfwVQ^sT%$mt;&cjDERONQOQujm)0y2-a9}6>l-seWA=?k=FS`QYSiH6`yAYsyx;57KNYid@^7n*u=%w?mc!$a*KTq+N@{4&37&O zz;=SiIJ5{Y(LybxMG4gr;MHp6xfY6_n1B&g<@1SuB^CBd`o20u4Na-6atoNMGETYG zKz;f%w}nw{gg@$vwR* zEh18!xGZd>Y0G__ToVD|GX?8wxfw0hS@9-zFMOK>)cAlJQWD!A_fwfgARksVCO{y_ z7k9g)Co1rvELA1@ATRT+(XS)pv16g-?B?OcaBec_;a7m=nTQGPbJBY2*I=*XcPne; z@Tn14VsohAsfwav0pU`jtfaqh3@2Eh^KdkuO_+~tSZliB#P!X?TbC0fAe~KMo0lNn z&kOwJfa|s;RM3<57GOp6Yy=6x?7=8GoZz<%#o%-6-h*y?>PUC2Vs0lpUZ6WJChrPd`{vx>C$V5}e(>TrEqthJNb@`WURy~~*TCyB9m>ual>$=G zq&+##-eEbGPd#=nEiL6(wK&)Eu&V^Z=maX^!A$O`ivY2c4nwFC8@SMy-f@ zHZ1-yQz^8?0jo>&HgB0o1XSRzzi)H1U@ZLSeeX|^T;N4g5s7f2Mfco#K{AuUQ+X*tH*R`40tI6Dl_)GUcrb*M7{mR$1<+wUe8Y49I;55_Q3>sY ztjK;4PKuMzpuND4-U)*=;njG^7O@|1J)Rih4tuhA#f1A{LfBOk4Z-Y6OL{JM!)OpO zM1_1F3C;DF!u##boEbBQ*Q1P$R3Op_JyruT(=%j6w-8$MjVCh}NbGet#{}G9>U{XCP(IW=}U`~?ndR|rch$mHV z8bNt$_bk%0?*M|CDn;BPkkL>N(BjFg4^yNm8E@(}H9pFK@VW-J5Zu|Mro&ox3e!oc5jPTYUA{A<05u4+H6>)vL9z&@%UH5`qFNnhxCCD9M(`IX9ii zXl$|HfdAMrnCaPM$rv#dq!#pQZ`2O{MGBM$aw5JAl zB<^^b9~o&Pgr83m+S;r+5bq+m1P^*7^oq~}j%#WdXRKN;CBWrp*kqJU@kZe#J53`t zI=e8LA}w?ZpLJwT_ZPDttxBhL(MnTs2GHP*k=$RBemfSL;X`=*p&@(>FsF`kb zHE0oya&Ql?$VNmx3uajBTjIQz21~5!IQfSw1!}uqSJWGvluK9P;u-Y)~+pNM&a<}#qm2fE7Xs+Ci0I>_G@YvHQUw&jo6K{ zU`?KSrwIbv#V{%+o)N$}N&IWEmL{H8RJv=t6O*A~r~y@UNNACQZL@7ei80kf!&j<( zFBj`ND-Un4={mS#&i5z=u5w5D-u*q`ywa|FfgZ0ng2k^Nusv<%ZSPF%)hlWMKGUj5 z#In~G`McjGe@R73GjZF3JWIkc3ynPT&JRISnqY8JVsfhMC-U0duu`f++aArQL*p*3 z*3V-&z@bhXV)<949KYUJ`(CW2BWAzpWL4Ex z`g6oO*puRh3~#h4M8AmQxFdBCBank?F>7$q8{BErtWA~*syLUmUpw7>sszg#IHS1TAKK~E`GX58zYwgiusz#hvq)XKDjv?bWll=YUsHidNcz7$U+Q*NO%gH4Dw?n!Ra$IpwJ>xjUr( zA=cILzs60haUn#nu<&ZMs6UEc@l=1P(QVd|SgW@!S(SLB>Y&tNU2a_2@zIw;kzLk( zu37iX146leKDgja>|e0V)7%l|%0q&|q(>pu=C}bLSz&cq<@tuQQkW21*G8G1K0qUz zmp4O}s$T`nym`tT9&=piDk%9w<2nybO_kJud1?gS{JLvs!=pE}mQ%{O;LuybQ_gu@ z1mm+D-+M1$8lmTD1B>OQJ1tt$Xzy{C@NZO3Ir(m9)WXT$(ji2M*SJHuXhe%*0I!t| z!IiR-_{vx_w{j8Hwujf(j0Tilgx^CTCO<6qLF2#xs_#B=IZT}>f7xiWkal^K}E_z)8l!*^LVhhA5 z$x=bA2LF2UqopPa9tpmx@hHSetJB^TuPC_@9M-k?#W-ke(-}p&q(bWDOrB=W{8l0J zXsgpE?u{4&+1-3fb==DnSB)IKU_u+6NSpiN-JnRVJcd=NcpWyfXB<8e(Vqfo);|nS zIiHdyt4UG{&@FX${`pQ|&CefOyut@`GKe$%zu(<5E)sx#?=F6yb3mH-1u&qkuB%q{ I&?fSK0D}4Yvj6}9 literal 0 HcmV?d00001 diff --git a/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_16x16.png b/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..2adcdb5f49180b2c150612ec2849e152da141eb5 GIT binary patch literal 487 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMfs{o%6S0MfW|Ns5__pe*GZriqP zE@wiMkLGROym{-^ty{Ki*|lpIkg@CF?wvb#9zA*#NIISk0-}=Rb*E09I(YElkt0Wd zT&J_aKBr>*PR06Pj1M>!A8;`t=1NxVm7L@g`N`J`Q?39g*(_V9@qwCnj-Ul<5 zo?5x`%(^WHw(dQ-|G?$L$BrF4e*E}}6DNRXo&kc(XU?8Id+z+X^FZ+F%H_}3uL8lB z8`pr~`nBsfuHX22`^MKhw{G3K{q5czAh>(y&bRw_f#BPN`#|vh(Sz@gA3l8e=+UFc zj~~By@#4*|cR)Wbj#25*2v(A_%|1a%S=)w9*qa^JpqxbPr@Sb)*jz~HT)z8QTN zYU1eV)XcK3sFJdf-h7bd$QGHHuV-~D1A3JC7Ip6y}-}9d5J>U7B?|j$x zVs=BpZeS1y1dWbDVS&2--CDT}$S6WF4Fvk&&%H4TyMXQgiXhTIlEAtMK)95bCIk{u z|1qkptPJQvp-?;?4`2)i)6mdBB9ZLv?O7~VettgC?e6Y|Kp;g$MN4z~`}+|H1b_j_ z5)&5}S65fJgx|{q0)fF`Bqk>Qn{R1pxpnInzyP4Wz8(gH0l2ZTv8JZR)6)~c_wU~a z&Va+=z~Fax6&xI#k&%&=m6el|bKt-MKR-W!d@sV-*htR4C~oq3$u-%W+-9>)&TH=~ z9-a^vELs;eW*t%8j91l5-i+QDE9)`*Wwluk3r?c@(q#6pYuZ7X0a~JFPbAb2{Z$dezavy!%{&baLiM^TS)T-Xz-iy*3M$1gaIQ|!Th2pKUP+HPw;`bWaozp#$?4<0T z)3rVIBC$v+m5RmUJBkN-i($Ytm_2xgXB0}6GDcr7oyAZn6h@;_B9Umd+5}!EXNH$O z_#>K`cD1{tn?W5J8JV1%)ai73y`InK4-E}v2yqM6g+ui3yJ%f9nM|cpsnu%0xbgAv zeFIr&W-5hB866#!%jHU?a$;hF&1Q>4A|8*I*L6uC5R8qD0j>#!!r|dzDwR4oIM~tA z!R2xX1_s*O+nG!z@HRLcPHSr`jYgx>>1}OoWHOmTp}Z`8MFxz?`T>iH2iYxK{^1Aq zpSY}b_i%%+0++k4sPcWV9kQk}0J?JHM~EFC2W{W!Q-1i2Nakg|vcb5sVXK2Rd0ijU z@zhXA=(kDxQnO^%vzvVUB6XB6CdXs8j7Uq7|t1LMd;{^-!bb|Yw9S4Ez z^wFrW1Q%C9S1cNS3Pd;RPg-p@4vTYw=yPIzuXAB@;RfOJqIge_a5aDJQ?%cDO&>8{ z|M*xr0_IU&cm47jI1IfUeBEVvNrkrl#mfPfd4I>H&`+)Q_y||)-TYO_xfl<3exTzj zPep0kn~)0y_~73!Zr(H&U`uy2bY6)LU{mt!lCz!9lsXXEVM*!z+hT9;a7?NkjS^*c zt#19M^`=*Yd&UzbV;&n57v@gjFZ+YBCUx7E%14V1Rn^t&xh>WB8Nbf4&#{$m6{EYo z4I)_M+ao_|kP|t`+=JdN>%Mea;o<5$TbNnN&eP?Zs&Tdc`)hu~-DZhW3Wi;nO~2y8 z&HfHODx;lp`YZoLX472xtP`UBBF|eX^g8E;e9H38HQl_fT40rGkQdU#BS_U9I$6&R z-5q~AXirTf99axz((cvT2Z&9<_aKUNA4pL#$B8X>R$OvDBCGa?1YX`Mn=3#Ebr7WR z+)VPukd0rzrZn9=8A9DkY`dGBjX=yjtc|F^Jr8}7pAdDvV>l-xIz_YR=%l)ssUVS8 zWAg&YM<>>5eVwrKdsW|&^i@eK2m&$DG97!41a*oMmp#5JXhs=k62rVsPr+Y>X)vD& wjK@)N3b?;}i`cZyBjDLf^m*=4H{S}`eCQSP!Jku`-~9s7yD+Gx@bt@n103|2K>z>% literal 0 HcmV?d00001 diff --git a/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_256x256.png b/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..4442d3eb27fa4973c50d3cab6069e933d9e7c02d GIT binary patch literal 11304 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 I&?fSK0D}4Yvj6}9 literal 0 HcmV?d00001 diff --git a/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_256x256@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..069b5c050245260272c21e9839b43593124370e5 GIT binary patch literal 26984 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 myuByemi_A3JC7Ip6y}-}9d5J>U7B?|j$x zVs=BpZeS1y1dWbDVS&2--CDT}$S6WF4Fvk&&%H4TyMXQgiXhTIlEAtMK)95bCIk{u z|1qkptPJQvp-?;?4`2)i)6mdBB9ZLv?O7~VettgC?e6Y|Kp;g$MN4z~`}+|H1b_j_ z5)&5}S65fJgx|{q0)fF`Bqk>Qn{R1pxpnInzyP4Wz8(gH0l2ZTv8JZR)6)~c_wU~a z&Va+=z~Fax6&xI#k&%&=m6el|bKt-MKR-W!d@sV-*htR4C~oq3$u-%W+-9>)&TH=~ z9-a^vELs;eW*t%8j91l5-i+QDE9)`*Wwluk3r?c@(q#6pYuZ7X0a~JFPbAb2{Z$dezavy!%{&baLiM^TS)T-Xz-iy*3M$1gaIQ|!Th2pKUP+HPw;`bWaozp#$?4<0T z)3rVIBC$v+m5RmUJBkN-i($Ytm_2xgXB0}6GDcr7oyAZn6h@;_B9Umd+5}!EXNH$O z_#>K`cD1{tn?W5J8JV1%)ai73y`InK4-E}v2yqM6g+ui3yJ%f9nM|cpsnu%0xbgAv zeFIr&W-5hB866#!%jHU?a$;hF&1Q>4A|8*I*L6uC5R8qD0j>#!!r|dzDwR4oIM~tA z!R2xX1_s*O+nG!z@HRLcPHSr`jYgx>>1}OoWHOmTp}Z`8MFxz?`T>iH2iYxK{^1Aq zpSY}b_i%%+0++k4sPcWV9kQk}0J?JHM~EFC2W{W!Q-1i2Nakg|vcb5sVXK2Rd0ijU z@zhXA=(kDxQnO^%vzvVUB6XB6CdXs8j7Uq7|t1LMd;{^-!bb|Yw9S4Ez z^wFrW1Q%C9S1cNS3Pd;RPg-p@4vTYw=yPIzuXAB@;RfOJqIge_a5aDJQ?%cDO&>8{ z|M*xr0_IU&cm47jI1IfUeBEVvNrkrl#mfPfd4I>H&`+)Q_y||)-TYO_xfl<3exTzj zPep0kn~)0y_~73!Zr(H&U`uy2bY6)LU{mt!lCz!9lsXXEVM*!z+hT9;a7?NkjS^*c zt#19M^`=*Yd&UzbV;&n57v@gjFZ+YBCUx7E%14V1Rn^t&xh>WB8Nbf4&#{$m6{EYo z4I)_M+ao_|kP|t`+=JdN>%Mea;o<5$TbNnN&eP?Zs&Tdc`)hu~-DZhW3Wi;nO~2y8 z&HfHODx;lp`YZoLX472xtP`UBBF|eX^g8E;e9H38HQl_fT40rGkQdU#BS_U9I$6&R z-5q~AXirTf99axz((cvT2Z&9<_aKUNA4pL#$B8X>R$OvDBCGa?1YX`Mn=3#Ebr7WR z+)VPukd0rzrZn9=8A9DkY`dGBjX=yjtc|F^Jr8}7pAdDvV>l-xIz_YR=%l)ssUVS8 zWAg&YM<>>5eVwrKdsW|&^i@eK2m&$DG97!41a*oMmp#5JXhs=k62rVsPr+Y>X)vD& wjK@)N3b?;}i`cZyBjDLf^m*=4H{S}`eCQSP!Jku`-~9s7yD+Gx@bt@n103|2K>z>% literal 0 HcmV?d00001 diff --git a/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_32x32@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c5e458164974ccedc45d8f686c1cbc5ecb3646b1 GIT binary patch literal 2423 zcmV--35fQIP)OP}II-HMiDGqRVn7K((pDbE8mrN^lrgbriaKdb zYf3;A4XrfWbSVP7J|8!KIdq-ht66W-YzLd`_ZjB#sMQJiXI2Ki7N8I0xd1=n^ET2I z`Lp^$o(u3I!@`FVKyS%&0bXKw;z0#4)@Sk$D}XVtpn6OGVFkb%YOyl-^)nCh#p|-S z(%!NU=xEJJ@@Y6nC+pA9N7bK_OY!@9Apiz{i{ZKHe&HK=zoqc@Xu3N(NE2D-aF-+s zM4IS6e<(XhryI|!@i+un!0^I!zHEEpE{UABmf4?2HaQL&?-ke_tQh;NEW1Z804}ng zPRQ2|HYg_DfA0<@bSKk)TS6$f^%AA^X43H3uwo3NMH>NL%eH?k0=%T{7kygiub6Oh zPdaVNb-HhybK&o)Zm>~3Ze(nP-Y(odr2vzq%d8Nfl}RFbM^BN)(@q z_Yqx40d0%oBM|^qUf9s+kX92zl*ny&>0c%-f{&vQ&{}}dwn+6vd+zmVH8J;+0}6tF zEceo10j_k!sVCYs+@saRb`5eXCyvwN`8GB-mH`0) z>vYry{B6>Tt_f#ZXD19z)cD3m$D5(=aN;=M_@2AF`_IF|!qVN`++NU$01gH2ipioc znChC;-VF8lhN0$31)5Ihr$K;~AdO>uGc&ViPnNG2Pgz^9+x815MT|?HTLY;;}8@Slph%xNzD6QOP4PF1$+M~ zoeA)t=8K96WBdEMh8^7I+_x#SFIO@4y;9F9+v}w6n{=q^WAdv0gg&nI(OLj&Yik#O zfB(q1xVX`)SFaKTICSXHX~ylxJT-J9fNkzO)IV}ZG3lnE77A;RBHxBD=ycP0is?+C zzI*+OG1q%iHQsMy&UPAPUFF1^A;-znGTn`iCY~Th{9aK}5v8Q0PoSdJ1_J-6_gCyPYj-_N|i zQRaOkoeLmOi4UueOT`RJnjf3^zgWK@7p8Qe;(?a8H*Wui_9lDM$#`FijE$5$TU}jE zJv}`VaiFB6gpl){ot@O(-A#psg>?Av;fsvZPx%kKsu>kPn~f6;$K_fbxTd{Jhm(%e z`PeThDJeIdwYA;gwu}#xOC4moHy#&p18HPrQwEdL*VTkn{4B zu ztgMXK`X7%&UWP?ed4EO)7=IX2akpAy=lEW-2ZhFk(e2x}sj8|<%Il`4CTebOrk0i# zH9-gfFGGab*jOnt@HqgXfW*Yav9)X0x-vdXwekLp3jkbfyDY8snreV|;wP9im6w-G z9!D{Q0I1{;L6Cz0hysXl>Cz>_a)&Wu9K=E`@bmM_kadJ}XN3S0;r$y2CGuFpkmEHq zHIe}J_4SG<5NUwVA|fKB@nW193!oNk*|OyTxB_eFdl#yLJ$hKWg8bY8R1b81$e!32rcJ@x&<(CoOmF+O-d!6J8WifMUnCuu@LrZ!|hUPEL*_hFlN`6dD@Z zziQPgH~x6sx3%f|9=Ab&r*`h#xgQZA!Wjaf*sKl{ z`~8fE{=C^vUI<|6;^MLg{k{+YVz5Fc;sf!NFadH*Y@3?z4)oS|aa_^hJR0 zE?KhVRp#+A5CX8&0ceZ>AqHO2-eNgqAwTTx?Hy-jW#z~#z7=0tRQf_*JdcQeV+E_; z1uT?DaEb$<(?e7M_#5xB)nJevau$1dczm^N*|Ht%x?l2wXTd$MSLEegCqnm?g$oyM zWj($%&ilqt_?vIu~$ai}?QCoWCyO?=Rq_=gNrdHTm&E p7;|FczvP(6e#uqI!dxe${|}^_Xq3Q%8$kd7002ovPDHLkV1fiMq&)xt literal 0 HcmV?d00001 diff --git a/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_512x512.png b/examples/cocoa-application/CocoaApplication/CocoaApplication.xcassets/AppIcon.appiconset/icon_512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..069b5c050245260272c21e9839b43593124370e5 GIT binary patch literal 26984 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%0D5p5 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�~Ug6?yhyz10_e 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 literal 0 HcmV?d00001 diff --git a/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_128x128.png b/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..6c556a8356d0a818c45a42ca845502a8b2e460a7 GIT binary patch literal 5256 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 M?&_&ms9Hb&A3*vBzW@LL literal 0 HcmV?d00001 diff --git a/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_128x128@2x.png b/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_128x128@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4442d3eb27fa4973c50d3cab6069e933d9e7c02d GIT binary patch literal 11304 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 I&?fSK0D}4Yvj6}9 literal 0 HcmV?d00001 diff --git a/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_16x16.png b/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..2adcdb5f49180b2c150612ec2849e152da141eb5 GIT binary patch literal 487 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMfs{o%6S0MfW|Ns5__pe*GZriqP zE@wiMkLGROym{-^ty{Ki*|lpIkg@CF?wvb#9zA*#NIISk0-}=Rb*E09I(YElkt0Wd zT&J_aKBr>*PR06Pj1M>!A8;`t=1NxVm7L@g`N`J`Q?39g*(_V9@qwCnj-Ul<5 zo?5x`%(^WHw(dQ-|G?$L$BrF4e*E}}6DNRXo&kc(XU?8Id+z+X^FZ+F%H_}3uL8lB z8`pr~`nBsfuHX22`^MKhw{G3K{q5czAh>(y&bRw_f#BPN`#|vh(Sz@gA3l8e=+UFc zj~~By@#4*|cR)Wbj#25*2v(A_%|1a%S=)w9*qa^JpqxbPr@Sb)*jz~HT)z8QTN zYU1eV)XcK3sFJdf-h7bd$QGHHuV-~D1A3JC7Ip6y}-}9d5J>U7B?|j$x zVs=BpZeS1y1dWbDVS&2--CDT}$S6WF4Fvk&&%H4TyMXQgiXhTIlEAtMK)95bCIk{u z|1qkptPJQvp-?;?4`2)i)6mdBB9ZLv?O7~VettgC?e6Y|Kp;g$MN4z~`}+|H1b_j_ z5)&5}S65fJgx|{q0)fF`Bqk>Qn{R1pxpnInzyP4Wz8(gH0l2ZTv8JZR)6)~c_wU~a z&Va+=z~Fax6&xI#k&%&=m6el|bKt-MKR-W!d@sV-*htR4C~oq3$u-%W+-9>)&TH=~ z9-a^vELs;eW*t%8j91l5-i+QDE9)`*Wwluk3r?c@(q#6pYuZ7X0a~JFPbAb2{Z$dezavy!%{&baLiM^TS)T-Xz-iy*3M$1gaIQ|!Th2pKUP+HPw;`bWaozp#$?4<0T z)3rVIBC$v+m5RmUJBkN-i($Ytm_2xgXB0}6GDcr7oyAZn6h@;_B9Umd+5}!EXNH$O z_#>K`cD1{tn?W5J8JV1%)ai73y`InK4-E}v2yqM6g+ui3yJ%f9nM|cpsnu%0xbgAv zeFIr&W-5hB866#!%jHU?a$;hF&1Q>4A|8*I*L6uC5R8qD0j>#!!r|dzDwR4oIM~tA z!R2xX1_s*O+nG!z@HRLcPHSr`jYgx>>1}OoWHOmTp}Z`8MFxz?`T>iH2iYxK{^1Aq zpSY}b_i%%+0++k4sPcWV9kQk}0J?JHM~EFC2W{W!Q-1i2Nakg|vcb5sVXK2Rd0ijU z@zhXA=(kDxQnO^%vzvVUB6XB6CdXs8j7Uq7|t1LMd;{^-!bb|Yw9S4Ez z^wFrW1Q%C9S1cNS3Pd;RPg-p@4vTYw=yPIzuXAB@;RfOJqIge_a5aDJQ?%cDO&>8{ z|M*xr0_IU&cm47jI1IfUeBEVvNrkrl#mfPfd4I>H&`+)Q_y||)-TYO_xfl<3exTzj zPep0kn~)0y_~73!Zr(H&U`uy2bY6)LU{mt!lCz!9lsXXEVM*!z+hT9;a7?NkjS^*c zt#19M^`=*Yd&UzbV;&n57v@gjFZ+YBCUx7E%14V1Rn^t&xh>WB8Nbf4&#{$m6{EYo z4I)_M+ao_|kP|t`+=JdN>%Mea;o<5$TbNnN&eP?Zs&Tdc`)hu~-DZhW3Wi;nO~2y8 z&HfHODx;lp`YZoLX472xtP`UBBF|eX^g8E;e9H38HQl_fT40rGkQdU#BS_U9I$6&R z-5q~AXirTf99axz((cvT2Z&9<_aKUNA4pL#$B8X>R$OvDBCGa?1YX`Mn=3#Ebr7WR z+)VPukd0rzrZn9=8A9DkY`dGBjX=yjtc|F^Jr8}7pAdDvV>l-xIz_YR=%l)ssUVS8 zWAg&YM<>>5eVwrKdsW|&^i@eK2m&$DG97!41a*oMmp#5JXhs=k62rVsPr+Y>X)vD& wjK@)N3b?;}i`cZyBjDLf^m*=4H{S}`eCQSP!Jku`-~9s7yD+Gx@bt@n103|2K>z>% literal 0 HcmV?d00001 diff --git a/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_256x256.png b/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_256x256.png new file mode 100644 index 0000000000000000000000000000000000000000..4442d3eb27fa4973c50d3cab6069e933d9e7c02d GIT binary patch literal 11304 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 I&?fSK0D}4Yvj6}9 literal 0 HcmV?d00001 diff --git a/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_256x256@2x.png b/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_256x256@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..069b5c050245260272c21e9839b43593124370e5 GIT binary patch literal 26984 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 myuByemi_A3JC7Ip6y}-}9d5J>U7B?|j$x zVs=BpZeS1y1dWbDVS&2--CDT}$S6WF4Fvk&&%H4TyMXQgiXhTIlEAtMK)95bCIk{u z|1qkptPJQvp-?;?4`2)i)6mdBB9ZLv?O7~VettgC?e6Y|Kp;g$MN4z~`}+|H1b_j_ z5)&5}S65fJgx|{q0)fF`Bqk>Qn{R1pxpnInzyP4Wz8(gH0l2ZTv8JZR)6)~c_wU~a z&Va+=z~Fax6&xI#k&%&=m6el|bKt-MKR-W!d@sV-*htR4C~oq3$u-%W+-9>)&TH=~ z9-a^vELs;eW*t%8j91l5-i+QDE9)`*Wwluk3r?c@(q#6pYuZ7X0a~JFPbAb2{Z$dezavy!%{&baLiM^TS)T-Xz-iy*3M$1gaIQ|!Th2pKUP+HPw;`bWaozp#$?4<0T z)3rVIBC$v+m5RmUJBkN-i($Ytm_2xgXB0}6GDcr7oyAZn6h@;_B9Umd+5}!EXNH$O z_#>K`cD1{tn?W5J8JV1%)ai73y`InK4-E}v2yqM6g+ui3yJ%f9nM|cpsnu%0xbgAv zeFIr&W-5hB866#!%jHU?a$;hF&1Q>4A|8*I*L6uC5R8qD0j>#!!r|dzDwR4oIM~tA z!R2xX1_s*O+nG!z@HRLcPHSr`jYgx>>1}OoWHOmTp}Z`8MFxz?`T>iH2iYxK{^1Aq zpSY}b_i%%+0++k4sPcWV9kQk}0J?JHM~EFC2W{W!Q-1i2Nakg|vcb5sVXK2Rd0ijU z@zhXA=(kDxQnO^%vzvVUB6XB6CdXs8j7Uq7|t1LMd;{^-!bb|Yw9S4Ez z^wFrW1Q%C9S1cNS3Pd;RPg-p@4vTYw=yPIzuXAB@;RfOJqIge_a5aDJQ?%cDO&>8{ z|M*xr0_IU&cm47jI1IfUeBEVvNrkrl#mfPfd4I>H&`+)Q_y||)-TYO_xfl<3exTzj zPep0kn~)0y_~73!Zr(H&U`uy2bY6)LU{mt!lCz!9lsXXEVM*!z+hT9;a7?NkjS^*c zt#19M^`=*Yd&UzbV;&n57v@gjFZ+YBCUx7E%14V1Rn^t&xh>WB8Nbf4&#{$m6{EYo z4I)_M+ao_|kP|t`+=JdN>%Mea;o<5$TbNnN&eP?Zs&Tdc`)hu~-DZhW3Wi;nO~2y8 z&HfHODx;lp`YZoLX472xtP`UBBF|eX^g8E;e9H38HQl_fT40rGkQdU#BS_U9I$6&R z-5q~AXirTf99axz((cvT2Z&9<_aKUNA4pL#$B8X>R$OvDBCGa?1YX`Mn=3#Ebr7WR z+)VPukd0rzrZn9=8A9DkY`dGBjX=yjtc|F^Jr8}7pAdDvV>l-xIz_YR=%l)ssUVS8 zWAg&YM<>>5eVwrKdsW|&^i@eK2m&$DG97!41a*oMmp#5JXhs=k62rVsPr+Y>X)vD& wjK@)N3b?;}i`cZyBjDLf^m*=4H{S}`eCQSP!Jku`-~9s7yD+Gx@bt@n103|2K>z>% literal 0 HcmV?d00001 diff --git a/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_32x32@2x.png b/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_32x32@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c5e458164974ccedc45d8f686c1cbc5ecb3646b1 GIT binary patch literal 2423 zcmV--35fQIP)OP}II-HMiDGqRVn7K((pDbE8mrN^lrgbriaKdb zYf3;A4XrfWbSVP7J|8!KIdq-ht66W-YzLd`_ZjB#sMQJiXI2Ki7N8I0xd1=n^ET2I z`Lp^$o(u3I!@`FVKyS%&0bXKw;z0#4)@Sk$D}XVtpn6OGVFkb%YOyl-^)nCh#p|-S z(%!NU=xEJJ@@Y6nC+pA9N7bK_OY!@9Apiz{i{ZKHe&HK=zoqc@Xu3N(NE2D-aF-+s zM4IS6e<(XhryI|!@i+un!0^I!zHEEpE{UABmf4?2HaQL&?-ke_tQh;NEW1Z804}ng zPRQ2|HYg_DfA0<@bSKk)TS6$f^%AA^X43H3uwo3NMH>NL%eH?k0=%T{7kygiub6Oh zPdaVNb-HhybK&o)Zm>~3Ze(nP-Y(odr2vzq%d8Nfl}RFbM^BN)(@q z_Yqx40d0%oBM|^qUf9s+kX92zl*ny&>0c%-f{&vQ&{}}dwn+6vd+zmVH8J;+0}6tF zEceo10j_k!sVCYs+@saRb`5eXCyvwN`8GB-mH`0) z>vYry{B6>Tt_f#ZXD19z)cD3m$D5(=aN;=M_@2AF`_IF|!qVN`++NU$01gH2ipioc znChC;-VF8lhN0$31)5Ihr$K;~AdO>uGc&ViPnNG2Pgz^9+x815MT|?HTLY;;}8@Slph%xNzD6QOP4PF1$+M~ zoeA)t=8K96WBdEMh8^7I+_x#SFIO@4y;9F9+v}w6n{=q^WAdv0gg&nI(OLj&Yik#O zfB(q1xVX`)SFaKTICSXHX~ylxJT-J9fNkzO)IV}ZG3lnE77A;RBHxBD=ycP0is?+C zzI*+OG1q%iHQsMy&UPAPUFF1^A;-znGTn`iCY~Th{9aK}5v8Q0PoSdJ1_J-6_gCyPYj-_N|i zQRaOkoeLmOi4UueOT`RJnjf3^zgWK@7p8Qe;(?a8H*Wui_9lDM$#`FijE$5$TU}jE zJv}`VaiFB6gpl){ot@O(-A#psg>?Av;fsvZPx%kKsu>kPn~f6;$K_fbxTd{Jhm(%e z`PeThDJeIdwYA;gwu}#xOC4moHy#&p18HPrQwEdL*VTkn{4B zu ztgMXK`X7%&UWP?ed4EO)7=IX2akpAy=lEW-2ZhFk(e2x}sj8|<%Il`4CTebOrk0i# zH9-gfFGGab*jOnt@HqgXfW*Yav9)X0x-vdXwekLp3jkbfyDY8snreV|;wP9im6w-G z9!D{Q0I1{;L6Cz0hysXl>Cz>_a)&Wu9K=E`@bmM_kadJ}XN3S0;r$y2CGuFpkmEHq zHIe}J_4SG<5NUwVA|fKB@nW193!oNk*|OyTxB_eFdl#yLJ$hKWg8bY8R1b81$e!32rcJ@x&<(CoOmF+O-d!6J8WifMUnCuu@LrZ!|hUPEL*_hFlN`6dD@Z zziQPgH~x6sx3%f|9=Ab&r*`h#xgQZA!Wjaf*sKl{ z`~8fE{=C^vUI<|6;^MLg{k{+YVz5Fc;sf!NFadH*Y@3?z4)oS|aa_^hJR0 zE?KhVRp#+A5CX8&0ceZ>AqHO2-eNgqAwTTx?Hy-jW#z~#z7=0tRQf_*JdcQeV+E_; z1uT?DaEb$<(?e7M_#5xB)nJevau$1dczm^N*|Ht%x?l2wXTd$MSLEegCqnm?g$oyM zWj($%&ilqt_?vIu~$ai}?QCoWCyO?=Rq_=gNrdHTm&E p7;|FczvP(6e#uqI!dxe${|}^_Xq3Q%8$kd7002ovPDHLkV1fiMq&)xt literal 0 HcmV?d00001 diff --git a/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_512x512.png b/examples/cocoa-application/CocoaApplication/dmg.iconset/icon_512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..069b5c050245260272c21e9839b43593124370e5 GIT binary patch literal 26984 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 z + + + 1080 + 11D50 + 2457 + 1138.32 + 568.00 + + com.apple.InterfaceBuilder.CocoaPlugin + 2457 + + + NSWindowTemplate + NSView + NSMenu + NSMenuItem + NSCustomObject + + + com.apple.InterfaceBuilder.CocoaPlugin + + + PluginDependencyRecalculationVersion + + + + + NSApplication + + + FirstResponder + + + NSApplication + + + AMainMenu + + + + CocoaApplication + + 1048576 + 2147483647 + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + + submenuAction: + + CocoaApplication + + + + About CocoaApplication + + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Preferences… + , + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Services + + 1048576 + 2147483647 + + + submenuAction: + + Services + + _NSServicesMenu + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Hide CocoaApplication + h + 1048576 + 2147483647 + + + + + + Hide Others + h + 1572864 + 2147483647 + + + + + + Show All + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Quit CocoaApplication + q + 1048576 + 2147483647 + + + + + _NSAppleMenu + + + + + File + + 1048576 + 2147483647 + + + submenuAction: + + File + + + + New + n + 1048576 + 2147483647 + + + + + + Open… + o + 1048576 + 2147483647 + + + + + + Open Recent + + 1048576 + 2147483647 + + + submenuAction: + + Open Recent + + + + Clear Menu + + 1048576 + 2147483647 + + + + + _NSRecentDocumentsMenu + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Close + w + 1048576 + 2147483647 + + + + + + Save… + s + 1048576 + 2147483647 + + + + + + Revert to Saved + + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Page Setup... + P + 1179648 + 2147483647 + + + + + + + Print… + p + 1048576 + 2147483647 + + + + + + + + + Edit + + 1048576 + 2147483647 + + + submenuAction: + + Edit + + + + Undo + z + 1048576 + 2147483647 + + + + + + Redo + Z + 1179648 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Cut + x + 1048576 + 2147483647 + + + + + + Copy + c + 1048576 + 2147483647 + + + + + + Paste + v + 1048576 + 2147483647 + + + + + + Paste and Match Style + V + 1572864 + 2147483647 + + + + + + Delete + + 1048576 + 2147483647 + + + + + + Select All + a + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Find + + 1048576 + 2147483647 + + + submenuAction: + + Find + + + + Find… + f + 1048576 + 2147483647 + + + 1 + + + + Find and Replace… + f + 1572864 + 2147483647 + + + 12 + + + + Find Next + g + 1048576 + 2147483647 + + + 2 + + + + Find Previous + G + 1179648 + 2147483647 + + + 3 + + + + Use Selection for Find + e + 1048576 + 2147483647 + + + 7 + + + + Jump to Selection + j + 1048576 + 2147483647 + + + + + + + + + Spelling and Grammar + + 1048576 + 2147483647 + + + submenuAction: + + Spelling and Grammar + + + + Show Spelling and Grammar + : + 1048576 + 2147483647 + + + + + + Check Document Now + ; + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Check Spelling While Typing + + 1048576 + 2147483647 + + + + + + Check Grammar With Spelling + + 1048576 + 2147483647 + + + + + + Correct Spelling Automatically + + 2147483647 + + + + + + + + + Substitutions + + 1048576 + 2147483647 + + + submenuAction: + + Substitutions + + + + Show Substitutions + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Smart Copy/Paste + f + 1048576 + 2147483647 + + + 1 + + + + Smart Quotes + g + 1048576 + 2147483647 + + + 2 + + + + Smart Dashes + + 2147483647 + + + + + + Smart Links + G + 1179648 + 2147483647 + + + 3 + + + + Text Replacement + + 2147483647 + + + + + + + + + Transformations + + 2147483647 + + + submenuAction: + + Transformations + + + + Make Upper Case + + 2147483647 + + + + + + Make Lower Case + + 2147483647 + + + + + + Capitalize + + 2147483647 + + + + + + + + + Speech + + 1048576 + 2147483647 + + + submenuAction: + + Speech + + + + Start Speaking + + 1048576 + 2147483647 + + + + + + Stop Speaking + + 1048576 + 2147483647 + + + + + + + + + + + + Format + + 2147483647 + + + submenuAction: + + Format + + + + Font + + 2147483647 + + + submenuAction: + + Font + + + + Show Fonts + t + 1048576 + 2147483647 + + + + + + Bold + b + 1048576 + 2147483647 + + + 2 + + + + Italic + i + 1048576 + 2147483647 + + + 1 + + + + Underline + u + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Bigger + + + 1048576 + 2147483647 + + + 3 + + + + Smaller + - + 1048576 + 2147483647 + + + 4 + + + + YES + YES + + + 2147483647 + + + + + + Kern + + 2147483647 + + + submenuAction: + + Kern + + + + Use Default + + 2147483647 + + + + + + Use None + + 2147483647 + + + + + + Tighten + + 2147483647 + + + + + + Loosen + + 2147483647 + + + + + + + + + Ligatures + + 2147483647 + + + submenuAction: + + Ligatures + + + + Use Default + + 2147483647 + + + + + + Use None + + 2147483647 + + + + + + Use All + + 2147483647 + + + + + + + + + Baseline + + 2147483647 + + + submenuAction: + + Baseline + + + + Use Default + + 2147483647 + + + + + + Superscript + + 2147483647 + + + + + + Subscript + + 2147483647 + + + + + + Raise + + 2147483647 + + + + + + Lower + + 2147483647 + + + + + + + + + YES + YES + + + 2147483647 + + + + + + Show Colors + C + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Copy Style + c + 1572864 + 2147483647 + + + + + + Paste Style + v + 1572864 + 2147483647 + + + + + _NSFontMenu + + + + + Text + + 2147483647 + + + submenuAction: + + Text + + + + Align Left + { + 1048576 + 2147483647 + + + + + + Center + | + 1048576 + 2147483647 + + + + + + Justify + + 2147483647 + + + + + + Align Right + } + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Writing Direction + + 2147483647 + + + submenuAction: + + Writing Direction + + + + YES + Paragraph + + 2147483647 + + + + + + CURlZmF1bHQ + + 2147483647 + + + + + + CUxlZnQgdG8gUmlnaHQ + + 2147483647 + + + + + + CVJpZ2h0IHRvIExlZnQ + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + YES + Selection + + 2147483647 + + + + + + CURlZmF1bHQ + + 2147483647 + + + + + + CUxlZnQgdG8gUmlnaHQ + + 2147483647 + + + + + + CVJpZ2h0IHRvIExlZnQ + + 2147483647 + + + + + + + + + YES + YES + + + 2147483647 + + + + + + Show Ruler + + 2147483647 + + + + + + Copy Ruler + c + 1310720 + 2147483647 + + + + + + Paste Ruler + v + 1310720 + 2147483647 + + + + + + + + + + + + View + + 1048576 + 2147483647 + + + submenuAction: + + View + + + + Show Toolbar + t + 1572864 + 2147483647 + + + + + + Customize Toolbar… + + 1048576 + 2147483647 + + + + + + + + + Window + + 1048576 + 2147483647 + + + submenuAction: + + Window + + + + Minimize + m + 1048576 + 2147483647 + + + + + + Zoom + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Bring All to Front + + 1048576 + 2147483647 + + + + + _NSWindowsMenu + + + + + Help + + 2147483647 + + + submenuAction: + + Help + + + + CocoaApplication Help + ? + 1048576 + 2147483647 + + + + + _NSHelpMenu + + + + _NSMainMenu + + + 15 + 2 + {{335, 390}, {480, 360}} + 1954021376 + CocoaApplication + NSWindow + + + + + 256 + {480, 360} + + {{0, 0}, {2560, 1418}} + {10000000000000, 10000000000000} + YES + + + AppDelegate + + + NSFontManager + + + + + + + terminate: + + + + 449 + + + + orderFrontStandardAboutPanel: + + + + 142 + + + + delegate + + + + 495 + + + + performMiniaturize: + + + + 37 + + + + arrangeInFront: + + + + 39 + + + + print: + + + + 86 + + + + runPageLayout: + + + + 87 + + + + clearRecentDocuments: + + + + 127 + + + + performClose: + + + + 193 + + + + toggleContinuousSpellChecking: + + + + 222 + + + + undo: + + + + 223 + + + + copy: + + + + 224 + + + + checkSpelling: + + + + 225 + + + + paste: + + + + 226 + + + + stopSpeaking: + + + + 227 + + + + cut: + + + + 228 + + + + showGuessPanel: + + + + 230 + + + + redo: + + + + 231 + + + + selectAll: + + + + 232 + + + + startSpeaking: + + + + 233 + + + + delete: + + + + 235 + + + + performZoom: + + + + 240 + + + + performFindPanelAction: + + + + 241 + + + + centerSelectionInVisibleArea: + + + + 245 + + + + toggleGrammarChecking: + + + + 347 + + + + toggleSmartInsertDelete: + + + + 355 + + + + toggleAutomaticQuoteSubstitution: + + + + 356 + + + + toggleAutomaticLinkDetection: + + + + 357 + + + + saveDocument: + + + + 362 + + + + revertDocumentToSaved: + + + + 364 + + + + runToolbarCustomizationPalette: + + + + 365 + + + + toggleToolbarShown: + + + + 366 + + + + hide: + + + + 367 + + + + hideOtherApplications: + + + + 368 + + + + unhideAllApplications: + + + + 370 + + + + newDocument: + + + + 373 + + + + openDocument: + + + + 374 + + + + raiseBaseline: + + + + 426 + + + + lowerBaseline: + + + + 427 + + + + copyFont: + + + + 428 + + + + subscript: + + + + 429 + + + + superscript: + + + + 430 + + + + tightenKerning: + + + + 431 + + + + underline: + + + + 432 + + + + orderFrontColorPanel: + + + + 433 + + + + useAllLigatures: + + + + 434 + + + + loosenKerning: + + + + 435 + + + + pasteFont: + + + + 436 + + + + unscript: + + + + 437 + + + + useStandardKerning: + + + + 438 + + + + useStandardLigatures: + + + + 439 + + + + turnOffLigatures: + + + + 440 + + + + turnOffKerning: + + + + 441 + + + + toggleAutomaticSpellingCorrection: + + + + 456 + + + + orderFrontSubstitutionsPanel: + + + + 458 + + + + toggleAutomaticDashSubstitution: + + + + 461 + + + + toggleAutomaticTextReplacement: + + + + 463 + + + + uppercaseWord: + + + + 464 + + + + capitalizeWord: + + + + 467 + + + + lowercaseWord: + + + + 468 + + + + pasteAsPlainText: + + + + 486 + + + + performFindPanelAction: + + + + 487 + + + + performFindPanelAction: + + + + 488 + + + + performFindPanelAction: + + + + 489 + + + + showHelp: + + + + 493 + + + + alignCenter: + + + + 518 + + + + pasteRuler: + + + + 519 + + + + toggleRuler: + + + + 520 + + + + alignRight: + + + + 521 + + + + copyRuler: + + + + 522 + + + + alignJustified: + + + + 523 + + + + alignLeft: + + + + 524 + + + + makeBaseWritingDirectionNatural: + + + + 525 + + + + makeBaseWritingDirectionLeftToRight: + + + + 526 + + + + makeBaseWritingDirectionRightToLeft: + + + + 527 + + + + makeTextWritingDirectionNatural: + + + + 528 + + + + makeTextWritingDirectionLeftToRight: + + + + 529 + + + + makeTextWritingDirectionRightToLeft: + + + + 530 + + + + performFindPanelAction: + + + + 535 + + + + addFontTrait: + + + + 421 + + + + addFontTrait: + + + + 422 + + + + modifyFont: + + + + 423 + + + + orderFrontFontPanel: + + + + 424 + + + + modifyFont: + + + + 425 + + + + window + + + + 532 + + + + + + 0 + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 29 + + + + + + + + + + + + + + 19 + + + + + + + + 56 + + + + + + + + 217 + + + + + + + + 83 + + + + + + + + 81 + + + + + + + + + + + + + + + + + 75 + + + + + 78 + + + + + 72 + + + + + 82 + + + + + 124 + + + + + + + + 77 + + + + + 73 + + + + + 79 + + + + + 112 + + + + + 74 + + + + + 125 + + + + + + + + 126 + + + + + 205 + + + + + + + + + + + + + + + + + + + + + + 202 + + + + + 198 + + + + + 207 + + + + + 214 + + + + + 199 + + + + + 203 + + + + + 197 + + + + + 206 + + + + + 215 + + + + + 218 + + + + + + + + 216 + + + + + + + + 200 + + + + + + + + + + + + + 219 + + + + + 201 + + + + + 204 + + + + + 220 + + + + + + + + + + + + + 213 + + + + + 210 + + + + + 221 + + + + + 208 + + + + + 209 + + + + + 57 + + + + + + + + + + + + + + + + + + 58 + + + + + 134 + + + + + 150 + + + + + 136 + + + + + 144 + + + + + 129 + + + + + 143 + + + + + 236 + + + + + 131 + + + + + + + + 149 + + + + + 145 + + + + + 130 + + + + + 24 + + + + + + + + + + + 92 + + + + + 5 + + + + + 239 + + + + + 23 + + + + + 295 + + + + + + + + 296 + + + + + + + + + 297 + + + + + 298 + + + + + 211 + + + + + + + + 212 + + + + + + + + + 195 + + + + + 196 + + + + + 346 + + + + + 348 + + + + + + + + 349 + + + + + + + + + + + + + + 350 + + + + + 351 + + + + + 354 + + + + + 371 + + + + + + + + 372 + + + + + 375 + + + + + + + + 376 + + + + + + + + + 377 + + + + + + + + 388 + + + + + + + + + + + + + + + + + + + + + + + 389 + + + + + 390 + + + + + 391 + + + + + 392 + + + + + 393 + + + + + 394 + + + + + 395 + + + + + 396 + + + + + 397 + + + + + + + + 398 + + + + + + + + 399 + + + + + + + + 400 + + + + + 401 + + + + + 402 + + + + + 403 + + + + + 404 + + + + + 405 + + + + + + + + + + + + 406 + + + + + 407 + + + + + 408 + + + + + 409 + + + + + 410 + + + + + 411 + + + + + + + + + + 412 + + + + + 413 + + + + + 414 + + + + + 415 + + + + + + + + + + + 416 + + + + + 417 + + + + + 418 + + + + + 419 + + + + + 420 + + + + + 450 + + + + + + + + 451 + + + + + + + + + + 452 + + + + + 453 + + + + + 454 + + + + + 457 + + + + + 459 + + + + + 460 + + + + + 462 + + + + + 465 + + + + + 466 + + + + + 485 + + + + + 490 + + + + + + + + 491 + + + + + + + + 492 + + + + + 494 + + + + + 496 + + + + + + + + 497 + + + + + + + + + + + + + + + + + 498 + + + + + 499 + + + + + 500 + + + + + 501 + + + + + 502 + + + + + 503 + + + + + + + + 504 + + + + + 505 + + + + + 506 + + + + + 507 + + + + + 508 + + + + + + + + + + + + + + + + 509 + + + + + 510 + + + + + 511 + + + + + 512 + + + + + 513 + + + + + 514 + + + + + 515 + + + + + 516 + + + + + 517 + + + + + 534 + + + + + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{380, 496}, {480, 360}} + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + + + + 535 + + + + + ABCardController + NSObject + + id + id + id + id + id + id + id + + + + addCardViewField: + id + + + copy: + id + + + cut: + id + + + doDelete: + id + + + find: + id + + + paste: + id + + + saveChanges: + id + + + + ABCardView + NSButton + NSManagedObjectContext + NSSearchField + NSTextField + NSWindow + + + + mCardView + ABCardView + + + mEditButton + NSButton + + + mManagedObjectContext + NSManagedObjectContext + + + mSearchField + NSSearchField + + + mStatusTextField + NSTextField + + + mWindow + NSWindow + + + + IBProjectSource + ./Classes/ABCardController.h + + + + ABCardView + NSView + + id + id + + + + commitAndSave: + id + + + statusImageClicked: + id + + + + NSObjectController + NSImageView + NSView + ABNameFrameView + NSView + NSImage + ABImageView + + + + mBindingsController + NSObjectController + + + mBuddyStatusImage + NSImageView + + + mHeaderView + NSView + + + mNameView + ABNameFrameView + + + mNextKeyView + NSView + + + mUserImage + NSImage + + + mUserImageView + ABImageView + + + + IBProjectSource + ./Classes/ABCardView.h + + + + ABImageView + NSImageView + + id + id + id + id + + + + copy: + id + + + cut: + id + + + delete: + id + + + paste: + id + + + + IBProjectSource + ./Classes/ABImageView.h + + + + DVTBorderedView + DVTLayoutView_ML + + contentView + NSView + + + contentView + + contentView + NSView + + + + IBProjectSource + ./Classes/DVTBorderedView.h + + + + DVTDelayedMenuButton + NSButton + + IBProjectSource + ./Classes/DVTDelayedMenuButton.h + + + + DVTGradientImageButton + NSButton + + IBProjectSource + ./Classes/DVTGradientImageButton.h + + + + DVTImageAndTextCell + NSTextFieldCell + + IBProjectSource + ./Classes/DVTImageAndTextCell.h + + + + DVTImageAndTextColumn + NSTableColumn + + IBProjectSource + ./Classes/DVTImageAndTextColumn.h + + + + DVTLayoutView_ML + NSView + + IBProjectSource + ./Classes/DVTLayoutView_ML.h + + + + DVTOutlineView + NSOutlineView + + IBProjectSource + ./Classes/DVTOutlineView.h + + + + DVTSplitView + NSSplitView + + IBProjectSource + ./Classes/DVTSplitView.h + + + + DVTStackView_ML + DVTLayoutView_ML + + IBProjectSource + ./Classes/DVTStackView_ML.h + + + + DVTTableView + NSTableView + + IBProjectSource + ./Classes/DVTTableView.h + + + + DVTViewController + NSViewController + + IBProjectSource + ./Classes/DVTViewController.h + + + + HFController + NSObject + + selectAll: + id + + + selectAll: + + selectAll: + id + + + + IBProjectSource + ./Classes/HFController.h + + + + HFRepresenterTextView + NSView + + selectAll: + id + + + selectAll: + + selectAll: + id + + + + IBProjectSource + ./Classes/HFRepresenterTextView.h + + + + IBEditor + NSObject + + id + id + id + id + id + + + + changeFont: + id + + + performCopy: + id + + + performCut: + id + + + selectAll: + id + + + sizeSelectionToFit: + id + + + + IBProjectSource + ./Classes/IBEditor.h + + + + IDECapsuleListView + DVTStackView_ML + + dataSource + id + + + dataSource + + dataSource + id + + + + IBProjectSource + ./Classes/IDECapsuleListView.h + + + + IDEDMArrayController + NSArrayController + + IBProjectSource + ./Classes/IDEDMArrayController.h + + + + IDEDMEditor + IDEEditor + + DVTBorderedView + NSView + IDEDMEditorSourceListController + DVTSplitView + + + + bottomToolbarBorderView + DVTBorderedView + + + sourceListSplitViewPane + NSView + + + sourceListViewController + IDEDMEditorSourceListController + + + splitView + DVTSplitView + + + + IBProjectSource + ./Classes/IDEDMEditor.h + + + + IDEDMEditorController + IDEViewController + + IBProjectSource + ./Classes/IDEDMEditorController.h + + + + IDEDMEditorSourceListController + IDEDMEditorController + + DVTBorderedView + IDEDMEditor + DVTImageAndTextColumn + DVTOutlineView + NSTreeController + + + + borderedView + DVTBorderedView + + + parentEditor + IDEDMEditor + + + primaryColumn + DVTImageAndTextColumn + + + sourceListOutlineView + DVTOutlineView + + + sourceListTreeController + NSTreeController + + + + IBProjectSource + ./Classes/IDEDMEditorSourceListController.h + + + + IDEDMHighlightImageAndTextCell + DVTImageAndTextCell + + IBProjectSource + ./Classes/IDEDMHighlightImageAndTextCell.h + + + + IDEDataModelBrowserEditor + IDEDMEditorController + + IDEDataModelPropertiesTableController + IDECapsuleListView + NSArrayController + IDEDataModelPropertiesTableController + IDEDataModelEntityContentsEditor + IDEDataModelPropertiesTableController + + + + attributesTableViewController + IDEDataModelPropertiesTableController + + + capsuleView + IDECapsuleListView + + + entityArrayController + NSArrayController + + + fetchedPropertiesTableViewController + IDEDataModelPropertiesTableController + + + parentEditor + IDEDataModelEntityContentsEditor + + + relationshipsTableViewController + IDEDataModelPropertiesTableController + + + + IBProjectSource + ./Classes/IDEDataModelBrowserEditor.h + + + + IDEDataModelConfigurationEditor + IDEDMEditorController + + IDECapsuleListView + IDEDataModelEditor + IDEDataModelConfigurationTableController + + + + capsuleListView + IDECapsuleListView + + + parentEditor + IDEDataModelEditor + + + tableController + IDEDataModelConfigurationTableController + + + + IBProjectSource + ./Classes/IDEDataModelConfigurationEditor.h + + + + IDEDataModelConfigurationTableController + IDEDMEditorController + + NSArrayController + NSArrayController + IDEDataModelConfigurationEditor + XDTableView + + + + configurationsArrayController + NSArrayController + + + entitiesArrayController + NSArrayController + + + parentEditor + IDEDataModelConfigurationEditor + + + tableView + XDTableView + + + + IBProjectSource + ./Classes/IDEDataModelConfigurationTableController.h + + + + IDEDataModelDiagramEditor + IDEDMEditorController + + XDDiagramView + IDEDataModelEntityContentsEditor + + + + diagramView + XDDiagramView + + + parentEditor + IDEDataModelEntityContentsEditor + + + + IBProjectSource + ./Classes/IDEDataModelDiagramEditor.h + + + + IDEDataModelEditor + IDEDMEditor + + DVTDelayedMenuButton + DVTDelayedMenuButton + NSSegmentedControl + IDEDataModelConfigurationEditor + IDEDataModelEntityContentsEditor + IDEDataModelFetchRequestEditor + NSSegmentedControl + NSTabView + + + + addEntityButton + DVTDelayedMenuButton + + + addPropertyButton + DVTDelayedMenuButton + + + browserDiagramSegmentControl + NSSegmentedControl + + + configurationViewController + IDEDataModelConfigurationEditor + + + entityContentsViewController + IDEDataModelEntityContentsEditor + + + fetchRequestViewController + IDEDataModelFetchRequestEditor + + + hierarchySegmentControl + NSSegmentedControl + + + tabView + NSTabView + + + + IBProjectSource + ./Classes/IDEDataModelEditor.h + + + + IDEDataModelEntityContentsEditor + IDEDMEditorController + + IDEDataModelBrowserEditor + IDEDataModelDiagramEditor + IDEDataModelEditor + NSTabView + + + + browserViewController + IDEDataModelBrowserEditor + + + diagramViewController + IDEDataModelDiagramEditor + + + parentEditor + IDEDataModelEditor + + + tabView + NSTabView + + + + IBProjectSource + ./Classes/IDEDataModelEntityContentsEditor.h + + + + IDEDataModelFetchRequestEditor + IDEDMEditorController + + NSArrayController + IDEDataModelEditor + IDECapsuleListView + + + + entityController + NSArrayController + + + parentEditor + IDEDataModelEditor + + + tableView + IDECapsuleListView + + + + IBProjectSource + ./Classes/IDEDataModelFetchRequestEditor.h + + + + IDEDataModelPropertiesTableController + IDEDMEditorController + + IDEDMArrayController + NSTableColumn + NSArrayController + IDEDataModelBrowserEditor + IDEDMHighlightImageAndTextCell + XDTableView + + + + arrayController + IDEDMArrayController + + + entitiesColumn + NSTableColumn + + + entityArrayController + NSArrayController + + + parentEditor + IDEDataModelBrowserEditor + + + propertyNameAndImageCell + IDEDMHighlightImageAndTextCell + + + tableView + XDTableView + + + + IBProjectSource + ./Classes/IDEDataModelPropertiesTableController.h + + + + IDEDocDownloadsTableViewController + NSObject + + NSButtonCell + DVTTableView + IDEDocViewingPrefPaneController + + + + _downloadButtonCell + NSButtonCell + + + _tableView + DVTTableView + + + prefPaneController + IDEDocViewingPrefPaneController + + + + IBProjectSource + ./Classes/IDEDocDownloadsTableViewController.h + + + + IDEDocSetOutlineView + NSOutlineView + + IBProjectSource + ./Classes/IDEDocSetOutlineView.h + + + + IDEDocSetOutlineViewController + NSObject + + id + id + id + id + id + + + + getDocSetAction: + id + + + showProblemInfoForUpdate: + id + + + subscribeToPublisherAction: + id + + + unsubscribeFromPublisher: + id + + + updateDocSetAction: + id + + + + docSetOutlineView + IDEDocSetOutlineView + + + docSetOutlineView + + docSetOutlineView + IDEDocSetOutlineView + + + + IBProjectSource + ./Classes/IDEDocSetOutlineViewController.h + + + + IDEDocViewingPrefPaneController + IDEViewController + + id + id + id + id + id + id + id + id + id + id + id + + + + addSubscription: + id + + + checkForAndInstallUpdatesNow: + id + + + deleteDocSet: + id + + + downloadAction: + id + + + minimumFontSizeComboBoxAction: + id + + + minimumFontSizeEnabledAction: + id + + + showHelp: + id + + + showSubscriptionSheet: + id + + + subscriptionCancelAction: + id + + + toggleAutoCheckForAndInstallUpdates: + id + + + toggleDocSetInfo: + id + + + + DVTGradientImageButton + DVTGradientImageButton + DVTGradientImageButton + NSSplitView + NSView + NSView + DVTBorderedView + DVTBorderedView + NSButton + NSTextView + IDEDocSetOutlineViewController + IDEDocDownloadsTableViewController + NSComboBox + NSTextField + NSButton + NSTextField + NSWindow + NSButton + + + + _addButton + DVTGradientImageButton + + + _deleteButton + DVTGradientImageButton + + + _showInfoAreaButton + DVTGradientImageButton + + + _splitView + NSSplitView + + + _splitViewDocSetInfoSubview + NSView + + + _splitViewDocSetsListSubview + NSView + + + borderedViewAroundSplitView + DVTBorderedView + + + borderedViewBelowTable + DVTBorderedView + + + checkAndInstallNowButton + NSButton + + + docSetInfoTextView + NSTextView + + + docSetOutlineViewController + IDEDocSetOutlineViewController + + + downloadsTableViewController + IDEDocDownloadsTableViewController + + + minimumFontSizeControl + NSComboBox + + + noUpdatesAvailableMessage + NSTextField + + + showInfoButton + NSButton + + + subscriptionTextField + NSTextField + + + subscriptionWindow + NSWindow + + + validateAddSubscriptionButton + NSButton + + + + IBProjectSource + ./Classes/IDEDocViewingPrefPaneController.h + + + + IDEEditor + IDEViewController + + IBProjectSource + ./Classes/IDEEditor.h + + + + IDEViewController + DVTViewController + + IBProjectSource + ./Classes/IDEViewController.h + + + + IKImageView + + id + id + id + id + + + + copy: + id + + + crop: + id + + + cut: + id + + + paste: + id + + + + IBProjectSource + ./Classes/IKImageView.h + + + + NSDocument + + id + id + id + id + id + id + + + + printDocument: + id + + + revertDocumentToSaved: + id + + + runPageLayout: + id + + + saveDocument: + id + + + saveDocumentAs: + id + + + saveDocumentTo: + id + + + + IBProjectSource + ./Classes/NSDocument.h + + + + NSResponder + + _insertFindPattern: + id + + + _insertFindPattern: + + _insertFindPattern: + id + + + + IBProjectSource + ./Classes/NSResponder.h + + + + QLPreviewBubble + NSObject + + id + id + + + + hide: + id + + + show: + id + + + + parentWindow + NSWindow + + + parentWindow + + parentWindow + NSWindow + + + + IBProjectSource + ./Classes/QLPreviewBubble.h + + + + QTMovieView + + id + id + id + id + id + + + + showAll: + id + + + showCustomButton: + id + + + toggleLoops: + id + + + zoomIn: + id + + + zoomOut: + id + + + + IBProjectSource + ./Classes/QTMovieView.h + + + + WebView + + id + id + id + id + + + + reloadFromOrigin: + id + + + resetPageZoom: + id + + + zoomPageIn: + id + + + zoomPageOut: + id + + + + IBProjectSource + ./Classes/WebView.h + + + + XDDiagramView + NSView + + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + + + + _graphLayouterMenuItemAction: + id + + + _zoomPopUpButtonAction: + id + + + alignBottomEdges: + id + + + alignCentersHorizontallyInContainer: + id + + + alignCentersVerticallyInContainer: + id + + + alignHorizontalCenters: + id + + + alignLeftEdges: + id + + + alignRightEdges: + id + + + alignTopEdges: + id + + + alignVerticalCenters: + id + + + bringToFront: + id + + + collapseAllCompartments: + id + + + copy: + id + + + cut: + id + + + delete: + id + + + deleteBackward: + id + + + deleteForward: + id + + + deselectAll: + id + + + diagramZoomIn: + id + + + diagramZoomOut: + id + + + expandAllCompartments: + id + + + flipHorizontally: + id + + + flipVertically: + id + + + layoutGraphicsConcentrically: + id + + + layoutGraphicsHierarchically: + id + + + lock: + id + + + makeSameHeight: + id + + + makeSameWidth: + id + + + moveDown: + id + + + moveDownAndModifySelection: + id + + + moveLeft: + id + + + moveLeftAndModifySelection: + id + + + moveRight: + id + + + moveRightAndModifySelection: + id + + + moveUp: + id + + + moveUpAndModifySelection: + id + + + paste: + id + + + rollDownAllCompartments: + id + + + rollUpAllCompartments: + id + + + selectAll: + id + + + sendToBack: + id + + + sizeToFit: + id + + + toggleGridShown: + id + + + toggleHiddenGraphicsShown: + id + + + togglePageBreaksShown: + id + + + toggleRuler: + id + + + toggleSnapsToGrid: + id + + + unlock: + id + + + + _diagramController + IDEDataModelDiagramEditor + + + _diagramController + + _diagramController + IDEDataModelDiagramEditor + + + + IBProjectSource + ./Classes/XDDiagramView.h + + + + XDTableView + NSTableView + + showAllTableColumns: + id + + + showAllTableColumns: + + showAllTableColumns: + id + + + + IBProjectSource + ./Classes/XDTableView.h + + + + AppDelegate + NSObject + + id + id + + + + applicationShouldTerminate: + id + + + applicationWillFinishLaunching: + id + + + + IBProjectSource + ./Classes/AppDelegate.h + + + + + 0 + IBCocoaFramework + YES + 3 + + {11, 11} + {10, 3} + + YES + + diff --git a/examples/cocoa-application/CocoaApplication/en_US.lproj b/examples/cocoa-application/CocoaApplication/en_US.lproj new file mode 120000 index 00000000..545e7ac7 --- /dev/null +++ b/examples/cocoa-application/CocoaApplication/en_US.lproj @@ -0,0 +1 @@ +en.lproj \ No newline at end of file diff --git a/examples/cocoa-application/CocoaApplication/main.m b/examples/cocoa-application/CocoaApplication/main.m new file mode 100644 index 00000000..18b23e7a --- /dev/null +++ b/examples/cocoa-application/CocoaApplication/main.m @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 + +int main(int argc, char *argv[]) +{ + return NSApplicationMain(argc, (const char **)argv); +} diff --git a/examples/cocoa-application/app.qbs b/examples/cocoa-application/app.qbs new file mode 100644 index 00000000..f51f94e8 --- /dev/null +++ b/examples/cocoa-application/app.qbs @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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 { condition: product.condition; name: "ib" } + condition: qbs.targetOS.contains("macos") + name: "Cocoa Application" + + cpp.useObjcPrecompiledHeader: true + cpp.minimumMacosVersion: "10.8" + cpp.frameworks: ["Cocoa"] + + Group { + prefix: "CocoaApplication/" + files: [ + "AppDelegate.h", + "AppDelegate.m", + "CocoaApplication-Info.plist", + "CocoaApplication.xcassets", + "main.m" + ] + } + + Group { + name: "Supporting Files" + prefix: "CocoaApplication/en.lproj/" + files: [ + "Credits.rtf", + "InfoPlist.strings", + "MainMenu.xib" + ] + } + + Group { + name: "Xcode Project" + files: [ + "CocoaApplication.xcodeproj/project.pbxproj" + ] + } + + Group { + files: ["CocoaApplication/CocoaApplication-Prefix.pch"] + fileTags: ["objc_pch_src"] + } + + Group { + fileTagsFilter: ["bundle.content"] + qbs.install: true + qbs.installDir: "Applications" + qbs.installSourceBase: product.destinationDirectory + } + + ib.appIconName: "AppIcon" +} diff --git a/examples/cocoa-application/cocoa-application.qbs b/examples/cocoa-application/cocoa-application.qbs new file mode 100644 index 00000000..47275876 --- /dev/null +++ b/examples/cocoa-application/cocoa-application.qbs @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 +import qbs.Utilities + +Project { + references: [ + "app.qbs" + ] + + SubProject { + filePath: "dmg.qbs" + Properties { + condition: Utilities.versionCompare(qbs.version, "1.9") >= 0 + } + } +} diff --git a/examples/cocoa-application/dmg.qbs b/examples/cocoa-application/dmg.qbs new file mode 100644 index 00000000..610c5d2a --- /dev/null +++ b/examples/cocoa-application/dmg.qbs @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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 + +AppleApplicationDiskImage { + condition: qbs.targetOS.contains("macos") + name: "Cocoa Application DMG" + targetName: "cocoa-application-" + version + version: "1.0" + + Depends { name: "Cocoa Application" } + Depends { name: "ib" } + + files: [ + "CocoaApplication/dmg.iconset", + "CocoaApplication/en_US.lproj/LICENSE", + ] + + // set to false to use a solid-color background (see dmg.backgroundColor below) + property bool useImageBackground: true + Group { + condition: useImageBackground + files: ["CocoaApplication/background*"] + } + + dmg.backgroundColor: "#41cd52" + dmg.badgeVolumeIcon: true + dmg.iconPositions: [ + {"x": 200, "y": 200, "path": "Cocoa Application.app"}, + {"x": 400, "y": 200, "path": "Applications"} + ] + dmg.windowX: 420 + dmg.windowY: 250 + dmg.windowWidth: 600 + dmg.windowHeight: 422 // this *includes* the macOS title bar height of 22 + dmg.iconSize: 64 +} diff --git a/examples/cocoa-touch-application/CocoaTouchApplication.xcodeproj/project.pbxproj b/examples/cocoa-touch-application/CocoaTouchApplication.xcodeproj/project.pbxproj new file mode 100644 index 00000000..07f3a1a4 --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication.xcodeproj/project.pbxproj @@ -0,0 +1,348 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 14E3FEAA175FB2E800C857C6 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14E3FEA9175FB2E800C857C6 /* UIKit.framework */; }; + 14E3FEAC175FB2E800C857C6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14E3FEAB175FB2E800C857C6 /* Foundation.framework */; }; + 14E3FEAE175FB2E800C857C6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 14E3FEAD175FB2E800C857C6 /* CoreGraphics.framework */; }; + 14E3FEB4175FB2E800C857C6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 14E3FEB2175FB2E800C857C6 /* InfoPlist.strings */; }; + 14E3FEB6175FB2E800C857C6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 14E3FEB5175FB2E800C857C6 /* main.m */; }; + 14E3FEBA175FB2E800C857C6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 14E3FEB9175FB2E800C857C6 /* AppDelegate.m */; }; + 14E3FEBC175FB2E800C857C6 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 14E3FEBB175FB2E800C857C6 /* Default.png */; }; + 14E3FEBE175FB2E800C857C6 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 14E3FEBD175FB2E800C857C6 /* Default@2x.png */; }; + 14E3FEC0175FB2E800C857C6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 14E3FEBF175FB2E800C857C6 /* Default-568h@2x.png */; }; + 14E3FEC3175FB2E800C857C6 /* MasterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 14E3FEC2175FB2E800C857C6 /* MasterViewController.m */; }; + 14E3FEC6175FB2E900C857C6 /* DetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 14E3FEC5175FB2E900C857C6 /* DetailViewController.m */; }; + 14E3FEC9175FB2E900C857C6 /* MasterViewController_iPhone.xib in Resources */ = {isa = PBXBuildFile; fileRef = 14E3FEC7175FB2E900C857C6 /* MasterViewController_iPhone.xib */; }; + 14E3FECC175FB2E900C857C6 /* MasterViewController_iPad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 14E3FECA175FB2E900C857C6 /* MasterViewController_iPad.xib */; }; + 14E3FECF175FB2E900C857C6 /* DetailViewController_iPhone.xib in Resources */ = {isa = PBXBuildFile; fileRef = 14E3FECD175FB2E900C857C6 /* DetailViewController_iPhone.xib */; }; + 14E3FED2175FB2E900C857C6 /* DetailViewController_iPad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 14E3FED0175FB2E900C857C6 /* DetailViewController_iPad.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 14E3FEA6175FB2E800C857C6 /* Cocoa Touch Application.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Cocoa Touch Application.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 14E3FEA9175FB2E800C857C6 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 14E3FEAB175FB2E800C857C6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 14E3FEAD175FB2E800C857C6 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 14E3FEB1175FB2E800C857C6 /* CocoaTouchApplication-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CocoaTouchApplication-Info.plist"; sourceTree = ""; }; + 14E3FEB3175FB2E800C857C6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 14E3FEB5175FB2E800C857C6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 14E3FEB7175FB2E800C857C6 /* CocoaTouchApplication-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CocoaTouchApplication-Prefix.pch"; sourceTree = ""; }; + 14E3FEB8175FB2E800C857C6 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 14E3FEB9175FB2E800C857C6 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 14E3FEBB175FB2E800C857C6 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; + 14E3FEBD175FB2E800C857C6 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; + 14E3FEBF175FB2E800C857C6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; + 14E3FEC1175FB2E800C857C6 /* MasterViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MasterViewController.h; sourceTree = ""; }; + 14E3FEC2175FB2E800C857C6 /* MasterViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MasterViewController.m; sourceTree = ""; }; + 14E3FEC4175FB2E800C857C6 /* DetailViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DetailViewController.h; sourceTree = ""; }; + 14E3FEC5175FB2E900C857C6 /* DetailViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DetailViewController.m; sourceTree = ""; }; + 14E3FEC8175FB2E900C857C6 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MasterViewController_iPhone.xib; sourceTree = ""; }; + 14E3FECB175FB2E900C857C6 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MasterViewController_iPad.xib; sourceTree = ""; }; + 14E3FECE175FB2E900C857C6 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/DetailViewController_iPhone.xib; sourceTree = ""; }; + 14E3FED1175FB2E900C857C6 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/DetailViewController_iPad.xib; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 14E3FEA3175FB2E800C857C6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 14E3FEAA175FB2E800C857C6 /* UIKit.framework in Frameworks */, + 14E3FEAC175FB2E800C857C6 /* Foundation.framework in Frameworks */, + 14E3FEAE175FB2E800C857C6 /* CoreGraphics.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 14E3FE9D175FB2E800C857C6 = { + isa = PBXGroup; + children = ( + 14E3FEAF175FB2E800C857C6 /* CocoaTouchApplication */, + 14E3FEA8175FB2E800C857C6 /* Frameworks */, + 14E3FEA7175FB2E800C857C6 /* Products */, + ); + sourceTree = ""; + }; + 14E3FEA7175FB2E800C857C6 /* Products */ = { + isa = PBXGroup; + children = ( + 14E3FEA6175FB2E800C857C6 /* Cocoa Touch Application.app */, + ); + name = Products; + sourceTree = ""; + }; + 14E3FEA8175FB2E800C857C6 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 14E3FEA9175FB2E800C857C6 /* UIKit.framework */, + 14E3FEAB175FB2E800C857C6 /* Foundation.framework */, + 14E3FEAD175FB2E800C857C6 /* CoreGraphics.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 14E3FEAF175FB2E800C857C6 /* CocoaTouchApplication */ = { + isa = PBXGroup; + children = ( + 14E3FEB8175FB2E800C857C6 /* AppDelegate.h */, + 14E3FEB9175FB2E800C857C6 /* AppDelegate.m */, + 14E3FEC1175FB2E800C857C6 /* MasterViewController.h */, + 14E3FEC2175FB2E800C857C6 /* MasterViewController.m */, + 14E3FEC4175FB2E800C857C6 /* DetailViewController.h */, + 14E3FEC5175FB2E900C857C6 /* DetailViewController.m */, + 14E3FEC7175FB2E900C857C6 /* MasterViewController_iPhone.xib */, + 14E3FECA175FB2E900C857C6 /* MasterViewController_iPad.xib */, + 14E3FECD175FB2E900C857C6 /* DetailViewController_iPhone.xib */, + 14E3FED0175FB2E900C857C6 /* DetailViewController_iPad.xib */, + 14E3FEB0175FB2E800C857C6 /* Supporting Files */, + ); + path = CocoaTouchApplication; + sourceTree = ""; + }; + 14E3FEB0175FB2E800C857C6 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 14E3FEB1175FB2E800C857C6 /* CocoaTouchApplication-Info.plist */, + 14E3FEB2175FB2E800C857C6 /* InfoPlist.strings */, + 14E3FEB5175FB2E800C857C6 /* main.m */, + 14E3FEB7175FB2E800C857C6 /* CocoaTouchApplication-Prefix.pch */, + 14E3FEBB175FB2E800C857C6 /* Default.png */, + 14E3FEBD175FB2E800C857C6 /* Default@2x.png */, + 14E3FEBF175FB2E800C857C6 /* Default-568h@2x.png */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 14E3FEA5175FB2E800C857C6 /* Cocoa Touch Application */ = { + isa = PBXNativeTarget; + buildConfigurationList = 14E3FED5175FB2E900C857C6 /* Build configuration list for PBXNativeTarget "Cocoa Touch Application" */; + buildPhases = ( + 14E3FEA2175FB2E800C857C6 /* Sources */, + 14E3FEA3175FB2E800C857C6 /* Frameworks */, + 14E3FEA4175FB2E800C857C6 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Cocoa Touch Application"; + productName = CocoaTouchApplication; + productReference = 14E3FEA6175FB2E800C857C6 /* Cocoa Touch Application.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 14E3FE9E175FB2E800C857C6 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0460; + ORGANIZATIONNAME = "Petroules Corporation"; + }; + buildConfigurationList = 14E3FEA1175FB2E800C857C6 /* Build configuration list for PBXProject "CocoaTouchApplication" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 14E3FE9D175FB2E800C857C6; + productRefGroup = 14E3FEA7175FB2E800C857C6 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 14E3FEA5175FB2E800C857C6 /* Cocoa Touch Application */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 14E3FEA4175FB2E800C857C6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14E3FEB4175FB2E800C857C6 /* InfoPlist.strings in Resources */, + 14E3FEBC175FB2E800C857C6 /* Default.png in Resources */, + 14E3FEBE175FB2E800C857C6 /* Default@2x.png in Resources */, + 14E3FEC0175FB2E800C857C6 /* Default-568h@2x.png in Resources */, + 14E3FEC9175FB2E900C857C6 /* MasterViewController_iPhone.xib in Resources */, + 14E3FECC175FB2E900C857C6 /* MasterViewController_iPad.xib in Resources */, + 14E3FECF175FB2E900C857C6 /* DetailViewController_iPhone.xib in Resources */, + 14E3FED2175FB2E900C857C6 /* DetailViewController_iPad.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 14E3FEA2175FB2E800C857C6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14E3FEB6175FB2E800C857C6 /* main.m in Sources */, + 14E3FEBA175FB2E800C857C6 /* AppDelegate.m in Sources */, + 14E3FEC3175FB2E800C857C6 /* MasterViewController.m in Sources */, + 14E3FEC6175FB2E900C857C6 /* DetailViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 14E3FEB2175FB2E800C857C6 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 14E3FEB3175FB2E800C857C6 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 14E3FEC7175FB2E900C857C6 /* MasterViewController_iPhone.xib */ = { + isa = PBXVariantGroup; + children = ( + 14E3FEC8175FB2E900C857C6 /* en */, + ); + name = MasterViewController_iPhone.xib; + sourceTree = ""; + }; + 14E3FECA175FB2E900C857C6 /* MasterViewController_iPad.xib */ = { + isa = PBXVariantGroup; + children = ( + 14E3FECB175FB2E900C857C6 /* en */, + ); + name = MasterViewController_iPad.xib; + sourceTree = ""; + }; + 14E3FECD175FB2E900C857C6 /* DetailViewController_iPhone.xib */ = { + isa = PBXVariantGroup; + children = ( + 14E3FECE175FB2E900C857C6 /* en */, + ); + name = DetailViewController_iPhone.xib; + sourceTree = ""; + }; + 14E3FED0175FB2E900C857C6 /* DetailViewController_iPad.xib */ = { + isa = PBXVariantGroup; + children = ( + 14E3FED1175FB2E900C857C6 /* en */, + ); + name = DetailViewController_iPad.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 14E3FED3175FB2E900C857C6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 14E3FED4175FB2E900C857C6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 14E3FED6175FB2E900C857C6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "CocoaTouchApplication/CocoaTouchApplication-Prefix.pch"; + INFOPLIST_FILE = "CocoaTouchApplication/CocoaTouchApplication-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 14E3FED7175FB2E900C857C6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "CocoaTouchApplication/CocoaTouchApplication-Prefix.pch"; + INFOPLIST_FILE = "CocoaTouchApplication/CocoaTouchApplication-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 14E3FEA1175FB2E800C857C6 /* Build configuration list for PBXProject "CocoaTouchApplication" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 14E3FED3175FB2E900C857C6 /* Debug */, + 14E3FED4175FB2E900C857C6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 14E3FED5175FB2E900C857C6 /* Build configuration list for PBXNativeTarget "Cocoa Touch Application" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 14E3FED6175FB2E900C857C6 /* Debug */, + 14E3FED7175FB2E900C857C6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 14E3FE9E175FB2E800C857C6 /* Project object */; +} diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/AppDelegate.h b/examples/cocoa-touch-application/CocoaTouchApplication/AppDelegate.h new file mode 100644 index 00000000..a4b05b98 --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/AppDelegate.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 + +@interface AppDelegate : UIResponder +{ + UIWindow *_window; + UINavigationController *_navigationController; + UISplitViewController *_splitViewController; +} + +@property (nonatomic, retain) UIWindow *window; + +@property (nonatomic, retain) UINavigationController *navigationController; + +@property (nonatomic, retain) UISplitViewController *splitViewController; + +@end diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/AppDelegate.m b/examples/cocoa-touch-application/CocoaTouchApplication/AppDelegate.m new file mode 100644 index 00000000..ed4d9f31 --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/AppDelegate.m @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 "AppDelegate.h" + +#import "MasterViewController.h" + +#import "DetailViewController.h" + +@implementation AppDelegate + +@synthesize window = _window; +@synthesize navigationController = _navigationController; +@synthesize splitViewController = _splitViewController; + +- (void)dealloc +{ + [_window release]; + [_navigationController release]; + [_splitViewController release]; + [super dealloc]; +} + +- (BOOL)application:(UIApplication *) __unused application didFinishLaunchingWithOptions:(NSDictionary *) __unused launchOptions +{ + self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; + // Override point for customization after application launch. + if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { + MasterViewController *masterViewController = [[[MasterViewController alloc] initWithNibName:@"MasterViewController_iPhone" bundle:nil] autorelease]; + self.navigationController = [[[UINavigationController alloc] initWithRootViewController:masterViewController] autorelease]; + self.window.rootViewController = self.navigationController; + } else { + MasterViewController *masterViewController = [[[MasterViewController alloc] initWithNibName:@"MasterViewController_iPad" bundle:nil] autorelease]; + UINavigationController *masterNavigationController = [[[UINavigationController alloc] initWithRootViewController:masterViewController] autorelease]; + + DetailViewController *detailViewController = [[[DetailViewController alloc] initWithNibName:@"DetailViewController_iPad" bundle:nil] autorelease]; + UINavigationController *detailNavigationController = [[[UINavigationController alloc] initWithRootViewController:detailViewController] autorelease]; + + masterViewController.detailViewController = detailViewController; + + self.splitViewController = [[[UISplitViewController alloc] init] autorelease]; + self.splitViewController.delegate = detailViewController; + self.splitViewController.viewControllers = [NSArray arrayWithObjects:masterNavigationController, detailNavigationController, nil]; + + self.window.rootViewController = self.splitViewController; + } + [self.window makeKeyAndVisible]; + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *) __unused application +{ + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *) __unused application +{ + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *) __unused application +{ + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *) __unused application +{ + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *) __unused application +{ + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/CocoaTouchApplication-Info.plist b/examples/cocoa-touch-application/CocoaTouchApplication/CocoaTouchApplication-Info.plist new file mode 100644 index 00000000..f95cb455 --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/CocoaTouchApplication-Info.plist @@ -0,0 +1,55 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + org.example.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarTintParameters + + UINavigationBar + + Style + UIBarStyleDefault + Translucent + + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/CocoaTouchApplication-Prefix.pch b/examples/cocoa-touch-application/CocoaTouchApplication/CocoaTouchApplication-Prefix.pch new file mode 100644 index 00000000..6a90a06f --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/CocoaTouchApplication-Prefix.pch @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 + +#ifndef __IPHONE_4_0 +#warning "This project uses features only available in iOS SDK 4.0 and later." +#endif + +#ifdef __OBJC__ + #import + #import +#endif diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/Default-568h@2x.png b/examples/cocoa-touch-application/CocoaTouchApplication/Default-568h@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0891b7aabfcf3422423b109c8beed2bab838c607 GIT binary patch literal 18594 zcmeI4X;f257Jx&9fS`ixvS;&$x8J@slQFSel)6zJN=?13FB7H(lQjRkSy8x_-S~tvu2gzn1oS+dLcF#eqtq$ z%tf9TTvX?`)R@}3uBI;jzS-=ZR-Td&MHaS&;!0?Ni*#$#`n*~CcQK)Q9vAQ~TUpnI!j)a2biYK^R)M~A5wUDZhx?ULMX z3x1P&qt=trOY6P2U67L=m=U?F|5#Uj(eCueNTZaHs_ceWiHeET+j+tp3Jt9g(ekqP z2WOvfR{qV+9r+o4J5?qK>7;;^+I7tGv-i)es$X_D=EoKF+S?zsyj^oRFElP}c}JT< zd8SUs-?O?}2YD#ngKbnHgzHBcboxK_2r9l(?eNCl-pEzkJm}fY?WC*jnS?VBE4EpY zO$fEejz6fU;W2Kl>JeQBZBl-%Irg`obSlg*@4QB;Dd1H7^Oi5wvt4d{RZ!8Og?^aE z)k0$1g+V3fd(gdQ3d&q2q-FL*uy#}|bc^=VhFsl0jBgUGJ+-s3U8MK9A!YJJMxpci z5hJ%|{DwV48fZn0{n5l$N_KcSb#NKE4plB`9I6Zt=Z!~-zw0{9tg$L&Ju1F0X)Cy8 zKF;(&lJ>x)Jw(=;p~sF(Sd9VWGwFE2rnyS9!f^DZ8+aCLq zQ};>lcJ1GDLqjm6Hd>|Eabno@P`~Bn(~6^aD_#yoEH(a?Nm1S<;S+hSxI5d16^<1lEM3NPFi zkqPrpL)+ zgnseFikg`gJVBha1&7C4;O6>h=dt~`ND+;Zd?W(4v2JIb7Pt>Td42%M-Ju-XAH#Pns762L}K3 zDhvsRqN0Ni(1UrishD2YvV?4*h2iFj$+&N||Fn$4n|^NSU+o?~jq`0jVQt8T9l{7b zXiwwODFh2V!Q6sqP9S>WH$oOf$N~=d0-bqTlD61!=`&0eAP-F>XN?*|gtOXX{ zQVTWyYo4ZK0GAw!GHf|pz9`D;-bbb*5LBX*{bnz|+)$@&P9|ORM2o?95{;ejvo&r- zq8cBhTN6nn)7~W>54U)%-F_-b?YKdfk5I8MHcuzBD5)!;yv#Z&R&^y=@=>VTIMy#r zX&U<=BsPkdqcMe<_}2+>H%XKyrr5ZR8_KVe>ZqYN z^=^~TFD};;rHJ$U;{~w^hYojl4hRI@SH$^K{YEo=sg)WY87r!*7blQK&qnpDo0`Vn zkl)9u9g=mCh&ZCJS(L4yN3k0kQ zuvg$h2KEEk51T+O0JQ+r0`R>g{jvqM0Mr6d3qUOZwE!?PI7HY@CE|dr sfw?Q;rAv?G4&^^8-z_>&sWXMxvD*gPOU4CBe-*@OtE+wfmVJNyHv)PfH~;_u literal 0 HcmV?d00001 diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/Default.png b/examples/cocoa-touch-application/CocoaTouchApplication/Default.png new file mode 100644 index 0000000000000000000000000000000000000000..4c8ca6f693f96d511e9113c0eb59eec552354e42 GIT binary patch literal 6540 zcmeAS@N?(olHy`uVBq!ia0y~yU~~ZD2OMlbkt;o0To@QwR5G2N13aCb6#|O#(=u~X z85k@CTSM>X-wqM6>&y>YB4)1;;ojbLbbV-W^iFB1wa3^zCog^LCAReC4K0-?R_2{6 zrP*)4+_uWUy3w5N52M3PW_}MFMP9a~>YLvVZ1D_k*IMQ2QT^fwzoOb(*3gH$%aYWC zkHmcab=va2<#X%jakpJ;<1@F;k__#bwtC&%^D0v(FBh9K&$sK+<}2RJS609D)17$w ztdQP8(eLM8Ka}m_IQ@3wyMKP)l=oM4-?`YS_*P?4V_ORLPxsj&7Ju#kH;>6^Kp?T7~ zl+q?{UOOqV==?+d{=)5s|M~T1mwtH@+Z^$G&eEO9JNP^AX@3jZ*J*!!>lc|1-W%fA z@AOQpXZ_Lt>rxFXrGp*zLPiW@uo_c7C{As>j zWeX)wi+LTp_)@KYZCX{j;H?|1yXT4DnlS(Fr8gyP5|uaX_gLvaW0ScZdnG7o+u{T6 zFI-%d{ls*WuCDa5UJ@|RXv&ejZe}*BMkiWY51&pnRPw(hlykSzvj6e%mYz-GdvzBD zF10?szF_~!jS=?2HyQuPCvARXAe}C}WP|yQ*>5~~=*Nxq8+HHW1~FMDRCP^TcacKuk$ z(U#REVv)D!PhJ*ecH-ELFUrfyV&*)Z)>UCOuS?yd^L@Afk>ihynYPc{^CRwu+JHX+#$@YsC4c|l0tGigsn@jy) zXD($Ouk>H+V(Mr6NQT0S9BFM~V6nkj;1OBOz`zY;a|<&v%$g$sEJPk;hD4M^`1)8S z=jZArrsOB3>Q&?x097+E*i={nnYpPYi3%0DIeEoa6}C!X6;?ntNLXJ<0j#7X+g2&U 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 literal 0 HcmV?d00001 diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/DetailViewController.h b/examples/cocoa-touch-application/CocoaTouchApplication/DetailViewController.h new file mode 100644 index 00000000..fd057606 --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/DetailViewController.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 + +@interface DetailViewController : UIViewController +{ + id _detailItem; + UILabel *_detailDescriptionLabel; + UIPopoverController *_masterPopoverController; +} + +@property (nonatomic, retain) id detailItem; + +@property (nonatomic, retain) IBOutlet UILabel *detailDescriptionLabel; + +@end diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/DetailViewController.m b/examples/cocoa-touch-application/CocoaTouchApplication/DetailViewController.m new file mode 100644 index 00000000..85a5d6dd --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/DetailViewController.m @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 "DetailViewController.h" + +@interface DetailViewController () +@property (nonatomic, retain) UIPopoverController *masterPopoverController; +- (void)configureView; +@end + +@implementation DetailViewController + +@synthesize detailItem = _detailItem; +@synthesize detailDescriptionLabel = _detailDescriptionLabel; +@synthesize masterPopoverController = _masterPopoverController; + +- (void)dealloc +{ + [_detailItem release]; + [_detailDescriptionLabel release]; + [_masterPopoverController release]; + [super dealloc]; +} + +#pragma mark - Managing the detail item + +- (void)setDetailItem:(id)newDetailItem +{ + if (_detailItem != newDetailItem) { + [_detailItem release]; + _detailItem = [newDetailItem retain]; + + // Update the view. + [self configureView]; + } + + if (self.masterPopoverController != nil) { + [self.masterPopoverController dismissPopoverAnimated:YES]; + } +} + +- (void)configureView +{ + // Update the user interface for the detail item. + + if (self.detailItem) { + self.detailDescriptionLabel.text = [self.detailItem description]; + } +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + // Do any additional setup after loading the view, typically from a nib. + [self configureView]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + self.title = NSLocalizedString(@"Detail", @"Detail"); + } + return self; +} + +#pragma mark - Split view + +- (void)splitViewController:(UISplitViewController *) __unused splitController willHideViewController:(UIViewController *) __unused viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)popoverController +{ + barButtonItem.title = NSLocalizedString(@"Master", @"Master"); + [self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES]; + self.masterPopoverController = popoverController; +} + +- (void)splitViewController:(UISplitViewController *) __unused splitController willShowViewController:(UIViewController *) __unused viewController invalidatingBarButtonItem:(UIBarButtonItem *) __unused barButtonItem +{ + // Called when the view is shown again in the split view, invalidating the button and popover controller. + [self.navigationItem setLeftBarButtonItem:nil animated:YES]; + self.masterPopoverController = nil; +} + +@end diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/MasterViewController.h b/examples/cocoa-touch-application/CocoaTouchApplication/MasterViewController.h new file mode 100644 index 00000000..27758ce3 --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/MasterViewController.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 + +@class DetailViewController; + +@interface MasterViewController : UITableViewController +{ + NSMutableArray *_objects; + DetailViewController *_detailViewController; +} + +@property (nonatomic, retain) DetailViewController *detailViewController; + +@end diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/MasterViewController.m b/examples/cocoa-touch-application/CocoaTouchApplication/MasterViewController.m new file mode 100644 index 00000000..ebac52dc --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/MasterViewController.m @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 "MasterViewController.h" + +#import "DetailViewController.h" + +@implementation MasterViewController + +@synthesize detailViewController = _detailViewController; + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + self.title = NSLocalizedString(@"Master", @"Master"); + if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { + self.clearsSelectionOnViewWillAppear = NO; + self.preferredContentSize = CGSizeMake(320.0, 600.0); + } + } + return self; +} + +- (void)dealloc +{ + [_detailViewController release]; + [_objects release]; + [super dealloc]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + // Do any additional setup after loading the view, typically from a nib. + self.navigationItem.leftBarButtonItem = self.editButtonItem; + + UIBarButtonItem *addButton = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(insertNewObject:)] autorelease]; + self.navigationItem.rightBarButtonItem = addButton; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (void)insertNewObject:(id) __unused sender +{ + if (!_objects) { + _objects = [[NSMutableArray alloc] init]; + } + [_objects insertObject:[NSDate date] atIndex:0]; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; + [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; +} + +#pragma mark - Table View + +- (NSInteger)numberOfSectionsInTableView:(UITableView *) __unused tableView +{ + return 1; +} + +- (NSInteger)tableView:(UITableView *) __unused tableView numberOfRowsInSection:(NSInteger) __unused section +{ + return _objects.count; +} + +// Customize the appearance of table view cells. +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + static NSString *CellIdentifier = @"Cell"; + + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) { + cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; + if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + } + } + + + NSDate *object = [_objects objectAtIndex:indexPath.row]; + cell.textLabel.text = [object description]; + return cell; +} + +- (BOOL)tableView:(UITableView *) __unused tableView canEditRowAtIndexPath:(NSIndexPath *) __unused indexPath +{ + // Return NO if you do not want the specified item to be editable. + return YES; +} + +- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (editingStyle == UITableViewCellEditingStyleDelete) { + [_objects removeObjectAtIndex:indexPath.row]; + [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; + } else if (editingStyle == UITableViewCellEditingStyleInsert) { + // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view. + } +} + +/* +// Override to support rearranging the table view. +- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath +{ +} +*/ + +/* +// Override to support conditional rearranging of the table view. +- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath +{ + // Return NO if you do not want the item to be re-orderable. + return YES; +} +*/ + +- (void)tableView:(UITableView *) __unused tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSDate *object = [_objects objectAtIndex:indexPath.row]; + if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { + if (!self.detailViewController) { + self.detailViewController = [[[DetailViewController alloc] initWithNibName:@"DetailViewController_iPhone" bundle:nil] autorelease]; + } + self.detailViewController.detailItem = object; + [self.navigationController pushViewController:self.detailViewController animated:YES]; + } else { + self.detailViewController.detailItem = object; + } +} + +@end diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/DetailViewController_iPad.xib b/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/DetailViewController_iPad.xib new file mode 100644 index 00000000..884dc206 --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/DetailViewController_iPad.xib @@ -0,0 +1,223 @@ + + + + 1536 + 12A206j + 2519 + 1172.1 + 613.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1856 + + + IBNSLayoutConstraint + IBProxyObject + IBUILabel + IBUIView + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBIPadFramework + + + IBFirstResponder + IBIPadFramework + + + + 274 + + + + 298 + {{20, 495}, {728, 18}} + + + + 3 + MQA + + YES + NO + IBIPadFramework + + 1 + 10 + Detail view content goes here + + 1 + MCAwIDAAA + + 1 + + 1 + 4 + + + Helvetica + 14 + 16 + + + + {{0, 20}, {768, 1004}} + + + + NO + + 2 + + IBIPadFramework + + + + + + + view + + + + 12 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 8 + + + + + 10 + 0 + + 10 + 1 + + 0.0 + + 1000 + + 5 + 22 + 2 + + + + 6 + 0 + + 6 + 1 + + 20 + + 1000 + + 8 + 29 + 3 + + + + 5 + 0 + + 5 + 1 + + 20 + + 1000 + + 8 + 29 + 3 + + + + + + + 81 + + + + + + 94 + + + + + 97 + + + + + 98 + + + + + + + DetailViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 98 + + + 0 + IBIPadFramework + YES + 3 + YES + 1856 + + diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/DetailViewController_iPhone.xib b/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/DetailViewController_iPhone.xib new file mode 100644 index 00000000..6c380252 --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/DetailViewController_iPhone.xib @@ -0,0 +1,253 @@ + + + + 1536 + 12A269 + 2835 + 1187 + 624.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1919 + + + IBNSLayoutConstraint + IBProxyObject + IBUILabel + IBUIView + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + + + + 298 + {{20, 265}, {280, 18}} + + + + + 3 + MQA + + YES + NO + IBCocoaTouchFramework + Detail view content goes here + + 1 + MCAwIDAAA + darkTextColor + + + 1 + 10 + 1 + + 1 + 4 + + + Helvetica + 14 + 16 + + + + {{0, 20}, {320, 548}} + + + + + 3 + MQA + + 2 + + + + + IBUIScreenMetrics + + YES + + + + + + {320, 568} + {568, 320} + + + IBCocoaTouchFramework + Retina 4 Full Screen + 2 + + IBCocoaTouchFramework + + + + + + + view + + + + 3 + + + + detailDescriptionLabel + + + + 6 + + + + + + 0 + + + + + + 1 + + + + + 10 + 0 + + 10 + 1 + + 0.0 + + 1000 + + 5 + 22 + 2 + + + + 6 + 0 + + 6 + 1 + + 20 + + 1000 + + 8 + 29 + 3 + + + + 5 + 0 + + 5 + 1 + + 20 + + 1000 + + 8 + 29 + 3 + + + + + + + -1 + + + File's Owner + + + -2 + + + + + 4 + + + + + 7 + + + + + 9 + + + + + 11 + + + + + + + DetailViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 11 + + + 0 + IBCocoaTouchFramework + YES + 3 + YES + 1919 + + diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/InfoPlist.strings b/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/InfoPlist.strings new file mode 100644 index 00000000..b92732c7 --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/InfoPlist.strings @@ -0,0 +1 @@ +/* Localized versions of Info.plist keys */ diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/MasterViewController_iPad.xib b/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/MasterViewController_iPad.xib new file mode 100644 index 00000000..2b4dba21 --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/MasterViewController_iPad.xib @@ -0,0 +1,152 @@ + + + + 1536 + 12A206j + 2519 + 1172.1 + 613.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1856 + + + IBProxyObject + IBUITableView + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBIPadFramework + + + IBFirstResponder + IBIPadFramework + + + + 274 + {{0, 20}, {320, 832}} + + + + 3 + MQA + + YES + + 2 + + + IBUISplitViewMasterSimulatedSizeMetrics + + YES + + + + + + {320, 852} + {320, 768} + + + IBIPadFramework + Master + IBUISplitViewController + + IBUISplitViewControllerContentSizeLocation + IBUISplitViewControllerContentSizeLocationMaster + + + IBIPadFramework + YES + 1 + 0 + YES + 44 + 22 + 22 + + + + + + + view + + + + 3 + + + + dataSource + + + + 4 + + + + delegate + + + + 5 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + + + MasterViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 5 + + + 0 + IBIPadFramework + YES + 3 + YES + 1856 + + diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/MasterViewController_iPhone.xib b/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/MasterViewController_iPhone.xib new file mode 100644 index 00000000..1000ecf2 --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/en.lproj/MasterViewController_iPhone.xib @@ -0,0 +1,147 @@ + + + + 1536 + 12A269 + 2835 + 1187 + 624.00 + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + 1919 + + + IBProxyObject + IBUITableView + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + PluginDependencyRecalculationVersion + + + + + IBFilesOwner + IBCocoaTouchFramework + + + IBFirstResponder + IBCocoaTouchFramework + + + + 274 + {{0, 20}, {320, 548}} + + + + + 3 + MQA + + YES + + + IBUIScreenMetrics + + YES + + + + + + {320, 568} + {568, 320} + + + IBCocoaTouchFramework + Retina 4 Full Screen + 2 + + IBCocoaTouchFramework + YES + 1 + 0 + YES + 44 + 22 + 22 + + + + + + + view + + + + 3 + + + + dataSource + + + + 4 + + + + delegate + + + + 5 + + + + + + 0 + + + + + + -1 + + + File's Owner + + + -2 + + + + + 2 + + + + + + + MasterViewController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + UIResponder + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + + + 5 + + + 0 + IBCocoaTouchFramework + YES + 3 + YES + 1919 + + diff --git a/examples/cocoa-touch-application/CocoaTouchApplication/main.m b/examples/cocoa-touch-application/CocoaTouchApplication/main.m new file mode 100644 index 00000000..7665efcf --- /dev/null +++ b/examples/cocoa-touch-application/CocoaTouchApplication/main.m @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 + +#import "AppDelegate.h" + +int main(int argc, char *argv[]) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + int retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + [pool release]; + return retVal; +} diff --git a/examples/cocoa-touch-application/cocoa-touch-application.qbs b/examples/cocoa-touch-application/cocoa-touch-application.qbs new file mode 100644 index 00000000..ec1772f1 --- /dev/null +++ b/examples/cocoa-touch-application/cocoa-touch-application.qbs @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 1.0 + +CppApplication { + Depends { name: "xcode"; required: false } + Depends { condition: product.condition; name: "ib" } + condition: qbs.hostOS.contains("macos") && xcode.present && qbs.targetPlatform.contains("ios") + name: "Cocoa Touch Application" + + cpp.useObjcPrecompiledHeader: true + cpp.minimumIosVersion: "8.0" + cpp.frameworks: [ "UIKit", "Foundation", "CoreGraphics" ] + + Group { + prefix: "CocoaTouchApplication/" + files: [ + "AppDelegate.h", + "AppDelegate.m", + "CocoaTouchApplication-Info.plist", + "Default-568h@2x.png", + "Default.png", + "Default@2x.png", + "DetailViewController.h", + "DetailViewController.m", + "MasterViewController.h", + "MasterViewController.m", + "main.m" + ] + } + + Group { + name: "Supporting Files" + prefix: "CocoaTouchApplication/en.lproj/" + files: [ + "DetailViewController_iPad.xib", + "DetailViewController_iPhone.xib", + "InfoPlist.strings", + "MasterViewController_iPad.xib", + "MasterViewController_iPhone.xib" + ] + } + + Group { + name: "Xcode Project" + files: [ + "CocoaTouchApplication.xcodeproj/project.pbxproj" + ] + } + + Group { + files: ["CocoaTouchApplication/CocoaTouchApplication-Prefix.pch"] + fileTags: ["objc_pch_src"] + } +} diff --git a/examples/code-generator/code-generator.qbs b/examples/code-generator/code-generator.qbs new file mode 100644 index 00000000..f17c1309 --- /dev/null +++ b/examples/code-generator/code-generator.qbs @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 + +Project { + // A code generator that outputs a "Hello World" C++ program. + CppApplication { + name: "hwgen" + consoleApplication: true + files: ["hwgen.cpp"] + Properties { + condition: qbs.toolchain.contains("gcc") || qbs.toolchain.contains("clang-cl") + cpp.cxxFlags: ["-Wno-deprecated-declarations"] + } + } + + // Generate and build a hello-world application. + CppApplication { + condition: qbs.targetOS === qbs.hostOS + name: "hello-world" + Depends { name: "hwgen" } + Rule { + inputsFromDependencies: ["application"] + Artifact { + filePath: "main.cpp" + fileTags: ["cpp"] + } + prepare: { + var hwgen = inputs["application"][0].filePath; + var cmd = new Command(hwgen, [output.filePath]); + cmd.description = "generating C++ source"; + return cmd; + } + } + } +} diff --git a/examples/code-generator/hwgen.cpp b/examples/code-generator/hwgen.cpp new file mode 100644 index 00000000..1e255664 --- /dev/null +++ b/examples/code-generator/hwgen.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** 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 + +int main(int argc, char **argv) +{ + if (argc < 2) + return 1; + FILE *f = fopen(argv[1], "w"); + if (!f) + return 2; + fprintf(f, "#include \n\n" + "int main()\n" + "{\n printf(\"Hello World!\\n\");\n return 0;\n}\n"); + fclose(f); + return 0; +} diff --git a/examples/collidingmice/collidingmice.qbs b/examples/collidingmice/collidingmice.qbs new file mode 100644 index 00000000..52231882 --- /dev/null +++ b/examples/collidingmice/collidingmice.qbs @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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 1.0 + +CppApplication { + name : "CollidingMice" + Depends { name: "Qt.widgets" } + property bool isBundle: qbs.targetOS.contains("darwin") && bundle.isBundle + files : [ + "images/cheese.jpg", + "main.cpp", + "mouse.cpp", + "mouse.h", + "mice.qrc" + ] + Group { + fileTagsFilter: isBundle ? ["bundle.content"] : ["application"] + qbs.install: true + qbs.installDir: isBundle ? "Applications" : (qbs.targetOS.contains("windows") ? "" : "bin") + qbs.installSourceBase: product.buildDirectory + } +} diff --git a/examples/collidingmice/images/cheese.jpg b/examples/collidingmice/images/cheese.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dea5795fd0b7a4dfa46bf7274d068885d700914e GIT binary patch literal 3029 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 Vi6{N-mmDs<0OvLl=N>_s`3L5NTuJ}{ literal 0 HcmV?d00001 diff --git a/examples/collidingmice/main.cpp b/examples/collidingmice/main.cpp new file mode 100644 index 00000000..2d051442 --- /dev/null +++ b/examples/collidingmice/main.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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 "mouse.h" + +#include +#include +#include +#include +#include + +#include + +static const int MouseCount = 7; + +//! [0] +int main(int argc, char **argv) +{ + QApplication app(argc, argv); +//! [0] + +//! [1] + QGraphicsScene scene; + scene.setSceneRect(-300, -300, 600, 600); +//! [1] //! [2] + scene.setItemIndexMethod(QGraphicsScene::NoIndex); +//! [2] + +//! [3] + for (int i = 0; i < MouseCount; ++i) { + const auto mouse = new Mouse; + mouse->setPos(::sin((i * 6.28) / MouseCount) * 200, + ::cos((i * 6.28) / MouseCount) * 200); + scene.addItem(mouse); + } +//! [3] + +//! [4] + QGraphicsView view(&scene); + view.setRenderHint(QPainter::Antialiasing); + view.setBackgroundBrush(QPixmap(":/images/cheese.jpg")); +//! [4] //! [5] + view.setCacheMode(QGraphicsView::CacheBackground); + view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); + view.setDragMode(QGraphicsView::ScrollHandDrag); +//! [5] //! [6] + view.setWindowTitle(QT_TRANSLATE_NOOP(QGraphicsView, "Colliding Mice")); + view.resize(400, 300); + view.show(); + + QTimer timer; + QObject::connect(&timer, &QTimer::timeout, &scene, &QGraphicsScene::advance); + timer.start(1000 / 33); + + return app.exec(); +} +//! [6] diff --git a/examples/collidingmice/mice.qrc b/examples/collidingmice/mice.qrc new file mode 100644 index 00000000..accdb4d0 --- /dev/null +++ b/examples/collidingmice/mice.qrc @@ -0,0 +1,5 @@ + + + images/cheese.jpg + + diff --git a/examples/collidingmice/mouse.cpp b/examples/collidingmice/mouse.cpp new file mode 100644 index 00000000..bbc71864 --- /dev/null +++ b/examples/collidingmice/mouse.cpp @@ -0,0 +1,209 @@ +/**************************************************************************** +** +** 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 "mouse.h" + +#include +#include +#include + +#include + +static const double Pi = 3.14159265358979323846264338327950288419717; +static double TwoPi = 2.0 * Pi; + +static qreal normalizeAngle(qreal angle) +{ + while (angle < 0) + angle += TwoPi; + while (angle > TwoPi) + angle -= TwoPi; + return angle; +} + +//! [0] +Mouse::Mouse() + : angle(0), speed(0), mouseEyeDirection(0), + color(m_rand.generate() % 256, m_rand.generate() % 256, m_rand.generate() % 256) +{ + setRotation(m_rand.generate() % (360 * 16)); +} +//! [0] + +//! [1] +QRectF Mouse::boundingRect() const +{ + qreal adjust = 0.5; + return {-18 - adjust, -22 - adjust, 36 + adjust, 60 + adjust}; +} +//! [1] + +//! [2] +QPainterPath Mouse::shape() const +{ + QPainterPath path; + path.addRect(-10, -20, 20, 40); + return path; +} +//! [2] + +//! [3] +void Mouse::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) +{ + // Body + painter->setBrush(color); + painter->drawEllipse(-10, -20, 20, 40); + + // Eyes + painter->setBrush(Qt::white); + painter->drawEllipse(-10, -17, 8, 8); + painter->drawEllipse(2, -17, 8, 8); + + // Nose + painter->setBrush(Qt::black); + painter->drawEllipse(QRectF(-2, -22, 4, 4)); + + // Pupils + painter->drawEllipse(QRectF(-8.0 + mouseEyeDirection, -17, 4, 4)); + painter->drawEllipse(QRectF(4.0 + mouseEyeDirection, -17, 4, 4)); + + // Ears + painter->setBrush(scene()->collidingItems(this).isEmpty() ? Qt::darkYellow : Qt::red); + painter->drawEllipse(-17, -12, 16, 16); + painter->drawEllipse(1, -12, 16, 16); + + // Tail + QPainterPath path(QPointF(0, 20)); + path.cubicTo(-5, 22, -5, 22, 0, 25); + path.cubicTo(5, 27, 5, 32, 0, 30); + path.cubicTo(-5, 32, -5, 42, 0, 35); + painter->setBrush(Qt::NoBrush); + painter->drawPath(path); +} +//! [3] + +//! [4] +void Mouse::advance(int step) +{ + if (!step) + return; +//! [4] + // Don't move too far away +//! [5] + QLineF lineToCenter(QPointF(0, 0), mapFromScene(0, 0)); + if (lineToCenter.length() > 150) { + qreal angleToCenter = ::acos(lineToCenter.dx() / lineToCenter.length()); + if (lineToCenter.dy() < 0) + angleToCenter = TwoPi - angleToCenter; + angleToCenter = normalizeAngle((Pi - angleToCenter) + Pi / 2); + + if (angleToCenter < Pi && angleToCenter > Pi / 4) { + // Rotate left + angle += (angle < -Pi / 2) ? 0.25 : -0.25; + } else if (angleToCenter >= Pi && angleToCenter < (Pi + Pi / 2 + Pi / 4)) { + // Rotate right + angle += (angle < Pi / 2) ? 0.25 : -0.25; + } + } else if (::sin(angle) < 0) { + angle += 0.25; + } else if (::sin(angle) > 0) { + angle -= 0.25; +//! [5] //! [6] + } +//! [6] + + // Try not to crash with any other mice +//! [7] + const QList dangerMice = scene()->items(QPolygonF() + << mapToScene(0, 0) + << mapToScene(-30, -50) + << mapToScene(30, -50)); + for (QGraphicsItem *item : dangerMice) { + if (item == this) + continue; + + QLineF lineToMouse(QPointF(0, 0), mapFromItem(item, 0, 0)); + qreal angleToMouse = ::acos(lineToMouse.dx() / lineToMouse.length()); + if (lineToMouse.dy() < 0) + angleToMouse = TwoPi - angleToMouse; + angleToMouse = normalizeAngle((Pi - angleToMouse) + Pi / 2); + + if (angleToMouse >= 0 && angleToMouse < Pi / 2) { + // Rotate right + angle += 0.5; + } else if (angleToMouse <= TwoPi && angleToMouse > (TwoPi - Pi / 2)) { + // Rotate left + angle -= 0.5; +//! [7] //! [8] + } +//! [8] //! [9] + } +//! [9] + + // Add some random movement +//! [10] + if (dangerMice.size() > 1 && (m_rand.generate() % 10) == 0) { + if (m_rand.generate() % 1) + angle += (m_rand.generate() % 100) / 500.0; + else + angle -= (m_rand.generate() % 100) / 500.0; + } +//! [10] + +//! [11] + speed += (-50 + m_rand.generate() % 100) / 100.0; + + qreal dx = ::sin(angle) * 10; + mouseEyeDirection = (qAbs(dx / 5) < 1) ? 0 : dx / 5; + + setRotation(rotation() + dx); + setPos(mapToParent(0, -(3 + sin(speed) * 3))); +} +//! [11] diff --git a/examples/collidingmice/mouse.h b/examples/collidingmice/mouse.h new file mode 100644 index 00000000..23b9faad --- /dev/null +++ b/examples/collidingmice/mouse.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef MOUSE_H +#define MOUSE_H + +#include +#include + +//! [0] +class Mouse : public QGraphicsItem +{ +public: + Mouse(); + + QRectF boundingRect() const override; + QPainterPath shape() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, + QWidget *widget) override; + +protected: + void advance(int step) override; + +private: + QRandomGenerator m_rand = QRandomGenerator::securelySeeded(); + qreal angle; + qreal speed; + qreal mouseEyeDirection; + QColor color; +}; +//! [0] + +#endif diff --git a/examples/compiled-qml/MainForm.ui.qml b/examples/compiled-qml/MainForm.ui.qml new file mode 100644 index 00000000..0b1653eb --- /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 0000000000000000000000000000000000000000..dea5795fd0b7a4dfa46bf7274d068885d700914e GIT binary patch literal 3029 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 Vi6{N-mmDs<0OvLl=N>_s`3L5NTuJ}{ literal 0 HcmV?d00001 diff --git a/examples/compiled-qml/compiled-qml.qbs b/examples/compiled-qml/compiled-qml.qbs new file mode 100644 index 00000000..3a900fac --- /dev/null +++ b/examples/compiled-qml/compiled-qml.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/main.cpp b/examples/compiled-qml/main.cpp new file mode 100644 index 00000000..27074068 --- /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 +#include + +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 00000000..4b7c4dc7 --- /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/qml.qrc b/examples/compiled-qml/qml.qrc new file mode 100644 index 00000000..deb314e5 --- /dev/null +++ b/examples/compiled-qml/qml.qrc @@ -0,0 +1,7 @@ + + + main.qml + MainForm.ui.qml + cheese.jpg + + diff --git a/examples/examples.qbs b/examples/examples.qbs new file mode 100644 index 00000000..18205d67 --- /dev/null +++ b/examples/examples.qbs @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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 + +Project { + references: [ + "app-and-lib/app-and-lib.qbs", + "cocoa-application/cocoa-application.qbs", + "cocoa-touch-application/cocoa-touch-application.qbs", + "code-generator/code-generator.qbs", + "collidingmice/collidingmice.qbs", + "compiled-qml/compiled-qml.qbs", + "grpc/grpc.qbs", + "helloworld-complex/helloworld-complex.qbs", + "helloworld-minimal/helloworld-minimal.qbs", + "helloworld-qt/helloworld-qt.qbs", + "install-bundle/install-bundle.qbs", + // these examples are broken because of the bug in the protobuf modules + // "protobuf/addressbook_cpp/addressbook_cpp.qbs", + // "protobuf/addressbook_objc/addressbook_objc.qbs", + "baremetal/baremetal.qbs", + "rule/rule.qbs", + "rpaths/rpaths.qbs", + ] +} diff --git a/examples/grpc/client.cpp b/examples/grpc/client.cpp new file mode 100644 index 00000000..e6af8e5e --- /dev/null +++ b/examples/grpc/client.cpp @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +** 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$ +** +****************************************************************************/ + +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-parameter" +#endif // __GNUC__ + +#include + +#include +#include +#include +#include + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif // __GNUC__ + +#include +#include +#include + +class Client +{ +public: + Client(const std::shared_ptr &channel) + : m_stub(PP::MyApi::NewStub(channel)) {} + + int ping(int count) { + PP::Ping request; + request.set_count(count); + PP::Pong reply; + grpc::ClientContext context; + + const auto status = m_stub->pingPong(&context, request, &reply); + if (status.ok()) { + return reply.count(); + } else { + throw std::runtime_error("invalid status"); + } + } + +private: + std::unique_ptr m_stub; +}; + +int main(int, char**) +{ + Client client( + grpc::CreateCustomChannel( + "localhost:50051", + grpc::InsecureChannelCredentials(), + grpc::ChannelArguments())); + + for (int i = 0; i < 1000; ++i) { + std::cout << "Sending ping " << i << "... "; + int result = client.ping(i); + if (result != i) { + std::cerr << "Invalid pong " << result << " for ping" << i << std::endl; + continue; + } + std::cout << "got pong " << result << std::endl; + } + + return 0; +} + diff --git a/examples/grpc/grpc.qbs b/examples/grpc/grpc.qbs new file mode 100644 index 00000000..c4f146ec --- /dev/null +++ b/examples/grpc/grpc.qbs @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://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.Utilities + +Project { + condition: Utilities.versionCompare(qbs.version, "1.14") >= 0 + + Application { + Depends { name: "cpp" } + Depends { name: "protobuf.cpp"; required: false } + condition: protobuf.cpp.present && qbs.targetOS === qbs.hostOS + protobuf.cpp.useGrpc: true + consoleApplication: true + cpp.cxxLanguageVersion: "c++17" + cpp.minimumMacosVersion: "10.8" + name: "client" + files: "client.cpp" + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.cxxFlags: "-Wno-deprecated-declarations" + } + Group { + files: "ping-pong-grpc.proto" + fileTags: "protobuf.grpc" + } + } + + Application { + Depends { name: "cpp" } + Depends { name: "protobuf.cpp"; required: false } + condition: protobuf.cpp.present && qbs.targetOS === qbs.hostOS + protobuf.cpp.useGrpc: true + consoleApplication: true + cpp.cxxLanguageVersion: "c++17" + cpp.minimumMacosVersion: "10.8" + name: "server" + files: "server.cpp" + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.cxxFlags: "-Wno-deprecated-declarations" + } + Group { + files: "ping-pong-grpc.proto" + fileTags: "protobuf.grpc" + } + } +} diff --git a/examples/grpc/ping-pong-grpc.proto b/examples/grpc/ping-pong-grpc.proto new file mode 100644 index 00000000..da6d9490 --- /dev/null +++ b/examples/grpc/ping-pong-grpc.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package PP; + +message Ping { + int32 count = 1; +} + +message Pong { + int32 count = 1; +} + +service MyApi { + rpc pingPong(Ping) returns (Pong) {} +} diff --git a/examples/grpc/server.cpp b/examples/grpc/server.cpp new file mode 100644 index 00000000..74460d29 --- /dev/null +++ b/examples/grpc/server.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +** 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$ +** +****************************************************************************/ + +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-parameter" +#endif // __GNUC__ + +#include + +#include +#include +#include +#include +#include + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif // __GNUC__ + +#include +#include +#include + +class Service final : public PP::MyApi::Service +{ + grpc::Status pingPong( + grpc::ServerContext* context, + const PP::Ping* request, + PP::Pong* reply) override + { + (void)context; + reply->set_count(request->count()); + return grpc::Status::OK; + } +}; + +int main(int, char**) +{ + std::string server_address("0.0.0.0:50051"); + Service service; + + grpc::ServerBuilder builder; + builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); + builder.RegisterService(&service); + std::unique_ptr server(builder.BuildAndStart()); + std::cout << "Server listening on " << server_address << std::endl; + server->Wait(); + return 0; +} + diff --git a/examples/helloworld-complex/helloworld-complex.qbs b/examples/helloworld-complex/helloworld-complex.qbs new file mode 100644 index 00000000..16e7eace --- /dev/null +++ b/examples/helloworld-complex/helloworld-complex.qbs @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 1.0 + +Project { + property bool hasSpecialFeature: true + Application { + name: 'HelloWorld-Complex' + + Depends { name: 'cpp' } + cpp.defines: ['SOMETHING'] + + + files: [ + "src/foo.h", + "src/foo.cpp" + ] + + Group { + condition: project.hasSpecialFeature + prefix: "src/" + files: ["specialfeature.cpp", "specialfeature.h"] + } + + Group { + cpp.defines: { + var defines = outer.concat([ + 'HAVE_MAIN_CPP', + cpp.debugInformation ? 'HAS_DEBUG' : 'HAS_RELEASE' + ]); + if (project.hasSpecialFeature) + defines.push("HAS_SPECIAL_FEATURE"); + return defines; + } + prefix: "src/" + files: [ + 'main.cpp' + ] + } + } +} diff --git a/examples/helloworld-complex/src/foo.cpp b/examples/helloworld-complex/src/foo.cpp new file mode 100644 index 00000000..74c5bf7b --- /dev/null +++ b/examples/helloworld-complex/src/foo.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef SOMETHING +# error missing define SOMETHING +#endif + +int someUsefulFunction() +{ + return 156; +} + diff --git a/examples/helloworld-complex/src/foo.h b/examples/helloworld-complex/src/foo.h new file mode 100644 index 00000000..abeef38b --- /dev/null +++ b/examples/helloworld-complex/src/foo.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef FOO_H +#define FOO_H + +int someUsefulFunction(); + +#endif + diff --git a/examples/helloworld-complex/src/main.cpp b/examples/helloworld-complex/src/main.cpp new file mode 100644 index 00000000..8827c1e5 --- /dev/null +++ b/examples/helloworld-complex/src/main.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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 "foo.h" + +#ifdef HAS_SPECIAL_FEATURE +#include "specialfeature.h" +#endif + +#include + +#ifndef HAVE_MAIN_CPP +# error missing define HAVE_MAIN_CPP +#endif + +#ifndef SOMETHING +# error missing define SOMETHING +#endif + +int main() +{ + someUsefulFunction(); +#if defined(HAS_DEBUG) + puts("Hello World! (debug version)"); +#elif defined(HAS_RELEASE) + puts("Hello World! (release version)"); +#endif +#ifdef HAS_SPECIAL_FEATURE + bragAboutSpecialFeature(); +#endif +} + diff --git a/examples/helloworld-complex/src/specialfeature.cpp b/examples/helloworld-complex/src/specialfeature.cpp new file mode 100644 index 00000000..63f5fd76 --- /dev/null +++ b/examples/helloworld-complex/src/specialfeature.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** 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 "specialfeature.h" + +#include + +void bragAboutSpecialFeature() +{ + std::cout << "I have a special feature!" << std::endl; +} diff --git a/examples/helloworld-complex/src/specialfeature.h b/examples/helloworld-complex/src/specialfeature.h new file mode 100644 index 00000000..9e1cfaaa --- /dev/null +++ b/examples/helloworld-complex/src/specialfeature.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef HELLO_SPECIAL_FEATURE +#define HELLO_SPECIAL_FEATURE + +void bragAboutSpecialFeature(); + +#endif diff --git a/examples/helloworld-minimal/helloworld-minimal.qbs b/examples/helloworld-minimal/helloworld-minimal.qbs new file mode 100644 index 00000000..5cad9f4b --- /dev/null +++ b/examples/helloworld-minimal/helloworld-minimal.qbs @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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 { + name: "HelloWorld-minimal" + files: "main.cpp" +} diff --git a/examples/helloworld-minimal/main.cpp b/examples/helloworld-minimal/main.cpp new file mode 100644 index 00000000..6d8ac039 --- /dev/null +++ b/examples/helloworld-minimal/main.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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 + +int main() +{ + std::cout << "Hello, World!" << std::endl; +} diff --git a/examples/helloworld-qt/helloworld-qt.qbs b/examples/helloworld-qt/helloworld-qt.qbs new file mode 100644 index 00000000..4c6e070a --- /dev/null +++ b/examples/helloworld-qt/helloworld-qt.qbs @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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 + +QtApplication { + name: "HelloWorld-Qt" + files: "main.cpp" +} diff --git a/examples/helloworld-qt/main.cpp b/examples/helloworld-qt/main.cpp new file mode 100644 index 00000000..e1e82259 --- /dev/null +++ b/examples/helloworld-qt/main.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** 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 +#include + +#include + +int main() +{ + QTextStream ts(stdout); + ts << QCoreApplication::translate("hello", "Hello, World!\n"); + ts.flush(); +} diff --git a/examples/install-bundle/MainMenu.xib b/examples/install-bundle/MainMenu.xib new file mode 100644 index 00000000..14312411 --- /dev/null +++ b/examples/install-bundle/MainMenu.xib @@ -0,0 +1,680 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/install-bundle/Storyboard.storyboard b/examples/install-bundle/Storyboard.storyboard new file mode 100644 index 00000000..06aef2bc --- /dev/null +++ b/examples/install-bundle/Storyboard.storyboard @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/install-bundle/assetcatalog1.xcassets/other.imageset/Contents.json b/examples/install-bundle/assetcatalog1.xcassets/other.imageset/Contents.json new file mode 100644 index 00000000..4b1cfb1f --- /dev/null +++ b/examples/install-bundle/assetcatalog1.xcassets/other.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "icon_16x16.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "icon_16x16@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/install-bundle/assetcatalog1.xcassets/other.imageset/icon_16x16.png b/examples/install-bundle/assetcatalog1.xcassets/other.imageset/icon_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..60365798f18a376a1193bca9e6044cc778c6042a GIT binary patch literal 649 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0WW zg+Z8+Vb&Z81_maE%#etZ2wxwolpi<;HsXMd|v6mX?>t*GR!IU;uq7Dc2xlVci@>1|NgPQZ$Q4%pMVYC< z00ISrouQ3Bh8R@6jXo%hkirZSAz)EpjM#D6=)+^zj!XI29Z;fC@^o+tIX_n~F(p4KRj(qq0H~UQ!KT6r$jnVGNmQuF&B-ga zs<2f8tFQvHLBje<3ScEA*|tg%z5xo(`9-M;rg|oN21<5Z3JMA~MJZ`kK`w4k?LeNb zQbtKhft9{~d3m{Bxv^e;QM$gNrKP35fswwEkuFe$ZgFK^Nn(X=Ua>O75STeGsl~}f znFS@8`FRQ;a}$&DOG|8(lt3220mPlD6`2T|@`|C}0(wv%B%^PrXP}QwTS;ab4s9SA zh&HglAlBJ{46_QztVqp?bji$3%_{~v&CbLIYzc-q!kI|=B5>$K5=YVpa)p(DQD!PI zfIz`uXK163AqG`%qYnxrq%ea-2v`&tBX(Ri`taDb<5E6$2b5@xJY5_^A~@e(aO7oR z;9xd9us>a4#)Hd}JC@$uT@houP5&(eBNGdUfPzBlpi<;HsXMd|v6mX?>t*GR!IU;uq7Dc2xlVci@>1|NgPQZ$Q4%pMVYC< z00ISrouQ3Bh8R@6jXo%hkirZSAz)EpjM#D6=)+^zj!XI29Z;fC@^o+tIX_n~F(p4KRj(qq0H~UQ!KT6r$jnVGNmQuF&B-ga zs<2f8tFQvHLBje<3ScEA*|tg%z5xo(`9-M;rg|oN21<5Z3JMA~MJZ`kK`w4k?LeNb zQbtKhft9{~d3m{Bxv^e;QM$gNrKP35fswwEkuFe$ZgFK^Nn(X=Ua>O75STeGsl~}f znFS@8`FRQ;a}$&DOG|8(lt3220mPlD6`2T|@`|C}0(wv%B%^PrXP}QwTS;ab4s9SA zh&HglAlBJ{46_QztVqp?bji$3%_{~v&CbLIYzc-q!kI|=B5>$K5=YVpa)p(DQD!PI zfIz`uXK163AqG`%qYnxrq%ea-2v`&tBX(Ri`taDb<5E6$2b5@xJY5_^A~@e(aO7oR z;9xd9us>a4#)Hd}JC@$uT@houP5&(eBNGdUfPzB +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QWidget window; + window.show(); + return app.exec(); +} diff --git a/examples/install-bundle/white.iconset/icon_16x16.png b/examples/install-bundle/white.iconset/icon_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..60365798f18a376a1193bca9e6044cc778c6042a GIT binary patch literal 649 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0WW zg+Z8+Vb&Z81_maE%#etZ2wxwolpi<;HsXMd|v6mX?>t*GR!IU;uq7Dc2xlVci@>1|NgPQZ$Q4%pMVYC< z00ISrouQ3Bh8R@6jXo%hkirZSAz)EpjM#D6=)+^zj!XI29Z;fC@^o+tIX_n~F(p4KRj(qq0H~UQ!KT6r$jnVGNmQuF&B-ga zs<2f8tFQvHLBje<3ScEA*|tg%z5xo(`9-M;rg|oN21<5Z3JMA~MJZ`kK`w4k?LeNb zQbtKhft9{~d3m{Bxv^e;QM$gNrKP35fswwEkuFe$ZgFK^Nn(X=Ua>O75STeGsl~}f znFS@8`FRQ;a}$&DOG|8(lt3220mPlD6`2T|@`|C}0(wv%B%^PrXP}QwTS;ab4s9SA zh&HglAlBJ{46_QztVqp?bji$3%_{~v&CbLIYzc-q!kI|=B5>$K5=YVpa)p(DQD!PI zfIz`uXK163AqG`%qYnxrq%ea-2v`&tBX(Ri`taDb<5E6$2b5@xJY5_^A~@e(aO7oR z;9xd9us>a4#)Hd}JC@$uT@houP5&(eBNGdUfPzB/protobuf. Then you'll need to set protobuf.libraryPath: "/Users//protobuf/lib" and protobuf.includePath: "/Users//protobuf/include" + +On Windows, you have to compile and install protobuf manually to any folder and use libraryPath and includePath as shown above diff --git a/examples/protobuf/addressbook_cpp/addressbook_cpp.qbs b/examples/protobuf/addressbook_cpp/addressbook_cpp.qbs new file mode 100644 index 00000000..f01ca47f --- /dev/null +++ b/examples/protobuf/addressbook_cpp/addressbook_cpp.qbs @@ -0,0 +1,18 @@ +import qbs + +CppApplication { + consoleApplication: true + condition: protobuf.cpp.present && qbs.targetPlatform === qbs.hostPlatform + + Depends { name: "cpp" } + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.8" + + Depends { name: "protobuf.cpp"; required: false } + + files: [ + "../shared/addressbook.proto", + "main.cpp", + "README.md", + ] +} diff --git a/examples/protobuf/addressbook_cpp/main.cpp b/examples/protobuf/addressbook_cpp/main.cpp new file mode 100644 index 00000000..31baba3d --- /dev/null +++ b/examples/protobuf/addressbook_cpp/main.cpp @@ -0,0 +1,179 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// 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 Google Inc. 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. + +#include +#include +#include +#include + +#ifdef __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-parameter" +# include +# pragma GCC diagnostic pop +#else +# include +#endif // __GNUC__ + +#include "addressbook.pb.h" + +using google::protobuf::util::TimeUtil; + +int printUsage(char *argv0) +{ + std::cerr << "Usage: " << argv0 << "add|list ADDRESS_BOOK_FILE" << std::endl; + return -1; +} + +std::string readString(const std::string &promt) +{ + std::string result; + std::cout << promt; + std::getline(std::cin, result); + return result; +} + +// This function fills in a Person message based on user input. +void promptForAddress(tutorial::Person* person) +{ + std::cout << "Enter person ID number: "; + int id; + std::cin >> id; + person->set_id(id); + std::cin.ignore(256, '\n'); + + *person->mutable_name() = readString("Enter name: "); + + const auto email = readString("Enter email address (blank for none): "); + if (!email.empty()) + person->set_email(email); + + while (true) { + const auto number = readString("Enter a phone number (or leave blank to finish): "); + if (number.empty()) + break; + + tutorial::Person::PhoneNumber *phone_number = person->add_phones(); + phone_number->set_number(number); + + const auto type = readString("Is this a mobile, home, or work phone? "); + if (type == "mobile") + phone_number->set_type(tutorial::Person::MOBILE); + else if (type == "home") + phone_number->set_type(tutorial::Person::HOME); + else if (type == "work") + phone_number->set_type(tutorial::Person::WORK); + else + std::cout << "Unknown phone type. Using default." << std::endl; + } + *person->mutable_last_updated() = TimeUtil::SecondsToTimestamp(time(NULL)); +} + +// Iterates though all people in the AddressBook and prints info about them. +void listPeople(const tutorial::AddressBook& address_book) +{ + for (int i = 0; i < address_book.people_size(); i++) { + const tutorial::Person& person = address_book.people(i); + + std::cout << "Person ID: " << person.id() << std::endl; + std::cout << " Name: " << person.name() << std::endl; + if (!person.email().empty()) { + std::cout << " E-mail address: " << person.email() << std::endl; + } + + for (int j = 0; j < person.phones_size(); j++) { + const tutorial::Person::PhoneNumber& phone_number = person.phones(j); + + switch (phone_number.type()) { + case tutorial::Person::MOBILE: + std::cout << " Mobile phone #: "; + break; + case tutorial::Person::HOME: + std::cout << " Home phone #: "; + break; + case tutorial::Person::WORK: + std::cout << " Work phone #: "; + break; + default: + std::cout << " Unknown phone #: "; + break; + } + std::cout << phone_number.number() << std::endl; + } + if (person.has_last_updated()) { + std::cout << " Updated: " << TimeUtil::ToString(person.last_updated()) << std::endl; + } + } +} + +int main(int argc, char* argv[]) { + // Verify that the version of the library that we linked against is + // compatible with the version of the headers we compiled against. + GOOGLE_PROTOBUF_VERIFY_VERSION; + + if (argc != 3) + return printUsage(argv[0]); + + tutorial::AddressBook address_book; + + // Read the existing address book. + std::fstream input(argv[2], std::ios::in | std::ios::binary); + if (!input) { + std::cout << argv[2] << ": File not found." << std::endl; + } else if (!address_book.ParseFromIstream(&input)) { + std::cerr << "Failed to parse address book." << std::endl; + return -1; + } + + const std::string mode(argv[1]); + if (mode == "add") { + // Add an address. + promptForAddress(address_book.add_people()); + + if (!input) + std::cout << "Creating a new file." << std::endl; + + // Write the new address book back to disk. + std::fstream output(argv[2], std::ios::out | std::ios::trunc | std::ios::binary); + if (!address_book.SerializeToOstream(&output)) { + std::cerr << "Failed to write address book." << std::endl; + return -1; + } + } else if (mode == "list") { + listPeople(address_book); + } else { + return printUsage(argv[0]); + } + + // Optional: Delete all global objects allocated by libprotobuf. + google::protobuf::ShutdownProtobufLibrary(); + + return 0; +} diff --git a/examples/protobuf/addressbook_objc/README.md b/examples/protobuf/addressbook_objc/README.md new file mode 100644 index 00000000..c0fc7c0e --- /dev/null +++ b/examples/protobuf/addressbook_objc/README.md @@ -0,0 +1,5 @@ +### Addressbook objc example + +This example shows how to build an objective-c application that uses Google protobuf. + +In order to build this example, you'll need to have a ProtocolBuffers library or framework installed in the system. diff --git a/examples/protobuf/addressbook_objc/addressbook_objc.qbs b/examples/protobuf/addressbook_objc/addressbook_objc.qbs new file mode 100644 index 00000000..35409c4d --- /dev/null +++ b/examples/protobuf/addressbook_objc/addressbook_objc.qbs @@ -0,0 +1,14 @@ +import qbs + +CppApplication { + consoleApplication: true + condition: protobuf.objc.present && qbs.targetOS.contains("macos") + + Depends { name: "cpp" } + Depends { name: "protobuf.objc"; required: false } + + files: [ + "../shared/addressbook.proto", + "main.m", + ] +} diff --git a/examples/protobuf/addressbook_objc/main.m b/examples/protobuf/addressbook_objc/main.m new file mode 100644 index 00000000..0c330a6c --- /dev/null +++ b/examples/protobuf/addressbook_objc/main.m @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ivan Komissarov (abbapoh@gmail.com) +** 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 "Addressbook.pbobjc.h" + +#import + +int printUsage(char *argv0) +{ + NSString *programName = [[NSString alloc] initWithUTF8String:argv0]; + NSLog(@"%@", [[NSString alloc] initWithFormat:@"Usage: %@ add|list ADDRESS_BOOK_FILE", programName]); + [programName release]; + return -1; +} + +NSString *readString(NSString *promt) +{ + NSLog(@"%@", promt); + NSFileHandle *inputFile = [NSFileHandle fileHandleWithStandardInput]; + NSData *inputData = [inputFile availableData]; + NSString *result = [[[NSString alloc]initWithData:inputData encoding:NSUTF8StringEncoding] autorelease]; + result = [[result stringByTrimmingCharactersInSet: + [NSCharacterSet whitespaceAndNewlineCharacterSet]] autorelease]; + return result; +} + +// This function fills in a Person message based on user input. +void promptForAddress(Person* person) +{ + person.id_p = [readString(@"Enter person ID number:") intValue]; + person.name = readString(@"Enter name:"); + + NSString *email = readString(@"Enter email address (blank for none):"); + if ([email length] != 0) + person.email = email; + + while (true) { + NSString *number = readString(@"Enter a phone number (or leave blank to finish):"); + if ([number length] == 0) + break; + + Person_PhoneNumber* phoneNumber = [[Person_PhoneNumber alloc] init]; + phoneNumber.number = number; + + NSString *type = readString(@"Is this a mobile, home, or work phone?:"); + NSLog(@"\"%@\"", type); + if ([type compare:@"mobile"] == NSOrderedSame) + phoneNumber.type = Person_PhoneType_Mobile; + else if ([type compare:@"home"] == NSOrderedSame) + phoneNumber.type = Person_PhoneType_Home; + else if ([type compare:@"work"] == NSOrderedSame) + phoneNumber.type = Person_PhoneType_Work; + else + NSLog(@"%@", @"Unknown phone type. Using default."); + + [person.phonesArray addObject:phoneNumber]; + } +} + +// Iterates though all people in the AddressBook and prints info about them. +void listPeople(AddressBook *addressBook) +{ + NSArray *people = addressBook.peopleArray; + for (unsigned i = 0; i < [people count]; i++) { + Person *person = [people objectAtIndex:i]; + + NSLog(@"%@", [[[NSString alloc] initWithFormat:@"Person ID: %d", person.id_p] autorelease]); + NSLog(@"%@", [[[NSString alloc] initWithFormat:@"Person name: %@", person.name] autorelease]); + + if ([person.email length] != 0) { + NSLog(@"%@", [[[NSString alloc] initWithFormat:@"E-mail address: %@", person.email] autorelease]); + } + + NSArray *phones = person.phonesArray; + for (unsigned j = 0; j < [phones count]; j++) { + Person_PhoneNumber *phoneNumber = [phones objectAtIndex:j]; + NSString *phonePrefix; + + switch (phoneNumber.type) { + case Person_PhoneType_Mobile: + phonePrefix = @"Mobile phone"; + break; + case Person_PhoneType_Home: + phonePrefix = @"Home phone"; + break; + case Person_PhoneType_Work: + phonePrefix = @"Work phone"; + break; + default: + phonePrefix = @"Unknown phone"; + break; + } + + NSLog(@"%@", [[[NSString alloc] initWithFormat:@" %@ #: %@", phonePrefix, phoneNumber.number] autorelease]); + } + printf("\n"); + } +} + +int main(int argc, char *argv[]) +{ + if (argc != 3) + return printUsage(argv[0]); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + AddressBook *addressBook;// = [AddressBook alloc]; + NSString *filePath = [[[NSString alloc] initWithUTF8String:argv[2]] autorelease]; + + // Read the existing address book. + NSData *data = [NSData dataWithContentsOfFile:filePath]; + if (!data) { + NSLog(@"%@", [[NSString alloc] initWithFormat:@"%@ : File not found.", filePath]); + addressBook = [[[AddressBook alloc] init] autorelease]; + } else { + NSError *error; + addressBook = [AddressBook parseFromData:data error:&error]; + if (!addressBook) { + NSLog(@"%@", @"Failed to parse address book."); + [pool drain]; + return -1; + } + } + + if (strcmp(argv[1], "add") == 0) { + // Add an address. + Person *person = [[Person alloc] init]; + promptForAddress(person); + [addressBook.peopleArray addObject:person]; + + if (!data) { + NSLog(@"%@", @"Creating a new file."); + } + [[addressBook data] writeToFile:filePath atomically:YES]; + } else if (strcmp(argv[1], "list") == 0) { + listPeople(addressBook); + } else { + [pool drain]; + return printUsage(argv[0]); + } + + [pool drain]; + return 0; +} diff --git a/examples/protobuf/shared/addressbook.proto b/examples/protobuf/shared/addressbook.proto new file mode 100644 index 00000000..b4b33b4c --- /dev/null +++ b/examples/protobuf/shared/addressbook.proto @@ -0,0 +1,51 @@ +// See README.txt for information and build instructions. +// +// Note: START and END tags are used in comments to define sections used in +// tutorials. They are not part of the syntax for Protocol Buffers. +// +// To get an in-depth walkthrough of this file and the related examples, see: +// https://developers.google.com/protocol-buffers/docs/tutorials + +// [START declaration] +syntax = "proto3"; +package tutorial; + +import "google/protobuf/timestamp.proto"; +// [END declaration] + +// [START java_declaration] +option java_package = "com.example.tutorial"; +option java_outer_classname = "AddressBookProtos"; +// [END java_declaration] + +// [START csharp_declaration] +option csharp_namespace = "Google.Protobuf.Examples.AddressBook"; +// [END csharp_declaration] + +// [START messages] +message Person { + string name = 1; + int32 id = 2; // Unique ID number for this person. + string email = 3; + + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + message PhoneNumber { + string number = 1; + PhoneType type = 2; + } + + repeated PhoneNumber phones = 4; + + google.protobuf.Timestamp last_updated = 5; +} + +// Our address book file is just one of these. +message AddressBook { + repeated Person people = 1; +} +// [END messages] diff --git a/examples/rpaths/main.cpp b/examples/rpaths/main.cpp new file mode 100644 index 00000000..b1b9a6d2 --- /dev/null +++ b/examples/rpaths/main.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** 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 "objecta.h" +#include "objectb.h" + +#include + +int main(int argc, char *argv[]) +{ + (void)argc; + (void)argv; + + std::cout << ObjectA::className() << std::endl; + std::cout << ObjectB::className() << std::endl; + + ObjectB b; + std::cout << b.getA().name() << std::endl; + + return 0; +} diff --git a/examples/rpaths/objecta.cpp b/examples/rpaths/objecta.cpp new file mode 100644 index 00000000..7fbce6d7 --- /dev/null +++ b/examples/rpaths/objecta.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** 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 "objecta.h" + +ObjectA::ObjectA(std::string name) : + m_name(std::move(name)) +{ +} + +std::string ObjectA::className() +{ + return "ObjectA"; +} + +const std::string &ObjectA::name() const +{ + return m_name; +} + +void ObjectA::setName(std::string name) +{ + m_name = std::move(name); +} diff --git a/examples/rpaths/objecta.h b/examples/rpaths/objecta.h new file mode 100644 index 00000000..e2bf145e --- /dev/null +++ b/examples/rpaths/objecta.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** 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$ +** +****************************************************************************/ + +#ifndef OBJECTA_H +#define OBJECTA_H + +#include + +class ObjectA +{ +public: + explicit ObjectA(std::string name); + + static std::string className(); + + const std::string &name() const; + void setName(std::string name); + +private: + std::string m_name; +}; + +#endif // OBJECTA_H diff --git a/examples/rpaths/objectb.cpp b/examples/rpaths/objectb.cpp new file mode 100644 index 00000000..228a68b0 --- /dev/null +++ b/examples/rpaths/objectb.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** 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 "objectb.h" + +ObjectB::ObjectB() = default; + +std::string ObjectB::className() +{ + return "ObjectB"; +} + +const ObjectA &ObjectB::getA() const +{ + return objectA; +} + diff --git a/examples/rpaths/objectb.h b/examples/rpaths/objectb.h new file mode 100644 index 00000000..7c0602a1 --- /dev/null +++ b/examples/rpaths/objectb.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** 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$ +** +****************************************************************************/ + +#ifndef OBJECTB_H +#define OBJECTB_H + +#include + +#include "objecta.h" + +class ObjectB +{ +public: + ObjectB(); + + static std::string className(); + + const ObjectA &getA() const; + +private: + ObjectA objectA{"A"}; +}; + +#endif // OBJECTB_H diff --git a/examples/rpaths/rpaths.qbs b/examples/rpaths/rpaths.qbs new file mode 100644 index 00000000..59d48174 --- /dev/null +++ b/examples/rpaths/rpaths.qbs @@ -0,0 +1,61 @@ +import qbs.FileInfo + +Project { + condition: qbs.targetOS.contains("unix") + + //! [0] + DynamicLibrary { + Depends { name: "cpp" } + Depends { name: "bundle" } + name: "LibraryA" + bundle.isBundle: false + cpp.sonamePrefix: qbs.targetOS.contains("macos") ? "@rpath" : undefined + cpp.rpaths: cpp.rpathOrigin + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.8" + files: [ + "objecta.cpp", + "objecta.h", + ] + install: true + installDir: "examples/lib" + } + //! [0] + + //! [1] + DynamicLibrary { + Depends { name: "cpp" } + Depends { name: "bundle" } + Depends { name: "LibraryA" } + name: "LibraryB" + bundle.isBundle: false + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.8" + cpp.sonamePrefix: qbs.targetOS.contains("macos") ? "@rpath" : undefined + cpp.rpaths: cpp.rpathOrigin + files: [ + "objectb.cpp", + "objectb.h", + ] + install: true + installDir: "examples/lib" + } + //! [1] + + //! [2] + CppApplication { + Depends { name: "bundle" } + Depends { name: "LibraryA" } + Depends { name: "LibraryB" } + name: "rpaths-app" + files: "main.cpp" + consoleApplication: true + bundle.isBundle: false + cpp.rpaths: FileInfo.joinPaths(cpp.rpathOrigin, "..", "lib") + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.8" + install: true + installDir: "examples/bin" + } + //! [2] +} diff --git a/examples/rule/lorem_ipsum.txt b/examples/rule/lorem_ipsum.txt new file mode 100644 index 00000000..2901fbce --- /dev/null +++ b/examples/rule/lorem_ipsum.txt @@ -0,0 +1,4 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer accumsan laoreet magna vitae +elementum. Duis semper ex pellentesque nibh ullamcorper lacinia. Suspendisse sed diam magna. +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In id +maximus turpis, mattis commodo mauris. Sed bibendum accumsan leo. Nulla placerat. diff --git a/examples/rule/rule.qbs b/examples/rule/rule.qbs new file mode 100644 index 00000000..8ec14ee4 --- /dev/null +++ b/examples/rule/rule.qbs @@ -0,0 +1,36 @@ +import qbs.TextFile + +Product { + type: "txt_output" + + Group { + name: "lorem_ipsum" + files: "lorem_ipsum.txt" + fileTags: "txt_input" + } + + //![1] + Rule { + multiplex: false + inputs: ["txt_input"] + Artifact { + filePath: input.fileName + ".out" + fileTags: ["txt_output"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = input.fileName + "->" + output.fileName; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + var file = new TextFile(input.filePath); + var content = file.readAll(); + file.close() + content = content.toUpperCase(); + file = new TextFile(output.filePath, TextFile.WriteOnly); + file.write(content); + file.close(); + } + return [cmd]; + } + } +} diff --git a/qbs-resources/imports/QbsApp.qbs b/qbs-resources/imports/QbsApp.qbs new file mode 100644 index 00000000..caec90f8 --- /dev/null +++ b/qbs-resources/imports/QbsApp.qbs @@ -0,0 +1,32 @@ +import qbs +import qbs.FileInfo + +QbsProduct { + Depends { name: "qbscore" } + Depends { name: "cpp" } + Depends { name: "qbsversion" } + type: ["application", "qbsapplication"] + version: qbsversion.version + consoleApplication: true + cpp.includePaths: [ + "../shared", // for the logger + ] + Group { + fileTagsFilter: product.type + .concat(qbs.buildVariant === "debug" ? ["debuginfo_app"] : []) + qbs.install: true + qbs.installDir: targetInstallDir + qbs.installSourceBase: buildDirectory + } + targetInstallDir: qbsbuildconfig.appInstallDir + Group { + name: "logging" + prefix: FileInfo.joinPaths(product.sourceDirectory, "../shared/logging") + '/' + files: [ + "coloredoutput.cpp", + "coloredoutput.h", + "consolelogger.cpp", + "consolelogger.h" + ] + } +} diff --git a/qbs-resources/imports/QbsAutotest.qbs b/qbs-resources/imports/QbsAutotest.qbs new file mode 100644 index 00000000..5d4143a4 --- /dev/null +++ b/qbs-resources/imports/QbsAutotest.qbs @@ -0,0 +1,42 @@ +import qbs +import qbs.FileInfo +import qbs.Utilities + +QtApplication { + type: ["application", "autotest"] + consoleApplication: true + property string testName + name: "tst_" + testName + property string targetInstallDir: qbsbuildconfig.appInstallDir + Depends { name: "Qt.testlib" } + Depends { name: "qbscore" } + Depends { name: "qbsbuildconfig" } + cpp.defines: [ + "QBS_TEST_SUITE_NAME=" + Utilities.cStringQuote(testName.toUpperCase().replace("-", "_")) + ] + cpp.includePaths: [ + "../../../src", + "../../../src/app/shared", // for the logger + ] + cpp.cxxLanguageVersion: "c++14" + Group { + name: "logging" + prefix: FileInfo.joinPaths(product.sourceDirectory, "../../../src/app/shared/logging") + '/' + files: [ + "coloredoutput.cpp", + "coloredoutput.h", + "consolelogger.cpp", + "consolelogger.h" + ] + } + cpp.rpaths: qbsbuildconfig.libRPaths + + qbs.commonRunEnvironment: ({ + "QBS_INSTALL_DIR": FileInfo.joinPaths(qbs.installRoot, qbs.installPrefix) + }) + Group { + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: targetInstallDir + } +} diff --git a/qbs-resources/imports/QbsLibrary.qbs b/qbs-resources/imports/QbsLibrary.qbs new file mode 100644 index 00000000..88244a90 --- /dev/null +++ b/qbs-resources/imports/QbsLibrary.qbs @@ -0,0 +1,53 @@ +import qbs.FileInfo + +QbsLibraryBase { + Depends { name: "Exporter.pkgconfig"; condition: generatePkgConfigFile } + Depends { name: "Exporter.qbs"; condition: generateQbsModule } + + Group { + fileTagsFilter: libType.concat("dynamiclibrary_symlink") + .concat(qbs.buildVariant === "debug" ? ["debuginfo_dll"] : []) + qbs.install: install + qbs.installDir: targetInstallDir + qbs.installSourceBase: buildDirectory + } + targetInstallDir: qbsbuildconfig.libInstallDir + Group { + fileTagsFilter: ["dynamiclibrary_import"] + qbs.install: install + qbs.installDir: qbsbuildconfig.importLibInstallDir + } + Group { + fileTagsFilter: "Exporter.pkgconfig.pc" + qbs.install: install + qbs.installDir: qbsbuildconfig.pkgConfigInstallDir + } + Group { + fileTagsFilter: "Exporter.qbs.module" + qbs.install: install + qbs.installDir: FileInfo.joinPaths(qbsbuildconfig.qbsModulesBaseDir, product.name) + } + + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + cpp.linkerFlags: ["-compatibility_version", cpp.soVersion] + } + + Export { + Depends { name: "cpp" } + Depends { name: "Qt"; submodules: ["core"] } + + Properties { + condition: product.hasExporter + prefixMapping: [{ + prefix: product.sourceDirectory, + replacement: FileInfo.joinPaths(product.qbs.installPrefix, + product.headerInstallPrefix) + }] + } + + cpp.includePaths: [product.sourceDirectory] + cpp.defines: product.visibilityType === "static" ? ["QBS_STATIC_LIB"] : [] + } +} diff --git a/qbs-resources/imports/QbsLibraryBase.qbs b/qbs-resources/imports/QbsLibraryBase.qbs new file mode 100644 index 00000000..7dc6b5f6 --- /dev/null +++ b/qbs-resources/imports/QbsLibraryBase.qbs @@ -0,0 +1,24 @@ +import qbs +import qbs.FileInfo +import qbs.Utilities + +QbsProduct { + Depends { name: "cpp" } + version: qbsversion.version + type: libType + targetName: (qbs.enableDebugCode && qbs.targetOS.contains("windows")) ? (name + 'd') : name + cpp.defines: base.concat(visibilityType === "static" ? ["QBS_STATIC_LIB"] : ["QBS_LIBRARY"]) + cpp.sonamePrefix: qbs.targetOS.contains("darwin") ? "@rpath" : undefined + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.soVersion: version.replace(/\.\d+$/, '') + } + cpp.visibility: "minimal" + property string visibilityType: staticBuild ? "static" : "dynamic" + property string headerInstallPrefix: "/include/qbs" + property bool hasExporter: Utilities.versionCompare(qbs.version, "1.12") >= 0 + property bool generatePkgConfigFile: qbsbuildconfig.generatePkgConfigFiles && hasExporter + property bool generateQbsModule: install && qbsbuildconfig.generateQbsModules && hasExporter + property bool staticBuild: Qt.core.staticBuild || qbsbuildconfig.staticBuild + property stringList libType: [staticBuild ? "staticlibrary" : "dynamiclibrary"] +} diff --git a/qbs-resources/imports/QbsProduct.qbs b/qbs-resources/imports/QbsProduct.qbs new file mode 100644 index 00000000..ed71f7e6 --- /dev/null +++ b/qbs-resources/imports/QbsProduct.qbs @@ -0,0 +1,25 @@ +import qbs + +Product { + Depends { name: "qbsbuildconfig" } + Depends { name: "qbsversion" } + Depends { name: "Qt.core"; versionAtLeast: minimumQtVersion } + property string minimumQtVersion: "5.11.0" + property bool install: true + property string targetInstallDir + cpp.defines: { + var res = [ + "QT_NO_CAST_FROM_ASCII", + "QT_NO_CAST_FROM_BYTEARRAY", + "QT_NO_PROCESS_COMBINED_ARGUMENT_START" + ]; + if (qbs.toolchain.contains("msvc")) + res.push("_SCL_SECURE_NO_WARNINGS"); + if (qbs.enableDebugCode) + res.push("QT_STRICT_ITERATORS"); + return res; + } + cpp.cxxLanguageVersion: "c++14" + cpp.enableExceptions: true + cpp.rpaths: qbsbuildconfig.libRPaths +} diff --git a/qbs-resources/imports/QbsStaticLibrary.qbs b/qbs-resources/imports/QbsStaticLibrary.qbs new file mode 100644 index 00000000..3342c94d --- /dev/null +++ b/qbs-resources/imports/QbsStaticLibrary.qbs @@ -0,0 +1,9 @@ +QbsLibraryBase { + staticBuild: true + Export { + Depends { name: "cpp" } + Depends { name: "Qt"; submodules: ["core"] } + + cpp.includePaths: [product.sourceDirectory] + } +} diff --git a/qbs-resources/modules/qbsbuildconfig/qbsbuildconfig.qbs b/qbs-resources/modules/qbsbuildconfig/qbsbuildconfig.qbs new file mode 100644 index 00000000..ffa61ec0 --- /dev/null +++ b/qbs-resources/modules/qbsbuildconfig/qbsbuildconfig.qbs @@ -0,0 +1,79 @@ +import qbs +import qbs.FileInfo +import qbs.Utilities + +Module { + Depends { + condition: project.withCode + name: "cpp" + } + property bool enableAddressSanitizer: false + property bool enableUbSanitizer: false + property bool enableThreadSanitizer: false + property bool enableUnitTests: false + property bool enableProjectFileUpdates: false + property bool enableRPath: true + property bool installApiHeaders: true + property bool enableBundledQt: false + property bool useBundledQtScript: false + property bool staticBuild: false + property string libDirName: "lib" + property string appInstallDir: "bin" + property string libInstallDir: qbs.targetOS.contains("windows") ? "bin" : libDirName + property string importLibInstallDir: libDirName + property string libexecInstallDir: qbs.targetOS.contains("windows") ? appInstallDir + : "libexec/qbs" + property string systemSettingsDir + property bool installManPage: qbs.targetOS.contains("unix") + property bool installHtml: true + property bool installQch: false + property bool generatePkgConfigFiles: installApiHeaders && qbs.targetOS.contains("unix") + && !qbs.targetOS.contains("darwin") + property bool generateQbsModules: installApiHeaders + property string docInstallDir: "share/doc/qbs/html" + property string pkgConfigInstallDir: FileInfo.joinPaths(libDirName, "pkgconfig") + property string qbsModulesBaseDir: FileInfo.joinPaths(libDirName, "qbs", "modules") + property string relativeLibexecPath: "../" + libexecInstallDir + property string relativePluginsPath: "../" + libDirName + property string relativeSearchPath: ".." + property stringList libRPaths: { + if (enableRPath && project.withCode && cpp.rpathOrigin && product.targetInstallDir) { + return [FileInfo.joinPaths(cpp.rpathOrigin, FileInfo.relativePath( + FileInfo.joinPaths('/', product.targetInstallDir), + FileInfo.joinPaths('/', libDirName)))]; + } + return []; + } + property string resourcesInstallDir: "" + property string pluginsInstallDir: libDirName + "/qbs/plugins" + property string qmlTypeDescriptionsInstallDir: FileInfo.joinPaths(resourcesInstallDir, + "share/qbs/qml-type-descriptions") + + Properties { + condition: project.withCode && qbs.toolchain.contains("gcc") + cpp.cxxFlags: { + var flags = []; + if (enableAddressSanitizer) + flags.push("-fno-omit-frame-pointer"); + function isClang() { return qbs.toolchain.contains("clang"); } + function versionAtLeast(v) { + return Utilities.versionCompare(cpp.compilerVersion, v) >= 0; + }; + if ((!isClang() && versionAtLeast("9")) + || (isClang() && !qbs.hostOS.contains("darwin") && versionAtLeast("10"))) { + flags.push("-Wno-deprecated-copy", "-Wno-constant-logical-operand"); + } + return flags; + } + cpp.driverFlags: { + var flags = []; + if (enableAddressSanitizer) + flags.push("-fsanitize=address"); + if (enableUbSanitizer) + flags.push("-fsanitize=undefined"); + if (enableThreadSanitizer) + flags.push("-fsanitize=thread"); + return flags; + } + } +} diff --git a/qbs-resources/modules/qbsversion/qbsversion.qbs b/qbs-resources/modules/qbsversion/qbsversion.qbs new file mode 100644 index 00000000..ff1fd18f --- /dev/null +++ b/qbs-resources/modules/qbsversion/qbsversion.qbs @@ -0,0 +1,24 @@ +import qbs +import qbs.File +import qbs.FileInfo +import qbs.TextFile + +Module { + Probe { + id: qbsVersionProbe + property var lastModified: File.lastModified(versionFilePath) + property string versionFilePath: FileInfo.joinPaths(path, "..", "..", "..", "VERSION") + property string version + configure: { + var tf = new TextFile(versionFilePath, TextFile.ReadOnly); + try { + version = tf.readAll().trim(); + found = !!version; + } finally { + tf.close(); + } + } + } + + version: qbsVersionProbe.version +} diff --git a/qbs.pro b/qbs.pro new file mode 100644 index 00000000..cf80b1fa --- /dev/null +++ b/qbs.pro @@ -0,0 +1,75 @@ +requires(!cross_compile) +include(src/lib/bundledlibs.pri) + +defineTest(minQtVersion) { + maj = $$1 + min = $$2 + patch = $$3 + isEqual(QT_MAJOR_VERSION, $$maj) { + isEqual(QT_MINOR_VERSION, $$min) { + isEqual(QT_PATCH_VERSION, $$patch) { + return(true) + } + greaterThan(QT_PATCH_VERSION, $$patch) { + return(true) + } + } + greaterThan(QT_MINOR_VERSION, $$min) { + return(true) + } + } + greaterThan(QT_MAJOR_VERSION, $$maj) { + return(true) + } + return(false) +} + +!minQtVersion(5, 11, 0) { + message("Cannot build qbs with Qt version $${QT_VERSION}.") + error("Use at least Qt 5.11.0.") +} + +TEMPLATE = subdirs +corelib.file = src/lib/corelib/corelib.pro +msbuildlib.subdir = src/lib/msbuild +msbuildlib.depends = corelib +src_app.subdir = src/app +src_app.depends = corelib +src_libexec.subdir = src/libexec +src_plugins.subdir = src/plugins +CONFIG(shared, static|shared) { + src_plugins.depends = corelib + src_app.depends += src_plugins +} +src_plugins.depends += msbuildlib +tests.depends = static_res +static_res.file = static-res.pro +static_res.depends = src_app src_libexec src_plugins static.pro +qbs_use_bundled_qtscript { + scriptenginelib.file = src/lib/scriptengine/scriptengine.pro + corelib.depends = scriptenginelib + SUBDIRS += scriptenginelib +} +SUBDIRS += \ + corelib\ + msbuildlib\ + src_app\ + src_libexec\ + src_plugins\ + static.pro\ + static_res\ + tests + +OTHER_FILES += \ + doc/*.qdoc \ + doc/reference/*.qdoc \ + doc/reference/items/convenience/*.qdoc \ + doc/reference/items/language/*.qdoc \ + doc/reference/jsextensions/*.qdoc \ + doc/reference/modules/*.qdoc \ + doc/targets/*.qdoc* \ + doc/qbs.qdocconf \ + doc/config/qbs-project.qdocconf + +include(qbs_version.pri) +include(doc/doc.pri) diff --git a/qbs.qbs b/qbs.qbs new file mode 100644 index 00000000..92f167b4 --- /dev/null +++ b/qbs.qbs @@ -0,0 +1,65 @@ +import qbs 1.0 + +Project { + minimumQbsVersion: "1.6" + qbsSearchPaths: ["qbs-resources"] + property bool withCode: true + property bool withDocumentation: true + property bool withExamples: false + property bool withTests: withCode + property stringList autotestArguments: [] + property stringList autotestWrapper: [] + + references: [ + "docker/docker.qbs", + "share/share.qbs", + "scripts/scripts.qbs", + ] + + SubProject { + filePath: "doc/doc.qbs" + Properties { + condition: parent.withDocumentation + } + } + + SubProject { + filePath: "examples/examples.qbs" + Properties { + condition: parent.withExamples + } + } + + SubProject { + filePath: "src/src.qbs" + Properties { + condition: parent.withCode + } + } + + SubProject { + filePath: "tests/tests.qbs" + Properties { + condition: parent.withTests + } + } + + Product { + name: "version" + files: ["VERSION"] + } + + Product { + name: "qmake project files for qbs" + files: ["**/*.pr[io]"] + } + + Product { + name: "continuous integration files" + files: [ + ".travis.yml", + ".clang-tidy", + "docker-compose.yml", + ] + } +} diff --git a/qbs_version.pri b/qbs_version.pri new file mode 100644 index 00000000..97c4c799 --- /dev/null +++ b/qbs_version.pri @@ -0,0 +1,4 @@ +QBS_VERSION = $$cat($$PWD/VERSION) +QBS_VERSION_MAJ = $$section(QBS_VERSION, ., 0, 0) +QBS_VERSION_MIN = $$section(QBS_VERSION, ., 1, 1) +DEFINES += QBS_VERSION=\\\"$$QBS_VERSION\\\" diff --git a/scripts/address-sanitizer-suppressions.txt b/scripts/address-sanitizer-suppressions.txt new file mode 100644 index 00000000..f3e1980b --- /dev/null +++ b/scripts/address-sanitizer-suppressions.txt @@ -0,0 +1,3 @@ +leak:libQt5Script.so.5 +leak:QThreadPrivate::QThreadPrivate +leak:QArrayData::allocate diff --git a/scripts/build-qbs-with-qbs.sh b/scripts/build-qbs-with-qbs.sh new file mode 100755 index 00000000..d1c1916f --- /dev/null +++ b/scripts/build-qbs-with-qbs.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +############################################################################# +## +## Copyright (C) 2019 Richard Weickelt. +## 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$ +## +############################################################################# +set -e + +# +# Qbs is built with the address sanitizer enabled. +# Suppress findings in some parts of Qbs / dependencies. +# +export LSAN_OPTIONS="suppressions=$( cd "$(dirname "$0")" ; pwd -P )/address-sanitizer-suppressions.txt:print_suppressions=0" + +if [ -z "${QBS_BUILD_PROFILE}" ]; then + QBS_BUILD_PROFILE=$(qbs config defaultProfile | cut -d: -f2 | tr -d '[:space:]') +fi +if [ -z "${QBS_BUILD_PROFILE}" ]; then + echo "Either QBS_BUILD_PROFILE or a defaultProfile must be set." + exit 1 +fi + +# +# Additional build options +# +BUILD_OPTIONS="\ + profile:${QBS_BUILD_PROFILE} \ + modules.qbsbuildconfig.enableAddressSanitizer:true \ + modules.qbsbuildconfig.enableProjectFileUpdates:true \ + modules.qbsbuildconfig.enableUnitTests:true \ + modules.cpp.treatWarningsAsErrors:true \ + modules.cpp.separateDebugInformation:true \ + ${BUILD_OPTIONS} \ + config:release \ +" + +# +# Build all default products of Qbs +# +qbs resolve ${BUILD_OPTIONS} +qbs build ${BUILD_OPTIONS} + +WITH_DOCS=${WITH_DOCS:-1} +if [ "$WITH_DOCS" -ne 0 ]; then + qbs build -p "qbs documentation" ${BUILD_OPTIONS} +fi + +QMAKE_PATH=${QMAKE_PATH:-$(which qmake)} + +# +# Set up profiles for the freshly built Qbs if not +# explicitly specified otherwise +# +if [ -z "${QBS_AUTOTEST_PROFILE}" ]; then + + export QBS_AUTOTEST_PROFILE=autotestprofile + export QBS_AUTOTEST_SETTINGS_DIR=`mktemp -d 2>/dev/null || mktemp -d -t 'qbs-settings'` + + QBS_AUTOTEST_QMAKE_PATH=${QBS_AUTOTEST_QMAKE_PATH:-${QMAKE_PATH}} + + RUN_OPTIONS="\ + --settings-dir ${QBS_AUTOTEST_SETTINGS_DIR} \ + " + + qbs run -p qbs_app ${BUILD_OPTIONS} -- setup-toolchains \ + ${RUN_OPTIONS} \ + --detect + + qbs run -p qbs_app ${BUILD_OPTIONS} -- setup-qt \ + ${RUN_OPTIONS} \ + "${QBS_AUTOTEST_QMAKE_PATH}" ${QBS_AUTOTEST_PROFILE} + + # Make sure that the Qt profile uses the same toolchain profile + # that was used for building in case a custom QBS_BUILD_PROFILE + # was set. Otherwise setup-qt automatically uses the default + # toolchain profile. + if [ -z "${QBS_AUTOTEST_BASE_PROFILE}" ]; then + QBS_AUTOTEST_BASE_PROFILE=$(qbs config profiles.${QBS_BUILD_PROFILE}.baseProfile | cut -d: -f2) + fi + if [ ! -z "${QBS_AUTOTEST_BASE_PROFILE}" ]; then + echo "Setting base profile for ${QBS_AUTOTEST_PROFILE} to ${QBS_AUTOTEST_BASE_PROFILE}" + qbs run -p qbs_app ${BUILD_OPTIONS} -- config \ + ${RUN_OPTIONS} \ + profiles.${QBS_AUTOTEST_PROFILE}.baseProfile ${QBS_AUTOTEST_BASE_PROFILE} + fi + + qbs run -p qbs_app ${BUILD_OPTIONS} -- config \ + ${RUN_OPTIONS} \ + --list + + # QBS_AUTOTEST_PROFILE has been added to the environment + # which requires a resolve step + qbs resolve ${BUILD_OPTIONS} +fi + +# +# Run all autotests with QBS_AUTOTEST_PROFILE. Some test cases might run for +# over 10 minutes. Output an empty line every 9:50 minutes to prevent a 10min +# timeout on Travis CI. +# +(while true; do echo "" && sleep 590; done) & +trap "kill $!; wait $! 2>/dev/null || true; killall sleep || true" EXIT +qbs build -p "autotest-runner" ${BUILD_OPTIONS} diff --git a/scripts/build-qbs-with-qmake.sh b/scripts/build-qbs-with-qmake.sh new file mode 100755 index 00000000..1e97d769 --- /dev/null +++ b/scripts/build-qbs-with-qmake.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +############################################################################# +## +## Copyright (C) 2019 Richard Weickelt. +## 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$ +## +############################################################################# +set -e + +# +# It might be desired to keep settings for Qbs testing +# in a separate folder. +# +export QBS_AUTOTEST_SETTINGS_DIR="${QBS_AUTOTEST_SETTINGS_DIR:-/tmp/qbs-settings}" + +# +# Build all default products of Qbs +# +qmake -r qbs.pro \ + CONFIG+=qbs_enable_unit_tests \ + CONFIG+=qbs_enable_project_file_updates \ + ${BUILD_OPTIONS} +make -j $(nproc --all) +make docs + +# +# Set up profiles for the freshly built Qbs if not +# explicitly specified otherwise +# +if [ -z "${QBS_AUTOTEST_PROFILE}" ]; then + + export QBS_AUTOTEST_PROFILE=autotestprofile + RUN_OPTIONS="\ + --settings-dir ${QBS_AUTOTEST_SETTINGS_DIR} \ + " + + ./bin/qbs setup-toolchains \ + ${RUN_OPTIONS} \ + --detect + + ./bin/qbs setup-qt \ + ${RUN_OPTIONS} \ + "${QMAKE_PATH:-$(which qmake)}" ${QBS_AUTOTEST_PROFILE} + + ./bin/qbs config \ + ${RUN_OPTIONS} \ + ${QBS_AUTOTEST_PROFILE}.baseProfile gcc + +fi + + +make check -j $(nproc --all) diff --git a/scripts/install-qt.sh b/scripts/install-qt.sh new file mode 100755 index 00000000..be953e77 --- /dev/null +++ b/scripts/install-qt.sh @@ -0,0 +1,319 @@ +#!/usr/bin/env bash +############################################################################# +## +## Copyright (C) 2019 Richard Weickelt. +## 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$ +## +############################################################################# +set -eu + +function help() { + cat < + Root directory where to install the components. + Maps to C:/Qt on Windows, /opt/Qt on Linux, /usr/local/Qt on Mac + by default. + + -f, --force + Force download and do not attempt to re-use an existing installation. + + --host + The host operating system. Can be one of linux_x64, mac_x64, + windows_x86. Auto-detected by default. + + --target + The desired target platform. Can be one of desktop, android, ios. + The default value is desktop. + + --toolchain + The toolchain that has been used to build the binaries. + Possible values depend on --host and --target, respectively: + + linux_x64 + android + any, android_armv7, android_arm64_v8a + desktop + gcc_64 (default) + + mac_x64 + android + any, android_armv7, android_arm64_v8a + desktop + clang_64 (default), + ios + ios + + windows_x86 + android + any, android_armv7, android_arm64_v8a + desktop + win64_mingw73, win64_msvc2017_64 (default) + + --version + The desired Qt version. Currently supported are all versions + above 5.9.0. + +EOF +} + +TARGET_PLATFORM=desktop +COMPONENTS= +VERSION= +FORCE_DOWNLOAD=false +MD5_TOOL=md5sum + +case "$OSTYPE" in + *linux*) + HOST_OS=linux_x64 + INSTALL_DIR=/opt/Qt + TOOLCHAIN=gcc_64 + ;; + *darwin*) + HOST_OS=mac_x64 + INSTALL_DIR=/usr/local/Qt + TOOLCHAIN=clang_64 + MD5_TOOL="md5 -r" + ;; + msys) + HOST_OS=windows_x86 + INSTALL_DIR=/c/Qt + TOOLCHAIN=win64_msvc2015_64 + ;; + *) + HOST_OS= + INSTALL_DIR= + ;; +esac + +while [ $# -gt 0 ]; do + case "$1" in + --directory|-d) + INSTALL_DIR="$2" + shift + ;; + --force|-f) + FORCE_DOWNLOAD=true + ;; + --host) + HOST_OS="$2" + shift + ;; + --target) + TARGET_PLATFORM="$2" + shift + ;; + --toolchain) + TOOLCHAIN=$(echo $2 | tr '[A-Z]' '[a-z]') + shift + ;; + --version) + VERSION="$2" + shift + ;; + --help|-h) + help + exit 0 + ;; + *) + COMPONENTS="${COMPONENTS} $1" + ;; + esac + shift +done + +if [ -z "${HOST_OS}" ]; then + echo "No --host specified or auto-detection failed." >&2 + exit 1 +fi + +if [ -z "${INSTALL_DIR}" ]; then + echo "No --directory specified or auto-detection failed." >&2 + exit 1 +fi + +if [ -z "${VERSION}" ]; then + echo "No --version specified." >&2 + exit 1 +fi + +if [ -z "${COMPONENTS}" ]; then + echo "No components specified." >&2 + exit 1 +fi + +case "$TARGET_PLATFORM" in + android) + ;; + ios) + ;; + desktop) + ;; + *) + echo "Error: TARGET_PLATFORM=${TARGET_PLATFORM} is not valid." >&2 + exit 1 + ;; +esac + +HASH=$(echo "${OSTYPE} ${TARGET_PLATFORM} ${TOOLCHAIN} ${VERSION} ${INSTALL_DIR}" | ${MD5_TOOL} | head -c 16) +HASH_FILEPATH="${INSTALL_DIR}/${HASH}.manifest" +INSTALLATION_IS_VALID=false +if ! ${FORCE_DOWNLOAD} && [ -f "${HASH_FILEPATH}" ]; then + INSTALLATION_IS_VALID=true + while read filepath; do + if [ ! -e "${filepath}" ]; then + INSTALLATION_IS_VALID=false + break + fi + done <"${HASH_FILEPATH}" +fi + +if ${INSTALLATION_IS_VALID}; then + echo "Already installed. Skipping download." + exit 0 +fi + +DOWNLOAD_DIR=`mktemp -d 2>/dev/null || mktemp -d -t 'install-qt'` + +# +# The repository structure is a mess. Try different URL variants +# +function compute_url(){ + local COMPONENT=$1 + local CURL="curl -s -L" + local BASE_URL="http://download.qt.io/online/qtsdkrepository/${HOST_OS}/${TARGET_PLATFORM}" + + if [[ "${COMPONENT}" =~ "qtcreator" ]]; then + + REMOTE_BASE="tools_qtcreator/qt.tools.qtcreator" + REMOTE_PATH="$(${CURL} ${BASE_URL}/${REMOTE_BASE}/ | grep -o -E "${VERSION}[0-9\-]*${COMPONENT}\.7z" | tail -1)" + + if [ ! -z "${REMOTE_PATH}" ]; then + echo "${BASE_URL}/${REMOTE_BASE}/${REMOTE_PATH}" + return 0 + fi + + else + REMOTE_BASES=( + # New repository format (>=5.9.6) + "qt5_${VERSION//./}/qt.qt5.${VERSION//./}.${TOOLCHAIN}" + "qt5_${VERSION//./}/qt.qt5.${VERSION//./}.${COMPONENT}.${TOOLCHAIN}" + # Multi-abi Android since 5.14 + "qt5_${VERSION//./}/qt.qt5.${VERSION//./}.${TARGET_PLATFORM}" + "qt5_${VERSION//./}/qt.qt5.${VERSION//./}.${COMPONENT}.${TARGET_PLATFORM}" + # Older repository format (<5.9.0) + "qt5_${VERSION//./}/qt.${VERSION//./}.${TOOLCHAIN}" + "qt5_${VERSION//./}/qt.${VERSION//./}.${COMPONENT}.${TOOLCHAIN}" + ) + + for REMOTE_BASE in ${REMOTE_BASES[*]}; do + REMOTE_PATH="$(${CURL} ${BASE_URL}/${REMOTE_BASE}/ | grep -o -E "[[:alnum:]_.\-]*7z" | grep "${COMPONENT}" | tail -1)" + if [ ! -z "${REMOTE_PATH}" ]; then + echo "${BASE_URL}/${REMOTE_BASE}/${REMOTE_PATH}" + return 0 + fi + done + fi + + echo "Could not determine a remote URL for ${COMPONENT} with version ${VERSION}">&2 + exit 1 +} + +mkdir -p ${INSTALL_DIR} +rm -f "${HASH_FILEPATH}" + +for COMPONENT in ${COMPONENTS}; do + + URL="$(compute_url ${COMPONENT})" + echo "Downloading ${COMPONENT}..." >&2 + curl --progress-bar -L -o ${DOWNLOAD_DIR}/package.7z ${URL} >&2 + 7z x -y -o${INSTALL_DIR} ${DOWNLOAD_DIR}/package.7z >/dev/null 2>&1 + 7z l -ba -slt -y ${DOWNLOAD_DIR}/package.7z | tr '\\' '/' | sed -n -e "s|^Path\ =\ |${INSTALL_DIR}/|p" >> "${HASH_FILEPATH}" 2>/dev/null + rm -f ${DOWNLOAD_DIR}/package.7z + + # + # conf file is needed for qmake + # + if [ "${COMPONENT}" == "qtbase" ]; then + if [[ "${TOOLCHAIN}" =~ "win64_mingw" ]]; then + SUBDIR="${TOOLCHAIN/win64_/}_64" + elif [[ "${TOOLCHAIN}" =~ "win32_mingw" ]]; then + SUBDIR="${TOOLCHAIN/win32_/}_32" + elif [[ "${TOOLCHAIN}" =~ "win64_msvc" ]]; then + SUBDIR="${TOOLCHAIN/win64_/}" + elif [[ "${TOOLCHAIN}" =~ "win32_msvc" ]]; then + SUBDIR="${TOOLCHAIN/win32_/}" + elif [[ "${TOOLCHAIN}" =~ "any" ]] && [[ "${TARGET_PLATFORM}" == "android" ]]; then + SUBDIR="android" + else + SUBDIR="${TOOLCHAIN}" + fi + + CONF_FILE="${INSTALL_DIR}/${VERSION}/${SUBDIR}/bin/qt.conf" + echo "[Paths]" > ${CONF_FILE} + echo "Prefix = .." >> ${CONF_FILE} + + # Adjust the license to be able to run qmake + # sed with -i requires intermediate file on Mac OS + PRI_FILE="${INSTALL_DIR}/${VERSION}/${SUBDIR}/mkspecs/qconfig.pri" + sed -i.bak 's/Enterprise/OpenSource/g' "${PRI_FILE}" + sed -i.bak 's/licheck.*//g' "${PRI_FILE}" + rm "${PRI_FILE}.bak" + + # Print the directory so that the caller can + # adjust the PATH variable. + echo $(dirname "${CONF_FILE}") + elif [[ "${COMPONENT}" =~ "qtcreator" ]]; then + echo "${INSTALL_DIR}/Tools/QtCreator/bin" + fi + +done diff --git a/scripts/make-release-archives.bat b/scripts/make-release-archives.bat new file mode 100644 index 00000000..0970d270 --- /dev/null +++ b/scripts/make-release-archives.bat @@ -0,0 +1,77 @@ +@echo off + +REM Copyright (C) 2017 The Qt Company Ltd. +REM Contact: https://www.qt.io/licensing/ +REM +REM This file is part of Qbs. +REM +REM $QT_BEGIN_LICENSE:LGPL$ +REM Commercial License Usage +REM Licensees holding valid commercial Qt licenses may use this file in +REM accordance with the commercial license agreement provided with the +REM Software or, alternatively, in accordance with the terms contained in +REM a written agreement between you and The Qt Company. For licensing terms +REM and conditions see https://www.qt.io/terms-conditions. For further +REM information use the contact form at https://www.qt.io/contact-us. +REM +REM GNU Lesser General Public License Usage +REM Alternatively, this file may be used under the terms of the GNU Lesser +REM General Public License version 3 as published by the Free Software +REM Foundation and appearing in the file LICENSE.LGPL3 included in the +REM packaging of this file. Please review the following information to +REM ensure the GNU Lesser General Public License version 3 requirements +REM will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +REM +REM GNU General Public License Usage +REM Alternatively, this file may be used under the terms of the GNU +REM General Public License version 2.0 or (at your option) the GNU General +REM Public license version 3 or any later version approved by the KDE Free +REM Qt Foundation. The licenses are as published by the Free Software +REM Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +REM included in the packaging of this file. Please review the following +REM information to ensure the GNU General Public License requirements will +REM be met: https://www.gnu.org/licenses/gpl-2.0.html and +REM https://www.gnu.org/licenses/gpl-3.0.html. +REM +REM $QT_END_LICENSE$ + +setlocal enabledelayedexpansion || exit /b +if not exist VERSION ( echo This script must be run from the qbs source directory 1>&2 && exit /b 1 ) +for /f %%j in (VERSION) do ( set "version=!version!%%j," ) +set "version=%version:~0,-1%" + +set builddir=%TEMP%\qbs-release-%version% +if exist "%builddir%" ( del /s /q "%builddir%" || exit /b ) + +qbs setup-toolchains --settings-dir "%builddir%\.settings" --detect || exit /b + +if exist "%QTDIR%" ( + qbs setup-qt --settings-dir "%builddir%\.settings"^ + "%QTDIR%\bin\qmake.exe" qt || exit /b +) else ( + echo QTDIR environment variable not set or does not exist: %QTDIR% + exit /b 1 +) +if exist "%QTDIR64%" ( + qbs setup-qt --settings-dir "%builddir%\.settings"^ + "%QTDIR64%\bin\qmake.exe" qt64 || exit /b +) else ( + echo QTDIR64 environment variable not set or does not exist: %QTDIR64% + exit /b 1 +) + +REM Work around QBS-1142, where symlinks to UNC named paths aren't resolved +REM properly, for example if this command is being run in a Docker container +REM where the current directory is a symlink +subst Q: "%CD%" && Q: + +qbs build --settings-dir "%builddir%\.settings"^ + -f qbs.qbs -d "%builddir%\build"^ + -p dist qbs.buildVariant:release project.withDocumentation:false "products.qbs archive.includeTopLevelDir:true"^ + modules.qbsbuildconfig.enableBundledQt:true^ + config:release "qbs.installRoot:%builddir%\qbs-windows-x86-%version%" profile:qt^ + config:release-64 "qbs.installRoot:%builddir%\qbs-windows-x86_64-%version%" profile:qt64 || exit /b + +copy /y "%builddir%\build\release\qbs.%version%.nupkg" dist || exit /b +copy /y "%builddir%\build\release\qbs-windows-x86-%version%.zip" dist || exit /b +copy /y "%builddir%\build\release-64\qbs-windows-x86_64-%version%.zip" dist || exit /b diff --git a/scripts/make-release-archives.sh b/scripts/make-release-archives.sh new file mode 100755 index 00000000..c3a0771e --- /dev/null +++ b/scripts/make-release-archives.sh @@ -0,0 +1,50 @@ +#!/bin/sh +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$ +## +############################################################################# + +test $# -eq 1 || { echo "Usage: $(basename "$0") " >&2; exit 1; } + +tag=${1} +version=${tag#v} +dir_name=qbs-src-${version} + +git archive --format=tar.gz "--prefix=${dir_name}/" -o "dist/${dir_name}.tar.gz" "${tag}" +git archive --format=zip "--prefix=${dir_name}/" -o "dist/${dir_name}.zip" "${tag}" diff --git a/scripts/run-analyzer.sh b/scripts/run-analyzer.sh new file mode 100755 index 00000000..a60bcc02 --- /dev/null +++ b/scripts/run-analyzer.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +############################################################################# +## +## Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +## 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$ +## +############################################################################# + +LLVM_INSTALL_DIR=${LLVM_INSTALL_DIR:-""} + +# on Debian, it might be necessary to setup which version of clang-tidy and run-clang-tidy.py +# is desired: +# update-alternatives --install /usr/bin/run-clang-tidy.py run-clang-tidy.py /usr/bin/run-clang-tidy-4.0.py 1 + +CLANG_TIDY=`which clang-tidy` +RUN_CLANG_TIDY=`which run-clang-tidy.py` + +if [ -z "$RUN_CLANG_TIDY" ] || [ -z "$CLANG_TIDY" ]; then + if [ ! -z "$LLVM_INSTALL_DIR" ]; then + CLANG_TIDY="$LLVM_INSTALL_DIR/bin/clang-tidy" + RUN_CLANG_TIDY="$LLVM_INSTALL_DIR/share/clang/run-clang-tidy.py" + else + echo "Can't find clang-tidy and/or run-clang-tidy.py in PATH, try setting LLVM_INSTALL_DIR" + exit 1 + fi +fi + +NPROC=`which nproc` +SYSCTL=`which sysctl` +CPU_COUNT=2 +if [ ! -z "$NPROC" ]; then # Linux + CPU_COUNT=`$NPROC --all` +elif [ ! -z "$SYSCTL" ]; then # macOS + CPU_COUNT=`$SYSCTL -n hw.ncpu` +fi + +BUILD_OPTIONS="\ + ${QBS_BUILD_PROFILE:+profile:${QBS_BUILD_PROFILE}} \ + modules.qbsbuildconfig.enableProjectFileUpdates:true \ + modules.cpp.treatWarningsAsErrors:true \ + modules.qbs.buildVariant:release \ + project.withTests:false \ + ${BUILD_OPTIONS} \ + config:analyzer +" + +QBS_SRC_DIR=${QBS_SRC_DIR:-`pwd`} + +if [ ! -f "$QBS_SRC_DIR/qbs.qbs" ]; then + echo "Can't find qbs.qbs in $QBS_SRC_DIR, try setting QBS_SRC_DIR" + exit 1 +fi + +set -e +set -o pipefail + +qbs resolve -f "$QBS_SRC_DIR/qbs.qbs" $BUILD_OPTIONS +qbs build -f "$QBS_SRC_DIR/qbs.qbs" $BUILD_OPTIONS +qbs generate -g clangdb -f "$QBS_SRC_DIR/qbs.qbs" $BUILD_OPTIONS + +SCRIPT=" +import json +import os +import sys + +dbFile = sys.argv[1] +blacklist = ['json.cpp'] +seenFiles = set() +patched_db = [] +with open(dbFile, 'r') as f: + db = json.load(f) + for item in db: + file = item['file'] + if (os.path.basename(file) not in blacklist) and (file not in seenFiles): + seenFiles.add(file) + patched_db.append(item) + +with open(dbFile, 'w') as f: + f.write(json.dumps(patched_db, indent=2)) +" +python3 -c "${SCRIPT}" analyzer/compile_commands.json + +RUN_CLANG_TIDY+=" -p analyzer -clang-tidy-binary ${CLANG_TIDY} -j ${CPU_COUNT} -header-filter=\".*qbs.*\.h$\" -quiet" +${RUN_CLANG_TIDY} 2>/dev/null | tee results.txt +echo "$(grep -c 'warning:' results.txt) warnings in total" diff --git a/scripts/scripts.qbs b/scripts/scripts.qbs new file mode 100644 index 00000000..9a631ed1 --- /dev/null +++ b/scripts/scripts.qbs @@ -0,0 +1,9 @@ +import qbs + +Product { + name: "qbs dev scripts" + files: [ + "*.bat", + "*.sh", + ] +} diff --git a/scripts/test-qt-for-android.sh b/scripts/test-qt-for-android.sh new file mode 100755 index 00000000..44a64db2 --- /dev/null +++ b/scripts/test-qt-for-android.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -eu + +############################################################################# +## +## Copyright (C) 2019 Richard Weickelt. +## 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$ +## +############################################################################# + +export PATH="$1:$PATH" + +# +# These are set outside of this script, for instance in the Docker image +# +QT_INSTALL_DIR=/opt/Qt/${QT_VERSION} +echo "Android SDK installed at ${ANDROID_SDK_ROOT}" +echo "Android NDK installed at ${ANDROID_NDK_ROOT}" +echo "Qt installed at ${QT_INSTALL_DIR}" + +# Cleaning profiles +qbs config --unset profiles.qbs_autotests-android +qbs config --unset profiles.qbs_autotests-android-qt + +# Setting auto test profiles +qbs setup-android --ndk-dir ${ANDROID_HOME}/ndk-bundle --sdk-dir ${ANDROID_HOME} qbs_autotests-android +qbs setup-android --ndk-dir ${ANDROID_HOME}/ndk-bundle --sdk-dir ${ANDROID_HOME} --qt-dir ${QT_INSTALL_DIR} qbs_autotests-android-qt + +export QBS_AUTOTEST_PROFILE=qbs_autotests-android +export QBS_AUTOTEST_ALWAYS_LOG_STDERR=true + +if [ ! "${QT_VERSION}" \< "5.14.0" ]; then + echo "Using multi-arch data sets for qml tests (only for qt version >= 5.14) with all architectures" + qbs config --list + tst_blackbox-android + + echo "Using multi-arch data sets for qml tests (only for qt version >= 5.14) with only armv7a and x86_64" + qbs config profiles.qbs_autotests-android-qt.qbs.architectures '["armv7a","x86_64"]' + qbs config --list + tst_blackbox-android +fi; + +echo "Using single-arch (armv7a) data sets for qml tests" +qbs config --unset profiles.qbs_autotests-android-qt.qbs.architectures +qbs config profiles.qbs_autotests-android-qt.qbs.architecture armv7a + +qbs config --list + +tst_blackbox-android + diff --git a/scripts/thread-sanitizer-suppressions.txt b/scripts/thread-sanitizer-suppressions.txt new file mode 100644 index 00000000..9b2a9baa --- /dev/null +++ b/scripts/thread-sanitizer-suppressions.txt @@ -0,0 +1 @@ +called_from_lib:libtsan.so diff --git a/scripts/update-dmgbuild.sh b/scripts/update-dmgbuild.sh new file mode 100755 index 00000000..ba4c2c82 --- /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.3 dmgbuild.git@v1.3.2 ds_store@v1.1.2 mac_alias.git@v2.0.7) +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/scripts/update-xcspecs.sh b/scripts/update-xcspecs.sh new file mode 100755 index 00000000..2c046f38 --- /dev/null +++ b/scripts/update-xcspecs.sh @@ -0,0 +1,53 @@ +#!/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$ +## +############################################################################# + +# 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")" > \ + "$specs_out_dir/${spec_file// /-}" +done +xcode_version="$(/usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' \ + "$(xcode-select --print-path)/../Info.plist")" +echo "Updated build specs from Xcode $xcode_version" diff --git a/share/qbs/imports/qbs/BundleTools/bundle-tools.js b/share/qbs/imports/qbs/BundleTools/bundle-tools.js new file mode 100644 index 00000000..275f410c --- /dev/null +++ b/share/qbs/imports/qbs/BundleTools/bundle-tools.js @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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 DarwinTools = require("qbs.DarwinTools"); +var PropertyList = require("qbs.PropertyList"); + +function destinationDirectoryForResource(product, input) { + var path = product.destinationDirectory; + var inputFilePath = FileInfo.joinPaths(input.baseDir, input.fileName); + var key = DarwinTools.localizationKey(inputFilePath); + if (key) { + path = FileInfo.joinPaths(path, localizedResourcesFolderPath(product, key)); + var subPath = DarwinTools.relativeResourcePath(inputFilePath); + if (subPath && subPath !== '.') + path = FileInfo.joinPaths(path, subPath); + } else { + path = FileInfo.joinPaths(path, product.moduleProperty("bundle", "unlocalizedResourcesFolderPath")); + } + return path; +} + +function localizedResourcesFolderPath(product, key) { + return FileInfo.joinPaths(product.moduleProperty("bundle", "unlocalizedResourcesFolderPath"), key + product.moduleProperty("bundle", "localizedResourcesFolderSuffix")); +} + +function infoPlistContents(infoPlistFilePath) { + if (infoPlistFilePath === undefined) + return undefined; + + var plist = new PropertyList(); + try { + plist.readFromFile(infoPlistFilePath); + return plist.toObject(); + } finally { + plist.clear(); + } +} + +function infoPlistFormat(infoPlistFilePath) { + if (infoPlistFilePath === undefined) + return undefined; + + var plist = new PropertyList(); + try { + plist.readFromFile(infoPlistFilePath); + return plist.format(); + } finally { + plist.clear(); + } +} diff --git a/share/qbs/imports/qbs/DarwinTools/darwin-tools.js b/share/qbs/imports/qbs/DarwinTools/darwin-tools.js new file mode 100644 index 00000000..5f907600 --- /dev/null +++ b/share/qbs/imports/qbs/DarwinTools/darwin-tools.js @@ -0,0 +1,271 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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 Utilities = require("qbs.Utilities"); + +var _deviceMap = { + "mac": undefined, // only devices have an ID + "iphone": 1, + "ipad": 2, + "tv": 3, + "watch": 4, + "car": 5 +}; + +var _platformMap = { + "ios": "iPhone", + "macos": "MacOSX", + "tvos": "AppleTV", + "watchos": "Watch" +}; + +var _platformDeviceMap = { + "ios": ["iphone", "ipad"], + "macos": ["mac"], + "tvos": ["tv"], + "watchos": ["watch"] +} + +/** + * Returns the numeric identifier corresponding to an Apple device name + * (i.e. for use by TARGETED_DEVICE_FAMILY). + */ +function appleDeviceNumber(deviceName) { + return _deviceMap[deviceName]; +} + +/** + * Returns the list of target devices available for the given qbs target OS list. + */ +function targetDevices(targetOS) { + for (var key in _platformDeviceMap) { + if (targetOS.contains(key)) + return _platformDeviceMap[key]; + } +} + +/** + * Returns the TARGETED_DEVICE_FAMILY string given a list of target device names. + */ +function targetedDeviceFamily(deviceNames) { + return deviceNames.map(function (deviceName) { + return appleDeviceNumber(deviceName); + }); +} + +/** + * Returns the most appropriate Apple platform name given a targetOS list. + */ +function applePlatformName(targetOSList, platformType) { + return applePlatformDirectoryName(targetOSList, platformType).toLowerCase(); +} + +/** + * Returns the most appropriate Apple platform directory name given a targetOS list and version. + */ +function applePlatformDirectoryName(targetOSList, platformType, version, throwOnError) { + var suffixMap = { + "device": "OS", + "simulator": "Simulator" + }; + + for (var key in _platformMap) { + if (targetOSList.contains(key)) { + // there are no MacOSXOS or MacOSXSimulator platforms + var suffix = (key !== "macos") ? (suffixMap[platformType] || "") : ""; + return _platformMap[key] + suffix + (version || ""); + } + } + + if (throwOnError || throwOnError === undefined) + throw("No Apple platform corresponds to target OS list: " + targetOSList); +} + +/** + * Returns the localization of the resource at the given path, + * or undefined if the path does not contain an {xx}.lproj path segment. + */ +function localizationKey(path) { + return _resourceFileProperties(path)[0]; +} + +/** + * Returns the path of a localized resource at the given path, + * relative to its containing {xx}.lproj directory, or '.' + * if the resource is unlocalized (not contained in an lproj directory). + */ +function relativeResourcePath(path) { + return _resourceFileProperties(path)[1]; +} + +function _resourceFileProperties(path) { + var lprojKey = ".lproj/"; + var lproj = path.indexOf(lprojKey); + if (lproj >= 0) { + // The slash preceding XX.lproj + var slashIndex = path.slice(0, lproj).lastIndexOf('/'); + if (slashIndex >= 0) { + var localizationKey = path.slice(slashIndex + 1, lproj); + var relativeResourcePath = FileInfo.path(path.slice(lproj + lprojKey.length)); + return [ localizationKey, relativeResourcePath ]; + } + } + + return [ undefined, '.' ]; +} + +var PropertyListVariableExpander = (function () { + function PropertyListVariableExpander() { + } + /** + * Recursively perform variable replacements in an environment dictionary. + */ + PropertyListVariableExpander.prototype.expand = function (obj, env) { + var $this = this; + + // Possible syntaxes for wrapping an environment variable name + var syntaxes = [ + {"open": "${", "close": "}"}, + {"open": "$(", "close": ")"}, + {"open": "@", "close": "@"} + ]; + + /** + * Finds the first index of a replacement starting with one of the supported syntaxes + * This is needed so we don't do recursive substitutions + */ + function indexOfReplacementStart(syntaxes, str) { + var syntax; + var idx = -1; + for (var i in syntaxes) { + var j = str.lastIndexOf(syntaxes[i].open); + if (j > idx) { + syntax = syntaxes[i]; + idx = j; + } + } + return { "syntax": syntax, "index": idx }; + } + + function expandRecursive(obj, env, checked) { + checked.push(obj); + for (var key in obj) { + var value = obj[key]; + var type = typeof(value); + if (type === "object") { + if (checked.indexOf(value) !== -1) + continue; + expandRecursive(value, env, checked); + } + if (type !== "string") + continue; + var repl = indexOfReplacementStart(syntaxes, value); + var i = repl.index; + var changes = false; + while (i !== -1) { + var j = value.indexOf(repl.syntax.close, i + repl.syntax.open.length); + if (j === -1) + break; + var varParts = value.slice(i + repl.syntax.open.length, j).split(':'); + var varName = varParts[0]; + var varFormatter = varParts[1]; + var varValue = env[varName]; + if (undefined === varValue) { + // skip replacement + if ($this.undefinedVariableFunction) + $this.undefinedVariableFunction(key, varName); + varValue = ""; + } + varValue = String(varValue); + if (varFormatter !== undefined) + varFormatter = varFormatter.toLowerCase(); + if (varFormatter === "rfc1034identifier") + varValue = Utilities.rfc1034Identifier(varValue); + value = value.slice(0, i) + varValue + value.slice(j + repl.syntax.close.length); + changes = true; + repl = indexOfReplacementStart(syntaxes, value); + i = repl.index; + } + if (changes) + obj[key] = value; + } + } + expandRecursive(obj, env, []); + return obj; + }; + return PropertyListVariableExpander; +}()); + +/** + * JSON.stringify(expandPlistEnvironmentVariables({a:"$(x)3$$(y)",b:{t:"%$(y) $(k)"}}, + * {x:"X",y:"Y"}, true)) + * Warning undefined variable k in variable expansion + * => {"a":"X3$Y","b":{"t":"%Y $(k)"}} + */ +function expandPlistEnvironmentVariables(obj, env, warn) { + var expander = new PropertyListVariableExpander(); + expander.undefinedVariableFunction = function (key, varName) { + if (warn) + console.warn("undefined variable " + varName + " in variable expansion"); + }; + return expander.expand(obj, env); +} + +/** + * Recursively removes any undefined, null, or empty string values from the property list. + */ +function cleanPropertyList(plist) { + if (typeof(plist) !== "object") + return; + + for (var key in plist) { + if (plist[key] === undefined || plist[key] === null || plist[key] === "") + delete plist[key]; + else + cleanPropertyList(plist[key]); + } +} + +function _codeSignTimestampFlags(product) { + // If signingTimestamp is undefined, do not specify the flag at all - + // this uses the system-specific default behavior + var signingTimestamp = product.moduleProperty("xcode", "signingTimestamp"); + if (signingTimestamp !== undefined) { + // If signingTimestamp is an empty string, specify the flag but do + // not specify a value - this uses a default Apple-provided server + var flag = "--timestamp"; + if (signingTimestamp) + flag += "=" + signingTimestamp; + return [flag]; + } + + return []; +} diff --git a/share/qbs/imports/qbs/ModUtils/utils.js b/share/qbs/imports/qbs/ModUtils/utils.js new file mode 100644 index 00000000..502fa1e4 --- /dev/null +++ b/share/qbs/imports/qbs/ModUtils/utils.js @@ -0,0 +1,636 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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 Environment = require("qbs.Environment"); +var File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var Process = require("qbs.Process"); +var TemporaryDir = require("qbs.TemporaryDir"); +var TextFile = require("qbs.TextFile"); +var Utilities = require("qbs.Utilities"); + +function mergeCFiles(inputs, outputFilePath) +{ + var f = new TextFile(outputFilePath, TextFile.WriteOnly); + try { + for (var i = 0; i < inputs.length; ++i) + f.writeLine('#include ' + Utilities.cStringQuote(inputs[i].filePath)); + } finally { + f.close(); + } +} + +function sanitizedList(list, product, fullPropertyName) { + if (!Array.isArray(list)) + return list; + var filterFunc = function(elem) { + if (typeof elem === "string" && elem.length === 0) { + var msg = "Removing empty string from value of property '" + fullPropertyName + "'"; + // product might actually be a module + if (product.name) + msg += " in product '" + product.name + "'."; + console.warn(msg); + return false; + } + return true; + } + return list.filter(filterFunc); +} + +function checkCompatibilityMode(project, minimumQbsVersion, message) { + if (Utilities.versionCompare(project.minimumQbsVersion || "1.3", minimumQbsVersion) < 0) { + console.warn([message || "", + "This message can be silenced by setting your Project's " + + "minimumQbsVersion to " + minimumQbsVersion + + " (and the new behavior will take effect)."].join(" ")); + return true; + } + + return false; +} + +function artifactInstalledFilePath(artifact) { + var relativeInstallDir = artifact.moduleProperty("qbs", "installDir"); + var installPrefix = artifact.moduleProperty("qbs", "installPrefix"); + var installSourceBase = artifact.moduleProperty("qbs", "installSourceBase"); + var targetDir = FileInfo.joinPaths(artifact.moduleProperty("qbs", "installRoot"), + installPrefix, relativeInstallDir); + if (installSourceBase) { + if (!FileInfo.isAbsolutePath(installSourceBase)) + throw "installSourceBase is not an absolute path"; + if (!artifact.filePath.startsWith(installSourceBase)) + throw "artifact file path doesn't start with the value of qbs.installSourceBase"; + return FileInfo.joinPaths(targetDir, artifact.filePath.substr(installSourceBase.length)); + } + return FileInfo.joinPaths(targetDir, artifact.fileName); +} + +/** + * Given a list of file tags, returns the file tag (one of [c, cpp, objc, objcpp]) + * corresponding to the C-family language the file should be compiled as. + * + * If no such tag is found, undefined is returned. If more than one match is + * found, an exception is thrown. + */ +function fileTagForTargetLanguage(fileTags) { + var srcTags = ["c", "cpp", "objc", "objcpp", "asm", "asm_cpp"]; + var pchTags = ["c_pch", "cpp_pch", "objc_pch", "objcpp_pch"]; + + var canonicalTag = undefined; + var foundTagCount = 0; + for (var i = 0; i < fileTags.length; ++i) { + var idx = srcTags.indexOf(fileTags[i]); + if (idx === -1) + idx = pchTags.indexOf(fileTags[i]); + + if (idx !== -1) { + canonicalTag = srcTags[idx]; + if (++foundTagCount > 1) + break; + } + } + + if (foundTagCount > 1) + throw ("source files cannot be identified as more than one language"); + + return foundTagCount == 1 ? canonicalTag : undefined; +} + +/** + * Returns the name of a language-specific property given the file tag + * for that property, and the base property name. + * + * If \a fileTag is undefined, the language-agnostic property name is returned. + */ +function languagePropertyName(propertyName, fileTag) { + if (!fileTag) + fileTag = "common"; + + var asm = { + "flags": "assemblerFlags", + "platformFlags": "platformAssemblerFlags" + }; + + var map = { + "c": { + "flags": "cFlags", + "platformFlags": "platformCFlags", + "usePrecompiledHeader": "useCPrecompiledHeader" + }, + "cpp": { + "flags": "cxxFlags", + "platformFlags": "platformCxxFlags", + "usePrecompiledHeader": "useCxxPrecompiledHeader" + }, + "objc": { + "flags": "objcFlags", + "platformFlags": "platformObjcFlags", + "usePrecompiledHeader": "useObjcPrecompiledHeader" + }, + "objcpp": { + "flags": "objcxxFlags", + "platformFlags": "platformObjcxxFlags", + "usePrecompiledHeader": "useObjcxxPrecompiledHeader" + }, + "common": { + "flags": "commonCompilerFlags", + "platformFlags": "platformCommonCompilerFlags" + }, + "asm": asm, + "asm_cpp": asm + }; + + var lang = map[fileTag]; + if (!lang) + return propertyName; + + return lang[propertyName] || propertyName; +} + +function modulePropertiesFromArtifacts(product, artifacts, moduleName, propertyName, langFilter) { + var result = product.moduleProperty( + moduleName, languagePropertyName(propertyName, langFilter)) || []; + for (var i in artifacts) { + var artifactProp = artifacts[i].moduleProperty( + moduleName, languagePropertyName(propertyName, langFilter)); + if (artifactProp) + result = result.concat(artifactProp); + } + return sanitizedList(result, product, moduleName + "." + propertyName); +} + +function moduleProperty(product, propertyName, langFilter) +{ + return sanitizedModuleProperty(product, product.moduleName, propertyName, langFilter); +} + +function sanitizedModuleProperty(obj, moduleName, propertyName, langFilter) { + return sanitizedList(obj.moduleProperty(moduleName, + languagePropertyName(propertyName, langFilter)), + obj, moduleName + "." + propertyName); +} + +/** + * Returns roughly the same value as moduleProperty for a product, but ensures that all of the + * given input artifacts share the same value of said property, as a sort of sanity check. + * + * This allows us to verify that users do not, for example, try to set different values on input + * artifacts for which the value is input specific (not product specific), but which must be the + * same for all inputs. + */ +function modulePropertyFromArtifacts(product, artifacts, moduleName, propertyName, langFilter) { + var values = [product.moduleProperty(moduleName, languagePropertyName(propertyName, langFilter))]; + for (var i in artifacts) { + var value = artifacts[i].moduleProperty(moduleName, languagePropertyName(propertyName, langFilter)); + if (!values.contains(value)) { + values.push(value); + } + } + + if (values.length !== 1) { + throw "The value of " + [moduleName, propertyName].join(".") + + " must be identical for the following input artifacts: " + + artifacts.map(function (artifact) { return artifact.filePath; }); + } + + return values[0]; +} + +function concatAll() { + var result = []; + for (var i = 0; i < arguments.length; ++i) { + var arg = arguments[i]; + if (arg === undefined) + continue; + else if (arg instanceof Array) + result = result.concat(arg); + else + result.push(arg); + } + return result; +} + +function allFileTags(fileTaggers) { + var tags = []; + for (var ext in fileTaggers) + tags = tags.uniqueConcat(fileTaggers[ext]); + return tags; +} + +/** + * Flattens a dictionary (string keys to strings) + * into a string list containing items like \c key=value1 + */ +function flattenDictionary(dict, separator) { + separator = separator || "="; + var list = []; + for (var i in dict) { + var value = i; + if (dict[i] !== undefined) // allow differentiation between undefined and empty string + value += separator + dict[i]; + list.push(value); + } + return list; +} + +function ModuleError(message) { + var e = new Error(message); + e.fileName = ""; + return e; +} + +var EnvironmentVariable = (function () { + function EnvironmentVariable(name, separator, convertPathSeparators) { + if (!name) + throw "EnvironmentVariable c'tor needs a name as first argument."; + this.name = name; + this.value = Environment.getEnv(name) || ""; + this.separator = separator || ""; + this.convertPathSeparators = convertPathSeparators || false; + } + EnvironmentVariable.prototype.prepend = function (v) { + if (this.value.length > 0 && this.value.charAt(0) !== this.separator) + this.value = this.separator + this.value; + if (this.convertPathSeparators) + v = FileInfo.toWindowsSeparators(v); + this.value = v + this.value; + }; + + EnvironmentVariable.prototype.append = function (v) { + if (this.value.length > 0) + this.value += this.separator; + if (this.convertPathSeparators) + v = FileInfo.toWindowsSeparators(v); + this.value += v; + }; + + EnvironmentVariable.prototype.set = function () { + Environment.putEnv(this.name, this.value); + }; + + EnvironmentVariable.prototype.unset = function () { + Environment.unsetEnv(this.name); + }; + + return EnvironmentVariable; +})(); + +var PropertyValidator = (function () { + function PropertyValidator(moduleName) { + this.requiredProperties = {}; + this.propertyValidators = []; + if (!moduleName) + throw "PropertyValidator c'tor needs a module name as a first argument."; + this.moduleName = moduleName; + } + PropertyValidator.prototype.setRequiredProperty = function (propertyName, propertyValue, message) { + this.requiredProperties[propertyName] = { propertyValue: propertyValue, message: message }; + }; + + PropertyValidator.prototype.addRangeValidator = function (propertyName, propertyValue, min, max, allowFloats) { + var message = []; + if (min !== undefined) + message.push(">= " + min); + if (max !== undefined) + message.push("<= " + max); + + this.addCustomValidator(propertyName, propertyValue, function (value) { + if (typeof value !== "number") + return false; + if (!allowFloats && value % 1 !== 0) + return false; + if (min !== undefined && value < min) + return false; + if (max !== undefined && value > max) + return false; + return true; + }, "must be " + (!allowFloats ? "an integer " : "") + message.join(" and ") + + ", actual value: " + propertyValue); + }; + + PropertyValidator.prototype.addVersionValidator = function (propertyName, propertyValue, minComponents, maxComponents, allowSuffixes) { + if (minComponents !== undefined && (typeof minComponents !== "number" || minComponents % 1 !== 0 || minComponents < 1)) + throw "minComponents must be at least 1"; + if (maxComponents !== undefined && (typeof maxComponents !== "number" || maxComponents % 1 !== 0 || maxComponents < minComponents)) + throw "maxComponents must be >= minComponents"; + + this.addCustomValidator(propertyName, propertyValue, function (value) { + if (typeof value !== "string") + return false; + return value && value.match("^[0-9]+(\\.[0-9]+){" + ((minComponents - 1) || 0) + "," + ((maxComponents - 1) || "") + "}" + (!allowSuffixes ? "$" : "")) !== null; + }, "must be a version number with " + (minComponents === maxComponents + ? minComponents : (minComponents + " to " + maxComponents)) + + (minComponents === maxComponents && minComponents === 1 + ? " component" : " components") + ", actual value: " + propertyValue); + }; + + PropertyValidator.prototype.addFileNameValidator = function (propertyName, propertyValue) { + this.addCustomValidator(propertyName, propertyValue, function (value) { + return !/[/?<>\\:*|"\u0000-\u001f\u0080-\u009f]/.test(propertyValue) + && propertyValue !== "." && propertyValue !== ".."; + }, "cannot contain reserved or control characters and cannot be \".\" or \"..\""); + }; + + PropertyValidator.prototype.addCustomValidator = function (propertyName, propertyValue, validator, message) { + this.propertyValidators.push({ + propertyName: propertyName, + propertyValue: propertyValue, + validator: validator, + message: message + }); + }; + + PropertyValidator.prototype.validate = function (throwOnError) { + var i; + var lines; + + // Find any missing properties + var missingProperties = {}; + for (i in this.requiredProperties) { + var propValue = this.requiredProperties[i].propertyValue; + if (propValue === undefined || propValue === null || propValue === "") { + missingProperties[i] = this.requiredProperties[i]; + } + } + + // Find any properties that don't satisfy their validator function + var invalidProperties = {}; + for (var j = 0; j < this.propertyValidators.length; ++j) { + var v = this.propertyValidators[j]; + if (!v.validator(v.propertyValue)) { + var messages = invalidProperties[v.propertyName] || []; + messages.push(v.message); + invalidProperties[v.propertyName] = messages; + } + } + + var errorMessage = ""; + if (Object.keys(missingProperties).length > 0) { + errorMessage += "The following properties are not set. Set them in your profile or product:\n"; + lines = []; + for (i in missingProperties) { + var obj = missingProperties[i]; + lines.push(this.moduleName + "." + i + ((obj && obj.message) ? (": " + obj.message) : "")); + } + errorMessage += lines.join("\n"); + } + + if (Object.keys(invalidProperties).length > 0) { + if (errorMessage) + errorMessage += "\n"; + errorMessage += "The following properties have invalid values:\n"; + lines = []; + for (i in invalidProperties) { + for (j in invalidProperties[i]) { + lines.push(this.moduleName + "." + i + ": " + invalidProperties[i][j]); + } + } + errorMessage += lines.join("\n"); + } + + if (throwOnError !== false && errorMessage.length > 0) + throw errorMessage; + + return errorMessage.length == 0; + }; + return PropertyValidator; +})(); + +var BlackboxOutputArtifactTracker = (function () { + function BlackboxOutputArtifactTracker() { + } + BlackboxOutputArtifactTracker.prototype.artifacts = function (outputDirectory) { + var process; + var fakeOutputDirectory; + try { + fakeOutputDirectory = new TemporaryDir(); + if (!fakeOutputDirectory.isValid()) + throw "could not create temporary directory"; + process = new Process(); + if (this.commandEnvironmentFunction) { + var env = this.commandEnvironmentFunction(fakeOutputDirectory.path()); + for (var key in env) + process.setEnv(key, env[key]); + } + process.exec(this.command, this.commandArgsFunction(fakeOutputDirectory.path()), true); + var artifacts = []; + if (this.fileTaggers) { + var files = this.findFiles(fakeOutputDirectory.path()); + for (var i = 0; i < files.length; ++i) + artifacts.push(this.createArtifact(fakeOutputDirectory.path(), files[i])); + } + if (this.processStdOutFunction) + artifacts = artifacts.concat(this.processStdOutFunction(process.readStdOut())); + artifacts = this.fixArtifactPaths(artifacts, outputDirectory, fakeOutputDirectory.path()); + return artifacts; + } + finally { + if (process) + process.close(); + if (fakeOutputDirectory) + fakeOutputDirectory.remove(); + } + }; + BlackboxOutputArtifactTracker.prototype.createArtifact = function (root, filePath) { + for (var ext in this.fileTaggers) { + if (filePath.endsWith(ext)) { + return { + filePath: filePath, + fileTags: this.fileTaggers[ext] + }; + } + } + if (!this.defaultFileTags) { + var relFilePath = (filePath.startsWith(root + '/') || filePath.startsWith(root + '\\')) + ? filePath.substring(root.length + 1) + : filePath; + throw "BlackboxOutputArtifactTracker: no matching file taggers for path '" + + relFilePath + "'. Set defaultFileTags to an array of file tags to " + + "apply to files not tagged by the fileTaggers map, which was:\n" + + JSON.stringify(this.fileTaggers, undefined, 4); + } + return { + filePath: filePath, + fileTags: this.defaultFileTags + }; + }; + BlackboxOutputArtifactTracker.prototype.findFiles = function (dir) { + var fileList = File.directoryEntries(dir, File.Files).map(function (p) { + return FileInfo.joinPaths(dir, p); }); + var dirList = File.directoryEntries(dir, File.Dirs | File.NoDotAndDotDot).map(function (p) { + return FileInfo.joinPaths(dir, p); }); + for (var i = 0; i < dirList.length; ++i) + fileList = fileList.concat(this.findFiles(dirList[i])); + return fileList; + }; + BlackboxOutputArtifactTracker.prototype.fixArtifactPaths = function (artifacts, realBasePath, fakeBasePath) { + for (var i = 0; i < artifacts.length; ++i) + artifacts[i].filePath = realBasePath + + artifacts[i].filePath.substr(fakeBasePath.length); + return artifacts; + }; + return BlackboxOutputArtifactTracker; +})(); + +function hasAnyOf(m, tokens) { + for (var i = 0; i < tokens.length; ++i) { + if (m[tokens[i]] !== undefined) + return true; + } +} + +function guessArchitecture(m) { + var architecture; + if (m) { + // based on the search algorithm from qprocessordetection.h in qtbase + var arm64Defs = ["_M_ARM64", "__aarch64__", "__ARM64__"]; + if (hasAnyOf(m, ["__arm__", "__TARGET_ARCH_ARM", "_M_ARM"].concat(arm64Defs))) { + if (hasAnyOf(m, arm64Defs)) { + architecture = "arm64"; + } else { + architecture = "arm"; + + var foundSubarch = false; + for (var i = 8; i >= 4; --i) { + var codes = ["zk", "tej", "te", "t2"].concat([].concat.apply([], + new Array(26)).map(function(_, i) { return String.fromCharCode(122 - i); })); + for (var j = 0; j < codes.length; ++j) { + if (m["__ARM_ARCH_" + i + codes[j].toUpperCase() + "__"] !== undefined) { + architecture += "v" + i + codes[j].toLowerCase(); + foundSubarch = true; + break; + } + } + + if (i === 7 && m["_ARM_ARCH_7"] !== undefined) { + architecture += "v7"; + foundSubarch = true; + } + + if (foundSubarch) + break; + } + } + } else if (hasAnyOf(m, ["__i386", "__i386__", "_M_IX86"])) { + architecture = "x86"; + } else if (hasAnyOf(m, ["__x86_64", "__x86_64__", "__amd64", "_M_X64", "_M_AMD64"])) { + architecture = "x86_64"; + if (hasAnyOf(m, ["__x86_64h", "__x86_64h__"])) + architecture = "x86_64h"; + } else if (hasAnyOf(m, ["__ia64", "__ia64__", "_M_IA64"])) { + architecture = "ia64"; + } else if (hasAnyOf(m, ["__mips", "__mips__", "_M_MRX000"])) { + architecture = "mips"; + if (hasAnyOf(m, ["_MIPS_ARCH_MIPS64", "__mips64"])) + architecture += "64"; + } else if (hasAnyOf(m, ["__ppc__", "__ppc", "__powerpc__", + "_ARCH_COM", "_ARCH_PWR", "_ARCH_PPC", "_M_MPPC", "_M_PPC"])) { + architecture = "ppc"; + if (hasAnyOf(m, ["__ppc64__", "__powerpc64__", "__64BIT__"])) + architecture += "64"; + } else if (hasAnyOf(m, ["__s390__"])) { + if (hasAnyOf(m, ["__s390x__"])) + architecture = "s390x"; + } else if (hasAnyOf(m, ["__sparc__"])) { + architecture = "sparc"; + if (hasAnyOf(m, ["__sparc64__"])) + architecture += "64"; + } else if (hasAnyOf(m, ["__AVR__"])) { + architecture = "avr"; + } else if (hasAnyOf(m, ["__AVR32__"])) { + architecture = "avr32"; + } else if (hasAnyOf(m, ["__MSP430__"])) { + architecture = "msp430"; + } else if (hasAnyOf(m, ["__RL78__"])) { + architecture = "rl78"; + } else if (hasAnyOf(m, ["__RX__"])) { + architecture = "rx"; + } else if (hasAnyOf(m, ["__v850__"])) { + architecture = "v850"; + } else if (hasAnyOf(m, ["__riscv"])) { + architecture = "riscv"; + } else if (hasAnyOf(m, ["__xtensa__", "__XTENSA__"])) { + architecture = "xtensa"; + } else if (hasAnyOf(m, ["__m68k__"])) { + architecture = "m68k"; + } else if (hasAnyOf(m, ["__m32c__"])) { + architecture = "m32c"; + } else if (hasAnyOf(m, ["__m32r__", "__M32R__"])) { + architecture = "m32r"; + } else if (hasAnyOf(m, ["__sh__", "__SH__"])) { + architecture = "sh"; + } else if (hasAnyOf(m, ["__CR16__"])) { + architecture = "cr16"; + } + } + + return Utilities.canonicalArchitecture(architecture); +} + +function guessTargetPlatform(m) { + if (m) { + if (hasAnyOf(m, ["__ANDROID__", "ANDROID"])) + return "android"; + if (hasAnyOf(m, ["__QNXNTO__"])) + return "qnx"; + if (hasAnyOf(m, ["__INTEGRITY"])) + return "integrity"; + if (hasAnyOf(m, ["__vxworks"])) + return "vxworks"; + if (hasAnyOf(m, ["__APPLE__"])) + return "darwin"; + if (hasAnyOf(m, ["WIN32", "_WIN32", "__WIN32__", "__NT__"])) + return "windows"; + if (hasAnyOf(m, ["_AIX"])) + return "aix"; + if (hasAnyOf(m, ["hpux", "__hpux"])) + return "hpux"; + if (hasAnyOf(m, ["__sun", "sun"])) + return "solaris"; + if (hasAnyOf(m, ["__linux__", "__linux"])) + return "linux"; + if (hasAnyOf(m, ["__FreeBSD__", "__DragonFly__", "__FreeBSD_kernel__"])) + return "freebsd"; + if (hasAnyOf(m, ["__NetBSD__"])) + return "netbsd"; + if (hasAnyOf(m, ["__OpenBSD__"])) + return "openbsd"; + if (hasAnyOf(m, ["__GNU__"])) + return "hurd"; + if (hasAnyOf(m, ["__HAIKU__"])) + return "haiku"; + } +} + +function toJSLiteral(v) { + if (v === undefined) + return "undefined"; + return JSON.stringify(v); +} diff --git a/share/qbs/imports/qbs/PathTools/path-tools.js b/share/qbs/imports/qbs/PathTools/path-tools.js new file mode 100644 index 00000000..b2cb63e3 --- /dev/null +++ b/share/qbs/imports/qbs/PathTools/path-tools.js @@ -0,0 +1,232 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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"); + +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); + + return product.moduleProperty("cpp", "executablePrefix") + + product.targetName + (variantSuffix || "") + + product.moduleProperty("cpp", "executableSuffix"); +} + +function loadableModuleFilePath(product, variantSuffix) { + if (variantSuffix === undefined) + variantSuffix = product.cpp.variantSuffix; + if (product.moduleProperty("bundle", "isBundle")) + return _bundleExecutableTemporaryFilePath(product, variantSuffix); + + return product.moduleProperty("cpp", "loadableModulePrefix") + + product.targetName + (variantSuffix || "") + + product.moduleProperty("cpp", "loadableModuleSuffix"); +} + +function staticLibraryFilePath(product, variantSuffix) { + if (variantSuffix === undefined) + variantSuffix = product.cpp.variantSuffix; + if (product.moduleProperty("bundle", "isBundle")) + return _bundleExecutableTemporaryFilePath(product, variantSuffix); + + return product.moduleProperty("cpp", "staticLibraryPrefix") + + product.targetName + (variantSuffix || "") + + product.moduleProperty("cpp", "staticLibrarySuffix"); +} + +function dynamicLibraryFilePath(product, variantSuffix, version, maxParts) { + if (variantSuffix === undefined) + variantSuffix = product.cpp.variantSuffix; + if (product.moduleProperty("bundle", "isBundle")) + return _bundleExecutableTemporaryFilePath(product, variantSuffix); + + // If no override version was given, use the product's version + // We specifically want to differentiate between undefined and i.e. + // empty string as empty string should be taken to mean "no version" + // and undefined should be taken to mean "use the product's version" + // (which could still end up being "no version") + if (version === undefined) + version = product.moduleProperty("cpp", "internalVersion"); + + // If we have a version number, potentially strip off some components + if (maxParts === 0) + version = undefined; + else if (maxParts && version) + version = version.split('.').slice(0, maxParts).join('.'); + + // Start with prefix + name (i.e. libqbs, qbs) + var fileName = product.moduleProperty("cpp", "dynamicLibraryPrefix") + + product.targetName + + (variantSuffix || ""); + + // For Mach-O images, append the version number if there is one (i.e. libqbs.1.0.0) + var imageFormat = product.moduleProperty("cpp", "imageFormat"); + if (version && imageFormat === "macho") { + fileName += "." + version; + version = undefined; + } + + // Append the suffix (i.e. libqbs.1.0.0.dylib, libqbs.so, qbs.dll) + fileName += product.moduleProperty("cpp", "dynamicLibrarySuffix"); + + // For ELF images, append the version number if there is one (i.e. libqbs.so.1.0.0) + if (version && imageFormat === "elf") + fileName += "." + version; + + return fileName; +} + +function linkerOutputFilePath(fileTag, product, variantSuffix, version, maxParts) { + switch (fileTag) { + case "application": + return applicationFilePath(product, variantSuffix); + case "loadablemodule": + return loadableModuleFilePath(product, variantSuffix); + case "staticlibrary": + return staticLibraryFilePath(product, variantSuffix); + case "dynamiclibrary": + return dynamicLibraryFilePath(product, variantSuffix, version, maxParts); + default: + throw new Error("Unknown linker output file tag: " + fileTag); + } +} + +function importLibraryFilePath(product) { + return product.moduleProperty("cpp", "dynamicLibraryPrefix") + + product.targetName + + (product.cpp.variantSuffix || "") + + product.moduleProperty("cpp", "dynamicLibraryImportSuffix"); +} + +function debugInfoIsBundle(product) { + if (!product.moduleProperty("qbs", "targetOS").contains("darwin")) + return false; + var flags = product.moduleProperty("cpp", "dsymutilFlags") || []; + return !flags.contains("-f") && !flags.contains("--flat"); +} + +function debugInfoFileName(product, variantSuffix, fileTag) { + var suffix = ""; + + // For dSYM bundles, the DWARF debug info file has no suffix + if (!product.moduleProperty("qbs", "targetOS").contains("darwin") + || !debugInfoIsBundle(product)) + suffix = product.moduleProperty("cpp", "debugInfoSuffix"); + + if (product.moduleProperty("bundle", "isBundle")) { + return FileInfo.fileName(bundleExecutableFilePath(product, variantSuffix)) + suffix; + } else { + switch (fileTag) { + case "application": + return applicationFilePath(product, variantSuffix) + suffix; + case "dynamiclibrary": + return dynamicLibraryFilePath(product, variantSuffix) + suffix; + case "loadablemodule": + return loadableModuleFilePath(product, variantSuffix) + suffix; + case "staticlibrary": + return staticLibraryFilePath(product, variantSuffix) + suffix; + default: + return product.targetName + (variantSuffix || "") + suffix; + } + } +} + +function debugInfoBundlePath(product, fileTag) { + if (!debugInfoIsBundle(product)) + return undefined; + var suffix = product.moduleProperty("cpp", "debugInfoBundleSuffix"); + if (product.moduleProperty("qbs", "targetOS").contains("darwin") + && product.moduleProperty("bundle", "isBundle")) + return product.moduleProperty("bundle", "bundleName") + suffix; + return debugInfoFileName(product, undefined, fileTag) + suffix; +} + +function debugInfoFilePath(product, variantSuffix, fileTag) { + var name = debugInfoFileName(product, variantSuffix, fileTag); + if (product.moduleProperty("qbs", "targetOS").contains("darwin") && debugInfoIsBundle(product)) { + return FileInfo.joinPaths(debugInfoBundlePath(product, fileTag), + "Contents", "Resources", "DWARF", name); + } else if (product.moduleProperty("bundle", "isBundle")) { + return FileInfo.joinPaths(product.moduleProperty("bundle", "executableFolderPath"), name); + } + + return name; +} + +function debugInfoPlistFilePath(product, fileTag) { + if (!debugInfoIsBundle(product)) + return undefined; + return FileInfo.joinPaths(debugInfoBundlePath(product, fileTag), "Contents", "Info.plist"); +} + +// Returns whether the string looks like a library filename +function isLibraryFileName(product, fileName, prefix, suffixes, isShared) { + var suffix, i; + var os = product.moduleProperty("qbs", "targetOS"); + for (i = 0; i < suffixes.length; ++i) { + suffix = suffixes[i]; + if (isShared && os.contains("unix") && !os.contains("darwin")) + suffix += "(\\.[0-9]+){0,3}"; + if (fileName.match("^" + prefix + ".+?\\" + suffix + "$")) + return true; + } + return false; +} + +function frameworkExecutablePath(frameworkPath) { + var suffix = ".framework"; + var isAbsoluteFrameworkPath = frameworkPath.slice(-suffix.length) === suffix; + if (isAbsoluteFrameworkPath) { + var frameworkName = FileInfo.fileName(frameworkPath).slice(0, -suffix.length); + return FileInfo.joinPaths(frameworkPath, frameworkName); + } + return undefined; +} + +// pathList is also a string, using the respective separator +function prependOrSetPath(path, pathList, separator) { + if (!pathList || pathList.length === 0) + return path; + return path + separator + pathList; +} diff --git a/share/qbs/imports/qbs/Probes/AndroidNdkProbe.qbs b/share/qbs/imports/qbs/Probes/AndroidNdkProbe.qbs new file mode 100644 index 00000000..4a86ee93 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/AndroidNdkProbe.qbs @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jake Petroules. +** 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.Environment +import qbs.File +import qbs.FileInfo +import qbs.TextFile +import "../../../modules/Android/android-utils.js" as AndroidUtils + +PathProbe { + // Inputs + property stringList hostOS: qbs.hostOS + property path sdkPath + + environmentPaths: Environment.getEnv("ANDROID_NDK_ROOT") + platformSearchPaths: { + var paths = []; + if (sdkPath) + paths.push(FileInfo.joinPaths(sdkPath, "ndk-bundle")); + if (qbs.hostOS.contains("windows")) + paths.push(FileInfo.joinPaths(Environment.getEnv("LOCALAPPDATA"), + "Android", "sdk", "ndk-bundle")); + if (qbs.hostOS.contains("macos")) + paths.push(FileInfo.joinPaths(Environment.getEnv("HOME"), + "Library", "Android", "sdk", "ndk-bundle")); + if (qbs.hostOS.contains("linux")) + paths.push(FileInfo.joinPaths(Environment.getEnv("HOME"), + "Android", "Sdk", "ndk-bundle")); + return paths; + } + + // Outputs + property stringList candidatePaths + property string samplesDir + property var hostArch + property stringList toolchains: [] + 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(platformSearchPaths || []); + candidatePaths = allPaths; + for (i in allPaths) { + var platforms = []; + if (hostOS.contains("windows")) + platforms.push("windows-x86_64", "windows"); + if (hostOS.contains("darwin")) + platforms.push("darwin-x86_64", "darwin-x86"); + if (hostOS.contains("linux")) + platforms.push("linux-x86_64", "linux-x86"); + for (j in platforms) { + if (File.exists(FileInfo.joinPaths(allPaths[i], "prebuilt", platforms[j]))) { + path = allPaths[i]; + if (File.exists(FileInfo.joinPaths(path, "samples"))) + samplesDir = FileInfo.joinPaths(path, "samples"); // removed in r11 + hostArch = platforms[j]; + toolchains = File.directoryEntries(FileInfo.joinPaths(path, "toolchains"), + File.Dirs | File.NoDotAndDotDot); + + // NDK r11 and above + 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) { + var m = lines[l].match(/^Pkg\.Revision\s*=\s*([0-9\.]+)$/); + if (m) { + ndkVersion = m[1]; + found = true; + return; + } + } + } + + // NDK r10 and below + 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; + } + } + } + } + } + } +} diff --git a/share/qbs/imports/qbs/Probes/AndroidSdkProbe.qbs b/share/qbs/imports/qbs/Probes/AndroidSdkProbe.qbs new file mode 100644 index 00000000..38feecdf --- /dev/null +++ b/share/qbs/imports/qbs/Probes/AndroidSdkProbe.qbs @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jake Petroules. +** 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.Environment +import qbs.File +import qbs.FileInfo +import "../../../modules/Android/sdk/utils.js" as SdkUtils +import "../../../modules/Android/android-utils.js" as AndroidUtils + +BinaryProbe { + environmentPaths: Environment.getEnv("ANDROID_HOME") + platformSearchPaths: { + if (qbs.hostOS.contains("windows")) + return [FileInfo.joinPaths(Environment.getEnv("LOCALAPPDATA"), "Android", "sdk")]; + if (qbs.hostOS.contains("macos")) + return [FileInfo.joinPaths(Environment.getEnv("HOME"), "Library", "Android", "sdk")]; + if (qbs.hostOS.contains("linux")) + return [FileInfo.joinPaths(Environment.getEnv("HOME"), "Android", "Sdk")]; + } + + // Outputs + property stringList candidatePaths + property var buildToolsVersions + property string buildToolsVersion + property var platforms + property string platform + + configure: { + var suffixes = nameSuffixes || [""]; + var i, allPaths = (environmentPaths || []).concat(platformSearchPaths || []); + candidatePaths = allPaths; + for (i in allPaths) { + for (var j in suffixes) { + if (File.exists(FileInfo.joinPaths(allPaths[i], + "tools", "android" + suffixes[j]))) { + path = allPaths[i]; + buildToolsVersions = SdkUtils.availableBuildToolsVersions(path) + buildToolsVersion = buildToolsVersions[buildToolsVersions.length - 1]; + platforms = AndroidUtils.availablePlatforms(path) + platform = platforms[platforms.length - 1]; + found = true; + return; + } + } + } + } +} diff --git a/share/qbs/imports/qbs/Probes/BinaryProbe.qbs b/share/qbs/imports/qbs/Probes/BinaryProbe.qbs new file mode 100644 index 00000000..0bb264bd --- /dev/null +++ b/share/qbs/imports/qbs/Probes/BinaryProbe.qbs @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +PathProbe { + nameSuffixes: qbs.hostOS.contains("windows") ? [".com", ".exe", ".bat", ".cmd"] : undefined + platformSearchPaths: hostOS.contains("unix") ? ["/usr/bin", "/usr/local/bin"] : [] + platformEnvironmentPaths: [ "PATH" ] +} diff --git a/share/qbs/imports/qbs/Probes/ClBinaryProbe.qbs b/share/qbs/imports/qbs/Probes/ClBinaryProbe.qbs new file mode 100644 index 00000000..bcaa9d1f --- /dev/null +++ b/share/qbs/imports/qbs/Probes/ClBinaryProbe.qbs @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** 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.FileInfo +import qbs.ModUtils +import qbs.Utilities +import "path-probe.js" as PathProbeConfigure + +BinaryProbe { + // input + property string preferredArchitecture; + + configure: { + var _selectors; + var results = PathProbeConfigure.configure(_selectors, names, nameSuffixes, nameFilter, + candidateFilter, searchPaths, pathSuffixes, + platformSearchPaths, environmentPaths, + platformEnvironmentPaths, pathListSeparator); + if (!results.found) { + var msvcs = Utilities.installedMSVCs(preferredArchitecture); + if (msvcs.length >= 1) { + var result = {}; + result.fileName = "cl.exe"; + result.path = msvcs[0].binPath; + result.filePath = FileInfo.joinPaths(path, fileName); + result.candidatePaths = result.filePath; + results.found = true; + results.files = [result]; + } + } + + found = results.found; + allResults = results.files; + + if (allResults.length === 1) { + var result = allResults[0]; + candidatePaths = result.candidatePaths; + path = result.path; + filePath = result.filePath; + fileName = result.fileName; + } + + } +} diff --git a/share/qbs/imports/qbs/Probes/ClangClBinaryProbe.qbs b/share/qbs/imports/qbs/Probes/ClangClBinaryProbe.qbs new file mode 100644 index 00000000..f4916a37 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/ClangClBinaryProbe.qbs @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** 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.FileInfo +import qbs.ModUtils +import qbs.Utilities +import "path-probe.js" as PathProbeConfigure + +BinaryProbe { + // output + property string vcvarsallPath; + + configure: { + var _selectors; + var results = PathProbeConfigure.configure(_selectors, names, nameSuffixes, nameFilter, + candidateFilter, searchPaths, pathSuffixes, + platformSearchPaths, environmentPaths, + platformEnvironmentPaths, pathListSeparator); + var compilerPath; + if (results.found) + compilerPath = results.files[0].filePath; + var compilers = Utilities.installedClangCls(compilerPath); + if (compilers.length >= 1) { + var result = {}; + result.fileName = "clang-cl.exe"; + result.path = compilers[0].toolchainInstallPath; + result.filePath = FileInfo.joinPaths(result.path, result.fileName); + result.candidatePaths = result.filePath; + result.vcvarsallPath = compilers[0].vcvarsallPath; + results.found = true; + results.files = [result]; + } + + found = results.found; + allResults = results.files; + + if (allResults.length === 1) { + var result = allResults[0]; + candidatePaths = result.candidatePaths; + path = result.path; + filePath = result.filePath; + fileName = result.fileName; + vcvarsallPath = result.vcvarsallPath; + } + } +} diff --git a/share/qbs/imports/qbs/Probes/ClangClProbe.qbs b/share/qbs/imports/qbs/Probes/ClangClProbe.qbs new file mode 100644 index 00000000..8205e92f --- /dev/null +++ b/share/qbs/imports/qbs/Probes/ClangClProbe.qbs @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +** 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.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Utilities +import "../../../modules/cpp/gcc.js" as Gcc + +PathProbe { + // Inputs + property string compilerFilePath + property string vcvarsallFilePath + property stringList enableDefinesByLanguage + property string preferredArchitecture + property string _nullDevice: qbs.nullDevice + property string _pathListSeparator: qbs.pathListSeparator + + // Outputs + property int versionMajor + property int versionMinor + property int versionPatch + property stringList includePaths + property string architecture + property var buildEnv + property var compilerDefinesByLanguage + + configure: { + var languages = enableDefinesByLanguage; + if (!languages || languages.length === 0) + languages = ["c"]; + + var info = languages.contains("c") + ? Utilities.clangClCompilerInfo(compilerFilePath, preferredArchitecture, vcvarsallFilePath, "c") : {}; + var infoCpp = languages.contains("cpp") + ? Utilities.clangClCompilerInfo(compilerFilePath, preferredArchitecture, vcvarsallFilePath, "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; + + versionMajor = parseInt(macros["__clang_major__"], 10); + versionMinor = parseInt(macros["__clang_minor__"], 10); + versionPatch = parseInt(macros["__clang_patchlevel__"], 10); + + buildEnv = info.buildEnvironment || infoCpp.buildEnvironment; + // clang-cl is just a wrapper around clang.exe, so the includes *should be* the same + var clangPath = FileInfo.joinPaths(FileInfo.path(compilerFilePath), "clang.exe"); + + var defaultPaths = Gcc.dumpDefaultPaths(buildEnv, clangPath, + [], _nullDevice, + _pathListSeparator, "", ""); + includePaths = defaultPaths.includePaths; + architecture = ModUtils.guessArchitecture(macros); + } +} diff --git a/share/qbs/imports/qbs/Probes/ConanfileProbe.qbs b/share/qbs/imports/qbs/Probes/ConanfileProbe.qbs new file mode 100644 index 00000000..e97e45f0 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/ConanfileProbe.qbs @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Richard Weickelt +** 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.Process +import qbs.File +import qbs.FileInfo +import qbs.TextFile +import qbs.Utilities + +Probe { + // Inputs + property stringList additionalArguments: [] + property path conanfilePath + property path packageReference + property path executable: "conan" + (qbs.hostOS.contains("windows") ? ".exe": "") + property stringList generators: ["json"]; + property var options + property var settings + + // Output + property var dependencies + property path generatedFilesPath + property var json + + // Internal + // Ensure that the probe is re-run automatically whenever conanfile changes + // by making a file system property part of the probe's signature. + property int _conanfileLastModified: conanfilePath ? File.lastModified(conanfilePath) : 0 + property path _projectBuildDirectory: project.buildDirectory + + configure: { + if (conanfilePath && packageReference) + throw("conanfilePath and packageReference must not be defined at the same time."); + + if (!conanfilePath && !packageReference) + throw("Either conanfilePath or packageReference must be defined."); + + var reference = packageReference || FileInfo.cleanPath(conanfilePath); + console.info("Probing '" + reference + "'. This might take a while..."); + if (conanfilePath && !File.exists(reference)) + throw("The conanfile '" + reference + "' does not exist."); + + var args = [ + "install", reference, + ]; + + if (options) { + if (typeof options !== "object") + throw("The property 'options' must be an object."); + Object.keys(options).forEach(function(key,index) { + args.push("-o"); + args.push(key + "=" + options[key]); + }); + } + + if (settings) { + if (typeof settings !== "object") + throw("The property 'settings' must be an object."); + Object.keys(settings).forEach(function(key,index) { + args.push("-s"); + args.push(key + "=" + settings[key]); + }); + } + + if (!generators.contains("json")) + generators.push("json"); + + for (var i = 0; i < generators.length; i++) + args = args.concat(["-g", generators[i]]); + + for (var i = 0; i < additionalArguments.length; i++) + args.push(additionalArguments[i]); + + generatedFilesPath = FileInfo.cleanPath(_projectBuildDirectory + + "/genconan/" + + Utilities.getHash(args.join())); + + args = args.concat(["-if", generatedFilesPath]); + var p = new Process(); + try { + p.exec(executable, args, true); + } finally { + p.close(); + } + + if (generators.contains("json")) { + if (!File.exists(generatedFilesPath + "/conanbuildinfo.json")) + throw("No conanbuildinfo.json has been generated."); + + var jsonFile = new TextFile(generatedFilesPath + "/conanbuildinfo.json", TextFile.ReadOnly); + json = JSON.parse(jsonFile.readAll()); + jsonFile.close(); + + dependencies = {}; + for (var i = 0; i < json.dependencies.length; ++i) { + var dep = json.dependencies[i]; + dependencies[dep.name] = dep; + } + } + + found = true; + } +} diff --git a/share/qbs/imports/qbs/Probes/FrameworkProbe.qbs b/share/qbs/imports/qbs/Probes/FrameworkProbe.qbs new file mode 100644 index 00000000..c3d98a49 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/FrameworkProbe.qbs @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +PathProbe { + platformSearchPaths: (qbs.sysroot ? [qbs.sysroot + "/System/Library/Frameworks"] : []).concat([ + "~/Library/Frameworks", + "/usr/local/lib", + "/Library/Frameworks", + "/System/Library/Frameworks" + ]) + nameSuffixes: ".framework" +} diff --git a/share/qbs/imports/qbs/Probes/GccBinaryProbe.qbs b/share/qbs/imports/qbs/Probes/GccBinaryProbe.qbs new file mode 100644 index 00000000..9081f5ef --- /dev/null +++ b/share/qbs/imports/qbs/Probes/GccBinaryProbe.qbs @@ -0,0 +1,78 @@ +import qbs.Environment +import qbs.FileInfo +import "path-probe.js" as PathProbeConfigure + +BinaryProbe { + nameSuffixes: undefined // _compilerName already contains ".exe" suffix on Windows + // Inputs + property string _compilerName + property string _toolchainPrefix + + // Outputs + property string tcPrefix + + platformSearchPaths: { + var paths = base; + if (qbs.targetOS.contains("windows") && qbs.hostOS.contains("windows")) + paths.push(FileInfo.joinPaths( + Environment.getEnv("SystemDrive"), "MinGW", "bin")); + return paths; + } + + names: { + var prefixes = []; + if (_toolchainPrefix) { + prefixes.push(_toolchainPrefix); + } else { + var arch = qbs.architecture; + if (qbs.targetOS.contains("windows")) { + if (!arch || arch === "x86") { + prefixes.push("mingw32-", + "i686-w64-mingw32-", + "i686-w64-mingw32.shared-", + "i686-w64-mingw32.static-", + "i686-mingw32-", + "i586-mingw32msvc-"); + } + if (!arch || arch === "x86_64") { + prefixes.push("x86_64-w64-mingw32-", + "x86_64-w64-mingw32.shared-", + "x86_64-w64-mingw32.static-", + "amd64-mingw32msvc-"); + } + } + } + return prefixes.map(function(prefix) { + return prefix + _compilerName; + }).concat([_compilerName]); + } + + configure: { + var selectors; + var results = PathProbeConfigure.configure( + selectors, names, nameSuffixes, nameFilter, candidateFilter, searchPaths, + pathSuffixes, platformSearchPaths, environmentPaths, platformEnvironmentPaths, + pathListSeparator); + + found = results.found; + if (!found) + return; + + var resultsMapper = function(result) { + (nameSuffixes || [""]).forEach(function(suffix) { + var end = _compilerName + suffix; + if (result.fileName.endsWith(end)) + result.tcPrefix = result.fileName.slice(0, -end.length); + }); + return result; + }; + results.files = results.files.map(resultsMapper); + allResults = results.files; + var result = results.files[0]; + candidatePaths = result.candidatePaths; + path = result.path; + filePath = result.filePath; + fileName = result.fileName; + tcPrefix = result.tcPrefix; + } +} diff --git a/share/qbs/imports/qbs/Probes/GccProbe.qbs b/share/qbs/imports/qbs/Probes/GccProbe.qbs new file mode 100644 index 00000000..9106ff27 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/GccProbe.qbs @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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.File +import qbs.ModUtils +import "../../../modules/cpp/gcc.js" as Gcc + +PathProbe { + // Inputs + property var compilerFilePathByLanguage + property stringList enableDefinesByLanguage + property stringList flags: [] + property var environment + + property string _nullDevice: qbs.nullDevice + property string _pathListSeparator: qbs.pathListSeparator + property string _sysroot: qbs.sysroot + property stringList _targetOS: qbs.targetOS + + // Outputs + property string architecture + property string endianness + property string targetPlatform + property stringList includePaths + property stringList libraryPaths + property stringList frameworkPaths + property var compilerDefinesByLanguage + + configure: { + 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 = compilerDefinesByLanguage["c"] + || compilerDefinesByLanguage["cpp"] + || compilerDefinesByLanguage["objc"] + || compilerDefinesByLanguage["objcpp"]; + var defaultPaths = Gcc.dumpDefaultPaths(environment, compilerFilePathByLanguage["cpp"] || + compilerFilePathByLanguage["c"], + flags, _nullDevice, + _pathListSeparator, _sysroot, _targetOS); + found = !!macros && !!defaultPaths; + + includePaths = defaultPaths.includePaths; + libraryPaths = defaultPaths.libraryPaths; + frameworkPaths = defaultPaths.frameworkPaths; + + // We have to dump the compiler's macros; -dumpmachine is not suitable because it is not + // always complete (for example, the subarch is not included for arm architectures). + architecture = ModUtils.guessArchitecture(macros); + targetPlatform = ModUtils.guessTargetPlatform(macros); + + switch (macros["__BYTE_ORDER__"]) { + case "__ORDER_BIG_ENDIAN__": + endianness = "big"; + break; + case "__ORDER_LITTLE_ENDIAN__": + endianness = "little"; + break; + } + } +} diff --git a/share/qbs/imports/qbs/Probes/GccVersionProbe.qbs b/share/qbs/imports/qbs/Probes/GccVersionProbe.qbs new file mode 100644 index 00000000..528719e5 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/GccVersionProbe.qbs @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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.File +import "../../../modules/cpp/gcc.js" as Gcc + +PathProbe { + // Inputs + property string compilerFilePath + property var environment + + property string _nullDevice: qbs.nullDevice + property stringList _toolchain: qbs.toolchain + + // Outputs + property int versionMajor + property int versionMinor + property int versionPatch + + configure: { + if (!File.exists(compilerFilePath)) { + found = false; + return; + } + + var macros = Gcc.dumpMacros(environment, compilerFilePath, undefined, _nullDevice); + + if (_toolchain.contains("clang")) { + versionMajor = parseInt(macros["__clang_major__"], 10); + versionMinor = parseInt(macros["__clang_minor__"], 10); + versionPatch = parseInt(macros["__clang_patchlevel__"], 10); + found = true; + } else { + versionMajor = parseInt(macros["__GNUC__"], 10); + versionMinor = parseInt(macros["__GNUC_MINOR__"], 10); + versionPatch = parseInt(macros["__GNUC_PATCHLEVEL__"], 10); + found = true; + } + } +} diff --git a/share/qbs/imports/qbs/Probes/IarProbe.qbs b/share/qbs/imports/qbs/Probes/IarProbe.qbs new file mode 100644 index 00000000..d261e906 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/IarProbe.qbs @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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.File +import "../../../modules/cpp/iar.js" as IAR + +PathProbe { + // Inputs + property string compilerFilePath; + property stringList enableDefinesByLanguage; + + property string _nullDevice: qbs.nullDevice + + // Outputs + property string architecture; + property string endianness; + property int versionMajor; + property int versionMinor; + property int versionPatch; + property stringList includePaths; + property var compilerDefinesByLanguage; + + configure: { + compilerDefinesByLanguage = {}; + + if (!File.exists(compilerFilePath)) { + found = false; + return; + } + + var languages = enableDefinesByLanguage; + if (!languages || languages.length === 0) + languages = ["c"]; + + for (var i = 0; i < languages.length; ++i) { + var tag = languages[i]; + compilerDefinesByLanguage[tag] = IAR.dumpMacros( + compilerFilePath, tag); + } + + var macros = compilerDefinesByLanguage["c"] + || compilerDefinesByLanguage["cpp"]; + + architecture = IAR.guessArchitecture(macros); + endianness = IAR.guessEndianness(macros); + + // FIXME: Do we need dump the default paths for both C + // and C++ languages? + var defaultPaths = IAR.dumpDefaultPaths( + compilerFilePath, languages[0]); + + includePaths = defaultPaths.includePaths; + + var version = IAR.guessVersion(macros, architecture); + if (version) { + versionMajor = version.major; + versionMinor = version.minor; + versionPatch = version.patch; + found = version && architecture && endianness; + } + } +} diff --git a/share/qbs/imports/qbs/Probes/IcoUtilsVersionProbe.qbs b/share/qbs/imports/qbs/Probes/IcoUtilsVersionProbe.qbs new file mode 100644 index 00000000..90c3a06b --- /dev/null +++ b/share/qbs/imports/qbs/Probes/IcoUtilsVersionProbe.qbs @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** 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 "../../../modules/ico/ico.js" as IcoUtils + +Probe { + // Inputs + property string toolFilePath + + // Outputs + property var version + + configure: { + version = IcoUtils.findIcoUtilsVersion(toolFilePath); + found = !!version; + } +} diff --git a/share/qbs/imports/qbs/Probes/IncludeProbe.qbs b/share/qbs/imports/qbs/Probes/IncludeProbe.qbs new file mode 100644 index 00000000..3c1059e6 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/IncludeProbe.qbs @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +PathProbe { + platformSearchPaths: qbs.targetOS.contains("unix") ? [ + "/usr/include", + "/usr/local/include", + ] : [] + platformEnvironmentPaths: { + if (qbs.toolchain.contains('msvc')) + return [ "INCLUDE" ]; + return undefined; + } +} diff --git a/share/qbs/imports/qbs/Probes/InnoSetupProbe.qbs b/share/qbs/imports/qbs/Probes/InnoSetupProbe.qbs new file mode 100644 index 00000000..2c06a6a0 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/InnoSetupProbe.qbs @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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.Utilities + +PathProbe { + // Outputs + property var version + + configure: { + var keySuffix = "Microsoft\\Windows\\CurrentVersion\\Uninstall\\Inno Setup 5_is1"; + var keys = [ + "HKEY_LOCAL_MACHINE\\SOFTWARE\\" + keySuffix, + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\" + keySuffix + ]; + for (var i in keys) { + var v = Utilities.getNativeSetting(keys[i], "DisplayVersion"); + if (v) { + path = Utilities.getNativeSetting(keys[i], "InstallLocation"); + version = v; + found = path && version; + return; + } + } + } +} diff --git a/share/qbs/imports/qbs/Probes/JdkProbe.qbs b/share/qbs/imports/qbs/Probes/JdkProbe.qbs new file mode 100644 index 00000000..1f414b0f --- /dev/null +++ b/share/qbs/imports/qbs/Probes/JdkProbe.qbs @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jake Petroules. +** 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.Environment +import "../../../modules/java/utils.js" as JavaUtils + +PathProbe { + // Inputs + property stringList hostOS: qbs.hostOS + property string architecture: !_androidCrossCompiling ? qbs.architecture : undefined + property bool _androidCrossCompiling: qbs.targetOS.contains("android") + && !qbs.hostOS.contains("android") + + environmentPaths: Environment.getEnv("JAVA_HOME") + platformSearchPaths: [ + "/usr/lib/jvm/default-java", // Debian/Ubuntu + "/etc/alternatives/java_sdk_openjdk", // Fedora + "/usr/lib/jvm/default" // Arch + ] + + configure: { + path = JavaUtils.findJdkPath(hostOS, architecture, environmentPaths, platformSearchPaths); + 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 00000000..786d62f7 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/JdkVersionProbe.qbs @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** 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 "../../../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/KeilProbe.qbs b/share/qbs/imports/qbs/Probes/KeilProbe.qbs new file mode 100644 index 00000000..17a2a62b --- /dev/null +++ b/share/qbs/imports/qbs/Probes/KeilProbe.qbs @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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.File +import "../../../modules/cpp/keil.js" as KEIL + +PathProbe { + // Inputs + property string compilerFilePath; + property stringList enableDefinesByLanguage; + + property string _nullDevice: qbs.nullDevice + + // Outputs + property string architecture; + property string endianness; + property int versionMajor; + property int versionMinor; + property int versionPatch; + property stringList includePaths; + property var compilerDefinesByLanguage; + + configure: { + compilerDefinesByLanguage = {}; + + if (!File.exists(compilerFilePath)) { + found = false; + return; + } + + var languages = enableDefinesByLanguage; + if (!languages || languages.length === 0) + languages = ["c"]; + + for (var i = 0; i < languages.length; ++i) { + var tag = languages[i]; + compilerDefinesByLanguage[tag] = KEIL.dumpMacros( + compilerFilePath, tag, _nullDevice); + } + + var macros = compilerDefinesByLanguage["c"] + || compilerDefinesByLanguage["cpp"]; + + architecture = KEIL.guessArchitecture(macros); + endianness = KEIL.guessEndianness(macros); + + var defaultPaths = KEIL.dumpDefaultPaths( + compilerFilePath, _nullDevice); + + includePaths = defaultPaths.includePaths; + + var version = KEIL.guessVersion(macros); + if (version) { + versionMajor = version.major; + versionMinor = version.minor; + versionPatch = version.patch; + found = version.found && architecture && endianness; + } + } +} diff --git a/share/qbs/imports/qbs/Probes/LibraryProbe.qbs b/share/qbs/imports/qbs/Probes/LibraryProbe.qbs new file mode 100644 index 00000000..0f422073 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/LibraryProbe.qbs @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ivan Komissarov (abbapoh@gmail.com) +** 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. +** +****************************************************************************/ + +PathProbe { + property string endianness + nameSuffixes: { + if (qbs.targetOS.contains("windows")) + return [".lib"]; + if (qbs.targetOS.contains("macos")) + return [".dylib", ".a"]; + return [".so", ".a"]; + } + platformSearchPaths: { + var result = []; + if (qbs.targetOS.contains("unix")) { + if (qbs.targetOS.contains("linux") && qbs.architecture) { + if (qbs.architecture === "armv7") + result = ["/usr/lib/arm-linux-gnueabihf"] + else if (qbs.architecture === "arm64") + result = ["/usr/lib/aarch64-linux-gnu"] + else if (qbs.architecture === "mips" && endianness === "big") + result = ["/usr/lib/mips-linux-gnu"] + else if (qbs.architecture === "mips" && endianness === "little") + result = ["/usr/lib/mipsel-linux-gnu"] + else if (qbs.architecture === "mips64") + result = ["/usr/lib/mips64el-linux-gnuabi64"] + else if (qbs.architecture === "ppc") + result = ["/usr/lib/powerpc-linux-gnu"] + else if (qbs.architecture === "ppc64") + result = ["/usr/lib/powerpc64le-linux-gnu"] + else if (qbs.architecture === "x86_64") + result = ["/usr/lib64", "/usr/lib/x86_64-linux-gnu"] + else if (qbs.architecture === "x86") + result = ["/usr/lib32", "/usr/lib/i386-linux-gnu"] + } + result = result.concat(["/usr/lib", "/usr/local/lib"]); + } + + return result; + } + nameFilter: { + if (qbs.targetOS.contains("unix")) { + return function(name) { + return "lib" + name; + } + } else { + return function(name) { + return name; + } + } + } + platformEnvironmentPaths: { + if (qbs.targetOS.contains("windows")) + return [ "PATH" ]; + else + return [ "LIBRARY_PATH" ]; + } +} diff --git a/share/qbs/imports/qbs/Probes/MsvcProbe.qbs b/share/qbs/imports/qbs/Probes/MsvcProbe.qbs new file mode 100644 index 00000000..2d5faecd --- /dev/null +++ b/share/qbs/imports/qbs/Probes/MsvcProbe.qbs @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Utilities + +PathProbe { + // Inputs + property string compilerFilePath + property stringList enableDefinesByLanguage + property string preferredArchitecture + + // Outputs + property string architecture + property int versionMajor + property int versionMinor + property int versionPatch + property stringList includePaths + property var buildEnv + property var compilerDefinesByLanguage + + configure: { + var languages = enableDefinesByLanguage; + if (!languages || languages.length === 0) + languages = ["c"]; + + 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"]; + + versionMajor = parseInt(ver.substr(0, 2), 10); + versionMinor = parseInt(ver.substr(2, 2), 10); + versionPatch = parseInt(ver.substr(4), 10); + + buildEnv = info.buildEnvironment || infoCpp.buildEnvironment; + var clParentDir = FileInfo.joinPaths(FileInfo.path(compilerFilePath), ".."); + var inclPath = FileInfo.joinPaths(clParentDir, "INCLUDE"); + if (!File.exists(inclPath)) + inclPath = FileInfo.joinPaths(clParentDir, "..", "INCLUDE"); + if (!File.exists(inclPath)) + inclPath = FileInfo.joinPaths(clParentDir, "..", "..", "INCLUDE"); + if (File.exists(inclPath)) + includePaths = [inclPath]; + + if (preferredArchitecture && Utilities.canonicalArchitecture(preferredArchitecture) + !== Utilities.canonicalArchitecture(architecture)) { + throw "'" + preferredArchitecture + + "' differs from the architecture produced by this compiler (" + + architecture + ")"; + } + } +} diff --git a/share/qbs/imports/qbs/Probes/NodeJsProbe.qbs b/share/qbs/imports/qbs/Probes/NodeJsProbe.qbs new file mode 100644 index 00000000..b0162c71 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/NodeJsProbe.qbs @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jake Petroules. +** 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.Environment +import qbs.FileInfo + +BinaryProbe { + names: ["node", "nodejs"] + platformSearchPaths: { + var paths = base; + if (qbs.hostOS.contains("windows")) { + var env32 = Environment.getEnv("PROGRAMFILES(X86)"); + var env64 = Environment.getEnv("PROGRAMFILES"); + if (env64 === env32 && env64.endsWith(" (x86)")) + env64 = env64.slice(0, -(" (x86)".length)); // QTBUG-3845 + paths.push(FileInfo.joinPaths(env64, "nodejs")); + paths.push(FileInfo.joinPaths(env32, "nodejs")); + } + return paths; + } +} diff --git a/share/qbs/imports/qbs/Probes/NpmProbe.qbs b/share/qbs/imports/qbs/Probes/NpmProbe.qbs new file mode 100644 index 00000000..3ca6a96c --- /dev/null +++ b/share/qbs/imports/qbs/Probes/NpmProbe.qbs @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.ModUtils +import "path-probe.js" as PathProbeConfigure +import "../../../modules/nodejs/nodejs.js" as NodeJs + +NodeJsProbe { + names: ["npm"] + + // Inputs + property string interpreterPath + + // Outputs + property path npmBin + property path npmRoot + property path npmPrefix + + configure: { + if (!interpreterPath) + throw '"interpreterPath" must be specified'; + + var selectors; + var results = PathProbeConfigure.configure( + selectors, names, nameSuffixes, nameFilter, candidateFilter, searchPaths, + pathSuffixes, platformSearchPaths, environmentPaths, platformEnvironmentPaths, + pathListSeparator); + + var v = new ModUtils.EnvironmentVariable("PATH", pathListSeparator, + hostOS.contains("windows")); + v.prepend(interpreterPath); + + var resultsMapper = function(result) { + result.npmBin = result.found + ? NodeJs.findLocation(result.filePath, "bin", v.value) + : undefined; + result.npmRoot = result.found + ? NodeJs.findLocation(result.filePath, "root", v.value) + : undefined; + result.npmPrefix = result.found + ? NodeJs.findLocation(result.filePath, "prefix", v.value) + : undefined; + return result; + }; + results.files = results.files.map(resultsMapper); + + found = results.found; + allResults = results.files; + + var result = results.files[0]; + candidatePaths = result.candidatePaths; + path = result.path; + filePath = result.filePath; + fileName = result.fileName; + npmBin = result.npmBin; + npmRoot = result.npmRoot; + npmPrefix = result.npmPrefix; + } +} diff --git a/share/qbs/imports/qbs/Probes/PathProbe.qbs b/share/qbs/imports/qbs/Probes/PathProbe.qbs new file mode 100644 index 00000000..768defd8 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/PathProbe.qbs @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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 "path-probe.js" as PathProbeConfigure +import qbs.ModUtils + +Probe { + // Inputs + property stringList names + property stringList nameSuffixes + property var nameFilter + property var candidateFilter + property varList selectors + property pathList searchPaths + property stringList pathSuffixes + property pathList platformSearchPaths: hostOS.contains("unix") ? ['/usr', '/usr/local'] : [] + property stringList environmentPaths + property stringList platformEnvironmentPaths + property stringList hostOS: qbs.hostOS + property string pathListSeparator: qbs.pathListSeparator + + // Output + property stringList candidatePaths + property string path + property string filePath + property string fileName + + property varList allResults + + configure: { + var results = PathProbeConfigure.configure(selectors, names, nameSuffixes, nameFilter, + candidateFilter, searchPaths, pathSuffixes, + platformSearchPaths, environmentPaths, + platformEnvironmentPaths, pathListSeparator); + found = results.found; + allResults = results.files; + + if (allResults.length === 1) { + var result = allResults[0]; + candidatePaths = result.candidatePaths; + path = result.path; + filePath = result.filePath; + fileName = result.fileName; + } + } +} diff --git a/share/qbs/imports/qbs/Probes/PkgConfigProbe.qbs b/share/qbs/imports/qbs/Probes/PkgConfigProbe.qbs new file mode 100644 index 00000000..b295c744 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/PkgConfigProbe.qbs @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.Process +import qbs.FileInfo + +Probe { + // Inputs + property string sysroot: qbs.sysroot + property string executable: 'pkg-config' + property string name + property stringList packageNames: [name] + property string minVersion + property string exactVersion + property string maxVersion + property bool forStaticBuild: false + property stringList libDirs // Full, non-sysrooted paths, mirroring the environment variable + property string pathListSeparator: qbs.pathListSeparator + + // Output + property stringList cflags // Unmodified --cflags output + property stringList libs // Unmodified --libs output + + property stringList defines + property stringList libraries + property stringList libraryPaths + property stringList includePaths + property stringList compilerFlags + property stringList linkerFlags + property string modversion + + configure: { + if (!packageNames || packageNames.length === 0) + throw 'PkgConfigProbe.packageNames must be specified.'; + var p = new Process(); + try { + var libDirsToSet = libDirs; + if (sysroot) { + p.setEnv("PKG_CONFIG_SYSROOT_DIR", sysroot); + if (!libDirsToSet) { + libDirsToSet = [ + sysroot + "/usr/lib/pkgconfig", + sysroot + "/usr/share/pkgconfig" + ]; + } + } + if (libDirsToSet) + p.setEnv("PKG_CONFIG_LIBDIR", libDirsToSet.join(pathListSeparator)); + var versionArgs = []; + if (minVersion !== undefined) + versionArgs.push("--atleast-version=" + minVersion); + if (exactVersion !== undefined) + versionArgs.push("--exact-version=" + exactVersion); + if (maxVersion !== undefined) + versionArgs.push("--max-version=" + maxVersion); + if (versionArgs.length !== 0 + && p.exec(executable, versionArgs.concat(packageNames)) !== 0) { + return; + } + var args = packageNames; + if (p.exec(executable, args.concat([ '--cflags' ])) === 0) { + cflags = p.readStdOut().trim(); + cflags = cflags ? cflags.split(/\s/) : []; + var libsArgs = args.concat("--libs"); + if (forStaticBuild) + libsArgs.push("--static"); + if (p.exec(executable, libsArgs) === 0) { + libs = p.readStdOut().trim(); + libs = libs ? libs.split(/\s/) : []; + if (p.exec(executable, [packageNames[0]].concat([ '--modversion' ])) === 0) { + modversion = p.readStdOut().trim(); + found = true; + includePaths = []; + defines = [] + compilerFlags = []; + for (var i = 0; i < cflags.length; ++i) { + var flag = cflags[i]; + if (flag.startsWith("-I")) + includePaths.push(flag.slice(2)); + else if (flag.startsWith("-D")) + defines.push(flag.slice(2)); + else + compilerFlags.push(flag); + } + libraries = []; + libraryPaths = []; + linkerFlags = []; + for (i = 0; i < libs.length; ++i) { + flag = libs[i]; + if (flag.startsWith("-l")) + libraries.push(flag.slice(2)); + else if (flag.startsWith("-L")) + libraryPaths.push(flag.slice(2)); + else + linkerFlags.push(flag); + } + console.debug("PkgConfigProbe: found packages " + packageNames); + return; + } + } + } + found = false; + cflags = undefined; + libs = undefined; + } finally { + p.close(); + } + } +} diff --git a/share/qbs/imports/qbs/Probes/SdccProbe.qbs b/share/qbs/imports/qbs/Probes/SdccProbe.qbs new file mode 100644 index 00000000..3595bb15 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/SdccProbe.qbs @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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.File +import "../../../modules/cpp/sdcc.js" as SDCC + +PathProbe { + // Inputs + property string compilerFilePath; + property string preferredArchitecture; + + // Outputs + property string architecture; + property string endianness; + property int versionMajor; + property int versionMinor; + property int versionPatch; + property stringList includePaths; + property var compilerDefinesByLanguage; + + configure: { + compilerDefinesByLanguage = {}; + + if (!File.exists(compilerFilePath)) { + found = false; + return; + } + + var macros = SDCC.dumpMacros(compilerFilePath, preferredArchitecture); + + // SDCC it is only the C language compiler. + compilerDefinesByLanguage["c"] = macros; + + architecture = SDCC.guessArchitecture(macros); + endianness = SDCC.guessEndianness(macros); + + var defaultPaths = SDCC.dumpDefaultPaths(compilerFilePath, architecture); + includePaths = defaultPaths.includePaths; + + var version = SDCC.guessVersion(macros); + versionMajor = version.major; + versionMinor = version.minor; + versionPatch = version.patch; + found = version.found; + } +} diff --git a/share/qbs/imports/qbs/Probes/TypeScriptProbe.qbs b/share/qbs/imports/qbs/Probes/TypeScriptProbe.qbs new file mode 100644 index 00000000..6a854a2e --- /dev/null +++ b/share/qbs/imports/qbs/Probes/TypeScriptProbe.qbs @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jake Petroules. +** 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.File +import qbs.FileInfo +import qbs.ModUtils +import "path-probe.js" as PathProbeConfigure +import "../../../modules/typescript/typescript.js" as TypeScript + +BinaryProbe { + id: tsc + names: ["tsc"] + searchPaths: packageManagerBinPath ? [packageManagerBinPath] : [] + + // Inputs + property path interpreterPath + property path packageManagerBinPath + property path packageManagerRootPath + + // Outputs + property var version + + configure: { + if (!condition) + return; + if (!interpreterPath) + throw '"interpreterPath" must be specified'; + if (!packageManagerBinPath) + throw '"packageManagerBinPath" must be specified'; + if (!packageManagerRootPath) + throw '"packageManagerRootPath" must be specified'; + + var selectors; + var results = PathProbeConfigure.configure( + selectors, names, nameSuffixes, nameFilter, candidateFilter, searchPaths, + pathSuffixes, platformSearchPaths, environmentPaths, platformEnvironmentPaths, + pathListSeparator); + + var v = new ModUtils.EnvironmentVariable("PATH", pathListSeparator, + hostOS.contains("windows")); + v.prepend(interpreterPath); + + var resultsMapper = function(result) { + result.version = result.found + ? TypeScript.findTscVersion(result.filePath, v.value) + : undefined; + if (FileInfo.fromNativeSeparators(packageManagerBinPath) !== result.path || + !File.exists( + FileInfo.fromNativeSeparators(packageManagerRootPath, "typescript"))) { + result = { found: false }; + } + return result; + }; + results.files = results.files.map(resultsMapper); + + found = results.found; + allResults = results.files; + + var result = results.files[0]; + candidatePaths = result.candidatePaths; + path = result.path; + filePath = result.filePath; + fileName = result.fileName; + version = result.version; + } +} diff --git a/share/qbs/imports/qbs/Probes/WiXProbe.qbs b/share/qbs/imports/qbs/Probes/WiXProbe.qbs new file mode 100644 index 00000000..561b275c --- /dev/null +++ b/share/qbs/imports/qbs/Probes/WiXProbe.qbs @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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.Utilities + +PathProbe { + // Inputs + property string registryKey: { + var keys = [ + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Installer XML", + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows Installer XML" + ]; + for (var i in keys) { + var groups = Utilities.nativeSettingGroups(keys[i]).filter(function (v) { + return v.match(/^([0-9]+)\.([0-9]+)$/); + }); + + groups.sort(function (a, b) { + var re = /^([0-9]+)\.([0-9]+)$/; + a = a.match(re); + b = b.match(re); + a = {major: a[1], minor: a[2]}; + b = {major: b[1], minor: b[2]}; + if (a.major === b.major) + return b.minor - a.minor; + return b.major - a.major; + }); + + for (var j in groups) { + var fullKey = keys[i] + "\\" + groups[j]; + if (Utilities.getNativeSetting(fullKey, "ProductVersion")) + return fullKey; + } + } + } + + // Outputs + property var root + property var version + + configure: { + var key = registryKey; + path = Utilities.getNativeSetting(key, "InstallFolder"); + root = Utilities.getNativeSetting(key, "InstallRoot"); + version = Utilities.getNativeSetting(key, "ProductVersion"); + found = path && root && version; + } +} diff --git a/share/qbs/imports/qbs/Probes/XcodeProbe.qbs b/share/qbs/imports/qbs/Probes/XcodeProbe.qbs new file mode 100644 index 00000000..e0ed9934 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/XcodeProbe.qbs @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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.File +import qbs.FileInfo +import qbs.Process +import qbs.PropertyList +import "../../../modules/xcode/xcode.js" as Xcode + +Probe { + // Inputs + property string sdksPath + property string developerPath + property string xcodebuildPath + property stringList targetOS: qbs.targetOS + property string platformType + property string platformPath + property string devicePlatformPath + property string _xcodeInfoPlist: FileInfo.joinPaths(developerPath, "..", "Info.plist") + + // Outputs + property var architectureSettings + property var availableSdks + property string xcodeVersion + + configure: { + if (File.exists(_xcodeInfoPlist)) { + // Optimized case (no forking): reads CFBundleShortVersionString from + // Xcode.app/Contents/Info.plist + var propertyList = new PropertyList(); + try { + propertyList.readFromFile(_xcodeInfoPlist); + + var plist = propertyList.toObject(); + if (plist) + xcodeVersion = plist["CFBundleShortVersionString"]; + } finally { + propertyList.clear(); + } + } else { + // Fallback case: execute xcodebuild -version if Xcode.app/Contents/Info.plist is + // missing; this can happen if developerPath is /, for example + var process; + try { + process = new Process(); + process.exec(xcodebuildPath, ["-version"], true); + var lines = process.readStdOut().trim().split(/\r?\n/g).filter(function (line) { + return line.length > 0; + }); + for (var l = 0; l < lines.length; ++l) { + var m = lines[l].match(/^Xcode ([0-9\.]+)$/); + if (m) { + xcodeVersion = m[1]; + break; + } + } + } finally { + process.close(); + } + } + + architectureSettings = {}; + var archSpecsPath = Xcode.archsSpecsPath(xcodeVersion, targetOS, platformType, + platformPath, devicePlatformPath); + var archSpecsReader = new Xcode.XcodeArchSpecsReader(archSpecsPath); + archSpecsReader.getArchitectureSettings().map(function (setting) { + var val = archSpecsReader.getArchitectureSettingValue(setting); + if (val) + architectureSettings[setting] = val; + }); + + availableSdks = Xcode.sdkInfoList(sdksPath); + found = !!xcodeVersion; + } +} diff --git a/share/qbs/imports/qbs/Probes/path-probe.js b/share/qbs/imports/qbs/Probes/path-probe.js new file mode 100644 index 00000000..b1bdf993 --- /dev/null +++ b/share/qbs/imports/qbs/Probes/path-probe.js @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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 Environment = require("qbs.Environment"); +var File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var ModUtils = require("qbs.ModUtils"); + +function asStringList(key, value) { + if (typeof(value) === "string") + return [value]; + if (Array.isArray(value)) + return value; + throw key + " must be a string or a stringList"; +} + +function canonicalSelectors(selectors, nameSuffixes) { + var mapper = function(selector) { + if (typeof(selector) === "string") + return {names : [selector]}; + if (Array.isArray(selector)) + return {names : selector}; + // dict + if (!selector.names) + throw '"names" must be specified'; + selector.names = asStringList("names", selector.names); + if (selector.nameSuffixes) + selector.nameSuffixes = asStringList("nameSuffixes", selector.nameSuffixes); + else + selector.nameSuffixes = nameSuffixes; + return selector; + }; + return selectors.map(mapper); +} + +function pathsFromEnvs(envs, pathListSeparator) { + envs = envs || []; + var result = []; + for (var i = 0; i < envs.length; ++i) { + var value = Environment.getEnv(envs[i]) || ''; + if (value.length > 0) + result = result.concat(value.split(pathListSeparator)); + } + return result; +} + +function configure(selectors, names, nameSuffixes, nameFilter, candidateFilter, + searchPaths, pathSuffixes, platformSearchPaths, environmentPaths, + platformEnvironmentPaths, pathListSeparator) { + var result = { found: false, files: [] }; + if (!selectors && !names) + throw '"names" or "selectors" must be specified'; + + if (!selectors) { + selectors = [ + {names: names, nameSuffixes: nameSuffixes} + ]; + } else { + selectors = canonicalSelectors(selectors, nameSuffixes); + } + + if (nameFilter) { + selectors.forEach(function(selector) { + selector.names = selector.names.map(nameFilter); + }); + } + + selectors.forEach(function(selector) { + selector.names = ModUtils.concatAll.apply(undefined, selector.names.map(function(name) { + return (selector.nameSuffixes || [""]).map(function(suffix) { + return name + suffix; + }); + })); + }); + + // FIXME: Suggest how to obtain paths from system + var _paths = ModUtils.concatAll( + pathsFromEnvs(environmentPaths, pathListSeparator), + searchPaths, + pathsFromEnvs(platformEnvironmentPaths, pathListSeparator), + platformSearchPaths); + var _suffixes = ModUtils.concatAll('', pathSuffixes); + _paths = _paths.map(function(p) { return FileInfo.fromNativeSeparators(p); }); + _suffixes = _suffixes.map(function(p) { return FileInfo.fromNativeSeparators(p); }); + + var findFile = function(selector) { + var file = { found: false, candidatePaths: [] }; + for (var i = 0; i < selector.names.length; ++i) { + for (var j = 0; j < _paths.length; ++j) { + for (var k = 0; k < _suffixes.length; ++k) { + var _filePath = FileInfo.joinPaths(_paths[j], _suffixes[k], selector.names[i]); + file.candidatePaths.push(_filePath); + if (File.exists(_filePath) + && (!candidateFilter || candidateFilter(_filePath))) { + file.found = true; + file.filePath = _filePath; + + // Manually specify the path components that constitute _filePath rather + // than using the FileInfo.path and FileInfo.fileName functions because we + // want to break _filePath into its constituent parts based on the input + // originally given by the user. For example, the FileInfo functions would + // produce a different result if any of the items in the names property + // contained more than a single path component. + file.fileName = selector.names[i]; + file.path = FileInfo.joinPaths(_paths[j], _suffixes[k]); + return file; + } + } + } + } + + return file; + }; + + result.files = selectors.map(findFile); + result.found = result.files.reduce(function(acc, value) { return acc && value.found }, true); + + return result; +} diff --git a/share/qbs/imports/qbs/UnixUtils/unix-utils.js b/share/qbs/imports/qbs/UnixUtils/unix-utils.js new file mode 100644 index 00000000..aba27726 --- /dev/null +++ b/share/qbs/imports/qbs/UnixUtils/unix-utils.js @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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"); + +function soname(product, outputFileName) { + var soVersion = product.moduleProperty("cpp", "soVersion"); + if (product.moduleProperty("qbs", "targetOS").contains("darwin")) { + // If this is a bundle, ignore the parameter and use the relative path to the bundle binary + // For example: qbs.framework/Versions/1/qbs + if (product.moduleProperty("bundle", "isBundle")) + outputFileName = product.moduleProperty("bundle", "executablePath"); + } else if (soVersion) { + // For non-Darwin platforms, append the shared library major version number to the soname + // For example: libqbscore.so.1 + var version = product.moduleProperty("cpp", "internalVersion"); + if (version) { + outputFileName = outputFileName.substr(0, outputFileName.length - version.length) + + soVersion; + } else { + outputFileName += "." + soVersion; + } + } + + // Prepend the soname prefix + // For example, @rpath/libqbscore.dylib or /usr/lib/libqbscore.so.1 + var prefix = product.moduleProperty("cpp", "sonamePrefix"); + if (prefix) + outputFileName = FileInfo.joinPaths(prefix, outputFileName); + + return outputFileName; +} diff --git a/share/qbs/imports/qbs/WindowsUtils/windows-utils.js b/share/qbs/imports/qbs/WindowsUtils/windows-utils.js new file mode 100644 index 00000000..e4b8487e --- /dev/null +++ b/share/qbs/imports/qbs/WindowsUtils/windows-utils.js @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +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") + defines.push("UNICODE", "_UNICODE"); + else if (charset === "mbcs") + defines.push("_MBCS"); + return defines; +} + +function canonicalizeVersion(version) { + switch (version) { + case "7": + return "6.1"; + case "8": + return "6.2"; + case "8.1": + return "6.3"; + default: + return version; + } +} + +function knownWindowsVersions() { + // Add new Windows versions to this list when they are released + return ['10.0', '6.3', '6.2', '6.1', '6.0', '5.2', '5.1', '5.0', '4.0']; +} + +function isValidWindowsVersion(systemVersion) { + var realVersions = knownWindowsVersions(); + for (i in realVersions) + if (systemVersion === realVersions[i]) + return true; + + return false; +} + +function getWindowsVersionInFormat(systemVersion, format) { + if (!systemVersion) + return undefined; + + var major = parseInt(systemVersion.split('.')[0], 10); + var minor = parseInt(systemVersion.split('.')[1], 10); + + switch (format) { + case "hex": + // https://msdn.microsoft.com/en-us/library/6sehtctf.aspx + return "0x" + ("0000" + ((major << 8) | minor).toString(16)).slice(-4); + case "subsystem": + // https://msdn.microsoft.com/en-us/library/fcc1zstk.aspx + return major + '.' + (minor < 10 ? '0' : '') + minor; + default: + throw ("Unrecognized Windows version format " + format + ". Must be in {hex, subsystem}."); + } +} diff --git a/share/qbs/imports/qbs/base/AndroidApk.qbs b/share/qbs/imports/qbs/base/AndroidApk.qbs new file mode 100644 index 00000000..70cf6aa9 --- /dev/null +++ b/share/qbs/imports/qbs/base/AndroidApk.qbs @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.File +import qbs.FileInfo + +Product { + type: ["android.package"] + qbs.targetPlatform: "android" + Depends { name: "Android.sdk" } +} diff --git a/share/qbs/imports/qbs/base/AppleApplicationDiskImage.qbs b/share/qbs/imports/qbs/base/AppleApplicationDiskImage.qbs new file mode 100644 index 00000000..134f4dee --- /dev/null +++ b/share/qbs/imports/qbs/base/AppleApplicationDiskImage.qbs @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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.File +import qbs.FileInfo +import qbs.ModUtils + +AppleDiskImage { + property string sourceBase: "/Applications" + readonly property string absoluteSourceBase: FileInfo.joinPaths(qbs.installRoot, sourceBase) + property stringList symlinks: ["/Applications:Applications"] + + readonly property string stageDirectory: FileInfo.joinPaths(destinationDirectory, "Volumes", dmg.volumeName) + + Rule { + multiplex: true + outputFileTags: ["dmg.input", "dmg.input.symlink"] + outputArtifacts: Array.prototype.map.call(product.symlinks, function (symlink) { + var symlinkTarget = symlink.split(':')[0]; + var symlinkName = symlink.split(':')[1] || symlinkTarget; + if (FileInfo.isAbsolutePath(symlinkName)) + throw(symlink + " is an invalid symlink; the destination must be a relative path"); + return { + filePath: FileInfo.joinPaths(product.stageDirectory, symlinkName), + fileTags: ["dmg.input", "dmg.input.symlink"], + dmg: { symlinkTarget: symlinkTarget, sourceBase: product.stageDirectory }, + }; + }) + prepare: Array.prototype.map.call(outputs["dmg.input"], function (symlink) { + var cmd = new Command("ln", ["-sfn", symlink.dmg.symlinkTarget, symlink.filePath]); + cmd.workingDirectory = product.stageDirectory; + cmd.description = "symlinking " + symlink.fileName + " => " + symlink.dmg.symlinkTarget; + return cmd; + }); + } + + Rule { + multiplex: true + inputs: ["dmg.input.symlink"] + inputsFromDependencies: ["installable"] + outputFileTags: ["dmg.input"] + outputArtifacts: { + var absSourceBase = product.absoluteSourceBase; + var symlinkPaths = (inputs["dmg.input.symlink"] || []).map(function (s) { return s.filePath; }); + return Array.prototype.map.call(inputs["installable"], function (a) { + var fp = ModUtils.artifactInstalledFilePath(a); + if (fp.startsWith(absSourceBase)) { + var outputFilePath = fp.replace(absSourceBase, product.stageDirectory); + + // Check for symlink conflicts + for (var i in symlinkPaths) { + if (outputFilePath.startsWith(symlinkPaths[i])) + throw new Error("Cannot install '" + a.filePath + + "' to '" + outputFilePath + "' because it " + + "would conflict with the symlink at '" + + symlinkPaths[i] + "'"); + } + + return { + filePath: outputFilePath, + fileTags: ["dmg.input"], + dmg: { sourceBase: product.stageDirectory } + } + } + }).filter(function (a) { return !!a; }); + } + prepare: { + var absSourceBase = product.absoluteSourceBase; + var cmds = [], dmgs = outputs["dmg.input"]; + for (var i in dmgs) { + var a = dmgs[i]; + var cmd = new JavaScriptCommand(); + cmd.src = a.filePath.replace(product.stageDirectory, absSourceBase); + cmd.dst = a.filePath; + cmd.silent = true; + cmd.sourceCode = function () { File.copy(src, dst); }; + cmds.push(cmd); + } + return cmds; + } + } +} diff --git a/share/qbs/imports/qbs/base/AppleDiskImage.qbs b/share/qbs/imports/qbs/base/AppleDiskImage.qbs new file mode 100644 index 00000000..1467d822 --- /dev/null +++ b/share/qbs/imports/qbs/base/AppleDiskImage.qbs @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +Product { + Depends { name: "dmg" } + type: ["dmg.dmg"] +} diff --git a/share/qbs/imports/qbs/base/Application.qbs b/share/qbs/imports/qbs/base/Application.qbs new file mode 100644 index 00000000..1e4f805a --- /dev/null +++ b/share/qbs/imports/qbs/base/Application.qbs @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +NativeBinary { + type: isForAndroid && !consoleApplication ? ["android.package"] : ["application"] + + property bool usesNativeCode + + Depends { + // Note: If we are multiplexing, then this dependency is technically only needed in + // the aggregate. However, the user should not have to write the respective + // condition when assigning to properties of this module, so we load it + // regardless and turn off its rules for the native part of the build. + name: "Android.sdk" + condition: isForAndroid && !consoleApplication + } + Properties { + condition: isForAndroid && !consoleApplication && !usesNativeCode + multiplexByQbsProperties: [] + aggregate: false + } + Properties { + condition: isForAndroid && !consoleApplication && usesNativeCode + && multiplexByQbsProperties && multiplexByQbsProperties.contains("architectures") + && qbs.architectures && qbs.architectures.length > 1 + aggregate: true + multiplexedType: "android.nativelibrary" + } + aggregate: base + multiplexByQbsProperties: base + multiplexedType: base + + installDir: isBundle ? "Applications" : "bin" + + Group { + condition: install + fileTagsFilter: isBundle ? "bundle.content" : "application"; + qbs.install: true + qbs.installDir: installDir + qbs.installSourceBase: isBundle ? destinationDirectory : outer + } + + Group { + condition: installDebugInformation + fileTagsFilter: ["debuginfo_app"] + qbs.install: true + qbs.installDir: debugInformationInstallDir + qbs.installSourceBase: destinationDirectory + } +} diff --git a/share/qbs/imports/qbs/base/ApplicationExtension.qbs b/share/qbs/imports/qbs/base/ApplicationExtension.qbs new file mode 100644 index 00000000..eae5145c --- /dev/null +++ b/share/qbs/imports/qbs/base/ApplicationExtension.qbs @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +XPCService { + Depends { name: "xcode" } + + type: base.concat(["applicationextension"]) + + property bool _useLegacyExtensionLibraries: + qbs.targetOS.contains("macos") && parseInt(xcode.sdkVersion.split(".")[1], 10) < 11 || + qbs.targetOS.contains("ios") && parseInt(xcode.sdkVersion.split(".")[0], 10) < 9 + + cpp.entryPoint: "_NSExtensionMain" + cpp.frameworkPaths: base.concat(_useLegacyExtensionLibraries + ? qbs.sysroot + "/System/Library/PrivateFrameworks/" + : []) + cpp.frameworks: { + var frameworks = base.concat(["Foundation"]); + if (_useLegacyExtensionLibraries) + frameworks.push("PlugInKit"); + return frameworks; + } + + cpp.requireAppExtensionSafeApi: true + + xpcServiceType: undefined + property var extensionAttributes + property string extensionPointIdentifier + property string extensionPrincipalClass + + bundle.infoPlist: { + var infoPlist = base; + infoPlist["NSExtension"] = { + "NSExtensionAttributes": extensionAttributes || {}, + "NSExtensionPointIdentifier": extensionPointIdentifier, + "NSExtensionPrincipalClass": extensionPrincipalClass + }; + return infoPlist; + } +} diff --git a/share/qbs/imports/qbs/base/AutotestRunner.qbs b/share/qbs/imports/qbs/base/AutotestRunner.qbs new file mode 100644 index 00000000..62ba7740 --- /dev/null +++ b/share/qbs/imports/qbs/base/AutotestRunner.qbs @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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.File +import qbs.FileInfo +import qbs.ModUtils + +Product { + name: "autotest-runner" + type: ["autotest-result"] + builtByDefault: false + property stringList arguments: [] + property stringList environment: ModUtils.flattenDictionary(qbs.commonRunEnvironment) + property bool limitToSubProject: true + property stringList wrapper: [] + property string workingDir + property stringList auxiliaryInputs + property int timeout: -1 + + Depends { + productTypes: "autotest" + limitToSubProject: product.limitToSubProject + } + Depends { + productTypes: auxiliaryInputs + limitToSubProject: product.limitToSubProject + } + + Rule { + inputsFromDependencies: "application" + auxiliaryInputs: product.auxiliaryInputs + outputFileTags: "autotest-result" + prepare: { + // TODO: This is hacky. Possible solution: Add autotest tag to application + // in autotest module and have that as inputsFromDependencies instead of application. + if (!input.product.type.contains("autotest")) { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + return cmd; + } + var commandFilePath; + var installed = input.moduleProperty("qbs", "install"); + if (installed) + commandFilePath = ModUtils.artifactInstalledFilePath(input); + if (!commandFilePath || !File.exists(commandFilePath)) + commandFilePath = input.filePath; + var workingDir = product.workingDir ? product.workingDir + : FileInfo.path(commandFilePath); + var arguments = product.arguments; + var allowFailure = false; + var timeout = product.timeout; + if (input.autotest) { + // FIXME: We'd like to let the user override with an empty list, but + // qbscore turns undefined lists into empty ones at the moment. + if (input.autotest.arguments && input.autotest.arguments.length > 0) + arguments = input.autotest.arguments; + + if (input.autotest.workingDir) + workingDir = input.autotest.workingDir; + allowFailure = input.autotest.allowFailure; + + if (input.autotest.timeout !== undefined) + timeout = input.autotest.timeout; + } + var fullCommandLine = product.wrapper + .concat([commandFilePath]) + .concat(arguments); + var cmd = new Command(fullCommandLine[0], fullCommandLine.slice(1)); + cmd.description = "Running test " + input.fileName; + cmd.environment = product.environment; + cmd.workingDirectory = workingDir; + cmd.timeout = timeout; + cmd.jobPool = "autotest-runner"; + if (allowFailure) + cmd.maxExitCode = 32767; + return cmd; + } + } +} diff --git a/share/qbs/imports/qbs/base/CppApplication.qbs b/share/qbs/imports/qbs/base/CppApplication.qbs new file mode 100644 index 00000000..86d2fc74 --- /dev/null +++ b/share/qbs/imports/qbs/base/CppApplication.qbs @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +Application { + Depends { name: "cpp" } + usesNativeCode: true +} + diff --git a/share/qbs/imports/qbs/base/DynamicLibrary.qbs b/share/qbs/imports/qbs/base/DynamicLibrary.qbs new file mode 100644 index 00000000..818665e3 --- /dev/null +++ b/share/qbs/imports/qbs/base/DynamicLibrary.qbs @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +Library { + type: ["dynamiclibrary"].concat(isForAndroid ? ["android.nativelibrary"] : []) +} diff --git a/share/qbs/imports/qbs/base/InnoSetup.qbs b/share/qbs/imports/qbs/base/InnoSetup.qbs new file mode 100644 index 00000000..5ea076eb --- /dev/null +++ b/share/qbs/imports/qbs/base/InnoSetup.qbs @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +Product { + Depends { name: "innosetup"; condition: qbs.targetOS.contains("windows") } + type: ["innosetup.exe"] +} diff --git a/share/qbs/imports/qbs/base/InstallPackage.qbs b/share/qbs/imports/qbs/base/InstallPackage.qbs new file mode 100644 index 00000000..e85fe382 --- /dev/null +++ b/share/qbs/imports/qbs/base/InstallPackage.qbs @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.FileInfo +import qbs.ModUtils +import qbs.TextFile + +Product { + type: ["archiver.archive"] + builtByDefault: false + Depends { name: "archiver" } + archiver.type: "tar" + archiver.workingDirectory: qbs.installRoot + + Rule { + multiplex: true + inputsFromDependencies: ["installable"] + Artifact { + filePath: product.name + ".tarlist" + fileTags: ["archiver.input-list"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode =function() { + var ofile = new TextFile(output.filePath, TextFile.WriteOnly); + try { + for (var i = 0; i < inputs["installable"].length; ++i) { + var inp = inputs["installable"][i]; + var installRoot = inp.moduleProperty("qbs", "installRoot"); + var installedFilePath = ModUtils.artifactInstalledFilePath(inp); + ofile.writeLine(FileInfo.relativePath(installRoot, installedFilePath)); + } + } finally { + ofile.close(); + } + }; + return [cmd]; + } + } +} diff --git a/share/qbs/imports/qbs/base/JavaClassCollection.qbs b/share/qbs/imports/qbs/base/JavaClassCollection.qbs new file mode 100644 index 00000000..23b975a0 --- /dev/null +++ b/share/qbs/imports/qbs/base/JavaClassCollection.qbs @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +Product { + Depends { name: "java" } + type: ["java.class"] +} diff --git a/share/qbs/imports/qbs/base/JavaJarFile.qbs b/share/qbs/imports/qbs/base/JavaJarFile.qbs new file mode 100644 index 00000000..39ab9ec4 --- /dev/null +++ b/share/qbs/imports/qbs/base/JavaJarFile.qbs @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +Product { + Depends { name: "java" } + type: ["java.jar"] + property string entryPoint +} diff --git a/share/qbs/imports/qbs/base/Library.qbs b/share/qbs/imports/qbs/base/Library.qbs new file mode 100644 index 00000000..62e5f9d3 --- /dev/null +++ b/share/qbs/imports/qbs/base/Library.qbs @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +NativeBinary { + type: { + if (qbs.targetOS.contains("ios") && parseInt(cpp.minimumIosVersion, 10) < 8) + return ["staticlibrary"]; + return ["dynamiclibrary"].concat(isForAndroid ? ["android.nativelibrary"] : []); + } + + readonly property bool isDynamicLibrary: type.contains("dynamiclibrary") + readonly property bool isStaticLibrary: type.contains("staticlibrary") + readonly property bool isLoadableModule: type.contains("loadablemodule") + + installDir: { + if (isBundle) + return "Library/Frameworks"; + if (isDynamicLibrary) + return qbs.targetOS.contains("windows") ? "bin" : "lib"; + if (isStaticLibrary) + return "lib"; + } + + property bool installImportLib: false + property string importLibInstallDir: "lib" + + Group { + condition: install + fileTagsFilter: { + if (isBundle) + return ["bundle.content"]; + if (isDynamicLibrary) + return ["dynamiclibrary", "dynamiclibrary_symlink"]; + if (isStaticLibrary) + return ["staticlibrary"]; + return []; + } + qbs.install: true + qbs.installDir: installDir + qbs.installSourceBase: isBundle ? destinationDirectory : outer + } + + Group { + condition: installImportLib && type.contains("dynamiclibrary") + fileTagsFilter: "dynamiclibrary_import" + qbs.install: true + qbs.installDir: importLibInstallDir + } + + Group { + condition: installDebugInformation + fileTagsFilter: { + if (isDynamicLibrary) + return ["debuginfo_dll"]; + else if (isLoadableModule) + return ["debuginfo_loadablemodule"]; + return []; + } + qbs.install: true + qbs.installDir: debugInformationInstallDir + qbs.installSourceBase: destinationDirectory + } +} diff --git a/share/qbs/imports/qbs/base/LoadableModule.qbs b/share/qbs/imports/qbs/base/LoadableModule.qbs new file mode 100644 index 00000000..34579e4d --- /dev/null +++ b/share/qbs/imports/qbs/base/LoadableModule.qbs @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +DynamicLibrary { + type: isForDarwin ? ["loadablemodule"] : base +} diff --git a/share/qbs/imports/qbs/base/NSISSetup.qbs b/share/qbs/imports/qbs/base/NSISSetup.qbs new file mode 100644 index 00000000..1362f271 --- /dev/null +++ b/share/qbs/imports/qbs/base/NSISSetup.qbs @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +Product { + Depends { name: "nsis"; condition: qbs.targetOS.contains("windows") } + type: ["nsissetup"] +} diff --git a/share/qbs/imports/qbs/base/NativeBinary.qbs b/share/qbs/imports/qbs/base/NativeBinary.qbs new file mode 100644 index 00000000..0928e96b --- /dev/null +++ b/share/qbs/imports/qbs/base/NativeBinary.qbs @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +Product { + property bool isForAndroid: qbs.targetOS.contains("android") + property bool isForDarwin: qbs.targetOS.contains("darwin") + property bool isBundle: isForDarwin && bundle.isBundle + + property bool install: false + property string installDir + + property bool installDebugInformation: false + property string debugInformationInstallDir: installDir + + Depends { name: "bundle"; condition: isForDarwin } + + aggregate: { + if (!isForDarwin) + return false; + var multiplexProps = multiplexByQbsProperties; + if (!multiplexProps) + return false; + if (multiplexProps.contains("architectures")) { + var archs = qbs.architectures; + if (archs && archs.length > 1) + return true; + } + if (multiplexProps.contains("buildVariants")) { + var variants = qbs.buildVariants; + return variants && variants.length > 1; + } + return false; + } + + multiplexByQbsProperties: { + if (isForDarwin) + return ["profiles", "architectures", "buildVariants"]; + if (isForAndroid) + return ["architectures"] + return ["profiles"]; + } +} diff --git a/share/qbs/imports/qbs/base/NetModule.qbs b/share/qbs/imports/qbs/base/NetModule.qbs new file mode 100644 index 00000000..0f52ffe4 --- /dev/null +++ b/share/qbs/imports/qbs/base/NetModule.qbs @@ -0,0 +1,4 @@ +Product { + Depends { name: "cli" } + type: ["cli.netmodule"] +} diff --git a/share/qbs/imports/qbs/base/NodeJSApplication.qbs b/share/qbs/imports/qbs/base/NodeJSApplication.qbs new file mode 100644 index 00000000..1908310e --- /dev/null +++ b/share/qbs/imports/qbs/base/NodeJSApplication.qbs @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +Product { + Depends { name: "nodejs" } +} diff --git a/share/qbs/imports/qbs/base/QtApplication.qbs b/share/qbs/imports/qbs/base/QtApplication.qbs new file mode 100644 index 00000000..b6776dca --- /dev/null +++ b/share/qbs/imports/qbs/base/QtApplication.qbs @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +CppApplication { + Depends { name: "Qt.core" } + Properties { + condition: isForAndroid && Qt.android_support._multiAbi + targetName: name + "_" + Android.ndk.abi + } +} diff --git a/share/qbs/imports/qbs/base/QtGuiApplication.qbs b/share/qbs/imports/qbs/base/QtGuiApplication.qbs new file mode 100644 index 00000000..7b2abf01 --- /dev/null +++ b/share/qbs/imports/qbs/base/QtGuiApplication.qbs @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +QtApplication { + Depends { name: "Qt.gui" } +} diff --git a/share/qbs/imports/qbs/base/StaticLibrary.qbs b/share/qbs/imports/qbs/base/StaticLibrary.qbs new file mode 100644 index 00000000..5a78a83b --- /dev/null +++ b/share/qbs/imports/qbs/base/StaticLibrary.qbs @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +Library { + type: ["staticlibrary"] +} diff --git a/share/qbs/imports/qbs/base/WindowsInstallerPackage.qbs b/share/qbs/imports/qbs/base/WindowsInstallerPackage.qbs new file mode 100644 index 00000000..791fc947 --- /dev/null +++ b/share/qbs/imports/qbs/base/WindowsInstallerPackage.qbs @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +Product { + Depends { name: "wix"; condition: qbs.targetOS.contains("windows") } + type: ["msi"] +} diff --git a/share/qbs/imports/qbs/base/WindowsSetupPackage.qbs b/share/qbs/imports/qbs/base/WindowsSetupPackage.qbs new file mode 100644 index 00000000..5175e9d8 --- /dev/null +++ b/share/qbs/imports/qbs/base/WindowsSetupPackage.qbs @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +Product { + Depends { name: "wix"; condition: qbs.targetOS.contains("windows") } + type: ["wixsetup"] +} diff --git a/share/qbs/imports/qbs/base/XPCService.qbs b/share/qbs/imports/qbs/base/XPCService.qbs new file mode 100644 index 00000000..16c25900 --- /dev/null +++ b/share/qbs/imports/qbs/base/XPCService.qbs @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +Application { + type: base.concat(["xpcservice"]) + + condition: qbs.targetOS.contains("darwin") + + property string xpcServiceType: "Application" + + bundle.infoPlist: { + var infoPlist = base; + if (xpcServiceType) { + infoPlist["XPCService"] = { + "ServiceType": xpcServiceType + }; + } + return infoPlist; + } +} diff --git a/share/qbs/module-providers/Qt/provider.qbs b/share/qbs/module-providers/Qt/provider.qbs new file mode 100644 index 00000000..33083c51 --- /dev/null +++ b/share/qbs/module-providers/Qt/provider.qbs @@ -0,0 +1,6 @@ +import "setup-qt.js" as SetupQt + +ModuleProvider { + property stringList qmakeFilePaths + relativeSearchPaths: SetupQt.doSetup(qmakeFilePaths, outputBaseDir, path, qbs) +} diff --git a/share/qbs/module-providers/Qt/setup-qt.js b/share/qbs/module-providers/Qt/setup-qt.js new file mode 100644 index 00000000..c7b72256 --- /dev/null +++ b/share/qbs/module-providers/Qt/setup-qt.js @@ -0,0 +1,1598 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +var Environment = require("qbs.Environment"); +var File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var ModUtils = require("qbs.ModUtils"); +var Process = require("qbs.Process"); +var TextFile = require("qbs.TextFile"); +var Utilities = require("qbs.Utilities"); + +function splitNonEmpty(s, c) { return s.split(c).filter(function(e) { return e; }); } +function toNative(p) { return FileInfo.toNativeSeparators(p); } +function exeSuffix(qbs) { return qbs.hostOS.contains("windows") ? ".exe" : ""; } + +function getQmakeFilePaths(qmakeFilePaths, qbs) { + if (qmakeFilePaths && qmakeFilePaths.length > 0) + return qmakeFilePaths; + console.info("Detecting Qt installations..."); + var pathValue = Environment.getEnv("PATH"); + if (!pathValue) + return []; + var dirs = splitNonEmpty(pathValue, qbs.pathListSeparator); + var suffix = exeSuffix(qbs); + var filePaths = []; + for (var i = 0; i < dirs.length; ++i) { + var candidate = FileInfo.joinPaths(dirs[i], "qmake" + suffix); + var canonicalCandidate = FileInfo.canonicalPath(candidate); + if (!canonicalCandidate || !File.exists(canonicalCandidate)) + continue; + if (FileInfo.completeBaseName(canonicalCandidate) !== "qtchooser") + candidate = canonicalCandidate; + if (!filePaths.contains(candidate)) { + console.info("Found Qt at '" + toNative(candidate) + "'."); + filePaths.push(candidate); + } + } + return filePaths; +} + +function queryQmake(qmakeFilePath) { + var qmakeProcess = new Process; + qmakeProcess.exec(qmakeFilePath, ["-query"]); + if (qmakeProcess.exitCode() !== 0) { + throw "The qmake executable '" + toNative(qmakeFilePath) + "' failed with exit code " + + qmakeProcess.exitCode() + "."; + } + var queryResult = {}; + while (!qmakeProcess.atEnd()) { + var line = qmakeProcess.readLine(); + var index = (line || "").indexOf(":"); + if (index !== -1) + queryResult[line.slice(0, index)] = line.slice(index + 1).trim(); + } + return queryResult; +} + +function pathQueryValue(queryResult, key) { + var p = queryResult[key]; + if (p) + return FileInfo.fromNativeSeparators(p); +} + +function readFileContent(filePath) { + var f = new TextFile(filePath, TextFile.ReadOnly); + var content = f.readAll(); + f.close(); + return content; +} + +// TODO: Don't do the split every time... +function configVariable(configContent, key) { + var configContentLines = configContent.split('\n'); + var regexp = new RegExp("^\\s*" + key + "\\s*\\+{0,1}=(.*)"); + for (var i = 0; i < configContentLines.length; ++i) { + var line = configContentLines[i]; + var match = regexp.exec(line); + if (match) + return match[1].trim(); + } +} + +function configVariableItems(configContent, key) { + return splitNonEmpty(configVariable(configContent, key), ' '); +} + +function msvcPrefix() { return "win32-msvc"; } + +function isMsvcQt(qtProps) { return qtProps.mkspecName.startsWith(msvcPrefix()); } + +function msvcCompilerVersionForYear(year) { + var mapping = { + "2005": "14", "2008": "15", "2010": "16", "2012": "17", "2013": "18", "2015": "19", + "2017": "19.1", "2019": "19.2" + }; + return mapping[year]; +} + +function msvcCompilerVersionFromMkspecName(mkspecName) { + return msvcCompilerVersionForYear(mkspecName.slice(msvcPrefix().length)); +} + +function addQtBuildVariant(qtProps, buildVariantName) { + if (qtProps.qtConfigItems.contains(buildVariantName)) + qtProps.buildVariant.push(buildVariantName); +} + +function checkForStaticBuild(qtProps) { + if (qtProps.qtMajorVersion >= 5) + return qtProps.qtConfigItems.contains("static"); + if (qtProps.frameworkBuild) + return false; // there are no Qt4 static frameworks + var isWin = qtProps.mkspecName.startsWith("win"); + var libDir = isWin ? qtProps.binaryPath : qtProps.libraryPath; + var coreLibFiles = File.directoryEntries(libDir, File.Files) + .filter(function(fp) { return fp.contains("Core"); }); + if (coreLibFiles.length === 0) + throw "Could not determine whether Qt is a static build."; + for (var i = 0; i < coreLibFiles.length; ++i) { + if (Utilities.isSharedLibrary(coreLibFiles[i])) + return false; + } + return true; +} + +function isForMinGw(qtProps) { + return qtProps.mkspecName.startsWith("win32-g++") || qtProps.mkspecName.startsWith("mingw"); +} + +function targetsDesktopWindows(qtProps) { + return qtProps.mkspecName.startsWith("win32-") || isForMinGw(qtProps); +} + +function guessMinimumWindowsVersion(qtProps) { + if (qtProps.mkspecName.startsWith("winrt-")) + return "10.0"; + if (!targetsDesktopWindows(qtProps)) + return ""; + if (qtProps.architecture === "x86_64" || qtProps.architecture === "ia64") + return "5.2" + var match = qtProps.mkspecName.match(/^win32-msvc(\d+)$/); + if (match) { + var msvcVersion = match[1]; + if (msvcVersion < 2012) + return "5.0"; + return "5.1"; + } + return qtProps.qtMajorVersion < 5 ? "5.0" : "5.1"; +} + +function needsDSuffix(qtProps) { + return !isForMinGw(qtProps) || Utilities.versionCompare(qtProps.qtVersion, "5.14.0") < 0 + || qtProps.configItems.contains("debug_and_release"); +} + +function fillEntryPointLibs(qtProps, debug) { + result = []; + var isMinGW = isForMinGw(qtProps); + + // Some Linux distributions rename the qtmain library. + var qtMainCandidates = ["qtmain"]; + if (isMinGW && qtProps.qtMajorVersion === 5) + qtMainCandidates.push("qt5main"); + + for (var i = 0; i < qtMainCandidates.length; ++i) { + var baseNameCandidate = qtMainCandidates[i]; + var qtmain = qtProps.libraryPath + '/'; + if (isMinGW) + qtmain += "lib"; + qtmain += baseNameCandidate + qtProps.qtLibInfix; + if (debug && needsDSuffix(qtProps)) + qtmain += 'd'; + if (isMinGW) { + qtmain += ".a"; + } else { + qtmain += ".lib"; + if (Utilities.versionCompare(qtProps.qtVersion, "5.4.0") >= 0) + result.push("Shell32.lib"); + } + if (File.exists(qtmain)) { + result.push(qtmain); + break; + } + } + if (result.length === 0) { + console.warn("Could not find the qtmain library at '" + toNative(qtProps.libraryPath) + + "'. You will not be able to link Qt applications."); + } + return result; +} + +function abiToArchitecture(abi) { + switch (abi) { + case "armeabi-v7a": + return "armv7a"; + case "arm64-v8a": + return "arm64"; + case "x86": + case "x86_64": + default: + return abi; + } +} + +function getQtProperties(qmakeFilePath, qbs) { + var queryResult = queryQmake(qmakeFilePath); + var qtProps = {}; + qtProps.installPrefixPath = pathQueryValue(queryResult, "QT_INSTALL_PREFIX"); + qtProps.documentationPath = pathQueryValue(queryResult, "QT_INSTALL_DOCS"); + qtProps.includePath = pathQueryValue(queryResult, "QT_INSTALL_HEADERS"); + qtProps.libraryPath = pathQueryValue(queryResult, "QT_INSTALL_LIBS"); + qtProps.hostLibraryPath = pathQueryValue(queryResult, "QT_HOST_LIBS"); + qtProps.binaryPath = pathQueryValue(queryResult, "QT_HOST_BINS") + || pathQueryValue(queryResult, "QT_INSTALL_BINS"); + qtProps.documentationPath = pathQueryValue(queryResult, "QT_INSTALL_DOCS"); + qtProps.pluginPath = pathQueryValue(queryResult, "QT_INSTALL_PLUGINS"); + qtProps.qmlPath = pathQueryValue(queryResult, "QT_INSTALL_QML"); + qtProps.qmlImportPath = pathQueryValue(queryResult, "QT_INSTALL_IMPORTS"); + qtProps.qtVersion = queryResult.QT_VERSION; + + var mkspecsBaseSrcPath; + if (Utilities.versionCompare(qtProps.qtVersion, "5") >= 0) { + qtProps.mkspecBasePath = FileInfo.joinPaths(pathQueryValue(queryResult, "QT_HOST_DATA"), + "mkspecs"); + mkspecsBaseSrcPath = FileInfo.joinPaths(pathQueryValue(queryResult, "QT_HOST_DATA/src"), + "mkspecs"); + } else { + qtProps.mkspecBasePath = FileInfo.joinPaths + (pathQueryValue(queryResult, "QT_INSTALL_DATA"), "mkspecs"); + } + if (!File.exists(qtProps.mkspecBasePath)) + throw "Cannot extract the mkspecs directory."; + + var qconfigContent = readFileContent(FileInfo.joinPaths(qtProps.mkspecBasePath, + "qconfig.pri")); + qtProps.qtMajorVersion = parseInt(configVariable(qconfigContent, "QT_MAJOR_VERSION")); + qtProps.qtMinorVersion = parseInt(configVariable(qconfigContent, "QT_MINOR_VERSION")); + qtProps.qtPatchVersion = parseInt(configVariable(qconfigContent, "QT_PATCH_VERSION")); + qtProps.qtNameSpace = configVariable(qconfigContent, "QT_NAMESPACE"); + qtProps.qtLibInfix = configVariable(qconfigContent, "QT_LIBINFIX") || ""; + qtProps.architecture = configVariable(qconfigContent, "QT_TARGET_ARCH") + || configVariable(qconfigContent, "QT_ARCH") || "x86"; + qtProps.configItems = configVariableItems(qconfigContent, "CONFIG"); + qtProps.qtConfigItems = configVariableItems(qconfigContent, "QT_CONFIG"); + + // retrieve the mkspec + if (qtProps.qtMajorVersion >= 5) { + qtProps.mkspecName = queryResult.QMAKE_XSPEC; + qtProps.mkspecPath = FileInfo.joinPaths(qtProps.mkspecBasePath, qtProps.mkspecName); + if (mkspecsBaseSrcPath && !File.exists(qtProps.mkspecPath)) + qtProps.mkspecPath = FileInfo.joinPaths(mkspecsBaseSrcPath, qtProps.mkspecName); + } else { + if (qbs.hostOS.contains("windows")) { + var baseDirPath = FileInfo.joinPaths(qtProps.mkspecBasePath, "default"); + var fileContent = readFileContent(FileInfo.joinPaths(baseDirPath, "qmake.conf")); + qtProps.mkspecPath = configVariable(fileContent, "QMAKESPEC_ORIGINAL"); + if (!File.exists(qtProps.mkspecPath)) { + // Work around QTBUG-28792. + // The value of QMAKESPEC_ORIGINAL is wrong for MinGW packages. Y u h8 me? + var match = fileContent.exec(/\binclude\(([^)]+)\/qmake\.conf\)/m); + if (match) { + qtProps.mkspecPath = FileInfo.cleanPath(FileInfo.joinPaths( + baseDirPath, match[1])); + } + } + } else { + qtProps.mkspecPath = FileInfo.canonicalPath( + FileInfo.joinPaths(qtProps.mkspecBasePath, "default")); + } + + // E.g. in qmake.conf for Qt 4.8/mingw we find this gem: + // QMAKESPEC_ORIGINAL=C:\\Qt\\Qt\\4.8\\mingw482\\mkspecs\\win32-g++ + qtProps.mkspecPath = FileInfo.cleanPath(qtProps.mkspecPath); + + qtProps.mkspecName = qtProps.mkspecPath; + var idx = qtProps.mkspecName.lastIndexOf('/'); + if (idx !== -1) + qtProps.mkspecName = qtProps.mkspecName.slice(idx + 1); + } + if (!File.exists(qtProps.mkspecPath)) + throw "mkspec '" + toNative(qtProps.mkspecPath) + "' does not exist"; + + // Starting with qt 5.14, android sdk provides multi-abi + if (Utilities.versionCompare(qtProps.qtVersion, "5.14.0") >= 0 + && qtProps.mkspecPath.contains("android")) { + var qdeviceContent = readFileContent(FileInfo.joinPaths(qtProps.mkspecBasePath, + "qdevice.pri")); + qtProps.androidAbis = configVariable(qdeviceContent, "DEFAULT_ANDROID_ABIS").split(' '); + } + + // determine MSVC version + if (isMsvcQt(qtProps)) { + var msvcMajor = configVariable(qconfigContent, "QT_MSVC_MAJOR_VERSION"); + var msvcMinor = configVariable(qconfigContent, "QT_MSVC_MINOR_VERSION"); + var msvcPatch = configVariable(qconfigContent, "QT_MSVC_PATCH_VERSION"); + if (msvcMajor && msvcMinor && msvcPatch) + qtProps.msvcVersion = msvcMajor + "." + msvcMinor + "." + msvcPatch; + else + qtProps.msvcVersion = msvcCompilerVersionFromMkspecName(qtProps.mkspecName); + } + + // determine whether we have a framework build + qtProps.frameworkBuild = qtProps.mkspecPath.contains("macx") + && qtProps.configItems.contains("qt_framework"); + + // determine whether Qt is built with debug, release or both + qtProps.buildVariant = []; + addQtBuildVariant(qtProps, "debug"); + addQtBuildVariant(qtProps, "release"); + + qtProps.staticBuild = checkForStaticBuild(qtProps); + + // determine whether user apps require C++11 + if (qtProps.qtConfigItems.contains("c++11") && qtProps.staticBuild) + qtProps.configItems.push("c++11"); + + // Set the minimum operating system versions appropriate for this Qt version + qtProps.windowsVersion = guessMinimumWindowsVersion(qtProps); + if (qtProps.windowsVersion) { // Is target OS Windows? + if (qtProps.buildVariant.contains("debug")) + qtProps.entryPointLibsDebug = fillEntryPointLibs(qtProps, true); + if (qtProps.buildVariant.contains("release")) + qtProps.entryPointLibsRelease = fillEntryPointLibs(qtProps, false); + } else if (qtProps.mkspecPath.contains("macx")) { + if (qtProps.qtMajorVersion >= 5) { + var lines = getFileContentsRecursively(FileInfo.joinPaths(qtProps.mkspecPath, + "qmake.conf")); + for (var i = 0; i < lines.length; ++i) { + var line = lines[i].trim(); + match = line.match + (/^QMAKE_(MACOSX|IOS|TVOS|WATCHOS)_DEPLOYMENT_TARGET\s*=\s*(.*)\s*$/); + if (match) { + var platform = match[1]; + var version = match[2]; + if (platform === "MACOSX") + qtProps.macosVersion = version; + else if (platform === "IOS") + qtProps.iosVersion = version; + else if (platform === "TVOS") + qtProps.tvosVersion = version; + else if (platform === "WATCHOS") + qtProps.watchosVersion = version; + } + } + var isMac = qtProps.mkspecName !== "macx-ios-clang" + && qtProps.mkspecName !== "macx-tvos-clang" + && qtProps.mkspecName !== "macx-watchos-clang"; + if (isMac) { + // Qt 5.0.x placed the minimum version in a different file + if (!qtProps.macosVersion) + qtProps.macosVersion = "10.6"; + + // If we're using C++11 with libc++, make sure the deployment target is >= 10.7 + if (Utilities.versionCompare(qtProps.macosVersion, "10, 7") < 0 + && qtProps.qtConfigItems.contains("c++11")) { + qtProps.macosVersion = "10.7"; + } + } + } else if (qtProps.qtMajorVersion === 4 && qtProps.qtMinorVersion >= 6) { + var qconfigDir = qtProps.frameworkBuild + ? FileInfo.joinPaths(qtProps.libraryPath, "QtCore.framework", "Headers") + : FileInfo.joinPaths(qtProps.includePath, "Qt"); + try { + var qconfig = new TextFile(FileInfo.joinPaths(qconfigDir, "qconfig.h"), + TextFile.ReadOnly); + var qtCocoaBuild = false; + var ok = true; + do { + line = qconfig.readLine(); + if (line.match(/\s*#define\s+QT_MAC_USE_COCOA\s+1\s*/)) { + qtCocoaBuild = true; + break; + } + } while (!qconfig.atEof()); + qtProps.macosVersion = qtCocoaBuild ? "10.5" : "10.4"; + } + catch (e) {} + finally { + if (qconfig) + qconfig.close(); + } + if (!qtProps.macosVersion) { + throw "Could not determine whether Qt is using Cocoa or Carbon from '" + + toNative(qconfig.filePath()) + "'."; + } + } + } else if (qtProps.mkspecPath.contains("android")) { + if (qtProps.qtMajorVersion >= 5) + qtProps.androidVersion = "2.3"; + else if (qtProps.qtMajorVersion === 4 && qtProps.qtMinorVersion >= 8) + qtProps.androidVersion = "1.6"; // Necessitas + } + return qtProps; +} + +function makePluginData() { + var pluginData = {}; + pluginData.type = undefined; + pluginData.className = undefined; + pluginData.autoLoad = true; + pluginData["extends"] = []; + return pluginData; +} + +function makeQtModuleInfo(name, qbsName, deps) { + var moduleInfo = {}; + moduleInfo.name = name; // As in the path to the headers and ".name" in the pri files. + if (moduleInfo.name === undefined) + moduleInfo.name = ""; + moduleInfo.qbsName = qbsName; // Lower-case version without "qt" prefix. + moduleInfo.dependencies = deps || []; // qbs names. + if (moduleInfo.qbsName && moduleInfo.qbsName !== "core" + && !moduleInfo.dependencies.contains("core")) { + moduleInfo.dependencies.unshift("core"); + } + moduleInfo.isPrivate = qbsName && qbsName.endsWith("-private"); + moduleInfo.hasLibrary = !moduleInfo.isPrivate; + moduleInfo.isStaticLibrary = false; + moduleInfo.isPlugin = false; + moduleInfo.mustExist = true; + moduleInfo.modulePrefix = ""; // empty value means "Qt". + moduleInfo.version = undefined; + moduleInfo.includePaths = []; + moduleInfo.compilerDefines = []; + moduleInfo.staticLibrariesDebug = []; + moduleInfo.staticLibrariesRelease = []; + moduleInfo.dynamicLibrariesDebug = []; + moduleInfo.dynamicLibrariesRelease = []; + moduleInfo.linkerFlagsDebug = []; + moduleInfo.linkerFlagsRelease = []; + moduleInfo.libFilePathDebug = undefined; + moduleInfo.libFilePathRelease = undefined; + moduleInfo.frameworksDebug = []; + moduleInfo.frameworksRelease = []; + moduleInfo.frameworkPathsDebug = []; + moduleInfo.frameworkPathsRelease = []; + moduleInfo.libraryPaths = []; + moduleInfo.libDir = ""; + moduleInfo.config = []; + moduleInfo.supportedPluginTypes = []; + moduleInfo.pluginData = makePluginData(); + return moduleInfo; +} + +function frameworkHeadersPath(qtModuleInfo, qtProps) { + return FileInfo.joinPaths(qtProps.libraryPath, qtModuleInfo.name + ".framework", "Headers"); +} + +function qt4ModuleIncludePaths(qtModuleInfo, qtProps) { + var paths = []; + if (isFramework(qtModuleInfo, qtProps)) + paths.push(frameworkHeadersPath(qtModuleInfo, qtProps)); + else + paths.push(qtProps.includePath, FileInfo.joinPaths(qtProps.includePath, qtModuleInfo.name)); + return paths; +} + +// We erroneously called the "testlib" module "test" for quite a while. Let's not punish users +// for that. +function addTestModule(modules) { + var testModule = makeQtModuleInfo("QtTest", "test", ["testlib"]); + testModule.hasLibrary = false; + modules.push(testModule); +} + +// See above. +function addDesignerComponentsModule(modules) { + var module = makeQtModuleInfo("QtDesignerComponents", "designercomponents", + ["designercomponents-private"]); + module.hasLibrary = false; + modules.push(module); +} + +function isFramework(modInfo, qtProps) { + if (!qtProps.frameworkBuild || modInfo.isStaticLibrary) + return false; + var modulesNeverBuiltAsFrameworks = [ + "bootstrap", "openglextensions", "platformsupport", "qmldevtools", "uitools", "harfbuzzng" + ]; + return !modulesNeverBuiltAsFrameworks.contains(modInfo.qbsName); +} + +function libBaseName(modInfo, libName, debugBuild, qtProps) { + var name = libName; + if (qtProps.mkspecName.startsWith("win")) { + if (debugBuild && needsDSuffix(qtProps)) + name += 'd'; + if (!modInfo.isStaticLibrary && qtProps.qtMajorVersion < 5) + name += qtProps.qtMajorVersion; + } + if (qtProps.mkspecName.contains("macx") + || qtProps.mkspecName.contains("ios") + || qtProps.mkspecName.contains("darwin")) { + if (!isFramework(modInfo, qtProps) + && qtProps.buildVariant.contains("debug") + && (!qtProps.buildVariant.contains("release") || debugBuild)) { + name += "_debug"; + } + } + return name; +} + +function moduleNameWithoutPrefix(modInfo) { + if (modInfo.name === "Phonon") + return "phonon"; + if (!modInfo.modulePrefix && modInfo.name.startsWith("Qt")) + return modInfo.name.slice(2); // Strip off "Qt". + if (modInfo.name.startsWith(modInfo.modulePrefix)) + return modInfo.name.slice(modInfo.modulePrefix.length); + return modInfo.name; +} + +function libraryBaseName(modInfo, qtProps, debugBuild) { + if (modInfo.isPlugin) + return libBaseName(modInfo, modInfo.name, debugBuild, qtProps); + + // Some modules use a different naming scheme, so it doesn't get boring. + var libNameBroken = modInfo.name === "Enginio" + || modInfo.name === "DataVisualization" + || modInfo.name === "Phonon"; + + var libName = !modInfo.modulePrefix && !libNameBroken ? "Qt" : modInfo.modulePrefix; + if (qtProps.qtMajorVersion >= 5 && !isFramework(modInfo, qtProps) && !libNameBroken) + libName += qtProps.qtMajorVersion; + libName += moduleNameWithoutPrefix(modInfo); + libName += qtProps.qtLibInfix; + return libBaseName(modInfo, libName, debugBuild, qtProps); +} + +function libNameForLinker(modInfo, qtProps, debugBuild) { + if (!modInfo.hasLibrary) + return undefined; + var libName = libraryBaseName(modInfo, qtProps, debugBuild); + if (qtProps.mkspecName.contains("msvc")) + libName += ".lib"; + return libName; +} + +function guessLibraryFilePath(prlFilePath, libDir, qtProps) { + var baseName = FileInfo.baseName(prlFilePath); + var prefixCandidates = ["", "lib"]; + var suffixCandidates = ["so." + qtProps.qtVersion, "so", "a", "lib", "dll.a"]; + for (var i = 0; i < prefixCandidates.length; ++i) { + var prefix = prefixCandidates[i]; + for (var j = 0; j < suffixCandidates.length; ++j) { + var suffix = suffixCandidates[j]; + var candidate = FileInfo.joinPaths(libDir, prefix + baseName + '.' + suffix); + if (File.exists(candidate)) + return candidate; + } + } +} + +function doReplaceQtLibNamesWithFilePath(namePathMap, libList) { + for (var i = 0; i < libList.length; ++i) { + var lib = libList[i]; + var path = namePathMap[lib]; + if (path) + libList[i] = path; + } +} + +function replaceQtLibNamesWithFilePath(modules, qtProps) { + // We don't want to add the libraries for Qt modules via "-l", because of the + // danger that a wrong one will be picked up, e.g. from /usr/lib. Instead, + // we pull them in using the full file path. + var linkerNamesToFilePathsDebug = {}; + var linkerNamesToFilePathsRelease = {}; + for (var i = 0; i < modules.length; ++i) { + var m = modules[i]; + linkerNamesToFilePathsDebug[libNameForLinker(m, qtProps, true)] = m.libFilePathDebug; + linkerNamesToFilePathsRelease[libNameForLinker(m, qtProps, false)] = m.libFilePathRelease; + } + for (i = 0; i < modules.length; ++i) { + var module = modules[i]; + doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsDebug, module.dynamicLibrariesDebug); + doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsDebug, module.staticLibrariesDebug); + doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsRelease, + module.dynamicLibrariesRelease); + doReplaceQtLibNamesWithFilePath(linkerNamesToFilePathsRelease, + module.staticLibrariesRelease); + } +} + +function doSetupLibraries(modInfo, qtProps, debugBuild, nonExistingPrlFiles, androidAbi) { + if (!modInfo.hasLibrary) + return; // Can happen for Qt4 convenience modules, like "widgets". + + if (debugBuild) { + if (!qtProps.buildVariant.contains("debug")) + return; + var modulesNeverBuiltAsDebug = ["bootstrap", "qmldevtools"]; + for (var i = 0; i < modulesNeverBuiltAsDebug.length; ++i) { + var m = modulesNeverBuiltAsDebug[i]; + if (modInfo.qbsName === m || modInfo.qbsName === m + "-private") + return; + } + } else if (!qtProps.buildVariant.contains("release")) { + return; + } + + var libs = modInfo.isStaticLibrary + ? (debugBuild ? modInfo.staticLibrariesDebug : modInfo.staticLibrariesRelease) + : (debugBuild ? modInfo.dynamicLibrariesDebug : modInfo.dynamicLibrariesRelease); + var frameworks = debugBuild ? modInfo.frameworksDebug : modInfo.frameworksRelease; + var frameworkPaths = debugBuild ? modInfo.frameworkPathsDebug : modInfo.frameworkPathsRelease; + var flags = debugBuild ? modInfo.linkerFlagsDebug : modInfo.linkerFlagsRelease; + var libFilePath; + + if (qtProps.mkspecName.contains("ios") && modInfo.isStaticLibrary) { + libs.push("z", "m"); + if (qtProps.qtMajorVersion === 5 && qtProps.qtMinorVersion < 8) { + var platformSupportModule = makeQtModuleInfo("QtPlatformSupport", "platformsupport"); + libs.push(libNameForLinker(platformSupportModule, qtProps, debugBuild)); + } + if (modInfo.name === "qios") { + flags.push("-force_load", FileInfo.joinPaths( + qtProps.pluginPath, "platforms", + libBaseName(modInfo, "libqios", debugBuild, qtProps) + ".a")); + } + } + var prlFilePath = modInfo.isPlugin + ? FileInfo.joinPaths(qtProps.pluginPath, modInfo.pluginData.type) + : (modInfo.libDir ? modInfo.libDir : qtProps.libraryPath); + var libDir = prlFilePath; + if (isFramework(modInfo, qtProps)) { + prlFilePath = FileInfo.joinPaths(prlFilePath, + libraryBaseName(modInfo, qtProps, false) + ".framework"); + libDir = prlFilePath; + if (Utilities.versionCompare(qtProps.qtVersion, "5.14") >= 0) + prlFilePath = FileInfo.joinPaths(prlFilePath, "Resources"); + } + var baseName = libraryBaseName(modInfo, qtProps, debugBuild); + if (!qtProps.mkspecName.startsWith("win") && !isFramework(modInfo, qtProps)) + baseName = "lib" + baseName; + prlFilePath = FileInfo.joinPaths(prlFilePath, baseName); + var isNonStaticQt4OnWindows = qtProps.mkspecName.startsWith("win") + && !modInfo.isStaticLibrary && qtProps.qtMajorVersion < 5; + if (isNonStaticQt4OnWindows) + prlFilePath = prlFilePath.slice(0, prlFilePath.length - 1); // The prl file base name does *not* contain the version number... + if (androidAbi.length > 0 + && modInfo.name !== "QtBootstrap" + && modInfo.name !== "QtQmlDevTools") { + prlFilePath += "_"; + prlFilePath += androidAbi; + } + + prlFilePath += ".prl"; + + try { + var prlFile = new TextFile(prlFilePath, TextFile.ReadOnly); + while (!prlFile.atEof()) { + var line = prlFile.readLine().trim(); + var equalsOffset = line.indexOf('='); + if (equalsOffset === -1) + continue; + if (line.startsWith("QMAKE_PRL_TARGET")) { + var isMingw = qtProps.mkspecName.startsWith("win") + && qtProps.mkspecName.contains("g++"); + var isQtVersionBefore56 = qtProps.qtMajorVersion < 5 + || (qtProps.qtMajorVersion === 5 && qtProps.qtMinorVersion < 6); + + // QMAKE_PRL_TARGET has a "lib" prefix, except for mingw. + // Of course, the exception has an exception too: For static libs, mingw *does* + // have the "lib" prefix. + var libFileName = ""; + if (isQtVersionBefore56 && qtProps.qtMajorVersion === 5 && isMingw + && !modInfo.isStaticLibrary) { + libFileName += "lib"; + } + + libFileName += line.slice(equalsOffset + 1).trim(); + if (isNonStaticQt4OnWindows) + libFileName += 4; // This is *not* part of QMAKE_PRL_TARGET... + if (isQtVersionBefore56) { + if (qtProps.mkspecName.contains("msvc")) { + libFileName += ".lib"; + } else if (isMingw) { + libFileName += ".a"; + if (!File.exists(FileInfo.joinPaths(libDir, libFileName))) + libFileName = libFileName.slice(0, -2) + ".dll"; + } + } + libFilePath = FileInfo.joinPaths(libDir, libFileName); + continue; + } + if (line.startsWith("QMAKE_PRL_CONFIG")) { + modInfo.config = splitNonEmpty(line.slice(equalsOffset + 1).trim(), ' '); + continue; + } + if (!line.startsWith("QMAKE_PRL_LIBS =")) + continue; + + var parts = extractPaths(line.slice(equalsOffset + 1).trim(), prlFilePath); + for (i = 0; i < parts.length; ++i) { + var part = parts[i]; + part = part.replace("$$[QT_INSTALL_LIBS]", qtProps.libraryPath); + if (part.startsWith("-l")) { + libs.push(part.slice(2)); + } else if (part.startsWith("-L")) { + modInfo.libraryPaths.push(part.slice(2)); + } else if (part.startsWith("-F")) { + frameworkPaths.push(part.slice(2)); + } else if (part === "-framework") { + if (++i < parts.length) + frameworks.push(parts[i]); + } else if (part === "-pthread") { + libs.push("pthread"); + } else if (part.startsWith('-')) { // Some other option + console.debug("QMAKE_PRL_LIBS contains non-library option '" + part + + "' in file '" + prlFilePath + "'"); + flags.push(part); + } else if (part.startsWith("/LIBPATH:")) { + libraryPaths.push(part.slice(9).replace(/\\/g, '/')); + } else { // Assume it's a file path/name. + libs.push(part.replace(/\\/g, '/')); + } + } + } + } catch (e) { + libFilePath = guessLibraryFilePath(prlFilePath, libDir, qtProps); + if (nonExistingPrlFiles.contains(prlFilePath)) + return; + nonExistingPrlFiles.push(prlFilePath); + if (!libFilePath && modInfo.mustExist) { + console.warn("Could not open prl file '" + toNative(prlFilePath) + "' for module '" + + modInfo.name + + "' (" + e + "), and failed to deduce the library file path. " + + " This module will likely not be usable by qbs."); + } + } + finally { + if (prlFile) + prlFile.close(); + } + + if (debugBuild) + modInfo.libFilePathDebug = libFilePath; + else + modInfo.libFilePathRelease = libFilePath; +} + +function setupLibraries(qtModuleInfo, qtProps, nonExistingPrlFiles, androidAbi) { + doSetupLibraries(qtModuleInfo, qtProps, true, nonExistingPrlFiles, androidAbi); + doSetupLibraries(qtModuleInfo, qtProps, false, nonExistingPrlFiles, androidAbi); +} + +function allQt4Modules(qtProps) { + // as per http://doc.qt.io/qt-4.8/modules.html + private stuff. + var modules = []; + + var core = makeQtModuleInfo("QtCore", "core"); + core.compilerDefines.push("QT_CORE_LIB"); + if (qtProps.qtNameSpace) + core.compilerDefines.push("QT_NAMESPACE=" + qtProps.qtNameSpace); + modules.push(core, + makeQtModuleInfo("QtCore", "core-private", ["core"]), + makeQtModuleInfo("QtGui", "gui"), + makeQtModuleInfo("QtGui", "gui-private", ["gui"]), + makeQtModuleInfo("QtMultimedia", "multimedia", ["gui", "network"]), + makeQtModuleInfo("QtMultimedia", "multimedia-private", ["multimedia"]), + makeQtModuleInfo("QtNetwork", "network"), + makeQtModuleInfo("QtNetwork", "network-private", ["network"]), + makeQtModuleInfo("QtOpenGL", "opengl", ["gui"]), + makeQtModuleInfo("QtOpenGL", "opengl-private", ["opengl"]), + makeQtModuleInfo("QtOpenVG", "openvg", ["gui"]), + makeQtModuleInfo("QtScript", "script"), + makeQtModuleInfo("QtScript", "script-private", ["script"]), + makeQtModuleInfo("QtScriptTools", "scripttools", ["script", "gui"]), + makeQtModuleInfo("QtScriptTools", "scripttools-private", ["scripttools"]), + makeQtModuleInfo("QtSql", "sql"), + makeQtModuleInfo("QtSql", "sql-private", ["sql"]), + makeQtModuleInfo("QtSvg", "svg", ["gui"]), + makeQtModuleInfo("QtSvg", "svg-private", ["svg"]), + makeQtModuleInfo("QtWebKit", "webkit", ["gui", "network"]), + makeQtModuleInfo("QtWebKit", "webkit-private", ["webkit"]), + makeQtModuleInfo("QtXml", "xml"), + makeQtModuleInfo("QtXml", "xml-private", ["xml"]), + makeQtModuleInfo("QtXmlPatterns", "xmlpatterns", ["network"]), + makeQtModuleInfo("QtXmlPatterns", "xmlpatterns-private", ["xmlpatterns"]), + makeQtModuleInfo("QtDeclarative", "declarative", ["gui", "script"]), + makeQtModuleInfo("QtDeclarative", "declarative-private", ["declarative"]), + makeQtModuleInfo("QtDesigner", "designer", ["gui", "xml"]), + makeQtModuleInfo("QtDesigner", "designer-private", ["designer"]), + makeQtModuleInfo("QtUiTools", "uitools"), + makeQtModuleInfo("QtUiTools", "uitools-private", ["uitools"]), + makeQtModuleInfo("QtHelp", "help", ["network", "sql"]), + makeQtModuleInfo("QtHelp", "help-private", ["help"]), + makeQtModuleInfo("QtTest", "testlib"), + makeQtModuleInfo("QtTest", "testlib-private", ["testlib"])); + if (qtProps.mkspecName.startsWith("win")) { + var axcontainer = makeQtModuleInfo("QAxContainer", "axcontainer"); + axcontainer.modulePrefix = "Q"; + axcontainer.isStaticLibrary = true; + axcontainer.includePaths.push(FileInfo.joinPaths(qtProps.includePath, "ActiveQt")); + modules.push(axcontainer); + + var axserver = makeQtModuleInfo("QAxServer", "axserver"); + axserver.modulePrefix = "Q"; + axserver.isStaticLibrary = true; + axserver.compilerDefines.push("QAXSERVER"); + axserver.includePaths.push(FileInfo.joinPaths(qtProps.includePath, "ActiveQt")); + modules.push(axserver); + } else { + modules.push(makeQtModuleInfo("QtDBus", "dbus")); + modules.push(makeQtModuleInfo("QtDBus", "dbus-private", ["dbus"])); + } + + var designerComponentsPrivate = makeQtModuleInfo( + "QtDesignerComponents", "designercomponents-private", + ["gui-private", "designer-private"]); + designerComponentsPrivate.hasLibrary = true; + modules.push(designerComponentsPrivate); + + var phonon = makeQtModuleInfo("Phonon", "phonon"); + phonon.includePaths = qt4ModuleIncludePaths(phonon, qtProps); + modules.push(phonon); + + // Set up include paths that haven't been set up before this point. + for (i = 0; i < modules.length; ++i) { + var module = modules[i]; + if (module.includePaths.length > 0) + continue; + module.includePaths = qt4ModuleIncludePaths(module, qtProps); + } + + // Set up compiler defines haven't been set up before this point. + for (i = 0; i < modules.length; ++i) { + module = modules[i]; + if (module.compilerDefines.length > 0) + continue; + module.compilerDefines.push("QT_" + module.qbsName.toUpperCase() + "_LIB"); + } + + // These are for the convenience of project file authors. It allows them + // to add a dependency to e.g. "Qt.widgets" without a version check. + var virtualModule = makeQtModuleInfo(undefined, "widgets", ["core", "gui"]); + virtualModule.hasLibrary = false; + modules.push(virtualModule); + virtualModule = makeQtModuleInfo(undefined, "quick", ["declarative"]); + virtualModule.hasLibrary = false; + modules.push(virtualModule); + virtualModule = makeQtModuleInfo(undefined, "concurrent"); + virtualModule.hasLibrary = false; + modules.push(virtualModule); + virtualModule = makeQtModuleInfo(undefined, "printsupport", ["core", "gui"]); + virtualModule.hasLibrary = false; + modules.push(virtualModule); + + addTestModule(modules); + addDesignerComponentsModule(modules); + + var modulesThatCanBeDisabled = [ + "xmlpatterns", "multimedia", "phonon", "svg", "webkit", "script", "scripttools", + "declarative", "gui", "dbus", "opengl", "openvg"]; + var nonExistingPrlFiles = []; + for (i = 0; i < modules.length; ++i) { + module = modules[i]; + var name = module.qbsName; + var privateIndex = name.indexOf("-private"); + if (privateIndex !== -1) + name = name.slice(0, privateIndex); + if (modulesThatCanBeDisabled.contains(name)) + module.mustExist = false; + if (qtProps.staticBuild) + module.isStaticLibrary = true; + setupLibraries(module, qtProps, nonExistingPrlFiles, ""); + } + replaceQtLibNamesWithFilePath(modules, qtProps); + + return modules; +} + +function getFileContentsRecursively(filePath) { + var file = new TextFile(filePath, TextFile.ReadOnly); + var lines = splitNonEmpty(file.readAll(), '\n'); + for (var i = 0; i < lines.length; ++i) { + var includeString = "include("; + var line = lines[i].trim(); + if (!line.startsWith(includeString)) + continue; + var offset = includeString.length; + var closingParenPos = line.indexOf(')', offset); + if (closingParenPos === -1) { + console.warn("Invalid include statement in '" + toNative(filePath) + "'"); + continue; + } + var includedFilePath = line.slice(offset, closingParenPos); + if (!FileInfo.isAbsolutePath(includedFilePath)) + includedFilePath = FileInfo.joinPaths(FileInfo.path(filePath), includedFilePath); + var includedContents = getFileContentsRecursively(includedFilePath); + var j = i; + for (var k = 0; k < includedContents.length; ++k) + lines.splice(++j, 0, includedContents[k]); + lines.splice(i--, 1); + } + file.close(); + return lines; +} + +function extractPaths(rhs, filePath) { + var paths = []; + var startIndex = 0; + for (;;) { + while (startIndex < rhs.length && rhs.charAt(startIndex) === ' ') + ++startIndex; + if (startIndex >= rhs.length) + break; + var endIndex; + if (rhs.charAt(startIndex) === '"') { + ++startIndex; + endIndex = rhs.indexOf('"', startIndex); + if (endIndex === -1) { + console.warn("Unmatched quote in file '" + toNative(filePath) + "'"); + break; + } + } else { + endIndex = rhs.indexOf(' ', startIndex + 1); + if (endIndex === -1) + endIndex = rhs.length; + } + paths.push(FileInfo.cleanPath(rhs.slice(startIndex, endIndex))); + startIndex = endIndex + 1; + } + return paths; +} + +function removeDuplicatedDependencyLibs(modules) { + var revDeps = {}; + var currentPath = []; + var getLibraries; + var getLibFilePath; + + function setupReverseDependencies(modules) { + var moduleByName = {}; + for (var i = 0; i < modules.length; ++i) + moduleByName[modules[i].qbsName] = modules[i]; + for (i = 0; i < modules.length; ++i) { + var module = modules[i]; + for (var j = 0; j < module.dependencies.length; ++j) { + var depmod = moduleByName[module.dependencies[j]]; + if (!depmod) + continue; + if (!revDeps[depmod.qbsName]) + revDeps[depmod.qbsName] = []; + revDeps[depmod.qbsName].push(module); + } + } + } + + function roots(modules) { + var result = []; + for (i = 0; i < modules.length; ++i) { + var module = modules[i] + if (module.dependencies.length === 0) + result.push(module); + } + return result; + } + + function traverse(module, libs) { + if (currentPath.contains(module)) + return; + currentPath.push(module); + var moduleLibraryLists = getLibraries(module); + for (var i = 0; i < moduleLibraryLists.length; ++i) { + var modLibList = moduleLibraryLists[i]; + for (j = modLibList.length - 1; j >= 0; --j) { + if (libs.contains(modLibList[j])) + modLibList.splice(j, 1); + } + } + + var libFilePath = getLibFilePath(module); + if (libFilePath) + libs.push(libFilePath); + for (i = 0; i < moduleLibraryLists.length; ++i) + libs = libs.concat(moduleLibraryLists[i]); + libs.sort(); + + var deps = revDeps[module.qbsName]; + for (i = 0; i < (deps || []).length; ++i) + traverse(deps[i], libs); + + currentPath.pop(); + } + + setupReverseDependencies(modules); + + // Traverse the debug variants of modules. + getLibraries = function(module) { + return [module.dynamicLibrariesDebug, module.staticLibrariesDebug]; + }; + getLibFilePath = function(module) { return module.libFilePathDebug; }; + var rootModules = roots(modules); + for (var i = 0; i < rootModules.length; ++i) + traverse(rootModules[i], []); + + // Traverse the release variants of modules. + getLibraries = function(module) { + return [module.dynamicLibrariesRelease, module.staticLibrariesRelease]; + }; + getLibFilePath = function(module) { return module.libFilePathRelease; }; + for (i = 0; i < rootModules.length; ++i) + traverse(rootModules[i], []); +} + +function allQt5Modules(qtProps, androidAbi) { + var nonExistingPrlFiles = []; + var modules = []; + var modulesDir = FileInfo.joinPaths(qtProps.mkspecBasePath, "modules"); + var modulePriFiles = File.directoryEntries(modulesDir, File.Files); + for (var i = 0; i < modulePriFiles.length; ++i) { + var priFileName = modulePriFiles[i]; + var priFilePath = FileInfo.joinPaths(modulesDir, priFileName); + var moduleFileNamePrefix = "qt_lib_"; + var pluginFileNamePrefix = "qt_plugin_"; + var moduleFileNameSuffix = ".pri"; + var fileHasPluginPrefix = priFileName.startsWith(pluginFileNamePrefix); + if (!fileHasPluginPrefix && (!priFileName.startsWith(moduleFileNamePrefix)) + || !priFileName.endsWith(moduleFileNameSuffix)) { + continue; + } + var moduleInfo = makeQtModuleInfo(); + moduleInfo.isPlugin = fileHasPluginPrefix; + var fileNamePrefix = moduleInfo.isPlugin ? pluginFileNamePrefix : moduleFileNamePrefix; + moduleInfo.qbsName = priFileName.slice(fileNamePrefix.length, -moduleFileNameSuffix.length); + if (moduleInfo.isPlugin) { + moduleInfo.name = moduleInfo.qbsName; + moduleInfo.isStaticLibrary = true; + } + var moduleKeyPrefix = (moduleInfo.isPlugin ? "QT_PLUGIN" : "QT") + + '.' + moduleInfo.qbsName + '.'; + moduleInfo.qbsName = moduleInfo.qbsName.replace("_private", "-private"); + var hasV2 = false; + var hasModuleEntry = false; + var lines = getFileContentsRecursively(priFilePath); + for (var j = 0; j < lines.length; ++j) { + var line = lines[j].trim(); + var firstEqualsOffset = line.indexOf('='); + if (firstEqualsOffset === -1) + continue; + var key = line.slice(0, firstEqualsOffset).trim(); + var value = line.slice(firstEqualsOffset + 1).trim(); + if (!key.startsWith(moduleKeyPrefix) || !value) + continue; + if (key.endsWith(".name")) { + moduleInfo.name = value; + } else if (key.endsWith(".module")) { + hasModuleEntry = true; + } else if (key.endsWith(".depends")) { + moduleInfo.dependencies = splitNonEmpty(value, ' '); + for (var k = 0; k < moduleInfo.dependencies.length; ++k) { + moduleInfo.dependencies[k] + = moduleInfo.dependencies[k].replace("_private", "-private"); + } + } else if (key.endsWith(".module_config")) { + var elems = splitNonEmpty(value, ' '); + for (k = 0; k < elems.length; ++k) { + var elem = elems[k]; + if (elem === "no_link") + moduleInfo.hasLibrary = false; + else if (elem === "staticlib") + moduleInfo.isStaticLibrary = true; + else if (elem === "internal_module") + moduleInfo.isPrivate = true; + else if (elem === "v2") + hasV2 = true; + } + } else if (key.endsWith(".includes")) { + moduleInfo.includePaths = extractPaths(value, priFilePath); + for (k = 0; k < moduleInfo.includePaths.length; ++k) { + moduleInfo.includePaths[k] = moduleInfo.includePaths[k] + .replace("$$QT_MODULE_INCLUDE_BASE", qtProps.includePath) + .replace("$$QT_MODULE_HOST_LIB_BASE", qtProps.hostLibraryPath) + .replace("$$QT_MODULE_LIB_BASE", qtProps.libraryPath); + } + } else if (key.endsWith(".libs")) { + var libDirs = extractPaths(value, priFilePath); + if (libDirs.length === 1) { + moduleInfo.libDir = libDirs[0] + .replace("$$QT_MODULE_HOST_LIB_BASE", qtProps.hostLibraryPath) + .replace("$$QT_MODULE_LIB_BASE", qtProps.libraryPath); + } else { + moduleInfo.libDir = qtProps.libraryPath; + } + } else if (key.endsWith(".DEFINES")) { + moduleInfo.compilerDefines = splitNonEmpty(value, ' '); + } else if (key.endsWith(".VERSION")) { + moduleInfo.version = value; + } else if (key.endsWith(".plugin_types")) { + moduleInfo.supportedPluginTypes = splitNonEmpty(value, ' '); + } else if (key.endsWith(".TYPE")) { + moduleInfo.pluginData.type = value; + } else if (key.endsWith(".EXTENDS")) { + moduleInfo.pluginData["extends"] = splitNonEmpty(value, ' '); + for (k = 0; k < moduleInfo.pluginData["extends"].length; ++k) { + if (moduleInfo.pluginData["extends"][k] === "-") { + moduleInfo.pluginData["extends"].splice(k, 1); + moduleInfo.pluginData.autoLoad = false; + break; + } + } + } else if (key.endsWith(".CLASS_NAME")) { + moduleInfo.pluginData.className = value; + } + } + if (hasV2 && !hasModuleEntry) + moduleInfo.hasLibrary = false; + + // Fix include paths for Apple frameworks. + // The qt_lib_XXX.pri files contain wrong values for versions < 5.6. + if (!hasV2 && isFramework(moduleInfo, qtProps)) { + moduleInfo.includePaths = []; + var baseIncDir = frameworkHeadersPath(moduleInfo, qtProps); + if (moduleInfo.isPrivate) { + baseIncDir = FileInfo.joinPaths(baseIncDir, moduleInfo.version); + moduleInfo.includePaths.push(baseIncDir, + FileInfo.joinPaths(baseIncDir, moduleInfo.name)); + } else { + moduleInfo.includePaths.push(baseIncDir); + } + } + + setupLibraries(moduleInfo, qtProps, nonExistingPrlFiles, androidAbi); + + modules.push(moduleInfo); + if (moduleInfo.qbsName === "testlib") + addTestModule(modules); + if (moduleInfo.qbsName === "designercomponents-private") + addDesignerComponentsModule(modules); + } + + replaceQtLibNamesWithFilePath(modules, qtProps); + removeDuplicatedDependencyLibs(modules); + return modules; +} + +function extractQbsArchs(module, qtProps) { + if (qtProps.mkspecName.startsWith("macx-")) { + var archs = []; + if (module.libFilePathRelease) + archs = Utilities.getArchitecturesFromBinary(module.libFilePathRelease); + return archs; + } + var qbsArch = Utilities.canonicalArchitecture(qtProps.architecture); + if (qbsArch === "arm" && qtProps.mkspecPath.contains("android")) + qbsArch = "armv7a"; + // Qt4 has "QT_ARCH = windows" in qconfig.pri for both MSVC and mingw. + if (qbsArch === "windows") + return [] + + return [qbsArch]; +} + +function qbsTargetPlatformFromQtMkspec(qtProps) { + var mkspec = qtProps.mkspecName; + var idx = mkspec.lastIndexOf('/'); + if (idx !== -1) + mkspec = mkspec.slice(idx + 1); + if (mkspec.startsWith("aix-")) + return "aix"; + if (mkspec.startsWith("android-")) + return "android"; + if (mkspec.startsWith("cygwin-")) + return "windows"; + if (mkspec.startsWith("darwin-")) + return "macos"; + if (mkspec.startsWith("freebsd-")) + return "freebsd"; + if (mkspec.startsWith("haiku-")) + return "haiku"; + if (mkspec.startsWith(("hpux-")) || mkspec.startsWith(("hpuxi-"))) + return "hpux"; + if (mkspec.startsWith("hurd-")) + return "hurd"; + if (mkspec.startsWith("integrity-")) + return "integrity"; + if (mkspec.startsWith("linux-")) + return "linux"; + if (mkspec.startsWith("macx-")) { + if (mkspec.startsWith("macx-ios-")) + return "ios"; + if (mkspec.startsWith("macx-tvos-")) + return "tvos"; + if (mkspec.startsWith("macx-watchos-")) + return "watchos"; + return "macos"; + } + if (mkspec.startsWith("netbsd-")) + return "netbsd"; + if (mkspec.startsWith("openbsd-")) + return "openbsd"; + if (mkspec.startsWith("qnx-")) + return "qnx"; + if (mkspec.startsWith("solaris-")) + return "solaris"; + if (mkspec.startsWith("vxworks-")) + return "vxworks"; + if (targetsDesktopWindows(qtProps) || mkspec.startsWith("winrt-")) + return "windows"; +} + +function pathToJSLiteral(path) { return JSON.stringify(FileInfo.fromNativeSeparators(path)); } + +function defaultQpaPlugin(module, qtProps) { + if (qtProps.qtMajorVersion < 5) + return undefined; + if (qtProps.qtMajorVersion === 5 && qtProps.qtMinorVersion < 8) { + var qConfigPri = new TextFile(FileInfo.joinPaths(qtProps.mkspecBasePath, "qconfig.pri")); + var magicString = "QT_DEFAULT_QPA_PLUGIN ="; + while (!qConfigPri.atEof()) { + var line = qConfigPri.readLine().trim(); + if (line.startsWith(magicString)) + return line.slice(magicString.length).trim(); + } + qConfigPri.close(); + } else { + var gtGuiHeadersPath = qtProps.frameworkBuild + ? FileInfo.joinPaths(qtProps.libraryPath, "QtGui.framework", "Headers") + : FileInfo.joinPaths(qtProps.includePath, "QtGui"); + var qtGuiConfigHeader = FileInfo.joinPaths(gtGuiHeadersPath, "qtgui-config.h"); + var headerFiles = []; + headerFiles.push(qtGuiConfigHeader); + while (headerFiles.length > 0) { + var filePath = headerFiles.shift(); + var headerFile = new TextFile(filePath, TextFile.ReadOnly); + var regexp = /^#define QT_QPA_DEFAULT_PLATFORM_NAME "(.+)".*$/; + var includeRegexp = /^#include "(.+)".*$/; + while (!headerFile.atEof()) { + line = headerFile.readLine().trim(); + var match = line.match(regexp); + if (match) + return 'q' + match[1]; + match = line.match(includeRegexp); + if (match) { + var includedFile = match[1]; + if (!FileInfo.isAbsolutePath(includedFile)) { + includedFile = FileInfo.cleanPath( + FileInfo.joinPaths(FileInfo.path(filePath), includedFile)); + } + headerFiles.push(includedFile); + } + } + headerFile.close(); + } + } + + if (module.isStaticLibrary) + console.warn("Could not determine default QPA plugin for static Qt."); +} + +function libraryFileTag(module, qtProps) { + if (module.isStaticLibrary) + return "staticlibrary"; + return isMsvcQt(qtProps) || qtProps.mkspecName.startsWith("win32-g++") + ? "dynamiclibrary_import" : "dynamiclibrary"; +} + +function findVariable(content, start) { + var result = [-1, -1]; + result[0] = content.indexOf('@', start); + if (result[0] === -1) + return result; + result[1] = content.indexOf('@', result[0] + 1); + if (result[1] === -1) { + result[0] = -1; + return result; + } + var forbiddenChars = [' ', '\n']; + for (var i = 0; i < forbiddenChars.length; ++i) { + var forbidden = forbiddenChars[i]; + var k = content.indexOf(forbidden, result[0] + 1); + if (k !== -1 && k < result[1]) + return findVariable(content, result[0] + 1); + } + return result; +} + +function minVersionJsString(minVersion) { + return !minVersion ? "original" : ModUtils.toJSLiteral(minVersion); +} + +function replaceSpecialValues(content, module, qtProps, abi) { + var architectures = []; + if (abi.length > 0) + architectures.push(abiToArchitecture(abi)); + else + architectures = extractQbsArchs(module, qtProps); + var dict = { + archs: ModUtils.toJSLiteral(architectures), + targetPlatform: ModUtils.toJSLiteral(qbsTargetPlatformFromQtMkspec(qtProps)), + config: ModUtils.toJSLiteral(qtProps.configItems), + qtConfig: ModUtils.toJSLiteral(qtProps.qtConfigItems), + binPath: ModUtils.toJSLiteral(qtProps.binaryPath), + libPath: ModUtils.toJSLiteral(qtProps.libraryPath), + pluginPath: ModUtils.toJSLiteral(qtProps.pluginPath), + incPath: ModUtils.toJSLiteral(qtProps.includePath), + docPath: ModUtils.toJSLiteral(qtProps.documentationPath), + mkspecName: ModUtils.toJSLiteral(qtProps.mkspecName), + mkspecPath: ModUtils.toJSLiteral(qtProps.mkspecPath), + version: ModUtils.toJSLiteral(qtProps.qtVersion), + libInfix: ModUtils.toJSLiteral(qtProps.qtLibInfix), + availableBuildVariants: ModUtils.toJSLiteral(qtProps.buildVariant), + staticBuild: ModUtils.toJSLiteral(qtProps.staticBuild), + frameworkBuild: ModUtils.toJSLiteral(qtProps.frameworkBuild), + name: ModUtils.toJSLiteral(moduleNameWithoutPrefix(module)), + has_library: ModUtils.toJSLiteral(module.hasLibrary), + dependencies: ModUtils.toJSLiteral(module.dependencies), + includes: ModUtils.toJSLiteral(module.includePaths), + staticLibsDebug: ModUtils.toJSLiteral(module.staticLibrariesDebug), + staticLibsRelease: ModUtils.toJSLiteral(module.staticLibrariesRelease), + dynamicLibsDebug: ModUtils.toJSLiteral(module.dynamicLibrariesDebug), + dynamicLibsRelease: ModUtils.toJSLiteral(module.dynamicLibrariesRelease), + linkerFlagsDebug: ModUtils.toJSLiteral(module.linkerFlagsDebug), + linkerFlagsRelease: ModUtils.toJSLiteral(module.linkerFlagsRelease), + libraryPaths: ModUtils.toJSLiteral(module.libraryPaths), + frameworkPathsDebug: ModUtils.toJSLiteral(module.frameworkPathsDebug), + frameworkPathsRelease: ModUtils.toJSLiteral(module.frameworkPathsRelease), + frameworksDebug: ModUtils.toJSLiteral(module.frameworksDebug), + frameworksRelease: ModUtils.toJSLiteral(module.frameworksRelease), + libFilePathDebug: ModUtils.toJSLiteral(module.libFilePathDebug), + libFilePathRelease: ModUtils.toJSLiteral(module.libFilePathRelease), + libNameForLinkerDebug: ModUtils.toJSLiteral(libNameForLinker(module, qtProps, true)), + pluginTypes: ModUtils.toJSLiteral(module.supportedPluginTypes), + moduleConfig: ModUtils.toJSLiteral(module.config), + libNameForLinkerRelease: ModUtils.toJSLiteral(libNameForLinker(module, qtProps, false)), + entryPointLibsDebug: ModUtils.toJSLiteral(qtProps.entryPointLibsDebug), + entryPointLibsRelease: ModUtils.toJSLiteral(qtProps.entryPointLibsRelease), + minWinVersion: minVersionJsString(qtProps.windowsVersion), + minMacVersion: minVersionJsString(qtProps.macosVersion), + minIosVersion: minVersionJsString(qtProps.iosVersion), + minTvosVersion: minVersionJsString(qtProps.tvosVersion), + minWatchosVersion: minVersionJsString(qtProps.watchosVersion), + minAndroidVersion: minVersionJsString(qtProps.androidVersion), + }; + + var additionalContent = ""; + var compilerDefines = ModUtils.toJSLiteral(module.compilerDefines); + if (module.qbsName === "declarative" || module.qbsName === "quick") { + var debugMacro = module.qbsName === "declarative" || qtProps.qtMajorVersion < 5 + ? "QT_DECLARATIVE_DEBUG" : "QT_QML_DEBUG"; + var indent = " "; + additionalContent = "property bool qmlDebugging: false\n" + + indent + "property string qmlPath"; + if (qtProps.qmlPath) + additionalContent += ": " + pathToJSLiteral(qtProps.qmlPath) + '\n'; + else + additionalContent += '\n'; + + additionalContent += indent + "property string qmlImportsPath: " + + pathToJSLiteral(qtProps.qmlImportPath); + + compilerDefines = "{\n" + + indent + indent + "var result = " + compilerDefines + ";\n" + + indent + indent + "if (qmlDebugging)\n" + + indent + indent + indent + "result.push(\"" + debugMacro + "\");\n" + + indent + indent + "return result;\n" + + indent + "}"; + } + dict.defines = compilerDefines; + if (module.qbsName === "gui") + dict.defaultQpaPlugin = ModUtils.toJSLiteral(defaultQpaPlugin(module, qtProps)); + if (module.qbsName === "qml") + dict.qmlPath = pathToJSLiteral(qtProps.qmlPath); + if (module.isStaticLibrary && module.qbsName !== "core") { + if (additionalContent) + additionalContent += "\n "; + additionalContent += "isStaticLibrary: true"; + } + if (module.isPlugin) { + dict.className = ModUtils.toJSLiteral(module.pluginData.className); + dict["extends"] = ModUtils.toJSLiteral(module.pluginData["extends"]); + } + indent = " "; + var metaTypesFile = qtProps.libraryPath + "/metatypes/qt" + + qtProps.qtMajorVersion + module.qbsName + "_metatypes.json"; + if (File.exists(metaTypesFile)) { + if (additionalContent) + additionalContent += "\n" + indent; + additionalContent += "Group {\n"; + additionalContent += indent + indent + "files: " + JSON.stringify(metaTypesFile) + "\n" + + indent + indent + "filesAreTargets: true\n" + + indent + indent + "fileTags: [\"qt.core.metatypes\"]\n" + + indent + "}"; + } + if (module.hasLibrary && !isFramework(module, qtProps)) { + if (additionalContent) + additionalContent += "\n" + indent; + additionalContent += "Group {\n"; + if (module.isPlugin) { + additionalContent += indent + indent + + "condition: Qt[\"" + module.qbsName + "\"].enableLinking\n"; + } + additionalContent += indent + indent + "files: [Qt[\"" + module.qbsName + "\"]" + + ".libFilePath]\n" + + indent + indent + "filesAreTargets: true\n" + + indent + indent + "fileTags: [\"" + libraryFileTag(module, qtProps) + + "\"]\n" + + indent + "}"; + } + dict.additionalContent = additionalContent; + + for (var pos = findVariable(content, 0); pos[0] !== -1; + pos = findVariable(content, pos[0])) { + var replacement = dict[content.slice(pos[0] + 1, pos[1])] || ""; + content = content.slice(0, pos[0]) + replacement + content.slice(pos[1] + 1); + pos[0] += replacement.length; + } + return content; +} + +function copyTemplateFile(fileName, targetDirectory, qtProps, abi, location, allFiles, module, + pluginMap, nonEssentialPlugins) +{ + if (!File.makePath(targetDirectory)) { + throw "Cannot create directory '" + toNative(targetDirectory) + "'."; + } + var sourceFile = new TextFile(FileInfo.joinPaths(location, "templates", fileName), + TextFile.ReadOnly); + var newContent = sourceFile.readAll(); + if (module) { + newContent = replaceSpecialValues(newContent, module, qtProps, abi); + } else { + newContent = newContent.replace("@allPluginsByType@", + '(' + ModUtils.toJSLiteral(pluginMap) + ')'); + newContent = newContent.replace("@nonEssentialPlugins@", + ModUtils.toJSLiteral(nonEssentialPlugins)); + newContent = newContent.replace("@version@", ModUtils.toJSLiteral(qtProps.qtVersion)); + } + sourceFile.close(); + var targetPath = FileInfo.joinPaths(targetDirectory, fileName); + allFiles.push(targetPath); + var targetFile = new TextFile(targetPath, TextFile.WriteOnly); + targetFile.write(newContent); + targetFile.close(); +} + +function setupOneQt(qmakeFilePath, outputBaseDir, uniquify, location, qbs) { + if (!File.exists(qmakeFilePath)) + throw "The specified qmake file path '" + toNative(qmakeFilePath) + "' does not exist."; + var qtProps = getQtProperties(qmakeFilePath, qbs); + var androidAbis = []; + if (qtProps.androidAbis !== undefined) + // Multiple androidAbis detected: Qt >= 5.14 + androidAbis = qtProps.androidAbis; + else + // Single abi detected: Qt < 5.14 + androidAbis.push(''); + if (androidAbis.length > 1) + console.info("Qt with multiple abi detected: '" + androidAbis + "'"); + + var relativeSearchPaths = []; + for (a = 0; a < androidAbis.length; ++a) { + if (androidAbis.length > 1) + console.info("Configuring abi '" + androidAbis[a] + "'..."); + var modules = qtProps.qtMajorVersion < 5 ? allQt4Modules(qtProps) : + allQt5Modules(qtProps,androidAbis[a]); + var pluginsByType = {}; + var nonEssentialPlugins = []; + for (var i = 0; i < modules.length; ++i) { + var m = modules[i]; + if (m.isPlugin) { + if (!pluginsByType[m.pluginData.type]) + pluginsByType[m.pluginData.type] = []; + pluginsByType[m.pluginData.type].push(m.name); + if (!m.pluginData.autoLoad) + nonEssentialPlugins.push(m.name); + } + } + + var relativeSearchPath = uniquify ? Utilities.getHash(qmakeFilePath) : ""; + relativeSearchPath = FileInfo.joinPaths(relativeSearchPath, androidAbis[a]); + var qbsQtModuleBaseDir = FileInfo.joinPaths(outputBaseDir, relativeSearchPath, + "modules", "Qt"); + if (File.exists(qbsQtModuleBaseDir)) + File.remove(qbsQtModuleBaseDir); + + var allFiles = []; + copyTemplateFile("QtModule.qbs", qbsQtModuleBaseDir, qtProps, androidAbis[a], location, + allFiles); + copyTemplateFile("QtPlugin.qbs", qbsQtModuleBaseDir, qtProps, androidAbis[a], location, + allFiles); + copyTemplateFile("plugin_support.qbs", + FileInfo.joinPaths(qbsQtModuleBaseDir, "plugin_support"), qtProps, + androidAbis[a], location, allFiles, undefined, pluginsByType, + nonEssentialPlugins); + + for (i = 0; i < modules.length; ++i) { + var module = modules[i]; + var qbsQtModuleDir = FileInfo.joinPaths(qbsQtModuleBaseDir, module.qbsName); + var moduleTemplateFileName; + if (module.qbsName === "core") { + moduleTemplateFileName = "core.qbs"; + copyTemplateFile("moc.js", qbsQtModuleDir, qtProps, androidAbis[a], location, + allFiles); + copyTemplateFile("qdoc.js", qbsQtModuleDir, qtProps, androidAbis[a], location, + allFiles); + } else if (module.qbsName === "gui") { + moduleTemplateFileName = "gui.qbs"; + } else if (module.qbsName === "scxml") { + moduleTemplateFileName = "scxml.qbs"; + } else if (module.qbsName === "dbus") { + moduleTemplateFileName = "dbus.qbs"; + copyTemplateFile("dbus.js", qbsQtModuleDir, qtProps, androidAbis[a], location, + allFiles); + } else if (module.qbsName === "qml") { + moduleTemplateFileName = "qml.qbs"; + copyTemplateFile("qml.js", qbsQtModuleDir, qtProps, androidAbis[a], location, + allFiles); + var qmlcacheStr = "qmlcache"; + if (File.exists(FileInfo.joinPaths(qtProps.binaryPath, + "qmlcachegen" + exeSuffix(qbs)))) { + copyTemplateFile(qmlcacheStr + ".qbs", + FileInfo.joinPaths(qbsQtModuleBaseDir, qmlcacheStr), qtProps, + androidAbis[a], location, allFiles); + } + } else if (module.qbsName === "quick") { + moduleTemplateFileName = "quick.qbs"; + copyTemplateFile("quick.js", qbsQtModuleDir, qtProps, androidAbis[a], location, + allFiles); + } else if (module.isPlugin) { + moduleTemplateFileName = "plugin.qbs"; + } else { + moduleTemplateFileName = "module.qbs"; + } + copyTemplateFile(moduleTemplateFileName, qbsQtModuleDir, qtProps, androidAbis[a], + location, allFiles, module); + } + + // Note that it's not strictly necessary to copy this one, as it has no variable content. + // But we do it anyway for consistency. + copyTemplateFile("android_support.qbs", + FileInfo.joinPaths(qbsQtModuleBaseDir, "android_support"), + qtProps, androidAbis[a], location, allFiles); + relativeSearchPaths.push(relativeSearchPath) + } + return relativeSearchPaths; +} + +function doSetup(qmakeFilePaths, outputBaseDir, location, qbs) { + qmakeFilePaths = getQmakeFilePaths(qmakeFilePaths, qbs); + if (!qmakeFilePaths || qmakeFilePaths.length === 0) + return []; + var uniquifySearchPath = qmakeFilePaths.length > 1; + var allSearchPaths = []; + for (var i = 0; i < qmakeFilePaths.length; ++i) { + try { + console.info("Setting up Qt at '" + toNative(qmakeFilePaths[i]) + "'..."); + var searchPaths = setupOneQt(qmakeFilePaths[i], outputBaseDir, uniquifySearchPath, + location, qbs); + if (searchPaths.length > 0) { + for (var j = 0; j < searchPaths.length; ++j ) + allSearchPaths.push(searchPaths[j]); + console.info("Qt was set up successfully."); + } + } catch (e) { + console.warn("Error setting up Qt for '" + toNative(qmakeFilePaths[i]) + "': " + e); + } + } + return allSearchPaths; +} diff --git a/share/qbs/module-providers/Qt/templates/QtModule.qbs b/share/qbs/module-providers/Qt/templates/QtModule.qbs new file mode 100644 index 00000000..aa7c1d15 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/QtModule.qbs @@ -0,0 +1,86 @@ +import qbs.FileInfo + +Module { + condition: (qbs.targetPlatform === targetPlatform || isCombinedUIKitBuild) + && (!qbs.architecture + || architectures.length === 0 + || architectures.contains(qbs.architecture)) + + readonly property bool isCombinedUIKitBuild: ["ios", "tvos", "watchos"].contains(targetPlatform) + && ["x86", "x86_64"].contains(qbs.architecture) + && qbs.targetPlatform === targetPlatform + "-simulator" + + Depends { name: "cpp" } + Depends { name: "Qt.core" } + + Depends { name: "Qt.plugin_support" } + property stringList pluginTypes + Qt.plugin_support.pluginTypes: pluginTypes + Depends { + condition: Qt.core.staticBuild && !isPlugin + name: "Qt"; + submodules: { + // We have to pull in all plugins here, because dependency resolving happens + // before module merging, and we don't know yet if someone set + // Qt.pluginSupport.pluginsByType in the product. + // The real filtering is done later by the plugin module files themselves. + var list = []; + var allPlugins = Qt.plugin_support.allPluginsByType; + for (var i = 0; i < (pluginTypes || []).length; ++i) + Array.prototype.push.apply(list, allPlugins[pluginTypes[i]]) + return list; + } + } + + property string qtModuleName + property path binPath: Qt.core.binPath + property path incPath: Qt.core.incPath + property path libPath: Qt.core.libPath + property string qtLibInfix: Qt.core.libInfix + property string libNameForLinkerDebug + property string libNameForLinkerRelease + property string libNameForLinker: Qt.core.qtBuildVariant === "debug" + ? libNameForLinkerDebug : libNameForLinkerRelease + property string libFilePathDebug + property string libFilePathRelease + property string libFilePath: Qt.core.qtBuildVariant === "debug" + ? libFilePathDebug : libFilePathRelease + version: Qt.core.version + property bool hasLibrary: true + property bool isStaticLibrary: false + property bool isPlugin: false + + property stringList architectures + property string targetPlatform + property stringList staticLibsDebug + property stringList staticLibsRelease + property stringList dynamicLibsDebug + property stringList dynamicLibsRelease + property stringList linkerFlagsDebug + property stringList linkerFlagsRelease + property stringList staticLibs: Qt.core.qtBuildVariant === "debug" + ? staticLibsDebug : staticLibsRelease + property stringList dynamicLibs: Qt.core.qtBuildVariant === "debug" + ? dynamicLibsDebug : dynamicLibsRelease + property stringList frameworksDebug + property stringList frameworksRelease + property stringList frameworkPathsDebug + property stringList frameworkPathsRelease + property stringList mFrameworks: Qt.core.qtBuildVariant === "debug" + ? frameworksDebug : frameworksRelease + property stringList mFrameworkPaths: Qt.core.qtBuildVariant === "debug" + ? frameworkPathsDebug: frameworkPathsRelease + cpp.linkerFlags: Qt.core.qtBuildVariant === "debug" + ? linkerFlagsDebug : linkerFlagsRelease + property bool enableLinking: qtModuleName != undefined && hasLibrary + property stringList moduleConfig + + Properties { + condition: enableLinking + cpp.staticLibraries: staticLibs + cpp.dynamicLibraries: dynamicLibs + cpp.frameworks: mFrameworks.concat(!isStaticLibrary && Qt.core.frameworkBuild + ? [libNameForLinker] : []) + cpp.frameworkPaths: mFrameworkPaths + } +} diff --git a/share/qbs/module-providers/Qt/templates/QtPlugin.qbs b/share/qbs/module-providers/Qt/templates/QtPlugin.qbs new file mode 100644 index 00000000..88bfa5a6 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/QtPlugin.qbs @@ -0,0 +1,51 @@ +import qbs.FileInfo +import qbs.TextFile + +QtModule { + isPlugin: true + + property string className + property stringList extendsModules + + enableLinking: { + if (!base) + return false; + if (!isStaticLibrary) + return false; + if (!Qt.plugin_support.linkPlugins) + return false; + if (!(Qt.plugin_support.enabledPlugins || []).contains(qtModuleName)) + return false; + if (!extendsModules || extendsModules.length === 0) + return true; + for (var i = 0; i < extendsModules.length; ++i) { + var moduleName = extendsModules[i]; + if (product.Qt[moduleName] && product.Qt[moduleName].present) + return true; + } + return false; + } + + Rule { + condition: enableLinking + multiplex: true + Artifact { + filePath: product.targetName + "_qt_plugin_import_" + + product.moduleProperty(product.moduleName, "qtModuleName") + ".cpp" + fileTags: "cpp" + } + + prepare: { + var cmd = new JavaScriptCommand(); + var pluginName = product.moduleProperty(product.moduleName, "qtModuleName"); + cmd.description = "Creating static import for plugin '" + pluginName + "'."; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + var className = product.moduleProperty(product.moduleName, "className"); + f.writeLine("#include \n\nQ_IMPORT_PLUGIN(" + className + ")"); + f.close(); + }; + return cmd; + } + } +} diff --git a/share/qbs/module-providers/Qt/templates/android_support.qbs b/share/qbs/module-providers/Qt/templates/android_support.qbs new file mode 100644 index 00000000..410b05ff --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/android_support.qbs @@ -0,0 +1,400 @@ +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.TextFile +import qbs.Utilities +import qbs.Process + +Module { + version: @version@ + property bool useMinistro: false + property string qmlRootDir: product.sourceDirectory + property stringList extraPrefixDirs + property stringList deploymentDependencies // qmake: ANDROID_DEPLOYMENT_DEPENDENCIES + property stringList extraPlugins // qmake: ANDROID_EXTRA_PLUGINS + property stringList extraLibs // qmake: ANDROID_EXTRA_LIBS + property bool verboseAndroidDeployQt: false + + property string _androidDeployQtFilePath: FileInfo.joinPaths(_qtInstallDir, "bin", + "androiddeployqt") + property string _qtInstallDir + property bool _enableSdkSupport: product.type && product.type.contains("android.package") + && !consoleApplication + property bool _enableNdkSupport: !product.aggregate || product.multiplexConfigurationId + property string _templatesBaseDir: FileInfo.joinPaths(_qtInstallDir, "src", "android") + property string _deployQtOutDir: FileInfo.joinPaths(product.buildDirectory, "deployqt_out") + + property bool _multiAbi: Utilities.versionCompare(version, "5.14") >= 0 + + Depends { name: "Android.sdk"; condition: _enableSdkSupport } + Depends { name: "Android.ndk"; condition: _enableNdkSupport } + Depends { name: "java"; condition: _enableSdkSupport } + Depends { name: "cpp" } + + Properties { + condition: _enableNdkSupport && qbs.toolchain.contains("clang") + Android.ndk.appStl: "c++_shared" + } + Properties { + condition: _enableNdkSupport && !qbs.toolchain.contains("clang") + Android.ndk.appStl: "gnustl_shared" + } + Properties { + condition: _enableSdkSupport + Android.sdk.customManifestProcessing: true + java._tagJniHeaders: false // prevent rule cycle + } + Properties { + condition: _enableNdkSupport && (Android.ndk.abi === "armeabi-v7a" || Android.ndk.abi === "x86") + cpp.defines: "ANDROID_HAS_WSTRING" + } + Properties { + condition: _enableSdkSupport + Android.sdk._archInName: _multiAbi + Android.sdk._bundledInAssets: _multiAbi + } + + Rule { + condition: _enableSdkSupport + multiplex: true + property stringList inputTags: "android.nativelibrary" + inputsFromDependencies: inputTags + inputs: product.aggregate ? [] : inputTags + Artifact { + filePath: "androiddeployqt.json" + fileTags: "qt_androiddeployqt_input" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.sourceCode = function() { + var theBinary; + var nativeLibs = inputs["android.nativelibrary"]; + var architectures = []; + var triples = []; + var hostArch; + var targetArchitecture; + if (nativeLibs.length === 1) { + theBinary = nativeLibs[0]; + hostArch = theBinary.Android.ndk.hostArch; + targetArchitecture = theBinary.Android.ndk.abi; + if (product.Qt.android_support._multiAbi) { + architectures.push(theBinary.Android.ndk.abi); + triples.push(theBinary.cpp.toolchainTriple); + } + } else { + for (i = 0; i < nativeLibs.length; ++i) { + var candidate = nativeLibs[i]; + if (product.Qt.android_support._multiAbi) { + if (candidate.product.name === product.name) { + architectures.push(candidate.Android.ndk.abi); + triples.push(candidate.cpp.toolchainTriple); + hostArch = candidate.Android.ndk.hostArch; + targetArchitecture = candidate.Android.ndk.abi; + theBinary = candidate; + } + } else { + if (!candidate.fileName.contains(candidate.product.targetName)) + continue; + if (!theBinary) { + theBinary = candidate; + hostArch = theBinary.Android.ndk.hostArch; + targetArchitecture = theBinary.Android.ndk.abi; + continue; + } + if (candidate.product.name !== product.name) { + continue; // This is not going to be a match + } + if (candidate.product.name === product.name + && theBinary.product.name !== product.name) { + theBinary = candidate; // The new candidate is a better match. + hostArch = theBinary.Android.ndk.hostArch; + targetArchitecture = theBinary.Android.ndk.abi; + continue; + } + + throw "Qt applications for Android support only one native binary " + + "per package.\n" + + "In particular, you cannot build a Qt app for more than " + + "one architecture at the same time."; + } + } + } + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.writeLine("{"); + f.writeLine('"description": "This file was generated by qbs to be read by ' + + 'androiddeployqt and should not be modified by hand.",'); + f.writeLine('"qt": "' + product.Qt.android_support._qtInstallDir + '",'); + f.writeLine('"sdk": "' + product.Android.sdk.sdkDir + '",'); + f.writeLine('"sdkBuildToolsRevision": "' + product.Android.sdk.buildToolsVersion + + '",'); + f.writeLine('"ndk": "' + product.Android.sdk.ndkDir + '",'); + f.writeLine('"toolchain-prefix": "llvm",'); + f.writeLine('"tool-prefix": "llvm",'); + f.writeLine('"useLLVM": true,'); + f.writeLine('"ndk-host": "' + hostArch + '",'); + if (!product.Qt.android_support._multiAbi) { + f.writeLine('"target-architecture": "' + targetArchitecture + '",'); + } + else { + var line = '"architectures": {'; + for (var i in architectures) { + line = line + '"' + architectures[i] + '":"' + triples[i] + '"'; + if (i < architectures.length-1) + line = line + ','; + } + line = line + "},"; + f.writeLine(line); + } + f.writeLine('"qml-root-path": "' + product.Qt.android_support.qmlRootDir + '",'); + var deploymentDeps = product.Qt.android_support.deploymentDependencies; + if (deploymentDeps && deploymentDeps.length > 0) + f.writeLine('"deployment-dependencies": "' + deploymentDeps.join() + '",'); + var extraPlugins = product.Qt.android_support.extraPlugins; + if (extraPlugins && extraPlugins.length > 0) + f.writeLine('"android-extra-plugins": "' + extraPlugins.join() + '",'); + var extraLibs = product.Qt.android_support.extraLibs; + if (extraLibs && extraLibs.length > 0) { + for (var i = 0; i < extraLibs.length; ++i) { + if (!FileInfo.isAbsolutePath(extraLibs[i])) { + extraLibs[i] = FileInfo.joinPaths(product.sourceDirectory, extraLibs[i]); + } + } + f.writeLine('"android-extra-libs": "' + extraLibs.join() + '",'); + } + var prefixDirs = product.Qt.android_support.extraPrefixDirs; + if (prefixDirs && prefixDirs.length > 0) + f.writeLine('"extraPrefixDirs": ' + JSON.stringify(prefixDirs) + ','); + if (Array.isArray(product.qmlImportPaths) && product.qmlImportPaths.length > 0) + f.writeLine('"qml-import-paths": "' + product.qmlImportPaths.join(',') + '",'); + + // QBS-1429 + if (!product.Qt.android_support._multiAbi) { + f.writeLine('"stdcpp-path": "' + (product.cpp.sharedStlFilePath + ? product.cpp.sharedStlFilePath : product.cpp.staticStlFilePath) + + '",'); + f.writeLine('"application-binary": "' + theBinary.filePath + '"'); + } else { + f.writeLine('"stdcpp-path": "' + product.Android.sdk.ndkDir + + '/toolchains/llvm/prebuilt/' + hostArch + '/sysroot/usr/lib/",'); + f.writeLine('"application-binary": "' + theBinary.product.name + '"'); + } + f.writeLine("}"); + f.close(); + }; + return cmd; + } + } + + // We use the manifest template from the Qt installation if and only if the project + // does not provide a manifest file. + Rule { + condition: _enableSdkSupport + multiplex: true + requiresInputs: false + inputs: "android.manifest" + excludedInputs: "qt.android_manifest" + outputFileTags: ["android.manifest", "qt.android_manifest"] + outputArtifacts: { + if (inputs["android.manifest"]) + return []; + return [{ + filePath: "qt_manifest/AndroidManifest.xml", + fileTags: ["android.manifest", "qt.android_manifest"] + }]; + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "copying Qt Android manifest template"; + cmd.sourceCode = function() { + File.copy(FileInfo.joinPaths(product.Qt.android_support._templatesBaseDir, + "templates", "AndroidManifest.xml"), output.filePath); + }; + return cmd; + } + } + + Rule { + condition: _enableSdkSupport + multiplex: true + property stringList defaultInputs: ["qt_androiddeployqt_input", + "android.manifest_processed"] + property stringList allInputs: ["qt_androiddeployqt_input", "android.manifest_processed", + "android.nativelibrary"] + inputsFromDependencies: "android.nativelibrary" + inputs: product.aggregate ? defaultInputs : allInputs + outputFileTags: [ + "android.manifest_final", "android.resources", "android.assets", "bundled_jar", + "android.deployqt_list", + ] + outputArtifacts: { + var artifacts = [ + { + filePath: "AndroidManifest.xml", + fileTags: "android.manifest_final" + }, + { + filePath: product.Qt.android_support._deployQtOutDir + "/res/values/libs.xml", + fileTags: "android.resources" + }, + { + filePath: product.Qt.android_support._deployQtOutDir + + "/res/values/strings.xml", + fileTags: "android.resources" + }, + { + filePath: product.Qt.android_support._deployQtOutDir + "/assets/.dummy", + fileTags: "android.assets" + }, + { + filePath: "deployqt.list", + fileTags: "android.deployqt_list" + }, + + ]; + if (!product.Qt.android_support.useMinistro) { + artifacts.push({ + filePath: FileInfo.joinPaths(product.java.classFilesDir, "QtAndroid.jar"), + fileTags: ["bundled_jar"] + }); + } + return artifacts; + } + prepare: { + var copyCmd = new JavaScriptCommand(); + copyCmd.description = "copying Qt resource templates"; + copyCmd.sourceCode = function() { + File.copy(inputs["android.manifest_processed"][0].filePath, + product.Qt.android_support._deployQtOutDir + "/AndroidManifest.xml"); + File.copy(product.Qt.android_support._templatesBaseDir + "/java/res", + product.Qt.android_support._deployQtOutDir + "/res"); + File.copy(product.Qt.android_support._templatesBaseDir + + "/templates/res/values/libs.xml", + product.Qt.android_support._deployQtOutDir + "/res/values/libs.xml"); + if (!product.Qt.android_support._multiAbi) { + try { + File.remove(FileInfo.path(outputs["android.assets"][0].filePath)); + } catch (e) { + } + } + else { + for (var i in inputs["android.nativelibrary"]) { + var input = inputs["android.nativelibrary"][i]; + File.copy(input.filePath, + FileInfo.joinPaths(product.Qt.android_support._deployQtOutDir, + "libs", + input.Android.ndk.abi, + input.fileName)); + } + } + }; + var androidDeployQtArgs = [ + "--output", product.Qt.android_support._deployQtOutDir, + "--input", inputs["qt_androiddeployqt_input"][0].filePath, "--aux-mode", + "--deployment", product.Qt.android_support.useMinistro ? "ministro" : "bundled", + "--android-platform", product.Android.sdk.platform, + ]; + if (product.Qt.android_support.verboseAndroidDeployQt) + androidDeployQtArgs.push("--verbose"); + var androidDeployQtCmd = new Command( + product.Qt.android_support._androidDeployQtFilePath, androidDeployQtArgs); + androidDeployQtCmd.description = "running androiddeployqt"; + + // We do not want androiddeployqt to write directly into our APK base dir, so + // we ran it on an isolated directory and now we move stuff over. + // We remember the files for which we do that, so if the next invocation + // of androiddeployqt creates fewer files, the other ones are removed from + // the APK base dir. + var moveCmd = new JavaScriptCommand(); + moveCmd.description = "processing androiddeployqt outout"; + moveCmd.sourceCode = function() { + File.move(product.Qt.android_support._deployQtOutDir + "/AndroidManifest.xml", + outputs["android.manifest_final"][0].filePath); + var libsDir = product.Qt.android_support._deployQtOutDir + "/libs"; + var libDir = product.Android.sdk.packageContentsDir + "/lib"; + var listFilePath = outputs["android.deployqt_list"][0].filePath; + var oldLibs = []; + try { + var listFile = new TextFile(listFilePath, TextFile.ReadOnly); + var listFileLine = listFile.readLine(); + while (listFileLine) { + oldLibs.push(listFileLine); + listFileLine = listFile.readLine(); + } + listFile.close(); + } catch (e) { + } + listFile = new TextFile(listFilePath, TextFile.WriteOnly); + var newLibs = []; + var moveLibFiles = function(prefix) { + var fullSrcPrefix = FileInfo.joinPaths(libsDir, prefix); + var files = File.directoryEntries(fullSrcPrefix, File.Files); + for (var i = 0; i < files.length; ++i) { + var file = files[i]; + var srcFilePath = FileInfo.joinPaths(fullSrcPrefix, file); + var targetFilePath; + if (file.endsWith(".jar")) + targetFilePath = FileInfo.joinPaths(product.java.classFilesDir, file); + else + targetFilePath = FileInfo.joinPaths(libDir, prefix, file); + File.move(srcFilePath, targetFilePath); + listFile.writeLine(targetFilePath); + newLibs.push(targetFilePath); + } + var dirs = File.directoryEntries(fullSrcPrefix, + File.Dirs | File.NoDotAndDotDot); + for (i = 0; i < dirs.length; ++i) + moveLibFiles(FileInfo.joinPaths(prefix, dirs[i])); + }; + moveLibFiles(""); + listFile.close(); + for (i = 0; i < oldLibs.length; ++i) { + if (!newLibs.contains(oldLibs[i])) + File.remove(oldLibs[i]); + } + }; + + // androiddeployqt doesn't strip the deployed libraries anymore so it has to done here + var stripLibsCmd = new JavaScriptCommand(); + stripLibsCmd.description = "Stripping unneeded symbols from deployed qt libraries"; + stripLibsCmd.sourceCode = function() { + var stripArgs = ["--strip-all"]; + var architectures = []; + for (var i in inputs["android.nativelibrary"]) + architectures.push(inputs["android.nativelibrary"][i].Android.ndk.abi); + for (var i in architectures) { + var abiDirPath = FileInfo.joinPaths(product.Android.sdk.packageContentsDir, + "lib", architectures[i]); + var files = File.directoryEntries(abiDirPath, File.Files); + for (var i = 0; i < files.length; ++i) { + var filePath = FileInfo.joinPaths(abiDirPath, files[i]); + if (FileInfo.suffix(filePath) == "so") { + stripArgs.push(filePath); + } + } + } + var process = new Process(); + process.exec(product.cpp.stripPath, stripArgs, false); + } + + return [copyCmd, androidDeployQtCmd, moveCmd, stripLibsCmd]; + } + } + + Group { + condition: Qt.android_support._enableSdkSupport + name: "helper sources from qt" + prefix: Qt.android_support._templatesBaseDir + "/java/" + Android.sdk.aidlSearchPaths: prefix + "src" + files: [ + "**/*.java", + "**/*.aidl", + ] + } + + validate: { + if (Utilities.versionCompare(version, "5.12") < 0) + throw ModUtils.ModuleError("Cannot use Qt " + version + " with Android. " + + "Version 5.12 or later is required."); + } +} diff --git a/share/qbs/module-providers/Qt/templates/core.qbs b/share/qbs/module-providers/Qt/templates/core.qbs new file mode 100644 index 00000000..10359e75 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/core.qbs @@ -0,0 +1,538 @@ +import qbs.FileInfo +import qbs.ModUtils +import qbs.TextFile +import qbs.Utilities +import qbs.Xml +import "moc.js" as Moc +import "qdoc.js" as Qdoc + +Module { + condition: (qbs.targetPlatform === targetPlatform || isCombinedUIKitBuild) + && (!qbs.architecture + || architectures.length === 0 + || architectures.contains(qbs.architecture)) + + readonly property bool isCombinedUIKitBuild: ["ios", "tvos", "watchos"].contains(targetPlatform) + && (!qbs.architecture || ["x86", "x86_64"].contains(qbs.architecture)) + && qbs.targetPlatform === targetPlatform + "-simulator" + + Depends { name: "cpp" } + + Depends { name: "Qt.android_support"; condition: qbs.targetOS.contains("android") } + Properties { + condition: qbs.targetOS.contains("android") + Qt.android_support._qtInstallDir: FileInfo.path(binPath) + Qt.android_support.version: version + } + + version: @version@ + property stringList architectures: @archs@ + property string targetPlatform: @targetPlatform@ + property string libInfix: @libInfix@ + property stringList config: @config@ + property stringList qtConfig: @qtConfig@ + property path binPath: @binPath@ + 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: [] + property string lreleaseName: "lrelease" + property string qdocName: versionMajor >= 5 ? "qdoc" : "qdoc3" + property stringList qdocEnvironment + property path docPath: @docPath@ + property stringList helpGeneratorArgs: versionMajor >= 5 ? ["-platform", "minimal"] : [] + property var versionParts: version ? version.split('.').map(function(item) { return parseInt(item, 10); }) : [] + property int versionMajor: versionParts[0] + property int versionMinor: versionParts[1] + property int versionPatch: versionParts[2] + property bool frameworkBuild: @frameworkBuild@ + property bool staticBuild: @staticBuild@ + property stringList pluginMetaData: [] + property bool enableKeywords: true + property bool generateMetaTypesFile + readonly property bool _generateMetaTypesFile: generateMetaTypesFile + && Utilities.versionCompare(version, "5.15") >= 0 + property string metaTypesInstallDir + + property stringList availableBuildVariants: @availableBuildVariants@ + property string qtBuildVariant: { + if (availableBuildVariants.contains(qbs.buildVariant)) + return qbs.buildVariant; + return availableBuildVariants.length > 0 ? availableBuildVariants[0] : ""; + } + + property stringList staticLibsDebug: @staticLibsDebug@ + property stringList staticLibsRelease: @staticLibsRelease@ + property stringList dynamicLibsDebug: @dynamicLibsDebug@ + property stringList dynamicLibsRelease: @dynamicLibsRelease@ + property stringList staticLibs: qtBuildVariant === "debug" + ? staticLibsDebug : staticLibsRelease + property stringList dynamicLibs: qtBuildVariant === "debug" + ? dynamicLibsDebug : dynamicLibsRelease + property stringList linkerFlagsDebug: @linkerFlagsDebug@ + property stringList linkerFlagsRelease: @linkerFlagsRelease@ + property stringList coreLinkerFlags: qtBuildVariant === "debug" + ? linkerFlagsDebug : linkerFlagsRelease + property stringList frameworksDebug: @frameworksDebug@ + property stringList frameworksRelease: @frameworksRelease@ + property stringList coreFrameworks: qtBuildVariant === "debug" + ? frameworksDebug : frameworksRelease + property stringList frameworkPathsDebug: @frameworkPathsDebug@ + property stringList frameworkPathsRelease: @frameworkPathsRelease@ + property stringList coreFrameworkPaths: qtBuildVariant === "debug" + ? frameworkPathsDebug : frameworkPathsRelease + property string libNameForLinkerDebug: @libNameForLinkerDebug@ + property string libNameForLinkerRelease: @libNameForLinkerRelease@ + property string libNameForLinker: qtBuildVariant === "debug" + ? libNameForLinkerDebug : libNameForLinkerRelease + property string libFilePathDebug: @libFilePathDebug@ + property string libFilePathRelease: @libFilePathRelease@ + property string libFilePath: qtBuildVariant === "debug" + ? libFilePathDebug : libFilePathRelease + + property stringList coreLibPaths: @libraryPaths@ + property bool hasLibrary: true + + // 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 qdocOutputDir: product.buildDirectory + "/qdoc_html" + property string qmDir: product.destinationDirectory + property string qmBaseName: product.targetName + property bool lreleaseMultiplexMode: false + + property stringList moduleConfig: @moduleConfig@ + Properties { + condition: moduleConfig.contains("use_gold_linker") + cpp.linkerVariant: "gold" + } + Properties { + condition: !moduleConfig.contains("use_gold_linker") && qbs.toolchain.contains("gcc") + cpp.linkerVariant: original + } + + cpp.cxxLanguageVersion: Utilities.versionCompare(version, "6.0.0") >= 0 + ? "c++17" + : 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 not derived + // from the build variant "debug" + if (!qbs.enableDebugCode) + defines.push("QT_NO_DEBUG"); + if (!enableKeywords) + defines.push("QT_NO_KEYWORDS"); + if (qbs.targetOS.containsAny(["ios", "tvos"])) { + defines = defines.concat(["DARWIN_NO_CARBON", "QT_NO_CORESERVICES", "QT_NO_PRINTER", + "QT_NO_PRINTDIALOG"]); + if (Utilities.versionCompare(version, "5.6.0") < 0) + defines.push("main=qtmn"); + } + return defines; + } + cpp.driverFlags: { + var flags = []; + if (qbs.toolchain.contains("gcc")) { + if (config.contains("sanitize_address")) + flags.push("-fsanitize=address"); + if (config.contains("sanitize_undefined")) + flags.push("-fsanitize=undefined"); + if (config.contains("sanitize_thread")) + flags.push("-fsanitize=thread"); + if (config.contains("sanitize_memory")) + flags.push("-fsanitize=memory"); + } + return flags; + } + cpp.includePaths: { + var paths = @includes@; + paths.push(mkspecPath, generatedHeadersDir); + return paths; + } + cpp.libraryPaths: { + var libPaths = [libPath]; + if (staticBuild && pluginPath) + libPaths.push(pluginPath + "/platforms"); + libPaths = libPaths.concat(coreLibPaths); + return libPaths; + } + cpp.staticLibraries: { + var libs = []; + if (qbs.targetOS.contains('windows') && !product.consoleApplication) { + libs = libs.concat(qtBuildVariant === "debug" + ? @entryPointLibsDebug@ : @entryPointLibsRelease@); + } + libs = libs.concat(staticLibs); + return libs; + } + cpp.dynamicLibraries: dynamicLibs + cpp.linkerFlags: coreLinkerFlags + cpp.frameworkPaths: coreFrameworkPaths.concat(frameworkBuild ? [libPath] : []) + cpp.frameworks: { + var frameworks = coreFrameworks + if (frameworkBuild) + frameworks.push(libNameForLinker); + if (qbs.targetOS.contains('ios') && staticBuild) + frameworks = frameworks.concat(["Foundation", "CoreFoundation"]); + if (frameworks.length === 0) + return undefined; + return frameworks; + } + cpp.rpaths: qbs.targetOS.contains('linux') ? [libPath] : undefined + cpp.runtimeLibrary: qbs.toolchain.contains("msvc") + ? config.contains("static_runtime") ? "static" : "dynamic" + : original + cpp.positionIndependentCode: versionMajor >= 5 ? true : undefined + cpp.cxxFlags: { + var flags = []; + if (qbs.toolchain.contains('msvc')) { + if (versionMajor < 5) + flags.push('/Zc:wchar_t-'); + } + + return flags; + } + cpp.cxxStandardLibrary: { + if (qbs.targetOS.contains('darwin') && qbs.toolchain.contains('clang') + && config.contains('c++11')) + return "libc++"; + return original; + } + 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.windowsApiAdditionalPartitions: mkspecPath.startsWith("winrt-") ? ["phone"] : undefined + cpp.requireAppContainer: mkspecName.startsWith("winrt-") + + additionalProductTypes: ["qm", "qt.core.metatypes"] + + validate: { + var validator = new ModUtils.PropertyValidator("Qt.core"); + validator.setRequiredProperty("binPath", binPath); + validator.setRequiredProperty("incPath", incPath); + validator.setRequiredProperty("libPath", libPath); + validator.setRequiredProperty("mkspecPath", mkspecPath); + validator.setRequiredProperty("version", version); + validator.setRequiredProperty("config", config); + validator.setRequiredProperty("qtConfig", qtConfig); + validator.setRequiredProperty("versionMajor", versionMajor); + validator.setRequiredProperty("versionMinor", versionMinor); + validator.setRequiredProperty("versionPatch", versionPatch); + + if (!staticBuild) + validator.setRequiredProperty("pluginPath", pluginPath); + + // Allow custom version suffix since some distributions might want to do this, + // but otherwise the version must start with a valid 3-component string + validator.addVersionValidator("version", version, 3, 3, true); + validator.addRangeValidator("versionMajor", versionMajor, 1); + validator.addRangeValidator("versionMinor", versionMinor, 0); + validator.addRangeValidator("versionPatch", versionPatch, 0); + + validator.addCustomValidator("availableBuildVariants", availableBuildVariants, function (v) { + return v.length > 0; + }, "the Qt installation supports no build variants"); + + validator.addCustomValidator("qtBuildVariant", qtBuildVariant, function (variant) { + return availableBuildVariants.contains(variant); + }, "'" + qtBuildVariant + "' is not supported by this Qt installation"); + + validator.addCustomValidator("qtBuildVariant", qtBuildVariant, function (variant) { + return variant === qbs.buildVariant || !qbs.toolchain.contains("msvc"); + }, " is '" + qtBuildVariant + "', but qbs.buildVariant is '" + qbs.buildVariant + + "', which is not allowed when using MSVC"); + + validator.addFileNameValidator("resourceFileBaseName", resourceFileBaseName); + + validator.validate(); + } + + FileTagger { + patterns: ["*.qrc"] + fileTags: ["qrc"] + } + + FileTagger { + patterns: ["*.ts"] + fileTags: ["ts"] + } + + FileTagger { + patterns: ["*.qdoc", "*.qdocinc"] + fileTags: ["qdoc"] + } + + FileTagger { + patterns: ["*.qdocconf"] + fileTags: ["qdocconf"] + } + + FileTagger { + patterns: ["*.qhp"] + fileTags: ["qhp"] + } + + property bool combineMocOutput: cpp.combineCxxSources + property bool enableBigResources: false + + Rule { + name: "QtCoreMocRuleCpp" + property string cppInput: cpp.combineCxxSources ? "cpp.combine" : "cpp" + property string objcppInput: cpp.combineObjcxxSources ? "objcpp.combine" : "objcpp" + inputs: [objcppInput, cppInput] + auxiliaryInputs: "qt_plugin_metadata" + excludedInputs: "unmocable" + outputFileTags: ["hpp", "unmocable", "qt.core.metatypes.in"] + outputArtifacts: Moc.outputArtifacts.apply(Moc, arguments) + prepare: Moc.commands.apply(Moc, arguments) + } + Rule { + name: "QtCoreMocRuleHpp" + inputs: "hpp" + auxiliaryInputs: ["qt_plugin_metadata", "cpp", "objcpp"]; + excludedInputs: "unmocable" + outputFileTags: ["hpp", "cpp", "moc_cpp", "unmocable", "qt.core.metatypes.in"] + outputArtifacts: Moc.outputArtifacts.apply(Moc, arguments) + prepare: Moc.commands.apply(Moc, arguments) + } + + Rule { + multiplex: true + inputs: ["moc_cpp"] + Artifact { + filePath: "amalgamated_moc_" + product.targetName + ".cpp" + fileTags: ["cpp", "unmocable"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + ModUtils.mergeCFiles(inputs["moc_cpp"], output.filePath); + }; + return [cmd]; + } + } + + Rule { + multiplex: true + inputs: "qt.core.metatypes.in" + Artifact { + filePath: product.targetName.toLowerCase() + "_metatypes.json" + fileTags: "qt.core.metatypes" + qbs.install: product.Qt.core.metaTypesInstallDir + qbs.installDir: product.Qt.core.metaTypesInstallDir + } + prepare: { + var inputFilePaths = inputs["qt.core.metatypes.in"].map(function(a) { + return a.filePath; + }); + var cmd = new Command(Moc.fullPath(product), + ["--collect-json", "-o", output.filePath].concat(inputFilePaths)); + cmd.description = "generating " + output.fileName; + cmd.highlight = "codegen"; + return cmd; + } + } + + property path resourceSourceBase + property string resourcePrefix: "/" + property string resourceFileBaseName: product.targetName + Rule { + multiplex: true + inputs: ["qt.core.resource_data"] + Artifact { + filePath: product.Qt.core.resourceFileBaseName + ".qrc" + fileTags: ["qrc"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { + var doc = new Xml.DomDocument("RCC"); + + var rccNode = doc.createElement("RCC"); + rccNode.setAttribute("version", "1.0"); + doc.appendChild(rccNode); + + var inputsByPrefix = {} + for (var i = 0; i < inputs["qt.core.resource_data"].length; ++i) { + var inp = inputs["qt.core.resource_data"][i]; + var prefix = inp.Qt.core.resourcePrefix; + var inputsList = inputsByPrefix[prefix] || []; + inputsList.push(inp); + inputsByPrefix[prefix] = inputsList; + } + + for (var prefix in inputsByPrefix) { + var qresourceNode = doc.createElement("qresource"); + qresourceNode.setAttribute("prefix", prefix); + rccNode.appendChild(qresourceNode); + + for (var i = 0; i < inputsByPrefix[prefix].length; ++i) { + var inp = inputsByPrefix[prefix][i]; + var fullResPath = inp.filePath; + var baseDir = inp.Qt.core.resourceSourceBase; + var resAlias = baseDir + ? FileInfo.relativePath(baseDir, fullResPath) : inp.fileName; + + var fileNode = doc.createElement("file"); + fileNode.setAttribute("alias", resAlias); + qresourceNode.appendChild(fileNode); + + var fileTextNode = doc.createTextNode(fullResPath); + fileNode.appendChild(fileTextNode); + } + } + + doc.save(output.filePath, 4); + }; + return [cmd]; + } + } + + Rule { + inputs: ["qrc"] + outputFileTags: ["cpp", "cpp_intermediate_object"] + outputArtifacts: { + var artifact = { + filePath: "qrc_" + input.completeBaseName + ".cpp", + fileTags: ["cpp"] + }; + if (input.Qt.core.enableBigResources) + artifact.fileTags.push("cpp_intermediate_object"); + return [artifact]; + } + prepare: { + var args = [input.filePath, + "-name", FileInfo.completeBaseName(input.filePath), + "-o", output.filePath]; + if (input.Qt.core.enableBigResources) + args.push("-pass", "1"); + var cmd = new Command(product.Qt.core.binPath + '/rcc', args); + cmd.description = "rcc " + + (input.Qt.core.enableBigResources ? "(pass 1) " : "") + + input.fileName; + cmd.highlight = 'codegen'; + return cmd; + } + } + + Rule { + inputs: ["intermediate_obj"] + Artifact { + filePath: input.completeBaseName + ".2.o" + fileTags: ["obj"] + } + prepare: { + function findChild(artifact, predicate) { + var children = artifact.children; + var len = children.length; + for (var i = 0; i < len; ++i) { + var child = children[i]; + if (predicate(child)) + return child; + child = findChild(child, predicate); + if (child) + return child; + } + return undefined; + } + var qrcArtifact = findChild(input, function(c) { return c.fileTags.contains("qrc"); }); + var cppArtifact = findChild(input, function(c) { return c.fileTags.contains("cpp"); }); + var cmd = new Command(product.Qt.core.binPath + '/rcc', + [qrcArtifact.filePath, + "-temp", input.filePath, + "-name", FileInfo.completeBaseName(input.filePath), + "-o", output.filePath, + "-pass", "2"]); + cmd.description = "rcc (pass 2) " + qrcArtifact.fileName; + cmd.highlight = 'codegen'; + return cmd; + } + } + + Rule { + inputs: ["ts"] + multiplex: lreleaseMultiplexMode + + Artifact { + filePath: FileInfo.joinPaths(product.Qt.core.qmDir, + (product.Qt.core.lreleaseMultiplexMode + ? product.Qt.core.qmBaseName + : input.baseName) + ".qm") + fileTags: ["qm"] + } + + prepare: { + var inputFilePaths; + if (product.Qt.core.lreleaseMultiplexMode) + inputFilePaths = inputs["ts"].map(function(artifact) { return artifact.filePath; }); + else + inputFilePaths = [input.filePath]; + var args = ['-silent', '-qm', output.filePath].concat(inputFilePaths); + var cmd = new Command(product.Qt.core.binPath + '/' + + product.Qt.core.lreleaseName, args); + cmd.description = 'Creating ' + output.fileName; + cmd.highlight = 'filegen'; + return cmd; + } + } + + Rule { + inputs: "qdocconf-main" + explicitlyDependsOn: ["qdoc", "qdocconf"] + + outputFileTags: ModUtils.allFileTags(Qdoc.qdocFileTaggers()) + outputArtifacts: Qdoc.outputArtifacts(product, input) + + prepare: { + var outputDir = product.Qt.core.qdocOutputDir; + var args = Qdoc.qdocArgs(product, input, outputDir); + var cmd = new Command(product.Qt.core.binPath + '/' + product.Qt.core.qdocName, args); + cmd.description = 'qdoc ' + input.fileName; + cmd.highlight = 'filegen'; + cmd.environment = product.Qt.core.qdocEnvironment; + cmd.environment.push("OUTDIR=" + outputDir); // Qt 4 replacement for -outputdir + return cmd; + } + } + + Rule { + inputs: "qhp" + auxiliaryInputs: ModUtils.allFileTags(Qdoc.qdocFileTaggers()) + .filter(function(tag) { return tag !== "qhp"; }) + + Artifact { + filePath: input.completeBaseName + ".qch" + fileTags: ["qch"] + } + + prepare: { + var args = [input.filePath]; + args = args.concat(product.Qt.core.helpGeneratorArgs); + args.push("-o"); + args.push(output.filePath); + var cmd = new Command(product.Qt.core.binPath + "/qhelpgenerator", args); + cmd.description = 'qhelpgenerator ' + input.fileName; + cmd.highlight = 'filegen'; + cmd.stdoutFilterFunction = function(output) { + return ""; + }; + return cmd; + } + } + + @additionalContent@ +} diff --git a/share/qbs/module-providers/Qt/templates/dbus.js b/share/qbs/module-providers/Qt/templates/dbus.js new file mode 100644 index 00000000..0674bf68 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/dbus.js @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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"); + +function outputFileName(input, suffix) +{ + var parts = input.fileName.split('.').filter(function(s) { return s.length > 0; }); + if (parts.length === 0) + throw "Cannot run qdbusxml2cpp on '" + input.filePath + "': Unsuitable file name."; + var outputBaseName = parts.length === 1 ? parts[0] : parts[parts.length - 2]; + return outputBaseName.toLowerCase() + suffix; +} + +function createCommands(product, input, outputs, option) +{ + var exe = ModUtils.moduleProperty(product, "binPath") + '/' + + ModUtils.moduleProperty(product, "xml2cppName"); + var hppOutput = outputs["hpp"][0]; + var hppArgs = ModUtils.moduleProperty(product, "xml2CppHeaderFlags"); + hppArgs.push(option, hppOutput.fileName + ':', input.filePath); // Can't use filePath on Windows + var hppCmd = new Command(exe, hppArgs) + hppCmd.description = "qdbusxml2cpp " + input.fileName + " -> " + hppOutput.fileName; + hppCmd.highlight = "codegen"; + hppCmd.workingDirectory = FileInfo.path(hppOutput.filePath); + var cppOutput = outputs["cpp"][0]; + var cppArgs = ModUtils.moduleProperty(product, "xml2CppSourceFlags"); + cppArgs.push("-i", hppOutput.filePath, option, ':' + cppOutput.fileName, input.filePath); + var cppCmd = new Command(exe, cppArgs) + cppCmd.description = "qdbusxml2cpp " + input.fileName + " -> " + cppOutput.fileName; + cppCmd.highlight = "codegen"; + cppCmd.workingDirectory = FileInfo.path(cppOutput.filePath); + return [hppCmd, cppCmd]; +} diff --git a/share/qbs/module-providers/Qt/templates/dbus.qbs b/share/qbs/module-providers/Qt/templates/dbus.qbs new file mode 100644 index 00000000..6556e2c9 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/dbus.qbs @@ -0,0 +1,74 @@ +import qbs.FileInfo +import qbs.ModUtils +import "../QtModule.qbs" as QtModule +import "dbus.js" as DBus + +QtModule { + qtModuleName: "DBus" + + property string xml2cppName: "qdbusxml2cpp" + property stringList xml2CppHeaderFlags: [] + property stringList xml2CppSourceFlags: [] + + Rule { + inputs: ["qt.dbus.adaptor"] + + Artifact { + filePath: FileInfo.joinPaths(input.moduleProperty("Qt.core", "generatedHeadersDir"), + DBus.outputFileName(input, "_adaptor.h")) + fileTags: ["hpp"] + } + Artifact { + filePath: DBus.outputFileName(input, "_adaptor.cpp") + fileTags: ["cpp"] + } + + prepare: { + return DBus.createCommands(product, input, outputs, "-a"); + } + } + + Rule { + inputs: ["qt.dbus.interface"] + + Artifact { + filePath: FileInfo.joinPaths(input.moduleProperty("Qt.core", "generatedHeadersDir"), + DBus.outputFileName(input, "_interface.h")) + fileTags: ["hpp"] + } + Artifact { + filePath: DBus.outputFileName(input, "_interface.cpp") + fileTags: ["cpp"] + } + + prepare: { + return DBus.createCommands(product, input, outputs, "-p"); + } + } + + architectures: @archs@ + targetPlatform: @targetPlatform@ + 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@ + pluginTypes: @pluginTypes@ + moduleConfig: @moduleConfig@ + + cpp.defines: @defines@ + cpp.includePaths: @includes@ + cpp.libraryPaths: @libraryPaths@ + + @additionalContent@ +} + diff --git a/share/qbs/module-providers/Qt/templates/gui.qbs b/share/qbs/module-providers/Qt/templates/gui.qbs new file mode 100644 index 00000000..a3c42717 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/gui.qbs @@ -0,0 +1,71 @@ +import qbs.FileInfo +import qbs.ModUtils +import qbs.Utilities +import '../QtModule.qbs' as QtModule + +QtModule { + qtModuleName: "Gui" + + property string uicName: "uic" + + FileTagger { + patterns: ["*.ui"] + fileTags: ["ui"] + } + + Rule { + inputs: ["ui"] + + Artifact { + filePath: FileInfo.joinPaths(input.moduleProperty("Qt.core", "generatedHeadersDir"), + 'ui_' + input.completeBaseName + '.h') + fileTags: ["hpp"] + } + + prepare: { + var cmd = new Command(ModUtils.moduleProperty(product, "binPath") + '/' + + ModUtils.moduleProperty(product, "uicName"), + [input.filePath, '-o', output.filePath]) + cmd.description = 'uic ' + input.fileName; + cmd.highlight = 'codegen'; + return cmd; + } + } + + property string defaultQpaPlugin: @defaultQpaPlugin@ + architectures: @archs@ + targetPlatform: @targetPlatform@ + 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@ + pluginTypes: @pluginTypes@ + + cpp.entryPoint: qbs.targetOS.containsAny(["ios", "tvos"]) + && Utilities.versionCompare(version, "5.6.0") >= 0 + ? "_qt_main_wrapper" + : undefined + + cpp.defines: @defines@ + cpp.includePaths: @includes@ + cpp.libraryPaths: @libraryPaths@ + + Properties { + condition: Qt.core.staticBuild && qbs.targetOS.contains("ios") + cpp.frameworks: base.concat(["UIKit", "QuartzCore", "CoreText", "CoreGraphics", + "Foundation", "CoreFoundation", "AudioToolbox"]) + } + cpp.frameworks: base + @additionalContent@ +} + diff --git a/share/qbs/module-providers/Qt/templates/moc.js b/share/qbs/module-providers/Qt/templates/moc.js new file mode 100644 index 00000000..92983e4f --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/moc.js @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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 ModUtils = require("qbs.ModUtils"); + +function args(product, input, outputs) +{ + var defines = product.cpp.compilerDefinesByLanguage; + if (input.fileTags.contains("objcpp")) + defines = ModUtils.flattenDictionary(defines["objcpp"]) || []; + else if (input.fileTags.contains("cpp") || input.fileTags.contains("hpp")) + defines = ModUtils.flattenDictionary(defines["cpp"]) || []; + else + defines = []; + defines = defines.uniqueConcat(product.cpp.platformDefines); + defines = defines.uniqueConcat(input.cpp.defines); + var includePaths = input.cpp.includePaths; + includePaths = includePaths.uniqueConcat(input.cpp.systemIncludePaths); + var useCompilerPaths = product.Qt.core.versionMajor >= 5; + if (useCompilerPaths) { + includePaths = includePaths.uniqueConcat(input.cpp.compilerIncludePaths); + } + var frameworkPaths = product.cpp.frameworkPaths; + frameworkPaths = frameworkPaths.uniqueConcat( + product.cpp.systemFrameworkPaths); + if (useCompilerPaths) { + frameworkPaths = frameworkPaths.uniqueConcat( + product.cpp.compilerFrameworkPaths); + } + var pluginMetaData = product.Qt.core.pluginMetaData; + var args = []; + if (product.Qt.core._generateMetaTypesFile) + args.push("--output-json"); + var outputFileName; + for (tag in outputs) { + if (tag !== "qt.core.metatypes.in") { + outputFileName = outputs[tag][0].filePath; + break; + } + } + args = args.concat( + defines.map(function(item) { return '-D' + item; }), + includePaths.map(function(item) { return '-I' + item; }), + frameworkPaths.map(function(item) { return '-F' + item; }), + pluginMetaData.map(function(item) { return '-M' + item; }), + product.Qt.core.mocFlags, + '-o', outputFileName, + input.filePath); + return args; +} + +function fullPath(product) +{ + return product.Qt.core.binPath + '/' + product.Qt.core.mocName; +} + +function outputArtifacts(project, product, inputs, input) +{ + var mocInfo = QtMocScanner.apply(input); + if (!mocInfo.hasQObjectMacro) + return []; + var artifact = { fileTags: ["unmocable"] }; + if (mocInfo.hasPluginMetaDataMacro) + artifact.explicitlyDependsOn = ["qt_plugin_metadata"]; + if (input.fileTags.contains("hpp")) { + artifact.filePath = input.Qt.core.generatedHeadersDir + + "/moc_" + input.completeBaseName + ".cpp"; + var amalgamate = input.Qt.core.combineMocOutput; + artifact.fileTags.push(mocInfo.mustCompile ? (amalgamate ? "moc_cpp" : "cpp") : "hpp"); + } else { + artifact.filePath = input.Qt.core.generatedHeadersDir + '/' + + input.completeBaseName + ".moc"; + artifact.fileTags.push("hpp"); + } + var artifacts = [artifact]; + if (product.Qt.core._generateMetaTypesFile) + artifacts.push({filePath: artifact.filePath + ".json", fileTags: "qt.core.metatypes.in"}); + return artifacts; +} + +function commands(project, product, inputs, outputs, input, output) +{ + var cmd = new Command(fullPath(product), args(product, input, outputs)); + cmd.description = 'moc ' + input.fileName; + cmd.highlight = 'codegen'; + return cmd; +} diff --git a/share/qbs/module-providers/Qt/templates/module.qbs b/share/qbs/module-providers/Qt/templates/module.qbs new file mode 100644 index 00000000..b09f79a8 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/module.qbs @@ -0,0 +1,30 @@ +import '../QtModule.qbs' as QtModule + +QtModule { + qtModuleName: @name@ + Depends { name: "Qt"; submodules: @dependencies@} + + architectures: @archs@ + targetPlatform: @targetPlatform@ + hasLibrary: @has_library@ + 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@ + pluginTypes: @pluginTypes@ + moduleConfig: @moduleConfig@ + cpp.defines: @defines@ + cpp.includePaths: @includes@ + cpp.libraryPaths: @libraryPaths@ + @additionalContent@ +} diff --git a/share/qbs/module-providers/Qt/templates/plugin.qbs b/share/qbs/module-providers/Qt/templates/plugin.qbs new file mode 100644 index 00000000..e73e2a4d --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/plugin.qbs @@ -0,0 +1,27 @@ +import '../QtPlugin.qbs' as QtPlugin + +QtPlugin { + qtModuleName: @name@ + Depends { name: "Qt"; submodules: @dependencies@} + + className: @className@ + architectures: @archs@ + targetPlatform: @targetPlatform@ + 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.libraryPaths: @libraryPaths@ + extendsModules: @extends@ + @additionalContent@ +} diff --git a/share/qbs/module-providers/Qt/templates/plugin_support.qbs b/share/qbs/module-providers/Qt/templates/plugin_support.qbs new file mode 100644 index 00000000..1de923f1 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/plugin_support.qbs @@ -0,0 +1,77 @@ +Module { + // Set by user. + property varList pluginsByType + property bool linkPlugins: product.type + && (product.type.contains("application") || product.type.contains("sharedlibrary")) + + // Set by Qt modules. + property stringList pluginTypes + + // Set by setup-qt. + readonly property var allPluginsByType: @allPluginsByType@ + readonly property stringList nonEssentialPlugins: @nonEssentialPlugins@ + + // Derived. + readonly property var defaultPluginsByType: { + var map = {}; + for (var i = 0; i < (pluginTypes || []).length; ++i) { + var pType = pluginTypes[i]; + map[pType] = (allPluginsByType[pType] || []).filter(function(p) { + return !nonEssentialPlugins.contains(p); }); + } + return map; + } + readonly property var effectivePluginsByType: { + var ppt = pluginsByType || []; + var eppt = {}; + for (var i = 0; i < ppt.length; ++i) { + var listEntry = ppt[i]; + for (var pluginType in listEntry) { + var newValue = listEntry[pluginType]; + if (!newValue) + newValue = []; + else if (typeof newValue == "string") + newValue = [newValue]; + if (!Array.isArray(newValue)) + throw "Invalid value '" + newValue + "' in Qt.plugin_support.pluginsByType"; + eppt[pluginType] = (eppt[pluginType] || []).uniqueConcat(newValue); + } + } + var dppt = defaultPluginsByType; + for (var pluginType in dppt) { + if (!eppt[pluginType]) + eppt[pluginType] = dppt[pluginType]; + } + return eppt; + } + readonly property stringList enabledPlugins: { + var list = []; + var eppt = effectivePluginsByType; + for (var t in eppt) + Array.prototype.push.apply(list, eppt[t]); + return list; + } + + validate: { + var ppt = pluginsByType; + if (!ppt) + return; + var appt = allPluginsByType; + for (var i = 0; i < ppt.length; ++i) { + for (var pluginType in ppt[i]) { + var requestedPlugins = ppt[i][pluginType]; + if (!requestedPlugins) + continue; + var availablePlugins = appt[pluginType] || []; + if (typeof requestedPlugins === "string") + requestedPlugins = [requestedPlugins]; + for (var j = 0; j < requestedPlugins.length; ++j) { + if (!availablePlugins.contains(requestedPlugins[j])) { + throw "Plugin '" + requestedPlugins[j] + "' of type '" + pluginType + + "' was requested, but is not available."; + } + } + } + } + } +} diff --git a/share/qbs/module-providers/Qt/templates/qdoc.js b/share/qbs/module-providers/Qt/templates/qdoc.js new file mode 100644 index 00000000..72c161c5 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/qdoc.js @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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 ModUtils = require("qbs.ModUtils"); + +function qdocArgs(product, input, outputDir) { + var args = [input.filePath]; + var qtVersion = ModUtils.moduleProperty(product, "versionMajor"); + if (qtVersion >= 5) { + args.push("-outputdir"); + args.push(outputDir); + } + + return args; +} + +var _qdocDefaultFileTag = "qdoc-output"; +function qdocFileTaggers() { + var t = _qdocDefaultFileTag; + return { + ".qhp": [t, "qhp"], + ".qhp.sha1": [t, "qhp-sha1"], + ".css": [t, "qdoc-css"], + ".html": [t, "qdoc-html"], + ".index": [t, "qdoc-index"], + ".png": [t, "qdoc-png"] + }; +} + +function outputArtifacts(product, input) { + var tracker = new ModUtils.BlackboxOutputArtifactTracker(); + tracker.hostOS = product.moduleProperty("qbs", "hostOS"); + tracker.shellPath = product.moduleProperty("qbs", "shellPath"); + tracker.defaultFileTags = [_qdocDefaultFileTag]; + tracker.fileTaggers = qdocFileTaggers(); + tracker.command = FileInfo.joinPaths(ModUtils.moduleProperty(product, "binPath"), + ModUtils.moduleProperty(product, "qdocName")); + tracker.commandArgsFunction = function (outputDirectory) { + return qdocArgs(product, input, outputDirectory); + }; + tracker.commandEnvironmentFunction = function (outputDirectory) { + var env = {}; + var qdocEnv = ModUtils.moduleProperty(product, "qdocEnvironment"); + for (var j = 0; j < qdocEnv.length; ++j) { + var e = qdocEnv[j]; + var idx = e.indexOf("="); + var name = e.slice(0, idx); + var value = e.slice(idx + 1, e.length); + env[name] = value; + } + env["OUTDIR"] = outputDirectory; // Qt 4 replacement for -outputdir + return env; + }; + return tracker.artifacts(ModUtils.moduleProperty(product, "qdocOutputDir")); +} diff --git a/share/qbs/module-providers/Qt/templates/qml.js b/share/qbs/module-providers/Qt/templates/qml.js new file mode 100644 index 00000000..df69034f --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/qml.js @@ -0,0 +1,69 @@ +var File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var Process = require("qbs.Process"); +var TextFile = require("qbs.TextFile"); + +function scannerData(scannerFilePath, qmlFiles, qmlPath) +{ + var p; + try { + p = new Process(); + p.exec(scannerFilePath, ["-qmlFiles"].concat(qmlFiles).concat(["-importPath", qmlPath]), + true); + return JSON.parse(p.readStdOut()); + } finally { + if (p) + p.close(); + } +} + +function getPrlRhs(line) +{ + return line.split('=')[1].trim(); +} + +function getLibsForPlugin(pluginData, buildVariant, targetOS, toolchain, qtLibDir) +{ + if (!pluginData.path) + return ""; + var prlFileName = ""; + if (!targetOS.contains("windows")) + prlFileName += "lib"; + prlFileName += pluginData.plugin; + if (buildVariant === "debug" && targetOS.contains("windows")) + prlFileName += "d"; + prlFileName += ".prl"; + var prlFilePath = FileInfo.joinPaths(pluginData.path, prlFileName); + if (!File.exists(prlFilePath)) { + console.warn("prl file for QML plugin '" + pluginData.plugin + "' not present at '" + + prlFilePath + "'. Linking may fail."); + return ""; + } + var prlFile = new TextFile(prlFilePath, TextFile.ReadOnly); + try { + var pluginLib; + var otherLibs = ""; + var line; + while (!prlFile.atEof()) { + line = prlFile.readLine().trim(); + if (!line) + continue; + if (line.startsWith("QMAKE_PRL_TARGET")) + pluginLib = FileInfo.joinPaths(pluginData.path, getPrlRhs(line)); + if (line.startsWith("QMAKE_PRL_LIBS = ")) { + var otherLibsLine = ' ' + getPrlRhs(line); + if (toolchain.contains("msvc")) { + otherLibsLine = otherLibsLine.replace(/ -L/g, " /LIBPATH:"); + otherLibsLine = otherLibsLine.replace(/-l([^ ]+)/g, "$1" + ".lib"); + } + otherLibsLine = otherLibsLine.replace(/\$\$\[QT_INSTALL_LIBS\]/g, qtLibDir); + otherLibs += otherLibsLine + '\n'; + } + } + if (!pluginLib) + throw "Malformed prl file '" + prlFilePath + "'."; + return pluginLib + ' ' + otherLibs; + } finally { + prlFile.close(); + } +} diff --git a/share/qbs/module-providers/Qt/templates/qml.qbs b/share/qbs/module-providers/Qt/templates/qml.qbs new file mode 100644 index 00000000..c6393764 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/qml.qbs @@ -0,0 +1,203 @@ +import qbs.TextFile +import '../QtModule.qbs' as QtModule +import "qml.js" as Qml + +QtModule { + qtModuleName: "Qml" + Depends { name: "Qt"; submodules: @dependencies@} + + property string qmlImportScannerName: "qmlimportscanner" + 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 + Properties { + condition: cachingEnabled + Qt.qmlcache.qmlCacheGenPath: qmlCacheGenPath || original + Qt.qmlcache.installDir: cacheFilesInstallDir || original + } + + property string cacheFilesInstallDir + + readonly property string pluginListFilePathDebug: product.buildDirectory + "/plugins.list.d" + readonly property string pluginListFilePathRelease: product.buildDirectory + "/plugins.list" + property bool linkPlugins: isStaticLibrary && Qt.plugin_support.linkPlugins + + hasLibrary: @has_library@ + architectures: @archs@ + targetPlatform: @targetPlatform@ + staticLibsDebug: (linkPlugins ? ['@' + pluginListFilePathDebug] : []).concat(@staticLibsDebug@) + staticLibsRelease: (linkPlugins ? ['@' + pluginListFilePathRelease] : []).concat(@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@ + pluginTypes: @pluginTypes@ + moduleConfig: @moduleConfig@ + cpp.defines: @defines@ + cpp.includePaths: @includes@ + cpp.libraryPaths: @libraryPaths@ + @additionalContent@ + + FileTagger { + patterns: ["*.qml"] + fileTags: ["qt.qml.qml"] + } + + FileTagger { + patterns: ["*.js"] + fileTags: ["qt.qml.js"] + } + + // Type registration + property string importName + property string importVersion: product.version + readonly property stringList _importVersionParts: (importVersion || "").split(".") + property string typesFileName: { + if (product.type && product.type.contains("application")) + return product.targetName + ".qmltypes"; + return "plugins.qmltypes"; + } + property string typesInstallDir + property stringList extraMetaTypesFiles + Properties { + condition: importName + Qt.core.generateMetaTypesFile: true + } + Qt.core.generateMetaTypesFile: original + Rule { + condition: importName + inputs: "qt.core.metatypes" + multiplex: true + explicitlyDependsOnFromDependencies: "qt.core.metatypes" + Artifact { + filePath: product.targetName.toLowerCase() + "_qmltyperegistrations.cpp" + fileTags: ["cpp", "unmocable"] + } + Artifact { + filePath: product.Qt.qml.typesFileName + fileTags: "qt.qml.types" + qbs.install: product.Qt.qml.typesInstallDir + qbs.installDir: product.Qt.qml.typesInstallDir + } + prepare: { + var versionParts = product.Qt.qml._importVersionParts; + var args = [ + "--generate-qmltypes=" + outputs["qt.qml.types"][0].filePath, + "--import-name=" + product.Qt.qml.importName, + "--major-version=" + versionParts[0], + "--minor-version=" + (versionParts.length > 1 ? versionParts[1] : "0") + ]; + var foreignTypes = product.Qt.qml.extraMetaTypesFiles || []; + var metaTypeArtifactsFromDeps = explicitlyDependsOn["qt.core.metatypes"] || []; + var filePathFromArtifact = function(a) { return a.filePath; }; + foreignTypes = foreignTypes.concat(metaTypeArtifactsFromDeps.map(filePathFromArtifact)); + if (foreignTypes.length > 0) + args.push("--foreign-types=" + foreignTypes.join(",")); + args.push("-o", outputs.cpp[0].filePath); + args = args.concat(inputs["qt.core.metatypes"].map(filePathFromArtifact)); + var cmd = new Command(product.Qt.core.binPath + "/qmltyperegistrar", args); + cmd.description = "running qmltyperegistrar"; + cmd.highlight = "codegen"; + return cmd; + } + } + + Rule { + condition: linkPlugins + multiplex: true + requiresInputs: false + inputs: ["qt.qml.qml"] + outputFileTags: ["cpp", "qt.qml.pluginlist"] + outputArtifacts: { + var list = []; + if (inputs["qt.qml.qml"]) + list.push({ filePath: "qml_plugin_import.cpp", fileTags: ["cpp"] }); + list.push({ + filePath: product.Qt.core.qtBuildVariant === "debug" + ? product.Qt.qml.pluginListFilePathDebug + : product.Qt.qml.pluginListFilePathRelease, + fileTags: ["qt.qml.pluginlist"] + }); + return list; + } + prepare: { + var cmd = new JavaScriptCommand(); + if (inputs["qt.qml.qml"]) + cmd.description = "Creating " + outputs["cpp"][0].fileName; + else + cmd.silent = true; + cmd.sourceCode = function() { + var qmlInputs = inputs["qt.qml.qml"]; + if (!qmlInputs) + qmlInputs = []; + var scannerData = Qml.scannerData(product.Qt.qml.qmlImportScannerFilePath, + qmlInputs.map(function(inp) { return inp.filePath; }), + product.Qt.qml.qmlPath); + var cppFile; + var listFile; + try { + if (qmlInputs.length > 0) + cppFile = new TextFile(outputs["cpp"][0].filePath, TextFile.WriteOnly); + listFile = new TextFile(outputs["qt.qml.pluginlist"][0].filePath, + TextFile.WriteOnly); + if (cppFile) + cppFile.writeLine("#include "); + var plugins = { }; + for (var p in scannerData) { + var plugin = scannerData[p].plugin; + if (!plugin || plugins[plugin]) + continue; + plugins[plugin] = true; + var className = scannerData[p].classname; + if (!className) { + throw "QML plugin '" + plugin + "' is missing a classname entry. " + + "Please add one to the qmldir file."; + } + if (cppFile) + cppFile.writeLine("Q_IMPORT_PLUGIN(" + className + ")"); + var libs = Qml.getLibsForPlugin(scannerData[p], + product.Qt.core.qtBuildVariant, + product.qbs.targetOS, + product.qbs.toolchain, + product.Qt.core.libPath); + listFile.write(libs + ' '); + } + } finally { + if (cppFile) + cppFile.close(); + if (listFile) + listFile.close(); + }; + }; + return [cmd]; + } + } + + validate: { + if (importName) { + if (!importVersion) + throw "Qt.qml.importVersion must be set if Qt.qml.importName is set."; + var isInt = function(value) { + return !isNaN(value) && parseInt(Number(value)) == value + && !isNaN(parseInt(value, 10)); + } + if (!isInt(_importVersionParts[0]) + || (_importVersionParts.length > 1 && !isInt(_importVersionParts[1]))) { + throw "Qt.qml.importVersion must be of the form x.y, where x and y are integers."; + } + + } + } +} diff --git a/share/qbs/module-providers/Qt/templates/qmlcache.qbs b/share/qbs/module-providers/Qt/templates/qmlcache.qbs new file mode 100644 index 00000000..9111eb50 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/qmlcache.qbs @@ -0,0 +1,85 @@ +import qbs.File +import qbs.FileInfo +import qbs.Process +import qbs.Utilities + +Module { + additionalProductTypes: ["qt.qml.qmlc", "qt.qml.jsc"] + validate: { + if (!qmlcachegenProbe.found) + throw "qmlcachegen unsupported for this target"; + } + property string qmlCacheGenPath: FileInfo.joinPaths(Qt.core.binPath, "qmlcachegen") + + (qbs.hostOS.contains("windows") ? ".exe" : "") + property bool supportsAllArchitectures: Utilities.versionCompare(Qt.core.version, "5.11") >= 0 + property string installDir + + readonly property stringList _targetArgs: { + if (supportsAllArchitectures) + return []; + 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 + + property string arch: qbs.architecture + property string _qmlCacheGenPath: qmlCacheGenPath + property stringList targetArgs: _targetArgs + property bool _supportsAllArchitectures: supportsAllArchitectures + + configure: { + if (_supportsAllArchitectures) { + found = File.exists(_qmlCacheGenPath); + return; + } + 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 '" + arch + "'."; + 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/share/qbs/module-providers/Qt/templates/quick.js b/share/qbs/module-providers/Qt/templates/quick.js new file mode 100644 index 00000000..4f3da2fb --- /dev/null +++ b/share/qbs/module-providers/Qt/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(/\//g, '_'); + 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/share/qbs/module-providers/Qt/templates/quick.qbs b/share/qbs/module-providers/Qt/templates/quick.qbs new file mode 100644 index 00000000..bf04fe86 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/quick.qbs @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** 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.File +import qbs.FileInfo +import qbs.Process +import qbs.TextFile +import qbs.Utilities +import '../QtModule.qbs' as QtModule +import 'quick.js' as QC + +QtModule { + qtModuleName: @name@ + Depends { name: "Qt"; submodules: @dependencies@ } + + hasLibrary: @has_library@ + architectures: @archs@ + targetPlatform: @targetPlatform@ + 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@ + pluginTypes: @pluginTypes@ + moduleConfig: @moduleConfig@ + cpp.defines: @defines@ + cpp.includePaths: @includes@ + cpp.libraryPaths: @libraryPaths@ + @additionalContent@ + + readonly property bool _compilerIsQmlCacheGen: Utilities.versionCompare(Qt.core.version, + "5.11") >= 0 + readonly property string _generatedLoaderFileName: _compilerIsQmlCacheGen + ? "qmlcache_loader.cpp" + : "qtquickcompiler_loader.cpp" + property string compilerBaseName: (_compilerIsQmlCacheGen ? "qmlcachegen" : "qtquickcompiler") + property string compilerFilePath: FileInfo.joinPaths(Qt.core.binPath, + compilerBaseName + product.cpp.executableSuffix) + property bool compilerAvailable: File.exists(compilerFilePath); + property bool useCompiler: compilerAvailable && !_compilerIsQmlCacheGen + + Scanner { + condition: useCompiler + inputs: 'qt.quick.qrc' + searchPaths: [FileInfo.path(input.filePath)] + scan: QC.scanQrc(input.filePath) + } + + FileTagger { + condition: useCompiler + patterns: "*.qrc" + fileTags: ["qt.quick.qrc"] + priority: 100 + } + + Rule { + condition: useCompiler + 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: useCompiler + 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: product.Qt.quick._generatedLoaderFileName, + 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 = product.Qt.quick.compilerFilePath; + var useCacheGen = product.Qt.quick._compilerIsQmlCacheGen; + 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); + var args = ["--filter-resource-file", + info.qrcFilePath]; + if (useCacheGen) + args.push("-o"); + args.push(findOutput(info.newQrcFileName).filePath); + cmd = new Command(qmlCompiler, args); + cmd.description = "generating " + info.newQrcFileName; + cmds.push(cmd); + } else { + loaderFlags.push("--resource-file-mapping=" + info.qrcFilePath); + } + info.qmlJsFiles.forEach(function (qmlJsFile) { + var args = ["--resource=" + info.qrcFilePath, qmlJsFile.input]; + if (useCacheGen) + args.push("-o"); + args.push(findOutput(qmlJsFile.output).filePath); + cmd = new Command(qmlCompiler, args); + cmd.description = "generating " + qmlJsFile.output; + cmd.workingDirectory = FileInfo.path(info.qrcFilePath); + cmds.push(cmd); + }); + }); + + var args = loaderFlags.concat(infos.map(function (info) { return info.qrcFilePath; })); + if (useCacheGen) + args.push("-o"); + args.push(findOutput(product.Qt.quick._generatedLoaderFileName).filePath); + cmd = new Command(qmlCompiler, args); + cmd.description = "generating loader source"; + cmds.push(cmd); + return cmds; + } + } +} diff --git a/share/qbs/module-providers/Qt/templates/scxml.qbs b/share/qbs/module-providers/Qt/templates/scxml.qbs new file mode 100644 index 00000000..7125ec53 --- /dev/null +++ b/share/qbs/module-providers/Qt/templates/scxml.qbs @@ -0,0 +1,80 @@ +import qbs.FileInfo +import qbs.Utilities +import "../QtModule.qbs" as QtModule + +QtModule { + qtModuleName: "Scxml" + + property string qscxmlcName: "qscxmlc" + property string className + property string namespace + property bool generateStateMethods + property stringList additionalCompilerFlags + + Rule { + inputs: ["qt.scxml.compilable"] + + Artifact { + filePath: FileInfo.joinPaths(input.moduleProperty("Qt.core", "generatedHeadersDir"), + input.baseName + ".h") + fileTags: ["hpp", "unmocable"] + } + Artifact { + filePath: input.baseName + ".cpp" + fileTags: ["cpp", "unmocable"] + } + + prepare: { + var compilerName = product.moduleProperty("Qt.scxml", "qscxmlcName"); + var compilerPath = FileInfo.joinPaths(input.moduleProperty("Qt.core", "binPath"), + compilerName); + var args = ["--header", outputs["hpp"][0].filePath, + "--impl", outputs["cpp"][0].filePath]; + var cxx98 = input.moduleProperty("cpp", "cxxLanguageVersion") === "c++98"; + if (cxx98) + args.push("-no-c++11"); + var className = input.moduleProperty("Qt.scxml", "className"); + if (className) + args.push("--classname", className); + var namespace = input.moduleProperty("Qt.scxml", "namespace"); + if (namespace) + args.push("--namespace", namespace); + if (input.Qt.scxml.generateStateMethods + && Utilities.versionCompare(product.Qt.scxml.version, "5.9") >= 0) { + args.push("--statemethods"); + } + if (input.Qt.scxml.additionalCompilerFlags) + args = args.concat(input.Qt.scxml.additionalCompilerFlags); + args.push(input.filePath); + var cmd = new Command(compilerPath, args); + cmd.description = "compiling " + input.fileName; + cmd.highlight = "codegen"; + return [cmd]; + } + } + + architectures: @archs@ + targetPlatform: @targetPlatform@ + 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@ + pluginTypes: @pluginTypes@ + moduleConfig: @moduleConfig@ + + cpp.defines: @defines@ + cpp.includePaths: @includes@ + cpp.libraryPaths: @libraryPaths@ + + @additionalContent@ +} diff --git a/share/qbs/module-providers/__fallback/fallback.qbs b/share/qbs/module-providers/__fallback/fallback.qbs new file mode 100644 index 00000000..632d1aa7 --- /dev/null +++ b/share/qbs/module-providers/__fallback/fallback.qbs @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +import qbs +import qbs.FileInfo +import qbs.Probes + +Module { + Depends { name: "cpp" } + Depends { name: "pkgconfig"; required: false } + + property string theName: FileInfo.completeBaseName(filePath) + + Probes.PkgConfigProbe { + id: pkgConfigProbe + condition: pkgconfig.present + sysroot: pkgconfig.sysroot + name: theName + executable: pkgconfig.executableFilePath + libDirs: pkgconfig.libDirs + forStaticBuild: pkgconfig.staticMode + } + + Properties { + condition: pkgConfigProbe.found + version: pkgConfigProbe.modversion + cpp.dynamicLibraries: pkgConfigProbe.libraries + cpp.libraryPaths: pkgConfigProbe.libraryPaths + cpp.includePaths: pkgConfigProbe.includePaths + cpp.defines: pkgConfigProbe.defines + cpp.driverLinkerFlags: pkgConfigProbe.linkerFlags + cpp.commonCompilerFlags: pkgConfigProbe.compilerFlags + } + + validate: { + if (!pkgConfigProbe.found) { + throw "Dependency '" + theName + "' not found for product '" + product.name + "'. " + + "Locating a package of this name via pkg-config also failed."; + } + } +} diff --git a/share/qbs/module-providers/__fallback/provider.qbs b/share/qbs/module-providers/__fallback/provider.qbs new file mode 100644 index 00000000..72740f3c --- /dev/null +++ b/share/qbs/module-providers/__fallback/provider.qbs @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +import qbs.File +import qbs.FileInfo + +ModuleProvider { + relativeSearchPaths: { + console.debug("Running fallback provider for module '" + name + "'."); + var inputFilePath = FileInfo.joinPaths(path, "fallback.qbs"); + var outputDir = FileInfo.joinPaths(outputBaseDir, "modules", name.replace(".", "/")); + File.makePath(outputDir); + var outputFilePath = FileInfo.joinPaths(outputDir, name + ".qbs"); + File.copy(inputFilePath, outputFilePath); + return ""; + } +} diff --git a/share/qbs/modules/Android/android-utils.js b/share/qbs/modules/Android/android-utils.js new file mode 100644 index 00000000..f5684d00 --- /dev/null +++ b/share/qbs/modules/Android/android-utils.js @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); + +function availablePlatforms(baseDir) { + var re = /^android-([0-9]+)$/; + var platforms = File.directoryEntries(FileInfo.joinPaths(baseDir, "platforms"), + File.Dirs | File.NoDotAndDotDot); + var versions = []; + for (var i = 0; i < platforms.length; ++i) { + var match = platforms[i].match(re); + if (match !== null) { + versions.push(platforms[i]); + } + } + + versions.sort(function (a, b) { + return parseInt(a.match(re)[1], 10) - parseInt(b.match(re)[1], 10); + }); + return versions; +} diff --git a/share/qbs/modules/Android/ndk/ndk.qbs b/share/qbs/modules/Android/ndk/ndk.qbs new file mode 100644 index 00000000..39042c08 --- /dev/null +++ b/share/qbs/modules/Android/ndk/ndk.qbs @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Probes +import qbs.Utilities + +import "utils.js" as NdkUtils + +Module { + Probes.AndroidNdkProbe { + id: ndkProbe + environmentPaths: (ndkDir ? [ndkDir] : []).concat(base) + } + + version: ndkProbe.ndkVersion + + readonly property string abi: NdkUtils.androidAbi(qbs.architecture) + PropertyOptions { + name: "abi" + description: "Supported Android ABIs" + allowedValues: ["arm64-v8a", "armeabi-v7a", "x86", "x86_64"] + } + + // See https://developer.android.com/ndk/guides/cpp-support.html + property string appStl: "c++_shared" + + PropertyOptions { + name: "appStl" + description: "C++ Runtime Libraries." + allowedValues: ["c++_static", "c++_shared"] + } + + property string hostArch: ndkProbe.hostArch + property string ndkDir: ndkProbe.path + property string ndkSamplesDir: ndkProbe.samplesDir + property string platform: { + switch (abi) { + case "armeabi-v7a": + // case "x86": // x86 has broken wstring support + return "android-19"; + default: + return "android-21"; + } + } + + property int platformVersion: { + if (platform) { + var match = platform.match(/^android-([0-9]+)$/); + if (match !== null) { + return parseInt(match[1], 10); + } + } + } + + property stringList abis: { + var list = ["armeabi-v7a"]; + if (platformVersion >= 16) + list.push("x86"); + if (platformVersion >= 21) + list.push("arm64-v8a", "x86_64"); + return list; + } + + property string armMode: abi && abi.startsWith("armeabi") + ? (qbs.buildVariant === "debug" ? "arm" : "thumb") + : undefined; + PropertyOptions { + name: "armMode" + description: "Determines the instruction set for armeabi configurations." + allowedValues: ["arm", "thumb"] + } + + validate: { + if (!ndkDir) { + throw ModUtils.ModuleError("Could not find an Android NDK at any of the following " + + "locations:\n\t" + ndkProbe.candidatePaths.join("\n\t") + + "\nInstall the Android NDK to one of the above locations, " + + "or set the Android.ndk.ndkDir property or " + + "ANDROID_NDK_ROOT environment variable to a valid " + + "Android NDK location."); + } + + if (Utilities.versionCompare(version, "19") < 0) + throw ModUtils.ModuleError("Unsupported NDK version " + + Android.ndk.version + + ", please upgrade your NDK to r19+"); + + if (product.aggregate && !product.multiplexConfigurationId) + return; + var validator = new ModUtils.PropertyValidator("Android.ndk"); + validator.setRequiredProperty("abi", abi); + validator.setRequiredProperty("appStl", appStl); + validator.setRequiredProperty("hostArch", hostArch); + validator.setRequiredProperty("platform", platform); + return validator.validate(); + } +} diff --git a/share/qbs/modules/Android/ndk/utils.js b/share/qbs/modules/Android/ndk/utils.js new file mode 100644 index 00000000..3605df31 --- /dev/null +++ b/share/qbs/modules/Android/ndk/utils.js @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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 Utilities = require("qbs.Utilities") + +function abiNameToDirName(abiName) { + switch (abiName) { + case "armeabi": + case "armeabi-v7a": + return "arm"; + case "arm64-v8a": + return "arm64"; + default: + return abiName; + } +} + +function androidAbi(arch) { + switch (arch) { + case "arm64": + return "arm64-v8a"; + case "armv5": + case "armv5te": + return "armeabi"; + case "armv7": + case "armv7a": + return "armeabi-v7a"; + default: + return arch; + } +} + +function commonCompilerFlags(toolchain, buildVariant, ndk) { + var flags = ["-Werror=format-security"]; + + var abi = ndk.abi; + var armMode = ndk.armMode; + if (abi === "arm64-v8a") + flags.push("-fpic"); + + if (abi === "armeabi" || abi === "armeabi-v7a") { + flags.push("-fpic"); + + if (abi === "armeabi") + flags.push("-mtune=xscale", "-msoft-float"); + + if (abi === "armeabi-v7a") { + flags.push("-mfpu=vfpv3-d16"); + flags.push("-mfloat-abi=softfp"); + } + } + + if (abi === "x86" || abi === "x86_64") + flags.push("-fPIC"); + + flags.push("-fno-limit-debug-info"); + + if (armMode) + flags.push("-m" + armMode); + + // https://github.com/android-ndk/ndk/issues/884 is fixed in r21 + if (ndk.version < 21) + flags.push("-fno-addrsig"); + + // https://github.com/android/ndk/issues/635 is fixed in api 24 + if (abi === "x86" && ndk.platformVersion < 24) + flags.push("-mstackrealign"); + + return flags; +} + +function commonLinkerFlags(abi) { + return ["-z", "noexecstack", "-z", "relro", "-z", "now", "--build-id=sha1", "--gc-sections" ]; +} + +function stlFilePath(path, ndk, suffix) { + return path + ndk.appStl.slice(0, ndk.appStl.indexOf('_')) + suffix + "." + ndk.platformVersion; +} diff --git a/share/qbs/modules/Android/sdk/sdk.qbs b/share/qbs/modules/Android/sdk/sdk.qbs new file mode 100644 index 00000000..31f3ed46 --- /dev/null +++ b/share/qbs/modules/Android/sdk/sdk.qbs @@ -0,0 +1,549 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.Environment +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Probes +import qbs.TextFile +import qbs.Utilities +import qbs.Xml +import "utils.js" as SdkUtils + +Module { + Probes.AndroidSdkProbe { + id: sdkProbe + environmentPaths: (sdkDir ? [sdkDir] : []).concat(base) + } + + Probes.AndroidNdkProbe { + id: ndkProbe + sdkPath: sdkProbe.path + environmentPaths: (ndkDir ? [ndkDir] : []).concat(base) + } + + Probes.PathProbe { + id: bundletoolProbe + platformSearchPaths: [Android.sdk.sdkDir] + names: ["bundletool-all"] + nameSuffixes: ["-0.11.0.jar", "-0.12.0.jar", "-0.13.0.jar", "-0.13.3.jar", "-0.13.4.jar", + "-0.14.0.jar", "-0.15.0.jar"] + } + + property path sdkDir: sdkProbe.path + property path ndkDir: ndkProbe.path + property path ndkSamplesDir: ndkProbe.samplesDir + property string buildToolsVersion: sdkProbe.buildToolsVersion + property var buildToolsVersionParts: buildToolsVersion ? buildToolsVersion.split('.').map(function(item) { return parseInt(item, 10); }) : [] + property int buildToolsVersionMajor: buildToolsVersionParts[0] + property int buildToolsVersionMinor: buildToolsVersionParts[1] + property int buildToolsVersionPatch: buildToolsVersionParts[2] + property string platform: sdkProbe.platform + + property path bundletoolFilePath: bundletoolProbe.filePath + + // Product-specific properties and files + property string packageName: { + var idx = product.name.indexOf(".") + if (idx > 0 && idx < product.name.length) + return product.name; + return "com.example." + product.name; + } + property int versionCode + property string versionName + property string apkBaseName: packageName + property bool automaticSources: true + property bool legacyLayout: false + property path sourceSetDir: legacyLayout + ? product.sourceDirectory + : FileInfo.joinPaths(product.sourceDirectory, "src/main") + property path resourcesDir: FileInfo.joinPaths(sourceSetDir, "res") + property path assetsDir: FileInfo.joinPaths(sourceSetDir, "assets") + property path sourcesDir: FileInfo.joinPaths(sourceSetDir, legacyLayout ? "src" : "java") + property string manifestFile: defaultManifestFile + readonly property string defaultManifestFile: FileInfo.joinPaths(sourceSetDir, + "AndroidManifest.xml") + + property bool _enableRules: !product.multiplexConfigurationId && !!packageName + + property bool _archInName: false + property bool _bundledInAssets: true + + Group { + name: "java sources" + condition: Android.sdk.automaticSources + prefix: FileInfo.resolvePath(product.sourceDirectory, Android.sdk.sourcesDir + '/') + files: "**/*.java" + } + + Group { + name: "android resources" + condition: Android.sdk.automaticSources + fileTags: ["android.resources"] + prefix: FileInfo.resolvePath(product.sourceDirectory, Android.sdk.resourcesDir + '/') + files: "**/*" + } + + Group { + name: "android assets" + condition: Android.sdk.automaticSources + fileTags: ["android.assets"] + prefix: FileInfo.resolvePath(product.sourceDirectory, Android.sdk.assetsDir + '/') + files: "**/*" + } + + Group { + name: "manifest" + condition: Android.sdk.automaticSources + fileTags: ["android.manifest"] + files: Android.sdk.manifestFile + && Android.sdk.manifestFile !== Android.sdk.defaultManifestFile + ? [Android.sdk.manifestFile] + : (File.exists(Android.sdk.defaultManifestFile) + ? [Android.sdk.defaultManifestFile] : []) + } + + + // Internal properties. + property int platformVersion: { + if (platform) { + var match = platform.match(/^android-([0-9]+)$/); + if (match !== null) { + return parseInt(match[1], 10); + } + } + } + + property string platformJavaVersion: { + if (platformVersion >= 21) + return "1.7"; + return "1.5"; + } + + property path buildToolsDir: FileInfo.joinPaths(sdkDir, "build-tools", buildToolsVersion) + property string aaptName: "aapt" + PropertyOptions { + name: "aaptName" + allowedValues: ["aapt", "aapt2"] + } + property path aaptFilePath: FileInfo.joinPaths(buildToolsDir, aaptName) + readonly property bool _enableAapt2: aaptName === "aapt2" + property string packageType: "apk" + PropertyOptions { + name: "packageType" + allowedValues: ["apk", "aab"] + } + readonly property bool _generateAab: packageType == "aab" + + 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") + property path androidJarFilePath: FileInfo.joinPaths(sdkDir, "platforms", platform, + "android.jar") + property path frameworkAidlFilePath: FileInfo.joinPaths(sdkDir, "platforms", platform, + "framework.aidl") + property path generatedJavaFilesBaseDir: FileInfo.joinPaths(product.buildDirectory, "gen") + property path generatedJavaFilesDir: FileInfo.joinPaths(generatedJavaFilesBaseDir, + (packageName || "").split('.').join('/')) + property path compiledResourcesDir: FileInfo.joinPaths(product.buildDirectory, + "compiled_resources") + property string packageContentsDir: FileInfo.joinPaths(product.buildDirectory, packageType) + property string debugKeyStorePath: FileInfo.joinPaths( + Environment.getEnv(qbs.hostOS.contains("windows") + ? "USERPROFILE" : "HOME"), + ".android", "debug.keystore") + property bool useApksigner: buildToolsVersion && Utilities.versionCompare( + buildToolsVersion, "24.0.3") >= 0 + property stringList aidlSearchPaths + + Depends { name: "java"; condition: _enableRules } + Properties { + condition: _enableRules + java.languageVersion: platformJavaVersion + java.runtimeVersion: platformJavaVersion + java.bootClassPaths: androidJarFilePath + } + + validate: { + if (!sdkDir) { + throw ModUtils.ModuleError("Could not find an Android SDK at any of the following " + + "locations:\n\t" + sdkProbe.candidatePaths.join("\n\t") + + "\nInstall the Android SDK to one of the above locations, " + + "or set the Android.sdk.sdkDir property or " + + "ANDROID_HOME environment variable to a valid " + + "Android SDK location."); + } + if (!bundletoolFilePath && _generateAab) { + throw ModUtils.ModuleError("Could not find Android bundletool at the following " + + "location:\n\t" + Android.sdk.sdkDir + + "\nInstall the Android bundletool to the above location, " + + "or set the Android.sdk.bundletoolFilePath property.\n" + + "Android bundletool can be downloaded from " + + "https://github.com/google/bundletool"); + } + } + + FileTagger { + patterns: ["AndroidManifest.xml"] + fileTags: ["android.manifest"] + } + + FileTagger { + patterns: ["*.aidl"] + 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 { + condition: _enableRules + inputs: ["android.aidl"] + Artifact { + filePath: FileInfo.joinPaths(Utilities.getHash(input.filePath), + input.completeBaseName + ".java") + fileTags: ["java.java"] + } + + prepare: { + var aidl = product.Android.sdk.aidlFilePath; + var args = ["-p" + product.Android.sdk.frameworkAidlFilePath]; + var aidlSearchPaths = input.Android.sdk.aidlSearchPaths; + for (var i = 0; i < (aidlSearchPaths ? aidlSearchPaths.length : 0); ++i) + args.push("-I" + aidlSearchPaths[i]); + args.push(input.filePath, output.filePath); + cmd = new Command(aidl, args); + cmd.description = "Processing " + input.fileName; + return [cmd]; + } + } + + property bool customManifestProcessing: false + Group { + condition: !Android.sdk.customManifestProcessing + fileTagsFilter: "android.manifest_processed" + fileTags: "android.manifest_final" + } + Rule { + condition: _enableRules + inputs: "android.manifest" + Artifact { + filePath: FileInfo.joinPaths("processed_manifest", input.fileName) + fileTags: "android.manifest_processed" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Ensuring correct package name in Android manifest file"; + cmd.sourceCode = function() { + var manifestData = new Xml.DomDocument(); + manifestData.load(input.filePath); + var rootElem = manifestData.documentElement(); + if (!rootElem || !rootElem.isElement() || rootElem.tagName() != "manifest") + throw "No manifest tag found in '" + input.filePath + "'."; + + // Quick sanity check. Don't try to be fancy; let's not risk rejecting valid names. + var packageName = product.Android.sdk.packageName; + if (!packageName.match(/^[^.]+(?:\.[^.]+)+$/)) { + throw "Package name '" + packageName + "' is not valid. Please set " + + "Android.sdk.packageName to a name following the " + + "'com.mycompany.myproduct' pattern." + } + rootElem.setAttribute("package", packageName); + if (product.Android.sdk.versionCode !== undefined) + rootElem.setAttribute("android:versionCode", product.Android.sdk.versionCode); + if (product.Android.sdk.versionName !== undefined) + rootElem.setAttribute("android:versionName", product.Android.sdk.versionName); + + if (product.Android.sdk._bundledInAssets) { + // Remove + // custom AndroidManifest.xml because assets are in rcc files for qt >= 5.14 + var appElem = rootElem.firstChild("application"); + if (!appElem || !appElem.isElement() || appElem.tagName() != "application") + throw "No application tag found in '" + input.filePath + "'."; + var activityElem = appElem.firstChild("activity"); + if (!activityElem || !activityElem.isElement() || + activityElem.tagName() != "activity") + throw "No activity tag found in '" + input.filePath + "'."; + var metaDataElem = activityElem.firstChild("meta-data"); + while (metaDataElem && metaDataElem.isElement()) { + if (SdkUtils.elementHasBundledAttributes(metaDataElem)) { + var elemToRemove = metaDataElem; + metaDataElem = metaDataElem.nextSibling("meta-data"); + activityElem.removeChild(elemToRemove); + } else { + metaDataElem = metaDataElem.nextSibling("meta-data"); + } + } + } + + manifestData.save(output.filePath, 4); + } + return cmd; + } + } + + Rule { + condition: _enableRules && !_enableAapt2 + multiplex: true + inputs: ["android.resources", "android.assets", "android.manifest_final"] + + outputFileTags: ["java.java"] + outputArtifacts: { + var artifacts = []; + var resources = inputs["android.resources"]; + if (resources && resources.length) { + artifacts.push({ + filePath: FileInfo.joinPaths(product.Android.sdk.generatedJavaFilesDir, + "R.java"), + fileTags: ["java.java"] + }); + } + + return artifacts; + } + + prepare: SdkUtils.prepareAaptGenerate.apply(SdkUtils, arguments) + } + + Rule { + condition: _enableRules && _enableAapt2 + inputs: ["android.resources"] + outputArtifacts: { + var outputs = []; + var resources = inputs["android.resources"]; + for (var i = 0; i < resources.length; ++i) { + var filePath = resources[i].filePath; + var resourceFileName = SdkUtils.generateAapt2ResourceFileName(filePath); + var compiledName = FileInfo.joinPaths(product.Android.sdk.compiledResourcesDir, + resourceFileName); + outputs.push({filePath: compiledName, fileTags: "android.resources_compiled"}); + } + return outputs; + } + outputFileTags: ["android.resources_compiled"] + + prepare: SdkUtils.prepareAapt2CompileResource.apply(SdkUtils, arguments) + } + + Rule { + condition: _enableRules && _enableAapt2 + multiplex: true + inputs: ["android.resources_compiled", "android.assets", "android.manifest_final"] + outputFileTags: ["java.java", "android.apk_resources"] + outputArtifacts: { + var artifacts = []; + artifacts.push({ + filePath: product.Android.sdk.apkBaseName + (product.Android.sdk._generateAab ? + ".apk_aab" : ".apk_apk"), + fileTags: ["android.apk_resources"] + }); + var resources = inputs["android.resources_compiled"]; + if (resources && resources.length) { + artifacts.push({ + filePath: FileInfo.joinPaths(product.Android.sdk.generatedJavaFilesDir, + "R.java"), + fileTags: ["java.java"] + }); + } + + return artifacts; + } + prepare: SdkUtils.prepareAapt2Link.apply(SdkUtils, arguments) + } + + Rule { + condition: _enableRules + multiplex: true + + Artifact { + filePath: FileInfo.joinPaths(product.Android.sdk.generatedJavaFilesDir, + "BuildConfig.java") + fileTags: ["java.java"] + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Generating BuildConfig.java"; + cmd.sourceCode = function() { + var debugValue = product.qbs.buildVariant === "debug" ? "true" : "false"; + var ofile = new TextFile(output.filePath, TextFile.WriteOnly); + ofile.writeLine("package " + product.Android.sdk.packageName + ";") + ofile.writeLine("public final class BuildConfig {"); + ofile.writeLine(" public final static boolean DEBUG = " + debugValue + ";"); + ofile.writeLine("}"); + ofile.close(); + }; + return [cmd]; + } + } + + Rule { + condition: _enableRules + multiplex: true + inputs: ["java.class"] + inputsFromDependencies: ["java.jar"] + Artifact { + filePath: product.Android.sdk._generateAab ? + FileInfo.joinPaths(product.Android.sdk.packageContentsDir, "dex", + "classes.dex") : + FileInfo.joinPaths(product.Android.sdk.packageContentsDir, "classes.dex") + fileTags: ["android.dex"] + } + prepare: SdkUtils.prepareDex.apply(SdkUtils, arguments) + } + + Rule { + condition: _enableRules + property stringList inputTags: "android.nativelibrary" + inputsFromDependencies: inputTags + inputs: product.aggregate ? [] : inputTags + Artifact { + filePath: FileInfo.joinPaths(product.Android.sdk.packageContentsDir, "lib", + input.Android.ndk.abi, input.fileName) + fileTags: "android.nativelibrary_deployed" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "copying " + input.fileName + " for packaging"; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); }; + return cmd; + } + } + + Rule { + condition: _enableRules + multiplex: true + property stringList inputTags: "android.stl" + inputsFromDependencies: inputTags + inputs: product.aggregate ? [] : inputTags + outputFileTags: "android.stl_deployed" + outputArtifacts: { + var deploymentData = SdkUtils.stlDeploymentData(product, inputs, "stl"); + var outputs = []; + for (i = 0; i < deploymentData.outputFilePaths.length; ++i) { + outputs.push({filePath: deploymentData.outputFilePaths[i], + fileTags: "android.stl_deployed"}); + } + return outputs; + } + prepare: { + var cmds = []; + var deploymentData = SdkUtils.stlDeploymentData(product, inputs); + for (var i = 0; i < deploymentData.uniqueInputs.length; ++i) { + var input = deploymentData.uniqueInputs[i]; + var stripArgs = ["--strip-all", "-o", deploymentData.outputFilePaths[i], + input.filePath]; + var cmd = new Command(input.cpp.stripPath, stripArgs); + cmd.description = "deploying " + input.fileName; + cmds.push(cmd); + } + return cmds; + } + } + + Rule { + condition: _enableRules && !_enableAapt2 && !_generateAab + multiplex: true + inputs: [ + "android.resources", "android.assets", "android.manifest_final", + "android.dex", "android.stl_deployed", + "android.nativelibrary_deployed", "android.keystore" + ] + Artifact { + filePath: product.Android.sdk.apkBaseName + ".apk" + fileTags: "android.package" + } + prepare: SdkUtils.prepareAaptPackage.apply(SdkUtils, arguments) + } + + Rule { + condition: _enableRules && _enableAapt2 && !_generateAab + multiplex: true + inputs: [ + "android.apk_resources", "android.manifest_final", + "android.dex", "android.stl_deployed", + "android.nativelibrary_deployed", "android.keystore" + ] + Artifact { + filePath: product.Android.sdk.apkBaseName + ".apk" + fileTags: "android.package" + } + prepare: SdkUtils.prepareApkPackage.apply(SdkUtils, arguments) + } + + Rule { + condition: _enableRules && _enableAapt2 && _generateAab + multiplex: true + inputs: [ + "android.apk_resources", "android.manifest_final", + "android.dex", "android.stl_deployed", + "android.nativelibrary_deployed" + ] + Artifact { + filePath: product.Android.sdk.apkBaseName + ".aab" + fileTags: "android.package" + } + prepare: SdkUtils.prepareBundletoolPackage.apply(SdkUtils, arguments) + } +} diff --git a/share/qbs/modules/Android/sdk/utils.js b/share/qbs/modules/Android/sdk/utils.js new file mode 100644 index 00000000..63bf1f5c --- /dev/null +++ b/share/qbs/modules/Android/sdk/utils.js @@ -0,0 +1,432 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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 File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var Process = require("qbs.Process"); +var TextFile = require("qbs.TextFile"); +var Utilities = require("qbs.Utilities"); + +function availableBuildToolsVersions(sdkDir) { + var re = /^([0-9]+)\.([0-9]+)\.([0-9]+)$/; + var buildTools = File.directoryEntries(FileInfo.joinPaths(sdkDir, "build-tools"), + File.Dirs | File.NoDotAndDotDot); + var versions = []; + for (var i = 0; i < buildTools.length; ++i) { + var match = buildTools[i].match(re); + if (match !== null) { + versions.push(buildTools[i]); + } + } + + // Sort by version number + versions.sort(function (a, b) { + return Utilities.versionCompare(a, b); + }); + + 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 findParentDir(filePath, parentDirName) +{ + var lastDir; + var currentDir = FileInfo.path(filePath); + while (lastDir !== currentDir) { + if (FileInfo.fileName(currentDir) === parentDirName) + return currentDir; + lastDir = currentDir; + currentDir = FileInfo.path(currentDir); + } +} + +function commonAaptPackageArgs(project, product, inputs, outputs, input, output, + explicitlyDependsOn) { + var manifestFilePath = inputs["android.manifest_final"][0].filePath; + var args = ["package", "--auto-add-overlay", "-f", + "-M", manifestFilePath, + "-I", product.Android.sdk.androidJarFilePath]; + var resources = inputs["android.resources"]; + var resourceDirs = []; + if (resources) { + for (var i = 0; i < resources.length; ++i) { + var resDir = findParentDir(resources[i].filePath, "res"); + if (!resDir) { + throw "File '" + resources[i].filePath + "' is tagged as an Android resource, " + + "but is not located under a directory called 'res'."; + } + if (!resourceDirs.contains(resDir)) + resourceDirs.push(resDir); + } + } + for (i = 0; i < resourceDirs.length; ++i) + args.push("-S", resourceDirs[i]); + var assets = inputs["android.assets"]; + var assetDirs = []; + if (assets) { + for (i = 0; i < assets.length; ++i) { + var assetDir = findParentDir(assets[i].filePath, "assets"); + if (!assetDir) { + throw "File '" + assets[i].filePath + "' is tagged as an Android asset, " + + "but is not located under a directory called 'assets'."; + } + if (!assetDirs.contains(assetDir)) + assetDirs.push(assetDir); + } + } + for (i = 0; i < assetDirs.length; ++i) + args.push("-A", assetDirs[i]); + if (product.qbs.buildVariant === "debug") + args.push("--debug-mode"); + return args; +} + +// Rules: from https://developer.android.com/studio/command-line/aapt2 +// Input Output +// XML resource files, such as Resource table with *.arsc.flat as its extension. +// String and Style, which are +// located in the res/values/ +// directory. + +// All other resource files. All files other than the files under res/values/ directory are +// converted to binary XML files with *.flat extensions. +// Additionally all PNG files are crunched by default and adopt +// *.png.flat extensions. +function generateAapt2ResourceFileName(filePath) { + var suffix = FileInfo.suffix(filePath); + if (suffix === "xml") { + var data = new Xml.DomDocument(); + data.load(filePath); + var rootElem = data.documentElement(); + if (rootElem && rootElem.isElement() && rootElem.tagName() === "resources") + // This is a valid XML resource file + suffix = "arsc"; + // If the xml file is not a "resources" one then it's treated like any other resource file. + } + var dir = FileInfo.path(filePath); + var baseName = FileInfo.completeBaseName(filePath) + return FileInfo.fileName(dir) + "_" + baseName + "." + suffix + ".flat"; +} + +function prepareAapt2CompileResource(project, product, inputs, outputs, input, output, + explicitlyDependsOn) { + var cmds = []; + var resources = inputs["android.resources"]; + var compilesResourcesDir = product.Android.sdk.compiledResourcesDir; + if (!File.makePath(compilesResourcesDir)) { + throw "Cannot create directory '" + FileInfo.toNativeSeparators(compilesResourcesDir) + + "'."; + } + var args = ["compile", input.filePath, "-o", compilesResourcesDir]; + var cmd = new Command(product.Android.sdk.aaptFilePath, args); + var outputFileName = generateAapt2ResourceFileName(input.filePath); + cmd.description = "compiling resource " + input.fileName + " into " + outputFileName; + cmds.push(cmd); + + return cmds; +} + +function prepareAapt2Link(project, product, inputs, outputs, input, output, explicitlyDependsOn) { + var cmds = []; + var baseOutputFilePath = outputs["android.apk_resources"][0].filePath; + var manifestFilePath = inputs["android.manifest_final"][0].filePath; + var compilesResourcesDir = product.Android.sdk.compiledResourcesDir; + var compiledResources = inputs["android.resources_compiled"]; + + var args = ["link", "-o", baseOutputFilePath, "-I", product.Android.sdk.androidJarFilePath]; + var i = 0; + if (compiledResources) { + for (i = 0; i < compiledResources.length; ++i) + args.push(compiledResources[i].filePath); + } + args.push("--no-auto-version"); + args.push("--auto-add-overlay"); + args.push("--manifest", manifestFilePath); + args.push("--java", product.Android.sdk.generatedJavaFilesBaseDir); + + var assets = inputs["android.assets"]; + var assetDirs = []; + if (assets) { + for (i = 0; i < assets.length; ++i) { + var assetDir = findParentDir(assets[i].filePath, "assets"); + if (!assetDir) { + throw "File '" + assets[i].filePath + "' is tagged as an Android asset, " + + "but is not located under a directory called 'assets'."; + } + if (!assetDirs.contains(assetDir)) + assetDirs.push(assetDir); + } + } + for (i = 0; i < assetDirs.length; ++i) + args.push("-A", assetDirs[i]); + if (product.qbs.buildVariant === "debug") + args.push("-v"); + if (product.Android.sdk._generateAab) + args.push("--proto-format"); + var cmd = new Command(product.Android.sdk.aaptFilePath, args); + cmd.description = "linking resources"; + cmd.workingDirectory = product.buildDirectory; + cmds.push(cmd); + + return cmds; +} + +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.package"][0]; + var args = commonAaptPackageArgs.apply(this, arguments); + args.push("-F", apkOutput.filePath + ".unaligned"); + args.push(product.Android.sdk.packageContentsDir); + var cmd = new Command(product.Android.sdk.aaptFilePath, args); + cmd.description = "generating " + apkOutput.fileName; + 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 prepareApkPackage(project, product, inputs, outputs, input, output, explicitlyDependsOn) { + var cmds = []; + var apkInputFilePath = inputs["android.apk_resources"][0].filePath; + var apkOutput = outputs["android.package"][0]; + var apkOutputFilePathUnaligned = outputs["android.package"][0].filePath + ".unaligned"; + var dexFilePath = inputs["android.dex"][0].filePath; + + var copyCmd = new JavaScriptCommand(); + copyCmd.description = "copying apk"; + copyCmd.source = apkInputFilePath; + copyCmd.target = apkOutputFilePathUnaligned; + copyCmd.sourceCode = function() { + File.copy(source, target); + } + cmds.push(copyCmd); + + var jarArgs = ["-uvf", apkOutputFilePathUnaligned, "."]; + var libPath = FileInfo.joinPaths(product.Android.sdk.packageContentsDir); + var jarCmd = new Command(product.java.jarFilePath, jarArgs); + jarCmd.description = "packaging files"; + jarCmd.workingDirectory = libPath; + cmds.push(jarCmd); + + if (!product.Android.sdk.useApksigner) { + args = ["-sigalg", "SHA1withRSA", "-digestalg", "SHA1", + "-keystore", inputs["android.keystore"][0].filePath, + "-storepass", "android", + apkOutputFilePathUnaligned, + "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", apkOutputFilePathUnaligned, apkOutput.filePath]); + cmd.silent = true; + cmds.push(cmd); + + cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.unalignedApk = apkOutputFilePathUnaligned; + 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 prepareBundletoolPackage(project, product, inputs, outputs, input, output, + explicitlyDependsOn) { + var cmds = []; + var baseModuleDir = product.Android.sdk.packageContentsDir; + var manifestDirName = FileInfo.joinPaths(baseModuleDir, "manifest"); + var pkgBaseFileName = inputs["android.apk_resources"][0].filePath; + + var jarResourcesArgs = ["xf", pkgBaseFileName]; + var jarResourcesCmd = new Command(product.java.jarFilePath, jarResourcesArgs); + jarResourcesCmd.description = "extracting resources apk"; + jarResourcesCmd.workingDirectory = baseModuleDir; + cmds.push(jarResourcesCmd); + + var moveManifestCmd = new JavaScriptCommand(); + moveManifestCmd.description = "moving manifest in manifest directory"; + moveManifestCmd.path = manifestDirName; + moveManifestCmd.manifestFilePath = baseModuleDir + "/AndroidManifest.xml"; + moveManifestCmd.sourceCode = function() { + if (!File.exists(path)) + File.makePath(path); + if (File.exists(manifestFilePath)) + File.move(manifestFilePath, path + "/AndroidManifest.xml"); + } + cmds.push(moveManifestCmd); + + var baseFilePath = FileInfo.joinPaths(product.buildDirectory, "base.zip"); + var jarBaseArgs = ["cfM", baseFilePath, "."]; + var jarBaseCmd = new Command(product.java.jarFilePath, jarBaseArgs); + jarBaseCmd.description = "compressing base module"; + jarBaseCmd.workingDirectory = baseModuleDir; + cmds.push(jarBaseCmd); + + var aabFilePath = outputs["android.package"][0].filePath; + var removeCmd = new JavaScriptCommand(); + removeCmd.description = "removing previous aab"; + removeCmd.filePath = aabFilePath; + removeCmd.sourceCode = function() { + if (File.exists(filePath)) + File.remove(filePath); + } + cmds.push(removeCmd); + + var args = ["-jar", product.Android.sdk.bundletoolFilePath, "build-bundle"]; + args.push("--modules=" + baseFilePath); + args.push("--output=" + aabFilePath); + var cmd = new Command(product.java.interpreterFilePath, args); + cmd.description = "generating " + outputs["android.package"][0].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); +} + +function stlDeploymentData(product, inputs, type) +{ + var data = { uniqueInputs: [], outputFilePaths: []}; + var uniqueFilePaths = []; + var theInputs = inputs["android.stl"]; + if (!theInputs) + return data; + for (var i = 0; i < theInputs.length; ++i) { + var currentInput = theInputs[i]; + if (uniqueFilePaths.contains(currentInput.filePath)) + continue; + uniqueFilePaths.push(currentInput.filePath); + data.uniqueInputs.push(currentInput); + var outputFileName = currentInput.fileName; + data.outputFilePaths.push(FileInfo.joinPaths(product.Android.sdk.packageContentsDir, "lib", + currentInput.Android.ndk.abi, + outputFileName)); + } + return data; +} + +function elementHasBundledAttributes(element) +{ + return element.hasAttribute("android:name") && + (element.attribute("android:name") === "android.app.bundled_in_assets_resource_id") || + (element.attribute("android:name") === "android.app.bundled_in_lib_resource_id"); +} diff --git a/share/qbs/modules/Exporter/pkgconfig/pkgconfig.js b/share/qbs/modules/Exporter/pkgconfig/pkgconfig.js new file mode 100644 index 00000000..a3109d61 --- /dev/null +++ b/share/qbs/modules/Exporter/pkgconfig/pkgconfig.js @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 ModUtils = require("qbs.ModUtils"); + +function quote(value) +{ + if (value.contains(" ") || value.contains("'") || value.contains('"')) { + return '"' + value.replace(/(["'\\])/g, "\\$1") + '"'; + } + return value; +} + +function writeEntry(product, file, key, propertyName, required, additionalValues) +{ + var value = product.Exporter.pkgconfig[propertyName]; + if (additionalValues && additionalValues.length > 0) + value = (value || []).concat(additionalValues); + var valueIsNotEmpty = value && (!Array.isArray(value) || value.length > 0); + if (valueIsNotEmpty) { + if (Array.isArray(value)) + value = value.join(' '); + file.writeLine(key + ": " + value); + } else if (required) { + throw "Failure creating " + FileInfo.fileName(file.filePath()) + ": The entry '" + key + + "' is required, but property Exporter.pkgconfig." + + propertyName + " is not set."; + } +} + +function collectAutodetectedData(topLevelProduct) +{ + var data = { + libs: [], + cflags: [], + requires: [], + requiresPrivate: [] + }; + if (!topLevelProduct.Exporter.pkgconfig.autoDetect) + return data; + + var excludedDeps = topLevelProduct.Exporter.pkgconfig.excludedDependencies || []; + var explicitRequires = topLevelProduct.Exporter.pkgconfig.requiresEntry || []; + var explicitRequiresPrivate = topLevelProduct.Exporter.pkgconfig.requiresPrivateEntry || []; + + var transformFunc = topLevelProduct.Exporter.pkgconfig.transformFunction; + + // Make use of the "prefix" convenience variable if applicable. + function quoteAndPrefixify(value) + { + var quotedValue = quote(value); + var installPrefix = topLevelProduct.qbs.installPrefix || ""; + if (!topLevelProduct.Exporter.pkgconfig._usePrefix || typeof value !== "string" + || !value.startsWith(installPrefix) + || (value.length > installPrefix.length && value[installPrefix.length] !== '/')) { + return quotedValue; + } + return quotedValue.replace(product.qbs.installPrefix, "${prefix}"); + } + + function transformedValue(product, moduleName, propertyName) + { + var originalValue = product.exports[moduleName][propertyName]; + var value = transformFunc + ? eval("(" + transformFunc + ")(product, moduleName, propertyName, originalValue)") + : originalValue; + if (Array.isArray(value)) + value.forEach(function(v, i, a) { a[i] = quoteAndPrefixify(v); }); + else if (value) + value = quoteAndPrefixify(value); + return value; + } + + function collectLibs(productOrModule) + { + var libs = []; + var libArtifacts; + var isProduct = !productOrModule.present; + var considerDynamicLibs = !isProduct || (productOrModule.type + && productOrModule.type.contains("dynamiclibrary")); + if (considerDynamicLibs) { + libArtifacts = productOrModule.artifacts.dynamiclibrary; + } else { + var considerStaticLibs = !isProduct || (productOrModule.type + && productOrModule.type.contains("staticlibrary")); + if (considerStaticLibs) + libArtifacts = productOrModule.artifacts.staticlibrary; + } + for (var i = 0; i < (libArtifacts || []).length; ++i) { + var libArtifact = libArtifacts[i]; + if (libArtifact.qbs.install) { + var installDir = FileInfo.path(ModUtils.artifactInstalledFilePath(libArtifact)); + installDir = installDir.slice(libArtifact.qbs.installRoot.length); + libs.push("-L" + quoteAndPrefixify(FileInfo.cleanPath(installDir)), + "-l" + quote(productOrModule.targetName)); + } + } + if (!productOrModule.exports.cpp) + return libs; + var libPaths = transformedValue(productOrModule, "cpp", "libraryPaths"); + if (libPaths) + libs.push.apply(libs, libPaths.map(function(p) { return "-L" + p; })); + function libNamesToLibEntries(libNames) { + return libNames.map(function(libName) { return "-l" + libName; }); + }; + var dlls = transformedValue(productOrModule, "cpp", "dynamicLibraries"); + if (dlls) + libs.push.apply(libs, libNamesToLibEntries(dlls)); + var staticLibs = transformedValue(productOrModule, "cpp", "staticLibraries"); + if (staticLibs) + libs.push.apply(libs, libNamesToLibEntries(staticLibs)); + var lFlags = transformedValue(productOrModule, "cpp", "linkerFlags"); + if (lFlags) + libs.push.apply(libs, lFlags); + lFlags = transformedValue(productOrModule, "cpp", "driverFlags"); + if (lFlags) + libs.push.apply(libs, lFlags); + lFlags = transformedValue(productOrModule, "cpp", "driverLinkerFlags"); + if (lFlags) + libs.push.apply(libs, lFlags); + return libs; + } + + function collectCFlags(productOrModule) + { + if (!productOrModule.exports.cpp) + return []; + var flags = []; + var defs = transformedValue(productOrModule, "cpp", "defines"); + if (defs) + flags.push.apply(flags, defs.map(function(d) { return "-D" + d; })); + var incPaths = transformedValue(productOrModule, "cpp", "includePaths"); + if (incPaths) + flags.push.apply(flags, incPaths.map(function(p) { return "-I" + p; })); + var cflags = transformedValue(productOrModule, "cpp", "commonCompilerFlags"); + if (cflags) + flags.push.apply(flags, cflags); + cflags = transformedValue(productOrModule, "cpp", "driverFlags"); + if (cflags) + flags.push.apply(flags, cflags); + cflags = transformedValue(productOrModule, "cpp", "cxxFlags") + || transformedValue(productOrModule, "cpp", "cFlags"); + if (cflags) + flags.push.apply(flags, cflags); + return flags; + } + + function collectAutodetectedDataRecursive(productOrModule, privateContext) + { + if (!privateContext) { + data.libs.push.apply(data.libs, collectLibs(productOrModule)); + data.cflags.push.apply(data.cflags, collectCFlags(productOrModule)); + } + var exportedDeps = productOrModule.exports ? productOrModule.exports.dependencies : []; + var exportedDepNames = []; + var privateDeps = []; + for (var i = 0; i < exportedDeps.length; ++i) + exportedDepNames.push(exportedDeps[i].name); + for (i = 0; i < (productOrModule.dependencies || []).length; ++i) { + var dep = productOrModule.dependencies[i]; + if (exportedDepNames.contains(dep.name)) + continue; + privateDeps.push(dep); + } + + function gatherData(dep) { + if (dep.name === "Exporter.pkgconfig") + return; + var depHasPkgConfig = dep.Exporter && dep.Exporter.pkgconfig; + if (depHasPkgConfig) { + var entry = FileInfo.completeBaseName(dep.Exporter.pkgconfig.fileName); + if (excludedDeps.contains(entry)) + return; + if (isPrivateDep && !data.requiresPrivate.contains(entry) + && !explicitRequiresPrivate.contains(entry)) { + data.requiresPrivate.push(entry); + } + if (!isPrivateDep && !data.requires.contains(entry) + && !explicitRequires.contains(entry)) { + data.requires.push(entry); + } + } else { + if (excludedDeps.contains(dep.name)) + return; + if (isPrivateDep && explicitRequiresPrivate.contains(dep.name)) + return; + if (!isPrivateDep && explicitRequires.contains(dep.name)) + return; + collectAutodetectedDataRecursive(dep, isPrivateDep); + } + } + var isPrivateDep = privateContext; + exportedDeps.forEach(gatherData); + isPrivateDep = true; + privateDeps.forEach(gatherData); + } + + collectAutodetectedDataRecursive(topLevelProduct, false); + return data; +} diff --git a/share/qbs/modules/Exporter/pkgconfig/pkgconfig.qbs b/share/qbs/modules/Exporter/pkgconfig/pkgconfig.qbs new file mode 100644 index 00000000..8cc55f88 --- /dev/null +++ b/share/qbs/modules/Exporter/pkgconfig/pkgconfig.qbs @@ -0,0 +1,87 @@ +import qbs.FileInfo +import qbs.TextFile + +import "pkgconfig.js" as HelperFunctions + +Module { + property string fileName: product.targetName + ".pc" + property bool autoDetect: true + property var transformFunction // function(product, moduleName, propertyName, valueElement) + property stringList excludedDependencies + + property string nameEntry: product.name + property string descriptionEntry: product.name + property string versionEntry: product.version + property string urlEntry + property stringList cflagsEntry: [] + property stringList libsEntry: [] + property stringList libsPrivateEntry: [] + property stringList requiresEntry: [] + property stringList requiresPrivateEntry: [] + property stringList conflictsEntry: [] + + property var customVariables + + property bool _usePrefix: autoDetect && qbs.installPrefix + + additionalProductTypes: ["Exporter.pkgconfig.pc"] + + Rule { + multiplex: true + requiresInputs: false + + // Make sure all relevant library artifacts have been created by the time we run. + auxiliaryInputs: { + if (!autoDetect) + return undefined; + if (product.type.contains("staticlibrary")) + return ["staticlibrary"]; + if (product.type.contains("dynamiclibrary")) + return ["dynamiclibrary"]; + } + + Artifact { + filePath: product.Exporter.pkgconfig.fileName + fileTags: ["Exporter.pkgconfig.pc"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + if (product.Exporter.pkgconfig._usePrefix) + f.writeLine("prefix=" + product.qbs.installPrefix + "\n"); + var customVariables = product.Exporter.pkgconfig.customVariables; + if (customVariables) { + for (var customVar in customVariables) + f.writeLine(customVar + "=" + customVariables[customVar]); + f.writeLine(""); + } + var autoDetectedData = HelperFunctions.collectAutodetectedData(product); + HelperFunctions.writeEntry(product, f, "Name", "nameEntry", true); + HelperFunctions.writeEntry(product, f, "Description", "descriptionEntry", true); + HelperFunctions.writeEntry(product, f, "Version", "versionEntry", true); + HelperFunctions.writeEntry(product, f, "URL", "urlEntry"); + HelperFunctions.writeEntry(product, f, "Cflags", "cflagsEntry", false, + autoDetectedData.cflags); + HelperFunctions.writeEntry(product, f, "Libs", "libsEntry", false, + autoDetectedData.libs); + HelperFunctions.writeEntry(product, f, "Libs.private", "libsPrivateEntry"); + HelperFunctions.writeEntry(product, f, "Requires", "requiresEntry", false, + autoDetectedData.requires); + HelperFunctions.writeEntry(product, f, "Requires.private", "requiresPrivateEntry", + false, autoDetectedData.requiresPrivate); + HelperFunctions.writeEntry(product, f, "Conflicts", "conflictsEntry"); + }; + return [cmd]; + } + } + + validate: { + if (requiresEntry && excludedDependencies + && requiresEntry.containsAny(excludedDependencies)) { + throw "The contents of Export.pkgconfig.requiresEntry and " + + "Export.pkgconfig.excludedDependencies must not overlap."; + } + } +} diff --git a/share/qbs/modules/Exporter/qbs/qbsexporter.js b/share/qbs/modules/Exporter/qbs/qbsexporter.js new file mode 100644 index 00000000..16408174 --- /dev/null +++ b/share/qbs/modules/Exporter/qbs/qbsexporter.js @@ -0,0 +1,272 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 ModUtils = require("qbs.ModUtils"); + +function tagListToString(tagList) +{ + return JSON.stringify(tagList); +} + +function stringToTagList(tagListString) +{ + return JSON.parse(tagListString); +} + +function writeTargetArtifactGroup(output, tagList, artifactList, moduleInstallDir, moduleFile) +{ + // Do not add our qbs module file itself. + if (tagListToString(tagList) === tagListToString(output.fileTags)) + return; + + moduleFile.writeLine(" Group {"); + moduleFile.writeLine(" filesAreTargets: true"); + var filteredTagList = tagList.filter(function(t) { return t !== "installable"; }); + moduleFile.writeLine(" fileTags: " + JSON.stringify(filteredTagList)); + moduleFile.writeLine(" files: ["); + for (i = 0; i < artifactList.length; ++i) { + var artifact = artifactList[i]; + var installedArtifactFilePath = ModUtils.artifactInstalledFilePath(artifact); + + // Use relative file paths for relocatability. + var relativeInstalledArtifactFilePath = FileInfo.relativePath(moduleInstallDir, + installedArtifactFilePath); + moduleFile.writeLine(" " + JSON.stringify(relativeInstalledArtifactFilePath) + + ","); + } + moduleFile.writeLine(" ]"); + moduleFile.writeLine(" }"); + +} + +function writeTargetArtifactGroups(product, output, moduleFile) +{ + var relevantArtifacts = []; + for (var i = 0; i < (product.Exporter.qbs._artifactTypes || []).length; ++i) { + var tag = product.Exporter.qbs._artifactTypes[i]; + var artifactsForTag = product.artifacts[tag] || []; + for (var j = 0; j < artifactsForTag.length; ++j) { + if (!relevantArtifacts.contains(artifactsForTag[j])) + relevantArtifacts.push(artifactsForTag[j]); + } + } + var artifactsByTags = {}; + var artifactCount = relevantArtifacts ? relevantArtifacts.length : 0; + for (i = 0; i < artifactCount; ++i) { + var artifact = relevantArtifacts[i]; + if (!artifact.fileTags.contains("installable")) + continue; + + // Put all artifacts with the same set of file tags into the same group, so we don't + // create more groups than necessary. + var key = tagListToString(artifact.fileTags); + var currentList = artifactsByTags[key]; + if (currentList) + currentList.push(artifact); + else + currentList = [artifact]; + artifactsByTags[key] = currentList; + } + var moduleInstallDir = FileInfo.path(ModUtils.artifactInstalledFilePath(output)); + for (var tagListKey in artifactsByTags) { + writeTargetArtifactGroup(output, stringToTagList(tagListKey), artifactsByTags[tagListKey], + moduleInstallDir, moduleFile); + } +} + +function checkValuePrefix(name, value, forbiddenPrefix, prefixDescription) +{ + if (value.startsWith(forbiddenPrefix)) { + throw "Value '" + value + "' for exported property '" + name + "' in product '" + + product.name + "' points into " + prefixDescription + ".\n" + + "Did you forget to set the prefixMapping property in an Export item?"; + } +} + +function stringifyValue(project, product, moduleInstallDir, prop, value) +{ + if (Array.isArray(value)) { + var repr = "["; + for (var i = 0; i < value.length; ++i) { + repr += stringifyValue(project, product, moduleInstallDir, prop, value[i]) + ", "; + } + repr += "]"; + return repr; + } + if (typeof(value) !== "string") { + var value = JSON.stringify(value); + if (prop.type === "variant") + value = '(' + value + ')'; + return value; + } + + // Catch user oversights: Paths that point into the project source or build directories + // make no sense in the module. + if (!value.startsWith(product.qbs.installRoot)) { + checkValuePrefix(prop.name, value, project.buildDirectory, "project build directory"); + checkValuePrefix(prop.name, value, project.sourceDirectory, "project source directory"); + } + + // Adapt file paths pointing into the install dir, that is, make them relative to the + // module file for relocatability. We accept them with or without the install root. + // The latter form will typically be a result of applying the prefixMapping property, + // while the first one could be an untransformed path, for instance if the project + // file is written in such a way that include paths are picked up from the installed + // location rather than the source directory. + var valuePrefixToStrip; + var fullInstallPrefix = FileInfo.joinPaths(product.qbs.installRoot, product.qbs.installPrefix); + if (fullInstallPrefix.length > 1 && value.startsWith(fullInstallPrefix)) { + valuePrefixToStrip = fullInstallPrefix; + } else { + var installPrefix = FileInfo.joinPaths("/", product.qbs.installPrefix); + if (installPrefix.length > 1 && value.startsWith(installPrefix)) + valuePrefixToStrip = installPrefix; + } + if (valuePrefixToStrip) { + var deployedModuleInstallDir = moduleInstallDir.slice(fullInstallPrefix.length); + return "FileInfo.cleanPath(FileInfo.joinPaths(path, FileInfo.relativePath(" + + JSON.stringify(deployedModuleInstallDir) + ", " + + JSON.stringify(value.slice(valuePrefixToStrip.length) || "/") + ")))"; + } + + return JSON.stringify(value); +} + +function writeProperty(project, product, moduleInstallDir, prop, indentation, considerValue, + moduleFile) +{ + var line = indentation; + var separatorIndex = prop.name.lastIndexOf("."); + var isModuleProperty = separatorIndex !== -1; + var needsDeclaration = !prop.isBuiltin && !isModuleProperty; + if (needsDeclaration) + line += "property " + prop.type + " "; + var moduleName; + if (isModuleProperty) { + moduleName = prop.name.slice(0, separatorIndex); + if ((product.Exporter.qbs.excludedDependencies || []).contains(moduleName)) + return; + } + line += prop.name + ": "; + + // We emit the literal value, unless the source code clearly refers to values from inside the + // original project, in which case the evaluated value is used. + if (considerValue && /(project|product)\./.test(prop.sourceCode)) { + var value; + if (isModuleProperty) { + var propertyName = prop.name.slice(separatorIndex + 1); + value = product.exports[moduleName][propertyName]; + } else { + value = product.exports[prop.name]; + } + line += stringifyValue(project, product, moduleInstallDir, prop, value); + } else { + line += prop.sourceCode.replace(/importingProduct\./g, "product."); + } + moduleFile.writeLine(line); +} + +function writeProperties(project, product, moduleInstallDir, list, indentation, considerValue, + moduleFile) +{ + for (var i = 0; i < list.length; ++i) { + writeProperty(project, product, moduleInstallDir, list[i], indentation, considerValue, + moduleFile); + } +} + +// This writes properties set on other modules in the Export item, i.e. property assignments +// like "cpp.includePaths: '...'". +function writeModuleProperties(project, product, output, moduleFile) +{ + var moduleInstallDir = FileInfo.path(ModUtils.artifactInstalledFilePath(output)); + var filteredProps = product.exports.properties.filter(function(p) { + return p.name !== "name"; + }); + + // The right-hand side can refer to values from the exporting product, in which case + // the evaluated value, rather than the source code, needs to go into the module file. + var considerValues = true; + writeProperties(project, product, moduleInstallDir, filteredProps, " ", considerValues, + moduleFile); +} + +function writeItem(product, item, indentation, moduleFile) +{ + moduleFile.writeLine(indentation + item.name + " {"); + var newIndentation = indentation + " "; + + // These are sub-items of the Export item, whose properties entirely live in the context + // of the importing product. Therefore, they must never use pre-evaluated values. + var considerValues = false; + writeProperties(undefined, product, undefined, item.properties, newIndentation, considerValues, + moduleFile) + + for (var i = 0; i < item.childItems.length; ++i) + writeItem(product, item.childItems[i], newIndentation, moduleFile); + moduleFile.writeLine(indentation + "}"); +} + +function isExcludedDependency(product, childItem) +{ + if ((product.Exporter.qbs.excludedDependencies || []).length === 0) + return false; + if (childItem.name !== "Depends") + return false; + for (var i = 0; i < childItem.properties.length; ++i) { + var prop = childItem.properties[i]; + var unquotedRhs = prop.sourceCode.slice(1, -1); + if (prop.name === "name" && product.Exporter.qbs.excludedDependencies.contains(unquotedRhs)) + return true; + } + return false; +} + +function writeChildItems(product, moduleFile) +{ + for (var i = 0; i < product.exports.childItems.length; ++i) { + var item = product.exports.childItems[i]; + if (!isExcludedDependency(product, item)) + writeItem(product, item, " ", moduleFile); + } +} + +function writeImportStatements(product, moduleFile) +{ + var imports = product.exports.imports; + + // We potentially use FileInfo ourselves when transforming paths in stringifyValue(). + if (!imports.contains("import qbs.FileInfo")) + imports.push("import qbs.FileInfo"); + + for (var i = 0; i < product.exports.imports.length; ++i) + moduleFile.writeLine(product.exports.imports[i]); +} diff --git a/share/qbs/modules/Exporter/qbs/qbsexporter.qbs b/share/qbs/modules/Exporter/qbs/qbsexporter.qbs new file mode 100644 index 00000000..861483ef --- /dev/null +++ b/share/qbs/modules/Exporter/qbs/qbsexporter.qbs @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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.FileInfo +import qbs.TextFile + +import "qbsexporter.js" as HelperFunctions + +Module { + property stringList artifactTypes + property string fileName: product.targetName + ".qbs" + property stringList excludedDependencies + property string additionalContent + + property stringList _artifactTypes: artifactTypes ? artifactTypes : ["installable"] + + additionalProductTypes: ["Exporter.qbs.module"] + + Rule { + multiplex: true + requiresInputs: false + + // Make sure we only run when all other artifacts are already present. + // TODO: This also matches target artifacts in dependencies. Should not hurt, + // but might be a hint that we should have auxiliaryInputsFromDependencies. + auxiliaryInputs: product.type.filter(function(t) { return t !== "Exporter.qbs.module"; }) + + Artifact { + filePath: product.Exporter.qbs.fileName + fileTags: ["Exporter.qbs.module"] + qbs.install: true + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.writeLine("import qbs"); + HelperFunctions.writeImportStatements(product, f); + f.writeLine("\nModule {"); + HelperFunctions.writeModuleProperties(project, product, output, f); + HelperFunctions.writeTargetArtifactGroups(product, output, f); + HelperFunctions.writeChildItems(product, f); + if (product.Exporter.qbs.additionalContent) + f.writeLine(product.Exporter.qbs.additionalContent); + f.writeLine("}"); + f.close(); + }; + return [cmd]; + } + } +} diff --git a/share/qbs/modules/archiver/archiver.qbs b/share/qbs/modules/archiver/archiver.qbs new file mode 100644 index 00000000..6ae53dd3 --- /dev/null +++ b/share/qbs/modules/archiver/archiver.qbs @@ -0,0 +1,240 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.Environment +import qbs.File +import qbs.FileInfo +import qbs.Probes + +Module { + // jar is a suitable fallback for creating zip files as they are the same format + // This will most likely end up being used on Windows + Depends { name: "java"; required: false } + + Probes.BinaryProbe { + id: tarProbe + names: ["tar"] + } + + Probes.BinaryProbe { + id: zipProbe + names: ["zip"] + } + + Probes.BinaryProbe { + id: sevenZipProbe + names: ["7z"] + platformSearchPaths: { + var paths = base; + if (qbs.hostOS.contains("windows")) { + var env32 = Environment.getEnv("PROGRAMFILES(X86)"); + var env64 = Environment.getEnv("PROGRAMFILES"); + if (env64 === env32 && env64.endsWith(" (x86)")) + env64 = env64.slice(0, -(" (x86)".length)); // QTBUG-3845 + paths.push(FileInfo.joinPaths(env64, "7-Zip")); + paths.push(FileInfo.joinPaths(env32, "7-Zip")); + } + return paths; + } + } + + property string type + property string archiveBaseName: product.targetName + property string workingDirectory + property stringList flags: [] + property path outputDirectory: product.destinationDirectory + property string archiveExtension: { + if (type === "7zip") + return "7z"; + if (type == "tar") { + var extension = "tar"; + if (compressionType !== "none") + extension += "." + compressionType; + return extension; + } + if (type === "zip") + return "zip"; + return undefined; + } + property string command: { + if (type === "7zip") + return sevenZipProbe.filePath; + if (type === "tar") { + if (tarProbe.found) + return tarProbe.filePath; + if (sevenZipProbe.found) + return sevenZipProbe.filePath; + } + if (type === "zip") { + // Prefer zip (probably Info-Zip) and fall back to 7z or jar when it's not available + // (as is the likely case on Windows) + if (zipProbe.found) + return zipProbe.filePath; + if (sevenZipProbe.found) + return sevenZipProbe.filePath; + if (java.present) + return java.jarFilePath; + } + return undefined; + } + + property string compressionLevel + property string compressionType: { + if (type === "tar") + return "gz"; + return undefined; + } + + PropertyOptions { + name: "type" + description: "The type of archive to create." + allowedValues: ["7zip", "tar", "zip"] + } + + PropertyOptions { + name: "compressionLevel" + description: "How much effort to put into compression.\n" + + "Higher numbers mean smaller archive files at the cost of taking more time.\n" + + "This property is only used for the '7zip' and 'zip' types.\n" + + "'7zip' only supports 0 and odd numbers." + allowedValues: [undefined, "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] + } + + PropertyOptions { + name: "compressionType" + description: "The compression format to use.\n" + + "For tar archives, the respective tool needs to be present.\n" + + "This property is only used for the 'tar' and 'zip' types." + allowedValues: ["none", "gz", "bz2", "Z", "xz", "deflate", "store"] + } + + Rule { + inputs: ["archiver.input-list"] + + Artifact { + filePath: FileInfo.joinPaths(product.moduleProperty("archiver", "outputDirectory"), + product.moduleProperty("archiver", "archiveBaseName") + '.' + + product.moduleProperty("archiver", "archiveExtension")); + fileTags: ["archiver.archive"] + } + + prepare: { + var binary = product.moduleProperty("archiver", "command"); + var binaryName = FileInfo.baseName(binary); + var args = []; + var commands = []; + var type = product.moduleProperty("archiver", "type"); + var compression = product.moduleProperty("archiver", "compressionType"); + var compressionLevel = product.moduleProperty("archiver", "compressionLevel"); + if (binaryName === "7z") { + var rmCommand = new JavaScriptCommand(); + rmCommand.silent = true; + rmCommand.sourceCode = function() { + if (File.exists(output.filePath)) + File.remove(output.filePath); + }; + commands.push(rmCommand); + args.push("a", "-y", "-mmt=on"); + switch (type) { + case "7zip": + args.push("-t7z"); + break; + case "zip": + args.push("-tzip"); + break; + case "tar": + if (compression === "gz") + args.push("-tgzip"); + else if (compression === "bz2") + args.push("-tbzip2"); + else + args.push("-ttar"); + break; + default: + throw "7zip: unrecognized archive type: '" + type + "'"; + } + + if (compressionLevel) + args.push("-mx" + compressionLevel); + args = args.concat(product.moduleProperty("archiver", "flags")); + args.push(output.filePath); + args.push("@" + input.filePath); + } else if (binaryName === "tar" && type === "tar") { + args.push("-c"); + if (compression === "gz") + args.push("-z"); + else if (compression === "bz2") + args.push("-j"); + else if (compression === "Z") + args.push("-Z"); + else if (compression === "xz") + args.push("-J"); + args.push("-f", output.filePath, "-T", input.filePath); + args = args.concat(product.moduleProperty("archiver", "flags")); + } else if (binaryName === "jar" && type === "zip") { + if (compression === "none" || compressionLevel === "0") + args.push("-0"); + + args.push("-cfM", output.filePath, "@" + input.filePath); + args = args.concat(product.moduleProperty("archiver", "flags")); + } else if (binaryName === "zip" && type === "zip") { + // The "zip" program included with most Linux and Unix distributions + // (including macOS) is Info-ZIP's Zip, so this should be fairly portable. + if (compression === "none") { + args.push("-0"); + } else { + compression = compression === "bz2" ? "bzip2" : compression; + if (["store", "deflate", "bzip2"].contains(compression)) + args.push("-Z", compression); + + if (compressionLevel) + args.push("-" + compressionLevel); + } + + args.push("-r", output.filePath, ".", "-i@" + input.filePath); + args = args.concat(product.moduleProperty("archiver", "flags")); + } else if (["tar", "zip", "jar"].contains(binaryName)) { + throw binaryName + ": unrecognized archive type: '" + type + "'"; + } else if (binaryName) { + throw "unrecognized archive tool: '" + binaryName + "'"; + } else { + throw "no archive tool available to produce archive type: '" + type + "'"; + } + + var archiverCommand = new Command(binary, args); + archiverCommand.description = "Creating archive file " + output.fileName; + archiverCommand.highlight = "linker"; + archiverCommand.workingDirectory + = product.moduleProperty("archiver", "workingDirectory"); + commands.push(archiverCommand); + return commands; + } + } +} diff --git a/share/qbs/modules/autotest/autotest.qbs b/share/qbs/modules/autotest/autotest.qbs new file mode 100644 index 00000000..c8a1c518 --- /dev/null +++ b/share/qbs/modules/autotest/autotest.qbs @@ -0,0 +1,6 @@ +Module { + property stringList arguments + property bool allowFailure: false + property string workingDir + property int timeout +} diff --git a/share/qbs/modules/bundle/BundleModule.qbs b/share/qbs/modules/bundle/BundleModule.qbs new file mode 100644 index 00000000..bf2555fa --- /dev/null +++ b/share/qbs/modules/bundle/BundleModule.qbs @@ -0,0 +1,806 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.BundleTools +import qbs.DarwinTools +import qbs.Environment +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.PropertyList +import qbs.TextFile +import qbs.Utilities +import "bundle.js" as Bundle + +Module { + Depends { name: "xcode"; required: false; } + + Probe { + id: bundleSettingsProbe + condition: qbs.targetOS.contains("darwin") + + property string xcodeDeveloperPath: xcode.developerPath + property var xcodeArchSettings: xcode._architectureSettings + property string productTypeIdentifier: _productTypeIdentifier + property bool useXcodeBuildSpecs: _useXcodeBuildSpecs + property bool isMacOs: qbs.targetOS.contains("macos") + property bool xcodePresent: xcode.present + property string xcodeVersion: xcode.version + + // Note that we include several settings pointing to properties which reference the output + // of this probe (WRAPPER_NAME, WRAPPER_EXTENSION, etc.). This is to ensure that derived + // properties take into account the value of these settings if the user customized them. + property var additionalSettings: ({ + "DEVELOPMENT_LANGUAGE": "English", + "EXECUTABLE_VARIANT_SUFFIX": "", // e.g. _debug, _profile + "FRAMEWORK_VERSION": frameworkVersion, + "GENERATE_PKGINFO_FILE": generatePackageInfo !== undefined + ? (generatePackageInfo ? "YES" : "NO") + : undefined, + "IS_MACCATALYST": "NO", + "LD_RUNPATH_SEARCH_PATHS_NO": [], + "PRODUCT_NAME": product.targetName, + "LOCAL_APPS_DIR": Environment.getEnv("HOME") + "/Applications", + "LOCAL_LIBRARY_DIR": Environment.getEnv("HOME") + "/Library", + "SWIFT_PLATFORM_TARGET_PREFIX": isMacOs ? "macos" + : qbs.targetOS.contains("ios") ? "ios" : "", + "TARGET_BUILD_DIR": product.buildDirectory, + "WRAPPER_NAME": bundleName, + "WRAPPER_EXTENSION": extension + }) + + // Outputs + property var xcodeSettings: ({}) + property var productTypeIdentifierChain: [] + + configure: { + var specsPath = path; + var specsSeparator = "-"; + if (xcodeDeveloperPath && useXcodeBuildSpecs) { + specsPath = Bundle.macOSSpecsPath(xcodeVersion, xcodeDeveloperPath); + specsSeparator = " "; + } + + var reader = new Bundle.XcodeBuildSpecsReader(specsPath, + specsSeparator, + additionalSettings, + !isMacOs); + var settings = reader.expandedSettings(productTypeIdentifier, + xcodePresent + ? xcodeArchSettings + : {}); + var chain = reader.productTypeIdentifierChain(productTypeIdentifier); + if (settings && chain) { + xcodeSettings = settings; + productTypeIdentifierChain = chain; + found = true; + } else { + xcodeSettings = {}; + productTypeIdentifierChain = []; + found = false; + } + } + } + + additionalProductTypes: !(product.multiplexed || product.aggregate) + || !product.multiplexConfigurationId ? ["bundle.content"] : [] + + property bool isBundle: !product.consoleApplication && qbs.targetOS.contains("darwin") + + readonly property bool isShallow: bundleSettingsProbe.xcodeSettings["SHALLOW_BUNDLE"] === "YES" + + property string identifierPrefix: "org.example" + property string identifier: [identifierPrefix, Utilities.rfc1034Identifier(product.targetName)].join(".") + + property string extension: bundleSettingsProbe.xcodeSettings["WRAPPER_EXTENSION"] + + property string packageType: Bundle.packageType(_productTypeIdentifier) + + property string signature: "????" // legacy creator code in Mac OS Classic (CFBundleSignature), can be ignored + + property string bundleName: bundleSettingsProbe.xcodeSettings["WRAPPER_NAME"] + + property string frameworkVersion: { + var n = parseInt(product.version, 10); + return isNaN(n) ? bundleSettingsProbe.xcodeSettings["FRAMEWORK_VERSION"] : String(n); + } + + property bool generatePackageInfo: { + // Make sure to return undefined as default to indicate "not set" + var genPkgInfo = bundleSettingsProbe.xcodeSettings["GENERATE_PKGINFO_FILE"]; + if (genPkgInfo) + return genPkgInfo === "YES"; + } + + property pathList publicHeaders + property pathList privateHeaders + property pathList resources + + property var infoPlist + property bool processInfoPlist: true + property bool embedInfoPlist: product.consoleApplication && !isBundle + property string infoPlistFormat: qbs.targetOS.contains("macos") ? "same-as-input" : "binary1" + + property string localizedResourcesFolderSuffix: ".lproj" + + property string lsregisterName: "lsregister" + property string lsregisterPath: FileInfo.joinPaths( + "/System/Library/Frameworks/CoreServices.framework" + + "/Versions/A/Frameworks/LaunchServices.framework" + + "/Versions/A/Support", lsregisterName); + + // all paths are relative to the directory containing the bundle + readonly property string infoPlistPath: bundleSettingsProbe.xcodeSettings["INFOPLIST_PATH"] + readonly property string infoStringsPath: bundleSettingsProbe.xcodeSettings["INFOSTRINGS_PATH"] + readonly property string pbdevelopmentPlistPath: bundleSettingsProbe.xcodeSettings["PBDEVELOPMENTPLIST_PATH"] + readonly property string pkgInfoPath: bundleSettingsProbe.xcodeSettings["PKGINFO_PATH"] + readonly property string versionPlistPath: bundleSettingsProbe.xcodeSettings["VERSIONPLIST_PATH"] + + readonly property string executablePath: bundleSettingsProbe.xcodeSettings["EXECUTABLE_PATH"] + + readonly property string contentsFolderPath: bundleSettingsProbe.xcodeSettings["CONTENTS_FOLDER_PATH"] + readonly property string documentationFolderPath: bundleSettingsProbe.xcodeSettings["DOCUMENTATION_FOLDER_PATH"] + readonly property string executableFolderPath: bundleSettingsProbe.xcodeSettings["EXECUTABLE_FOLDER_PATH"] + readonly property string executablesFolderPath: bundleSettingsProbe.xcodeSettings["EXECUTABLES_FOLDER_PATH"] + readonly property string frameworksFolderPath: bundleSettingsProbe.xcodeSettings["FRAMEWORKS_FOLDER_PATH"] + readonly property string javaFolderPath: bundleSettingsProbe.xcodeSettings["JAVA_FOLDER_PATH"] + readonly property string localizedResourcesFolderPath: bundleSettingsProbe.xcodeSettings["LOCALIZED_RESOURCES_FOLDER_PATH"] + readonly property string pluginsFolderPath: bundleSettingsProbe.xcodeSettings["PLUGINS_FOLDER_PATH"] + readonly property string privateHeadersFolderPath: bundleSettingsProbe.xcodeSettings["PRIVATE_HEADERS_FOLDER_PATH"] + readonly property string publicHeadersFolderPath: bundleSettingsProbe.xcodeSettings["PUBLIC_HEADERS_FOLDER_PATH"] + readonly property string scriptsFolderPath: bundleSettingsProbe.xcodeSettings["SCRIPTS_FOLDER_PATH"] + readonly property string sharedFrameworksFolderPath: bundleSettingsProbe.xcodeSettings["SHARED_FRAMEWORKS_FOLDER_PATH"] + readonly property string sharedSupportFolderPath: bundleSettingsProbe.xcodeSettings["SHARED_SUPPORT_FOLDER_PATH"] + readonly property string unlocalizedResourcesFolderPath: bundleSettingsProbe.xcodeSettings["UNLOCALIZED_RESOURCES_FOLDER_PATH"] + readonly property string versionsFolderPath: bundleSettingsProbe.xcodeSettings["VERSIONS_FOLDER_PATH"] + + // private properties + property string _productTypeIdentifier: Bundle.productTypeIdentifier(product.type) + property stringList _productTypeIdentifierChain: bundleSettingsProbe.productTypeIdentifierChain + + property bool _useXcodeBuildSpecs: true // false to use ONLY the qbs build specs + + readonly property var extraEnv: ({ + "PRODUCT_BUNDLE_IDENTIFIER": identifier + }) + + readonly property var qmakeEnv: { + return { + "BUNDLEIDENTIFIER": identifier, + "EXECUTABLE": product.targetName, + "FULL_VERSION": product.version || "1.0", // CFBundleVersion + "ICON": product.targetName, // ### QBS-73 + "LIBRARY": product.targetName, + "SHORT_VERSION": product.version || "1.0", // CFBundleShortVersionString + "TYPEINFO": signature // CFBundleSignature + }; + } + + readonly property var defaultInfoPlist: { + return { + CFBundleDevelopmentRegion: "en", // default localization + CFBundleDisplayName: product.targetName, // localizable + CFBundleExecutable: product.targetName, + CFBundleIdentifier: identifier, + CFBundleInfoDictionaryVersion: "6.0", + CFBundleName: product.targetName, // short display name of the bundle, localizable + CFBundlePackageType: packageType, + CFBundleShortVersionString: product.version || "1.0", // "release" version number, localizable + CFBundleSignature: signature, // legacy creator code in Mac OS Classic, can be ignored + CFBundleVersion: product.version || "1.0.0" // build version number, must be 3 octets + }; + } + + validate: { + if (!qbs.targetOS.contains("darwin")) + return; + if (!bundleSettingsProbe.found) { + var error = "Bundle product type " + _productTypeIdentifier + " is not supported."; + if ((_productTypeIdentifier || "").startsWith("com.apple.product-type.")) + error += " You may need to upgrade Xcode."; + throw error; + } + + var validator = new ModUtils.PropertyValidator("bundle"); + validator.setRequiredProperty("bundleName", bundleName); + validator.setRequiredProperty("infoPlistPath", infoPlistPath); + validator.setRequiredProperty("pbdevelopmentPlistPath", pbdevelopmentPlistPath); + validator.setRequiredProperty("pkgInfoPath", pkgInfoPath); + validator.setRequiredProperty("versionPlistPath", versionPlistPath); + validator.setRequiredProperty("executablePath", executablePath); + validator.setRequiredProperty("contentsFolderPath", contentsFolderPath); + validator.setRequiredProperty("documentationFolderPath", documentationFolderPath); + validator.setRequiredProperty("executableFolderPath", executableFolderPath); + validator.setRequiredProperty("executablesFolderPath", executablesFolderPath); + validator.setRequiredProperty("frameworksFolderPath", frameworksFolderPath); + validator.setRequiredProperty("javaFolderPath", javaFolderPath); + validator.setRequiredProperty("localizedResourcesFolderPath", localizedResourcesFolderPath); + validator.setRequiredProperty("pluginsFolderPath", pluginsFolderPath); + validator.setRequiredProperty("privateHeadersFolderPath", privateHeadersFolderPath); + validator.setRequiredProperty("publicHeadersFolderPath", publicHeadersFolderPath); + validator.setRequiredProperty("scriptsFolderPath", scriptsFolderPath); + validator.setRequiredProperty("sharedFrameworksFolderPath", sharedFrameworksFolderPath); + validator.setRequiredProperty("sharedSupportFolderPath", sharedSupportFolderPath); + validator.setRequiredProperty("unlocalizedResourcesFolderPath", unlocalizedResourcesFolderPath); + + if (packageType === "FMWK") { + validator.setRequiredProperty("frameworkVersion", frameworkVersion); + validator.setRequiredProperty("versionsFolderPath", versionsFolderPath); + } + + // extension and infoStringsPath might not be set + return validator.validate(); + } + + FileTagger { + fileTags: ["infoplist"] + patterns: ["Info.plist", "*-Info.plist"] + } + + Rule { + condition: qbs.targetOS.contains("darwin") + multiplex: true + requiresInputs: false // TODO: The resources property should probably be a tag instead. + inputs: ["infoplist", "partial_infoplist"] + + outputFileTags: ["bundle.input", "aggregate_infoplist"] + outputArtifacts: { + var artifacts = []; + var embed = ModUtils.moduleProperty(product, "embedInfoPlist"); + if (ModUtils.moduleProperty(product, "isBundle") || embed) { + artifacts.push({ + filePath: FileInfo.joinPaths( + product.destinationDirectory, product.name + "-Info.plist"), + fileTags: ["aggregate_infoplist"].concat(!embed ? ["bundle.input"] : []), + bundle: { + _bundleFilePath: FileInfo.joinPaths( + product.destinationDirectory, + ModUtils.moduleProperty(product, "infoPlistPath")), + } + }); + } + return artifacts; + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating Info.plist for " + product.name; + cmd.highlight = "codegen"; + cmd.infoPlist = ModUtils.moduleProperty(product, "infoPlist") || {}; + cmd.processInfoPlist = ModUtils.moduleProperty(product, "processInfoPlist"); + cmd.infoPlistFormat = ModUtils.moduleProperty(product, "infoPlistFormat"); + cmd.extraEnv = ModUtils.moduleProperty(product, "extraEnv"); + cmd.qmakeEnv = ModUtils.moduleProperty(product, "qmakeEnv"); + + cmd.buildEnv = product.moduleProperty("cpp", "buildEnv"); + + cmd.developerPath = product.moduleProperty("xcode", "developerPath"); + cmd.platformInfoPlist = product.moduleProperty("xcode", "platformInfoPlist"); + cmd.sdkSettingsPlist = product.moduleProperty("xcode", "sdkSettingsPlist"); + cmd.toolchainInfoPlist = product.moduleProperty("xcode", "toolchainInfoPlist"); + + cmd.osBuildVersion = product.moduleProperty("qbs", "hostOSBuildVersion"); + + cmd.sourceCode = function() { + var plist, process, key, i; + + // Contains the combination of default, file, and in-source keys and values + // Start out with the contents of this file as the "base", if given + var aggregatePlist = {}; + + for (i = 0; i < (inputs.infoplist || []).length; ++i) { + aggregatePlist = + BundleTools.infoPlistContents(inputs.infoplist[i].filePath); + infoPlistFormat = (infoPlistFormat === "same-as-input") + ? BundleTools.infoPlistFormat(inputs.infoplist[i].filePath) + : "xml1"; + break; + } + + // Add local key-value pairs (overrides equivalent keys specified in the file if + // one was given) + for (key in infoPlist) { + if (infoPlist.hasOwnProperty(key)) + aggregatePlist[key] = infoPlist[key]; + } + + // Do some postprocessing if desired + if (processInfoPlist) { + // Add default values to the aggregate plist if the corresponding keys + // for those values are not already present + var defaultValues = ModUtils.moduleProperty(product, "defaultInfoPlist"); + for (key in defaultValues) { + if (defaultValues.hasOwnProperty(key) && !(key in aggregatePlist)) + aggregatePlist[key] = defaultValues[key]; + } + + // Add keys from platform's Info.plist if not already present + var platformInfo = {}; + var sdkSettings = {}; + var toolchainInfo = {}; + if (developerPath) { + plist = new PropertyList(); + try { + plist.readFromFile(platformInfoPlist); + platformInfo = plist.toObject(); + } finally { + plist.clear(); + } + + var additionalProps = platformInfo["AdditionalInfo"]; + for (key in additionalProps) { + // override infoPlist? + if (additionalProps.hasOwnProperty(key) && !(key in aggregatePlist)) + aggregatePlist[key] = defaultValues[key]; + } + props = platformInfo['OverrideProperties']; + for (key in props) { + aggregatePlist[key] = props[key]; + } + + plist = new PropertyList(); + try { + plist.readFromFile(sdkSettingsPlist); + sdkSettings = plist.toObject(); + } finally { + plist.clear(); + } + + plist = new PropertyList(); + try { + plist.readFromFile(toolchainInfoPlist); + toolchainInfo = plist.toObject(); + } finally { + plist.clear(); + } + } + + aggregatePlist["BuildMachineOSBuild"] = osBuildVersion; + + // setup env + env = { + "SDK_NAME": sdkSettings["CanonicalName"], + "XCODE_VERSION_ACTUAL": toolchainInfo["DTXcode"], + "SDK_PRODUCT_BUILD_VERSION": toolchainInfo["DTPlatformBuild"], + "GCC_VERSION": platformInfo["DTCompiler"], + "XCODE_PRODUCT_BUILD_VERSION": platformInfo["DTPlatformBuild"], + "PLATFORM_PRODUCT_BUILD_VERSION": platformInfo["ProductBuildVersion"], + } + env["MAC_OS_X_PRODUCT_BUILD_VERSION"] = osBuildVersion; + + for (key in extraEnv) + env[key] = extraEnv[key]; + + for (key in buildEnv) + env[key] = buildEnv[key]; + + for (key in qmakeEnv) + env[key] = qmakeEnv[key]; + + var expander = new DarwinTools.PropertyListVariableExpander(); + expander.undefinedVariableFunction = function (key, varName) { + var msg = "Info.plist variable expansion encountered undefined variable '" + + varName + "' when expanding value for key '" + key + + "', defined in one of the following files:\n\t"; + var allFilePaths = []; + + for (i = 0; i < (inputs.infoplist || []).length; ++i) + allFilePaths.push(inputs.infoplist[i].filePath); + + if (platformInfoPlist) + allFilePaths.push(platformInfoPlist); + msg += allFilePaths.join("\n\t") + "\n"; + msg += "or in the bundle.infoPlist property of product '" + + product.name + "'"; + console.warn(msg); + }; + aggregatePlist = expander.expand(aggregatePlist, env); + + // Add keys from partial Info.plists from asset catalogs, XIBs, and storyboards. + for (var j = 0; j < (inputs.partial_infoplist || []).length; ++j) { + var partialInfoPlist = + BundleTools.infoPlistContents( + inputs.partial_infoplist[j].filePath) + || {}; + for (key in partialInfoPlist) { + if (partialInfoPlist.hasOwnProperty(key) + && aggregatePlist[key] === undefined) + aggregatePlist[key] = partialInfoPlist[key]; + } + } + + } + + // Anything with an undefined or otherwise empty value should be removed + // Only JSON-formatted plists can have null values, other formats error out + // This also follows Xcode behavior + DarwinTools.cleanPropertyList(aggregatePlist); + + if (infoPlistFormat === "same-as-input") + infoPlistFormat = "xml1"; + + var validFormats = [ "xml1", "binary1", "json" ]; + if (!validFormats.contains(infoPlistFormat)) + throw("Invalid Info.plist format " + infoPlistFormat + ". " + + "Must be in [xml1, binary1, json]."); + + // Write the plist contents in the format appropriate for the current platform + plist = new PropertyList(); + try { + plist.readFromObject(aggregatePlist); + plist.writeToFile(outputs.aggregate_infoplist[0].filePath, infoPlistFormat); + } finally { + plist.clear(); + } + } + return cmd; + } + } + + Rule { + condition: qbs.targetOS.contains("darwin") + multiplex: true + inputs: ["aggregate_infoplist"] + + outputFileTags: ["bundle.input", "pkginfo"] + outputArtifacts: { + var artifacts = []; + if (ModUtils.moduleProperty(product, "isBundle") && ModUtils.moduleProperty(product, "generatePackageInfo")) { + artifacts.push({ + filePath: FileInfo.joinPaths(product.destinationDirectory, "PkgInfo"), + fileTags: ["bundle.input", "pkginfo"], + bundle: { _bundleFilePath: FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "pkgInfoPath")) } + }); + } + return artifacts; + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating PkgInfo for " + product.name; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + var infoPlist = BundleTools.infoPlistContents(inputs.aggregate_infoplist[0].filePath); + + var pkgType = infoPlist['CFBundlePackageType']; + if (!pkgType) + throw("CFBundlePackageType not found in Info.plist; this should not happen"); + + var pkgSign = infoPlist['CFBundleSignature']; + if (!pkgSign) + throw("CFBundleSignature not found in Info.plist; this should not happen"); + + var pkginfo = new TextFile(outputs.pkginfo[0].filePath, TextFile.WriteOnly); + pkginfo.write(pkgType + pkgSign); + pkginfo.close(); + } + return cmd; + } + } + + Rule { + condition: qbs.targetOS.contains("darwin") + multiplex: true + inputs: ["bundle.input", + "aggregate_infoplist", "pkginfo", "hpp", + "icns", "xcent", + "compiled_ibdoc", "compiled_assetcatalog", + "xcode.provisioningprofile.main"] + + // Make sure the inputs of this rule are only those rules which produce outputs compatible + // with the type of the bundle being produced. + excludedInputs: Bundle.excludedAuxiliaryInputs(project, product) + + outputFileTags: [ + "bundle.content", + "bundle.symlink.headers", "bundle.symlink.private-headers", + "bundle.symlink.resources", "bundle.symlink.executable", + "bundle.symlink.version", "bundle.hpp", "bundle.resource", + "bundle.provisioningprofile", "bundle.content.copied", "bundle.application-executable", + "bundle.code-signature"] + outputArtifacts: { + var i, artifacts = []; + if (ModUtils.moduleProperty(product, "isBundle")) { + for (i in inputs["bundle.input"]) { + var fp = inputs["bundle.input"][i].moduleProperty("bundle", "_bundleFilePath"); + if (!fp) + throw("Artifact " + inputs["bundle.input"][i].filePath + " has no associated bundle file path"); + var extraTags = inputs["bundle.input"][i].fileTags.contains("application") + ? ["bundle.application-executable"] : []; + artifacts.push({ + filePath: fp, + fileTags: ["bundle.content", "bundle.content.copied"].concat(extraTags) + }); + } + + for (i in inputs["xcode.provisioningprofile.main"]) { + var ext = inputs["xcode.provisioningprofile.main"][i].fileName.split('.')[1]; + artifacts.push({ + filePath: FileInfo.joinPaths(product.destinationDirectory, + ModUtils.moduleProperty(product, + "contentsFolderPath"), + "embedded." + ext), + fileTags: ["bundle.provisioningprofile", "bundle.content"] + }); + } + + var packageType = ModUtils.moduleProperty(product, "packageType"); + var isShallow = ModUtils.moduleProperty(product, "isShallow"); + if (packageType === "FMWK" && !isShallow) { + var publicHeaders = ModUtils.moduleProperty(product, "publicHeaders"); + if (publicHeaders && publicHeaders.length) { + artifacts.push({ + filePath: FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "bundleName"), "Headers"), + fileTags: ["bundle.symlink.headers", "bundle.content"] + }); + } + + var privateHeaders = ModUtils.moduleProperty(product, "privateHeaders"); + if (privateHeaders && privateHeaders.length) { + artifacts.push({ + filePath: FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "bundleName"), "PrivateHeaders"), + fileTags: ["bundle.symlink.private-headers", "bundle.content"] + }); + } + + artifacts.push({ + filePath: FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "bundleName"), "Resources"), + fileTags: ["bundle.symlink.resources", "bundle.content"] + }); + + artifacts.push({ + filePath: FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "bundleName"), product.targetName), + fileTags: ["bundle.symlink.executable", "bundle.content"] + }); + + artifacts.push({ + filePath: FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "versionsFolderPath"), "Current"), + fileTags: ["bundle.symlink.version", "bundle.content"] + }); + } + + var headerTypes = ["public", "private"]; + for (var h in headerTypes) { + var sources = ModUtils.moduleProperty(product, headerTypes[h] + "Headers"); + var destination = FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, headerTypes[h] + "HeadersFolderPath")); + for (i in sources) { + artifacts.push({ + filePath: FileInfo.joinPaths(destination, FileInfo.fileName(sources[i])), + fileTags: ["bundle.hpp", "bundle.content"] + }); + } + } + + sources = ModUtils.moduleProperty(product, "resources"); + for (i in sources) { + destination = BundleTools.destinationDirectoryForResource(product, {baseDir: FileInfo.path(sources[i]), fileName: FileInfo.fileName(sources[i])}); + artifacts.push({ + filePath: FileInfo.joinPaths(destination, FileInfo.fileName(sources[i])), + fileTags: ["bundle.resource", "bundle.content"] + }); + } + + var wrapperPath = FileInfo.joinPaths( + product.destinationDirectory, + ModUtils.moduleProperty(product, "bundleName")); + for (var i = 0; i < artifacts.length; ++i) + artifacts[i].bundle = { wrapperPath: wrapperPath }; + + if (product.qbs.hostOS.contains("darwin") && product.xcode + && product.xcode.signingIdentity) { + artifacts.push({ + filePath: FileInfo.joinPaths(product.bundle.contentsFolderPath, "_CodeSignature/CodeResources"), + fileTags: ["bundle.code-signature", "bundle.content"] + }); + } + } + return artifacts; + } + + prepare: { + var i, cmd, commands = []; + var packageType = ModUtils.moduleProperty(product, "packageType"); + + var bundleType = "bundle"; + if (packageType === "APPL") + bundleType = "application"; + if (packageType === "FMWK") + bundleType = "framework"; + + // Product is unbundled + if (!product.bundle.isBundle) { + cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function () { }; + commands.push(cmd); + } + + var symlinks = outputs["bundle.symlink.version"]; + for (i in symlinks) { + cmd = new Command("ln", ["-sfn", ModUtils.moduleProperty(product, "frameworkVersion"), + symlinks[i].filePath]); + cmd.silent = true; + commands.push(cmd); + } + + var publicHeaders = outputs["bundle.symlink.headers"]; + for (i in publicHeaders) { + cmd = new Command("ln", ["-sfn", "Versions/Current/Headers", + publicHeaders[i].filePath]); + cmd.silent = true; + commands.push(cmd); + } + + var privateHeaders = outputs["bundle.symlink.private-headers"]; + for (i in privateHeaders) { + cmd = new Command("ln", ["-sfn", "Versions/Current/PrivateHeaders", + privateHeaders[i].filePath]); + cmd.silent = true; + commands.push(cmd); + } + + var resources = outputs["bundle.symlink.resources"]; + for (i in resources) { + cmd = new Command("ln", ["-sfn", "Versions/Current/Resources", + resources[i].filePath]); + cmd.silent = true; + commands.push(cmd); + } + + var executables = outputs["bundle.symlink.executable"]; + for (i in executables) { + cmd = new Command("ln", ["-sfn", FileInfo.joinPaths("Versions", "Current", product.targetName), + executables[i].filePath]); + cmd.silent = true; + commands.push(cmd); + } + + function sortedArtifactList(list, func) { + if (list) { + return list.sort(func || (function (a, b) { + return a.filePath.localeCompare(b.filePath); + })); + } + } + + var bundleInputs = sortedArtifactList(inputs["bundle.input"], function (a, b) { + return a.moduleProperty("bundle", "_bundleFilePath").localeCompare( + b.moduleProperty("bundle", "_bundleFilePath")); + }); + var bundleContents = sortedArtifactList(outputs["bundle.content.copied"]); + for (i in bundleContents) { + cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.source = bundleInputs[i].filePath; + cmd.destination = bundleContents[i].filePath; + cmd.sourceCode = function() { + File.copy(source, destination); + }; + commands.push(cmd); + } + + var provisioningProfiles = outputs["bundle.provisioningprofile"]; + for (i in provisioningProfiles) { + cmd = new JavaScriptCommand(); + cmd.description = "copying provisioning profile"; + cmd.highlight = "filegen"; + cmd.source = inputs["xcode.provisioningprofile.main"][i].filePath; + cmd.destination = provisioningProfiles[i].filePath; + cmd.sourceCode = function() { + File.copy(source, destination); + }; + commands.push(cmd); + } + + cmd = new JavaScriptCommand(); + cmd.description = "copying public headers"; + cmd.highlight = "filegen"; + cmd.sources = ModUtils.moduleProperty(product, "publicHeaders"); + cmd.destination = FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "publicHeadersFolderPath")); + cmd.sourceCode = function() { + var i; + for (var i in sources) { + File.copy(sources[i], FileInfo.joinPaths(destination, FileInfo.fileName(sources[i]))); + } + }; + if (cmd.sources && cmd.sources.length) + commands.push(cmd); + + cmd = new JavaScriptCommand(); + cmd.description = "copying private headers"; + cmd.highlight = "filegen"; + cmd.sources = ModUtils.moduleProperty(product, "privateHeaders"); + cmd.destination = FileInfo.joinPaths(product.destinationDirectory, ModUtils.moduleProperty(product, "privateHeadersFolderPath")); + cmd.sourceCode = function() { + var i; + for (var i in sources) { + File.copy(sources[i], FileInfo.joinPaths(destination, FileInfo.fileName(sources[i]))); + } + }; + if (cmd.sources && cmd.sources.length) + commands.push(cmd); + + cmd = new JavaScriptCommand(); + cmd.description = "copying resources"; + cmd.highlight = "filegen"; + cmd.sources = ModUtils.moduleProperty(product, "resources"); + cmd.sourceCode = function() { + var i; + for (var i in sources) { + var destination = BundleTools.destinationDirectoryForResource(product, {baseDir: FileInfo.path(sources[i]), fileName: FileInfo.fileName(sources[i])}); + File.copy(sources[i], FileInfo.joinPaths(destination, FileInfo.fileName(sources[i]))); + } + }; + if (cmd.sources && cmd.sources.length) + commands.push(cmd); + + if (product.moduleProperty("qbs", "hostOS").contains("darwin")) { + var actualSigningIdentity = product.moduleProperty("xcode", "actualSigningIdentity"); + var codesignDisplayName = product.moduleProperty("xcode", "actualSigningIdentityDisplayName"); + if (actualSigningIdentity) { + var args = product.moduleProperty("xcode", "codesignFlags") || []; + args.push("--force"); + args.push("--sign", actualSigningIdentity); + args = args.concat(DarwinTools._codeSignTimestampFlags(product)); + + for (var j in inputs.xcent) { + args.push("--entitlements", inputs.xcent[j].filePath); + break; // there should only be one + } + + // If this is a framework, we need to sign its versioned directory + if (bundleType === "framework") { + args.push(product.bundle.contentsFolderPath); + } else { + args.push(product.bundle.bundleName); + } + + cmd = new Command(product.moduleProperty("xcode", "codesignPath"), args); + cmd.workingDirectory = product.destinationDirectory; + cmd.description = "codesign " + + ModUtils.moduleProperty(product, "bundleName") + + " using " + codesignDisplayName + + " (" + actualSigningIdentity + ")"; + commands.push(cmd); + } + + if (bundleType === "application" + && product.moduleProperty("qbs", "targetOS").contains("macos")) { + cmd = new Command(ModUtils.moduleProperty(product, "lsregisterPath"), + ["-f", product.bundle.bundleName]); + cmd.description = "register " + ModUtils.moduleProperty(product, "bundleName"); + commands.push(cmd); + } + } + + return commands; + } + } +} diff --git a/share/qbs/modules/bundle/MacOSX-Package-Types.xcspec b/share/qbs/modules/bundle/MacOSX-Package-Types.xcspec new file mode 100644 index 00000000..b36353fc --- /dev/null +++ b/share/qbs/modules/bundle/MacOSX-Package-Types.xcspec @@ -0,0 +1,462 @@ +[ + { + "DefaultBuildSettings" : { + "EXECUTABLE_PATH" : "$(EXECUTABLE_NAME)", + "EXECUTABLE_PREFIX" : "", + "EXECUTABLE_SUFFIX" : "", + "EXECUTABLE_NAME" : "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)$(EXECUTABLE_VARIANT_SUFFIX)$(EXECUTABLE_SUFFIX)" + }, + "Identifier" : "com.apple.package-type.mach-o-executable", + "Type" : "PackageType", + "Name" : "Mach-O Executable", + "Description" : "Mach-O executable", + "ProductReference" : { + "FileType" : "compiled.mach-o.executable", + "Name" : "$(EXECUTABLE_NAME)", + "IsLaunchable" : "YES" + } + }, + { + "DefaultBuildSettings" : { + "EXECUTABLE_PATH" : "$(EXECUTABLE_NAME)", + "EXECUTABLE_PREFIX" : "", + "EXECUTABLE_SUFFIX" : "", + "EXECUTABLE_NAME" : "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)$(EXECUTABLE_VARIANT_SUFFIX)$(EXECUTABLE_SUFFIX)" + }, + "Identifier" : "com.apple.package-type.mach-o-objfile", + "Type" : "PackageType", + "Name" : "Mach-O Object File", + "Description" : "Mach-O Object File", + "ProductReference" : { + "FileType" : "compiled.mach-o.objfile", + "Name" : "$(EXECUTABLE_NAME)", + "IsLaunchable" : "NO" + } + }, + { + "DefaultBuildSettings" : { + "EXECUTABLE_PATH" : "$(EXECUTABLE_NAME)", + "EXECUTABLE_PREFIX" : "", + "EXECUTABLE_SUFFIX" : "", + "EXECUTABLE_NAME" : "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)$(EXECUTABLE_VARIANT_SUFFIX)$(EXECUTABLE_SUFFIX)" + }, + "Identifier" : "com.apple.package-type.mach-o-dylib", + "Type" : "PackageType", + "Name" : "Mach-O Dynamic Library", + "Description" : "Mach-O dynamic library", + "ProductReference" : { + "FileType" : "compiled.mach-o.dylib", + "Name" : "$(EXECUTABLE_NAME)", + "IsLaunchable" : "NO" + } + }, + { + "DefaultBuildSettings" : { + "EXECUTABLE_PATH" : "$(EXECUTABLE_NAME)", + "EXECUTABLE_PREFIX" : "lib", + "EXECUTABLE_SUFFIX" : ".a", + "EXECUTABLE_NAME" : "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)$(EXECUTABLE_VARIANT_SUFFIX)$(EXECUTABLE_SUFFIX)" + }, + "Identifier" : "com.apple.package-type.static-library", + "Type" : "PackageType", + "Name" : "Mach-O Static Library", + "Description" : "Mach-O static library", + "ProductReference" : { + "FileType" : "archive.ar", + "Name" : "$(EXECUTABLE_NAME)", + "IsLaunchable" : "NO" + } + }, + { + "DefaultBuildSettings" : { + "EXECUTABLE_PATH" : "$(EXECUTABLE_NAME)", + "EXECUTABLE_PREFIX" : "", + "EXECUTABLE_SUFFIX" : ".dylib", + "EXECUTABLE_NAME" : "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)$(EXECUTABLE_VARIANT_SUFFIX)$(EXECUTABLE_SUFFIX)" + }, + "Identifier" : "com.apple.package-type.mach-o-bundle", + "Type" : "PackageType", + "Name" : "Mach-O Loadable", + "Description" : "Mach-O loadable", + "ProductReference" : { + "FileType" : "compiled.mach-o.bundle", + "Name" : "$(EXECUTABLE_NAME)", + "IsLaunchable" : "NO" + } + }, + { + "DefaultBuildSettings" : { + "PUBLIC_HEADERS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/Headers", + "EXECUTABLE_NAME" : "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)$(EXECUTABLE_VARIANT_SUFFIX)$(EXECUTABLE_SUFFIX)", + "EXECUTABLE_PREFIX" : "", + "PLUGINS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/PlugIns", + "DOCUMENTATION_FOLDER_PATH" : "$(LOCALIZED_RESOURCES_FOLDER_PATH)\/Documentation", + "EXECUTABLES_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/Executables", + "INFOSTRINGS_PATH" : "$(LOCALIZED_RESOURCES_FOLDER_PATH)\/InfoPlist.strings", + "INFOPLIST_PATH" : "$(CONTENTS_FOLDER_PATH)\/Info.plist", + "EXECUTABLE_SUFFIX" : "", + "VERSIONPLIST_PATH" : "$(CONTENTS_FOLDER_PATH)\/version.plist", + "SHARED_SUPPORT_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/SharedSupport", + "EXECUTABLE_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/MacOS", + "PBDEVELOPMENTPLIST_PATH" : "$(CONTENTS_FOLDER_PATH)\/pbdevelopment.plist", + "FRAMEWORKS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/Frameworks", + "LOCALIZED_RESOURCES_FOLDER_PATH" : "$(UNLOCALIZED_RESOURCES_FOLDER_PATH)\/$(DEVELOPMENT_LANGUAGE).lproj", + "SCRIPTS_FOLDER_PATH" : "$(UNLOCALIZED_RESOURCES_FOLDER_PATH)\/Scripts", + "WRAPPER_PREFIX" : "", + "PRIVATE_HEADERS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/PrivateHeaders", + "CONTENTS_FOLDER_PATH" : "$(WRAPPER_NAME)\/Contents", + "WRAPPER_NAME" : "$(WRAPPER_PREFIX)$(PRODUCT_NAME)$(WRAPPER_SUFFIX)", + "PKGINFO_PATH" : "$(CONTENTS_FOLDER_PATH)\/PkgInfo", + "EXECUTABLE_PATH" : "$(EXECUTABLE_FOLDER_PATH)\/$(EXECUTABLE_NAME)", + "UNLOCALIZED_RESOURCES_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/Resources", + "JAVA_FOLDER_PATH" : "$(UNLOCALIZED_RESOURCES_FOLDER_PATH)\/Java", + "SHARED_FRAMEWORKS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/SharedFrameworks", + "WRAPPER_SUFFIX" : ".bundle" + }, + "Identifier" : "com.apple.package-type.wrapper", + "Type" : "PackageType", + "Name" : "Wrapper", + "Description" : "Wrapper", + "ProductReference" : { + "FileType" : "wrapper.cfbundle", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + } + }, + { + "ProductReference" : { + "FileType" : "wrapper.cfbundle", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + }, + "DefaultBuildSettings" : { + "CONTENTS_FOLDER_PATH" : "$(WRAPPER_NAME)", + "UNLOCALIZED_RESOURCES_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)", + "SHALLOW_BUNDLE" : "YES", + "EXECUTABLE_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)" + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.wrapper", + "Name" : "Wrapper (Shallow)", + "Identifier" : "com.apple.package-type.wrapper.shallow", + "Description" : "Shallow Wrapper" + }, + { + "ProductReference" : { + "FileType" : "wrapper.application", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "YES" + }, + "DefaultBuildSettings" : { + "GENERATE_PKGINFO_FILE" : "YES" + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.wrapper", + "Name" : "Application Wrapper", + "Identifier" : "com.apple.package-type.wrapper.application", + "Description" : "Application Wrapper" + }, + { + "ProductReference" : { + "FileType" : "wrapper.application", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "YES" + }, + "DefaultBuildSettings" : { + "UNLOCALIZED_RESOURCES_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)", + "SHALLOW_BUNDLE" : "YES", + "GENERATE_PKGINFO_FILE" : "YES" + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.wrapper.shallow", + "Name" : "Application Wrapper (Shallow)", + "Identifier" : "com.apple.package-type.wrapper.application.shallow", + "Description" : "Shallow Application Wrapper" + }, + { + "ProductReference" : { + "FileType" : "wrapper.cfbundle", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + }, + "DefaultBuildSettings" : { + "PRIVATE_HEADERS_FOLDER_PATH" : "$(KEXT_FRAMEWORK)\/Contents\/PrivateHeaders\/$(KEXT_FAMILY_NAME)", + "PUBLIC_HEADERS_FOLDER_PATH" : "$(KEXT_FRAMEWORK)\/Contents\/Headers\/$(KEXT_FAMILY_NAME)" + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.wrapper", + "Name" : "Kernel Extension Wrapper", + "Identifier" : "com.apple.package-type.wrapper.kernel-extension", + "Description" : "Kernel Extension Wrapper" + }, + { + "ProductReference" : { + "FileType" : "wrapper.cfbundle", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + }, + "DefaultBuildSettings" : { + "PRIVATE_HEADERS_FOLDER_PATH" : "$(KEXT_FRAMEWORK)\/Contents\/PrivateHeaders\/$(KEXT_FAMILY_NAME)", + "PUBLIC_HEADERS_FOLDER_PATH" : "$(KEXT_FRAMEWORK)\/Contents\/Headers\/$(KEXT_FAMILY_NAME)", + "SHALLOW_BUNDLE" : "YES" + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.wrapper.shallow", + "Name" : "Kernel Extension Wrapper (Shallow)", + "Identifier" : "com.apple.package-type.wrapper.kernel-extension.shallow", + "Description" : "Shallow Kernel Extension Wrapper" + }, + { + "DefaultBuildSettings" : { + "PUBLIC_HEADERS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/Headers", + "EXECUTABLE_NAME" : "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)$(EXECUTABLE_VARIANT_SUFFIX)$(EXECUTABLE_SUFFIX)", + "EXECUTABLE_PREFIX" : "", + "PLUGINS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/PlugIns", + "DOCUMENTATION_FOLDER_PATH" : "$(LOCALIZED_RESOURCES_FOLDER_PATH)\/Documentation", + "EXECUTABLES_FOLDER_PATH" : "$(LOCALIZED_RESOURCES_FOLDER_PATH)", + "INFOPLIST_PATH" : "$(UNLOCALIZED_RESOURCES_FOLDER_PATH)\/Info.plist", + "EXECUTABLE_SUFFIX" : "", + "INFOPLISTSTRINGS_PATH" : "$(LOCALIZED_RESOURCES_FOLDER_PATH)\/InfoPlist.strings", + "VERSIONPLIST_PATH" : "$(UNLOCALIZED_RESOURCES_FOLDER_PATH)\/version.plist", + "SHARED_SUPPORT_FOLDER_PATH" : "$(UNLOCALIZED_RESOURCES_FOLDER_PATH)", + "EXECUTABLE_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)", + "PBDEVELOPMENTPLIST_PATH" : "$(CONTENTS_FOLDER_PATH)\/pbdevelopment.plist", + "VERSIONS_FOLDER_PATH" : "$(WRAPPER_NAME)\/Versions", + "FRAMEWORKS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/Frameworks", + "CODESIGNING_FOLDER_PATH" : "$(TARGET_BUILD_DIR)\/$(CONTENTS_FOLDER_PATH)", + "LOCALIZED_RESOURCES_FOLDER_PATH" : "$(UNLOCALIZED_RESOURCES_FOLDER_PATH)\/$(DEVELOPMENT_LANGUAGE).lproj", + "SCRIPTS_FOLDER_PATH" : "$(UNLOCALIZED_RESOURCES_FOLDER_PATH)\/Scripts", + "WRAPPER_PREFIX" : "", + "PRIVATE_HEADERS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/PrivateHeaders", + "CURRENT_VERSION" : "Current", + "PKGINFO_PATH" : "$(WRAPPER_NAME)\/PkgInfo", + "WRAPPER_NAME" : "$(WRAPPER_PREFIX)$(PRODUCT_NAME)$(WRAPPER_SUFFIX)", + "CONTENTS_FOLDER_PATH" : "$(VERSIONS_FOLDER_PATH)\/$(FRAMEWORK_VERSION)", + "EXECUTABLE_PATH" : "$(EXECUTABLE_FOLDER_PATH)\/$(EXECUTABLE_NAME)", + "UNLOCALIZED_RESOURCES_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/Resources", + "JAVA_FOLDER_PATH" : "$(UNLOCALIZED_RESOURCES_FOLDER_PATH)\/Java", + "SHARED_FRAMEWORKS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)\/SharedFrameworks", + "WRAPPER_SUFFIX" : ".framework" + }, + "Identifier" : "com.apple.package-type.wrapper.framework", + "Type" : "PackageType", + "Name" : "Framework Wrapper", + "Description" : "Framework wrapper", + "ProductReference" : { + "FileType" : "wrapper.framework", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + } + }, + { + "ProductReference" : { + "FileType" : "wrapper.framework.static", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + }, + "DefaultBuildSettings" : { + "EXECUTABLE_SUFFIX" : "", + "EXECUTABLE_NAME" : "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)$(EXECUTABLE_VARIANT_SUFFIX)$(EXECUTABLE_SUFFIX)", + "EXECUTABLE_PREFIX" : "" + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.wrapper.framework", + "Name" : "Mach-O Static Framework", + "Identifier" : "com.apple.package-type.wrapper.framework.static", + "Description" : "Mach-O static framework" + }, + { + "ProductReference" : { + "FileType" : "wrapper.framework", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + }, + "DefaultBuildSettings" : { + "UNLOCALIZED_RESOURCES_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)", + "CONTENTS_FOLDER_PATH" : "$(WRAPPER_NAME)", + "SHALLOW_BUNDLE" : "YES", + "VERSIONS_FOLDER_PATH" : "$(WRAPPER_NAME)" + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.wrapper.framework", + "Name" : "Shallow Framework Wrapper", + "Identifier" : "com.apple.package-type.wrapper.framework.shallow", + "Description" : "Shallow framework wrapper" + }, + { + "ProductReference" : { + "FileType" : "wrapper.cfbundle", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + }, + "DefaultBuildSettings" : { + "WRAPPER_SUFFIX" : "xctest" + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.wrapper", + "Name" : "Unit Test Bundle", + "Identifier" : "com.apple.package-type.bundle.unit-test", + "Description" : "Unit Test Bundle" + }, + { + "ProductReference" : { + "FileType" : "wrapper.cfbundle", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + }, + "DefaultBuildSettings" : { + "WRAPPER_SUFFIX" : "octest" + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.wrapper", + "Name" : "OCUnit Test Bundle", + "Identifier" : "com.apple.package-type.bundle.ocunit-test", + "Description" : "OCUnit Test Bundle" + }, + { + "ProductReference" : { + "FileType" : "folder", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + }, + "DefaultBuildSettings" : { + "EXECUTABLE_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)", + "JAVA_FOLDER_PATH" : "$(UNLOCALIZED_RESOURCES_FOLDER_PATH)", + "INFOSTRINGS_PATH" : "$(LOCALIZED_RESOURCES_FOLDER_PATH)\/ContentInfo.strings", + "INFOPLIST_PATH" : "$(WRAPPER_NAME)\/ContentInfo.plist", + "WRAPPER_SUFFIX" : "", + "UNLOCALIZED_RESOURCES_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)", + "DOCUMENTATION_FOLDER_PATH" : "$(LOCALIZED_RESOURCES_FOLDER_PATH)", + "EXECUTABLES_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)", + "LOCALIZED_RESOURCES_FOLDER_PATH" : "$(UNLOCALIZED_RESOURCES_FOLDER_PATH)\/$(DEVELOPMENT_LANGUAGE).lproj", + "PLUGINS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)", + "PUBLIC_HEADERS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)", + "SHARED_SUPPORT_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)", + "SHARED_FRAMEWORKS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)", + "PRIVATE_HEADERS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)", + "SCRIPTS_FOLDER_PATH" : "$(UNLOCALIZED_RESOURCES_FOLDER_PATH)", + "FRAMEWORKS_FOLDER_PATH" : "$(CONTENTS_FOLDER_PATH)" + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.wrapper", + "Name" : "In-App Purchase Content", + "Identifier" : "com.apple.package-type.in-app-purchase-content", + "Description" : "In-App Purchase Content" + }, + { + "ProductReference" : { + "FileType" : "wrapper.xpc-service", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + }, + "DefaultBuildSettings" : { + "WRAPPER_SUFFIX" : ".xpc" + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.wrapper", + "Name" : "XPC Service", + "Identifier" : "com.apple.package-type.xpc-service", + "Description" : "XPC Service" + }, + { + "ProductReference" : { + "FileType" : "wrapper.app-extension", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + }, + "DefaultBuildSettings" : { + "WRAPPER_SUFFIX" : ".pluginkit" + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.xpc-service", + "Name" : "PlugInKit PlugIn", + "Identifier" : "com.apple.package-type.pluginkit-plugin", + "Description" : "PlugInKit PlugIn" + }, + { + "ProductReference" : { + "FileType" : "wrapper.app-extension", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + }, + "DefaultBuildSettings" : { + "WRAPPER_SUFFIX" : ".appex" + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.pluginkit-plugin", + "Name" : "App Extension", + "Identifier" : "com.apple.package-type.app-extension", + "Description" : "App Extension" + }, + { + "ProductReference" : { + "FileType" : "wrapper.spotlight-importer", + "Name" : "$(WRAPPER_NAME)", + "IsLaunchable" : "NO" + }, + "DefaultBuildSettings" : { + + }, + "Type" : "PackageType", + "BasedOn" : "com.apple.package-type.wrapper", + "Name" : "Spotlight Importer", + "Identifier" : "com.apple.package-type.spotlight-importer", + "Description" : "Spotlight Importer" + }, + { + "DefaultBuildSettings" : { + "EXECUTABLE_PATH" : "$(EXECUTABLE_NAME)", + "JAVA_MAKE_ZIPFILE" : "NO", + "JAVA_ARCHIVE_CLASSES" : "YES", + "EXECUTABLE_PREFIX" : "", + "EXECUTABLE_SUFFIX" : ".jar", + "EXECUTABLE_NAME" : "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)$(EXECUTABLE_SUFFIX)" + }, + "Identifier" : "com.apple.package-type.jarfile", + "Type" : "PackageType", + "Name" : "Jar File", + "Description" : "Jar file", + "ProductReference" : { + "FileType" : "archive.jar", + "Name" : "$(EXECUTABLE_NAME)", + "IsLaunchable" : "NO" + } + }, + { + "DefaultBuildSettings" : { + "EXECUTABLE_PATH" : "$(EXECUTABLE_NAME)", + "JAVA_MAKE_ZIPFILE" : "YES", + "JAVA_ARCHIVE_CLASSES" : "YES", + "EXECUTABLE_PREFIX" : "", + "EXECUTABLE_SUFFIX" : ".zip", + "EXECUTABLE_NAME" : "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)$(EXECUTABLE_SUFFIX)" + }, + "Identifier" : "com.apple.package-type.zipfile", + "Type" : "PackageType", + "Name" : "Zip File", + "Description" : "Zip file", + "ProductReference" : { + "FileType" : "archive.zip", + "Name" : "$(EXECUTABLE_NAME)", + "IsLaunchable" : "NO" + } + }, + { + "DefaultBuildSettings" : { + "EXECUTABLE_PATH" : "$(EXECUTABLE_NAME)", + "JAVA_ARCHIVE_CLASSES" : "NO", + "EXECUTABLE_PREFIX" : "", + "EXECUTABLE_SUFFIX" : "", + "EXECUTABLE_NAME" : "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME)$(EXECUTABLE_SUFFIX)" + }, + "Identifier" : "com.apple.package-type.javaclassfolder", + "Type" : "PackageType", + "Name" : "Class Folder", + "Description" : "Class folder", + "ProductReference" : { + "FileType" : "wrapper.java-classfolder", + "Name" : "$(EXECUTABLE_NAME)", + "IsLaunchable" : "NO" + } + } +] diff --git a/share/qbs/modules/bundle/MacOSX-Product-Types.xcspec b/share/qbs/modules/bundle/MacOSX-Product-Types.xcspec new file mode 100644 index 00000000..8d845086 --- /dev/null +++ b/share/qbs/modules/bundle/MacOSX-Product-Types.xcspec @@ -0,0 +1,590 @@ +[ + { + "IconNamePrefix" : "TargetExecutable", + "DefaultBuildProperties" : { + "REZ_EXECUTABLE" : "YES", + "FULL_PRODUCT_NAME" : "$(EXECUTABLE_NAME)", + "LIBRARY_FLAG_NOSPACE" : "YES", + "FRAMEWORK_FLAG_PREFIX" : "-framework", + "INSTALL_PATH" : "\/usr\/local\/bin", + "GCC_INLINES_ARE_PRIVATE_EXTERN" : "YES", + "GCC_DYNAMIC_NO_PIC" : "NO", + "GCC_SYMBOLS_PRIVATE_EXTERN" : "YES", + "CODE_SIGNING_ALLOWED" : "YES", + "STRIP_STYLE" : "all", + "EXECUTABLE_PREFIX" : "", + "MACH_O_TYPE" : "mh_execute", + "EXECUTABLE_SUFFIX" : "", + "LIBRARY_FLAG_PREFIX" : "-l" + }, + "PackageTypes" : [ + "com.apple.package-type.mach-o-executable" + ], + "Type" : "ProductType", + "DefaultTargetName" : "Command-line Tool", + "Name" : "Command-line Tool", + "Identifier" : "com.apple.product-type.tool", + "Description" : "Standalone command-line tool", + "Class" : "PBXToolProductType" + }, + { + "IconNamePrefix" : "TargetExecutable", + "IsJava" : "YES", + "PackageTypes" : [ + "com.apple.package-type.jarfile", + "com.apple.package-type.zipfile", + "com.apple.package-type.javaclassfolder" + ], + "Type" : "ProductType", + "DefaultTargetName" : "Java Command-line Tool", + "Name" : "Java Command-line Tool", + "Identifier" : "com.apple.product-type.tool.java", + "Description" : "Java Command-line tool", + "DefaultBuildProperties" : { + "REZ_EXECUTABLE" : "YES", + "INSTALL_PATH" : "\/usr\/local\/bin", + "FULL_PRODUCT_NAME" : "$(EXECUTABLE_NAME)" + } + }, + { + "IconNamePrefix" : "TargetPlugin", + "DefaultBuildProperties" : { + "DEAD_CODE_STRIPPING" : "NO", + "REZ_EXECUTABLE" : "YES", + "LINK_WITH_STANDARD_LIBRARIES" : "NO", + "FULL_PRODUCT_NAME" : "$(EXECUTABLE_NAME)", + "LIBRARY_FLAG_NOSPACE" : "YES", + "FRAMEWORK_FLAG_PREFIX" : "-framework", + "INSTALL_PATH" : "$(HOME)\/Objects", + "SKIP_INSTALL" : "YES", + "GCC_INLINES_ARE_PRIVATE_EXTERN" : "YES", + "KEEP_PRIVATE_EXTERNS" : "YES", + "EXECUTABLE_EXTENSION" : "o", + "PUBLIC_HEADERS_FOLDER_PATH" : "\/usr\/local\/include", + "MACH_O_TYPE" : "mh_object", + "EXECUTABLE_SUFFIX" : ".$(EXECUTABLE_EXTENSION)", + "LIBRARY_FLAG_PREFIX" : "-l", + "PRIVATE_HEADERS_FOLDER_PATH" : "\/usr\/local\/include", + "STRIP_STYLE" : "debugging" + }, + "PackageTypes" : [ + "com.apple.package-type.mach-o-objfile" + ], + "Type" : "ProductType", + "DefaultTargetName" : "Object File", + "Name" : "Object File", + "Identifier" : "com.apple.product-type.objfile", + "Description" : "Object File", + "Class" : "XCStandaloneExecutableProductType" + }, + { + "IconNamePrefix" : "TargetLibrary", + "DefaultBuildProperties" : { + "LIBRARY_FLAG_PREFIX" : "-l", + "STRIP_STYLE" : "debugging", + "REZ_EXECUTABLE" : "YES", + "FULL_PRODUCT_NAME" : "$(EXECUTABLE_NAME)", + "LD_DYLIB_INSTALL_NAME" : "$(DYLIB_INSTALL_NAME_BASE:standardizepath)\/$(EXECUTABLE_PATH)", + "DYLIB_COMPATIBILITY_VERSION" : "1", + "INSTALL_PATH" : "\/usr\/local\/lib", + "FRAMEWORK_FLAG_PREFIX" : "-framework", + "LIBRARY_FLAG_NOSPACE" : "YES", + "GCC_INLINES_ARE_PRIVATE_EXTERN" : "YES", + "CODE_SIGNING_ALLOWED" : "YES", + "CODE_SIGNING_REQUIRED" : "NO", + "EXECUTABLE_EXTENSION" : "dylib", + "PUBLIC_HEADERS_FOLDER_PATH" : "\/usr\/local\/include", + "DYLIB_INSTALL_NAME_BASE" : "$(INSTALL_PATH)", + "EXECUTABLE_SUFFIX" : ".$(EXECUTABLE_EXTENSION)", + "PRIVATE_HEADERS_FOLDER_PATH" : "\/usr\/local\/include", + "MACH_O_TYPE" : "mh_dylib", + "DYLIB_CURRENT_VERSION" : "1" + }, + "PackageTypes" : [ + "com.apple.package-type.mach-o-dylib" + ], + "Type" : "ProductType", + "DefaultTargetName" : "Dynamic Library", + "Name" : "Dynamic Library", + "Identifier" : "com.apple.product-type.library.dynamic", + "Description" : "Dynamic library", + "Class" : "PBXDynamicLibraryProductType" + }, + { + "IconNamePrefix" : "TargetLibrary", + "DefaultBuildProperties" : { + "STRIP_STYLE" : "debugging", + "CLANG_ENABLE_MODULE_DEBUGGING" : "NO", + "REZ_EXECUTABLE" : "YES", + "FULL_PRODUCT_NAME" : "$(EXECUTABLE_NAME)", + "LIBRARY_FLAG_NOSPACE" : "YES", + "FRAMEWORK_FLAG_PREFIX" : "-framework", + "INSTALL_PATH" : "\/usr\/local\/lib", + "EXECUTABLE_EXTENSION" : "a", + "EXECUTABLE_PREFIX" : "lib", + "PUBLIC_HEADERS_FOLDER_PATH" : "\/usr\/local\/include", + "EXECUTABLE_SUFFIX" : ".$(EXECUTABLE_EXTENSION)", + "LIBRARY_FLAG_PREFIX" : "-l", + "PRIVATE_HEADERS_FOLDER_PATH" : "\/usr\/local\/include", + "MACH_O_TYPE" : "staticlib" + }, + "AlwaysPerformSeparateStrip" : "YES", + "PackageTypes" : [ + "com.apple.package-type.static-library" + ], + "Type" : "ProductType", + "DefaultTargetName" : "Static Library", + "Name" : "Static Library", + "Identifier" : "com.apple.product-type.library.static", + "Description" : "Static library", + "Class" : "PBXStaticLibraryProductType" + }, + { + "IconNamePrefix" : "TargetPlugin", + "IsJava" : "YES", + "PackageTypes" : [ + "com.apple.package-type.jarfile", + "com.apple.package-type.zipfile", + "com.apple.package-type.javaclassfolder" + ], + "Type" : "ProductType", + "DefaultTargetName" : "Java Library", + "Name" : "Java Library", + "Identifier" : "com.apple.product-type.library.java.archive", + "Description" : "Java library packaged as a Jar file, Zip file, or class folder", + "DefaultBuildProperties" : { + "INSTALL_PATH" : "\/usr\/local\/lib", + "FULL_PRODUCT_NAME" : "$(PRODUCT_NAME)" + } + }, + { + "HasInfoPlistStrings" : "YES", + "Description" : "Generic bundle", + "HasInfoPlist" : "YES", + "Name" : "Bundle", + "Class" : "PBXBundleProductType", + "DefaultTargetName" : "Bundle", + "DefaultBuildProperties" : { + "LIBRARY_FLAG_NOSPACE" : "YES", + "WRAPPER_NAME" : "$(WRAPPER_PREFIX)$(PRODUCT_NAME)$(WRAPPER_SUFFIX)", + "FRAMEWORK_FLAG_PREFIX" : "-framework", + "GCC_INLINES_ARE_PRIVATE_EXTERN" : "YES", + "WRAPPER_SUFFIX" : ".$(WRAPPER_EXTENSION)", + "FULL_PRODUCT_NAME" : "$(WRAPPER_NAME)", + "WRAPPER_EXTENSION" : "bundle", + "CODE_SIGNING_ALLOWED" : "YES", + "WRAPPER_PREFIX" : "", + "STRIP_STYLE" : "non-global", + "MACH_O_TYPE" : "mh_bundle", + "LIBRARY_FLAG_PREFIX" : "-l" + }, + "PackageTypes" : [ + "com.apple.package-type.wrapper", + "com.apple.package-type.wrapper.shallow" + ], + "IsWrapper" : "YES", + "Type" : "ProductType", + "Identifier" : "com.apple.product-type.bundle", + "IconNamePrefix" : "TargetPlugin" + }, + { + "PackageTypes" : [ + "com.apple.package-type.wrapper.shallow" + ], + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.bundle", + "Name" : "Bundle (Shallow)", + "Identifier" : "com.apple.product-type.bundle.shallow", + "Description" : "Bundle (Shallow)", + "Class" : "PBXBundleProductType" + }, + { + "Description" : "Application", + "Class" : "PBXApplicationProductType", + "Name" : "Application", + "RunpathSearchPathForEmbeddedFrameworks" : "@executable_path\/..\/Frameworks", + "ValidateEmbeddedBinaries" : "YES", + "ProvisioningProfileSupported" : "YES", + "DefaultTargetName" : "Application", + "DefaultBuildProperties" : { + "INSTALL_PATH" : "$(LOCAL_APPS_DIR)", + "WRAPPER_EXTENSION" : "app", + "GCC_DYNAMIC_NO_PIC" : "NO", + "STRIP_STYLE" : "all", + "CODE_SIGNING_ALLOWED" : "YES", + "GCC_INLINES_ARE_PRIVATE_EXTERN" : "YES", + "WRAPPER_SUFFIX" : ".$(WRAPPER_EXTENSION)", + "GCC_SYMBOLS_PRIVATE_EXTERN" : "YES", + "MACH_O_TYPE" : "mh_execute" + }, + "BasedOn" : "com.apple.product-type.bundle", + "ProvisioningProfileRequired" : "NO", + "PackageTypes" : [ + "com.apple.package-type.wrapper.application" + ], + "Type" : "ProductType", + "CanEmbedCompilerSanitizerLibraries" : "YES", + "Identifier" : "com.apple.product-type.application", + "IconNamePrefix" : "TargetApp" + }, + { + "PackageTypes" : [ + "com.apple.package-type.wrapper.application.shallow" + ], + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.application", + "Name" : "Application (Shallow Bundle)", + "Identifier" : "com.apple.product-type.application.shallow", + "Description" : "Application (Shallow Bundle)", + "Class" : "PBXApplicationProductType" + }, + { + "DefaultBuildProperties" : { + "PKGINFO_PATH" : "", + "INFOPLIST_PATH" : "" + }, + "IsJava" : "YES", + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.application", + "Name" : "Java Application", + "Identifier" : "com.apple.product-type.application.java", + "Description" : "Java Application", + "DefaultTargetName" : "Java Application" + }, + { + "IconNamePrefix" : "TargetFramework", + "DefaultTargetName" : "Framework", + "DefaultBuildProperties" : { + "CODE_SIGNING_REQUIRES_TEAM" : "YES", + "LD_DYLIB_INSTALL_NAME" : "$(DYLIB_INSTALL_NAME_BASE:standardizepath)\/$(EXECUTABLE_PATH)", + "CODE_SIGNING_REQUIRED" : "NO", + "CODE_SIGNING_ALLOWED" : "YES", + "INSTALL_PATH" : "$(LOCAL_LIBRARY_DIR)\/Frameworks", + "WRAPPER_SUFFIX" : ".$(WRAPPER_EXTENSION)", + "WRAPPER_EXTENSION" : "framework", + "FRAMEWORK_VERSION" : "A", + "ENTITLEMENTS_REQUIRED" : "NO", + "STRIP_STYLE" : "debugging", + "DYLIB_INSTALL_NAME_BASE" : "$(INSTALL_PATH)", + "MACH_O_TYPE" : "mh_dylib" + }, + "PackageTypes" : [ + "com.apple.package-type.wrapper.framework" + ], + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.bundle", + "Name" : "Framework", + "Identifier" : "com.apple.product-type.framework", + "Description" : "Framework", + "Class" : "PBXFrameworkProductType" + }, + { + "PackageTypes" : [ + "com.apple.package-type.wrapper.framework.shallow" + ], + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.framework", + "Name" : "Framework (Shallow Bundle)", + "Identifier" : "com.apple.product-type.framework.shallow", + "Description" : "Framework (Shallow Bundle)", + "Class" : "PBXFrameworkProductType" + }, + { + "IconNamePrefix" : "TargetFramework", + "DefaultTargetName" : "Static Framework", + "DefaultBuildProperties" : { + "WRAPPER_EXTENSION" : "framework", + "DYLIB_INSTALL_NAME_BASE" : "", + "CODE_SIGNING_ALLOWED" : "NO", + "FRAMEWORK_VERSION" : "A", + "LD_DYLIB_INSTALL_NAME" : "", + "GCC_INLINES_ARE_PRIVATE_EXTERN" : "NO", + "INSTALL_PATH" : "$(LOCAL_LIBRARY_DIR)\/Frameworks", + "WRAPPER_SUFFIX" : ".$(WRAPPER_EXTENSION)", + "MACH_O_TYPE" : "staticlib" + }, + "AlwaysPerformSeparateStrip" : "YES", + "PackageTypes" : [ + "com.apple.package-type.wrapper.framework.static" + ], + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.framework", + "Name" : "Static Framework", + "Identifier" : "com.apple.product-type.framework.static", + "Description" : "Static Framework", + "Class" : "XCStaticFrameworkProductType" + }, + { + "DefaultTargetName" : "Kernel Extension", + "DefaultBuildProperties" : { + "GCC_ENABLE_BUILTIN_FUNCTIONS" : "NO", + "MODULE_START" : "0", + "PRIVATE_HEADERS_FOLDER_PATH" : "$(KEXT_FRAMEWORK)\/Contents\/PrivateHeaders\/$(KEXT_FAMILY_NAME)", + "WRAPPER_SUFFIX" : ".$(WRAPPER_EXTENSION)", + "MACH_O_TYPE" : "mh_execute", + "GCC_ENABLE_KERNEL_DEVELOPMENT" : "YES", + "PRODUCT_TYPE_CPLUSPLUSFLAGS" : "$(inherited) $(KEXT_CPLUSPLUSFLAGS)", + "MODULE_STOP" : "0", + "GCC_ENABLE_FLOATING_POINT_LIBRARY_CALLS" : "YES", + "GCC_PRODUCT_TYPE_PREPROCESSOR_DEFINITIONS" : "$(inherited) KERNEL KERNEL_PRIVATE DRIVER_PRIVATE APPLE NeXT", + "GCC_DISABLE_STATIC_FUNCTION_INLINING" : "YES", + "ENABLE_APPLE_KEXT_CODE_GENERATION" : "YES", + "CODE_SIGNING_ALLOWED" : "YES", + "GCC_FORCE_CPU_SUBTYPE_ALL" : "YES", + "PRODUCT_SPECIFIC_LDFLAGS" : "$(inherited) $(KEXT_LDFLAGS)", + "WRAPPER_EXTENSION" : "kext", + "KERNEL_EXTENSION_HEADER_SEARCH_PATHS" : "$(KERNEL_FRAMEWORK)\/PrivateHeaders $(KERNEL_FRAMEWORK_HEADERS)", + "GCC_INLINES_ARE_PRIVATE_EXTERN" : "NO", + "PRODUCT_TYPE_CFLAGS" : "$(inherited) $(KEXT_CFLAGS)", + "KEXT_FRAMEWORK_NAME" : "Kernel", + "GCC_NO_COMMON_BLOCKS" : "YES", + "GCC_ENABLE_PASCAL_STRINGS" : "NO", + "PUBLIC_HEADERS_FOLDER_PATH" : "$(KEXT_FRAMEWORK)\/Contents\/Headers\/$(KEXT_FAMILY_NAME)", + "GCC_ENABLE_FUNCTION_INLINING" : "YES", + "KERNEL_FRAMEWORK_HEADERS" : "$(KERNEL_FRAMEWORK)\/Headers", + "KEXT_FAMILY_NAME" : "family", + "KEXT_FRAMEWORK" : "$(SYSTEM_LIBRARY_DIR)\/Frameworks\/$(KEXT_FRAMEWORK_NAME).framework", + "GCC_ENABLE_CPP_EXCEPTIONS" : "NO", + "GCC_ENABLE_CPP_RTTI" : "NO", + "MODULE_NAME" : "com.company.driver.modulename", + "GCC_USE_STANDARD_INCLUDE_SEARCHING" : "NO", + "KERNEL_FRAMEWORK" : "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)\/Frameworks\/Kernel.framework", + "MODULE_VERSION" : "1.0", + "INSTALL_PATH" : "$(DEFAULT_KEXT_INSTALL_PATH)", + "PRODUCT_TYPE_HEADER_SEARCH_PATHS" : "$(inherited) $(KERNEL_EXTENSION_HEADER_SEARCH_PATHS)", + "GCC_CHECK_RETURN_VALUE_OF_OPERATOR_NEW" : "YES", + "STRIP_STYLE" : "debugging" + }, + "PackageTypes" : [ + "com.apple.package-type.wrapper.kernel-extension", + "com.apple.package-type.wrapper.kernel-extension.shallow" + ], + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.bundle", + "Name" : "Kernel Extension", + "Identifier" : "com.apple.product-type.kernel-extension", + "Description" : "Kernel extension", + "Class" : "XCKernelExtensionProductType" + }, + { + "DefaultBuildProperties" : { + + }, + "PackageTypes" : [ + "com.apple.package-type.wrapper.kernel-extension.shallow" + ], + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.kernel-extension", + "Name" : "Kernel Extension (Shallow)", + "Identifier" : "com.apple.product-type.kernel-extension.shallow", + "Description" : "Kernel extension (shallow)", + "Class" : "XCKernelExtensionProductType" + }, + { + "DefaultTargetName" : "IOKit Kernel Extension", + "DefaultBuildProperties" : { + "CODE_SIGNING_ALLOWED" : "YES" + }, + "PackageTypes" : [ + "com.apple.package-type.wrapper.kernel-extension", + "com.apple.package-type.wrapper.kernel-extension.shallow" + ], + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.kernel-extension", + "Name" : "IOKit Kernel Extension", + "Identifier" : "com.apple.product-type.kernel-extension.iokit", + "Description" : "IOKit Kernel extension", + "Class" : "XCKernelExtensionProductType" + }, + { + "DefaultTargetName" : "IOKit Kernel Extension (Shallow)", + "DefaultBuildProperties" : { + + }, + "PackageTypes" : [ + "com.apple.package-type.wrapper.kernel-extension.shallow" + ], + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.kernel-extension", + "Name" : "IOKit Kernel Extension (Shallow)", + "Identifier" : "com.apple.product-type.kernel-extension.iokit.shallow", + "Description" : "IOKit Kernel extension (Shallow)", + "Class" : "XCKernelExtensionProductType" + }, + { + "DefaultBuildProperties" : { + "TEST_FRAMEWORK_SEARCH_PATHS" : [ + "$(inherited)", + "$(PLATFORM_DIR)\/Developer\/Library\/Frameworks" + ], + "PRODUCT_SPECIFIC_LDFLAGS" : "-framework XCTest", + "WRAPPER_EXTENSION" : "xctest", + "PRODUCT_TYPE_FRAMEWORK_SEARCH_PATHS" : "$(TEST_FRAMEWORK_SEARCH_PATHS)" + }, + "PackageTypes" : [ + "com.apple.package-type.bundle.unit-test" + ], + "CanEmbedCompilerSanitizerLibraries" : "YES", + "IsUnitTest" : "YES", + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.bundle", + "Name" : "Unit Test Bundle", + "Identifier" : "com.apple.product-type.bundle.unit-test", + "Description" : "Unit Test Bundle", + "Class" : "PBXXCTestBundleProductType" + }, + { + "DefaultBuildProperties" : { + "TEST_FRAMEWORK_SEARCH_PATHS" : [ + "$(inherited)", + "$(PLATFORM_DIR)\/Developer\/Library\/Frameworks" + ], + "PRODUCT_SPECIFIC_LDFLAGS" : "-framework XCTest", + "WRAPPER_EXTENSION" : "xctest", + "USES_XCTRUNNER" : "YES", + "PRODUCT_TYPE_FRAMEWORK_SEARCH_PATHS" : "$(TEST_FRAMEWORK_SEARCH_PATHS)" + }, + "PackageTypes" : [ + "com.apple.package-type.bundle.unit-test" + ], + "ProvisioningProfileSupported" : "YES", + "IsUITest" : "YES", + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.bundle.unit-test", + "Name" : "UI Testing Bundle", + "Identifier" : "com.apple.product-type.bundle.ui-testing", + "Description" : "UI Testing Bundle", + "Class" : "PBXXCTestBundleProductType" + }, + { + "DefaultBuildProperties" : { + "WRAPPER_EXTENSION" : "octest" + }, + "PackageTypes" : [ + "com.apple.package-type.bundle.ocunit-test" + ], + "IsUnitTest" : "YES", + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.bundle", + "Name" : "OCUnit Test Bundle", + "Identifier" : "com.apple.product-type.bundle.ocunit-test", + "Description" : "OCUnit Test Bundle", + "Class" : "PBXBundleProductType" + }, + { + "HasInfoPlistStrings" : "NO", + "PackageTypes" : [ + "com.apple.package-type.in-app-purchase-content" + ], + "HasInfoPlist" : "YES", + "IsWrapper" : "YES", + "Type" : "ProductType", + "DefaultBuildProperties" : { + "FULL_PRODUCT_NAME" : "$(WRAPPER_NAME)" + }, + "Name" : "In-App Purchase Content", + "Identifier" : "com.apple.product-type.in-app-purchase-content", + "Description" : "In-App Purchase Content", + "Class" : "PBXBundleProductType" + }, + { + "IconNamePrefix" : "XPCService", + "DefaultTargetName" : "XPC Service", + "CanEmbedCompilerSanitizerLibraries" : "YES", + "DefaultBuildProperties" : { + "MACH_O_TYPE" : "mh_execute", + "WRAPPER_EXTENSION" : "xpc" + }, + "PackageTypes" : [ + "com.apple.package-type.xpc-service" + ], + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.bundle", + "Name" : "XPC Service", + "Identifier" : "com.apple.product-type.xpc-service", + "Description" : "XPC Service", + "Class" : "PBXBundleProductType" + }, + { + "IconNamePrefix" : "XPCService", + "DefaultTargetName" : "PlugInKit PlugIn", + "DefaultBuildProperties" : { + "WRAPPER_EXTENSION" : "pluginkit", + "PRODUCT_SPECIFIC_LDFLAGS" : "$(SDKROOT)\/System\/Library\/PrivateFrameworks\/PlugInKit.framework\/PlugInKit" + }, + "PackageTypes" : [ + "com.apple.package-type.pluginkit-plugin" + ], + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.xpc-service", + "Name" : "PlugInKit PlugIn", + "Identifier" : "com.apple.product-type.pluginkit-plugin", + "Description" : "PlugInKit PlugIn", + "Class" : "PBXBundleProductType" + }, + { + "Description" : "App Extension", + "Class" : "PBXBundleProductType", + "Name" : "App Extension", + "ProvisioningProfileSupported" : "YES", + "DefaultTargetName" : "App Extension", + "DefaultBuildProperties" : { + "APPLICATION_EXTENSION_API_ONLY" : "YES", + "PRODUCT_SPECIFIC_LDFLAGS" : "-e _NSExtensionMain", + "WRAPPER_EXTENSION" : "appex", + "CODE_SIGNING_ALLOWED" : "YES" + }, + "BasedOn" : "com.apple.product-type.pluginkit-plugin", + "ProvisioningProfileRequired" : "NO", + "PackageTypes" : [ + "com.apple.package-type.app-extension" + ], + "Type" : "ProductType", + "Identifier" : "com.apple.product-type.app-extension", + "IconNamePrefix" : "AppExtension" + }, + { + "Description" : "Xcode Extension", + "Class" : "PBXBundleProductType", + "Name" : "Xcode Extension", + "ProvisioningProfileSupported" : "YES", + "DefaultTargetName" : "Xcode Extension", + "DefaultBuildProperties" : { + "APPLICATION_EXTENSION_API_ONLY" : "YES", + "PRODUCT_SPECIFIC_LDFLAGS" : "-e _XCExtensionMain -lXcodeExtension -weak_framework XcodeKit", + "PRODUCT_TYPE_LIBRARY_SEARCH_PATHS" : [ + "$(inherited)", + "$(DEVELOPER_USR_DIR)\/lib" + ], + "CODE_SIGNING_ALLOWED" : "YES", + "WRAPPER_EXTENSION" : "appex", + "PRODUCT_TYPE_FRAMEWORK_SEARCH_PATHS" : [ + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)" + ] + }, + "BasedOn" : "com.apple.product-type.app-extension", + "ProvisioningProfileRequired" : "NO", + "PackageTypes" : [ + "com.apple.package-type.app-extension" + ], + "Type" : "ProductType", + "Identifier" : "com.apple.product-type.xcode-extension", + "IconNamePrefix" : "XcodeExtension" + }, + { + "DefaultTargetName" : "Spotlight", + "DefaultBuildProperties" : { + "CODE_SIGNING_ALLOWED" : "YES" + }, + "PackageTypes" : [ + "com.apple.package-type.spotlight-importer" + ], + "Type" : "ProductType", + "BasedOn" : "com.apple.product-type.bundle", + "Name" : "Spotlight Importer", + "Identifier" : "com.apple.product-type.spotlight-importer", + "Description" : "Spotlight Importer", + "Class" : "PBXBundleProductType" + } +] diff --git a/share/qbs/modules/bundle/bundle.js b/share/qbs/modules/bundle/bundle.js new file mode 100644 index 00000000..6d930570 --- /dev/null +++ b/share/qbs/modules/bundle/bundle.js @@ -0,0 +1,300 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 DarwinTools = require("qbs.DarwinTools"); +var ModUtils = require("qbs.ModUtils"); +var Process = require("qbs.Process"); +var Utilities = require("qbs.Utilities"); + +// HACK: Workaround until the PropertyList extension is supported cross-platform +var TextFile = require("qbs.TextFile"); +var PropertyList2 = (function () { + function PropertyList2() { + } + PropertyList2.prototype.readFromFile = function (filePath) { + var str; + var process = new Process(); + try { + if (process.exec("plutil", ["-convert", "json", "-o", "-", filePath], false) === 0) { + str = process.readStdOut(); + } else { + var tf = new TextFile(filePath); + try { + str = tf.readAll(); + } finally { + tf.close(); + } + } + } finally { + process.close(); + } + + if (str) + this.obj = JSON.parse(str); + }; + PropertyList2.prototype.toObject = function () { + return this.obj; + }; + PropertyList2.prototype.clear = function () { + }; + return PropertyList2; +}()); + +// Order is significant due to productTypeIdentifier() search path +var _productTypeIdentifiers = { + "inapppurchase": "com.apple.product-type.in-app-purchase-content", + "applicationextension": "com.apple.product-type.app-extension", + "xpcservice": "com.apple.product-type.xpc-service", + "application": "com.apple.product-type.application", + "dynamiclibrary": "com.apple.product-type.framework", + "loadablemodule": "com.apple.product-type.bundle", + "staticlibrary": "com.apple.product-type.framework.static", + "kernelmodule": "com.apple.product-type.kernel-extension" +}; + +function productTypeIdentifier(productType) { + for (var k in _productTypeIdentifiers) { + if (productType.contains(k)) + return _productTypeIdentifiers[k]; + } + return "com.apple.package-type.wrapper"; +} + +function excludedAuxiliaryInputs(project, product) { + var chain = product.moduleProperty("bundle", "_productTypeIdentifierChain"); + var bestPossibleType; + for (var i = chain.length - 1; i >= 0; --i) { + switch (chain[i]) { + case "com.apple.product-type.bundle": + bestPossibleType = "loadablemodule"; + break; + case "com.apple.product-type.framework": + bestPossibleType = "dynamiclibrary"; + break; + case "com.apple.product-type.framework.static": + bestPossibleType = "staticlibrary"; + break; + case "com.apple.product-type.application": + case "com.apple.product-type.xpc-service": + bestPossibleType = "application"; + break; + } + } + + var excluded = []; + var possibleTypes = ["application", "dynamiclibrary", "staticlibrary", "loadablemodule"]; + for (i = 0; i < possibleTypes.length; ++i) { + if (possibleTypes[i] !== bestPossibleType) + excluded.push(possibleTypes[i]); + } + + return excluded; +} + +function packageType(productTypeIdentifier) { + switch (productTypeIdentifier) { + case "com.apple.product-type.in-app-purchase-content": + return undefined; + case "com.apple.product-type.app-extension": + case "com.apple.product-type.xpc-service": + return "XPC!"; + case "com.apple.product-type.application": + return "APPL"; + case "com.apple.product-type.framework": + case "com.apple.product-type.framework.static": + return "FMWK"; + case "com.apple.product-type.kernel-extension": + case "com.apple.product-type.kernel-extension.iokit": + return "KEXT"; + default: + return "BNDL"; + } +} + +function _assign(target, source) { + if (source) { + for (var k in source) { + if (source.hasOwnProperty(k)) + target[k] = source[k]; + } + return target; + } +} + +function macOSSpecsPath(version, developerPath) { + if (Utilities.versionCompare(version, "12") >= 0) { + return FileInfo.joinPaths( + developerPath, "Platforms", "MacOSX.platform", "Developer", "Library", "Xcode", + "PrivatePlugIns", "IDEOSXSupportCore.ideplugin", "Contents", "Resources"); + } + return FileInfo.joinPaths( + developerPath, "Platforms", "MacOSX.platform", "Developer", "Library", "Xcode", + "Specifications"); +} + +var XcodeBuildSpecsReader = (function () { + function XcodeBuildSpecsReader(specsPath, separator, additionalSettings, useShallowBundles) { + this._additionalSettings = additionalSettings; + this._useShallowBundles = useShallowBundles; + var i; + var plist = new PropertyList2(); + var plist2 = new PropertyList2(); + try { + plist.readFromFile(specsPath + ["/MacOSX", "Package", "Types.xcspec"].join(separator)); + plist2.readFromFile(specsPath + ["/MacOSX", "Product", "Types.xcspec"].join(separator)); + this._packageTypes = plist.toObject(); + this._productTypes = plist2.toObject(); + this._types = {}; + for (i = 0; i < this._packageTypes.length; ++i) + this._types[this._packageTypes[i]["Identifier"]] = this._packageTypes[i]; + for (i = 0; i < this._productTypes.length; ++i) + this._types[this._productTypes[i]["Identifier"]] = this._productTypes[i]; + } finally { + plist.clear(); + plist2.clear(); + } + } + XcodeBuildSpecsReader.prototype.productTypeIdentifierChain = function (typeIdentifier) { + var ids = [typeIdentifier]; + var obj = this._types[typeIdentifier]; + var parentId = obj && obj["BasedOn"]; + if (parentId) + return ids.concat(this.productTypeIdentifierChain(parentId)); + return ids; + }; + XcodeBuildSpecsReader.prototype.settings = function (typeIdentifier, recursive, skipPackageTypes) { + // Silently use shallow bundles when preferred since it seems to be some sort of automatic + // shadowing mechanism. For example, this matches Xcode behavior where static frameworks + // are shallow even though no such product specification exists, and also seems to match + // other behavior i.e. where productType in pbxproj files is never explicitly shallow. + if (this._useShallowBundles && this._types[typeIdentifier + ".shallow"] && !skipPackageTypes) + typeIdentifier += ".shallow"; + + var typesObject = this._types[typeIdentifier]; + if (typesObject) { + var buildProperties = {}; + + if (recursive) { + // Get all the settings for the product's package type + if (!skipPackageTypes && typesObject["PackageTypes"]) { + for (var k = 0; k < typesObject["PackageTypes"].length; ++k) { + var props = this.settings(typesObject["PackageTypes"][k], recursive, true); + for (var y in props) { + if (props.hasOwnProperty(y)) + buildProperties[y] = props[y]; + } + break; + } + } + + // Get all the settings for the product's inherited product type + if (typesObject["BasedOn"]) { + // We'll only do the auto shallow substitution for wrapper package types... + // this ensures that in-app purchase content bundles are non-shallow on both + // macOS and iOS, for example (which matches Xcode behavior) + var isWrapper = false; + if (typesObject["ProductReference"]) { + var fileType = typesObject["ProductReference"]["FileType"]; + if (fileType) + isWrapper = fileType.startsWith("wrapper."); + } + + // Prevent recursion loop if this spec's base plus .shallow would be the same + // as the current spec's identifier + var baseIdentifier = typesObject["BasedOn"]; + if (this._useShallowBundles && isWrapper + && this._types[baseIdentifier + ".shallow"] + && typeIdentifier !== baseIdentifier + ".shallow") + baseIdentifier += ".shallow"; + + props = this.settings(baseIdentifier, recursive, true); + for (y in props) { + if (props.hasOwnProperty(y)) + buildProperties[y] = props[y]; + } + } + } + + + if (typesObject["Type"] === "PackageType") { + props = typesObject["DefaultBuildSettings"]; + for (y in props) { + if (props.hasOwnProperty(y)) + buildProperties[y] = props[y]; + } + } + + if (typesObject["Type"] === "ProductType") { + props = typesObject["DefaultBuildProperties"]; + for (y in props) { + if (props.hasOwnProperty(y)) + buildProperties[y] = props[y]; + } + } + + return buildProperties; + } + }; + XcodeBuildSpecsReader.prototype.setting = function (typeIdentifier, settingName) { + var obj = this.settings(typeIdentifier, false); + if (obj) { + return obj[settingName]; + } + }; + XcodeBuildSpecsReader.prototype.expandedSettings = function (typeIdentifier, baseSettings) { + var obj = this.settings(typeIdentifier, true); + if (obj) { + for (var k in obj) + obj[k] = this.expandedSetting(typeIdentifier, baseSettings, k); + return obj; + } + }; + XcodeBuildSpecsReader.prototype.expandedSetting = function (typeIdentifier, baseSettings, + settingName) { + var obj = baseSettings || {}; + obj = _assign(obj, this.settings(typeIdentifier, true)); + if (obj) { + for (var x in this._additionalSettings) { + var additionalSetting = this._additionalSettings[x]; + if (additionalSetting !== undefined) + obj[x] = additionalSetting; + } + var setting = obj[settingName]; + var original; + while (original !== setting) { + original = setting; + setting = DarwinTools.expandPlistEnvironmentVariables({ key: setting }, obj, true)["key"]; + } + return setting; + } + }; + return XcodeBuildSpecsReader; +}()); diff --git a/share/qbs/modules/capnproto/capnproto.js b/share/qbs/modules/capnproto/capnproto.js new file mode 100644 index 00000000..dff37932 --- /dev/null +++ b/share/qbs/modules/capnproto/capnproto.js @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://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 File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var Utilities = require("qbs.Utilities"); + +function validateCompiler(name, path) { + if (!File.exists(path)) + throw "Cannot find executable '" + name + "'. Please set the compilerPath " + + "property or make sure the compiler is found in PATH"; +} + +function validatePlugin(name, path) { + if (!name) + throw "pluginName is not set"; + if (!File.exists(path)) + throw "Cannot find plugin '" + name + "'. Please set the pluginPath " + + "property or make sure the plugin is found in PATH"; +} + +function getOutputDir(module, input) { + var outputDir = module._outputDir; + var importPaths = module.importPaths; + if (importPaths.length !== 0) { + var canonicalInput = File.canonicalFilePath(FileInfo.path(input.filePath)); + for (var i = 0; i < importPaths.length; ++i) { + var path = File.canonicalFilePath(importPaths[i]); + + if (canonicalInput.startsWith(path)) { + return outputDir + "/" + FileInfo.relativePath(path, canonicalInput); + } + } + } + return outputDir; +} + +function artifact(outputDir, input, tag, suffix) { + return { + fileTags: [tag], + filePath: FileInfo.joinPaths(outputDir, FileInfo.baseName(input.fileName) + suffix), + cpp: { + includePaths: [].concat(input.cpp.includePaths, outputDir), + warningLevel: "none", + } + }; +} + +function doPrepare(module, product, input, outputs, lang) +{ + var outputDir = FileInfo.path(outputs["cpp"][0].filePath); + + var args = []; + args.push("--output=" + module.pluginPath + ":" + outputDir); + args.push("--src-prefix=" + FileInfo.path(input.filePath)); + + var importPaths = module.importPaths; + importPaths.forEach(function(path) { + if (!FileInfo.isAbsolutePath(path)) + path = FileInfo.joinPaths(product.sourceDirectory, path); + args.push("--import-path", path); + }); + + args.push(input.filePath); + + var cmd = new Command(module.compilerPath, args); + cmd.highlight = "codegen"; + cmd.description = "generating " + lang + " files for " + input.fileName; + return [cmd]; +} diff --git a/share/qbs/modules/capnproto/capnprotobase.qbs b/share/qbs/modules/capnproto/capnprotobase.qbs new file mode 100644 index 00000000..e557f7b7 --- /dev/null +++ b/share/qbs/modules/capnproto/capnprotobase.qbs @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://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.Probes +import "capnproto.js" as HelperFunctions + +Module { + property string compilerName: "capnpc" + property string compilerPath: compilerProbe.filePath + + property string pluginName + property string pluginPath: pluginProbe.filePath + + property pathList importPaths: [] + + property string _outputDir: product.buildDirectory + "/capnp" + + Probes.BinaryProbe { + id: compilerProbe + names: compilerName ? [compilerName] : [] + } + + Probes.BinaryProbe { + id: pluginProbe + names: pluginName ? [pluginName] : [] + } + + FileTagger { + patterns: ["*.capnp"] + fileTags: ["capnproto.input"]; + } + + validate: { + HelperFunctions.validateCompiler(compilerName, compilerPath); + HelperFunctions.validatePlugin(pluginName, pluginPath); + } +} diff --git a/share/qbs/modules/capnproto/cpp/capnprotocpp.qbs b/share/qbs/modules/capnproto/cpp/capnprotocpp.qbs new file mode 100644 index 00000000..e8c61dc8 --- /dev/null +++ b/share/qbs/modules/capnproto/cpp/capnprotocpp.qbs @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://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 "../capnprotobase.qbs" as CapnProtoBase +import "../capnproto.js" as HelperFunctions + +CapnProtoBase { + property bool useRpc: false + + Depends { name: "cpp" } + Depends { name: "capnp" } + Depends { name: "capnp-rpc"; condition: useRpc } + + pluginName: "capnpc-c++" + + cpp.systemIncludePaths: _outputDir + cpp.cxxLanguageVersion: "c++14" + + Rule { + inputs: ["capnproto.input"] + outputFileTags: ["hpp", "cpp"] + outputArtifacts: { + var outputDir = HelperFunctions.getOutputDir(input.capnproto.cpp, input); + var result = [ + HelperFunctions.artifact(outputDir, input, "hpp", ".capnp.h"), + HelperFunctions.artifact(outputDir, input, "cpp", ".capnp.c++") + ]; + return result; + } + + prepare: { + var result = HelperFunctions.doPrepare( + input.capnproto.cpp, product, input, outputs, "cpp"); + return result; + } + } +} diff --git a/share/qbs/modules/cli/CLIModule.qbs b/share/qbs/modules/cli/CLIModule.qbs new file mode 100644 index 00000000..d0ca120e --- /dev/null +++ b/share/qbs/modules/cli/CLIModule.qbs @@ -0,0 +1,195 @@ +// base for Common Language Infrastructure modules +import qbs.FileInfo +import qbs.ModUtils +import "cli.js" as CLI + +Module { + Depends { name: "bundle"; condition: qbs.targetOS.contains("darwin") } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + + condition: false + + property string warningLevel: 'all' // 'none', 'all' + property bool treatWarningsAsErrors: false + property string architecture: "anycpu" // for the CLI this is a much better default than qbs.architecture + property string optimization: qbs.optimization + property bool debugInformation: qbs.debugInformation + property stringList defines + property stringList platformDefines: qbs.enableDebugCode ? [] : ["NDEBUG"] + property stringList compilerDefines + PropertyOptions { + name: "compilerDefines" + description: "preprocessor macros that are defined when using this particular compiler" + } + + property pathList libraryPaths + property string csharpCompilerName + property string csharpCompilerPath: FileInfo.joinPaths(toolchainInstallPath, csharpCompilerName) + property string vbCompilerName + property string vbCompilerPath: FileInfo.joinPaths(toolchainInstallPath, vbCompilerName) + property string fsharpCompilerName + property string fsharpCompilerPath: FileInfo.joinPaths(toolchainInstallPath, fsharpCompilerName) + property string resgenName: "resgen" + property string resgenPath: FileInfo.joinPaths(toolchainInstallPath, resgenName) + property string dynamicLibrarySuffix: ".dll" + property string executableSuffix: ".exe" + property string netmoduleSuffix: ".netmodule" + property string debugInfoSuffix + property stringList dynamicLibraries // list of names, will be linked with /reference:name + property stringList netmodules // list of netmodule files, will be linked with /addmodule:name + + property bool generateManifestFile: true + + property string toolchainInstallPath + + property stringList compilerFlags + PropertyOptions { + name: "compilerFlags" + description: "additional compiler flags" + } + + // Platform properties. Those are intended to be set by the toolchain setup + // and are prepended to the corresponding user properties. + property stringList platformCompilerFlags + + FileTagger { + patterns: ["*.cs", "*.CS"] + fileTags: ["cli.csharp"] + } + + FileTagger { + patterns: ["*.vb", "*.VB"] + fileTags: ["cli.vb"] + } + + FileTagger { + patterns: ["*.fs", "*.FS"] + fileTags: ["cli.fsharp"] + } + + FileTagger { + patterns: ["*.fsi", "*.FSI"] + fileTags: ["cli.fsharp_signature"] + } + + FileTagger { + patterns: ["*.resx", "*.RESX"] + fileTags: ["cli.resx"] + } + + validate: { + var validator = new ModUtils.PropertyValidator("cli"); + validator.setRequiredProperty("toolchainInstallPath", toolchainInstallPath); + validator.validate(); + } + + setupBuildEnvironment: { + var v = new ModUtils.EnvironmentVariable("PATH", product.qbs.pathListSeparator, + product.qbs.hostOS.contains("windows")); + v.prepend(product.cli.toolchainInstallPath); + v.set(); + } + + Rule { + id: cliApplication + multiplex: true + inputs: ["cli.csharp", "cli.vb", "cli.fsharp"] + inputsFromDependencies: ["cli.netmodule", "dynamiclibrary", "cli.resources"] + + Artifact { + fileTags: ["application"] + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + + product.moduleProperty(product.moduleName, + "executableSuffix")) + } + + Artifact { + fileTags: ["debuginfo_app"] + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + + product.moduleProperty(product.moduleName, + "debugInfoSuffix")) + } + + prepare: { + return CLI.prepareCompiler(product, inputs, outputs.application[0]); + } + } + + Rule { + id: cliDynamicLibrary + multiplex: true + inputs: ["cli.csharp", "cli.vb", "cli.fsharp"] + inputsFromDependencies: ["cli.netmodule", "dynamiclibrary", "cli.resources"] + + Artifact { + fileTags: ["dynamiclibrary"] + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + + product.moduleProperty(product.moduleName, + "dynamicLibrarySuffix")) + } + + Artifact { + fileTags: ["debuginfo_dll"] + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + + product.moduleProperty(product.moduleName, + "debugInfoSuffix")) + } + + prepare: { + return CLI.prepareCompiler(product, inputs, outputs.dynamiclibrary[0]); + } + } + + Rule { + id: netmodule + multiplex: true + inputs: ["cli.csharp", "cli.vb", "cli.fsharp"] + inputsFromDependencies: ["cli.netmodule", "dynamiclibrary", "cli.resources"] + + Artifact { + fileTags: ["cli.netmodule"] + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + + product.moduleProperty(product.moduleName, + "netmoduleSuffix")) + } + + Artifact { + fileTags: ["debuginfo_netmodule"] + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + + product.moduleProperty(product.moduleName, + "debugInfoSuffix")) + } + + prepare: { + return CLI.prepareCompiler(product, inputs, outputs["cli.netmodule"][0]); + } + } + + Rule { + inputs: ["cli.resx"] + + Artifact { + fileTags: ["cli.resources"] + filePath: FileInfo.joinPaths(product.destinationDirectory, + input.completeBaseName + ".resources") + } + + prepare: { + var args = [ input.filePath, output.filePath ]; + var cmd = new Command(ModUtils.moduleProperty(product, "resgenPath"), args); + cmd.description = "building " + input.fileName; + cmd.highlight = "compiler"; + cmd.workingDirectory = FileInfo.path(output.filePath); + return cmd; + } + } +} diff --git a/share/qbs/modules/cli/cli.js b/share/qbs/modules/cli/cli.js new file mode 100644 index 00000000..38833ac5 --- /dev/null +++ b/share/qbs/modules/cli/cli.js @@ -0,0 +1,181 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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"); + +function prepareCompiler(product, inputs, output) { + var i; + var args = ["/nologo"]; + + var platformDefines = product.moduleProperty("cli", "platformDefines"); + var compilerDefines = product.moduleProperty("cli", "compilerDefines"); + var defines = product.moduleProperty("cli", "defines"); + var platformCompilerFlags = product.moduleProperty("cli", "platformCompilerFlags"); + var compilerFlags = product.moduleProperty("cli", "compilerFlags") + var libraryPaths = product.moduleProperty("cli", "libraryPaths"); + var dynamicLibraries = product.moduleProperty("cli", "dynamicLibraries"); + var netmodules = product.moduleProperty("cli", "netmodules"); + var warnAsError = product.moduleProperty("cli", "treatWarningsAsErrors"); + var warningLevel = product.moduleProperty("cli", "warningLevel"); + var debugInformation = product.moduleProperty("cli", "debugInformation"); + var optimization = product.moduleProperty("cli", "optimization"); + var architecture = product.moduleProperty("cli", "architecture"); + + var csharpCompilerPath = product.moduleProperty("cli", "csharpCompilerPath"); + var vbCompilerPath = product.moduleProperty("cli", "vbCompilerPath"); + var fsharpCompilerPath = product.moduleProperty("cli", "fsharpCompilerPath"); + + var compilers = { + "cli.csharp": csharpCompilerPath, + "cli.vb": vbCompilerPath, + "cli.fsharp": fsharpCompilerPath + }; + + var pathFunction = product.moduleProperty("qbs", "hostOS").contains("windows") + ? FileInfo.toWindowsSeparators + : function (path) { return path; }; + + var outputDescription = "assembly"; + if (output.fileTags.contains("application")) { + args.push("/target:" + (product.consoleApplication === false ? "winexe" : "exe")); + } else if (output.fileTags.contains("dynamiclibrary")) { + args.push("/target:library"); + } else if (output.fileTags.contains("cli.netmodule")) { + args.push("/target:module"); + outputDescription = "netmodule"; + } + + // Make sure our input files are all the same language + var keys = Object.keys(inputs); + var language; + for (i in keys) { + if (Object.keys(compilers).contains(keys[i])) { + if (language) + throw("You cannot compile source files in more than one CLI language into a single " + outputDescription + "."); + language = keys[i]; + } + } + + if (!compilers[language]) { + throw("No CLI compiler available to compile " + language + " files."); + } + + // docs state "/platform is not available in the development environment in Visual C# Express" + // does this mean from the IDE or the compiler itself shipped with Express does not support it? + if (architecture !== undefined) { + if (architecture === "x86" || + architecture === "anycpu" || + architecture === "anycpu32bitpreferred") // requires .NET 4.5 + ; + else if (architecture === "x86_64") + architecture = "x64"; + else if (architecture === "ia64") + architecture = "Itanium"; + else if (architecture === "arm") + architecture = "ARM"; + else + throw("Invalid CLI architecture: " + architecture); + + args.push("/platform:" + architecture); + } + + if (debugInformation !== undefined) + args.push("/debug" + (debugInformation ? "+" : "-")); + + if (optimization !== undefined) + args.push("/optimize" + (optimization !== "none" ? "+" : "-")); + + if (warnAsError !== undefined) + args.push("/warnaserror" + (warnAsError ? "+" : "-")); + + if (warningLevel !== undefined) { + if (language === "cli.vb") { + if (warningLevel === "none" || warningLevel === "0") + args.push("/quiet"); + } else { + if (warningLevel === "all") + warningLevel = "4"; + else if (warningLevel === "none") + warningLevel = "0"; + args.push("/warn:" + warningLevel); + } + } + + // Preprocessor defines + var allDefines = (platformDefines || []).concat(compilerDefines || []).concat(defines || []); + if (allDefines.length > 0) + args.push("/define:" + allDefines.join(";")); + + // Library search paths + for (i in libraryPaths) + args.push("/lib:" + libraryPaths.join(",")); + + // Dependent libraries + for (i in dynamicLibraries) { + args.push("/reference:" + + dynamicLibraries[i] + + product.moduleProperty("cli", "dynamicLibrarySuffix")); + } + + for (i in inputs.dynamiclibrary) + args.push("/reference:" + inputs.dynamiclibrary[i].filePath); + + // Dependent netmodules + for (i in netmodules) { + args.push("/addmodule:" + netmodules.map(function (f) { + return f + product.moduleProperty("cli", "netmoduleSuffix"); + }).join(";")); + } + + if (inputs["cli.netmodule"]) { + args.push("/addmodule:" + inputs["cli.netmodule"].map(function (f) { + return f.filePath; + }).join(";")); + } + + // Dependent resources + for (i in inputs["cli.resources"]) + args.push("/resource:" + pathFunction(inputs["cli.resources"][i].filePath)); + + // Additional compiler flags + args = args.concat((platformCompilerFlags || []).concat(compilerFlags || [])); + + args.push("/out:" + pathFunction(output.filePath)); + + for (i in inputs[keys[0]]) + args.push(pathFunction(inputs[keys[0]][i].filePath)); + + var cmd = new Command(compilers[language], args); + cmd.description = "linking " + output.fileName; + cmd.highlight = "linker"; + cmd.workingDirectory = FileInfo.path(output.filePath); + return cmd; +} diff --git a/share/qbs/modules/cli/mono.qbs b/share/qbs/modules/cli/mono.qbs new file mode 100644 index 00000000..f04956a5 --- /dev/null +++ b/share/qbs/modules/cli/mono.qbs @@ -0,0 +1,26 @@ +import qbs.File +import qbs.Probes + +CLIModule { + condition: qbs.toolchain && qbs.toolchain.contains("mono") + + debugInfoSuffix: ".mdb" + csharpCompilerName: "mcs" + vbCompilerName: "vbnc" + fsharpCompilerName: "fsharpc" + + Probes.PathProbe { + id: monoProbe + names: ["mono"] + platformSearchPaths: { + var paths = []; + if (qbs.hostOS.contains("macos")) + paths.push("/Library/Frameworks/Mono.framework/Commands"); + if (qbs.hostOS.contains("unix")) + paths.push("/usr/bin"); + return paths; + } + } + + toolchainInstallPath: monoProbe.path +} diff --git a/share/qbs/modules/cli/windows-dotnet.qbs b/share/qbs/modules/cli/windows-dotnet.qbs new file mode 100644 index 00000000..6fde50b8 --- /dev/null +++ b/share/qbs/modules/cli/windows-dotnet.qbs @@ -0,0 +1,35 @@ +import qbs.Utilities + +CLIModule { + condition: qbs.toolchain && qbs.toolchain.contains("dotnet") + + debugInfoSuffix: ".pdb" + csharpCompilerName: "csc" + vbCompilerName: "vbc" + fsharpCompilerName: "fsc" + + Probe { + id: dotnetProbe + + property string toolchainInstallPath // Output + + configure: { + // https://msdn.microsoft.com/en-us/library/hh925568.aspx + var keys = [ + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\NET Framework Setup\\NDP", + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\NET Framework Setup\\NDP" + ]; + for (var i in keys) { + var key = keys[i] + "\\v4\\Full"; + var value = Utilities.getNativeSetting(key, "InstallPath"); + if (value) { + toolchainInstallPath = value; + found = true; + break; + } + } + } + } + + toolchainInstallPath: dotnetProbe.toolchainInstallPath +} diff --git a/share/qbs/modules/cpp/CppModule.qbs b/share/qbs/modules/cpp/CppModule.qbs new file mode 100644 index 00000000..b2897c30 --- /dev/null +++ b/share/qbs/modules/cpp/CppModule.qbs @@ -0,0 +1,547 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +// base for Cpp modules +import qbs.ModUtils +import qbs.Utilities +import qbs.WindowsUtils + +import "setuprunenv.js" as SetupRunEnv + +Module { + condition: false + + Depends { name: "cpufeatures" } + + version: compilerVersion + property string compilerVersion: + [compilerVersionMajor, compilerVersionMinor, compilerVersionPatch].join(".") + property int compilerVersionMajor + property int compilerVersionMinor + property int compilerVersionPatch + property string warningLevel : 'all' // 'none', 'all' + property bool treatWarningsAsErrors : false + property bool enableSuspiciousLinkerFlagWarnings: true + property string architecture: qbs.architecture + property string endianness + property string machineType // undocumented + property string imageFormat // undocumented + property string optimization: qbs.optimization + property bool debugInformation: qbs.debugInformation + property bool enableReproducibleBuilds: false + property bool separateDebugInformation: false + property pathList prefixHeaders + property bool useCPrecompiledHeader: true + property bool useCxxPrecompiledHeader: true + property bool useObjcPrecompiledHeader: true + property bool useObjcxxPrecompiledHeader: true + + property bool treatSystemHeadersAsDependencies: false + + property stringList defines + property stringList platformDefines: qbs.enableDebugCode ? [] : ["NDEBUG"] + property stringList compilerDefines: compilerDefinesByLanguage + ? ModUtils.flattenDictionary(compilerDefinesByLanguage["c"]) + : [] + property var compilerDefinesByLanguage: undefined + PropertyOptions { + 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 { + name: "minimumWindowsVersion" + description: "A version number in the format [major].[minor] indicating the earliest \ + version of Windows that the product should run on. Defines WINVER, \ + _WIN32_WINNT, and _WIN32_WINDOWS, and applies a version number to the \ + linker flags /SUBSYSTEM and /OSVERSION for MSVC or \ + --major-subsystem-version, --minor-subsystem-version, \ + --major-os-version and --minor-os-version for MinGW. \ + If undefined, compiler defaults will be used." + } + + property string minimumOsxVersion + + property string minimumMacosVersion: minimumOsxVersion + PropertyOptions { + name: "minimumMacosVersion" + description: "a version number in the format [major].[minor] indicating the earliest \ + version of macOS that the product should run on. passes -mmacosx-version-min= \ + to the compiler. if undefined, compiler defaults will be used." + } + + property string minimumIosVersion: qbs.architecture == "armv7a" ? "6.0" : undefined + PropertyOptions { + name: "minimumIosVersion" + description: "a version number in the format [major].[minor] indicating the earliest \ + version of iOS that the product should run on. passes -miphoneos-version-min= \ + to the compiler. if undefined, compiler defaults will be used." + } + + property string minimumWatchosVersion + PropertyOptions { + name: "minimumWatchosVersion" + description: "a version number in the format [major].[minor] indicating the earliest \ + version of watchOS that the product should run on. if undefined, compiler \ + defaults will be used." + } + + property string minimumTvosVersion: "6.0" + PropertyOptions { + name: "minimumTvosVersion" + description: "a version number in the format [major].[minor] indicating the earliest \ + version of tvOS that the product should run on. if undefined, compiler \ + defaults will be used." + } + + property string minimumAndroidVersion // not used, undocumented + PropertyOptions { + name: "minimumAndroidVersion" + description: "a version number in the format [major].[minor] indicating the earliest \ + version of Android that the product should run on. this value is converted into an SDK \ + version which is then written to AndroidManifest.xml." + } + + property string maximumAndroidVersion // not used, undocumented + PropertyOptions { + name: "maximumAndroidVersion" + description: "a version number in the format [major].[minor] indicating the latest \ + version of Android that the product should run on. this value is converted into an SDK \ + version which is then written to AndroidManifest.xml. if undefined, no upper limit will \ + be set." + } + + property pathList includePaths + property pathList systemIncludePaths + property pathList distributionIncludePaths + property pathList compilerIncludePaths + property pathList libraryPaths + property pathList distributionLibraryPaths + property pathList compilerLibraryPaths + property pathList frameworkPaths + property pathList systemFrameworkPaths + property pathList distributionFrameworkPaths + property pathList compilerFrameworkPaths + property stringList systemRunPaths: [] + + property string assemblerName + property string assemblerPath: assemblerName + property string compilerName + property string compilerPath: compilerName + property var compilerPathByLanguage + property stringList compilerWrapper + property string linkerName + property string linkerPath: linkerName + property stringList linkerWrapper + property string staticLibraryPrefix: "" + property string dynamicLibraryPrefix: "" + property string loadableModulePrefix: "" + property string executablePrefix: "" + property string staticLibrarySuffix: "" + property string dynamicLibrarySuffix: "" + property string loadableModuleSuffix: "" + property string executableSuffix: "" + property string debugInfoSuffix: "" + property string debugInfoBundleSuffix: "" + property string variantSuffix: "" + property string dynamicLibraryImportSuffix: ".lib" + property bool createSymlinks: true + property stringList dynamicLibraries // list of names, will be linked with -lname + property stringList staticLibraries // list of static library files + property stringList frameworks // list of frameworks, will be linked with '-framework ' + property stringList weakFrameworks // list of weakly-linked frameworks, will be linked with '-weak_framework ' + property string rpathOrigin + property stringList rpaths + property string sonamePrefix: "" + property bool useRPaths: true + property bool useRPathLink + property string rpathLinkFlag + property bool discardUnusedData + property bool removeDuplicateLibraries: true + + property string linkerMode: "automatic" + PropertyOptions { + name: "linkerMode" + allowedValues: ["automatic", "manual"] + description: "Controls whether to automatically use an appropriate compiler frontend " + + "in place of the system linker when linking binaries. The default is \"automatic\", " + + "which chooses either the C++ compiler, C compiler, or system linker specified by " + + "the linkerName/linkerPath properties, depending on the type of object files " + + "present on the linker command line. \"manual\" allows you to explicitly specify " + + "the linker using the linkerName/linkerPath properties, and allows linker flags " + + "passed to the linkerFlags and platformLinkerFlags properties to be escaped " + + "manually (using -Wl or -Xlinker) instead of automatically based on the selected " + + "linker." + } + + property stringList assemblerFlags + PropertyOptions { + name: "assemblerFlags" + description: "additional flags for the assembler" + } + + property stringList cppFlags + PropertyOptions { + name: "cppFlags" + description: "additional flags for the C preprocessor" + } + + property stringList cFlags + PropertyOptions { + name: "cFlags" + description: "additional flags for the C compiler" + } + + property stringList cxxFlags + PropertyOptions { + name: "cxxFlags" + description: "additional flags for the C++ compiler" + } + + property stringList objcFlags + PropertyOptions { + name: "objcFlags" + description: "additional flags for the Objective-C compiler" + } + + property stringList objcxxFlags + PropertyOptions { + name: "objcxxFlags" + description: "additional flags for the Objective-C++ compiler" + } + property stringList commonCompilerFlags + PropertyOptions { + name: "commonCompilerFlags" + description: "flags added to all compilation independently of the language" + } + + property stringList linkerFlags + PropertyOptions { + name: "linkerFlags" + description: "additional linker flags" + } + + property stringList driverFlags + PropertyOptions { + name: "driverFlags" + description: "additional compiler driver flags" + } + + property stringList driverLinkerFlags + PropertyOptions { + name: "driverLinkerFlags" + description: "additional compiler driver flags used for linking only" + } + + property bool generateLinkerMapFile: false + PropertyOptions { + name: "generateLinkerMapFile" + description: "generate linker map file" + } + + property bool generateCompilerListingFiles: false + PropertyOptions { + name: "generateCompilerListingFiles" + description: "generate compiler listing files" + } + + property bool generateAssemblerListingFiles: false + PropertyOptions { + name: "generateAssemblerListingFiles" + description: "generate assembler listing files" + } + + property bool positionIndependentCode: true + PropertyOptions { + name: "positionIndependentCode" + description: "generate position independent code" + } + + property string entryPoint + PropertyOptions { + name: "entryPoint" + description: "entry point symbol for an executable or dynamic library" + } + + property string runtimeLibrary + PropertyOptions { + name: "runtimeLibrary" + description: "determine which runtime library to use" + allowedValues: ['static', 'dynamic'] + } + + property string visibility: 'default' + PropertyOptions { + name: "visibility" + description: "export symbols visibility level" + allowedValues: ['default', 'hidden', 'hiddenInlines', 'minimal'] + } + + property stringList cLanguageVersion + PropertyOptions { + name: "cLanguageVersion" + allowedValues: ["c89", "c99", "c11"] + description: "The version of the C standard with which the code must comply." + } + + property stringList cxxLanguageVersion + PropertyOptions { + name: "cxxLanguageVersion" + allowedValues: ["c++98", "c++11", "c++14", "c++17"] + description: "The version of the C++ standard with which the code must comply." + } + + property bool useLanguageVersionFallback + PropertyOptions { + name: "useLanguageVersionFallback" + description: "whether to explicitly use the language standard version fallback values in " + + "compiler command line invocations" + } + + property string cxxStandardLibrary + PropertyOptions { + name: "cxxStandardLibrary" + allowedValues: ["libstdc++", "libc++"] + description: "version of the C++ standard library to use" + } + + property bool enableExceptions: true + PropertyOptions { + name: "enableExceptions" + description: "enable/disable exception handling (enabled by default)" + } + + property string exceptionHandlingModel: "default" + PropertyOptions { + name: "exceptionHandlingModel" + description: "the kind of exception handling to be used by the compiler" + } + + property bool enableRtti + + // Platform properties. Those are intended to be set by the toolchain setup + // and are prepended to the corresponding user properties. + property stringList platformAssemblerFlags + property stringList platformCommonCompilerFlags + property stringList platformCFlags + property stringList platformCxxFlags + property stringList platformObjcFlags + property stringList platformObjcxxFlags + property stringList platformLinkerFlags + property stringList platformDriverFlags + + // Apple platforms properties + property bool automaticReferenceCounting + PropertyOptions { + name: "automaticReferenceCounting" + description: "whether to enable Automatic Reference Counting (ARC) for Objective-C" + } + + property bool requireAppExtensionSafeApi + PropertyOptions { + name: "requireAppExtensionSafeApi" + description: "whether to require app-extension-safe APIs only" + } + + property bool allowUnresolvedSymbols + + property bool combineCSources: false + property bool combineCxxSources: false + property bool combineObjcSources: false + property bool combineObjcxxSources: false + + // Those are set internally by different cpp module implementations + property stringList targetAssemblerFlags + property stringList targetDriverFlags + property stringList targetLinkerFlags + + property bool _skipAllChecks: false // Internal + + property bool validateTargetTriple: true // undocumented + + // TODO: The following four rules could use a convenience base item if rule properties + // were available in Artifact items and prepare scripts. + Rule { + multiplex: true + inputs: ["c.combine"] + Artifact { + filePath: "amalgamated_" + product.targetName + ".c" + fileTags: ["c"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + ModUtils.mergeCFiles(inputs["c.combine"], output.filePath); + }; + return [cmd]; + } + } + Rule { + multiplex: true + inputs: ["cpp.combine"] + Artifact { + filePath: "amalgamated_" + product.targetName + ".cpp" + fileTags: ["cpp"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + ModUtils.mergeCFiles(inputs["cpp.combine"], output.filePath); + }; + return [cmd]; + } + } + Rule { + multiplex: true + inputs: ["objc.combine"] + Artifact { + filePath: "amalgamated_" + product.targetName + ".m" + fileTags: ["objc"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + ModUtils.mergeCFiles(inputs["objc.combine"], output.filePath); + }; + return [cmd]; + } + } + Rule { + multiplex: true + inputs: ["objcpp.combine"] + Artifact { + filePath: "amalgamated_" + product.targetName + ".mm" + fileTags: ["objccpp"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + ModUtils.mergeCFiles(inputs["objcpp.combine"], output.filePath); + }; + return [cmd]; + } + } + + FileTagger { + patterns: ["*.c"] + fileTags: combineCSources ? ["c.combine"] : ["c"] + } + + FileTagger { + patterns: ["*.C", "*.cpp", "*.cxx", "*.c++", "*.cc"] + fileTags: combineCxxSources ? ["cpp.combine"] : ["cpp"] + } + + FileTagger { + patterns: ["*.m"] + fileTags: combineObjcSources ? ["objc.combine"] : ["objc"] + } + + FileTagger { + patterns: ["*.mm"] + fileTags: combineObjcxxSources ? ["objcpp.combine"] : ["objcpp"] + } + + FileTagger { + patterns: ["*.h", "*.H", "*.hpp", "*.hxx", "*.h++"] + fileTags: ["hpp"] + } + + property var validateFunc: { + return function() { + var validator = new ModUtils.PropertyValidator("cpp"); + validator.setRequiredProperty("architecture", architecture, + "you might want to re-run 'qbs-setup-toolchains'"); + validator.addCustomValidator("architecture", architecture, function (value) { + return !architecture || architecture === Utilities.canonicalArchitecture(architecture); + }, "'" + 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); + validator.setRequiredProperty("compilerVersionPatch", compilerVersionPatch); + validator.addVersionValidator("compilerVersion", compilerVersion, 3, 3); + validator.addRangeValidator("compilerVersionMajor", compilerVersionMajor, 1); + validator.addRangeValidator("compilerVersionMinor", compilerVersionMinor, 0); + validator.addRangeValidator("compilerVersionPatch", compilerVersionPatch, 0); + if (minimumWindowsVersion) { + validator.addVersionValidator("minimumWindowsVersion", minimumWindowsVersion, 2, 2); + validator.addCustomValidator("minimumWindowsVersion", minimumWindowsVersion, function (v) { + return !v || v === WindowsUtils.canonicalizeVersion(v); + }, "'" + minimumWindowsVersion + "' is invalid. Did you mean '" + + WindowsUtils.canonicalizeVersion(minimumWindowsVersion) + "'?"); + } + validator.validate(); + + if (minimumWindowsVersion && !WindowsUtils.isValidWindowsVersion(minimumWindowsVersion)) { + console.warn("Unknown Windows version '" + minimumWindowsVersion + + "'; expected one of: " + + WindowsUtils.knownWindowsVersions().map(function (a) { + return '"' + a + '"'; }).join(", ") + + ". See https://docs.microsoft.com/en-us/windows/desktop/SysInfo/operating-system-version"); + } + } + } + + validate: { + return validateFunc(); + } + + setupRunEnvironment: { + SetupRunEnv.setupRunEnvironment(product, config); + } + + Parameter { + property bool link + } + Parameter { + property bool linkWholeArchive + } + Parameter { + property string symbolLinkMode + } +} diff --git a/share/qbs/modules/cpp/DarwinGCC.qbs b/share/qbs/modules/cpp/DarwinGCC.qbs new file mode 100644 index 00000000..9332603e --- /dev/null +++ b/share/qbs/modules/cpp/DarwinGCC.qbs @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.DarwinTools +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.PathTools +import qbs.Probes +import qbs.PropertyList +import qbs.TextFile +import qbs.Utilities +import "darwin.js" as Darwin +import "gcc.js" as Gcc + +UnixGCC { + condition: false + + Depends { name: "xcode"; required: qbs.toolchain && qbs.toolchain.contains("xcode") } + + Probes.BinaryProbe { + id: lipoProbe + condition: !_skipAllChecks + names: [lipoName] + platformSearchPaths: { + var paths = (xcode.present && xcode.devicePlatformPath) + ? [xcode.devicePlatformPath + "/Developer/usr/bin"] + : []; + return paths.concat([toolchainInstallPath, "/usr/bin"]); + } + } + + property string lipoPathPrefix: Gcc.pathPrefix(lipoProbe.found + ? lipoProbe.path + : toolchainInstallPath, toolchainPrefix) + + lipoName: "lipo" + lipoPath: lipoPathPrefix + lipoName + property bool enableAggregationRules: product.aggregate && !product.multiplexConfigurationId + + targetVendor: "apple" + targetSystem: "darwin" + targetAbi: "macho" + imageFormat: "macho" + + cxxStandardLibrary: libcxxAvailable ? "libc++" : base + loadableModulePrefix: "" + loadableModuleSuffix: ".bundle" + dynamicLibrarySuffix: ".dylib" + + Properties { + condition: product.multiplexByQbsProperties.contains("buildVariants") + && qbs.buildVariants && qbs.buildVariants.length > 1 + && (!product.aggregate || !!product.multiplexConfigurationId) + && qbs.buildVariant !== "release" + variantSuffix: "_" + qbs.buildVariant + } + + separateDebugInformation: true + debugInfoBundleSuffix: ".dSYM" + debugInfoSuffix: ".dwarf" + rpathOrigin: "@loader_path" + useRPathLink: !minimumDarwinVersion + || Utilities.versionCompare(minimumDarwinVersion, "10.5") < 0 + rpathLinkFlag: "-L" + + toolchainInstallPath: xcode.present + ? FileInfo.joinPaths(xcode.toolchainPath, "usr", "bin") : base + sysroot: xcode.present ? xcode.sdkPath : base + sysrootFlags: sysroot ? ["-isysroot", sysroot] : [] + + setupBuildEnvironment: { + for (var key in product.cpp.buildEnv) { + v = new ModUtils.EnvironmentVariable(key); + v.value = product.cpp.buildEnv[key]; + v.set(); + } + } + + property var defaultInfoPlist: { + var dict = {}; + + if (qbs.targetOS.contains("macos")) { + dict["NSPrincipalClass"] = "NSApplication"; // needed for Retina display support + + if (minimumMacosVersion) + dict["LSMinimumSystemVersion"] = minimumMacosVersion; + } + + if (qbs.targetOS.containsAny(["ios", "tvos"])) { + dict["LSRequiresIPhoneOS"] = true; + + if (xcode.platformType === "device") { + if (qbs.targetOS.contains("ios")) { + if (qbs.architecture === "arm64") + dict["UIRequiredDeviceCapabilities"] = ["arm64"]; + else + dict["UIRequiredDeviceCapabilities"] = ["armv7"]; + } + + if (qbs.targetOS.contains("tvos")) + dict["UIRequiredDeviceCapabilities"] = ["arm64"]; + } + } + + if (xcode.present) { + var targetDevices = DarwinTools.targetedDeviceFamily(xcode.targetDevices); + if (qbs.targetOS.contains("ios")) + dict["UIDeviceFamily"] = targetDevices; + + if (qbs.targetOS.containsAny(["ios", "watchos"])) { + var orientations = [ + "UIInterfaceOrientationPortrait", + "UIInterfaceOrientationPortraitUpsideDown", + "UIInterfaceOrientationLandscapeLeft", + "UIInterfaceOrientationLandscapeRight" + ]; + + if (targetDevices.contains("ipad")) + dict["UISupportedInterfaceOrientations~ipad"] = orientations; + + if (targetDevices.contains("watch")) + dict["UISupportedInterfaceOrientations"] = orientations.slice(0, 2); + + if (targetDevices.contains("iphone")) { + orientations.splice(1, 1); + dict["UISupportedInterfaceOrientations"] = orientations; + } + } + } + + return dict; + } + + targetLinkerFlags: darwinArchFlags.concat(minimumDarwinVersionLinkerFlags) + + targetAssemblerFlags: !assemblerHasTargetOption ? darwinArchFlags : base; + + targetDriverFlags: !compilerHasTargetOption ? legacyTargetDriverFlags : base + + property stringList legacyTargetDriverFlags: + base.concat(darwinArchFlags).concat(minimumDarwinVersionCompilerFlags) + + // private properties + readonly property stringList darwinArchFlags: targetArch ? ["-arch", targetArch] : [] + + readonly property stringList minimumDarwinVersionCompilerFlags: + (minimumDarwinVersionCompilerFlag && minimumDarwinVersion) + ? [minimumDarwinVersionCompilerFlag + "=" + minimumDarwinVersion] : [] + + readonly property stringList minimumDarwinVersionLinkerFlags: + (minimumDarwinVersionLinkerFlag && minimumDarwinVersion && compilerVersionMajor < 11) + ? [minimumDarwinVersionLinkerFlag, minimumDarwinVersion] : [] + + readonly property var buildEnv: { + var env = { + "ARCHS_STANDARD": xcode.standardArchitectures, + "EXECUTABLE_NAME": product.targetName, + "LANG": "en_US.US-ASCII", + "PRODUCT_NAME": product.name + } + + // Set the corresponding environment variable even if the minimum OS version is undefined, + // because this indicates the default deployment target for that OS + if (qbs.targetOS.contains("ios") && minimumIosVersion) + env["IPHONEOS_DEPLOYMENT_TARGET"] = minimumIosVersion; + if (qbs.targetOS.contains("macos") && minimumMacosVersion) + env["MACOSX_DEPLOYMENT_TARGET"] = minimumMacosVersion; + if (qbs.targetOS.contains("watchos") && minimumWatchosVersion) + env["WATCHOS_DEPLOYMENT_TARGET"] = minimumWatchosVersion; + if (qbs.targetOS.contains("tvos") && minimumTvosVersion) + env["TVOS_DEPLOYMENT_TARGET"] = minimumTvosVersion; + + if (xcode.present) + env["TARGETED_DEVICE_FAMILY"] = + DarwinTools.targetedDeviceFamily(xcode.targetDevices).join(","); + return env; + } + + property string minimumDarwinVersion + property string minimumDarwinVersionCompilerFlag + property string minimumDarwinVersionLinkerFlag + + property bool libcxxAvailable: qbs.toolchain.contains("clang") && cxxLanguageVersion !== "c++98" + + Rule { + condition: enableAggregationRules + inputsFromDependencies: ["application"] + multiplex: true + + outputFileTags: ["bundle.input", "application", "primary", "debuginfo_app", + "debuginfo_bundle", "bundle.variant_symlink", "debuginfo_plist"] + outputArtifacts: Darwin.lipoOutputArtifacts(product, inputs, "application", "app") + + prepare: Darwin.prepareLipo.apply(Darwin, arguments) + } + + Rule { + condition: enableAggregationRules + inputsFromDependencies: ["loadablemodule"] + multiplex: true + + outputFileTags: ["bundle.input", "loadablemodule", "primary", "debuginfo_loadablemodule"] + outputArtifacts: Darwin.lipoOutputArtifacts(product, inputs, "loadablemodule", + "loadablemodule") + + prepare: Darwin.prepareLipo.apply(Darwin, arguments) + } + + Rule { + condition: enableAggregationRules + inputsFromDependencies: ["dynamiclibrary"] + multiplex: true + + outputFileTags: ["bundle.input", "dynamiclibrary", "dynamiclibrary_symbols", "primary", + "debuginfo_dll","debuginfo_bundle","bundle.variant_symlink", + "debuginfo_plist"] + outputArtifacts: Darwin.lipoOutputArtifacts(product, inputs, "dynamiclibrary", "dll") + + prepare: Darwin.prepareLipo.apply(Darwin, arguments) + } + + Rule { + condition: enableAggregationRules + inputsFromDependencies: ["staticlibrary"] + multiplex: true + + outputFileTags: ["bundle.input", "staticlibrary", "primary"] + outputArtifacts: Darwin.lipoOutputArtifacts(product, inputs, "staticlibrary") + + prepare: Darwin.prepareLipo.apply(Darwin, arguments) + } + + Rule { + condition: qbs.targetOS.contains("darwin") + multiplex: true + + Artifact { + filePath: product.name + "-cpp-Info.plist" + fileTags: ["partial_infoplist"] + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.inputData = product.cpp.defaultInfoPlist; + cmd.outputFilePath = output.filePath; + cmd.sourceCode = function() { + var plist = new PropertyList(); + try { + plist.readFromObject(inputData); + plist.writeToFile(outputFilePath, "xml1"); + } finally { + plist.clear(); + } + }; + return [cmd]; + } + } +} diff --git a/share/qbs/modules/cpp/GenericGCC.qbs b/share/qbs/modules/cpp/GenericGCC.qbs new file mode 100644 index 00000000..b69f2673 --- /dev/null +++ b/share/qbs/modules/cpp/GenericGCC.qbs @@ -0,0 +1,732 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.PathTools +import qbs.Probes +import qbs.Process +import qbs.TextFile +import qbs.Utilities +import qbs.UnixUtils +import qbs.WindowsUtils +import 'gcc.js' as Gcc + +CppModule { + condition: qbs.toolchain && qbs.toolchain.contains("gcc") + priority: -100 + + Probes.GccBinaryProbe { + id: compilerPathProbe + condition: !toolchainInstallPath && !_skipAllChecks + _compilerName: compilerName + _toolchainPrefix: toolchainPrefix + } + + // Find the version as early as possible in case other things depend on it, + // for example the question of whether certain flags are supported and which need to be used + // in the GccProbe itself. + Probes.GccVersionProbe { + id: gccVersionProbe + compilerFilePath: compilerPath + environment: probeEnv + } + + Probes.GccProbe { + id: gccProbe + condition: !_skipAllChecks + compilerFilePathByLanguage: compilerPathByLanguage + enableDefinesByLanguage: enableCompilerDefinesByLanguage + environment: probeEnv + flags: targetDriverFlags.concat(sysrootFlags) + _sysroot: sysroot + } + property var probeEnv + + Probes.BinaryProbe { + id: binutilsProbe + condition: !File.exists(archiverPath) && !_skipAllChecks + names: Gcc.toolNames([archiverName, assemblerName, linkerName, nmName, + objcopyName, stripName], toolchainPrefix) + } + + targetLinkerFlags: Gcc.targetFlags("linker", false, + target, targetArch, machineType, qbs.targetOS) + targetAssemblerFlags: Gcc.targetFlags("assembler", assemblerHasTargetOption, + target, targetArch, machineType, qbs.targetOS) + targetDriverFlags: Gcc.targetFlags("compiler", compilerHasTargetOption, + target, targetArch, machineType, qbs.targetOS) + + Probe { + id: nmProbe + condition: !_skipAllChecks + property string theNmPath: nmPath + property bool hasDynamicOption + configure: { + var proc = new Process(); + try { + hasDynamicOption = proc.exec(theNmPath, ["-D", theNmPath], false) == 0; + console.debug("nm has -D: " + hasDynamicOption); + } finally { + proc.close(); + } + } + } + + qbs.architecture: gccProbe.found ? gccProbe.architecture : original + endianness: gccProbe.endianness + + compilerDefinesByLanguage: gccProbe.compilerDefinesByLanguage + + compilerVersionMajor: gccVersionProbe.versionMajor + compilerVersionMinor: gccVersionProbe.versionMinor + compilerVersionPatch: gccVersionProbe.versionPatch + + compilerIncludePaths: gccProbe.includePaths + compilerFrameworkPaths: gccProbe.frameworkPaths + compilerLibraryPaths: gccProbe.libraryPaths + + staticLibraryPrefix: "lib" + staticLibrarySuffix: ".a" + + property bool compilerHasTargetOption: qbs.toolchain.contains("clang") + && Utilities.versionCompare(compilerVersion, "3.1") >= 0 + property bool assemblerHasTargetOption: qbs.toolchain.contains("xcode") + && Utilities.versionCompare(compilerVersion, "7") >= 0 + property string target: targetArch + ? [targetArch, targetVendor, targetSystem, targetAbi].join("-") + : undefined + property string targetArch: Utilities.canonicalTargetArchitecture( + qbs.architecture, endianness, + targetVendor, targetSystem, targetAbi) + property string targetVendor: "unknown" + property string targetSystem: "unknown" + property string targetAbi: "unknown" + + property string toolchainPrefix: compilerPathProbe.found + ? compilerPathProbe.tcPrefix + : undefined + property string toolchainInstallPath: compilerPathProbe.found ? compilerPathProbe.path + : undefined + property string binutilsPath: binutilsProbe.found ? binutilsProbe.path : toolchainInstallPath + + assemblerName: 'as' + compilerExtension + compilerName: cxxCompilerName + linkerName: 'ld' + compilerExtension + property string archiverName: 'ar' + compilerExtension + property string nmName: 'nm' + compilerExtension + property string objcopyName: "objcopy" + compilerExtension + property string stripName: "strip" + compilerExtension + property string dsymutilName: "dsymutil" + compilerExtension + property string lipoName + property string sysroot: qbs.sysroot + property string syslibroot: sysroot + property stringList sysrootFlags: sysroot ? ["--sysroot=" + sysroot] : [] + + property string exportedSymbolsCheckMode: "ignore-undefined" + PropertyOptions { + name: "exportedSymbolsCheckMode" + allowedValues: ["strict", "ignore-undefined"] + description: "Controls when we consider an updated dynamic library as changed with " + + "regards to other binaries depending on it. The default is \"ignore-undefined\", " + + "which means we do not care about undefined symbols being added or removed. " + + "If you do care about that, e.g. because you link dependent products with an option " + + "such as \"--no-undefined\", then you should set this property to \"strict\"." + } + + property string linkerVariant + PropertyOptions { + name: "linkerVariant" + allowedValues: ["bfd", "gold", "lld"] + description: "Allows to specify the linker variant. Maps to gcc's and clang's -fuse-ld " + + "option." + } + Properties { + condition: linkerVariant + driverLinkerFlags: "-fuse-ld=" + linkerVariant + } + + property string toolchainPathPrefix: Gcc.pathPrefix(toolchainInstallPath, toolchainPrefix) + property string binutilsPathPrefix: Gcc.pathPrefix(binutilsPath, toolchainPrefix) + + property string compilerExtension: qbs.hostOS.contains("windows") ? ".exe" : "" + property string cCompilerName: (qbs.toolchain.contains("clang") ? "clang" : "gcc") + + compilerExtension + property string cxxCompilerName: (qbs.toolchain.contains("clang") ? "clang++" : "g++") + + compilerExtension + + compilerPathByLanguage: ({ + "c": toolchainPathPrefix + cCompilerName, + "cpp": toolchainPathPrefix + cxxCompilerName, + "objc": toolchainPathPrefix + cCompilerName, + "objcpp": toolchainPathPrefix + cxxCompilerName, + "asm_cpp": toolchainPathPrefix + cCompilerName + }) + + assemblerPath: binutilsPathPrefix + assemblerName + compilerPath: toolchainPathPrefix + compilerName + linkerPath: binutilsPathPrefix + linkerName + property string archiverPath: binutilsPathPrefix + archiverName + property string nmPath: binutilsPathPrefix + nmName + property bool _nmHasDynamicOption: nmProbe.hasDynamicOption + property string objcopyPath: binutilsPathPrefix + objcopyName + property string stripPath: binutilsPathPrefix + stripName + property string dsymutilPath: toolchainPathPrefix + dsymutilName + property string lipoPath + property stringList dsymutilFlags + + property bool alwaysUseLipo: false + property string includeFlag: "-I" + property string systemIncludeFlag: "-isystem" + + readonly property bool shouldCreateSymlinks: { + return createSymlinks && internalVersion && ["macho", "elf"].contains(cpp.imageFormat); + } + + property string internalVersion: { + if (product.version === undefined) + return undefined; + + var coreVersion = product.version.match("^([0-9]+\.){0,3}[0-9]+"); + if (!coreVersion) + return undefined; + + var maxVersionParts = 3; + var versionParts = coreVersion[0].split('.').slice(0, maxVersionParts); + + // pad if necessary + for (var i = versionParts.length; i < maxVersionParts; ++i) + versionParts.push("0"); + + return versionParts.join('.'); + } + + property string soVersion: { + if (!internalVersion) + return ""; + + return internalVersion.split('.')[0]; + } + + property var buildEnv: { + var env = {}; + if (qbs.toolchain.contains("mingw")) + env.PATH = [toolchainInstallPath]; // For libwinpthread etc + return env; + } + + exceptionHandlingModel: { + if (qbs.toolchain.contains("mingw")) { + // https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html claims + // __USING_SJLJ_EXCEPTIONS__ is defined as 1 when using SJLJ exceptions, but there don't + // seem to be defines for the other models, so use the presence of the DLLs for now. + var prefix = toolchainInstallPath; + if (!qbs.hostOS.contains("windows")) + prefix = FileInfo.joinPaths(toolchainInstallPath, "..", "lib", "gcc", + toolchainPrefix, + [compilerVersionMajor, compilerVersionMinor].join(".")); + var models = ["seh", "sjlj", "dw2"]; + for (var i = 0; i < models.length; ++i) { + if (File.exists(FileInfo.joinPaths(prefix, "libgcc_s_" + models[i] + "-1.dll"))) { + return models[i]; + } + } + } + return base; + } + + validate: { + if (_skipAllChecks) + return; + if (!File.exists(compilerPath)) { + var pathMessage = FileInfo.isAbsolutePath(compilerPath) + ? "at '" + compilerPath + "'" + : "'" + compilerPath + "' in PATH"; + throw ModUtils.ModuleError("Could not find selected C++ compiler " + pathMessage + ". " + + "Ensure that the compiler is properly " + + "installed, or set cpp.toolchainInstallPath to a valid " + + "toolchain path, or consider whether you meant to set " + + "cpp.compilerName instead."); + } + + var isWrongTriple = false; + + if (gccProbe.architecture) { + if (Utilities.canonicalArchitecture(architecture) + !== Utilities.canonicalArchitecture(gccProbe.architecture)) + isWrongTriple = true; + } else if (architecture) { + // This is a warning and not an error on the rare chance some new architecture comes + // about which qbs does not know about the macros of. But it *might* still work. + console.warn("Unknown architecture '" + architecture + "' " + + "may not be supported by this compiler."); + } + + if (gccProbe.endianness) { + if (endianness !== gccProbe.endianness) + isWrongTriple = true; + } else if (endianness) { + console.warn("Could not detect endianness ('" + + endianness + "' given)"); + } + + if (gccProbe.targetPlatform) { + // Can't differentiate Darwin OSes at the compiler level alone + if (gccProbe.targetPlatform === "darwin" + ? !qbs.targetOS.contains("darwin") + : qbs.targetPlatform !== gccProbe.targetPlatform) + isWrongTriple = true; + } else if (qbs.targetPlatform) { + console.warn("Could not detect target platform ('" + + qbs.targetPlatform + "' given)"); + } + + if (isWrongTriple) { + var realTriple = [ + Utilities.canonicalArchitecture(gccProbe.architecture), + gccProbe.targetPlatform, + gccProbe.endianness ? gccProbe.endianness + "-endian" : undefined + ].join("-"); + var givenTriple = [ + Utilities.canonicalArchitecture(architecture), + qbs.targetPlatform, + endianness ? endianness + "-endian" : undefined + ].join("-"); + var msg = "The selected compiler '" + compilerPath + "' produces code for '" + + realTriple + "', but '" + givenTriple + "' was given, which is incompatible."; + if (validateTargetTriple) { + msg += " If you are absolutely certain that your configuration is correct " + + "(check the values of the qbs.architecture, qbs.targetPlatform, " + + "and qbs.endianness properties) and that this message has been " + + "emitted in error, set the cpp.validateTargetTriple property to " + + "false. However, you should consider submitting a bug report in any " + + "case."; + throw ModUtils.ModuleError(msg); + } else { + console.warn(msg); + } + } + + var validateFlagsFunction = function (value) { + if (value) { + for (var i = 0; i < value.length; ++i) { + if (["-target", "-triple", "-arch"].contains(value[i])) + return false; + } + } + return true; + } + + var validator = new ModUtils.PropertyValidator("cpp"); + var msg = "'-target', '-triple' and '-arch' cannot appear in flags; set qbs.architecture instead"; + validator.addCustomValidator("assemblerFlags", assemblerFlags, validateFlagsFunction, msg); + validator.addCustomValidator("cppFlags", cppFlags, validateFlagsFunction, msg); + validator.addCustomValidator("cFlags", cFlags, validateFlagsFunction, msg); + validator.addCustomValidator("cxxFlags", cxxFlags, validateFlagsFunction, msg); + validator.addCustomValidator("objcFlags", objcFlags, validateFlagsFunction, msg); + validator.addCustomValidator("objcxxFlags", objcxxFlags, validateFlagsFunction, msg); + validator.addCustomValidator("commonCompilerFlags", commonCompilerFlags, validateFlagsFunction, msg); + validator.addCustomValidator("platformAssemblerFlags", platformAssemblerFlags, validateFlagsFunction, msg); + //validator.addCustomValidator("platformCppFlags", platformCppFlags, validateFlagsFunction, msg); + validator.addCustomValidator("platformCFlags", platformCFlags, validateFlagsFunction, msg); + validator.addCustomValidator("platformCxxFlags", platformCxxFlags, validateFlagsFunction, msg); + validator.addCustomValidator("platformObjcFlags", platformObjcFlags, validateFlagsFunction, msg); + validator.addCustomValidator("platformObjcxxFlags", platformObjcxxFlags, validateFlagsFunction, msg); + validator.addCustomValidator("platformCommonCompilerFlags", platformCommonCompilerFlags, validateFlagsFunction, msg); + + validator.setRequiredProperty("compilerVersion", compilerVersion); + validator.setRequiredProperty("compilerVersionMajor", compilerVersionMajor); + validator.setRequiredProperty("compilerVersionMinor", compilerVersionMinor); + validator.setRequiredProperty("compilerVersionPatch", compilerVersionPatch); + validator.addVersionValidator("compilerVersion", compilerVersion, 3, 3); + validator.addRangeValidator("compilerVersionMajor", compilerVersionMajor, 1); + validator.addRangeValidator("compilerVersionMinor", compilerVersionMinor, 0); + validator.addRangeValidator("compilerVersionPatch", compilerVersionPatch, 0); + + validator.setRequiredProperty("compilerIncludePaths", compilerIncludePaths); + validator.setRequiredProperty("compilerFrameworkPaths", compilerFrameworkPaths); + validator.setRequiredProperty("compilerLibraryPaths", compilerLibraryPaths); + + validator.validate(); + } + + // Product should be linked if it's not multiplexed or aggregated at all, + // or if it is multiplexed, if it's not the aggregate product + readonly property bool shouldLink: !(product.multiplexed || product.aggregate) + || product.multiplexConfigurationId + + Rule { + name: "dynamicLibraryLinker" + condition: product.cpp.shouldLink + multiplex: true + inputs: { + var tags = ["obj", "linkerscript", "versionscript"]; + if (product.bundle && product.bundle.embedInfoPlist + && product.qbs.targetOS.contains("darwin")) { + tags.push("aggregate_infoplist"); + } + return tags; + } + inputsFromDependencies: ["dynamiclibrary_symbols", "staticlibrary", "dynamiclibrary_import"] + + outputFileTags: [ + "bundle.input", + "dynamiclibrary", "dynamiclibrary_symlink", "dynamiclibrary_symbols", "debuginfo_dll", + "debuginfo_bundle","dynamiclibrary_import", "debuginfo_plist", + ] + outputArtifacts: { + var artifacts = [{ + filePath: product.destinationDirectory + "/" + + PathTools.dynamicLibraryFilePath(product), + fileTags: ["bundle.input", "dynamiclibrary"], + bundle: { + _bundleFilePath: product.destinationDirectory + "/" + + PathTools.bundleExecutableFilePath(product) + } + }]; + if (product.cpp.imageFormat === "pe") { + artifacts.push({ + fileTags: ["dynamiclibrary_import"], + filePath: FileInfo.joinPaths(product.destinationDirectory, + PathTools.importLibraryFilePath(product)), + alwaysUpdated: false + }); + } else { + // List of libfoo's public symbols for smart re-linking. + artifacts.push({ + filePath: product.destinationDirectory + "/.sosymbols/" + + PathTools.dynamicLibraryFilePath(product), + fileTags: ["dynamiclibrary_symbols"], + alwaysUpdated: false, + }); + } + + if (product.cpp.shouldCreateSymlinks && (!product.bundle || !product.bundle.isBundle)) { + var maxVersionParts = product.cpp.internalVersion ? 3 : 1; + for (var i = 0; i < maxVersionParts; ++i) { + var symlink = { + filePath: product.destinationDirectory + "/" + + PathTools.dynamicLibraryFilePath(product, undefined, undefined, + i), + fileTags: ["dynamiclibrary_symlink"] + }; + if (i > 0 && artifacts[i-1].filePath == symlink.filePath) + break; // Version number has less than three components. + artifacts.push(symlink); + } + } + if (!product.aggregate) + artifacts = artifacts.concat(Gcc.debugInfoArtifacts(product, undefined, "dll")); + return artifacts; + } + + prepare: { + return Gcc.prepareLinker.apply(Gcc, arguments); + } + } + + Rule { + name: "staticLibraryLinker" + condition: product.cpp.shouldLink + multiplex: true + inputs: ["obj", "linkerscript"] + inputsFromDependencies: ["dynamiclibrary_symbols", "dynamiclibrary_import", "staticlibrary"] + + outputFileTags: ["bundle.input", "staticlibrary", "c_staticlibrary", "cpp_staticlibrary"] + outputArtifacts: { + var tags = ["bundle.input", "staticlibrary"]; + var objs = inputs["obj"]; + var objCount = objs ? objs.length : 0; + for (var i = 0; i < objCount; ++i) { + var ft = objs[i].fileTags; + if (ft.contains("c_obj")) + tags.push("c_staticlibrary"); + if (ft.contains("cpp_obj")) + tags.push("cpp_staticlibrary"); + } + return [{ + filePath: FileInfo.joinPaths(product.destinationDirectory, + PathTools.staticLibraryFilePath(product)), + fileTags: tags, + bundle: { + _bundleFilePath: FileInfo.joinPaths(product.destinationDirectory, + PathTools.bundleExecutableFilePath(product)) + } + }]; + } + + prepare: { + var args = ['rcs', output.filePath]; + for (var i in inputs.obj) + args.push(inputs.obj[i].filePath); + var cmd = new Command(product.cpp.archiverPath, args); + cmd.description = 'creating ' + output.fileName; + cmd.highlight = 'linker' + cmd.jobPool = "linker"; + cmd.responseFileUsagePrefix = '@'; + return cmd; + } + } + + Rule { + name: "loadableModuleLinker" + condition: product.cpp.shouldLink + multiplex: true + inputs: { + var tags = ["obj", "linkerscript"]; + if (product.bundle && product.bundle.embedInfoPlist + && product.qbs.targetOS.contains("darwin")) { + tags.push("aggregate_infoplist"); + } + return tags; + } + inputsFromDependencies: ["dynamiclibrary_symbols", "dynamiclibrary_import", "staticlibrary"] + + outputFileTags: ["bundle.input", "loadablemodule", "debuginfo_loadablemodule", + "debuginfo_bundle","debuginfo_plist"] + outputArtifacts: { + var app = { + filePath: FileInfo.joinPaths(product.destinationDirectory, + PathTools.loadableModuleFilePath(product)), + fileTags: ["bundle.input", "loadablemodule"], + bundle: { + _bundleFilePath: FileInfo.joinPaths(product.destinationDirectory, + PathTools.bundleExecutableFilePath(product)) + } + } + var artifacts = [app]; + if (!product.aggregate) + artifacts = artifacts.concat(Gcc.debugInfoArtifacts(product, undefined, + "loadablemodule")); + return artifacts; + } + + prepare: { + return Gcc.prepareLinker.apply(Gcc, arguments); + } + } + + Rule { + name: "applicationLinker" + condition: product.cpp.shouldLink + multiplex: true + inputs: { + var tags = ["obj", "linkerscript"]; + if (product.bundle && product.bundle.embedInfoPlist + && product.qbs.targetOS.contains("darwin")) { + tags.push("aggregate_infoplist"); + } + return tags; + } + inputsFromDependencies: ["dynamiclibrary_symbols", "dynamiclibrary_import", "staticlibrary"] + + outputFileTags: ["bundle.input", "application", "debuginfo_app","debuginfo_bundle", + "debuginfo_plist", "mem_map"] + outputArtifacts: { + var app = { + filePath: FileInfo.joinPaths(product.destinationDirectory, + PathTools.applicationFilePath(product)), + fileTags: ["bundle.input", "application"], + bundle: { + _bundleFilePath: FileInfo.joinPaths(product.destinationDirectory, + PathTools.bundleExecutableFilePath(product)) + } + } + var artifacts = [app]; + if (!product.aggregate) + artifacts = artifacts.concat(Gcc.debugInfoArtifacts(product, undefined, "app")); + if (product.cpp.generateLinkerMapFile) { + artifacts.push({ + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + ".map"), + fileTags: ["mem_map"] + }); + } + return artifacts; + } + + prepare: { + return Gcc.prepareLinker.apply(Gcc, arguments); + } + } + + Rule { + name: "compiler" + inputs: ["cpp", "c", "objcpp", "objc", "asm_cpp"] + auxiliaryInputs: ["hpp"] + explicitlyDependsOn: ["c_pch", "cpp_pch", "objc_pch", "objcpp_pch"] + + outputFileTags: ["obj", "c_obj", "cpp_obj", "intermediate_obj"] + outputArtifacts: { + var tags; + if (input.fileTags.contains("cpp_intermediate_object")) + tags = ["intermediate_obj"]; + else + tags = ["obj"]; + if (inputs.c || inputs.objc) + tags.push("c_obj"); + if (inputs.cpp || inputs.objcpp) + tags.push("cpp_obj"); + return [{ + fileTags: tags, + filePath: FileInfo.joinPaths(Utilities.getHash(input.baseDir), + input.fileName + ".o") + }]; + } + + prepare: { + return Gcc.prepareCompiler.apply(Gcc, arguments); + } + } + + Rule { + name: "assembler" + inputs: ["asm"] + + Artifact { + fileTags: ["obj"] + filePath: FileInfo.joinPaths(Utilities.getHash(input.baseDir), input.fileName + ".o") + } + + prepare: { + return Gcc.prepareAssembler.apply(Gcc, arguments); + } + } + + Rule { + condition: useCPrecompiledHeader + inputs: ["c_pch_src"] + auxiliaryInputs: ["hpp"] + Artifact { + filePath: product.name + "_c.gch" + fileTags: ["c_pch"] + } + prepare: { + return Gcc.prepareCompiler.apply(Gcc, arguments); + } + } + + Rule { + condition: useCxxPrecompiledHeader + inputs: ["cpp_pch_src"] + auxiliaryInputs: ["hpp"] + Artifact { + filePath: product.name + "_cpp.gch" + fileTags: ["cpp_pch"] + } + prepare: { + return Gcc.prepareCompiler.apply(Gcc, arguments); + } + } + + Rule { + condition: useObjcPrecompiledHeader + inputs: ["objc_pch_src"] + auxiliaryInputs: ["hpp"] + Artifact { + filePath: product.name + "_objc.gch" + fileTags: ["objc_pch"] + } + prepare: { + return Gcc.prepareCompiler.apply(Gcc, arguments); + } + } + + Rule { + condition: useObjcxxPrecompiledHeader + inputs: ["objcpp_pch_src"] + auxiliaryInputs: ["hpp"] + Artifact { + filePath: product.name + "_objcpp.gch" + fileTags: ["objcpp_pch"] + } + prepare: { + return Gcc.prepareCompiler.apply(Gcc, arguments); + } + } + + FileTagger { + patterns: "*.s" + fileTags: ["asm"] + } + + FileTagger { + patterns: "*.S" + fileTags: ["asm_cpp"] + } + + FileTagger { + patterns: "*.sx" + fileTags: ["asm_cpp"] + } + + Scanner { + inputs: ["linkerscript"] + recursive: true + scan: { + console.debug("scanning linkerscript " + filePath + " for dependencies"); + var retval = []; + var linkerScript = new TextFile(filePath, TextFile.ReadOnly); + var regexp = /[\s]*INCLUDE[\s]+(\S+).*/ // "INCLUDE filename" + var match; + while (!linkerScript.atEof()) { + match = regexp.exec(linkerScript.readLine()); + if (match) { + var dependencyFileName = match[1]; + retval.push(dependencyFileName); + console.debug("linkerscript " + filePath + " depends on " + dependencyFileName); + } + } + linkerScript.close(); + return retval; + } + searchPaths: { + var retval = []; + for (var i = 0; i < (product.cpp.libraryPaths || []).length; i++) + retval.push(product.cpp.libraryPaths[i]); + var regexp = /[\s]*SEARCH_DIR\((\S+)\).*/ // "SEARCH_DIR(path)" + var match; + var linkerScript = new TextFile(input.filePath, TextFile.ReadOnly); + while (!linkerScript.atEof()) { + match = regexp.exec(linkerScript.readLine()); + if(match) { + var additionalPath = match[1]; + // path can be quoted to use non-latin letters, remove quotes if present + if (additionalPath.startsWith("\"") && additionalPath.endsWith("\"")) + additionalPath = additionalPath.slice(1, additionalPath.length - 1); + retval.push(additionalPath); + } + } + linkerScript.close(); + return retval; + } + } +} diff --git a/share/qbs/modules/cpp/LinuxGCC.qbs b/share/qbs/modules/cpp/LinuxGCC.qbs new file mode 100644 index 00000000..14fb0a7e --- /dev/null +++ b/share/qbs/modules/cpp/LinuxGCC.qbs @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.Process + +UnixGCC { + condition: qbs.targetOS.contains('linux') && + qbs.toolchain && qbs.toolchain.contains('gcc') + priority: 1 + + targetVendor: "pc" + targetSystem: "linux" + targetAbi: "gnu" + + Probe { + id: runPathsProbe + condition: !_skipAllChecks && qbs.targetPlatform === qbs.hostPlatform + property stringList systemRunPaths: [] + configure: { + var paths = []; + var ldconfig = new Process(); + try { + var success = ldconfig.exec("ldconfig", ["-vNX"]); + if (success === -1) + return; + var line; + do { + line = ldconfig.readLine(); + if (line.charAt(0) === '/') { + var colonIndex = line.indexOf(':'); + if (colonIndex == -1) + continue; + paths.push(line.slice(0, colonIndex)); + } + } while (line && line.length > 0) + found = true; + systemRunPaths = paths; + } finally { + ldconfig.close(); + } + } + } + + systemRunPaths: runPathsProbe.found ? runPathsProbe.systemRunPaths : base +} diff --git a/share/qbs/modules/cpp/MingwBaseModule.qbs b/share/qbs/modules/cpp/MingwBaseModule.qbs new file mode 100644 index 00000000..831512c5 --- /dev/null +++ b/share/qbs/modules/cpp/MingwBaseModule.qbs @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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.TextFile +import qbs.Utilities +import qbs.WindowsUtils + +import "setuprunenv.js" as SetupRunEnv + +GenericGCC { + condition: false + + dynamicLibrarySuffix: ".dll" + executableSuffix: ".exe" + debugInfoSuffix: ".debug" + imageFormat: "pe" + windowsApiCharacterSet: "unicode" + platformDefines: base.concat(WindowsUtils.characterSetDefines(windowsApiCharacterSet)) + .concat("WIN32") + + Properties { + condition: product.multiplexByQbsProperties.contains("buildVariants") + && qbs.buildVariants && qbs.buildVariants.length > 1 + && qbs.buildVariant !== "release" + && product.type.containsAny(["staticlibrary", "dynamiclibrary"]) + variantSuffix: "d" + } + + FileTagger { + patterns: ["*.manifest"] + fileTags: ["native.pe.manifest"] + } + + FileTagger { + patterns: ["*.rc"] + fileTags: ["rc"] + } + + Rule { + inputs: ["native.pe.manifest"] + multiplex: true + + outputFileTags: ["rc"] + outputArtifacts: { + if (product.type.containsAny(["application", "dynamiclibrary"])) { + return [{ + filePath: input.completeBaseName + ".rc", + fileTags: ["rc"] + }]; + } + return []; + } + + prepare: { + var inputList = inputs["native.pe.manifest"]; + // TODO: Emulate manifest merging like Microsoft's mt.exe tool does + if (inputList.length !== 1) { + throw("The MinGW toolchain does not support manifest merging; " + + "you may only specify a single manifest file to embed into your assembly."); + } + + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.productType = product.type; + cmd.inputFilePath = inputList[0].filePath; + cmd.outputFilePath = output.filePath; + cmd.sourceCode = function() { + var tf; + try { + tf = new TextFile(outputFilePath, TextFile.WriteOnly); + if (productType.contains("application")) + tf.write("1 "); // CREATEPROCESS_MANIFEST_RESOURCE_ID + else if (productType.contains("dynamiclibrary")) + tf.write("2 "); // ISOLATIONAWARE_MANIFEST_RESOURCE_ID + tf.write("24 "); // RT_MANIFEST + tf.writeLine(Utilities.cStringQuote(inputFilePath)); + } finally { + if (tf) + tf.close(); + } + }; + return [cmd]; + } + } +} + diff --git a/share/qbs/modules/cpp/UnixGCC.qbs b/share/qbs/modules/cpp/UnixGCC.qbs new file mode 100644 index 00000000..94dfb190 --- /dev/null +++ b/share/qbs/modules/cpp/UnixGCC.qbs @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.File + +GenericGCC { + condition: qbs.toolchain && qbs.toolchain.contains("gcc") + && qbs.targetOS.contains("unix") + priority: -50 + + dynamicLibraryPrefix: "lib" + loadableModulePrefix: "lib" + dynamicLibrarySuffix: ".so" + debugInfoSuffix: ".debug" + imageFormat: "elf" + systemRunPaths: ["/lib", "/usr/lib"].filter(function(p) { return File.exists(p); }) + rpathOrigin: "$ORIGIN" + useRPathLink: true + rpathLinkFlag: "-rpath-link=" +} + diff --git a/share/qbs/modules/cpp/android-gcc.qbs b/share/qbs/modules/cpp/android-gcc.qbs new file mode 100644 index 00000000..bd58cbcc --- /dev/null +++ b/share/qbs/modules/cpp/android-gcc.qbs @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.TextFile +import qbs.Utilities +import "../../modules/Android/ndk/utils.js" as NdkUtils +import 'gcc.js' as Gcc + +LinuxGCC { + Depends { name: "Android.ndk" } + + condition: qbs.targetOS.contains("android") && qbs.toolchain && qbs.toolchain.contains("llvm") + priority: 2 + rpaths: [rpathOrigin] + + cxxLanguageVersion: "c++14" + property string cxxStlBaseDir: FileInfo.joinPaths(Android.ndk.ndkDir, "sources", "cxx-stl") + property string stlBaseDir: FileInfo.joinPaths(cxxStlBaseDir, "llvm-libc++") + + property string stlLibsDir: { + if (stlBaseDir) + return FileInfo.joinPaths(stlBaseDir, "libs", Android.ndk.abi); + return undefined; + } + + property string sharedStlFilePath: (stlLibsDir && Android.ndk.appStl.endsWith("_shared")) + ? FileInfo.joinPaths(stlLibsDir, dynamicLibraryPrefix + Android.ndk.appStl + dynamicLibrarySuffix) + : undefined + property string staticStlFilePath: (stlLibsDir && Android.ndk.appStl.endsWith("_static")) + ? FileInfo.joinPaths(stlLibsDir, NdkUtils.stlFilePath(staticLibraryPrefix, Android.ndk, staticLibrarySuffix)) + : undefined + + Group { + name: "Android STL" + condition: product.cpp.sharedStlFilePath && product.cpp.shouldLink + files: product.cpp.sharedStlFilePath ? [product.cpp.sharedStlFilePath] : [] + fileTags: "android.stl" + } + + toolchainInstallPath: FileInfo.joinPaths(Android.ndk.ndkDir, "toolchains", + "llvm", "prebuilt", + Android.ndk.hostArch, "bin") + + property string toolchainTriple: [targetAbi === "androideabi" ? "arm" : targetArch, + targetSystem, targetAbi].join("-") + + internalVersion: undefined + toolchainPrefix: undefined + + machineType: { + if (Android.ndk.abi === "armeabi-v7a") + return "armv7-a"; + } + + qbs.optimization: targetAbi === "androideabi" ? "small" : base + + enableExceptions: Android.ndk.appStl !== "system" + enableRtti: Android.ndk.appStl !== "system" + + commonCompilerFlags: NdkUtils.commonCompilerFlags(qbs.toolchain, qbs.buildVariant, Android.ndk) + + linkerFlags: NdkUtils.commonLinkerFlags(Android.ndk.abi); + driverLinkerFlags: { + var flags = ["-fuse-ld=lld", "-Wl,--exclude-libs,libgcc.a", "-Wl,--exclude-libs,libatomic.a", "-nostdlib++"]; + if (Android.ndk.appStl.startsWith("c++") && Android.ndk.abi === "armeabi-v7a") + flags = flags.concat(["-Wl,--exclude-libs,libunwind.a"]); + return flags; + } + + platformDriverFlags: ["-fdata-sections", "-ffunction-sections", "-funwind-tables", + "-fstack-protector-strong", "-no-canonical-prefixes"] + + libraryPaths: { + var prefix = FileInfo.joinPaths(sysroot, "usr"); + var paths = []; + if (Android.ndk.abi === "x86_64") // no lib64 for arm64-v8a + paths.push(FileInfo.joinPaths(prefix, "lib64")); + paths.push(FileInfo.joinPaths(prefix, "lib")); + paths.push(stlLibsDir); + return paths; + } + + dynamicLibraries: { + var libs = ["c", "m"]; + if (sharedStlFilePath) + libs.push(FileInfo.joinPaths(stlLibsDir, NdkUtils.stlFilePath(dynamicLibraryPrefix, Android.ndk, dynamicLibrarySuffix))); + return libs; + } + staticLibraries: staticStlFilePath + systemIncludePaths: { + var includes = [FileInfo.joinPaths(sysroot, "usr", "include", toolchainTriple)]; + if (Android.ndk.abi === "armeabi-v7a") { + includes.push(FileInfo.joinPaths(Android.ndk.ndkDir, "sources", "android", + "support", "include")); + } + includes.push(FileInfo.joinPaths(stlBaseDir, "include")); + includes.push(FileInfo.joinPaths(stlBaseDir + "abi", "include")); + return includes; + } + + defines: { + var list = ["ANDROID"]; + // Might be superseded by an -mandroid-version or similar Clang compiler flag in future + list.push("__ANDROID_API__=" + Android.ndk.platformVersion); + return list; + } + + binutilsPath: FileInfo.joinPaths(Android.ndk.ndkDir, "toolchains", "llvm", "prebuilt", + Android.ndk.hostArch, "bin"); + binutilsPathPrefix: FileInfo.joinPaths(binutilsPath, "llvm-") + syslibroot: FileInfo.joinPaths(Android.ndk.ndkDir, "platforms", + Android.ndk.platform, "arch-" + + NdkUtils.abiNameToDirName(Android.ndk.abi)) + sysroot: FileInfo.joinPaths(Android.ndk.ndkDir, "sysroot") + + targetArch: { + switch (qbs.architecture) { + case "arm64": + return "aarch64"; + case "armv5": + case "armv5te": + return "armv5te"; + case "armv7a": + case "x86_64": + return qbs.architecture; + case "x86": + return "i686"; + } + } + + targetVendor: "none" + targetSystem: "linux" + targetAbi: "android" + (["armeabi", "armeabi-v7a"].contains(Android.ndk.abi) ? "eabi" : "") + + endianness: "little" + + Rule { + condition: shouldLink + inputs: "dynamiclibrary" + Artifact { + filePath: FileInfo.joinPaths("stripped-libs", input.fileName) + fileTags: "android.nativelibrary" + } + prepare: { + var stripArgs = ["--strip-all", "-o", output.filePath, input.filePath]; + var stripCmd = new Command(product.cpp.stripPath, stripArgs); + stripCmd.description = "Stripping unneeded symbols from " + input.fileName; + return stripCmd; + } + } + + _skipAllChecks: !shouldLink + + validate: { + if (_skipAllChecks) + return; + var baseValidator = new ModUtils.PropertyValidator("qbs"); + baseValidator.addCustomValidator("architecture", targetArch, function (value) { + return value !== undefined; + }, "unknown Android architecture '" + qbs.architecture + "'."); + + var validator = new ModUtils.PropertyValidator("cpp"); + validator.setRequiredProperty("targetArch", targetArch); + + return baseValidator.validate() && validator.validate(); + } +} diff --git a/share/qbs/modules/cpp/cpp.js b/share/qbs/modules/cpp/cpp.js new file mode 100644 index 00000000..0e440bdb --- /dev/null +++ b/share/qbs/modules/cpp/cpp.js @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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. +** +****************************************************************************/ + +function languageVersion(versionArray, knownValues, lang) { + if (!versionArray) + return undefined; + var versions = [].uniqueConcat(versionArray); + if (versions.length === 1) + return versions[0]; + for (var i = 0; i < knownValues.length; ++i) { + var candidate = knownValues[i]; + if (versions.contains(candidate)) + return candidate; + } + var version = versions[0]; + console.debug("Randomly choosing '" + version + + "' from list of unknown " + lang + " version strings (" + versions + ")"); + return version; +} diff --git a/share/qbs/modules/cpp/darwin.js b/share/qbs/modules/cpp/darwin.js new file mode 100644 index 00000000..6373b57c --- /dev/null +++ b/share/qbs/modules/cpp/darwin.js @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** 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 File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var Gcc = require("./gcc.js"); +var ModUtils = require("qbs.ModUtils"); +var PathTools = require("qbs.PathTools"); + +function lipoOutputArtifacts(product, inputs, fileTag, debugSuffix) { + var buildVariants = []; + for (var i = 0; i < inputs[fileTag].length; ++i) { + var variant = inputs[fileTag][i].qbs.buildVariant; + var suffix = inputs[fileTag][i].cpp.variantSuffix; + if (!buildVariants.some(function (x) { return x.name === variant; })) + buildVariants.push({ name: variant, suffix: suffix }); + } + + var list = []; + + if (fileTag === "dynamiclibrary") { + Array.prototype.push.apply(list, buildVariants.map(function (variant) { + return { + filePath: product.destinationDirectory + "/.sosymbols/" + + PathTools.dynamicLibraryFilePath(product, variant.suffix), + fileTags: ["dynamiclibrary_symbols"], + qbs: { buildVariant: variant.name }, + cpp: { variantSuffix: variant.suffix }, + alwaysUpdated: false + }; + })); + } + + // Bundles should have a "normal" variant. In the case of frameworks, they cannot normally be + // linked to without a default variant unless a variant is specifically chosen at link time + // by passing the full path to the shared library executable instead of the -framework switch. + // Technically this doesn't affect qbs since qbs always uses full paths for internal + // dependencies but the "normal" variant is always the one that is linked to, since the + // alternative variants should only be chosen at runtime using the DYLD_IMAGE_SUFFIX variable. + // So for frameworks we'll create a symlink to the "default" variant as chosen by the user + // (we cannot do this automatically since the user must tell us which variant should be + // preferred, if there are multiple alternative variants). Applications are fine without a + // symlink but still need an explicitly chosen variant to set as the CFBundleExecutable so that + // 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"; }) + && 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) + defaultVariant = variant; + }); + if (!defaultVariant) { + throw new Error("qbs.defaultBuildVariant is '" + defaultBuildVariant + "', but this " + + "variant is not in the qbs.buildVariants list (" + + product.qbs.buildVariants.join(", ") + ")"); + } + + buildVariants.push({ + name: "release", + suffix: "", + isSymLink: true + }); + } + + Array.prototype.push.apply(list, buildVariants.map(function (variant) { + var tags = ["bundle.input"]; + if (variant.isSymLink) + tags.push("bundle.variant_symlink"); + else + tags.push(fileTag, "primary"); + + return { + filePath: FileInfo.joinPaths(product.destinationDirectory, + PathTools.linkerOutputFilePath(fileTag, product, + variant.suffix)), + fileTags: tags, + qbs: { + buildVariant: variant.name, + _buildVariantFileName: variant.isSymLink && defaultVariant + ? FileInfo.fileName(PathTools.linkerOutputFilePath( + fileTag, product, + defaultVariant.suffix)) + : undefined + }, + bundle: { + _bundleFilePath: product.destinationDirectory + "/" + + PathTools.bundleExecutableFilePath(product, variant.suffix) + }, + cpp: { + variantSuffix: variant.suffix + } + }; + })); + if (debugSuffix) + Array.prototype.push.apply(list, Gcc.debugInfoArtifacts(product, buildVariants, + debugSuffix)); + return list; +} + +function prepareLipo(project, product, inputs, outputs, input, output) { + var cmd; + var commands = []; + for (var p in inputs) + inputs[p] = inputs[p].filter(function(inp) { return inp.product.name === product.name; }); + var allInputs = [].concat.apply([], Object.keys(inputs).map(function (tag) { + return ["application", "dynamiclibrary", "staticlibrary", "loadablemodule"].contains(tag) + ? inputs[tag] : []; + })); + + (outputs["bundle.variant_symlink"] || []).map(function (symlink) { + cmd = new Command("ln", ["-sfn", symlink.qbs._buildVariantFileName, symlink.filePath]); + cmd.silent = true; + commands.push(cmd); + }); + + for (var i = 0; i < outputs.primary.length; ++i) { + var vInputs = allInputs.filter(function (f) { + return f.qbs.buildVariant === outputs.primary[i].qbs.buildVariant + }).map(function (f) { + return f.filePath + }); + + if (vInputs.length > 1 || product.cpp.alwaysUseLipo) { + cmd = new Command(ModUtils.moduleProperty(product, "lipoPath"), + ["-create", "-output", outputs.primary[i].filePath].concat(vInputs)); + cmd.description = "lipo " + outputs.primary[i].fileName; + cmd.highlight = "linker"; + } else { + cmd = new JavaScriptCommand(); + cmd.src = vInputs[0]; + cmd.dst = outputs.primary[i].filePath; + cmd.sourceCode = function () { + File.copy(src, dst); + }; + cmd.silent = true; + } + + commands.push(cmd); + } + + var debugInfo = outputs.debuginfo_app || outputs.debuginfo_dll + || outputs.debuginfo_loadablemodule; + if (debugInfo) { + var dsymPath = debugInfo[0].filePath; + if (outputs.debuginfo_bundle && outputs.debuginfo_bundle[0]) + dsymPath = outputs.debuginfo_bundle[0].filePath; + var flags = ModUtils.moduleProperty(product, "dsymutilFlags") || []; + cmd = new Command(ModUtils.moduleProperty(product, "dsymutilPath"), flags.concat([ + "-o", dsymPath + ]).concat(outputs.primary.map(function (f) { return f.filePath; }))); + cmd.description = "generating dSYM for " + product.name; + commands.push(cmd); + } + + cmd = new Command(ModUtils.moduleProperty(product, "stripPath"), + ["-S", outputs.primary[0].filePath]); + cmd.silent = true; + commands.push(cmd); + if (outputs.dynamiclibrary_symbols) + Array.prototype.push.apply(commands, Gcc.createSymbolCheckingCommands(product, outputs)); + return commands; +} + diff --git a/share/qbs/modules/cpp/freebsd-gcc.qbs b/share/qbs/modules/cpp/freebsd-gcc.qbs new file mode 100644 index 00000000..929c4e55 --- /dev/null +++ b/share/qbs/modules/cpp/freebsd-gcc.qbs @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "freebsd.js" as FreeBSD + +UnixGCC { + condition: qbs.targetOS.contains("freebsd") && + qbs.toolchain && qbs.toolchain.contains("gcc") + priority: 1 + + targetSystem: "freebsd" + (qbs.hostOS.contains("freebsd") ? FreeBSD.hostKernelRelease() : "") + + distributionIncludePaths: ["/usr/local/include"] + distributionLibraryPaths: ["/usr/local/lib"] +} diff --git a/share/qbs/modules/cpp/freebsd.js b/share/qbs/modules/cpp/freebsd.js new file mode 100644 index 00000000..37dc432a --- /dev/null +++ b/share/qbs/modules/cpp/freebsd.js @@ -0,0 +1,10 @@ +var Utilities = require("qbs.Utilities"); + +function stripKernelReleaseSuffix(r) { + var idx = r.indexOf("-RELEASE"); + return idx >= 0 ? r.substr(0, idx) : r; +} + +function hostKernelRelease() { + return stripKernelReleaseSuffix(Utilities.kernelVersion()); +} diff --git a/share/qbs/modules/cpp/gcc.js b/share/qbs/modules/cpp/gcc.js new file mode 100644 index 00000000..57666fe4 --- /dev/null +++ b/share/qbs/modules/cpp/gcc.js @@ -0,0 +1,1579 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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 Cpp = require("cpp.js"); +var File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var DarwinTools = require("qbs.DarwinTools"); +var ModUtils = require("qbs.ModUtils"); +var PathTools = require("qbs.PathTools"); +var Process = require("qbs.Process"); +var TextFile = require("qbs.TextFile"); +var UnixUtils = require("qbs.UnixUtils"); +var Utilities = require("qbs.Utilities"); +var WindowsUtils = require("qbs.WindowsUtils"); + +function effectiveLinkerPath(product, inputs) { + if (product.cpp.linkerMode === "automatic") { + var compilers = product.cpp.compilerPathByLanguage; + if (compilers) { + if (inputs.cpp_obj || inputs.cpp_staticlibrary) { + console.log("Found C++ or Objective-C++ objects, choosing C++ linker for " + + product.name); + return compilers["cpp"]; + } + + if (inputs.c_obj || inputs.c_staticlibrary) { + console.log("Found C or Objective-C objects, choosing C linker for " + + product.name); + return compilers["c"]; + } + } + + console.log("Found no C-language objects, choosing system linker for " + + product.name); + } + + return product.cpp.linkerPath; +} + +function useCompilerDriverLinker(product, inputs) { + var linker = effectiveLinkerPath(product, inputs); + var compilers = product.cpp.compilerPathByLanguage; + if (compilers) { + return linker === compilers["cpp"] + || linker === compilers["c"]; + } + return linker === product.cpp.compilerPath; +} + +function collectLibraryDependencies(product, isDarwin) { + var publicDeps = {}; + var objects = []; + var objectByFilePath = {}; + var tagForLinkingAgainstSharedLib = product.cpp.imageFormat === "pe" + ? "dynamiclibrary_import" : "dynamiclibrary"; + var removeDuplicateLibraries = product.cpp.removeDuplicateLibraries + + function addObject(obj, addFunc) { + /* If the object is already known, remove its previous usage and insert + * it again in the new desired position. This preserves the order of + * the other objects, and is analogous to what qmake does (see the + * mergeLflags parameter in UnixMakefileGenerator::findLibraries()). + */ + if (removeDuplicateLibraries && (obj.filePath in objectByFilePath)) { + var oldObj = objectByFilePath[obj.filePath]; + var i = objects.indexOf(oldObj); + if (i >= 0) + objects.splice(i, 1); + } + addFunc.call(objects, obj); + objectByFilePath[obj.filePath] = obj; + } + + function addPublicFilePath(filePath, dep) { + var existing = objectByFilePath[filePath]; + var wholeArchive = dep.parameters.cpp && dep.parameters.cpp.linkWholeArchive; + var symbolLinkMode = dep.parameters.cpp && dep.parameters.cpp.symbolLinkMode; + if (existing) { + existing.direct = true; + existing.wholeArchive = wholeArchive; + existing.symbolLinkMode = symbolLinkMode; + } else { + addObject({ direct: true, filePath: filePath, + wholeArchive: wholeArchive, symbolLinkMode: symbolLinkMode }, + Array.prototype.unshift); + } + } + + function addPrivateFilePath(filePath) { + var existing = objectByFilePath[filePath]; + if (!existing) + addObject({ direct: false, filePath: filePath }, Array.prototype.unshift); + } + + function addArtifactFilePaths(dep, tag, addFunction) { + var artifacts = dep.artifacts[tag]; + if (!artifacts) + return; + var artifactFilePaths = artifacts.map(function(a) { return a.filePath; }); + for (var i = 0; i < artifactFilePaths.length; ++i) + addFunction(artifactFilePaths[i], dep); + } + + function addExternalLibs(obj) { + if (!obj.cpp) + return; + function ensureArray(a) { + return Array.isArray(a) ? a : []; + } + function sanitizedModuleListProperty(obj, moduleName, propertyName) { + return ensureArray(ModUtils.sanitizedModuleProperty(obj, moduleName, propertyName)); + } + var externalLibs = [].concat( + ensureArray(sanitizedModuleListProperty(obj, "cpp", "staticLibraries")), + ensureArray(sanitizedModuleListProperty(obj, "cpp", "dynamicLibraries"))); + for (var i = 0, len = externalLibs.length; i < len; ++i) + addObject({ direct: true, filePath: externalLibs[i] }, Array.prototype.push); + if (isDarwin) { + externalLibs = [].concat( + ensureArray(sanitizedModuleListProperty(obj, "cpp", "frameworks"))); + for (var i = 0, len = externalLibs.length; i < len; ++i) + addObject({ direct: true, filePath: externalLibs[i], framework: true }, + Array.prototype.push); + externalLibs = [].concat( + ensureArray(sanitizedModuleListProperty(obj, "cpp", "weakFrameworks"))); + for (var i = 0, len = externalLibs.length; i < len; ++i) + addObject({ direct: true, filePath: externalLibs[i], framework: true, + symbolLinkMode: "weak" }, Array.prototype.push); + } + } + + function traverse(dep, isBelowIndirectDynamicLib) { + if (publicDeps[dep.name]) + return; + + if (dep.parameters.cpp && dep.parameters.cpp.link === false) + return; + + var isStaticLibrary = typeof dep.artifacts["staticlibrary"] !== "undefined"; + var isDynamicLibrary = !isStaticLibrary + && typeof dep.artifacts[tagForLinkingAgainstSharedLib] !== "undefined"; + if (!isStaticLibrary && !isDynamicLibrary) + return; + + var nextIsBelowIndirectDynamicLib = isBelowIndirectDynamicLib || isDynamicLibrary; + dep.dependencies.forEach(function(depdep) { + // If "dep" is an aggregate product, and "depdep" is one of the multiplexed variants + // of the same product, we don't want to depend on the multiplexed variants, because + // that could mean linking more than one time against the same library. Instead skip + // the multiplexed dependency, and depend only on the aggregate one. + if (depdep.name === dep.name) + return; + traverse(depdep, nextIsBelowIndirectDynamicLib); + }); + if (isStaticLibrary) { + if (!isBelowIndirectDynamicLib) { + addArtifactFilePaths(dep, "staticlibrary", addPublicFilePath); + addExternalLibs(dep); + publicDeps[dep.name] = true; + } + } else if (isDynamicLibrary) { + if (!isBelowIndirectDynamicLib) { + addArtifactFilePaths(dep, tagForLinkingAgainstSharedLib, addPublicFilePath); + publicDeps[dep.name] = true; + } else { + addArtifactFilePaths(dep, tagForLinkingAgainstSharedLib, addPrivateFilePath); + } + } + } + + function traverseDirectDependency(dep) { + traverse(dep, false); + } + + product.dependencies.forEach(traverseDirectDependency); + addExternalLibs(product); + + var seenRPathLinkDirs = {}; + var result = { libraries: [], rpath_link: [] }; + objects.forEach( + function (obj) { + if (obj.direct) { + result.libraries.push({ filePath: obj.filePath, + wholeArchive: obj.wholeArchive, + symbolLinkMode: obj.symbolLinkMode, + framework: obj.framework }); + } else { + var dirPath = FileInfo.path(obj.filePath); + if (!seenRPathLinkDirs.hasOwnProperty(dirPath)) { + seenRPathLinkDirs[dirPath] = true; + result.rpath_link.push(dirPath); + } + } + }); + return result; +} + +function escapeLinkerFlags(product, inputs, linkerFlags) { + if (!linkerFlags || linkerFlags.length === 0) + return []; + + if (useCompilerDriverLinker(product, inputs)) { + var sep = ","; + var useXlinker = linkerFlags.some(function (f) { return f.contains(sep); }); + if (useXlinker) { + // One or more linker arguments contain the separator character itself + // Use -Xlinker to handle these + var xlinkerFlags = []; + linkerFlags.map(function (linkerFlag) { + if (product.cpp.enableSuspiciousLinkerFlagWarnings + && linkerFlag.startsWith("-Wl,")) { + console.warn("Encountered escaped linker flag '" + linkerFlag + "'. This may " + + "cause the target to fail to link. Please do not escape these " + + "flags manually; qbs does that for you."); + } + xlinkerFlags.push("-Xlinker", linkerFlag); + }); + return xlinkerFlags; + } + + if (product.cpp.enableSuspiciousLinkerFlagWarnings && linkerFlags.contains("-Xlinker")) { + console.warn("Encountered -Xlinker linker flag escape sequence. This may cause the " + + "target to fail to link. Please do not escape these flags manually; " + + "qbs does that for you."); + } + + // If no linker arguments contain the separator character we can just use -Wl, + // which is more compact and easier to read in logs + return [["-Wl"].concat(linkerFlags).join(sep)]; + } + + return linkerFlags; +} + +function linkerFlags(project, product, inputs, outputs, primaryOutput, linkerPath) { + var libraryPaths = product.cpp.libraryPaths; + var distributionLibraryPaths = product.cpp.distributionLibraryPaths; + var isDarwin = product.qbs.targetOS.contains("darwin"); + var libraryDependencies = collectLibraryDependencies(product, isDarwin); + var frameworks = product.cpp.frameworks; + var weakFrameworks = product.cpp.weakFrameworks; + var rpaths = (product.cpp.useRPaths !== false) ? product.cpp.rpaths : undefined; + var systemRunPaths = product.cpp.systemRunPaths || []; + var canonicalSystemRunPaths = systemRunPaths.map(function(p) { + return File.canonicalFilePath(p); + }); + var i, args = additionalCompilerAndLinkerFlags(product); + + var escapableLinkerFlags = []; + + if (primaryOutput.fileTags.contains("dynamiclibrary")) { + if (isDarwin) { + args.push((function () { + var tags = ["c", "cpp", "objc", "objcpp", "asm_cpp"]; + for (var i = 0; i < tags.length; ++i) { + if (linkerPath === product.cpp.compilerPathByLanguage[tags[i]]) + return "-dynamiclib"; + } + return "-dylib"; // for ld64 + })()); + } else { + args.push("-shared"); + } + + if (isDarwin) { + if (product.cpp.internalVersion) + args.push("-current_version", product.cpp.internalVersion); + escapableLinkerFlags.push("-install_name", UnixUtils.soname(product, + primaryOutput.fileName)); + } else if (product.cpp.imageFormat === "elf") { + escapableLinkerFlags.push("-soname=" + UnixUtils.soname(product, + primaryOutput.fileName)); + } + } + + if (primaryOutput.fileTags.contains("loadablemodule")) + args.push(isDarwin ? "-bundle" : "-shared"); + + if (primaryOutput.fileTags.containsAny(["dynamiclibrary", "loadablemodule"])) { + if (isDarwin) + escapableLinkerFlags.push("-headerpad_max_install_names"); + else if (product.cpp.imageFormat === "elf") + escapableLinkerFlags.push("--as-needed"); + } + + if (isLegacyQnxSdk(product)) { + ["c", "cpp"].map(function (tag) { + if (linkerPath === product.cpp.compilerPathByLanguage[tag]) + args = args.concat(qnxLangArgs(product, tag)); + }); + } + + var targetLinkerFlags = product.cpp.targetLinkerFlags; + if (targetLinkerFlags) + Array.prototype.push.apply(escapableLinkerFlags, targetLinkerFlags); + + var sysroot = product.cpp.syslibroot; + if (sysroot) { + if (product.qbs.toolchain.contains("qcc")) + escapableLinkerFlags.push("--sysroot=" + sysroot); + else if (isDarwin) + escapableLinkerFlags.push("-syslibroot", sysroot); + else + args.push("--sysroot=" + sysroot); // do not escape, compiler-as-linker also needs it + } + + if (product.cpp.allowUnresolvedSymbols) { + if (isDarwin) + escapableLinkerFlags.push("-undefined", "suppress"); + else + escapableLinkerFlags.push("--unresolved-symbols=ignore-all"); + } + + function fixupRPath(rpath) { + // iOS, tvOS, watchOS, and others, are fine + if (!product.qbs.targetOS.contains("macos")) + return rpath; + + // ...as are newer versions of macOS + var min = product.cpp.minimumMacosVersion; + if (min && Utilities.versionCompare(min, "10.10") >= 0) + return rpath; + + // In older versions of dyld, a trailing slash is required + if (!rpath.endsWith("/")) + return rpath + "/"; + + return rpath; + } + + if (!product.qbs.targetOS.contains("windows")) { + function isNotSystemRunPath(p) { + return !FileInfo.isAbsolutePath(p) || (!systemRunPaths.contains(p) + && !canonicalSystemRunPaths.contains(File.canonicalFilePath(p))); + }; + for (i in rpaths) { + if (isNotSystemRunPath(rpaths[i])) + escapableLinkerFlags.push("-rpath", fixupRPath(rpaths[i])); + } + } + + if (product.cpp.entryPoint) + escapableLinkerFlags.push("-e", product.cpp.entryPoint); + + if (product.qbs.toolchain.contains("mingw")) { + if (product.consoleApplication !== undefined) + escapableLinkerFlags.push("-subsystem", + product.consoleApplication ? "console" : "windows"); + + var minimumWindowsVersion = product.cpp.minimumWindowsVersion; + if (minimumWindowsVersion) { + var subsystemVersion = WindowsUtils.getWindowsVersionInFormat(minimumWindowsVersion, 'subsystem'); + if (subsystemVersion) { + var major = subsystemVersion.split('.')[0]; + var minor = subsystemVersion.split('.')[1]; + + // http://sourceware.org/binutils/docs/ld/Options.html + escapableLinkerFlags.push("--major-subsystem-version", major, + "--minor-subsystem-version", minor, + "--major-os-version", major, + "--minor-os-version", minor); + } + } + } + + if (inputs.aggregate_infoplist) + args.push("-sectcreate", "__TEXT", "__info_plist", inputs.aggregate_infoplist[0].filePath); + + var isLinkingCppObjects = !!(inputs.cpp_obj || inputs.cpp_staticlibrary); + var stdlib = isLinkingCppObjects + ? product.cpp.cxxStandardLibrary + : undefined; + if (stdlib && product.qbs.toolchain.contains("clang")) + args.push("-stdlib=" + stdlib); + + // Flags for library search paths + var allLibraryPaths = []; + if (libraryPaths) + allLibraryPaths = allLibraryPaths.uniqueConcat(libraryPaths); + if (distributionLibraryPaths) + allLibraryPaths = allLibraryPaths.uniqueConcat(distributionLibraryPaths); + if (systemRunPaths.length > 0) + allLibraryPaths = allLibraryPaths.filter(isNotSystemRunPath); + args = args.concat(allLibraryPaths.map(function(path) { return '-L' + path })); + + var linkerScripts = inputs.linkerscript + ? inputs.linkerscript.map(function(a) { return a.filePath; }) : []; + 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; }) : []; + Array.prototype.push.apply(escapableLinkerFlags, [].uniqueConcat(versionScripts) + .map(function(path) { return '--version-script=' + path })); + + if (isDarwin && product.cpp.warningLevel === "none") + args.push('-w'); + + var useCompilerDriver = useCompilerDriverLinker(product, inputs); + args = args.concat(configFlags(product, useCompilerDriver)); + 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 + // sending the rest of the arguments through the response file. + args.push("-o", primaryOutput.filePath); + + if (inputs.obj) + args = args.concat(inputs.obj.map(function (obj) { return obj.filePath })); + + for (i in frameworks) { + frameworkExecutablePath = PathTools.frameworkExecutablePath(frameworks[i]); + if (FileInfo.isAbsolutePath(frameworkExecutablePath)) + args.push(frameworkExecutablePath); + else + args = args.concat(['-framework', frameworks[i]]); + } + + for (i in weakFrameworks) { + frameworkExecutablePath = PathTools.frameworkExecutablePath(weakFrameworks[i]); + if (FileInfo.isAbsolutePath(frameworkExecutablePath)) + args = args.concat(['-weak_library', frameworkExecutablePath]); + else + args = args.concat(['-weak_framework', weakFrameworks[i]]); + } + + var wholeArchiveActive = false; + var prevLib; + for (i = 0; i < libraryDependencies.libraries.length; ++i) { + var dep = libraryDependencies.libraries[i]; + var lib = dep.filePath; + if (lib === prevLib) + continue; + prevLib = lib; + if (dep.wholeArchive && !wholeArchiveActive) { + var wholeArchiveFlag; + if (isDarwin) { + wholeArchiveFlag = "-force_load"; + } else { + wholeArchiveFlag = "--whole-archive"; + wholeArchiveActive = true; + } + Array.prototype.push.apply(args, + escapeLinkerFlags(product, inputs, [wholeArchiveFlag])); + } + if (!dep.wholeArchive && wholeArchiveActive) { + Array.prototype.push.apply(args, + escapeLinkerFlags(product, inputs, ["--no-whole-archive"])); + wholeArchiveActive = false; + } + + var symbolLinkMode = dep.symbolLinkMode; + if (isDarwin && symbolLinkMode) { + if (!["lazy", "reexport", "upward", "weak"].contains(symbolLinkMode)) + throw new Error("unknown value '" + symbolLinkMode + "' for cpp.symbolLinkMode"); + + if (FileInfo.isAbsolutePath(lib) || lib.startsWith('@')) + escapableLinkerFlags.push("-" + symbolLinkMode + "_library", lib); + else if (dep.framework) + escapableLinkerFlags.push("-" + symbolLinkMode + "_framework", lib); + else + escapableLinkerFlags.push("-" + symbolLinkMode + "-l" + lib); + } else if (FileInfo.isAbsolutePath(lib) || lib.startsWith('@')) { + args.push(dep.framework ? PathTools.frameworkExecutablePath(lib) : lib); + } else if (dep.framework) { + args.push("-framework", lib); + } else { + args.push('-l' + lib); + } + } + if (wholeArchiveActive) { + 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"); + Array.prototype.push.apply(escapableLinkerFlags, libraryDependencies.rpath_link.map( + function(dir) { + return product.cpp.rpathLinkFlag + dir; + })); + } + + var importLibs = outputs.dynamiclibrary_import; + if (importLibs) + escapableLinkerFlags.push("--out-implib", importLibs[0].filePath); + + if (outputs.application && product.cpp.generateLinkerMapFile) { + if (isDarwin) + escapableLinkerFlags.push("-map", outputs.mem_map[0].filePath); + else + escapableLinkerFlags.push("-Map=" + outputs.mem_map[0].filePath); + } + + var escapedLinkerFlags = escapeLinkerFlags(product, inputs, escapableLinkerFlags); + Array.prototype.push.apply(escapedLinkerFlags, args); + var driverLinkerFlags = useCompilerDriver ? product.cpp.driverLinkerFlags : undefined; + if (driverLinkerFlags) + Array.prototype.push.apply(escapedLinkerFlags, driverLinkerFlags); + return escapedLinkerFlags; +} + +// for compiler AND linker +function configFlags(config, isDriver) { + var args = []; + + if (isDriver !== false) { + args = args.concat(config.cpp.platformDriverFlags); + args = args.concat(config.cpp.driverFlags); + args = args.concat(config.cpp.targetDriverFlags); + } + + var frameworkPaths = config.cpp.frameworkPaths; + if (frameworkPaths) + args = args.uniqueConcat(frameworkPaths.map(function(path) { return '-F' + path })); + + var allSystemFrameworkPaths = []; + + var systemFrameworkPaths = config.cpp.systemFrameworkPaths; + if (systemFrameworkPaths) + allSystemFrameworkPaths = allSystemFrameworkPaths.uniqueConcat(systemFrameworkPaths); + + var distributionFrameworkPaths = config.cpp.distributionFrameworkPaths; + if (distributionFrameworkPaths) + allSystemFrameworkPaths = allSystemFrameworkPaths.uniqueConcat(distributionFrameworkPaths); + + args = args.concat(allSystemFrameworkPaths.map(function(path) { return '-iframework' + path })); + + return args; +} + +function languageTagFromFileExtension(toolchain, fileName) { + var i = fileName.lastIndexOf('.'); + if (i === -1) + return; + var m = { + "c" : "c", + "C" : "cpp", + "cpp" : "cpp", + "cxx" : "cpp", + "c++" : "cpp", + "cc" : "cpp", + "m" : "objc", + "mm" : "objcpp", + "s" : "asm", + "S" : "asm_cpp" + }; + if (!toolchain.contains("clang")) + m["sx"] = "asm_cpp"; // clang does NOT recognize .sx + return m[fileName.substring(i + 1)]; +} + +// Older versions of the QNX SDK have C and C++ compilers whose filenames differ only by case, +// which won't work in case insensitive environments like Win32+NTFS, HFS+ and APFS +function isLegacyQnxSdk(config) { + return config.qbs.toolchain.contains("qcc") && config.qnx && !config.qnx.qnx7; +} + +function effectiveCompilerInfo(toolchain, input, output) { + var compilerPath, language; + var tag = ModUtils.fileTagForTargetLanguage(input.fileTags.concat(output.fileTags)); + + // Whether we're compiling a precompiled header or normal source file + var pchOutput = output.fileTags.contains(tag + "_pch"); + + var compilerPathByLanguage = input.cpp.compilerPathByLanguage; + if (compilerPathByLanguage) + compilerPath = compilerPathByLanguage[tag]; + if (!compilerPath + || tag !== languageTagFromFileExtension(toolchain, input.fileName) + || isLegacyQnxSdk(input)) { + if (input.qbs.toolchain.contains("qcc")) + language = qnxLangArgs(input, tag); + else + language = ["-x", languageName(tag) + (pchOutput ? '-header' : '')]; + } + if (!compilerPath) + // fall back to main compiler + compilerPath = input.cpp.compilerPath; + return { + path: compilerPath, + language: language, + tag: tag + }; +} + + +function qnxLangArgs(config, tag) { + switch (tag) { + case "c": + return ["-lang-c"]; + case "cpp": + return ["-lang-c++"]; + default: + return []; + } +} + +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 standardFallbackValueOrDefault(toolchain, compilerVersion, languageVersion, + useLanguageVersionFallback) { + // NEVER use the fallback values (safety brake for users in case our version map is ever wrong) + if (useLanguageVersionFallback === false) + return languageVersion; + + // Deprecated, but compatible with older compiler versions. + // Note that these versions are the first to support the *value* to the -std= command line + // option, not necessarily the first versions where support for that language standard was + // considered fully implemented. Tested manually. + var languageVersionsMap = { + "c++11": { + "fallback": "c++0x", + "toolchains": [ + {"name": "xcode", "version": "4.3"}, + {"name": "clang", "version": "3.0"}, + {"name": "gcc", "version": "4.7"} + ] + }, + "c11": { + "fallback": "c1x", + "toolchains": [ + {"name": "xcode", "version": "5.0"}, + {"name": "clang", "version": "3.1"}, + {"name": "gcc", "version": "4.7"} + ] + }, + "c++14": { + "fallback": "c++1y", + "toolchains": [ + {"name": "xcode", "version": "6.3"}, + {"name": "clang", "version": "3.5"}, + {"name": "gcc", "version": "4.9"} + ] + }, + "c++17": { + "fallback": "c++1z", + "toolchains": [ + {"name": "xcode", "version": "9.3"}, + {"name": "clang", "version": "5.0"}, + {"name": "gcc", "version": "5.1"} + ] + }, + "c++20": { + "fallback": "c++2a", + "toolchains": [ + {"name": "xcode"}, // not yet implemented + {"name": "clang"}, // not yet implemented + {"name": "gcc"} // not yet implemented + ] + } + }; + + var m = languageVersionsMap[languageVersion]; + if (m) { + for (var idx = 0; idx < m.toolchains.length; ++idx) { + var tc = m.toolchains[idx]; + if (toolchain.contains(tc.name)) { + // If we found our toolchain and it doesn't yet support the language standard + // we're requesting, or we're using an older version that only supports the + // preliminary flag, use that. + if (useLanguageVersionFallback + || !tc.version + || Utilities.versionCompare(compilerVersion, tc.version) < 0) + return m.fallback; + break; + } + } + } + + // If we didn't find our toolchain at all, simply use the standard value. + return languageVersion; +} + +function compilerFlags(project, product, input, output, explicitlyDependsOn) { + var i; + + var includePaths = input.cpp.includePaths; + var systemIncludePaths = input.cpp.systemIncludePaths; + var distributionIncludePaths = input.cpp.distributionIncludePaths; + + var platformDefines = input.cpp.platformDefines; + var defines = input.cpp.defines; + + // Determine which C-language we're compiling + var tag = ModUtils.fileTagForTargetLanguage(input.fileTags.concat(output.fileTags)); + if (!["c", "cpp", "objc", "objcpp", "asm_cpp"].contains(tag)) + throw ("unsupported source language: " + tag); + + var compilerInfo = effectiveCompilerInfo(product.qbs.toolchain, + input, output); + + var args = additionalCompilerAndLinkerFlags(product); + + Array.prototype.push.apply(args, product.cpp.sysrootFlags); + handleCpuFeatures(input, args); + + if (input.cpp.debugInformation) + args.push('-g'); + var opt = input.cpp.optimization + if (opt === 'fast') + args.push('-O2'); + if (opt === 'small') + args.push('-Os'); + if (opt === 'none') + args.push('-O0'); + + var warnings = input.cpp.warningLevel + if (warnings === 'none') + args.push('-w'); + if (warnings === 'all') { + args.push('-Wall'); + args.push('-Wextra'); + } + if (input.cpp.treatWarningsAsErrors) + args.push('-Werror'); + + args = args.concat(configFlags(input)); + + if (!input.qbs.toolchain.contains("qcc")) + args.push('-pipe'); + + if (input.cpp.enableReproducibleBuilds) { + var toolchain = product.qbs.toolchain; + if (!toolchain.contains("clang")) { + var hashString = FileInfo.relativePath(project.sourceDirectory, input.filePath); + var hash = Utilities.getHash(hashString); + args.push("-frandom-seed=0x" + hash.substring(0, 8)); + } + + var major = product.cpp.compilerVersionMajor; + var minor = product.cpp.compilerVersionMinor; + if ((toolchain.contains("clang") && (major > 3 || (major === 3 && minor >= 5))) || + (toolchain.contains("gcc") && (major > 4 || (major === 4 && minor >= 9)))) { + args.push("-Wdate-time"); + } + } + + var useArc = input.cpp.automaticReferenceCounting; + if (useArc !== undefined && (tag === "objc" || tag === "objcpp")) { + args.push(useArc ? "-fobjc-arc" : "-fno-objc-arc"); + } + + var enableExceptions = input.cpp.enableExceptions; + if (enableExceptions !== undefined) { + if (tag === "cpp" || tag === "objcpp") + args.push(enableExceptions ? "-fexceptions" : "-fno-exceptions"); + + if (tag === "objc" || tag === "objcpp") { + args.push(enableExceptions ? "-fobjc-exceptions" : "-fno-objc-exceptions"); + if (useArc !== undefined) + args.push(useArc ? "-fobjc-arc-exceptions" : "-fno-objc-arc-exceptions"); + } + } + + var enableRtti = input.cpp.enableRtti; + if (enableRtti !== undefined && (tag === "cpp" || tag === "objcpp")) { + args.push(enableRtti ? "-frtti" : "-fno-rtti"); + } + + var visibility = input.cpp.visibility; + if (!product.qbs.toolchain.contains("mingw")) { + if (visibility === 'hidden' || visibility === 'minimal') + args.push('-fvisibility=hidden'); + if ((visibility === 'hiddenInlines' || visibility === 'minimal') && tag === 'cpp') + args.push('-fvisibility-inlines-hidden'); + if (visibility === 'default') + args.push('-fvisibility=default') + } + + if (compilerInfo.language) + // Only push language arguments if we have to. + Array.prototype.push.apply(args, compilerInfo.language); + + args = args.concat(ModUtils.moduleProperty(input, 'platformFlags'), + ModUtils.moduleProperty(input, 'flags'), + ModUtils.moduleProperty(input, 'platformFlags', tag), + ModUtils.moduleProperty(input, 'flags', tag)); + + var pchTag = compilerInfo.tag + "_pch"; + var pchOutput = output.fileTags.contains(pchTag); + var pchInputs = explicitlyDependsOn[pchTag]; + if (!pchOutput && pchInputs && pchInputs.length === 1 + && ModUtils.moduleProperty(input, 'usePrecompiledHeader', tag)) { + var pchInput = pchInputs[0]; + var pchFilePath = FileInfo.joinPaths(FileInfo.path(pchInput.filePath), + pchInput.completeBaseName); + args.push('-include', pchFilePath); + } + + var prefixHeaders = input.cpp.prefixHeaders; + for (i in prefixHeaders) { + args.push('-include'); + args.push(prefixHeaders[i]); + } + + var positionIndependentCode = input.cpp.positionIndependentCode; + if (positionIndependentCode && !product.qbs.targetOS.contains("windows")) + args.push('-fPIC'); + + var cppFlags = input.cpp.cppFlags; + for (i in cppFlags) + args.push('-Wp,' + cppFlags[i]) + + var allDefines = []; + if (platformDefines) + allDefines = allDefines.uniqueConcat(platformDefines); + if (defines) + allDefines = allDefines.uniqueConcat(defines); + args = args.concat(allDefines.map(function(define) { return '-D' + define })); + if (includePaths) { + args = args.concat([].uniqueConcat(includePaths).map(function(path) { + return input.cpp.includeFlag + path; + })); + } + + var allSystemIncludePaths = []; + if (systemIncludePaths) + allSystemIncludePaths = allSystemIncludePaths.uniqueConcat(systemIncludePaths); + if (distributionIncludePaths) + allSystemIncludePaths = allSystemIncludePaths.uniqueConcat(distributionIncludePaths); + allSystemIncludePaths.forEach(function(v) { args.push(input.cpp.systemIncludeFlag, v); }); + + var minimumWindowsVersion = input.cpp.minimumWindowsVersion; + if (minimumWindowsVersion && product.qbs.targetOS.contains("windows")) { + var hexVersion = WindowsUtils.getWindowsVersionInFormat(minimumWindowsVersion, 'hex'); + if (hexVersion) { + var versionDefs = [ 'WINVER', '_WIN32_WINNT', '_WIN32_WINDOWS' ]; + for (i in versionDefs) + args.push('-D' + versionDefs[i] + '=' + hexVersion); + } + } + + function currentLanguageVersion(tag) { + switch (tag) { + case "c": + case "objc": + var knownValues = ["c11", "c99", "c90", "c89"]; + return Cpp.languageVersion(input.cpp.cLanguageVersion, knownValues, "C"); + case "cpp": + case "objcpp": + knownValues = ["c++20", "c++2a", "c++17", "c++1z", + "c++14", "c++1y", "c++11", "c++0x", + "c++03", "c++98"]; + return Cpp.languageVersion(input.cpp.cxxLanguageVersion, knownValues, "C++"); + } + } + + var langVersion = currentLanguageVersion(tag); + if (langVersion) { + args.push("-std=" + standardFallbackValueOrDefault(product.qbs.toolchain, + product.cpp.compilerVersion, + langVersion, + product.cpp.useLanguageVersionFallback)); + } + + if (tag === "cpp" || tag === "objcpp") { + var cxxStandardLibrary = product.cpp.cxxStandardLibrary; + if (cxxStandardLibrary && product.qbs.toolchain.contains("clang")) { + args.push("-stdlib=" + cxxStandardLibrary); + } + } + + args.push("-o", output.filePath); + args.push("-c", input.filePath); + + return args; +} + +function additionalCompilerAndLinkerFlags(product) { + var args = [] + + var requireAppExtensionSafeApi = product.cpp.requireAppExtensionSafeApi; + if (requireAppExtensionSafeApi !== undefined && product.qbs.targetOS.contains("darwin")) { + args.push(requireAppExtensionSafeApi ? "-fapplication-extension" : "-fno-application-extension"); + } + + return args +} + +// Returns the GCC language name equivalent to fileTag, accepted by the -x argument +function languageName(fileTag) { + if (fileTag === 'c') + return 'c'; + else if (fileTag === 'cpp') + return 'c++'; + else if (fileTag === 'objc') + return 'objective-c'; + else if (fileTag === 'objcpp') + return 'objective-c++'; + else if (fileTag === 'asm') + return 'assembler'; + else if (fileTag === 'asm_cpp') + return 'assembler-with-cpp'; +} + +function prepareAssembler(project, product, inputs, outputs, input, output) { + var assemblerPath = product.cpp.assemblerPath; + + var includePaths = input.cpp.includePaths; + var systemIncludePaths = input.cpp.systemIncludePaths; + var distributionIncludePaths = input.cpp.distributionIncludePaths; + + var args = product.cpp.targetAssemblerFlags; + + if (input.cpp.debugInformation) + args.push('-g'); + + var warnings = input.cpp.warningLevel + if (warnings === 'none') + args.push('-W'); + + var tag = "asm"; + args = args.concat(ModUtils.moduleProperty(input, 'platformFlags', tag), + ModUtils.moduleProperty(input, 'flags', tag)); + + var allIncludePaths = []; + if (includePaths) + allIncludePaths = allIncludePaths.uniqueConcat(includePaths); + if (systemIncludePaths) + allIncludePaths = allIncludePaths.uniqueConcat(systemIncludePaths); + if (distributionIncludePaths) + allIncludePaths = allIncludePaths.uniqueConcat(distributionIncludePaths); + args = args.concat(allIncludePaths.map(function(path) { return input.cpp.includeFlag + path })); + + args.push("-o", output.filePath); + args.push(input.filePath); + + var cmd = new Command(assemblerPath, args); + cmd.description = "assembling " + input.fileName; + cmd.highlight = "compiler"; + cmd.jobPool = "assembler"; + return cmd; +} + +function compilerEnvVars(config, compilerInfo) +{ + if (config.qbs.toolchain.contains("qcc")) + return ["QCC_CONF_PATH"]; + + var list = ["CPATH", "TMPDIR"]; + if (compilerInfo.tag === "c") + list.push("C_INCLUDE_PATH"); + else if (compilerInfo.tag === "cpp") + list.push("CPLUS_INCLUDE_PATH"); + else if (compilerInfo.tag === "objc") + list.push("OBJC_INCLUDE_PATH"); + else if (compilerInfo.tag === "objccpp") + list.push("OBJCPLUS_INCLUDE_PATH"); + if (config.qbs.targetOS.contains("macos")) + list.push("MACOSX_DEPLOYMENT_TARGET"); + else if (config.qbs.targetOS.contains("ios")) + list.push("IPHONEOS_DEPLOYMENT_TARGET"); + else if (config.qbs.targetOS.contains("tvos")) + list.push("TVOS_DEPLOYMENT_TARGET"); + else if (config.qbs.targetOS.contains("watchos")) + list.push("WATCHOS_DEPLOYMENT_TARGET"); + if (config.qbs.toolchain.contains("clang")) { + list.push("TEMP", "TMP"); + } else { + list.push("LANG", "LC_CTYPE", "LC_MESSAGES", "LC_ALL", "GCC_COMPARE_DEBUG", + "GCC_EXEC_PREFIX", "COMPILER_PATH", "SOURCE_DATE_EPOCH"); + } + return list; +} + +function linkerEnvVars(config, inputs) +{ + if (config.qbs.toolchain.contains("clang") || config.qbs.toolchain.contains("qcc")) + return []; + var list = ["GNUTARGET", "LDEMULATION", "COLLECT_NO_DEMANGLE"]; + if (useCompilerDriverLinker(config, inputs)) + list.push("LIBRARY_PATH"); + return list; +} + +function setResponseFileThreshold(command, product) +{ + if (product.qbs.targetOS.contains("windows") && product.qbs.hostOS.contains("windows")) + command.responseFileThreshold = 10000; +} + +function prepareCompiler(project, product, inputs, outputs, input, output, explicitlyDependsOn) { + var compilerInfo = effectiveCompilerInfo(product.qbs.toolchain, + input, output); + var compilerPath = compilerInfo.path; + var pchOutput = output.fileTags.contains(compilerInfo.tag + "_pch"); + + var args = compilerFlags(project, product, input, output, explicitlyDependsOn); + var wrapperArgsLength = 0; + var wrapperArgs = product.cpp.compilerWrapper; + var extraEnv; + if (wrapperArgs && wrapperArgs.length > 0) { + + // distcc cannot deal with absolute compiler paths (QBS-1336). + for (var i = 0; i < wrapperArgs.length; ++i) { + if (FileInfo.baseName(wrapperArgs[i]) !== "distcc") + continue; + if (i === wrapperArgs.length - 1) { + if (FileInfo.isAbsolutePath(compilerPath)) { + extraEnv = ["PATH=" + FileInfo.path(compilerPath)]; + compilerPath = FileInfo.fileName(compilerPath); + } + } else if (FileInfo.isAbsolutePath(wrapperArgs[i + 1])) { + extraEnv = ["PATH=" + FileInfo.path(FileInfo.path(wrapperArgs[i + 1]))]; + wrapperArgs[i + 1] = FileInfo.fileName(wrapperArgs[i + 1]); + } + break; + } + + wrapperArgsLength = wrapperArgs.length; + args.unshift(compilerPath); + compilerPath = wrapperArgs.shift(); + args = wrapperArgs.concat(args); + } + + var cmd = new Command(compilerPath, args); + cmd.description = (pchOutput ? 'pre' : '') + 'compiling ' + input.fileName; + if (pchOutput) + cmd.description += ' (' + compilerInfo.tag + ')'; + cmd.highlight = "compiler"; + cmd.jobPool = "compiler"; + cmd.relevantEnvironmentVariables = compilerEnvVars(input, compilerInfo); + if (extraEnv) + cmd.environment = extraEnv; + cmd.responseFileArgumentIndex = wrapperArgsLength; + cmd.responseFileUsagePrefix = '@'; + setResponseFileThreshold(cmd, product); + return cmd; +} + +// Concatenates two arrays of library names and preserves the dependency order that ld needs. +function concatLibs(libs, deplibs) { + var r = []; + var s = {}; + + function addLibs(lst) { + for (var i = lst.length; --i >= 0;) { + var lib = lst[i]; + if (!s[lib]) { + s[lib] = true; + r.unshift(lib); + } + } + } + + addLibs(deplibs); + addLibs(libs); + return r; +} + +function collectStdoutLines(command, args) +{ + var p = new Process(); + try { + p.exec(command, args); + return p.readStdOut().split(/\r?\n/g).filter(function (e) { return e; }); + } finally { + p.close(); + } +} + +function getSymbolInfo(product, inputFile) +{ + var result = { }; + var command = product.cpp.nmPath; + var args = ["-g", "-P"]; + if (product.cpp._nmHasDynamicOption) + args.push("-D"); + try { + result.allGlobalSymbols = collectStdoutLines(command, args.concat(inputFile)); + + // GNU nm has the "--defined" option but POSIX nm does not, so we have to manually + // construct the list of defined symbols by subtracting. + var undefinedGlobalSymbols = collectStdoutLines(command, args.concat(["-u", inputFile])); + result.definedGlobalSymbols = result.allGlobalSymbols.filter(function(line) { + return !undefinedGlobalSymbols.contains(line); }); + result.success = true; + } catch (e) { + console.debug("Failed to collect symbols for shared library: nm command '" + + command + "' failed (" + e.toString() + ")"); + result.success = false; + } + return result; +} + +function createSymbolFile(filePath, allSymbols, definedSymbols) +{ + var file; + try { + file = new TextFile(filePath, TextFile.WriteOnly); + for (var lineNr in allSymbols) + file.writeLine(allSymbols[lineNr]); + file.writeLine("==="); + for (lineNr in definedSymbols) + file.writeLine(definedSymbols[lineNr]); + } finally { + if (file) + file.close(); + } +} + +function readSymbolFile(filePath) +{ + var result = { success: true, allGlobalSymbols: [], definedGlobalSymbols: [] }; + var file; + try { + file = new TextFile(filePath, TextFile.ReadOnly); + var prop = "allGlobalSymbols"; + while (true) { + var line = file.readLine(); + if (!line) + break; + if (line === "===") { + prop = "definedGlobalSymbols"; + continue; + } + result[prop].push(line); + } + } catch (e) { + console.debug("Failed to read symbols from '" + filePath + "'"); + result.success = false; + } finally { + if (file) + file.close(); + } + return result; +} + +function createSymbolCheckingCommands(product, outputs) { + var commands = []; + if (!outputs.dynamiclibrary || !outputs.dynamiclibrary_symbols) + return commands; + + if (outputs.dynamiclibrary.length !== outputs.dynamiclibrary_symbols.length) + throw new Error("The number of outputs tagged dynamiclibrary (" + + outputs.dynamiclibrary.length + ") must be equal to the number of " + + "outputs tagged dynamiclibrary_symbols (" + + outputs.dynamiclibrary_symbols.length + ")"); + + for (var d = 0; d < outputs.dynamiclibrary_symbols.length; ++d) { + // Update the symbols file if the list of relevant symbols has changed. + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.d = d; + cmd.sourceCode = function() { + if (outputs.dynamiclibrary[d].qbs.buildVariant + !== outputs.dynamiclibrary_symbols[d].qbs.buildVariant) + throw new Error("Build variant of output tagged dynamiclibrary (" + + outputs.dynamiclibrary[d].qbs.buildVariant + ") is not equal to " + + "build variant of output tagged dynamiclibrary_symbols (" + + outputs.dynamiclibrary_symbols[d].qbs.buildVariant + ") at index " + + d); + + var libFilePath = outputs.dynamiclibrary[d].filePath; + var symbolFilePath = outputs.dynamiclibrary_symbols[d].filePath; + + var newNmResult = getSymbolInfo(product, libFilePath); + if (!newNmResult.success) + return; + + if (!File.exists(symbolFilePath)) { + console.debug("Symbol file '" + symbolFilePath + "' does not yet exist."); + createSymbolFile(symbolFilePath, newNmResult.allGlobalSymbols, + newNmResult.definedGlobalSymbols); + return; + } + + var oldNmResult = readSymbolFile(symbolFilePath); + var checkMode = product.cpp.exportedSymbolsCheckMode; + var oldSymbols; + var newSymbols; + if (checkMode === "strict") { + oldSymbols = oldNmResult.allGlobalSymbols; + newSymbols = newNmResult.allGlobalSymbols; + } else { + oldSymbols = oldNmResult.definedGlobalSymbols; + newSymbols = newNmResult.definedGlobalSymbols; + } + if (oldSymbols.length !== newSymbols.length) { + console.debug("List of relevant symbols differs for '" + libFilePath + "'."); + createSymbolFile(symbolFilePath, newNmResult.allGlobalSymbols, + newNmResult.definedGlobalSymbols); + return; + } + for (var i = 0; i < oldSymbols.length; ++i) { + var oldLine = oldSymbols[i]; + var newLine = newSymbols[i]; + var oldLineElems = oldLine.split(/\s+/); + var newLineElems = newLine.split(/\s+/); + if (oldLineElems[0] !== newLineElems[0] // Object name. + || oldLineElems[1] !== newLineElems[1]) { // Object type + console.debug("List of relevant symbols differs for '" + libFilePath + "'."); + createSymbolFile(symbolFilePath, newNmResult.allGlobalSymbols, + newNmResult.definedGlobalSymbols); + return; + } + } + } + commands.push(cmd); + } + return commands; +} + +function prepareLinker(project, product, inputs, outputs, input, output) { + var i, primaryOutput, cmd, commands = []; + + if (outputs.application) { + primaryOutput = outputs.application[0]; + } else if (outputs.dynamiclibrary) { + primaryOutput = outputs.dynamiclibrary[0]; + } else if (outputs.loadablemodule) { + primaryOutput = outputs.loadablemodule[0]; + } + + var linkerPath = effectiveLinkerPath(product, inputs) + + var args = linkerFlags(project, product, inputs, outputs, primaryOutput, linkerPath); + var wrapperArgsLength = 0; + var wrapperArgs = product.cpp.linkerWrapper; + if (wrapperArgs && wrapperArgs.length > 0) { + wrapperArgsLength = wrapperArgs.length; + args.unshift(linkerPath); + linkerPath = wrapperArgs.shift(); + args = wrapperArgs.concat(args); + } + + var responseFileArgumentIndex = wrapperArgsLength; + + // qcc doesn't properly handle response files, so we have to do it manually + var useQnxResponseFileHack = product.qbs.toolchain.contains("qcc") + && useCompilerDriverLinker(product, inputs); + if (useQnxResponseFileHack) { + // qcc needs to see at least one object/library file to think it has something to do, + // so start the response file at the second object file (so, 3 after the last -o option) + var idx = args.lastIndexOf("-o"); + if (idx !== -1 && idx + 3 < args.length) + responseFileArgumentIndex += idx + 3; + } + + cmd = new Command(linkerPath, args); + cmd.description = 'linking ' + primaryOutput.fileName; + cmd.highlight = 'linker'; + cmd.jobPool = "linker"; + cmd.relevantEnvironmentVariables = linkerEnvVars(product, inputs); + cmd.responseFileArgumentIndex = responseFileArgumentIndex; + cmd.responseFileUsagePrefix = useQnxResponseFileHack ? "-Wl,@" : "@"; + setResponseFileThreshold(cmd, product); + commands.push(cmd); + + var debugInfo = outputs.debuginfo_app || outputs.debuginfo_dll + || outputs.debuginfo_loadablemodule; + if (debugInfo) { + if (product.qbs.targetOS.contains("darwin")) { + if (!product.aggregate) { + var dsymPath = debugInfo[0].filePath; + if (outputs.debuginfo_bundle && outputs.debuginfo_bundle[0]) + dsymPath = outputs.debuginfo_bundle[0].filePath; + var flags = product.cpp.dsymutilFlags || []; + cmd = new Command(product.cpp.dsymutilPath, flags.concat([ + "-o", dsymPath, primaryOutput.filePath + ])); + cmd.description = "generating dSYM for " + product.name; + commands.push(cmd); + + cmd = new Command(product.cpp.stripPath, + ["-S", primaryOutput.filePath]); + cmd.silent = true; + commands.push(cmd); + } + } else { + var objcopy = product.cpp.objcopyPath; + + cmd = new Command(objcopy, ["--only-keep-debug", primaryOutput.filePath, + debugInfo[0].filePath]); + cmd.silent = true; + commands.push(cmd); + + cmd = new Command(objcopy, ["--strip-debug", primaryOutput.filePath]); + cmd.silent = true; + commands.push(cmd); + + cmd = new Command(objcopy, ["--add-gnu-debuglink=" + debugInfo[0].filePath, + primaryOutput.filePath]); + cmd.silent = true; + commands.push(cmd); + } + } + + if (outputs.dynamiclibrary) { + Array.prototype.push.apply(commands, createSymbolCheckingCommands(product, outputs)); + + // Create symlinks from {libfoo, libfoo.1, libfoo.1.0} to libfoo.1.0.0 + var links = outputs["dynamiclibrary_symlink"]; + var symlinkCount = links ? links.length : 0; + for (i = 0; i < symlinkCount; ++i) { + cmd = new Command("ln", ["-sf", primaryOutput.fileName, + links[i].filePath]); + cmd.highlight = "filegen"; + cmd.description = "creating symbolic link '" + + links[i].fileName + "'"; + cmd.workingDirectory = FileInfo.path(primaryOutput.filePath); + commands.push(cmd); + } + } + + if (product.xcode && product.bundle) { + var actualSigningIdentity = product.xcode.actualSigningIdentity; + var codesignDisplayName = product.xcode.actualSigningIdentityDisplayName; + if (actualSigningIdentity && !product.bundle.isBundle) { + args = product.xcode.codesignFlags || []; + args.push("--force"); + args.push("--sign", actualSigningIdentity); + args = args.concat(DarwinTools._codeSignTimestampFlags(product)); + + for (var j in inputs.xcent) { + args.push("--entitlements", inputs.xcent[j].filePath); + break; // there should only be one + } + args.push(primaryOutput.filePath); + cmd = new Command(product.xcode.codesignPath, args); + cmd.description = "codesign " + + primaryOutput.fileName + + " using " + codesignDisplayName + + " (" + actualSigningIdentity + ")"; + commands.push(cmd); + } + } + + return commands; +} + +function debugInfoArtifacts(product, variants, debugInfoTagSuffix) { + var fileTag; + switch (debugInfoTagSuffix) { + case "app": + fileTag = "application"; + break; + case "dll": + fileTag = "dynamiclibrary"; + break; + default: + fileTag = debugInfoTagSuffix; + break; + } + + variants = variants || [{}]; + + var artifacts = []; + if (product.cpp.separateDebugInformation) { + variants.map(function (variant) { + artifacts.push({ + filePath: FileInfo.joinPaths(product.destinationDirectory, + PathTools.debugInfoFilePath(product, + variant.suffix, + fileTag)), + fileTags: ["debuginfo_" + debugInfoTagSuffix] + }); + }); + if (PathTools.debugInfoIsBundle(product)) { + artifacts.push({ + filePath: FileInfo.joinPaths(product.destinationDirectory, + PathTools.debugInfoBundlePath(product, fileTag)), + fileTags: ["debuginfo_bundle"] + }); + artifacts.push({ + filePath: FileInfo.joinPaths(product.destinationDirectory, + PathTools.debugInfoPlistFilePath(product, fileTag)), + fileTags: ["debuginfo_plist"] + }); + } + } + return artifacts; +} + +function dumpMacros(env, compilerFilePath, args, nullDevice, tag) { + var p = new Process(); + try { + p.setEnv("LC_ALL", "C"); + for (var key in env) + 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", languageName(tag || "c") , nullDevice]), + true); + var map = {}; + p.readStdOut().trim().split(/\r?\n/g).map(function (line) { + var parts = line.split(" ", 3); + map[parts[1]] = parts[2]; + }); + return map; + } finally { + p.close(); + } +} + +function dumpDefaultPaths(env, compilerFilePath, args, nullDevice, pathListSeparator, sysroot, + targetOS) { + var p = new Process(); + try { + p.setEnv("LC_ALL", "C"); + for (var key in env) + p.setEnv(key, env[key]); + args = args || []; + p.exec(compilerFilePath, args.concat(["-v", "-E", "-x", "c++", nullDevice]), true); + var suffix = " (framework directory)"; + var includePaths = []; + var libraryPaths = []; + var frameworkPaths = []; + var addIncludes = false; + var lines = p.readStdErr().trim().split(/\r?\n/g).map(function (line) { return line.trim(); }); + for (var i = 0; i < lines.length; ++i) { + var line = lines[i]; + var prefix = "LIBRARY_PATH="; + if (line.startsWith(prefix)) { + libraryPaths = libraryPaths.concat(line.substr(prefix.length) + .split(pathListSeparator)); + } else if (line === "#include <...> search starts here:") { + addIncludes = true; + } else if (line === "End of search list.") { + addIncludes = false; + } else if (addIncludes) { + if (line.endsWith(suffix)) + frameworkPaths.push(line.substr(0, line.length - suffix.length)); + else + includePaths.push(line); + } + } + + sysroot = sysroot || ""; + + if (includePaths.length === 0) + includePaths.push(sysroot + "/usr/include", sysroot + "/usr/local/include"); + + if (libraryPaths.length === 0) + libraryPaths.push(sysroot + "/lib", sysroot + "/usr/lib"); + + if (frameworkPaths.length === 0 && targetOS.contains("darwin")) + frameworkPaths.push(sysroot + "/System/Library/Frameworks"); + + return { + "includePaths": includePaths, + "libraryPaths": libraryPaths, + "frameworkPaths": frameworkPaths + }; + } finally { + p.close(); + } +} + +function targetFlags(tool, hasTargetOption, target, targetArch, machineType, targetOS) { + var args = []; + if (hasTargetOption) { + if (target) + args.push("-target", target); + } else { + var archArgs = { + "compiler": { + "i386": ["-m32"], + "x86_64": ["-m64"], + }, + "linker": { + "i386": ["-m", targetOS.contains("windows") ? "i386pe" : "elf_i386"], + "x86_64": ["-m", targetOS.contains("windows") ? "i386pep" : "elf_x86_64"], + }, + "assembler": { + "i386": ["--32"], + "x86_64": ["--64"], + }, + }; + + var flags = archArgs[tool] ? archArgs[tool][targetArch] : undefined; + if (flags) + args = args.concat(flags); + + if (machineType && tool !== "linker") + args.push("-march=" + machineType); + } + return args; +} + +function toolNames(rawToolNames, toolchainPrefix) +{ + return toolchainPrefix + ? rawToolNames.map(function(rawName) { return toolchainPrefix + rawName; }) + : rawToolNames; +} + +function pathPrefix(baseDir, prefix) +{ + var path = ''; + if (baseDir) { + path += baseDir; + if (path.substr(-1) !== '/') + path += '/'; + } + if (prefix) + path += prefix; + return path; +} diff --git a/share/qbs/modules/cpp/iar.js b/share/qbs/modules/cpp/iar.js new file mode 100644 index 00000000..0c912d0a --- /dev/null +++ b/share/qbs/modules/cpp/iar.js @@ -0,0 +1,851 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 Cpp = require("cpp.js"); +var Environment = require("qbs.Environment"); +var File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var ModUtils = require("qbs.ModUtils"); +var Process = require("qbs.Process"); +var TemporaryDir = require("qbs.TemporaryDir"); +var TextFile = require("qbs.TextFile"); + +function supportXLinker(architecture) { + return architecture === "78k" || architecture === "avr" + || architecture === "avr32" || architecture === "mcs51" + || architecture === "msp430" || architecture === "v850" + || architecture === "m68k" || architecture === "m32c" + || architecture === "r32c" || architecture === "m16c" + || architecture === "cr16"; +} + +function supportILinker(architecture) { + return architecture.startsWith("arm") + || architecture === "rh850" || architecture === "rl78" + || architecture === "rx" || architecture === "stm8" + || architecture === "sh" || architecture === "riscv"; +} + +function supportXArchiver(architecture) { + return architecture === "mcs51" || architecture === "avr" + || architecture === "msp430" || architecture === "v850" + || architecture === "78k" || architecture === "avr32" + || architecture === "m68k" || architecture === "m32c" + || architecture === "r32c" || architecture === "m16c" + || architecture === "cr16"; +} + +function supportIArchiver(architecture) { + return architecture.startsWith("arm") + || architecture === "stm8" || architecture === "rl78" + || architecture === "rx" || architecture === "rh850" + || architecture === "sh" || architecture === "riscv"; +} + +// It is a 'magic' IAR-specific target architecture code. +function architectureCode(architecture) { + switch (architecture) { + case "78k": + return "26"; + case "avr": + return "90"; + case "avr32": + return "82"; + case "mcs51": + return "51"; + case "msp430": + return "43"; + case "v850": + return "85"; + case "m68k": + return "68"; + case "m32c": + return "48"; + case "r32c": + return "53"; + case "m16c": + return "34"; + case "cr16": + return "45"; + case "rh850": case "rl78": case "rx": case "stm8": case "sh": case "riscv": + return ""; + default: + if (architecture.startsWith("arm")) + return ""; + break; + } +} + +function compilerName(qbs) { + var architecture = qbs.architecture; + if (architecture.startsWith("arm")) + return "iccarm"; + else if (architecture === "mcs51") + return "icc8051"; + else if (architecture === "avr") + return "iccavr"; + else if (architecture === "stm8") + return "iccstm8"; + else if (architecture === "msp430") + return "icc430"; + else if (architecture === "v850") + return "iccv850"; + else if (architecture === "78k") + return "icc78k"; + else if (architecture === "rl78") + return "iccrl78"; + else if (architecture === "rx") + return "iccrx"; + else if (architecture === "rh850") + return "iccrh850"; + else if (architecture === "avr32") + return "iccavr32"; + else if (architecture === "sh") + return "iccsh"; + else if (architecture === "riscv") + return "iccriscv"; + else if (architecture === "m68k") + return "icccf"; + else if (architecture === "m32c") + return "iccm32c"; + else if (architecture === "r32c") + return "iccr32c"; + else if (architecture === "m16c") + return "iccm16c"; + else if (architecture === "cr16") + return "icccr16c"; + throw "Unable to deduce compiler name for unsupported architecture: '" + + architecture + "'"; +} + +function assemblerName(qbs) { + var architecture = qbs.architecture; + if (architecture.startsWith("arm")) + return "iasmarm"; + else if (architecture === "rl78") + return "iasmrl78"; + else if (architecture === "rx") + return "iasmrx"; + else if (architecture === "rh850") + return "iasmrh850"; + else if (architecture === "mcs51") + return "a8051"; + else if (architecture === "avr") + return "aavr"; + else if (architecture === "stm8") + return "iasmstm8"; + else if (architecture === "msp430") + return "a430"; + else if (architecture === "v850") + return "av850"; + else if (architecture === "78k") + return "a78k"; + else if (architecture === "avr32") + return "aavr32"; + else if (architecture === "sh") + return "iasmsh"; + else if (architecture === "riscv") + return "iasmriscv"; + else if (architecture === "m68k") + return "acf"; + else if (architecture === "m32c") + return "am32c"; + else if (architecture === "r32c") + return "ar32c"; + else if (architecture === "m16c") + return "am16c"; + else if (architecture === "cr16") + return "acr16c"; + throw "Unable to deduce assembler name for unsupported architecture: '" + + architecture + "'"; +} + +function linkerName(qbs) { + var architecture = qbs.architecture; + if (supportXLinker(architecture)) + return "xlink"; + else if (supportILinker(architecture)) + return architecture.startsWith("arm") ? "ilinkarm" : ("ilink" + architecture); + throw "Unable to deduce linker name for unsupported architecture: '" + + architecture + "'"; +} + +function archiverName(qbs) { + var architecture = qbs.architecture; + if (supportXArchiver(architecture)) + return "xar"; + else if (supportIArchiver(architecture)) + return "iarchive"; + throw "Unable to deduce archiver name for unsupported architecture: '" + + architecture + "'"; +} + +function staticLibrarySuffix(qbs) { + var architecture = qbs.architecture; + var code = architectureCode(architecture); + if (code === undefined) { + throw "Unable to deduce static library suffix for unsupported architecture: '" + + architecture + "'"; + } + return (code !== "") ? (".r" + code) : ".a"; +} + +function executableSuffix(qbs) { + var architecture = qbs.architecture; + var code = architectureCode(architecture); + if (code === undefined) { + throw "Unable to deduce executable suffix for unsupported architecture: '" + + architecture + "'"; + } + return (code !== "") ? ((qbs.debugInformation) ? (".d" + code) : (".a" + code)) : ".out"; +} + +function objectSuffix(qbs) { + var architecture = qbs.architecture; + var code = architectureCode(architecture); + if (code === undefined) { + throw "Unable to deduce object file suffix for unsupported architecture: '" + + architecture + "'"; + } + return (code !== "") ? (".r" + code) : ".o"; +} + +function imageFormat(qbs) { + var architecture = qbs.architecture; + var code = architectureCode(architecture); + if (code === undefined) { + throw "Unable to deduce image format for unsupported architecture: '" + + architecture + "'"; + } + return (code !== "") ? "ubrof" : "elf"; +} + +function guessArmArchitecture(core) { + var arch = "arm"; + if (core === "__ARM4M__") + arch += "v4m"; + else if (core === "__ARM4TM__") + arch += "v4tm"; + else if (core === "__ARM5__") + arch += "v5"; + else if (core === "__ARM5E__") + arch += "v5e"; + else if (core === "__ARM6__") + arch += "v6"; + else if (core === "__ARM6M__") + arch += "v6m"; + else if (core === "__ARM6SM__") + arch += "v6sm"; + else if (core === "__ARM7M__") + arch += "v7m"; + else if (core === "__ARM7R__") + arch += "v7r"; + return arch; +} + +function guessArchitecture(macros) { + if (macros["__ICCARM__"] === "1") + return guessArmArchitecture(macros["__CORE__"]); + else if (macros["__ICC8051__"] === "1") + return "mcs51"; + else if (macros["__ICCAVR__"] === "1") + return "avr"; + else if (macros["__ICCSTM8__"] === "1") + return "stm8"; + else if (macros["__ICC430__"] === "1") + return "msp430"; + else if (macros["__ICCRL78__"] === "1") + return "rl78"; + else if (macros["__ICCRX__"] === "1") + return "rx"; + else if (macros["__ICCRH850__"] === "1") + return "rh850"; + else if (macros["__ICCV850__"] === "1") + return "v850"; + else if (macros["__ICC78K__"] === "1") + return "78k"; + else if (macros["__ICCAVR32__"] === "1") + return "avr32"; + else if (macros["__ICCSH__"] === "1") + return "sh"; + else if (macros["__ICCRISCV__"] === "1") + return "riscv"; + else if (macros["__ICCCF__"] === "1") + return "m68k"; + else if (macros["__ICCM32C__"] === "1") + return "m32c"; + else if (macros["__ICCR32C__"] === "1") + return "r32c"; + else if (macros["__ICCM16C__"] === "1") + return "m16c"; + else if (macros["__ICCCR16C__"] === "1") + return "cr16"; +} + +function guessEndianness(macros) { + if (macros["__LITTLE_ENDIAN__"] === "1") + return "little"; + return "big" +} + +function guessVersion(macros, architecture) +{ + var version = parseInt(macros["__VER__"], 10); + if (architecture.startsWith("arm")) { + return { major: parseInt(version / 1000000), + minor: parseInt(version / 1000) % 1000, + patch: parseInt(version) % 1000, + found: true } + } else if (architecture === "mcs51" || architecture === "avr" || architecture === "stm8" + || architecture === "msp430" || architecture === "rl78" || architecture === "rx" + || architecture === "rh850" || architecture === "v850" || architecture === "78k" + || architecture === "avr32" || architecture === "sh" || architecture === "riscv" + || architecture === "m68k" || architecture === "m32c" || architecture === "r32c" + || architecture === "m16c" || architecture === "cr16") { + return { major: parseInt(version / 100), + minor: parseInt(version % 100), + patch: 0, + found: true } + } +} + +function cppLanguageOption(compilerFilePath) { + var baseName = FileInfo.baseName(compilerFilePath); + switch (baseName) { + case "iccarm": + case "iccrl78": + case "iccrx": + case "iccrh850": + case "iccriscv": + return "--c++"; + case "icc8051": + case "iccavr": + case "iccstm8": + case "icc430": + case "iccv850": + case "icc78k": + case "iccavr32": + case "iccsh": + case "icccf": + case "iccm32c": + case "iccr32c": + case "iccm16c": + case "icccr16c": + return "--ec++"; + } + throw "Unable to deduce C++ language option for unsupported compiler: '" + + FileInfo.toNativeSeparators(compilerFilePath) + "'"; +} + +function dumpMacros(compilerFilePath, tag) { + var tempDir = new TemporaryDir(); + var inFilePath = FileInfo.fromNativeSeparators(tempDir.path() + "/empty-source.c"); + var inFile = new TextFile(inFilePath, TextFile.WriteOnly); + var outFilePath = FileInfo.fromNativeSeparators(tempDir.path() + "/iar-macros.predef"); + + var args = [ inFilePath, "--predef_macros", outFilePath ]; + if (tag && tag === "cpp") + args.push(cppLanguageOption(compilerFilePath)); + + var p = new Process(); + p.exec(compilerFilePath, args, true); + var outFile = new TextFile(outFilePath, TextFile.ReadOnly); + var map = {}; + outFile.readAll().trim().split(/\r?\n/g).map(function (line) { + var parts = line.split(" ", 3); + map[parts[1]] = parts[2]; + }); + return map; +} + +function dumpDefaultPaths(compilerFilePath, tag) { + var tempDir = new TemporaryDir(); + var inFilePath = FileInfo.fromNativeSeparators(tempDir.path() + "/empty-source.c"); + var inFile = new TextFile(inFilePath, TextFile.WriteOnly); + + var args = [ inFilePath, "--preinclude", "." ]; + if (tag === "cpp") + args.push(cppLanguageOption(compilerFilePath)); + + var p = new Process(); + // This process should return an error, don't throw + // an error in this case. + p.exec(compilerFilePath, args, false); + var output = p.readStdErr(); + + var includePaths = []; + var pass = 0; + for (var pos = 0; pos < output.length; ++pos) { + var searchIndex = output.indexOf("searched:", pos); + if (searchIndex === -1) + break; + var startQuoteIndex = output.indexOf('"', searchIndex + 1); + if (startQuoteIndex === -1) + break; + var endQuoteIndex = output.indexOf('"', startQuoteIndex + 1); + if (endQuoteIndex === -1) + break; + pos = endQuoteIndex + 1; + + // Ignore the first path as it is not a compiler include path. + ++pass; + if (pass === 1) + continue; + + var parts = output.substring(startQuoteIndex + 1, endQuoteIndex).split("\n"); + var includePath = ""; + for (var i in parts) + includePath += parts[i].trim(); + + includePaths.push(includePath); + } + + return { + "includePaths": includePaths + }; +} + +function collectLibraryDependencies(product) { + var seen = {}; + var result = []; + + function addFilePath(filePath) { + result.push({ filePath: filePath }); + } + + function addArtifactFilePaths(dep, artifacts) { + if (!artifacts) + return; + var artifactFilePaths = artifacts.map(function(a) { return a.filePath; }); + artifactFilePaths.forEach(addFilePath); + } + + function addExternalStaticLibs(obj) { + if (!obj.cpp) + return; + function ensureArray(a) { + return Array.isArray(a) ? a : []; + } + function sanitizedModuleListProperty(obj, moduleName, propertyName) { + return ensureArray(ModUtils.sanitizedModuleProperty(obj, moduleName, propertyName)); + } + var externalLibs = [].concat( + sanitizedModuleListProperty(obj, "cpp", "staticLibraries")); + var staticLibrarySuffix = obj.moduleProperty("cpp", "staticLibrarySuffix"); + externalLibs.forEach(function(staticLibraryName) { + if (!staticLibraryName.endsWith(staticLibrarySuffix)) + staticLibraryName += staticLibrarySuffix; + addFilePath(staticLibraryName); + }); + } + + function traverse(dep) { + if (seen.hasOwnProperty(dep.name)) + return; + seen[dep.name] = true; + + if (dep.parameters.cpp && dep.parameters.cpp.link === false) + return; + + var staticLibraryArtifacts = dep.artifacts["staticlibrary"]; + if (staticLibraryArtifacts) { + dep.dependencies.forEach(traverse); + addArtifactFilePaths(dep, staticLibraryArtifacts); + addExternalStaticLibs(dep); + } + } + + product.dependencies.forEach(traverse); + addExternalStaticLibs(product); + return result; +} + +function compilerOutputArtifacts(input, useListing) { + var artifacts = []; + artifacts.push({ + fileTags: ["obj"], + filePath: Utilities.getHash(input.baseDir) + "/" + + input.fileName + input.cpp.objectSuffix + }); + if (useListing) { + artifacts.push({ + fileTags: ["lst"], + filePath: Utilities.getHash(input.baseDir) + "/" + + input.fileName + ".lst" + }); + } + return artifacts; +} + +function applicationLinkerOutputArtifacts(product) { + var app = { + fileTags: ["application"], + filePath: FileInfo.joinPaths( + product.destinationDirectory, + PathTools.applicationFilePath(product)) + }; + var mem_map = { + fileTags: ["mem_map"], + filePath: FileInfo.joinPaths( + product.destinationDirectory, + product.targetName + ".map") + }; + return [app, mem_map] +} + +function staticLibraryLinkerOutputArtifacts(product) { + var staticLib = { + fileTags: ["staticlibrary"], + filePath: FileInfo.joinPaths( + product.destinationDirectory, + PathTools.staticLibraryFilePath(product)) + }; + return [staticLib] +} + +function compilerFlags(project, product, input, outputs, explicitlyDependsOn) { + // Determine which C-language we're compiling. + var tag = ModUtils.fileTagForTargetLanguage(input.fileTags.concat(outputs.obj[0].fileTags)); + + var args = []; + + // Input. + args.push(input.filePath); + + // Output. + args.push("-o", outputs.obj[0].filePath); + + var prefixHeaders = input.cpp.prefixHeaders; + for (var i in prefixHeaders) + args.push("--preinclude", prefixHeaders[i]); + + // Defines. + var allDefines = []; + var platformDefines = input.cpp.platformDefines; + if (platformDefines) + allDefines = allDefines.uniqueConcat(platformDefines); + var defines = input.cpp.defines; + if (defines) + allDefines = allDefines.uniqueConcat(defines); + args = args.concat(allDefines.map(function(define) { return "-D" + define })); + + // Includes. + var allIncludePaths = []; + var includePaths = input.cpp.includePaths; + if (includePaths) + allIncludePaths = allIncludePaths.uniqueConcat(includePaths); + var systemIncludePaths = input.cpp.systemIncludePaths; + if (systemIncludePaths) + allIncludePaths = allIncludePaths.uniqueConcat(systemIncludePaths); + var distributionIncludePaths = input.cpp.distributionIncludePaths; + if (distributionIncludePaths) + allIncludePaths = allIncludePaths.uniqueConcat(distributionIncludePaths); + args = args.concat(allIncludePaths.map(function(include) { return "-I" + include })); + + // Silent output generation flag. + args.push("--silent"); + + // Debug information flags. + if (input.cpp.debugInformation) + args.push("--debug"); + + // Optimization flags. + switch (input.cpp.optimization) { + case "small": + args.push("-Ohz"); + break; + case "fast": + args.push("-Ohs"); + break; + case "none": + args.push("-On"); + break; + } + + var architecture = input.qbs.architecture; + + // Warning level flags. + switch (input.cpp.warningLevel) { + case "none": + args.push("--no_warnings"); + break; + case "all": + if (architecture !== "78k") { + if (architecture !== "avr32" && architecture !== "r32c" + && architecture !== "sh" && architecture !== "m16c") { + args.push("--deprecated_feature_warnings=" + +"+attribute_syntax," + +"+preprocessor_extensions," + +"+segment_pragmas"); + } + if (tag === "cpp") + args.push("--warn_about_c_style_casts"); + } + break; + } + if (input.cpp.treatWarningsAsErrors) + args.push("--warnings_are_errors"); + + // C language version flags. + if (tag === "c" && (architecture !== "78k")) { + var knownValues = ["c89"]; + var cLanguageVersion = Cpp.languageVersion( + input.cpp.cLanguageVersion, knownValues, "C"); + switch (cLanguageVersion) { + case "c89": + args.push("--c89"); + break; + default: + // Default C language version is C11/C99 that + // depends on the IAR version. + break; + } + } + + // C++ language version flags. + if (tag === "cpp") { + if (architecture.startsWith("arm") + || architecture === "rl78" || architecture === "rx" + || architecture === "rh850" || architecture === "riscv") { + // Enable C++ language flags. + args.push("--c++"); + // Exceptions flags. + if (!input.cpp.enableExceptions) + args.push("--no_exceptions"); + // RTTI flags. + if (!input.cpp.enableRtti) + args.push("--no_rtti"); + } else if (architecture === "stm8" || architecture === "mcs51" + || architecture === "avr" || architecture === "msp430" + || architecture === "v850" || architecture === "78k" + || architecture === "avr32" || architecture === "sh" + || architecture === "m68k" || architecture === "m32c" + || architecture === "r32c" || architecture === "m16c" + || architecture === "cr16") { + args.push("--ec++"); + } + } + + // Byte order flags. + if (architecture.startsWith("arm") || architecture === "rx") { + var endianness = input.cpp.endianness; + if (endianness) + args.push("--endian=" + endianness); + } + + // Listing files generation flag. + if (input.cpp.generateCompilerListingFiles) + args.push("-l", outputs.lst[0].filePath); + + // Misc flags. + args = args.concat(ModUtils.moduleProperty(input, "platformFlags"), + ModUtils.moduleProperty(input, "flags"), + ModUtils.moduleProperty(input, "platformFlags", tag), + ModUtils.moduleProperty(input, "flags", tag), + ModUtils.moduleProperty(input, "driverFlags", tag)); + return args; +} + +function assemblerFlags(project, product, input, outputs, explicitlyDependsOn) { + // Determine which C-language we"re compiling + var tag = ModUtils.fileTagForTargetLanguage(input.fileTags.concat(outputs.obj[0].fileTags)); + + var args = []; + + // Input. + args.push(input.filePath); + + // Output. + args.push("-o", outputs.obj[0].filePath); + + // Includes. + var allIncludePaths = []; + var systemIncludePaths = input.cpp.systemIncludePaths; + if (systemIncludePaths) + allIncludePaths = allIncludePaths.uniqueConcat(systemIncludePaths); + var distributionIncludePaths = input.cpp.distributionIncludePaths; + if (distributionIncludePaths) + allIncludePaths = allIncludePaths.uniqueConcat(distributionIncludePaths); + args = args.concat(allIncludePaths.map(function(include) { return "-I" + include })); + + // Debug information flags. + if (input.cpp.debugInformation) + args.push("-r"); + + // Architecture specific flags. + var architecture = input.qbs.architecture; + if (architecture === "stm8" || architecture === "rl78" + || architecture === "rx" || architecture === "rh850" + || architecture === "avr32" || architecture === "sh" + || architecture === "riscv" || architecture === "m68k" + || architecture === "r32c" || architecture === "cr16") { + // Silent output generation flag. + args.push("--silent"); + // Warning level flags. + if (input.cpp.warningLevel === "none") + args.push("--no_warnings"); + if (input.cpp.treatWarningsAsErrors) + args.push("--warnings_are_errors"); + } else { + // Silent output generation flag. + args.push("-S"); + // Warning level flags. + args.push("-w" + (input.cpp.warningLevel === "none" ? "-" : "+")); + } + + // Listing files generation flag. + if (input.cpp.generateAssemblerListingFiles) + args.push("-l", outputs.lst[0].filePath); + + // Misc flags. + args = args.concat(ModUtils.moduleProperty(input, "platformFlags", tag), + ModUtils.moduleProperty(input, "flags", tag)); + return args; +} + +function linkerFlags(project, product, inputs, outputs) { + var args = []; + + // Inputs. + if (inputs.obj) + args = args.concat(inputs.obj.map(function(obj) { return obj.filePath })); + + // Output. + args.push("-o", outputs.application[0].filePath); + + // Library paths. + var allLibraryPaths = []; + var libraryPaths = product.cpp.libraryPaths; + if (libraryPaths) + allLibraryPaths = allLibraryPaths.uniqueConcat(libraryPaths); + var distributionLibraryPaths = product.cpp.distributionLibraryPaths; + if (distributionLibraryPaths) + allLibraryPaths = allLibraryPaths.uniqueConcat(distributionLibraryPaths); + + // Library dependencies. + var libraryDependencies = collectLibraryDependencies(product); + if (libraryDependencies) + args = args.concat(libraryDependencies.map(function(dep) { return dep.filePath })); + + // Linker scripts. + var linkerScripts = inputs.linkerscript + ? inputs.linkerscript.map(function(a) { return a.filePath; }) : []; + + // Architecture specific flags. + var architecture = product.qbs.architecture; + if (supportILinker(architecture)) { + args = args.concat(allLibraryPaths.map(function(path) { return '-L' + path })); + // Silent output generation flag. + args.push("--silent"); + // Map file generation flag. + if (product.cpp.generateLinkerMapFile) + args.push("--map", outputs.mem_map[0].filePath); + // Entry point flag. + if (product.cpp.entryPoint) + args.push("--entry", product.cpp.entryPoint); + // Linker scripts flags. + linkerScripts.forEach(function(script) { args.push("--config", script); }); + } else if (supportXLinker(architecture)) { + args = args.concat(allLibraryPaths.map(function(path) { return '-I' + path })); + // Silent output generation flag. + args.push("-S"); + // Debug information flag. + if (product.cpp.debugInformation) + args.push("-rt"); + // Map file generation flag. + if (product.cpp.generateLinkerMapFile) + args.push("-l", outputs.mem_map[0].filePath); + // Entry point flag. + if (product.cpp.entryPoint) + args.push("-s", product.cpp.entryPoint); + // Linker scripts flags. + linkerScripts.forEach(function(script) { args.push("-f", script); }); + } + + // Misc flags. + args = args.concat(ModUtils.moduleProperty(product, "driverLinkerFlags")); + return args; +} + +function archiverFlags(project, product, inputs, outputs) { + var args = []; + + // Inputs. + if (inputs.obj) + args = args.concat(inputs.obj.map(function(obj) { return obj.filePath })); + + // Output. + var architecture = product.qbs.architecture; + if (supportIArchiver(architecture)) + args.push("--create"); + args.push("-o", outputs.staticlibrary[0].filePath); + + return args; +} + +function prepareCompiler(project, product, inputs, outputs, input, output, explicitlyDependsOn) { + var args = compilerFlags(project, product, input, outputs, explicitlyDependsOn); + var compilerPath = input.cpp.compilerPath; + var cmd = new Command(compilerPath, args); + cmd.description = "compiling " + input.fileName; + cmd.highlight = "compiler"; + return [cmd]; +} + +function prepareAssembler(project, product, inputs, outputs, input, output, explicitlyDependsOn) { + var args = assemblerFlags(project, product, input, outputs, explicitlyDependsOn); + var assemblerPath = input.cpp.assemblerPath; + var cmd = new Command(assemblerPath, args); + cmd.description = "assembling " + input.fileName; + cmd.highlight = "compiler"; + return [cmd]; +} + +function prepareLinker(project, product, inputs, outputs, input, output) { + var primaryOutput = outputs.application[0]; + var args = linkerFlags(project, product, inputs, outputs); + var linkerPath = product.cpp.linkerPath; + var cmd = new Command(linkerPath, args); + cmd.description = "linking " + primaryOutput.fileName; + cmd.highlight = "linker"; + return [cmd]; +} + +function prepareArchiver(project, product, inputs, outputs, input, output) { + var args = archiverFlags(project, product, inputs, outputs); + var archiverPath = product.cpp.archiverPath; + var cmd = new Command(archiverPath, args); + cmd.description = "linking " + output.fileName; + cmd.highlight = "linker"; + cmd.stdoutFilterFunction = function(output) { + return ""; + }; + return [cmd]; +} diff --git a/share/qbs/modules/cpp/iar.qbs b/share/qbs/modules/cpp/iar.qbs new file mode 100644 index 00000000..519d30f2 --- /dev/null +++ b/share/qbs/modules/cpp/iar.qbs @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 1.0 +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.PathTools +import qbs.Probes +import qbs.Utilities +import "iar.js" as IAR + +CppModule { + condition: qbs.toolchain && qbs.toolchain.contains("iar") + + Probes.BinaryProbe { + id: compilerPathProbe + condition: !toolchainInstallPath && !_skipAllChecks + names: ["iccarm"] + } + + Probes.IarProbe { + id: iarProbe + condition: !_skipAllChecks + compilerFilePath: compilerPath + enableDefinesByLanguage: enableCompilerDefinesByLanguage + } + + qbs.architecture: iarProbe.found ? iarProbe.architecture : original + qbs.targetPlatform: "none" + + compilerVersionMajor: iarProbe.versionMajor + compilerVersionMinor: iarProbe.versionMinor + compilerVersionPatch: iarProbe.versionPatch + endianness: iarProbe.endianness + + compilerDefinesByLanguage: iarProbe.compilerDefinesByLanguage + compilerIncludePaths: iarProbe.includePaths + + property string toolchainInstallPath: compilerPathProbe.found + ? compilerPathProbe.path : undefined + + property string compilerExtension: qbs.hostOS.contains("windows") ? ".exe" : "" + + /* Work-around for QtCreator which expects these properties to exist. */ + property string cCompilerName: compilerName + property string cxxCompilerName: compilerName + + compilerName: IAR.compilerName(qbs) + compilerExtension + compilerPath: FileInfo.joinPaths(toolchainInstallPath, compilerName) + + assemblerName: IAR.assemblerName(qbs) + compilerExtension + assemblerPath: FileInfo.joinPaths(toolchainInstallPath, assemblerName) + + linkerName: IAR.linkerName(qbs) + compilerExtension + linkerPath: FileInfo.joinPaths(toolchainInstallPath, linkerName) + + property string archiverName: IAR.archiverName(qbs) + compilerExtension + property string archiverPath: FileInfo.joinPaths(toolchainInstallPath, archiverName) + + runtimeLibrary: "static" + + staticLibrarySuffix: IAR.staticLibrarySuffix(qbs) + executableSuffix: IAR.executableSuffix(qbs) + + property string objectSuffix: IAR.objectSuffix(qbs) + + imageFormat: IAR.imageFormat(qbs) + + enableExceptions: false + enableRtti: false + + Rule { + id: assembler + inputs: ["asm"] + outputFileTags: ["obj", "lst"] + outputArtifacts: IAR.compilerOutputArtifacts( + input, input.cpp.generateAssemblerListingFiles) + prepare: IAR.prepareAssembler.apply(IAR, arguments) + } + + FileTagger { + patterns: ["*.s", "*.s43", "*.s51", "*.s90", "*.msa", "*.asm"] + fileTags: ["asm"] + } + + Rule { + id: compiler + inputs: ["cpp", "c"] + auxiliaryInputs: ["hpp"] + outputFileTags: ["obj", "lst"] + outputArtifacts: IAR.compilerOutputArtifacts( + input, input.cpp.generateCompilerListingFiles) + prepare: IAR.prepareCompiler.apply(IAR, arguments) + } + + Rule { + id: applicationLinker + multiplex: true + inputs: ["obj", "linkerscript"] + inputsFromDependencies: ["staticlibrary"] + outputFileTags: ["application", "mem_map"] + outputArtifacts: IAR.applicationLinkerOutputArtifacts(product) + prepare: IAR.prepareLinker.apply(IAR, arguments) + } + + Rule { + id: staticLibraryLinker + multiplex: true + inputs: ["obj"] + inputsFromDependencies: ["staticlibrary"] + outputFileTags: ["staticlibrary"] + outputArtifacts: IAR.staticLibraryLinkerOutputArtifacts(product) + prepare: IAR.prepareArchiver.apply(IAR, arguments) + } +} diff --git a/share/qbs/modules/cpp/ios-gcc.qbs b/share/qbs/modules/cpp/ios-gcc.qbs new file mode 100644 index 00000000..0ff99679 --- /dev/null +++ b/share/qbs/modules/cpp/ios-gcc.qbs @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.DarwinTools +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Utilities + +DarwinGCC { + priority: 1 + condition: qbs.targetOS.contains('ios') && + qbs.toolchain && qbs.toolchain.contains('gcc') + + targetSystem: "ios" + (minimumIosVersion || "") + + minimumDarwinVersion: minimumIosVersion + minimumDarwinVersionCompilerFlag: qbs.targetOS.contains("ios-simulator") + ? "-mios-simulator-version-min" + : "-miphoneos-version-min" + minimumDarwinVersionLinkerFlag: qbs.targetOS.contains("ios-simulator") + ? "-ios_simulator_version_min" + : "-iphoneos_version_min" + + libcxxAvailable: base + && (!minimumDarwinVersion + || Utilities.versionCompare(minimumDarwinVersion, "5") >= 0) + + platformObjcFlags: base.concat(simulatorObjcFlags) + platformObjcxxFlags: base.concat(simulatorObjcFlags) + + // Private properties + readonly property stringList simulatorObjcFlags: { + // default in Xcode and also required for building 32-bit Simulator binaries with ARC + // since the default ABI version is 0 for 32-bit targets + return qbs.targetOS.contains("ios-simulator") + ? ["-fobjc-abi-version=2", "-fobjc-legacy-dispatch"] + : []; + } + + Rule { + condition: !product.qbs.targetOS.contains("ios-simulator") + inputsFromDependencies: ["bundle.content"] + + Artifact { + filePath: FileInfo.joinPaths(product.destinationDirectory, product.targetName + ".ipa") + fileTags: ["ipa"] + } + + prepare: { + var cmd = new Command("PackageApplication", [input.filePath, "-o", output.filePath]); + cmd.description = "creating ipa"; + cmd.highlight = "codegen"; + return cmd; + } + } +} diff --git a/share/qbs/modules/cpp/keil.js b/share/qbs/modules/cpp/keil.js new file mode 100644 index 00000000..fcea3c4b --- /dev/null +++ b/share/qbs/modules/cpp/keil.js @@ -0,0 +1,1215 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 Cpp = require("cpp.js"); +var Environment = require("qbs.Environment"); +var File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var ModUtils = require("qbs.ModUtils"); +var PathTools = require("qbs.PathTools"); +var Process = require("qbs.Process"); +var TemporaryDir = require("qbs.TemporaryDir"); +var TextFile = require("qbs.TextFile"); +var Utilities = require("qbs.Utilities"); + +function isMcs51Architecture(architecture) { + return architecture === "mcs51"; +} + +function isMcs251Architecture(architecture) { + return architecture === "mcs251"; +} + +function isMcsArchitecture(architecture) { + return isMcs51Architecture(architecture) + || isMcs251Architecture(architecture); +} + +function isC166Architecture(architecture) { + return architecture === "c166"; +} + +function isArmArchitecture(architecture) { + return architecture.startsWith("arm"); +} + +function isMcsCompiler(compilerPath) { + var base = FileInfo.baseName(compilerPath).toLowerCase(); + return base === "c51" || base === "cx51" || base === "c251"; +} + +function isC166Compiler(compilerPath) { + return FileInfo.baseName(compilerPath).toLowerCase() === "c166"; +} + +function isArmCCCompiler(compilerPath) { + return FileInfo.baseName(compilerPath).toLowerCase() === "armcc"; +} + +function isArmClangCompiler(compilerPath) { + return FileInfo.baseName(compilerPath).toLowerCase() === "armclang"; +} + +function compilerName(qbs) { + var architecture = qbs.architecture; + if (architecture === "mcs51") + return "c51"; + if (architecture === "mcs251") + return "c251"; + if (isC166Architecture(architecture)) + return "c166"; + if (isArmArchitecture(architecture)) + return "armcc"; + throw "Unable to deduce compiler name for unsupported architecture: '" + + architecture + "'"; +} + +function assemblerName(qbs) { + var architecture = qbs.architecture; + if (architecture === "mcs51") + return "a51"; + if (architecture === "mcs251") + return "a251"; + if (isC166Architecture(architecture)) + return "a166"; + if (isArmArchitecture(architecture)) + return "armasm"; + throw "Unable to deduce assembler name for unsupported architecture: '" + + architecture + "'"; +} + +function linkerName(qbs) { + var architecture = qbs.architecture; + if (architecture === "mcs51") + return "bl51"; + if (architecture === "mcs251") + return "l251"; + if (isC166Architecture(architecture)) + return "l166"; + if (isArmArchitecture(architecture)) + return "armlink"; + throw "Unable to deduce linker name for unsupported architecture: '" + + architecture + "'"; +} + +function archiverName(qbs) { + var architecture = qbs.architecture; + if (architecture === "mcs51") + return "lib51"; + if (architecture === "mcs251") + return "lib251"; + if (isC166Architecture(architecture)) + return "lib166"; + if (isArmArchitecture(architecture)) + return "armar"; + throw "Unable to deduce archiver name for unsupported architecture: '" + + architecture + "'"; +} + +function staticLibrarySuffix(qbs) { + var architecture = qbs.architecture; + if (isMcsArchitecture(architecture) || isC166Architecture(architecture) + || isArmArchitecture(architecture)) { + return ".lib"; + } + throw "Unable to deduce static library suffix for unsupported architecture: '" + + architecture + "'"; +} + +function executableSuffix(qbs) { + var architecture = qbs.architecture; + if (isMcsArchitecture(architecture) || isC166Architecture(architecture)) + return ".abs"; + if (isArmArchitecture(architecture)) + return ".axf"; + throw "Unable to deduce executable suffix for unsupported architecture: '" + + architecture + "'"; +} + +function objectSuffix(qbs) { + var architecture = qbs.architecture; + if (isMcsArchitecture(architecture) || isC166Architecture(architecture)) + return ".obj"; + if (isArmArchitecture(architecture)) + return ".o"; + throw "Unable to deduce object file suffix for unsupported architecture: '" + + architecture + "'"; +} + +function mapFileSuffix(qbs) { + var architecture = qbs.architecture; + if (isMcs51Architecture(architecture)) + return ".m51"; + if (isC166Architecture(architecture)) + return ".m66"; + return ".map"; +} + +function imageFormat(qbs) { + var architecture = qbs.architecture; + if (isMcsArchitecture(architecture) || isC166Architecture(architecture)) + // Keil OMF51 or OMF2 Object Module Format (which is an + // extension of the original Intel OMF51). + return "omf"; + if (isArmArchitecture(architecture)) + return "elf"; + throw "Unable to deduce image format for unsupported architecture: '" + + architecture + "'"; +} + +function preincludeFlag(compilerPath) { + if (isArmCCCompiler(compilerPath)) + return "--preinclude"; + else if (isArmClangCompiler(compilerPath)) + return "-include"; +} + +function guessArmCCArchitecture(targetArchArm, targetArchThumb) { + var arch = "arm"; + if (targetArchArm === "4" && targetArchThumb === "0") + arch += "v4"; + else if (targetArchArm === "4" && targetArchThumb === "1") + arch += "v4t"; + else if (targetArchArm === "5" && targetArchThumb === "2") + arch += "v5t"; + else if (targetArchArm === "6" && targetArchThumb === "3") + arch += "v6"; + else if (targetArchArm === "6" && targetArchThumb === "4") + arch += "v6t2"; + else if (targetArchArm === "0" && targetArchThumb === "3") + arch += "v6m"; + else if (targetArchArm === "7" && targetArchThumb === "4") + arch += "v7r"; + else if (targetArchArm === "0" && targetArchThumb === "4") + arch += "v7m"; + return arch; +} + +function guessArmClangArchitecture(targetArchArm, targetArchProfile) { + targetArchProfile = targetArchProfile.replace(/'/g, ""); + var arch = "arm"; + if (targetArchArm !== "" && targetArchProfile !== "") + arch += "v" + targetArchArm + targetArchProfile.toLowerCase(); + return arch; +} + +function guessArchitecture(macros) { + if (macros["__C51__"]) + return "mcs51"; + else if (macros["__C251__"]) + return "mcs251"; + else if (macros["__C166__"]) + return "c166"; + else if (macros["__CC_ARM"] === "1") + return guessArmCCArchitecture(macros["__TARGET_ARCH_ARM"], macros["__TARGET_ARCH_THUMB"]); + else if (macros["__clang__"] === "1" && macros["__arm__"] === "1") + return guessArmClangArchitecture(macros["__ARM_ARCH"], macros["__ARM_ARCH_PROFILE"]); +} + +function guessEndianness(macros) { + if (macros["__C51__"] || macros["__C251__"]) { + // The 8051 processors are 8-bit. So, the data with an integer type + // represented by more than one byte is stored as big endian in the + // Keil toolchain. See for more info: + // * http://www.keil.com/support/man/docs/c51/c51_ap_2bytescalar.htm + // * http://www.keil.com/support/man/docs/c51/c51_ap_4bytescalar.htm + // * http://www.keil.com/support/man/docs/c251/c251_ap_2bytescalar.htm + // * http://www.keil.com/support/man/docs/c251/c251_ap_4bytescalar.htm + return "big"; + } else if (macros["__C166__"]) { + // The C166 processors are 16-bit. So, the data with an integer type + // represented by more than one byte is stored as little endian in the + // Keil toolchain. See for more info: + // * http://www.keil.com/support/man/docs/c166/c166_ap_ints.htm + // * http://www.keil.com/support/man/docs/c166/c166_ap_longs.htm + return "little"; + } else if (macros["__CC_ARM"]) { + return macros["__BIG_ENDIAN"] ? "big" : "little"; + } else if (macros["__clang__"] && macros["__arm__"]) { + switch (macros["__BYTE_ORDER__"]) { + case "__ORDER_BIG_ENDIAN__": + return "big"; + case "__ORDER_LITTLE_ENDIAN__": + return "little"; + } + } +} + +function guessVersion(macros) { + if (macros["__C51__"] || macros["__C251__"]) { + var mcsVersion = macros["__C51__"] || macros["__C251__"]; + return { major: parseInt(mcsVersion / 100), + minor: parseInt(mcsVersion % 100), + patch: 0, + found: true } + } else if (macros["__C166__"]) { + var xcVersion = macros["__C166__"]; + return { major: parseInt(xcVersion / 100), + minor: parseInt(xcVersion % 100), + patch: 0, + found: true } + } else if (macros["__CC_ARM"] || macros["__clang__"]) { + var armVersion = macros["__ARMCC_VERSION"]; + return { major: parseInt(armVersion / 1000000), + minor: parseInt(armVersion / 10000) % 100, + patch: parseInt(armVersion) % 10000, + found: true } + } +} + +function dumpMcsCompilerMacros(compilerFilePath, tag) { + // C51 or C251 compiler support only C language. + if (tag === "cpp") + return {}; + + // Note: The C51 or C251 compiler does not support the predefined + // macros dumping. So, we do it with the following trick, where we try + // to create and compile a special temporary file and to parse the console + // output with the own magic pattern: (""|"key"|"value"|""). + + function createDumpMacrosFile() { + var td = new TemporaryDir(); + var fn = FileInfo.fromNativeSeparators(td.path() + "/dump-macros.c"); + var tf = new TextFile(fn, TextFile.WriteOnly); + tf.writeLine("#define VALUE_TO_STRING(x) #x"); + tf.writeLine("#define VALUE(x) VALUE_TO_STRING(x)"); + + // Prepare for C51 compiler. + tf.writeLine("#if defined(__C51__) || defined(__CX51__)"); + tf.writeLine("# define VAR_NAME_VALUE(var) \"(\"\"\"\"|\"#var\"|\"VALUE(var)\"|\"\"\"\")\""); + tf.writeLine("# if defined (__C51__)"); + tf.writeLine("# pragma message (VAR_NAME_VALUE(__C51__))"); + tf.writeLine("# endif"); + tf.writeLine("# if defined(__CX51__)"); + tf.writeLine("# pragma message (VAR_NAME_VALUE(__CX51__))"); + tf.writeLine("# endif"); + tf.writeLine("# if defined(__MODEL__)"); + tf.writeLine("# pragma message (VAR_NAME_VALUE(__MODEL__))"); + tf.writeLine("# endif"); + tf.writeLine("# if defined(__STDC__)"); + tf.writeLine("# pragma message (VAR_NAME_VALUE(__STDC__))"); + tf.writeLine("# endif"); + tf.writeLine("#endif"); + + // Prepare for C251 compiler. + tf.writeLine("#if defined(__C251__)"); + tf.writeLine("# define VAR_NAME_VALUE(var) \"\"|#var|VALUE(var)|\"\""); + tf.writeLine("# if defined (__C251__)"); + tf.writeLine("# warning (VAR_NAME_VALUE(__C251__))"); + tf.writeLine("# endif"); + tf.writeLine("# if defined (__MODEL__)"); + tf.writeLine("# warning (VAR_NAME_VALUE(__MODEL__))"); + tf.writeLine("# endif"); + tf.writeLine("# if defined (__STDC__)"); + tf.writeLine("# warning (VAR_NAME_VALUE(__STDC__))"); + tf.writeLine("# endif"); + tf.writeLine("# if defined (__FLOAT64__)"); + tf.writeLine("# warning (VAR_NAME_VALUE(__FLOAT64__))"); + tf.writeLine("# endif"); + tf.writeLine("# if defined (__MODSRC__)"); + tf.writeLine("# warning (VAR_NAME_VALUE(__MODSRC__))"); + tf.writeLine("# endif"); + tf.writeLine("#endif"); + tf.close(); + return fn; + } + + var fn = createDumpMacrosFile(); + var p = new Process(); + p.exec(compilerFilePath, [ fn ], false); + var map = {}; + p.readStdOut().trim().split(/\r?\n/g).map(function(line) { + var parts = line.split("\"|\"", 4); + if (parts.length === 4) + map[parts[1]] = parts[2]; + }); + return map; +} + +function dumpC166CompilerMacros(compilerFilePath, tag) { + // C166 compiler support only C language. + if (tag === "cpp") + return {}; + + // Note: The C166 compiler does not support the predefined + // macros dumping. Also, it does not support the '#pragma' and + // '#message|warning|error' directives properly (it is impossible + // to print to console the value of macro). + // So, we do it with the following trick, where we try + // to create and compile a special temporary file and to parse the console + // output with the own magic pattern, e.g: + // + // *** WARNING C320 IN LINE 41 OF c51.c: __C166__ + // *** WARNING C2 IN LINE 42 OF c51.c: '757': unknown #pragma/control, line ignored + // + // where the '__C166__' is a key, and the '757' is a value. + + function createDumpMacrosFile() { + var td = new TemporaryDir(); + var fn = FileInfo.fromNativeSeparators(td.path() + "/dump-macros.c"); + var tf = new TextFile(fn, TextFile.WriteOnly); + + // Prepare for C166 compiler. + tf.writeLine("#if defined(__C166__)"); + tf.writeLine("# if defined(__C166__)"); + tf.writeLine("# warning __C166__"); + tf.writeLine("# pragma __C166__"); + tf.writeLine("# endif"); + tf.writeLine("# if defined(__DUS__)"); + tf.writeLine("# warning __DUS__"); + tf.writeLine("# pragma __DUS__"); + tf.writeLine("# endif"); + tf.writeLine("# if defined(__MAC__)"); + tf.writeLine("# warning __MAC__"); + tf.writeLine("# pragma __MAC__"); + tf.writeLine("# endif"); + tf.writeLine("# if defined(__MOD167__)"); + tf.writeLine("# warning __MOD167__"); + tf.writeLine("# pragma __MOD167__"); + tf.writeLine("# endif"); + tf.writeLine("# if defined(__MODEL__)"); + tf.writeLine("# warning __MODEL__"); + tf.writeLine("# pragma __MODEL__"); + tf.writeLine("# endif"); + tf.writeLine("# if defined(__MODV2__)"); + tf.writeLine("# warning __MODV2__"); + tf.writeLine("# pragma __MODV2__"); + tf.writeLine("# endif"); + tf.writeLine("# if defined(__SAVEMAC__)"); + tf.writeLine("# warning __SAVEMAC__"); + tf.writeLine("# pragma __SAVEMAC__"); + tf.writeLine("# endif"); + tf.writeLine("# if defined(__STDC__)"); + tf.writeLine("# warning __STDC__"); + tf.writeLine("# pragma __STDC__"); + tf.writeLine("# endif"); + tf.writeLine("#endif"); + + tf.close(); + return fn; + } + + var fn = createDumpMacrosFile(); + var p = new Process(); + p.exec(compilerFilePath, [ fn ], false); + var lines = p.readStdOut().trim().split(/\r?\n/g); + + var map = {}; + for (var i = 0; i < lines.length; ++i) { + // First line should contains the macro key. + var keyLine = lines[i]; + if (!keyLine.startsWith("***")) + continue; + var key; + if (keyLine.endsWith("__C166__")) + key = "__C166__"; + else if (keyLine.endsWith("__DUS__")) + key = "__DUS__"; + else if (keyLine.endsWith("__MAC__")) + key = "__MAC__"; + else if (keyLine.endsWith("__MOD167__")) + key = "__MOD167__"; + else if (keyLine.endsWith("__MODEL__")) + key = "__MODEL__"; + else if (keyLine.endsWith("__MODV2__")) + key = "__MODV2__"; + else if (keyLine.endsWith("__SAVEMAC__")) + key = "__SAVEMAC__"; + else if (keyLine.endsWith("__STDC__")) + key = "__STDC__"; + else + continue; + + i += 1; + if (i >= lines.length) + break; + + // Second line should contains the macro value. + var valueLine = lines[i]; + if (!valueLine.startsWith("***")) + continue; + + var startQuoteIndex = valueLine.indexOf("'"); + if (startQuoteIndex === -1) + continue; + var stopQuoteIndex = valueLine.indexOf("'", startQuoteIndex + 1); + if (stopQuoteIndex === -1) + continue; + + var value = valueLine.substring(startQuoteIndex + 1, stopQuoteIndex); + map[key] = value; + } + return map; +} + +function dumpArmCCCompilerMacros(compilerFilePath, tag, nullDevice) { + var args = [ "-E", "--list-macros", "--cpu=cortex-m0", nullDevice ]; + if (tag === "cpp") + args.push("--cpp"); + + var p = new Process(); + p.exec(compilerFilePath, args, false); + var map = {}; + p.readStdOut().trim().split(/\r?\n/g).map(function (line) { + if (!line.startsWith("#define")) + return; + var parts = line.split(" ", 3); + map[parts[1]] = parts[2]; + }); + return map; +} + +function dumpArmClangCompilerMacros(compilerFilePath, tag, nullDevice) { + var args = [ "--target=arm-arm-none-eabi", "-mcpu=cortex-m0", "-dM", "-E", + "-x", ((tag === "cpp") ? "c++" : "c"), nullDevice ]; + var p = new Process(); + p.exec(compilerFilePath, args, false); + var map = {}; + p.readStdOut().trim().split(/\r?\n/g).map(function (line) { + var parts = line.split(" ", 3); + map[parts[1]] = parts[2]; + }); + return map; +} + +function dumpMacros(compilerFilePath, tag, nullDevice) { + if (isMcsCompiler(compilerFilePath)) + return dumpMcsCompilerMacros(compilerFilePath, tag, nullDevice); + else if (isC166Compiler(compilerFilePath)) + return dumpC166CompilerMacros(compilerFilePath, tag, nullDevice); + else if (isArmCCCompiler(compilerFilePath)) + return dumpArmCCCompilerMacros(compilerFilePath, tag, nullDevice); + else if (isArmClangCompiler(compilerFilePath)) + return dumpArmClangCompilerMacros(compilerFilePath, tag, nullDevice); + return {}; +} + +function dumpMcsCompilerIncludePaths(compilerFilePath) { + var includePath = compilerFilePath.replace(/bin[\\\/](.*)$/i, "inc"); + return [includePath]; +} + +function dumpC166CompilerIncludePaths(compilerFilePath) { + // Same as for MCS compiler. + return dumpMcsCompilerIncludePaths(compilerFilePath); +} + +function dumpArmCCCompilerIncludePaths(compilerFilePath) { + var includePath = compilerFilePath.replace(/bin[\\\/](.*)$/i, "include"); + return [includePath]; +} + +function dumpArmClangCompilerIncludePaths(compilerFilePath, nullDevice) { + var args = [ "--target=arm-arm-none-eabi", "-mcpu=cortex-m0", "-v", "-E", + "-x", "c++", nullDevice ]; + var p = new Process(); + p.exec(compilerFilePath, args, false); + var lines = p.readStdErr().trim().split(/\r?\n/g).map(function (line) { return line.trim(); }); + var addIncludes = false; + var includePaths = []; + for (var i = 0; i < lines.length; ++i) { + var line = lines[i]; + if (line === "#include <...> search starts here:") + addIncludes = true; + else if (line === "End of search list.") + addIncludes = false; + else if (addIncludes) + includePaths.push(line); + } + return includePaths; +} + +function dumpDefaultPaths(compilerFilePath, nullDevice) { + var includePaths = []; + if (isMcsCompiler(compilerFilePath)) + includePaths = dumpMcsCompilerIncludePaths(compilerFilePath); + else if (isC166Compiler(compilerFilePath)) + includePaths = dumpC166CompilerIncludePaths(compilerFilePath); + else if (isArmCCCompiler(compilerFilePath)) + includePaths = dumpArmCCCompilerIncludePaths(compilerFilePath); + else if (isArmClangCompiler(compilerFilePath)) + includePaths = dumpArmClangCompilerIncludePaths(compilerFilePath, nullDevice); + return { "includePaths": includePaths }; +} + +function adjustPathsToWindowsSeparators(sourcePaths) { + var resulingPaths = []; + sourcePaths.forEach(function(path) { + resulingPaths.push(FileInfo.toWindowsSeparators(path)); + }); + return resulingPaths; +} + +function collectLibraryDependencies(product) { + var seen = {}; + var result = []; + + function addFilePath(filePath) { + result.push({ filePath: filePath }); + } + + function addArtifactFilePaths(dep, artifacts) { + if (!artifacts) + return; + var artifactFilePaths = artifacts.map(function(a) { return a.filePath; }); + artifactFilePaths.forEach(addFilePath); + } + + function addExternalStaticLibs(obj) { + if (!obj.cpp) + return; + function ensureArray(a) { + return Array.isArray(a) ? a : []; + } + function sanitizedModuleListProperty(obj, moduleName, propertyName) { + return ensureArray(ModUtils.sanitizedModuleProperty(obj, moduleName, propertyName)); + } + var externalLibs = [].concat( + sanitizedModuleListProperty(obj, "cpp", "staticLibraries")); + var staticLibrarySuffix = obj.moduleProperty("cpp", "staticLibrarySuffix"); + externalLibs.forEach(function(staticLibraryName) { + if (!staticLibraryName.endsWith(staticLibrarySuffix)) + staticLibraryName += staticLibrarySuffix; + addFilePath(staticLibraryName); + }); + } + + function traverse(dep) { + if (seen.hasOwnProperty(dep.name)) + return; + seen[dep.name] = true; + + if (dep.parameters.cpp && dep.parameters.cpp.link === false) + return; + + var staticLibraryArtifacts = dep.artifacts["staticlibrary"]; + if (staticLibraryArtifacts) { + dep.dependencies.forEach(traverse); + addArtifactFilePaths(dep, staticLibraryArtifacts); + addExternalStaticLibs(dep); + } + } + + product.dependencies.forEach(traverse); + addExternalStaticLibs(product); + return result; +} + +function filterMcsOutput(output) { + var filteredLines = []; + output.split(/\r\n|\r|\n/).forEach(function(line) { + var regexp = /^\s*\*{3}\s|^\s{29}|^\s{4}\S|^\s{2}[0-9A-F]{4}|^\s{21,25}\d+|^[0-9A-F]{4}\s\d+/; + if (regexp.exec(line)) + filteredLines.push(line); + }); + return filteredLines.join('\n'); +}; + +function filterC166Output(output) { + var filteredLines = []; + output.split(/\r\n|\r|\n/).forEach(function(line) { + var regexp = /^\s*\*{3}\s|^\s{29}|^\s{27,28}\d+|^\s{21}\d+|^\s{4}\S|^\s{2}[0-9A-F]{4}|^[0-9A-F]{4}\s\d+/; + if (regexp.exec(line)) + filteredLines.push(line); + }); + return filteredLines.join('\n'); +}; + +function compilerOutputArtifacts(input, useListing) { + var artifacts = []; + artifacts.push({ + fileTags: ["obj"], + filePath: Utilities.getHash(input.baseDir) + "/" + + input.fileName + input.cpp.objectSuffix + }); + if (useListing) { + artifacts.push({ + fileTags: ["lst"], + filePath: Utilities.getHash(input.baseDir) + "/" + + ((isMcsArchitecture(input.cpp.architecture) + || isC166Architecture(input.cpp.architecture)) + ? input.fileName : input.baseName) + + ".lst" + }); + } + return artifacts; +} + +function applicationLinkerOutputArtifacts(product) { + var app = { + fileTags: ["application"], + filePath: FileInfo.joinPaths( + product.destinationDirectory, + PathTools.applicationFilePath(product)) + }; + var mem_map = { + fileTags: ["mem_map"], + filePath: FileInfo.joinPaths( + product.destinationDirectory, + product.targetName + product.cpp.mapFileSuffix) + }; + return [app, mem_map]; +} + +function staticLibraryLinkerOutputArtifacts(product) { + var staticLib = { + fileTags: ["staticlibrary"], + filePath: FileInfo.joinPaths( + product.destinationDirectory, + PathTools.staticLibraryFilePath(product)) + }; + return [staticLib] +} + +function compilerFlags(project, product, input, outputs, explicitlyDependsOn) { + // Determine which C-language we're compiling. + var tag = ModUtils.fileTagForTargetLanguage(input.fileTags.concat(outputs.obj[0].fileTags)); + var args = []; + + var allDefines = []; + var platformDefines = input.cpp.platformDefines; + if (platformDefines) + allDefines = allDefines.uniqueConcat(platformDefines); + var defines = input.cpp.defines; + if (defines) + allDefines = allDefines.uniqueConcat(defines); + + var allIncludePaths = []; + var includePaths = input.cpp.includePaths; + if (includePaths) + allIncludePaths = allIncludePaths.uniqueConcat(includePaths); + var systemIncludePaths = input.cpp.systemIncludePaths; + if (systemIncludePaths) + allIncludePaths = allIncludePaths.uniqueConcat(systemIncludePaths); + var distributionIncludePaths = input.cpp.distributionIncludePaths; + if (distributionIncludePaths) + allIncludePaths = allIncludePaths.uniqueConcat(distributionIncludePaths); + + var architecture = input.qbs.architecture; + if (isMcsArchitecture(architecture) || isC166Architecture(architecture)) { + // Input. + args.push(FileInfo.toWindowsSeparators(input.filePath)); + + // Output. + args.push("OBJECT (" + FileInfo.toWindowsSeparators(outputs.obj[0].filePath) + ")"); + + // Defines. + if (allDefines.length > 0) + args = args.concat("DEFINE (" + allDefines.join(",") + ")"); + + // Includes. + if (allIncludePaths.length > 0) { + var adjusted = adjustPathsToWindowsSeparators(allIncludePaths); + args = args.concat("INCDIR (" + adjusted.join(";") + ")"); + } + + // Debug information flags. + if (input.cpp.debugInformation) + args.push("DEBUG"); + + // Optimization level flags. + switch (input.cpp.optimization) { + case "small": + args.push("OPTIMIZE (SIZE)"); + break; + case "fast": + args.push("OPTIMIZE (SPEED)"); + break; + case "none": + args.push("OPTIMIZE (0)"); + break; + } + + // Warning level flags. + switch (input.cpp.warningLevel) { + case "none": + args.push("WARNINGLEVEL (0)"); + break; + case "all": + args.push("WARNINGLEVEL (2)"); + if (architecture === "mcs51") + args.push("FARWARNING"); + break; + } + + // Listing files generation flag. + if (!input.cpp.generateCompilerListingFiles) + args.push("NOPRINT"); + else + args.push("PRINT(" + FileInfo.toWindowsSeparators(outputs.lst[0].filePath) + ")"); + } else if (isArmArchitecture(architecture)) { + // Input. + args.push("-c", input.filePath); + // Output. + args.push("-o", outputs.obj[0].filePath); + + // Defines. + args = args.concat(allDefines.map(function(define) { return '-D' + define })); + // Includes. + args = args.concat(allIncludePaths.map(function(include) { return '-I' + include })); + + var prefixHeaders = input.cpp.prefixHeaders; + for (var i in prefixHeaders) + args.push(input.cpp.preincludeFlag, prefixHeaders[i]); + + var compilerPath = input.cpp.compilerPath; + if (isArmCCCompiler(compilerPath)) { + // Debug information flags. + if (input.cpp.debugInformation) { + args.push("--debug"); + args.push("-g"); + } + + // Optimization level flags. + switch (input.cpp.optimization) { + case "small": + args.push("-Ospace") + break; + case "fast": + args.push("-Otime") + break; + case "none": + args.push("-O0") + break; + } + + // Warning level flags. + switch (input.cpp.warningLevel) { + case "none": + args.push("-W"); + break; + default: + // By default all warnings are enabled. + break; + } + + if (tag === "c") { + // C language version flags. + var knownCLanguageValues = ["c99", "c90"]; + var cLanguageVersion = Cpp.languageVersion( + input.cpp.cLanguageVersion, knownCLanguageValues, "C"); + switch (cLanguageVersion) { + case "c99": + args.push("--c99"); + break; + case "c90": + args.push("--c90"); + break; + } + } else if (tag === "cpp") { + // C++ language version flags. + var knownCppLanguageValues = ["c++11", "c++03"]; + var cppLanguageVersion = Cpp.languageVersion( + input.cpp.cxxLanguageVersion, knownCppLanguageValues, "C++"); + switch (cppLanguageVersion) { + case "c++11": + args.push("--cpp11"); + break; + default: + // Default C++ language is C++03. + args.push("--cpp"); + break; + } + + // Exceptions flags. + var enableExceptions = input.cpp.enableExceptions; + if (enableExceptions !== undefined) + args.push(enableExceptions ? "--exceptions" : "--no_exceptions"); + + // RTTI flags. + var enableRtti = input.cpp.enableRtti; + if (enableRtti !== undefined) + args.push(enableRtti ? "--rtti" : "--no_rtti"); + } + + // Listing files generation flag. + if (input.cpp.generateCompilerListingFiles) { + args.push("--list"); + args.push("--list_dir", FileInfo.path(outputs.lst[0].filePath)); + } + } else if (isArmClangCompiler(compilerPath)) { + // Debug information flags. + if (input.cpp.debugInformation) + args.push("-g"); + + // Optimization level flags. + switch (input.cpp.optimization) { + case "small": + args.push("-Oz") + break; + case "fast": + args.push("-Ofast") + break; + case "none": + args.push("-O0") + break; + } + + // Warning level flags. + switch (input.cpp.warningLevel) { + case "all": + args.push("-Wall"); + break; + default: + break; + } + + if (input.cpp.treatWarningsAsErrors) + args.push("-Werror"); + + if (tag === "c") { + // C language version flags. + var knownCLanguageValues = ["c11", "c99", "c90", "c89"]; + var cLanguageVersion = Cpp.languageVersion( + input.cpp.cLanguageVersion, knownCLanguageValues, "C"); + if (cLanguageVersion) + args.push("-std=" + cLanguageVersion); + } else if (tag === "cpp") { + // C++ language version flags. + var knownCppLanguageValues = ["c++17", "c++14", "c++11", "c++03", "c++98"]; + var cppLanguageVersion = Cpp.languageVersion( + input.cpp.cxxLanguageVersion, knownCppLanguageValues, "C++"); + if (cppLanguageVersion) + args.push("-std=" + cppLanguageVersion); + + // Exceptions flags. + var enableExceptions = input.cpp.enableExceptions; + if (enableExceptions !== undefined) + args.push(enableExceptions ? "-fexceptions" : "-fno-exceptions"); + + // RTTI flags. + var enableRtti = input.cpp.enableRtti; + if (enableRtti !== undefined) + args.push(enableRtti ? "-frtti" : "-fno-rtti"); + } + + // Listing files generation does not supported! + // There are only a workaround: http://www.keil.com/support/docs/4152.htm + } + } + + // Misc flags. + args = args.concat(ModUtils.moduleProperty(input, "platformFlags"), + ModUtils.moduleProperty(input, "flags"), + ModUtils.moduleProperty(input, "platformFlags", tag), + ModUtils.moduleProperty(input, "flags", tag), + ModUtils.moduleProperty(input, "driverFlags", tag)); + return args; +} + +function assemblerFlags(project, product, input, outputs, explicitlyDependsOn) { + // Determine which C-language we're compiling + var tag = ModUtils.fileTagForTargetLanguage(input.fileTags.concat(outputs.obj[0].fileTags)); + var args = []; + + var allDefines = []; + var platformDefines = input.cpp.platformDefines; + if (platformDefines) + allDefines = allDefines.uniqueConcat(platformDefines); + var defines = input.cpp.defines; + if (defines) + allDefines = allDefines.uniqueConcat(defines); + + var allIncludePaths = []; + var includePaths = input.cpp.includePaths; + if (includePaths) + allIncludePaths = allIncludePaths.uniqueConcat(includePaths); + var systemIncludePaths = input.cpp.systemIncludePaths; + if (systemIncludePaths) + allIncludePaths = allIncludePaths.uniqueConcat(systemIncludePaths); + var distributionIncludePaths = input.cpp.distributionIncludePaths; + if (distributionIncludePaths) + allIncludePaths = allIncludePaths.uniqueConcat(distributionIncludePaths); + + var architecture = input.qbs.architecture; + if (isMcsArchitecture(architecture) || isC166Architecture(architecture)) { + // Input. + args.push(FileInfo.toWindowsSeparators(input.filePath)); + + // Output. + args.push("OBJECT (" + FileInfo.toWindowsSeparators(outputs.obj[0].filePath) + ")"); + + // Defines. + if (allDefines.length > 0) + args = args.concat("DEFINE (" + allDefines.join(",") + ")"); + + // Includes. + if (allIncludePaths.length > 0) { + var adjusted = adjustPathsToWindowsSeparators(allIncludePaths); + args = args.concat("INCDIR (" + adjusted.join(";") + ")"); + } + + // Debug information flags. + if (input.cpp.debugInformation) + args.push("DEBUG"); + + // Enable errors printing. + args.push("EP"); + + // Listing files generation flag. + if (!input.cpp.generateAssemblerListingFiles) + args.push("NOPRINT"); + else + args.push("PRINT(" + FileInfo.toWindowsSeparators(outputs.lst[0].filePath) + ")"); + } else if (isArmArchitecture(architecture)) { + // Input. + args.push(input.filePath); + + // Output. + args.push("-o", outputs.obj[0].filePath); + + // Defines. + allDefines.forEach(function(define) { + var parts = define.split("="); + args.push("--pd"); + if (parts[1] === undefined) + args.push(parts[0] + " SETA " + 1); + else if (parts[1].contains("\"")) + args.push(parts[0] + " SETS " + parts[1]); + else + args.push(parts[0] + " SETA " + parts[1]); + }); + + // Includes. + args = args.concat(allIncludePaths.map(function(include) { return '-I' + include })); + + // Debug information flags. + if (input.cpp.debugInformation) { + args.push("--debug"); + args.push("-g"); + } + + // Warning level flags. + if (input.cpp.warningLevel === "none") + args.push("--no_warn"); + + // Byte order flags. + var endianness = input.cpp.endianness; + if (endianness) + args.push((endianness === "little") ? "--littleend" : "--bigend"); + + // Listing files generation flag. + if (input.cpp.generateAssemblerListingFiles) + args.push("--list", outputs.lst[0].filePath); + } + + // Misc flags. + args = args.concat(ModUtils.moduleProperty(input, "platformFlags", tag), + ModUtils.moduleProperty(input, "flags", tag)); + return args; +} + +function linkerFlags(project, product, inputs, outputs) { + var args = []; + + var architecture = product.qbs.architecture; + if (isMcsArchitecture(architecture) || isC166Architecture(architecture)) { + // Note: The C51/256/166 linker does not distinguish an object files and + // a libraries, it interpret all this stuff as an input objects, + // so, we need to pass it together in one string. + + var allObjectPaths = []; + function addObjectPath(obj) { + allObjectPaths.push(obj.filePath); + } + + // Inputs. + if (inputs.obj) + inputs.obj.map(function(obj) { addObjectPath(obj) }); + + // Library dependencies. + var libraryObjects = collectLibraryDependencies(product); + libraryObjects.forEach(function(dep) { addObjectPath(dep); }) + + // Add all input objects as arguments (application and library object files). + var adjusted = adjustPathsToWindowsSeparators(allObjectPaths); + args = args.concat(adjusted.join(",")); + + // Output. + // Note: We need to wrap an output file name with quotes. Otherwise + // the linker will ignore a specified file name. + args.push("TO", '"' + FileInfo.toWindowsSeparators(outputs.application[0].filePath) + '"'); + + // Map file generation flag. + if (!product.cpp.generateLinkerMapFile) + args.push("NOPRINT"); + else + args.push("PRINT(" + FileInfo.toWindowsSeparators(outputs.mem_map[0].filePath) + ")"); + } else if (isArmArchitecture(architecture)) { + // Inputs. + if (inputs.obj) + args = args.concat(inputs.obj.map(function(obj) { return obj.filePath })); + + // Output. + args.push("--output", outputs.application[0].filePath); + + // Library paths. + var libraryPaths = product.cpp.libraryPaths; + if (libraryPaths) + args.push("--userlibpath=" + libraryPaths.join(",")); + + // Library dependencies. + var libraryDependencies = collectLibraryDependencies(product); + args = args.concat(libraryDependencies.map(function(dep) { return dep.filePath; })); + + // Debug information flag. + var debugInformation = product.cpp.debugInformation; + if (debugInformation !== undefined) + args.push(debugInformation ? "--debug" : "--no_debug"); + + // Map file generation flag. + if (product.cpp.generateLinkerMapFile) + args.push("--list", outputs.mem_map[0].filePath); + + // Entry point flag. + if (product.cpp.entryPoint) + args.push("--entry", product.cpp.entryPoint); + + // Linker scripts flags. + var linkerScripts = inputs.linkerscript + ? inputs.linkerscript.map(function(a) { return a.filePath; }) : []; + linkerScripts.forEach(function(script) { args.push("--scatter", script); }); + } + + // Misc flags. + args = args.concat(ModUtils.moduleProperty(product, "driverLinkerFlags")); + return args; +} + +function archiverFlags(project, product, inputs, outputs) { + var args = []; + + var architecture = product.qbs.architecture; + if (isMcsArchitecture(architecture) || isC166Architecture(architecture)) { + // Library creation command. + args.push("TRANSFER"); + + var allObjectPaths = []; + function addObjectPath(obj) { + allObjectPaths.push(obj.filePath); + } + + // Inputs. + if (inputs.obj) + inputs.obj.map(function(obj) { addObjectPath(obj) }); + + // Add all input objects as arguments. + var adjusted = adjustPathsToWindowsSeparators(allObjectPaths); + args = args.concat(adjusted.join(",")); + + // Output. + // Note: We need to wrap a output file name with quotes. Otherwise + // the linker will ignore a specified file name. + args.push("TO", '"' + FileInfo.toWindowsSeparators(outputs.staticlibrary[0].filePath) + '"'); + } else if (isArmArchitecture(architecture)) { + // Note: The ARM archiver command line expect the output file + // first, and then a set of input objects. + + // Output. + args.push("--create", outputs.staticlibrary[0].filePath); + + // Inputs. + if (inputs.obj) + args = args.concat(inputs.obj.map(function(obj) { return obj.filePath })); + + // Debug information flag. + if (product.cpp.debugInformation) + args.push("--debug_symbols"); + } + + return args; +} + +function prepareCompiler(project, product, inputs, outputs, input, output, explicitlyDependsOn) { + var args = compilerFlags(project, product, input, outputs, explicitlyDependsOn); + var compilerPath = input.cpp.compilerPath; + var architecture = input.cpp.architecture; + var cmd = new Command(compilerPath, args); + cmd.description = "compiling " + input.fileName; + cmd.highlight = "compiler"; + if (isMcsArchitecture(architecture)) { + cmd.maxExitCode = 1; + cmd.stdoutFilterFunction = filterMcsOutput; + } else if (isC166Architecture(architecture)) { + cmd.maxExitCode = 1; + cmd.stdoutFilterFunction = filterC166Output; + } + return [cmd]; +} + +function prepareAssembler(project, product, inputs, outputs, input, output, explicitlyDependsOn) { + var args = assemblerFlags(project, product, input, outputs, explicitlyDependsOn); + var assemblerPath = input.cpp.assemblerPath; + var architecture = input.cpp.architecture; + var cmd = new Command(assemblerPath, args); + cmd.description = "assembling " + input.fileName; + cmd.highlight = "compiler"; + if (isMcsArchitecture(architecture)) { + cmd.maxExitCode = 1; + cmd.stdoutFilterFunction = filterMcsOutput; + } else if (isC166Architecture(architecture)) { + cmd.maxExitCode = 1; + cmd.stdoutFilterFunction = filterC166Output; + } + return [cmd]; +} + +function prepareLinker(project, product, inputs, outputs, input, output) { + var primaryOutput = outputs.application[0]; + var args = linkerFlags(project, product, inputs, outputs); + var linkerPath = product.cpp.linkerPath; + var architecture = product.cpp.architecture; + var cmd = new Command(linkerPath, args); + cmd.description = "linking " + primaryOutput.fileName; + cmd.highlight = "linker"; + if (isMcsArchitecture(architecture)) { + cmd.maxExitCode = 1; + cmd.stdoutFilterFunction = filterMcsOutput; + } else if (isC166Architecture(architecture)) { + cmd.maxExitCode = 1; + cmd.stdoutFilterFunction = filterC166Output; + } + return [cmd]; +} + +function prepareArchiver(project, product, inputs, outputs, input, output) { + var args = archiverFlags(project, product, inputs, outputs); + var archiverPath = product.cpp.archiverPath; + var architecture = product.cpp.architecture; + var cmd = new Command(archiverPath, args); + cmd.description = "linking " + output.fileName; + cmd.highlight = "linker"; + if (isMcsArchitecture(architecture)) { + cmd.stdoutFilterFunction = filterMcsOutput; + } else if (isC166Architecture(architecture)) { + cmd.stdoutFilterFunction = filterC166Output; + } + return [cmd]; +} diff --git a/share/qbs/modules/cpp/keil.qbs b/share/qbs/modules/cpp/keil.qbs new file mode 100644 index 00000000..7a994d7e --- /dev/null +++ b/share/qbs/modules/cpp/keil.qbs @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 1.0 +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Probes +import "keil.js" as KEIL + +CppModule { + condition: qbs.hostOS.contains("windows") && qbs.toolchain && qbs.toolchain.contains("keil") + + Probes.BinaryProbe { + id: compilerPathProbe + condition: !toolchainInstallPath && !_skipAllChecks + names: ["c51"] + } + + Probes.KeilProbe { + id: keilProbe + condition: !_skipAllChecks + compilerFilePath: compilerPath + enableDefinesByLanguage: enableCompilerDefinesByLanguage + } + + qbs.architecture: keilProbe.found ? keilProbe.architecture : original + qbs.targetPlatform: "none" + + compilerVersionMajor: keilProbe.versionMajor + compilerVersionMinor: keilProbe.versionMinor + compilerVersionPatch: keilProbe.versionPatch + endianness: keilProbe.endianness + + compilerDefinesByLanguage: keilProbe.compilerDefinesByLanguage + compilerIncludePaths: keilProbe.includePaths + + property string toolchainInstallPath: compilerPathProbe.found + ? compilerPathProbe.path : undefined + + property string compilerExtension: qbs.hostOS.contains("windows") ? ".exe" : "" + + /* Work-around for QtCreator which expects these properties to exist. */ + property string cCompilerName: compilerName + property string cxxCompilerName: compilerName + + compilerName: KEIL.compilerName(qbs) + compilerExtension + compilerPath: FileInfo.joinPaths(toolchainInstallPath, compilerName) + + assemblerName: KEIL.assemblerName(qbs) + compilerExtension + assemblerPath: FileInfo.joinPaths(toolchainInstallPath, assemblerName) + + linkerName: KEIL.linkerName(qbs) + compilerExtension + linkerPath: FileInfo.joinPaths(toolchainInstallPath, linkerName) + + property string archiverName: KEIL.archiverName(qbs) + compilerExtension + property string archiverPath: FileInfo.joinPaths(toolchainInstallPath, archiverName) + + runtimeLibrary: "static" + + staticLibrarySuffix: KEIL.staticLibrarySuffix(qbs) + executableSuffix: KEIL.executableSuffix(qbs) + + property string objectSuffix: KEIL.objectSuffix(qbs) + property string mapFileSuffix: KEIL.mapFileSuffix(qbs) + + imageFormat: KEIL.imageFormat(qbs) + + enableExceptions: false + enableRtti: false + + property string preincludeFlag: KEIL.preincludeFlag(compilerPath) + + Rule { + id: assembler + inputs: ["asm"] + outputFileTags: ["obj", "lst"] + outputArtifacts: KEIL.compilerOutputArtifacts( + input, input.cpp.generateAssemblerListingFiles) + prepare: KEIL.prepareAssembler.apply(KEIL, arguments) + } + + FileTagger { + patterns: ["*.s", "*.a51", ".asm"] + fileTags: ["asm"] + } + + Rule { + id: compiler + inputs: ["cpp", "c"] + auxiliaryInputs: ["hpp"] + outputFileTags: ["obj", "lst"] + outputArtifacts: KEIL.compilerOutputArtifacts( + input, input.cpp.generateCompilerListingFiles) + prepare: KEIL.prepareCompiler.apply(KEIL, arguments) + } + + Rule { + id: applicationLinker + multiplex: true + inputs: ["obj", "linkerscript"] + inputsFromDependencies: ["staticlibrary"] + outputFileTags: ["application", "mem_map"] + outputArtifacts: KEIL.applicationLinkerOutputArtifacts(product) + prepare: KEIL.prepareLinker.apply(KEIL, arguments) + } + + Rule { + id: staticLibraryLinker + multiplex: true + inputs: ["obj"] + inputsFromDependencies: ["staticlibrary"] + outputFileTags: ["staticlibrary"] + outputArtifacts: KEIL.staticLibraryLinkerOutputArtifacts(product) + prepare: KEIL.prepareArchiver.apply(KEIL, arguments) + } +} diff --git a/share/qbs/modules/cpp/macos-gcc.qbs b/share/qbs/modules/cpp/macos-gcc.qbs new file mode 100644 index 00000000..612b46ae --- /dev/null +++ b/share/qbs/modules/cpp/macos-gcc.qbs @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.ModUtils +import qbs.Utilities + +DarwinGCC { + priority: 1 + condition: qbs.targetOS.contains('macos') && + qbs.toolchain && qbs.toolchain.contains('gcc') + + targetSystem: "macosx" + (minimumMacosVersion || "") + + minimumDarwinVersion: minimumMacosVersion + minimumDarwinVersionCompilerFlag: "-mmacosx-version-min" + minimumDarwinVersionLinkerFlag: "-macosx_version_min" + + libcxxAvailable: base + && minimumDarwinVersion + && Utilities.versionCompare(minimumDarwinVersion, "10.7") >= 0 +} diff --git a/share/qbs/modules/cpp/msvc.js b/share/qbs/modules/cpp/msvc.js new file mode 100644 index 00000000..df1f5eb5 --- /dev/null +++ b/share/qbs/modules/cpp/msvc.js @@ -0,0 +1,703 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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 Cpp = require("cpp.js"); +var File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var ModUtils = require("qbs.ModUtils"); +var Utilities = require("qbs.Utilities"); +var WindowsUtils = require("qbs.WindowsUtils"); + +function effectiveLinkerPath(product, inputs) { + if (product.cpp.linkerMode === "automatic") { + var compiler = product.cpp.compilerPath; + if (compiler) { + if (inputs.obj || inputs.staticlibrary) { + console.log("Found C/C++ objects, using compiler as a linker for " + product.name); + return compiler; + } + } + + console.log("Found no C-language objects, choosing system linker for " + product.name); + } + + return product.cpp.linkerPath; +} + +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 hasCxx17Option(input) +{ + // Probably this is not the earliest version to support the flag, but we have tested this one + // and it's a pain to find out the exact minimum. + return Utilities.versionCompare(input.cpp.compilerVersion, "19.12.25831") >= 0 + || (input.qbs.toolchain.contains("clang-cl") && input.cpp.compilerVersionMajor >= 7); +} + +function supportsExternalIncludesOption(input) { + if (input.qbs.toolchain.contains("clang-cl")) + return false; // Exclude clang-cl. + // This option was introcuded since MSVC 2017 v15.6 (aka _MSC_VER 19.13). + // But due to some MSVC bugs: + // * https://developercommunity.visualstudio.com/content/problem/181006/externali-include-paths-not-working.html + // this option has been fixed since MSVC 2017 update 9, v15.9 (aka _MSC_VER 19.16). + return Utilities.versionCompare(input.cpp.compilerVersion, "19.16") >= 0; +} + +function addLanguageVersionFlag(input, args) { + var cxxVersion = Cpp.languageVersion(input.cpp.cxxLanguageVersion, + ["c++17", "c++14", "c++11", "c++98"], "C++"); + if (!cxxVersion) + return; + + // Visual C++ 2013, Update 3 + var hasStdOption = Utilities.versionCompare(input.cpp.compilerVersion, "18.00.30723") >= 0 + // or clang-cl + || input.qbs.toolchain.contains("clang-cl"); + if (!hasStdOption) + return; + + var flag; + if (cxxVersion === "c++14") + flag = "/std:c++14"; + else if (cxxVersion === "c++17" && hasCxx17Option(input)) + flag = "/std:c++17"; + else if (cxxVersion !== "c++11" && cxxVersion !== "c++98") + flag = "/std:c++latest"; + if (flag) + args.push(flag); +} + +function handleClangClArchitectureFlags(product, architecture, flags) { + if (product.qbs.toolchain.contains("clang-cl")) { + if (architecture === "x86") + flags.push("-m32"); + else if (architecture === "x86_64") + flags.push("-m64"); + } +} + +function prepareCompiler(project, product, inputs, outputs, input, output, explicitlyDependsOn) { + var i; + 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)) + throw ("unsupported source language"); + + var enableExceptions = input.cpp.enableExceptions; + if (enableExceptions) { + var ehModel = input.cpp.exceptionHandlingModel; + switch (ehModel) { + case "default": + args.push("/EHsc"); // "Yes" in VS + break; + case "seh": + args.push("/EHa"); // "Yes with SEH exceptions" in VS + break; + case "externc": + args.push("/EHs"); // "Yes with Extern C functions" in VS + break; + } + } + + var enableRtti = input.cpp.enableRtti; + if (enableRtti !== undefined) { + args.push(enableRtti ? "/GR" : "/GR-"); + } + + switch (input.cpp.optimization) { + case "small": + args.push('/Os') + break; + case "fast": + args.push('/O2') + break; + case "none": + args.push("/Od"); + break; + } + + handleClangClArchitectureFlags(product, input.cpp.architecture, args); + + if (debugInformation) { + if (product.cpp.separateDebugInformation) + args.push('/Zi'); + else + args.push('/Z7'); + } + + var rtl = product.cpp.runtimeLibrary; + if (rtl) { + rtl = (rtl === "static" ? "/MT" : "/MD"); + if (product.qbs.enableDebugCode) + rtl += "d"; + args.push(rtl); + } + + var driverFlags = product.cpp.driverFlags; + if (driverFlags) + args = args.concat(driverFlags); + + // warnings: + var warningLevel = input.cpp.warningLevel; + if (warningLevel === 'none') + args.push('/w') + if (warningLevel === 'all') + args.push('/Wall') + if (input.cpp.treatWarningsAsErrors) + args.push('/WX') + var includePaths = input.cpp.includePaths; + if (includePaths) { + args = args.concat([].uniqueConcat(includePaths).map(function(path) { + return '/I' + FileInfo.toWindowsSeparators(path); + })); + } + + var allSystemIncludePaths = []; + var systemIncludePaths = input.cpp.systemIncludePaths; + if (systemIncludePaths) + allSystemIncludePaths = allSystemIncludePaths.uniqueConcat(systemIncludePaths); + var distributionIncludePaths = input.cpp.distributionIncludePaths; + if (distributionIncludePaths) + allSystemIncludePaths = allSystemIncludePaths.uniqueConcat(distributionIncludePaths); + var includeFlag = "/I"; + if (supportsExternalIncludesOption(input)) { + args.push("/experimental:external"); + includeFlag = "/external:I" + } + allSystemIncludePaths.forEach(function(path) { + args.push(includeFlag, FileInfo.toWindowsSeparators(path)); }); + + var allDefines = []; + var platformDefines = input.cpp.platformDefines; + if (platformDefines) + allDefines = allDefines.uniqueConcat(platformDefines); + var defines = input.cpp.defines; + if (defines) + allDefines = allDefines.uniqueConcat(defines); + for (i in allDefines) + args.push('/D' + allDefines[i].replace(/%/g, "%%")); + + var minimumWindowsVersion = product.cpp.minimumWindowsVersion; + if (minimumWindowsVersion) { + var hexVersion = WindowsUtils.getWindowsVersionInFormat(minimumWindowsVersion, 'hex'); + if (hexVersion) { + var versionDefs = [ 'WINVER', '_WIN32_WINNT', '_WIN32_WINDOWS' ]; + for (i in versionDefs) { + args.push('/D' + versionDefs[i] + '=' + hexVersion); + } + } + } + + if (product.cpp.debugInformation && product.cpp.separateDebugInformation) + args.push("/Fd" + product.targetName + ".cl" + product.cpp.debugInfoSuffix); + + if (input.cpp.generateCompilerListingFiles) + args.push("/Fa" + FileInfo.toWindowsSeparators(outputs.lst[0].filePath)); + + var objectMap = outputs.obj || outputs.intermediate_obj + var objOutput = objectMap ? objectMap[0] : undefined + args.push('/Fo' + FileInfo.toWindowsSeparators(objOutput.filePath)) + args.push(FileInfo.toWindowsSeparators(input.filePath)) + + var prefixHeaders = product.cpp.prefixHeaders; + for (i in prefixHeaders) + args.push("/FI" + FileInfo.toWindowsSeparators(prefixHeaders[i])); + + // Language + if (tag === "cpp") { + args.push("/TP"); + addLanguageVersionFlag(input, args); + } else if (tag === "c") { + args.push("/TC"); + } + + // Whether we're compiling a precompiled header or normal source file + var pchOutput = outputs[tag + "_pch"] ? outputs[tag + "_pch"][0] : undefined; + var pchInputs = explicitlyDependsOn[tag + "_pch"]; + if (pchOutput) { + // create PCH + if (input.qbs.toolchain.contains("clang-cl")) { + // clang-cl does not support /Yc flag without filename + args.push("/Yc" + FileInfo.toWindowsSeparators(input.filePath)); + // clang-cl complains when pch file is not included + args.push("/FI" + FileInfo.toWindowsSeparators(input.filePath)); + args.push("/Fp" + FileInfo.toWindowsSeparators(pchOutput.filePath)); + args.push("/Fo" + FileInfo.toWindowsSeparators(objOutput.filePath)); + } else { // real msvc + args.push("/Yc"); + args.push("/Fp" + FileInfo.toWindowsSeparators(pchOutput.filePath)); + args.push("/Fo" + FileInfo.toWindowsSeparators(objOutput.filePath)); + args.push(FileInfo.toWindowsSeparators(input.filePath)); + } + + } else if (pchInputs && pchInputs.length === 1 + && ModUtils.moduleProperty(input, "usePrecompiledHeader", tag)) { + // use PCH + var pchSourceArtifacts = product.artifacts[tag + "_pch_src"]; + if (pchSourceArtifacts && pchSourceArtifacts.length > 0) { + var pchSourceFilePath = pchSourceArtifacts[0].filePath; + var pchFilePath = FileInfo.toWindowsSeparators(pchInputs[0].filePath); + args.push("/FI" + pchSourceFilePath); + args.push("/Yu" + pchSourceFilePath); + args.push("/Fp" + pchFilePath); + } else { + console.warning("products." + product.name + ".usePrecompiledHeader is true, " + + "but there is no " + tag + "_pch_src artifact."); + } + } + + args = args.concat(ModUtils.moduleProperty(input, 'platformFlags'), + ModUtils.moduleProperty(input, 'flags'), + ModUtils.moduleProperty(input, 'platformFlags', tag), + ModUtils.moduleProperty(input, 'flags', tag)); + + var compilerPath = product.cpp.compilerPath; + var wrapperArgs = product.cpp.compilerWrapper; + if (wrapperArgs && wrapperArgs.length > 0) { + args.unshift(compilerPath); + compilerPath = wrapperArgs.shift(); + args = wrapperArgs.concat(args); + } + var cmd = new Command(compilerPath, args) + cmd.description = (pchOutput ? 'pre' : '') + 'compiling ' + input.fileName; + if (pchOutput) + cmd.description += ' (' + tag + ')'; + cmd.highlight = "compiler"; + cmd.jobPool = "compiler"; + cmd.workingDirectory = product.buildDirectory; + cmd.responseFileUsagePrefix = '@'; + // cl.exe outputs the cpp file name. We filter that out. + cmd.inputFileName = input.fileName; + cmd.relevantEnvironmentVariables = ["CL", "_CL_", "INCLUDE"]; + cmd.stdoutFilterFunction = function(output) { + return output.split(inputFileName + "\r\n").join(""); + }; + return [cmd]; +} + +function collectLibraryDependencies(product) { + var seen = {}; + var result = []; + + function addFilePath(filePath, wholeArchive, productName) { + result.push({ filePath: filePath, wholeArchive: wholeArchive, productName: productName }); + } + + function addArtifactFilePaths(dep, artifacts) { + if (!artifacts) + return; + var artifactFilePaths = artifacts.map(function(a) { return a.filePath; }); + var wholeArchive = dep.parameters.cpp && dep.parameters.cpp.linkWholeArchive; + var artifactsAreImportLibs = artifacts.length > 0 + && artifacts[0].fileTags.contains("dynamiclibrary_import"); + for (var i = 0; i < artifactFilePaths.length; ++i) { + addFilePath(artifactFilePaths[i], wholeArchive, + artifactsAreImportLibs ? dep.name : undefined); + } + } + + function addExternalLibs(obj) { + if (!obj.cpp) + return; + function ensureArray(a) { + return Array.isArray(a) ? a : []; + } + function sanitizedModuleListProperty(obj, moduleName, propertyName) { + return ensureArray(ModUtils.sanitizedModuleProperty(obj, moduleName, propertyName)); + } + var externalLibs = [].concat( + sanitizedModuleListProperty(obj, "cpp", "staticLibraries"), + sanitizedModuleListProperty(obj, "cpp", "dynamicLibraries")); + externalLibs.forEach(function (libName) { + if (!libName.match(/\.lib$/i) && !libName.startsWith('@')) + libName += ".lib"; + addFilePath(libName, false); + }); + } + + function traverse(dep) { + if (seen.hasOwnProperty(dep.name)) + return; + + if (dep.parameters.cpp && dep.parameters.cpp.link === false) + return; + + var staticLibraryArtifacts = dep.artifacts["staticlibrary"]; + var dynamicLibraryArtifacts = staticLibraryArtifacts + ? null : dep.artifacts["dynamiclibrary_import"]; + if (staticLibraryArtifacts) { + seen[dep.name] = true; + dep.dependencies.forEach(traverse); + addArtifactFilePaths(dep, staticLibraryArtifacts); + addExternalLibs(dep); + } else if (dynamicLibraryArtifacts) { + seen[dep.name] = true; + addArtifactFilePaths(dep, dynamicLibraryArtifacts); + } + } + + product.dependencies.forEach(traverse); + addExternalLibs(product); + return result; +} + +function linkerSupportsWholeArchive(product) +{ + return Utilities.versionCompare(product.cpp.compilerVersion, "19.0.24215.1") >= 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) + var primaryOutput = (linkDLL ? outputs.dynamiclibrary[0] : outputs.application[0]) + var debugInformation = product.cpp.debugInformation; + var additionalManifestInputs = Array.prototype.map.call(inputs["native.pe.manifest"], + function (a) { + return a.filePath; + }); + var moduleDefinitionInputs = Array.prototype.map.call(inputs["def"], + function (a) { + return a.filePath; + }); + var generateManifestFiles = !linkDLL && product.cpp.generateManifestFile; + var useClangCl = product.qbs.toolchain.contains("clang-cl"); + var canEmbedManifest = useClangCl || product.cpp.compilerVersionMajor >= 17 // VS 2012 + + var linkerPath = effectiveLinkerPath(product, inputs); + var useCompilerDriver = linkerPath === product.cpp.compilerPath; + // args variable is built as follows: + // [linkerWrapper] linkerPath /nologo [driverFlags driverLinkerFlags] + // allInputs libDeps [/link] linkerArgs + var args = [] + + if (useCompilerDriver) { + args.push('/nologo'); + var driverFlags = product.cpp.driverFlags; + if (driverFlags) + args = args.concat(driverFlags); + var driverLinkerFlags = product.cpp.driverLinkerFlags; + if (driverLinkerFlags) + args = args.concat(driverLinkerFlags); + } + + var allInputs = inputs.obj || []; + for (i in allInputs) { + var fileName = FileInfo.toWindowsSeparators(allInputs[i].filePath) + args.push(fileName) + } + + var linkerArgs = ['/nologo'] + if (linkDLL) { + linkerArgs.push('/DLL'); + linkerArgs.push('/IMPLIB:' + FileInfo.toWindowsSeparators(outputs.dynamiclibrary_import[0].filePath)); + } + + if (debugInformation) { + linkerArgs.push("/DEBUG"); + var debugInfo = outputs.debuginfo_app || outputs.debuginfo_dll; + if (debugInfo) + linkerArgs.push("/PDB:" + debugInfo[0].fileName); + } else { + linkerArgs.push('/INCREMENTAL:NO') + } + + switch (product.qbs.architecture) { + case "x86": + linkerArgs.push("/MACHINE:X86"); + break; + case "x86_64": + linkerArgs.push("/MACHINE:X64"); + break; + case "ia64": + linkerArgs.push("/MACHINE:IA64"); + break; + case "armv7": + linkerArgs.push("/MACHINE:ARM"); + break; + case "arm64": + linkerArgs.push("/MACHINE:ARM64"); + break; + } + + if (useCompilerDriver) + handleClangClArchitectureFlags(product, product.qbs.architecture, args); + + var requireAppContainer = product.cpp.requireAppContainer; + if (requireAppContainer !== undefined) + linkerArgs.push("/APPCONTAINER" + (requireAppContainer ? "" : ":NO")); + + var minimumWindowsVersion = product.cpp.minimumWindowsVersion; + var subsystemSwitch = undefined; + if (minimumWindowsVersion || product.consoleApplication !== undefined) { + // Ensure that we default to console if product.consoleApplication is undefined + // since that could still be the case if only minimumWindowsVersion had been defined + subsystemSwitch = product.consoleApplication === false ? '/SUBSYSTEM:WINDOWS' : '/SUBSYSTEM:CONSOLE'; + } + + var useLldLink = useCompilerDriver && product.cpp.linkerVariant === "lld" + || !useCompilerDriver && product.cpp.linkerName === "lld-link.exe"; + if (minimumWindowsVersion) { + var subsystemVersion = WindowsUtils.getWindowsVersionInFormat(minimumWindowsVersion, + 'subsystem'); + if (subsystemVersion) { + subsystemSwitch += ',' + subsystemVersion; + // llvm linker does not support /OSVERSION + if (!useLldLink) + linkerArgs.push('/OSVERSION:' + subsystemVersion); + } + } + + if (subsystemSwitch) + linkerArgs.push(subsystemSwitch); + + var linkerOutputNativeFilePath = FileInfo.toWindowsSeparators(primaryOutput.filePath); + var manifestFileNames = []; + if (generateManifestFiles) { + if (canEmbedManifest) { + linkerArgs.push("/MANIFEST:embed"); + additionalManifestInputs.forEach(function (manifestFileName) { + linkerArgs.push("/MANIFESTINPUT:" + manifestFileName); + }); + } else { + linkerOutputNativeFilePath + = FileInfo.toWindowsSeparators( + FileInfo.path(primaryOutput.filePath) + "/intermediate." + + primaryOutput.fileName); + + var manifestFileName = linkerOutputNativeFilePath + ".manifest"; + linkerArgs.push('/MANIFEST', '/MANIFESTFILE:' + manifestFileName); + manifestFileNames = [manifestFileName].concat(additionalManifestInputs); + } + } + + if (moduleDefinitionInputs.length === 1) + linkerArgs.push("/DEF:" + moduleDefinitionInputs[0]); + else if (moduleDefinitionInputs.length > 1) + throw new Error("Only one '.def' file can be specified for linking"); + + var wholeArchiveSupported = linkerSupportsWholeArchive(product); + var wholeArchiveRequested = false; + var libDeps = collectLibraryDependencies(product); + var prevLib; + for (i = 0; i < libDeps.length; ++i) { + var dep = libDeps[i]; + var lib = dep.filePath; + if (lib === prevLib) + continue; + prevLib = lib; + + if (wholeArchiveSupported && dep.wholeArchive) { + // need to pass libraries to the driver to avoid "no input files" error if no object + // files are specified; thus libraries are duplicated when using "WHOLEARCHIVE" + if (useCompilerDriver && allInputs.length === 0) + args.push(FileInfo.toWindowsSeparators(lib)); + linkerArgs.push("/WHOLEARCHIVE:" + FileInfo.toWindowsSeparators(lib)); + } else { + args.push(FileInfo.toWindowsSeparators(lib)); + } + if (dep.wholeArchive) + wholeArchiveRequested = true; + } + if (wholeArchiveRequested && !wholeArchiveSupported) { + console.warn("Product '" + product.name + "' sets cpp.linkWholeArchive on a dependency, " + + "but your linker does not support the /WHOLEARCHIVE option. " + + "Please upgrade to Visual Studio 2015 Update 2 or higher."); + } + + if (product.cpp.entryPoint) + linkerArgs.push("/ENTRY:" + product.cpp.entryPoint); + + if (outputs.application && product.cpp.generateLinkerMapFile) { + if (useLldLink) + linkerArgs.push("/lldmap:" + outputs.mem_map[0].filePath); + else + linkerArgs.push("/MAP:" + outputs.mem_map[0].filePath); + } + + if (useCompilerDriver) + args.push('/Fe' + linkerOutputNativeFilePath); + else + linkerArgs.push('/OUT:' + linkerOutputNativeFilePath); + var libraryPaths = product.cpp.libraryPaths; + if (libraryPaths) + libraryPaths = [].uniqueConcat(libraryPaths); + for (i in libraryPaths) { + linkerArgs.push('/LIBPATH:' + FileInfo.toWindowsSeparators(libraryPaths[i])) + } + handleDiscardProperty(product, linkerArgs); + var linkerFlags = product.cpp.platformLinkerFlags.concat(product.cpp.linkerFlags); + linkerArgs = linkerArgs.concat(linkerFlags); + if (product.cpp.allowUnresolvedSymbols) + linkerArgs.push("/FORCE:UNRESOLVED"); + + var wrapperArgs = product.cpp.linkerWrapper; + if (wrapperArgs && wrapperArgs.length > 0) { + linkerArgs.unshift(linkerPath); + linkerPath = wrapperArgs.shift(); + linkerArgs = wrapperArgs.concat(linkerArgs); + } + var commands = []; + var warningCmd = new JavaScriptCommand(); + warningCmd.silent = true; + warningCmd.libDeps = libDeps; + warningCmd.sourceCode = function() { + for (var i = 0; i < libDeps.length; ++i) { + if (!libDeps[i].productName || File.exists(libDeps[i].filePath)) + continue; + console.warn("Import library '" + FileInfo.toNativeSeparators(libDeps[i].filePath) + + "' does not exist. This typically happens when a DLL does not " + + "export any symbols. Please make sure the '" + libDeps[i].productName + + "' library exports symbols, or, if you do not intend to actually " + + "link against it, specify \"cpp.link: false\" in the Depends item."); + } + }; + commands.push(warningCmd); + + if (linkerArgs.length !== 0) + args = args.concat(useCompilerDriver ? ['/link'] : []).concat(linkerArgs); + + var cmd = new Command(linkerPath, args) + cmd.description = 'linking ' + primaryOutput.fileName; + cmd.highlight = 'linker'; + cmd.jobPool = "linker"; + cmd.relevantEnvironmentVariables = ["LINK", "_LINK_", "LIB", "TMP"]; + cmd.workingDirectory = FileInfo.path(primaryOutput.filePath) + cmd.responseFileUsagePrefix = '@'; + cmd.responseFileSeparator = useCompilerDriver ? ' ' : '\n'; + cmd.stdoutFilterFunction = function(output) { + res = output.replace(/^.*performing full link.*\s*/, ""); + res = res.replace(/^ *Creating library.*\s*/, ""); + res = res.replace(/^\s*Generating code\s*/, ""); + res = res.replace(/^\s*Finished generating code\s*/, ""); + return res; + }; + commands.push(cmd); + + if (generateManifestFiles && !canEmbedManifest) { + var outputNativeFilePath = FileInfo.toWindowsSeparators(primaryOutput.filePath); + cmd = new JavaScriptCommand(); + cmd.src = linkerOutputNativeFilePath; + cmd.dst = outputNativeFilePath; + cmd.sourceCode = function() { + File.copy(src, dst); + } + cmd.silent = true + commands.push(cmd); + + args = ['/nologo', '/manifest'].concat(manifestFileNames); + args.push("/outputresource:" + outputNativeFilePath + ";#" + (linkDLL ? "2" : "1")); + cmd = new Command("mt.exe", args) + cmd.description = 'embedding manifest into ' + primaryOutput.fileName; + cmd.highlight = 'linker'; + cmd.workingDirectory = FileInfo.path(primaryOutput.filePath) + commands.push(cmd); + } + + return commands; +} + +function createRcCommand(binary, input, output, logo) { + var platformDefines = input.cpp.platformDefines; + var defines = input.cpp.defines; + var includePaths = input.cpp.includePaths; + var systemIncludePaths = input.cpp.systemIncludePaths; + + var args = []; + if (logo === "can-suppress-logo") + args.push("/nologo"); + for (i in platformDefines) { + args.push("/d"); + args.push(platformDefines[i]); + } + for (i in defines) { + args.push("/d"); + args.push(defines[i]); + } + for (i in includePaths) { + args.push("/I"); + args.push(includePaths[i]); + } + for (i in systemIncludePaths) { + args.push("/I"); + args.push(systemIncludePaths[i]); + } + args = args.concat(["/FO", output.filePath, input.filePath]); + var cmd = new Command(binary, args); + cmd.description = 'compiling ' + input.fileName; + cmd.highlight = 'compiler'; + cmd.jobPool = "compiler"; + + if (logo === "always-shows-logo") { + // Remove the first two lines of stdout. That's the logo. + cmd.stdoutFilterFunction = function(output) { + var idx = 0; + for (var i = 0; i < 3; ++i) + idx = output.indexOf('\n', idx + 1); + return output.substr(idx + 1); + } + } + + return cmd; +} diff --git a/share/qbs/modules/cpp/qnx-qcc.qbs b/share/qbs/modules/cpp/qnx-qcc.qbs new file mode 100644 index 00000000..a39a6a99 --- /dev/null +++ b/share/qbs/modules/cpp/qnx-qcc.qbs @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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.FileInfo + +UnixGCC { + Depends { name: "qnx" } + + condition: qbs.targetOS.contains("qnx") && + qbs.toolchain && qbs.toolchain.contains("qcc") + priority: 1 + + distributionIncludePaths: FileInfo.joinPaths(qnx.targetDir, "usr", "include") + + toolchainInstallPath: FileInfo.joinPaths(qnx.hostDir, "usr", "bin") + + sysroot: qnx.targetDir + sysrootFlags: sysroot ? [systemIncludeFlag + FileInfo.joinPaths(sysroot, "usr", "include")] : [] + + cCompilerName: "qcc" + compilerExtension + cxxCompilerName: (qnx.qnx7 ? "q++" : "QCC") + compilerExtension + + targetDriverFlags: qnxTarget ? ["-V" + qnxTarget] : [] + + systemIncludeFlag: !qnx.qnx7 ? includeFlag : base + + property string qnxTarget: qbs.architecture + ? qnx.compilerName + "_" + targetSystem + qnxTargetArchName + : undefined + + property string qnxTargetArchName: { + switch (qbs.architecture) { + case "arm64": + return "aarch64le"; + case "armv7a": + return "armv7le"; + case "x86": + case "x86_64": + return qbs.architecture; + } + } + + // QNX doesn't support Objective-C or Objective-C++ and qcc/q++ don't use toolchainPrefix + compilerPath: FileInfo.joinPaths(toolchainInstallPath, compilerName) + compilerPathByLanguage: ({ + "c": FileInfo.joinPaths(toolchainInstallPath, cCompilerName), + "cpp": FileInfo.joinPaths(toolchainInstallPath, cxxCompilerName), + "objc": undefined, + "objcpp": undefined, + "asm_cpp": FileInfo.joinPaths(toolchainInstallPath, cCompilerName) + }) + + toolchainPrefix: target + "-" + + targetVendor: ["x86", "x86_64"].contains(qbs.architecture) ? "pc" : base + targetSystem: "nto" + targetAbi: "qnx" + qnx.version + (qnxTargetArchName === "armv7le" ? "eabi" : "") + + buildEnv: qnx.buildEnv + probeEnv: buildEnv + + setupBuildEnvironment: { + for (var key in product.cpp.buildEnv) { + v = new ModUtils.EnvironmentVariable(key); + v.value = product.cpp.buildEnv[key]; + v.set(); + } + } +} diff --git a/share/qbs/modules/cpp/sdcc.js b/share/qbs/modules/cpp/sdcc.js new file mode 100644 index 00000000..c34db1b2 --- /dev/null +++ b/share/qbs/modules/cpp/sdcc.js @@ -0,0 +1,631 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 Cpp = require("cpp.js"); +var Environment = require("qbs.Environment"); +var File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var ModUtils = require("qbs.ModUtils"); +var PathTools = require("qbs.PathTools"); +var Process = require("qbs.Process"); +var TemporaryDir = require("qbs.TemporaryDir"); +var TextFile = require("qbs.TextFile"); +var Utilities = require("qbs.Utilities"); +var WindowsUtils = require("qbs.WindowsUtils"); + +function compilerName(qbs) { + return "sdcc"; +} + +function assemblerName(qbs) { + switch (qbs.architecture) { + case "mcs51": + return "sdas8051"; + case "stm8": + return "sdasstm8"; + } + throw "Unable to deduce assembler name for unsupported architecture: '" + + qbs.architecture + "'"; +} + +function linkerName(qbs) { + switch (qbs.architecture) { + case "mcs51": + return "sdld"; + case "stm8": + return "sdldstm8"; + } + throw "Unable to deduce linker name for unsupported architecture: '" + + qbs.architecture + "'"; +} + +function archiverName(qbs) { + return "sdar"; +} + +function targetArchitectureFlag(architecture) { + if (architecture === "mcs51") + return "-mmcs51"; + if (architecture === "stm8") + return "-mstm8"; +} + +function guessArchitecture(macros) { + if (macros["__SDCC_mcs51"] === "1") + return "mcs51"; + if (macros["__SDCC_stm8"] === "1") + return "stm8"; +} + +function guessEndianness(macros) { + // SDCC stores numbers in little-endian format. + return "little"; +} + +function guessVersion(macros) { + if ("__SDCC_VERSION_MAJOR" in macros + && "__SDCC_VERSION_MINOR" in macros + && "__SDCC_VERSION_PATCH" in macros) { + return { major: parseInt(macros["__SDCC_VERSION_MAJOR"], 10), + minor: parseInt(macros["__SDCC_VERSION_MINOR"], 10), + patch: parseInt(macros["__SDCC_VERSION_PATCH"], 10), + found: macros["SDCC"] } + } else if ("__SDCC" in macros) { + var versions = macros["__SDCC"].split("_"); + if (versions.length === 3) { + return { + major: parseInt(versions[0], 10), + minor: parseInt(versions[1], 10), + patch: parseInt(versions[2], 10), + found: macros["SDCC"] }; + } + } + return { found: false }; +} + +function dumpMacros(compilerFilePath, architecture) { + var tempDir = new TemporaryDir(); + var fakeIn = new TextFile(tempDir.path() + "/empty-source.c", TextFile.WriteOnly); + var args = [ fakeIn.filePath(), "-dM", "-E" ]; + + var targetFlag = targetArchitectureFlag(architecture); + if (targetFlag) + args.push(targetFlag); + + var p = new Process(); + p.exec(compilerFilePath, args, true); + var map = {}; + p.readStdOut().trim().split(/\r?\n/g).map(function (line) { + var parts = line.split(" ", 3); + map[parts[1]] = parts[2]; + }); + return map; +} + +function dumpDefaultPaths(compilerFilePath, architecture) { + var args = [ "--print-search-dirs" ]; + + var targetFlag = targetArchitectureFlag(architecture); + if (targetFlag) + args.push(targetFlag); + + var p = new Process(); + p.exec(compilerFilePath, args, true); + var includePaths = []; + var addIncludePaths = false; + var lines = p.readStdOut().trim().split(/\r?\n/g); + for (var i in lines) { + var line = lines[i]; + if (line.startsWith("includedir:")) { + addIncludePaths = true; + } else if (line.startsWith("programs:") + || line.startsWith("datadir:") + || line.startsWith("libdir:") + || line.startsWith("libpath:")) { + addIncludePaths = false; + } else if (addIncludePaths) { + includePaths.push(line); + } + } + + return { + "includePaths": includePaths + } +} + +function effectiveLinkerPath(product) { + if (product.cpp.linkerMode === "automatic") + return product.cpp.compilerPath; + return product.cpp.linkerPath; +} + +function useCompilerDriverLinker(product) { + var linker = effectiveLinkerPath(product); + return linker === product.cpp.compilerPath; +} + +function escapeLinkerFlags(product, linkerFlags) { + if (!linkerFlags || linkerFlags.length === 0 || !useCompilerDriverLinker(product)) + return linkerFlags; + return ["-Wl " + linkerFlags.join(",")]; +} + +function escapePreprocessorFlags(preprocessorFlags) { + if (!preprocessorFlags || preprocessorFlags.length === 0) + return preprocessorFlags; + return ["-Wp " + preprocessorFlags.join(",")]; +} + +function collectLibraryDependencies(product) { + var seen = {}; + var result = []; + + function addFilePath(filePath) { + result.push({ filePath: filePath }); + } + + function addArtifactFilePaths(dep, artifacts) { + if (!artifacts) + return; + var artifactFilePaths = artifacts.map(function(a) { return a.filePath; }); + artifactFilePaths.forEach(addFilePath); + } + + function addExternalStaticLibs(obj) { + if (!obj.cpp) + return; + function ensureArray(a) { + return Array.isArray(a) ? a : []; + } + function sanitizedModuleListProperty(obj, moduleName, propertyName) { + return ensureArray(ModUtils.sanitizedModuleProperty(obj, moduleName, propertyName)); + } + var externalLibs = [].concat( + sanitizedModuleListProperty(obj, "cpp", "staticLibraries")); + var staticLibrarySuffix = obj.moduleProperty("cpp", "staticLibrarySuffix"); + externalLibs.forEach(function(staticLibraryName) { + if (!staticLibraryName.endsWith(staticLibrarySuffix)) + staticLibraryName += staticLibrarySuffix; + addFilePath(staticLibraryName); + }); + } + + function traverse(dep) { + if (seen.hasOwnProperty(dep.name)) + return; + seen[dep.name] = true; + + if (dep.parameters.cpp && dep.parameters.cpp.link === false) + return; + + var staticLibraryArtifacts = dep.artifacts["staticlibrary"]; + if (staticLibraryArtifacts) { + dep.dependencies.forEach(traverse); + addArtifactFilePaths(dep, staticLibraryArtifacts); + addExternalStaticLibs(dep); + } + } + + product.dependencies.forEach(traverse); + addExternalStaticLibs(product); + return result; +} + +function compilerOutputArtifacts(input, useListing) { + var obj = { + fileTags: ["obj"], + filePath: Utilities.getHash(input.baseDir) + "/" + + input.fileName + input.cpp.objectSuffix + }; + + // We need to use the asm_adb, asm_src, asm_sym and rst_data + // artifacts without of any conditions. Because SDCC always generates + // it (and seems, this behavior can not be disabled for SDCC). + var asm_adb = { + fileTags: ["asm_adb"], + filePath: Utilities.getHash(input.baseDir) + "/" + + input.fileName + ".adb" + }; + var asm_src = { + fileTags: ["asm_src"], + filePath: Utilities.getHash(input.baseDir) + "/" + + input.fileName + ".asm" + }; + var asm_sym = { + fileTags: ["asm_sym"], + filePath: Utilities.getHash(input.baseDir) + "/" + + input.fileName + ".sym" + }; + var rst_data = { + fileTags: ["rst_data"], + filePath: Utilities.getHash(input.baseDir) + "/" + + input.fileName + ".rst" + }; + var artifacts = [obj, asm_adb, asm_src, asm_sym, rst_data]; + if (useListing) { + artifacts.push({ + fileTags: ["lst"], + filePath: Utilities.getHash(input.baseDir) + "/" + + input.fileName + ".lst" + }); + } + return artifacts; +} + +function applicationLinkerOutputArtifacts(product) { + var app = { + fileTags: ["application"], + filePath: FileInfo.joinPaths( + product.destinationDirectory, + PathTools.applicationFilePath(product)) + }; + var lk_cmd = { + fileTags: ["lk_cmd"], + filePath: FileInfo.joinPaths( + product.destinationDirectory, + product.targetName + ".lk") + }; + var mem_summary = { + fileTags: ["mem_summary"], + filePath: FileInfo.joinPaths( + product.destinationDirectory, + product.targetName + ".mem") + }; + var mem_map = { + fileTags: ["mem_map"], + filePath: FileInfo.joinPaths( + product.destinationDirectory, + product.targetName + ".map") + }; + return [app, lk_cmd, mem_summary, mem_map] +} + +function staticLibraryLinkerOutputArtifacts(product) { + var staticLib = { + fileTags: ["staticlibrary"], + filePath: FileInfo.joinPaths( + product.destinationDirectory, + PathTools.staticLibraryFilePath(product)) + }; + return [staticLib] +} + +function compilerFlags(project, product, input, outputs, explicitlyDependsOn) { + // Determine which C-language we"re compiling. + var tag = ModUtils.fileTagForTargetLanguage(input.fileTags.concat(outputs.obj[0].fileTags)); + + var args = []; + var escapablePreprocessorFlags = []; + + // Input. + args.push(input.filePath); + + // Output. + args.push("-c"); + args.push("-o", outputs.obj[0].filePath); + + var prefixHeaders = input.cpp.prefixHeaders; + for (var i in prefixHeaders) + escapablePreprocessorFlags.push("-include " + prefixHeaders[i]); + + // Defines. + var allDefines = []; + var platformDefines = input.cpp.platformDefines; + if (platformDefines) + allDefines = allDefines.uniqueConcat(platformDefines); + var defines = input.cpp.defines; + if (defines) + allDefines = allDefines.uniqueConcat(defines); + args = args.concat(allDefines.map(function(define) { return "-D" + define })); + + // Includes. + var includePaths = input.cpp.includePaths; + if (includePaths) { + args = args.concat([].uniqueConcat(includePaths).map(function(path) { + return "-I" + path; })); + } + + var allSystemIncludePaths = []; + var systemIncludePaths = input.cpp.systemIncludePaths; + if (systemIncludePaths) + allSystemIncludePaths = allSystemIncludePaths.uniqueConcat(systemIncludePaths); + var distributionIncludePaths = input.cpp.distributionIncludePaths; + if (distributionIncludePaths) + allSystemIncludePaths = allSystemIncludePaths.uniqueConcat(distributionIncludePaths); + escapablePreprocessorFlags = escapablePreprocessorFlags.concat( + allSystemIncludePaths.map(function(include) { return "-isystem " + include })); + + var targetFlag = targetArchitectureFlag(input.cpp.architecture); + if (targetFlag) + args.push(targetFlag); + + // Debug information flags. + if (input.cpp.debugInformation) + args.push("--debug"); + + // Optimization level flags. + switch (input.cpp.optimization) { + case "small": + args.push("--opt-code-size"); + break; + case "fast": + args.push("--opt-code-speed"); + break; + case "none": + // SDCC has not option to disable the optimization. + break; + } + + // Warning level flags. + var warnings = input.cpp.warningLevel + if (warnings === "none") { + args.push("--less-pedantic"); + escapablePreprocessorFlags.push("-w"); + } else if (warnings === "all") { + escapablePreprocessorFlags.push("-Wall"); + } + if (input.cpp.treatWarningsAsErrors) + args.push("--Werror"); + + // C language version flags. + if (tag === "c") { + var knownValues = ["c11", "c99", "c89"]; + var cLanguageVersion = Cpp.languageVersion( + input.cpp.cLanguageVersion, knownValues, "C"); + switch (cLanguageVersion) { + case "c89": + args.push("--std-c89"); + break; + case "c99": + args.push("--std-c99"); + break; + case "c11": + args.push("--std-c11"); + break; + } + } + + // Misc flags. + var escapedPreprocessorFlags = escapePreprocessorFlags(escapablePreprocessorFlags); + if (escapedPreprocessorFlags) + Array.prototype.push.apply(args, escapedPreprocessorFlags); + + args = args.concat(ModUtils.moduleProperty(input, "platformFlags"), + ModUtils.moduleProperty(input, "flags"), + ModUtils.moduleProperty(input, "platformFlags", tag), + ModUtils.moduleProperty(input, "flags", tag), + ModUtils.moduleProperty(input, "driverFlags", tag)); + + return args; +} + +function assemblerFlags(project, product, input, outputs, explicitlyDependsOn) { + // Determine which C-language we"re compiling + var tag = ModUtils.fileTagForTargetLanguage(input.fileTags.concat(outputs.obj[0].fileTags)); + + var args = []; + + // Includes. + var includePaths = input.cpp.includePaths; + if (includePaths) { + args = args.concat([].uniqueConcat(includePaths).map(function(path) { + return "-I" + path; })); + } + + var allSystemIncludePaths = []; + var systemIncludePaths = input.cpp.systemIncludePaths; + if (systemIncludePaths) + allSystemIncludePaths = allSystemIncludePaths.uniqueConcat(systemIncludePaths); + var distributionIncludePaths = input.cpp.distributionIncludePaths; + if (distributionIncludePaths) + allSystemIncludePaths = allSystemIncludePaths.uniqueConcat(distributionIncludePaths); + args = args.concat(allSystemIncludePaths.map(function(include) { return "-I" + include })); + + // Misc flags. + args = args.concat(ModUtils.moduleProperty(input, "platformFlags", tag), + ModUtils.moduleProperty(input, "flags", tag)); + + args.push("-ol"); + args.push(outputs.obj[0].filePath); + args.push(input.filePath); + return args; +} + +function linkerFlags(project, product, inputs, outputs) { + var args = []; + + // Target MCU flag. + var targetFlag = targetArchitectureFlag(product.cpp.architecture); + if (targetFlag) + args.push(targetFlag); + + var allLibraryPaths = []; + var libraryPaths = product.cpp.libraryPaths; + if (libraryPaths) + allLibraryPaths = allLibraryPaths.uniqueConcat(libraryPaths); + var distributionLibraryPaths = product.cpp.distributionLibraryPaths; + if (distributionLibraryPaths) + allLibraryPaths = allLibraryPaths.uniqueConcat(distributionLibraryPaths); + + var libraryDependencies = collectLibraryDependencies(product); + + var escapableLinkerFlags = []; + + // Map file generation flag. + if (product.cpp.generateLinkerMapFile) + escapableLinkerFlags.push("-m"); + + if (product.cpp.platformLinkerFlags) + Array.prototype.push.apply(escapableLinkerFlags, product.cpp.platformLinkerFlags); + if (product.cpp.linkerFlags) + Array.prototype.push.apply(escapableLinkerFlags, product.cpp.linkerFlags); + + var useCompilerDriver = useCompilerDriverLinker(product); + if (useCompilerDriver) { + // Output. + args.push("-o", outputs.application[0].filePath); + + // Inputs. + if (inputs.obj) + args = args.concat(inputs.obj.map(function(obj) { return obj.filePath })); + + // Library paths. + args = args.concat(allLibraryPaths.map(function(path) { return "-L" + path })); + + // Linker scripts. + var scripts = inputs.linkerscript + ? inputs.linkerscript.map(function(scr) { return "-f" + scr.filePath; }) : []; + if (scripts) + Array.prototype.push.apply(escapableLinkerFlags, scripts); + } else { + // Output. + args.push(outputs.application[0].filePath); + + // Inputs. + if (inputs.obj) + args = args.concat(inputs.obj.map(function(obj) { return obj.filePath })); + + // Library paths. + args = args.concat(allLibraryPaths.map(function(path) { return "-k" + path })); + + // Linker scripts. + // Note: We need to split the '-f' and the file path to separate + // lines; otherwise the linking fails. + inputs.linkerscript.forEach(function(scr) { + escapableLinkerFlags.push("-f", scr.filePath); + }); + } + + // Library dependencies. + if (libraryDependencies) + args = args.concat(libraryDependencies.map(function(dep) { return "-l" + dep.filePath })); + + // Misc flags. + var escapedLinkerFlags = escapeLinkerFlags(product, escapableLinkerFlags); + if (escapedLinkerFlags) + Array.prototype.push.apply(args, escapedLinkerFlags); + var driverLinkerFlags = useCompilerDriver ? product.cpp.driverLinkerFlags : undefined; + if (driverLinkerFlags) + Array.prototype.push.apply(args, driverLinkerFlags); + return args; +} + +function archiverFlags(project, product, inputs, outputs) { + var args = ["-rc"]; + args.push(outputs.staticlibrary[0].filePath); + if (inputs.obj) + args = args.concat(inputs.obj.map(function(obj) { return obj.filePath })); + return args; +} + +function prepareCompiler(project, product, inputs, outputs, input, output, explicitlyDependsOn) { + var cmds = []; + var args = compilerFlags(project, product, input, outputs, explicitlyDependsOn); + var compilerPath = input.cpp.compilerPath; + var cmd = new Command(compilerPath, args); + cmd.description = "compiling " + input.fileName; + cmd.highlight = "compiler"; + cmds.push(cmd); + + // This is the workaround for the SDCC bug on a Windows host: + // * https://sourceforge.net/p/sdcc/bugs/2970/ + // We need to replace the '\r\n\' line endings with the'\n' line + // endings for each generated object file. + var isWindows = input.qbs.hostOS.contains("windows"); + if (isWindows) { + cmd = new JavaScriptCommand(); + cmd.objectPath = outputs.obj[0].filePath; + cmd.sourceCode = function() { + var lines = []; + var file = new TextFile(objectPath, TextFile.ReadWrite); + while (!file.atEof()) + lines.push(file.readLine() + "\n"); + file.truncate(); + for (var l in lines) + file.write(lines[l]); + }; + cmds.push(cmd); + } + + return cmds; +} + +function prepareAssembler(project, product, inputs, outputs, input, output, explicitlyDependsOn) { + var args = assemblerFlags(project, product, input, outputs, explicitlyDependsOn); + var assemblerPath = input.cpp.assemblerPath; + var cmd = new Command(assemblerPath, args); + cmd.description = "assembling " + input.fileName; + cmd.highlight = "compiler"; + return [cmd]; +} + +function prepareLinker(project, product, inputs, outputs, input, output) { + var cmds = []; + var primaryOutput = outputs.application[0]; + var args = linkerFlags(project, product, inputs, outputs); + var linkerPath = effectiveLinkerPath(product); + var cmd = new Command(linkerPath, args); + cmd.description = "linking " + primaryOutput.fileName; + cmd.highlight = "linker"; + cmds.push(cmd); + + // It is a workaround which removes the generated listing files + // if it is disabled by cpp.generateCompilerListingFiles property. + // Reason is that the SDCC compiler does not have an option to + // disable generation for a listing files. Besides, the SDCC + // compiler use this files and for the linking. So, we can to + // remove a listing files only after the linking completes. + if (!product.cpp.generateCompilerListingFiles) { + cmd = new JavaScriptCommand(); + cmd.objectPaths = inputs.obj.map(function(a) { return a.filePath; }); + cmd.objectSuffix = product.cpp.objectSuffix; + cmd.sourceCode = function() { + objectPaths.forEach(function(objectPath) { + if (!objectPath.endsWith(".c" + objectSuffix)) + return; // Skip the assembler objects. + var listingPath = FileInfo.joinPaths( + FileInfo.path(objectPath), + FileInfo.completeBaseName(objectPath) + ".lst"); + File.remove(listingPath); + }); + }; + cmds.push(cmd); + } + return cmds; +} + +function prepareArchiver(project, product, inputs, outputs, input, output) { + var args = archiverFlags(project, product, inputs, outputs); + var archiverPath = product.cpp.archiverPath; + var cmd = new Command(archiverPath, args); + cmd.description = "linking " + output.fileName; + cmd.highlight = "linker"; + return [cmd]; +} diff --git a/share/qbs/modules/cpp/sdcc.qbs b/share/qbs/modules/cpp/sdcc.qbs new file mode 100644 index 00000000..3c5be7cd --- /dev/null +++ b/share/qbs/modules/cpp/sdcc.qbs @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 1.0 +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Probes +import "sdcc.js" as SDCC + +CppModule { + condition: qbs.toolchain && qbs.toolchain.contains("sdcc") + + Probes.BinaryProbe { + id: compilerPathProbe + condition: !toolchainInstallPath && !_skipAllChecks + names: ["sdcc"] + } + + Probes.SdccProbe { + id: sdccProbe + condition: !_skipAllChecks + compilerFilePath: compilerPath + preferredArchitecture: qbs.architecture + } + + qbs.architecture: sdccProbe.found ? sdccProbe.architecture : original + qbs.targetPlatform: "none" + + compilerVersionMajor: sdccProbe.versionMajor + compilerVersionMinor: sdccProbe.versionMinor + compilerVersionPatch: sdccProbe.versionPatch + endianness: sdccProbe.endianness + + compilerDefinesByLanguage: sdccProbe.compilerDefinesByLanguage + compilerIncludePaths: sdccProbe.includePaths + + property string toolchainInstallPath: compilerPathProbe.found + ? compilerPathProbe.path : undefined + + property string compilerExtension: qbs.hostOS.contains("windows") ? ".exe" : "" + + /* Work-around for QtCreator which expects these properties to exist. */ + property string cCompilerName: compilerName + property string cxxCompilerName: compilerName + + property string linkerMode: "automatic" + + compilerName: SDCC.compilerName(qbs) + compilerExtension + compilerPath: FileInfo.joinPaths(toolchainInstallPath, compilerName) + + assemblerName: SDCC.assemblerName(qbs) + compilerExtension + assemblerPath: FileInfo.joinPaths(toolchainInstallPath, assemblerName) + + linkerName: SDCC.linkerName(qbs) + compilerExtension + linkerPath: FileInfo.joinPaths(toolchainInstallPath, linkerName) + + property string archiverName: SDCC.archiverName(qbs) + compilerExtension + property string archiverPath: FileInfo.joinPaths(toolchainInstallPath, archiverName) + + runtimeLibrary: "static" + + staticLibrarySuffix: ".lib" + executableSuffix: ".ihx" + + property string objectSuffix: ".rel" + + imageFormat: "ihx" + + enableExceptions: false + enableRtti: false + + Rule { + id: assembler + inputs: ["asm"] + outputFileTags: ["obj", "asm_adb", "lst", "asm_src", "asm_sym", "rst_data"] + outputArtifacts: SDCC.compilerOutputArtifacts(input, true) + prepare: SDCC.prepareAssembler.apply(SDCC, arguments) + } + + FileTagger { + patterns: ["*.s", "*.a51", "*.asm"] + fileTags: ["asm"] + } + + Rule { + id: compiler + inputs: ["cpp", "c"] + auxiliaryInputs: ["hpp"] + outputFileTags: ["obj", "asm_adb", "lst", "asm_src", "asm_sym", "rst_data"] + outputArtifacts: SDCC.compilerOutputArtifacts( + input, input.cpp.generateCompilerListingFiles) + prepare: SDCC.prepareCompiler.apply(SDCC, arguments) + } + + Rule { + id: applicationLinker + multiplex: true + inputs: ["obj", "linkerscript"] + inputsFromDependencies: ["staticlibrary"] + outputFileTags: ["application", "lk_cmd", "mem_summary", "mem_map"] + outputArtifacts: SDCC.applicationLinkerOutputArtifacts(product) + prepare: SDCC.prepareLinker.apply(SDCC, arguments) + } + + Rule { + id: staticLibraryLinker + multiplex: true + inputs: ["obj"] + inputsFromDependencies: ["staticlibrary"] + outputFileTags: ["staticlibrary"] + outputArtifacts: SDCC.staticLibraryLinkerOutputArtifacts(product) + prepare: SDCC.prepareArchiver.apply(SDCC, arguments) + } +} diff --git a/share/qbs/modules/cpp/setuprunenv.js b/share/qbs/modules/cpp/setuprunenv.js new file mode 100644 index 00000000..550d08d6 --- /dev/null +++ b/share/qbs/modules/cpp/setuprunenv.js @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** 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 File = require("qbs.File"); +var ModUtils = require("qbs.ModUtils"); // TODO: append/prepend functionality should go to qbs.Environment + +function addNewElement(list, elem) +{ + if (!list.contains(elem)) + list.push(elem); +} + +function artifactDir(artifact) +{ + if (!artifact.qbs.install) + return FileInfo.path(artifact.filePath); + return FileInfo.path(ModUtils.artifactInstalledFilePath(artifact)); +} + +function addExternalLibPath(product, list, path) +{ + addNewElement(list, path); + if (product.qbs.hostOS.contains("windows") && FileInfo.fileName(path) === "lib") { + var binPath = FileInfo.joinPaths(FileInfo.path(path), "bin"); + if (File.exists(binPath)) + addNewElement(list, binPath); + } +} + +function gatherPaths(product, libPaths, frameworkPaths, seenProducts) +{ + if (seenProducts.contains(product.name)) + return; + seenProducts.push(product.name); + + // Gather explicitly given library paths. + if (product.cpp && product.cpp.libraryPaths) + product.cpp.libraryPaths.forEach(function(p) { addExternalLibPath(product, libPaths, p); }); + if (product.cpp && product.cpp.frameworkPaths) + product.cpp.frameworkPaths.forEach(function(p) { addNewElement(frameworkPaths, p); }); + + // Extract paths from dynamic libraries, if they are given as file paths. + if (product.cpp && product.cpp.dynamicLibraries) { + product.cpp.dynamicLibraries.forEach(function(dll) { + if (FileInfo.isAbsolutePath(dll)) + addExternalLibPath(product, libPaths, FileInfo.path(dll)); + }); + } + + // Traverse library dependencies. + for (var i = 0; i < product.dependencies.length; ++i) { + var dep = product.dependencies[i]; + var dllSymlinkArtifacts = dep.artifacts["bundle.symlink.executable"]; + if (dllSymlinkArtifacts) { + var addArtifact = function(artifact) { + addNewElement(frameworkPaths, FileInfo.path(artifactDir(artifact))); + }; + dllSymlinkArtifacts.forEach(addArtifact); // TODO: Will also catch applications. Can we prevent that? + } else { + addArtifact = function(artifact) { + addNewElement(libPaths, artifactDir(artifact)); + }; + var dllArtifacts = dep.artifacts["dynamiclibrary"]; + if (dllArtifacts) + dllArtifacts.forEach(addArtifact); + var loadableModuleArtifacts = dep.artifacts["loadablemodule"]; + if (loadableModuleArtifacts) + loadableModuleArtifacts.forEach(addArtifact); + } + if (!dep.hasOwnProperty("present")) // Recurse if the dependency is a product. TODO: Provide non-heuristic way to decide whether dependency is a product. + gatherPaths(dep, libPaths, frameworkPaths, seenProducts); + } +} + + +function setupRunEnvironment(product, config) +{ + if (config.contains("ignore-lib-dependencies")) + return; + + if (product.qbs.hostPlatform !== product.qbs.targetPlatform) + return; + + var libPaths = []; + var frameworkPaths = []; + gatherPaths(product, libPaths, frameworkPaths, []); + + var runPaths = product.cpp ? product.cpp.systemRunPaths : undefined; + if (runPaths && runPaths.length > 0) { + var canonicalRunPaths = runPaths.map(function(p) { return File.canonicalFilePath(p); }); + var filterFunc = function(libPath) { + return !runPaths.contains(libPath) + && !canonicalRunPaths.contains(File.canonicalFilePath(libPath)); + }; + libPaths = libPaths.filter(filterFunc); + frameworkPaths = frameworkPaths.filter(filterFunc); + } + + if (libPaths.length > 0) { + var envVarName; + if (product.qbs.targetOS.contains("windows")) + envVarName = "PATH"; + else if (product.qbs.targetOS.contains("macos")) + envVarName = "DYLD_LIBRARY_PATH"; + else + envVarName = "LD_LIBRARY_PATH"; + var envVar = new ModUtils.EnvironmentVariable(envVarName, product.qbs.pathListSeparator, + product.qbs.hostOS.contains("windows")); + libPaths.forEach(function(p) { envVar.prepend(p); }); + envVar.set(); + } + + if (product.qbs.targetOS.contains("macos") && frameworkPaths.length > 0) { + envVar = new ModUtils.EnvironmentVariable("DYLD_FRAMEWORK_PATH", ':', false); + frameworkPaths.forEach(function(p) { envVar.prepend(p); }); + envVar.set(); + } +} diff --git a/share/qbs/modules/cpp/tvos-gcc.qbs b/share/qbs/modules/cpp/tvos-gcc.qbs new file mode 100644 index 00000000..19bc9b78 --- /dev/null +++ b/share/qbs/modules/cpp/tvos-gcc.qbs @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +DarwinGCC { + priority: 1 + condition: qbs.targetOS.contains('tvos') && + qbs.toolchain && qbs.toolchain.contains('gcc') + + targetSystem: "tvos" + (minimumTvosVersion || "") + + minimumDarwinVersion: minimumTvosVersion + minimumDarwinVersionCompilerFlag: qbs.targetOS.contains("tvos-simulator") + ? "-mtvos-simulator-version-min" + : "-mtvos-version-min" + minimumDarwinVersionLinkerFlag: qbs.targetOS.contains("tvos-simulator") + ? "-tvos_simulator_version_min" + : "-tvos_version_min" +} diff --git a/share/qbs/modules/cpp/watchos-gcc.qbs b/share/qbs/modules/cpp/watchos-gcc.qbs new file mode 100644 index 00000000..46e4bf87 --- /dev/null +++ b/share/qbs/modules/cpp/watchos-gcc.qbs @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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. +** +****************************************************************************/ + +DarwinGCC { + priority: 1 + condition: qbs.targetOS.contains('watchos') && + qbs.toolchain && qbs.toolchain.contains('gcc') + + targetSystem: "watchos" + (minimumWatchosVersion || "") + + minimumDarwinVersion: minimumWatchosVersion + minimumDarwinVersionCompilerFlag: qbs.targetOS.contains("watchos-simulator") + ? "-mwatchos-simulator-version-min" + : "-mwatchos-version-min" + minimumDarwinVersionLinkerFlag: qbs.targetOS.contains("watchos-simulator") + ? "-watchos_simulator_version_min" + : "-watchos_version_min" +} diff --git a/share/qbs/modules/cpp/windows-clang-cl.qbs b/share/qbs/modules/cpp/windows-clang-cl.qbs new file mode 100644 index 00000000..a34a67ad --- /dev/null +++ b/share/qbs/modules/cpp/windows-clang-cl.qbs @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +** 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.ModUtils +import qbs.Probes +import qbs.FileInfo +import 'windows-msvc-base.qbs' as MsvcBaseModule + +MsvcBaseModule { + condition: qbs.hostOS.contains('windows') && + qbs.targetOS.contains('windows') && + qbs.toolchain && qbs.toolchain.contains('clang-cl') + priority: 100 + + Probes.ClangClBinaryProbe { + id: clangPathProbe + condition: !toolchainInstallPath && !_skipAllChecks + names: ["clang-cl"] + } + + Probes.ClangClProbe { + id: clangClProbe + condition: !_skipAllChecks + compilerFilePath: compilerPath + vcvarsallFilePath: vcvarsallPath + enableDefinesByLanguage: enableCompilerDefinesByLanguage + preferredArchitecture: qbs.architecture + } + + qbs.architecture: clangClProbe.found ? clangClProbe.architecture : original + + compilerVersionMajor: clangClProbe.versionMajor + compilerVersionMinor: clangClProbe.versionMinor + compilerVersionPatch: clangClProbe.versionPatch + compilerIncludePaths: clangClProbe.includePaths + compilerDefinesByLanguage: clangClProbe.compilerDefinesByLanguage + + toolchainInstallPath: clangPathProbe.found ? clangPathProbe.path + : undefined + buildEnv: clangClProbe.buildEnv + + property string linkerVariant + PropertyOptions { + name: "linkerVariant" + allowedValues: ["lld", "link"] + description: "Allows to specify the linker variant. Maps to clang-cl's -fuse-ld option." + } + Properties { + condition: linkerVariant + driverLinkerFlags: "-fuse-ld=" + linkerVariant + } + + property string vcvarsallPath : clangPathProbe.found ? clangPathProbe.vcvarsallPath : undefined + + compilerName: "clang-cl.exe" + linkerName: "lld-link.exe" + linkerPath: FileInfo.joinPaths(toolchainInstallPath, linkerName) + + validateFunc: { + return function() { + if (_skipAllChecks) + return; + var validator = new ModUtils.PropertyValidator("cpp"); + validator.setRequiredProperty("vcvarsallPath", vcvarsallPath); + validator.validate(); + base(); + } + } +} diff --git a/share/qbs/modules/cpp/windows-clang-mingw.qbs b/share/qbs/modules/cpp/windows-clang-mingw.qbs new file mode 100644 index 00000000..8389dbf2 --- /dev/null +++ b/share/qbs/modules/cpp/windows-clang-mingw.qbs @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Utilities +import "msvc.js" as MSVC + +import "setuprunenv.js" as SetupRunEnv + +MingwBaseModule { + condition: qbs.targetOS.contains("windows") && + qbs.toolchain && qbs.toolchain.contains("clang") + priority: 0 + + // llvm-as and llvm-objopy are not shipped with the official binaries on Windows at the + // moment (8.0). We fall back to using the mingw versions in that case. + assemblerName: "llvm-as" + compilerExtension + assemblerPath: { + if (File.exists(base)) + return base; + if (qbs.sysroot) + return FileInfo.joinPaths(qbs.sysroot, "bin", "as" + compilerExtension); + } + objcopyName: "llvm-objcopy" + compilerExtension + objcopyPath: { + if (File.exists(base)) + return base; + if (qbs.sysroot) + return FileInfo.joinPaths(qbs.sysroot, "bin", "objcopy" + compilerExtension); + } + + archiverName: "llvm-ar" + compilerExtension + + linkerVariant: "lld" + targetVendor: "pc" + targetSystem: "windows" + targetAbi: "gnu" + property string rcFilePath: FileInfo.joinPaths(toolchainInstallPath, + "llvm-rc" + compilerExtension) + + setupBuildEnvironment: { + if (product.qbs.hostOS.contains("windows") && product.qbs.sysroot) { + var v = new ModUtils.EnvironmentVariable("PATH", product.qbs.pathListSeparator, true); + v.prepend(FileInfo.joinPaths(product.qbs.sysroot, "bin")); + v.set(); + } + } + + setupRunEnvironment: { + if (product.qbs.hostOS.contains("windows") && product.qbs.sysroot) { + var v = new ModUtils.EnvironmentVariable("PATH", product.qbs.pathListSeparator, true); + v.prepend(FileInfo.joinPaths(product.qbs.sysroot, "bin")); + v.set(); + SetupRunEnv.setupRunEnvironment(product, config); + } + } + + Rule { + inputs: ["rc"] + auxiliaryInputs: ["hpp"] + + Artifact { + filePath: Utilities.getHash(input.baseDir) + "/" + input.completeBaseName + ".res" + fileTags: ["obj"] + } + + prepare: MSVC.createRcCommand(product.cpp.rcFilePath, input, output); + } +} diff --git a/share/qbs/modules/cpp/windows-mingw.qbs b/share/qbs/modules/cpp/windows-mingw.qbs new file mode 100644 index 00000000..ffed76cd --- /dev/null +++ b/share/qbs/modules/cpp/windows-mingw.qbs @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Utilities + +import "setuprunenv.js" as SetupRunEnv + +MingwBaseModule { + condition: qbs.targetOS.contains("windows") && + qbs.toolchain && qbs.toolchain.contains("mingw") + priority: 0 + + probeEnv: buildEnv + + property string windresName: "windres" + compilerExtension + property path windresPath: { + var filePath = toolchainPathPrefix + windresName; + if (!File.exists(filePath)) + filePath = FileInfo.joinPaths(toolchainInstallPath, windresName); + return filePath; + } + + setupBuildEnvironment: { + var v = new ModUtils.EnvironmentVariable("PATH", product.qbs.pathListSeparator, true); + v.prepend(product.cpp.toolchainInstallPath); + v.set(); + } + + setupRunEnvironment: { + var v = new ModUtils.EnvironmentVariable("PATH", product.qbs.pathListSeparator, true); + v.prepend(product.cpp.toolchainInstallPath); + v.set(); + SetupRunEnv.setupRunEnvironment(product, config); + } + + Rule { + inputs: ["rc"] + auxiliaryInputs: ["hpp"] + + Artifact { + filePath: Utilities.getHash(input.baseDir) + "/" + input.completeBaseName + "_res.o" + fileTags: ["obj"] + } + + prepare: { + var platformDefines = input.cpp.platformDefines; + var defines = input.cpp.defines; + var includePaths = input.cpp.includePaths; + var systemIncludePaths = input.cpp.systemIncludePaths; + var args = []; + var i; + for (i in platformDefines) { + args.push('-D'); + args.push(platformDefines[i]); + } + for (i in defines) { + args.push('-D'); + args.push(defines[i]); + } + for (i in includePaths) { + args.push('-I'); + args.push(includePaths[i]); + } + for (i in systemIncludePaths) { + args.push('-I'); + args.push(systemIncludePaths[i]); + } + + args = args.concat(['-i', input.filePath, '-o', output.filePath]); + var cmd = new Command(product.cpp.windresPath, args); + cmd.description = 'compiling ' + input.fileName; + cmd.highlight = 'compiler'; + return cmd; + } + } +} + diff --git a/share/qbs/modules/cpp/windows-msvc-base.qbs b/share/qbs/modules/cpp/windows-msvc-base.qbs new file mode 100644 index 00000000..e88c3f15 --- /dev/null +++ b/share/qbs/modules/cpp/windows-msvc-base.qbs @@ -0,0 +1,353 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.PathTools +import qbs.Probes +import qbs.Utilities +import qbs.WindowsUtils +import 'msvc.js' as MSVC + +CppModule { + condition: false + + windowsApiCharacterSet: "unicode" + 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; + } + warningLevel: "default" + compilerName: "cl.exe" + compilerPath: FileInfo.joinPaths(toolchainInstallPath, compilerName) + assemblerName: { + switch (qbs.architecture) { + case "armv7": + return "armasm.exe"; + case "arm64": + return "armasm64.exe"; + case "ia64": + return "ias.exe"; + case "x86": + return "ml.exe"; + case "x86_64": + return "ml64.exe"; + } + } + + linkerName: "link.exe" + runtimeLibrary: "dynamic" + separateDebugInformation: true + + property bool generateManifestFile: true + property string toolchainInstallPath + + architecture: qbs.architecture + endianness: "little" + staticLibrarySuffix: ".lib" + dynamicLibrarySuffix: ".dll" + executableSuffix: ".exe" + debugInfoSuffix: ".pdb" + 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 + + setupBuildEnvironment: { + for (var key in product.cpp.buildEnv) { + var v = new ModUtils.EnvironmentVariable(key, ';'); + v.prepend(product.cpp.buildEnv[key]); + v.set(); + } + } + + Rule { + condition: useCPrecompiledHeader + inputs: ["c_pch_src"] + auxiliaryInputs: ["hpp"] + Artifact { + fileTags: ['obj'] + filePath: Utilities.getHash(input.completeBaseName) + '_c.obj' + } + Artifact { + fileTags: ['c_pch'] + filePath: product.name + '_c.pch' + } + prepare: { + return MSVC.prepareCompiler.apply(MSVC, arguments); + } + } + + Rule { + condition: useCxxPrecompiledHeader + inputs: ["cpp_pch_src"] + explicitlyDependsOn: ["c_pch"] // to prevent vc--0.pdb conflict + auxiliaryInputs: ["hpp"] + Artifact { + fileTags: ['obj'] + filePath: Utilities.getHash(input.completeBaseName) + '_cpp.obj' + } + Artifact { + fileTags: ['cpp_pch'] + filePath: product.name + '_cpp.pch' + } + prepare: { + return MSVC.prepareCompiler.apply(MSVC, arguments); + } + } + + Rule { + name: "compiler" + inputs: ["cpp", "c"] + auxiliaryInputs: ["hpp"] + explicitlyDependsOn: ["c_pch", "cpp_pch"] + + outputFileTags: ["obj", "intermediate_obj", "lst"] + outputArtifacts: { + var tags = input.fileTags.contains("cpp_intermediate_object") + ? ["intermediate_obj"] + : ["obj"]; + var artifacts = []; + artifacts.push({ + fileTags: tags, + filePath: Utilities.getHash(input.baseDir) + "/" + input.fileName + ".obj" + }); + if (input.cpp.generateCompilerListingFiles) { + artifacts.push({ + fileTags: ["lst"], + filePath: Utilities.getHash(input.baseDir) + "/" + input.fileName + ".lst" + }); + } + return artifacts; + } + + prepare: { + return MSVC.prepareCompiler.apply(MSVC, arguments); + } + } + + FileTagger { + patterns: ["*.manifest"] + fileTags: ["native.pe.manifest"] + } + + FileTagger { + patterns: ["*.def"] + fileTags: ["def"] + } + + Rule { + name: "applicationLinker" + multiplex: true + inputs: ['obj', 'native.pe.manifest', 'def'] + inputsFromDependencies: ['staticlibrary', 'dynamiclibrary_import', "debuginfo_app"] + + outputFileTags: ["application", "debuginfo_app", "mem_map"] + outputArtifacts: { + var app = { + fileTags: ["application"], + filePath: FileInfo.joinPaths( + product.destinationDirectory, + PathTools.applicationFilePath(product)) + }; + var artifacts = [app]; + if (product.cpp.debugInformation && product.cpp.separateDebugInformation) { + artifacts.push({ + fileTags: ["debuginfo_app"], + filePath: app.filePath.substr(0, app.filePath.length - 4) + + product.cpp.debugInfoSuffix + }); + } + if (product.cpp.generateLinkerMapFile) { + artifacts.push({ + fileTags: ["mem_map"], + filePath: FileInfo.joinPaths( + product.destinationDirectory, + product.targetName + ".map") + }); + } + return artifacts; + } + + prepare: { + return MSVC.prepareLinker.apply(MSVC, arguments); + } + } + + Rule { + name: "dynamicLibraryLinker" + multiplex: true + inputs: ['obj', 'native.pe.manifest', 'def'] + inputsFromDependencies: ['staticlibrary', 'dynamiclibrary_import', "debuginfo_dll"] + + outputFileTags: ["dynamiclibrary", "dynamiclibrary_import", "debuginfo_dll"] + outputArtifacts: { + var artifacts = [ + { + fileTags: ["dynamiclibrary"], + filePath: product.destinationDirectory + "/" + PathTools.dynamicLibraryFilePath(product) + }, + { + fileTags: ["dynamiclibrary_import"], + filePath: product.destinationDirectory + "/" + PathTools.importLibraryFilePath(product), + alwaysUpdated: false + } + ]; + if (product.cpp.debugInformation && product.cpp.separateDebugInformation) { + var lib = artifacts[0]; + artifacts.push({ + fileTags: ["debuginfo_dll"], + filePath: lib.filePath.substr(0, lib.filePath.length - 4) + + product.cpp.debugInfoSuffix + }); + } + return artifacts; + } + + prepare: { + return MSVC.prepareLinker.apply(MSVC, arguments); + } + } + + Rule { + name: "libtool" + multiplex: true + inputs: ["obj"] + inputsFromDependencies: ["staticlibrary", "dynamiclibrary_import"] + outputFileTags: ["staticlibrary", "debuginfo_cl"] + outputArtifacts: { + var artifacts = [ + { + fileTags: ["staticlibrary"], + filePath: FileInfo.joinPaths(product.destinationDirectory, + PathTools.staticLibraryFilePath(product)) + } + ]; + if (product.cpp.debugInformation && product.cpp.separateDebugInformation) { + artifacts.push({ + fileTags: ["debuginfo_cl"], + filePath: product.targetName + ".cl" + product.cpp.debugInfoSuffix + }); + } + return artifacts; + } + prepare: { + var args = ['/nologo'] + var lib = outputs["staticlibrary"][0]; + var nativeOutputFileName = FileInfo.toWindowsSeparators(lib.filePath) + args.push('/OUT:' + nativeOutputFileName) + for (var i in inputs.obj) { + var fileName = FileInfo.toWindowsSeparators(inputs.obj[i].filePath) + args.push(fileName) + } + var cmd = new Command("lib.exe", args); + cmd.description = 'creating ' + lib.fileName; + cmd.highlight = 'linker'; + cmd.jobPool = "linker"; + cmd.workingDirectory = FileInfo.path(lib.filePath) + cmd.responseFileUsagePrefix = '@'; + return cmd; + } + } + + FileTagger { + patterns: ["*.rc"] + fileTags: ["rc"] + } + + Rule { + inputs: ["rc"] + auxiliaryInputs: ["hpp"] + + Artifact { + filePath: Utilities.getHash(input.baseDir) + "/" + input.completeBaseName + ".res" + fileTags: ["obj"] + } + + prepare: { + // From MSVC 2010 on, the logo can be suppressed. + var logo = product.cpp.compilerVersionMajor >= 16 + ? "can-suppress-logo" : "always-shows-logo"; + return MSVC.createRcCommand("rc", input, output, logo); + } + } + + FileTagger { + patterns: "*.asm" + fileTags: ["asm"] + } + + Rule { + inputs: ["asm"] + Artifact { + filePath: Utilities.getHash(input.baseDir) + "/" + input.completeBaseName + ".obj" + fileTags: ["obj"] + } + prepare: { + var args = ["/nologo", "/c", + "/Fo" + FileInfo.toWindowsSeparators(output.filePath), + FileInfo.toWindowsSeparators(input.filePath)]; + if (product.cpp.debugInformation) + args.push("/Zi"); + args = args.concat(ModUtils.moduleProperty(input, 'platformFlags', 'asm'), + ModUtils.moduleProperty(input, 'flags', 'asm')); + var cmd = new Command(product.cpp.assemblerPath, args); + cmd.description = "assembling " + input.fileName; + cmd.jobPool = "assembler"; + cmd.inputFileName = input.fileName; + cmd.stdoutFilterFunction = function(output) { + var lines = output.split("\r\n").filter(function (s) { + return !s.endsWith(inputFileName); }); + return lines.join("\r\n"); + }; + return cmd; + } + } +} diff --git a/share/qbs/modules/cpp/windows-msvc.qbs b/share/qbs/modules/cpp/windows-msvc.qbs new file mode 100644 index 00000000..d5b5baf9 --- /dev/null +++ b/share/qbs/modules/cpp/windows-msvc.qbs @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +** 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.Probes +import "windows-msvc-base.qbs" as MsvcBaseModule + +MsvcBaseModule { + condition: qbs.hostOS.contains('windows') && + qbs.targetOS.contains('windows') && + qbs.toolchain && qbs.toolchain.contains('msvc') + priority: 50 + + Probes.ClBinaryProbe { + id: compilerPathProbe + preferredArchitecture: qbs.architecture + condition: !toolchainInstallPath && !_skipAllChecks + names: ["cl"] + } + + Probes.MsvcProbe { + id: msvcProbe + condition: !_skipAllChecks + compilerFilePath: compilerPath + enableDefinesByLanguage: enableCompilerDefinesByLanguage + preferredArchitecture: qbs.architecture + } + + qbs.architecture: msvcProbe.found ? msvcProbe.architecture : original + + compilerVersionMajor: msvcProbe.versionMajor + compilerVersionMinor: msvcProbe.versionMinor + compilerVersionPatch: msvcProbe.versionPatch + compilerIncludePaths: msvcProbe.includePaths + compilerDefinesByLanguage: msvcProbe.compilerDefinesByLanguage + + toolchainInstallPath: compilerPathProbe.found ? compilerPathProbe.path + : undefined + buildEnv: msvcProbe.buildEnv +} diff --git a/share/qbs/modules/cpufeatures/cpufeatures.qbs b/share/qbs/modules/cpufeatures/cpufeatures.qbs new file mode 100644 index 00000000..17e6b851 --- /dev/null +++ b/share/qbs/modules/cpufeatures/cpufeatures.qbs @@ -0,0 +1,23 @@ +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/dmg/DMGModule.qbs b/share/qbs/modules/dmg/DMGModule.qbs new file mode 100644 index 00000000..c5d097a0 --- /dev/null +++ b/share/qbs/modules/dmg/DMGModule.qbs @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** 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.DarwinTools +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Process +import qbs.TextFile +import "dmg.js" as Dmg + +Module { + Depends { name: "xcode"; required: false } + + condition: qbs.hostOS.contains("darwin") && qbs.targetOS.contains("darwin") + + property string volumeName: product.targetName + PropertyOptions { + name: "volumeName" + description: "the name of the disk image (displayed in Finder when mounted)" + } + + property bool badgeVolumeIcon: false + PropertyOptions { + name: "badgeVolumeIcon" + description: "whether to render the user-supplied icon on top of the " + + "default volume icon instead of using it directly" + } + + property string format: "UDBZ" + PropertyOptions { + name: "format" + description: "the format to create the disk image in" + } + + property int compressionLevel: qbs.buildVariant === "release" ? 9 : undefined + PropertyOptions { + name: "compressionLevel" + description: "sets the zlib or bzip2 compression level for UDZO and UDBZ disk images" + } + + property string textutilPath: "/usr/bin/textutil" + property string hdiutilPath: "/usr/bin/hdiutil" + property string dmgSuffix: ".dmg" + + property string sourceBase + + readonly property string pythonPath: File.canonicalFilePath(FileInfo.joinPaths(path, + "..", "..", + "python")) + + property string backgroundColor + property int iconSize: 128 + property int windowX: 100 + property int windowY: 100 + property int windowWidth: 640 + property int windowHeight: 480 + property var iconPositions + + property int iconX: windowWidth / 2 + property int iconY: windowHeight / 2 + + property string defaultLicenseLocale + property string licenseLocale + property string licenseLanguageName + property string licenseAgreeButtonText + property string licenseDisagreeButtonText + property string licensePrintButtonText + property string licenseSaveButtonText + property string licenseInstructionText + + FileTagger { + patterns: [ + "*.txt", "*.rtf", "*.html", "*.doc", "*.docx", "*.odt", "*.xml", "*.webarchive", + "LICENSE" + ] + fileTags: ["dmg.license.input"] + } + + FileTagger { + patterns: ["*.icns"] + fileTags: ["icns"] + } + + FileTagger { + patterns: ["*.tif", "*.tiff"] + fileTags: ["tiff"] + } + + Rule { + inputs: ["dmg.license.input"] + + outputFileTags: ["dmg.license"] + outputArtifacts: ([{ + filePath: FileInfo.joinPaths(product.destinationDirectory, "licenses", + FileInfo.relativePath(product.sourceDirectory, + input.filePath) + ".rtf"), + fileTags: ["dmg.license"], + dmg: { + licenseLocale: input.dmg.licenseLocale, + licenseLanguageName: input.dmg.licenseLanguageName, + licenseAgreeButtonText: input.dmg.licenseAgreeButtonText, + licenseDisagreeButtonText: input.dmg.licenseDisagreeButtonText, + licensePrintButtonText: input.dmg.licensePrintButtonText, + licenseSaveButtonText: input.dmg.licenseSaveButtonText, + licenseInstructionText: input.dmg.licenseInstructionText + } + }]) + + prepare: Dmg.prepareLicense.apply(Dmg, arguments) + } + + Rule { + multiplex: true + inputs: ["dmg.input", "dmg.license", "icns", "tiff"] + + Artifact { + fileTags: ["dmg.dmg"] + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + product.dmg.dmgSuffix) + } + + prepare: Dmg.prepareDmg.apply(Dmg, arguments) + } +} diff --git a/share/qbs/modules/dmg/dmg.js b/share/qbs/modules/dmg/dmg.js new file mode 100644 index 00000000..4d972db9 --- /dev/null +++ b/share/qbs/modules/dmg/dmg.js @@ -0,0 +1,212 @@ +/**************************************************************************** +** +** 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 DarwinTools = require("qbs.DarwinTools"); +var FileInfo = require("qbs.FileInfo"); +var TextFile = require("qbs.TextFile"); +var Utilities = require("qbs.Utilities"); + +function localizationFromArtifact(input) { + var locale = input.dmg.licenseLocale || DarwinTools.localizationKey(input.filePath); + if (!locale) + throw("Could not determine localization for license file: " + input.filePath); + return locale; +} + +function dmgbuildSettings(product, inputs) { + var backgroundImages = inputs["tiff"]; + var backgroundImage; + if (backgroundImages) { + if (backgroundImages.length > 1) + throw new Error("only one background image may be specified"); + backgroundImage = backgroundImages[0].filePath; + } + + var volumeIcons = inputs["icns"]; + var volumeIcon; + if (volumeIcons) { + if (volumeIcons.length > 1) + throw new Error("only one volume icon may be specified"); + volumeIcon = volumeIcons[0].filePath; + } + + var licenseFileObjects = Array.prototype.map.call(inputs["dmg.license"], function (a) { + return { + "dmg": { + "licenseLocale": localizationFromArtifact(a), + "licenseLanguageName": a.dmg.licenseLanguageName, + "licenseAgreeButtonText": a.dmg.licenseAgreeButtonText, + "licenseDisagreeButtonText": a.dmg.licenseDisagreeButtonText, + "licensePrintButtonText": a.dmg.licensePrintButtonText, + "licenseSaveButtonText": a.dmg.licenseSaveButtonText, + "licenseInstructionText": a.dmg.licenseInstructionText, + }, + "filePath": a.filePath + }; + }); + + function reduceLicensesForKey(licenseFileObjects, key) { + return licenseFileObjects.reduce(function (accumulator, currentValue) { + var locale = currentValue.dmg.licenseLocale; + if (accumulator[locale]) + throw new Error("Multiple license files for localization '" + locale + "'"); + switch (key) { + case "licenses": + accumulator[locale] = currentValue.filePath; + break; + case "buttons": + var texts = [ + currentValue.dmg.licenseLanguageName, + currentValue.dmg.licenseAgreeButtonText, + currentValue.dmg.licenseDisagreeButtonText, + currentValue.dmg.licensePrintButtonText, + currentValue.dmg.licenseSaveButtonText, + currentValue.dmg.licenseInstructionText + ]; + accumulator[locale] = texts.every(function (a) { return !!a; }) ? texts : undefined; + break; + } + return accumulator; + }, {}); + } + + var contentsArray = Array.prototype.map.call(inputs["dmg.input"], function (a) { + if (a.dmg.sourceBase && !a.filePath.startsWith(a.dmg.sourceBase)) { + throw new Error("Cannot install '" + a.filePath + "', " + + "because it doesn't start with the value of " + + "dmg.sourceBase '" + a.dmg.sourceBase + "'."); + } + + var isSymlink = a.fileTags.contains("dmg.input.symlink"); + return { + "x": a.dmg.iconX, + "y": a.dmg.iconY, + "type": isSymlink ? "link" : "file", + "path": isSymlink ? a.dmg.symlinkTarget : a.filePath, + "name": FileInfo.relativePath(a.dmg.sourceBase || FileInfo.path(a.filePath), a.filePath) + }; + }); + + Array.prototype.forEach.call(product.dmg.iconPositions, function (obj) { + var existingIndex = -1; + Array.prototype.forEach.call(contentsArray, function (contentsItem, i) { + if (contentsItem["name"] === obj["path"]) + existingIndex = i; + }); + + if (existingIndex >= 0) { + contentsArray[existingIndex]["x"] = obj["x"]; + contentsArray[existingIndex]["y"] = obj["y"]; + } else { + contentsArray.push({ + "type": "position", + "name": obj["path"], // name => path is not a typo + "path": obj["path"], + "x": obj["x"], + "y": obj["y"] + }); + } + }); + + return { + "title": product.dmg.volumeName, + "icon": !product.dmg.badgeVolumeIcon ? volumeIcon : undefined, + "badge-icon": product.dmg.badgeVolumeIcon ? volumeIcon : undefined, + "background": backgroundImage, + "background-color": product.dmg.backgroundColor, + "icon-size": product.dmg.iconSize, + "window": { + "position": { + "x": product.dmg.windowX, + "y": product.dmg.windowY + }, + "size": { + "width": product.dmg.windowWidth, + "height": product.dmg.windowHeight + } + }, + "format": product.dmg.format, + "compression-level": product.dmg.compressionLevel, + "license": { + "default-language": product.dmg.defaultLicenseLocale, + "licenses": reduceLicensesForKey(licenseFileObjects, "licenses"), + "buttons": reduceLicensesForKey(licenseFileObjects, "buttons") + }, + "contents": contentsArray + }; +} + +function prepareLicense(project, product, inputs, outputs, input, output) { + var cmd = new Command(product.dmg.textutilPath, [ + "-convert", "rtf", + "-strip", + "-font", "Arial", + "-output", output.filePath, + "--", input.filePath + ]); + cmd.description = "converting " + input.fileName; + return [cmd]; +} + +function prepareDmg(project, product, inputs, outputs, input, output) { + var i; + var cmd; + var cmds = []; + + var settingsJsonFilePath = FileInfo.joinPaths(product.destinationDirectory, + "settings.json"); + cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.settingsJSON = dmgbuildSettings(product, inputs); + cmd.settingsJsonFilePath = settingsJsonFilePath; + cmd.sourceCode = function () { + var tf; + try { + tf = new TextFile(settingsJsonFilePath, TextFile.WriteOnly); + tf.writeLine(JSON.stringify(settingsJSON, undefined, 4)); + } finally { + if (tf) + tf.close(); + } + } + cmds.push(cmd); + + // Create the actual DMG via dmgbuild + cmd = new Command(FileInfo.joinPaths(product.qbs.libexecPath, "dmgbuild"), + [product.dmg.volumeName, + output.filePath, + "--no-hidpi", // qbs handles this by itself + "--settings", settingsJsonFilePath]); + cmd.environment = ["PYTHONPATH=" + product.dmg.pythonPath]; + cmd.description = "creating " + output.fileName; + cmds.push(cmd); + + return cmds; +} diff --git a/share/qbs/modules/freedesktop/FreeDesktop.qbs b/share/qbs/modules/freedesktop/FreeDesktop.qbs new file mode 100644 index 00000000..bb2ba79f --- /dev/null +++ b/share/qbs/modules/freedesktop/FreeDesktop.qbs @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Alberto Mardegan +** 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.ModUtils +import qbs.TextFile +import "freedesktop.js" as Fdo + +Module { + id: fdoModule + + property string name: product.name + + property var desktopKeys + + readonly property var defaultDesktopKeys: { + return { + 'Type': 'Application', + 'Name': product.freedesktop.name, + 'Exec': product.targetName, + 'Terminal': 'false', + 'Version': '1.1', + } + } + property bool _fdoSupported: qbs.targetOS.contains("unix") && !qbs.targetOS.contains("darwin") + + additionalProductTypes: "freedesktop.desktopfile" + + FileTagger { + patterns: [ "*.desktop" ] + fileTags: [ "freedesktop.desktopfile_source" ] + } + + Rule { + condition: _fdoSupported + + inputs: [ "freedesktop.desktopfile_source" ] + outputFileTags: [ "freedesktop.desktopfile" ] + + Artifact { + fileTags: [ "freedesktop.desktopfile" ] + filePath: input.fileName + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = input.fileName + "->" + output.fileName; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + var aggregateDesktopKeys = Fdo.parseDesktopFile(input.filePath); + var desktopKeys = ModUtils.moduleProperty(product, "desktopKeys") || {} + var mainSection = aggregateDesktopKeys['Desktop Entry']; + for (key in desktopKeys) { + if (desktopKeys.hasOwnProperty(key)) { + mainSection[key] = desktopKeys[key]; + } + } + + var defaultValues = product.freedesktop.defaultDesktopKeys + for (key in defaultValues) { + if (!(key in mainSection)) { + mainSection[key] = defaultValues[key]; + } + } + + Fdo.dumpDesktopFile(output.filePath, aggregateDesktopKeys); + } + return [cmd]; + } + } + + Group { + condition: fdoModule._fdoSupported + fileTagsFilter: [ "freedesktop.desktopfile" ] + qbs.install: true + qbs.installDir: "share/applications" + } + + Group { + condition: fdoModule._fdoSupported + fileTagsFilter: [ "freedesktop.appIcon" ] + qbs.install: true + qbs.installDir: "share/icons/hicolor/scalable/apps" + } + + FileTagger { + patterns: [ "*.metainfo.xml", "*.appdata.xml" ] + fileTags: [ "freedesktop.appstream" ] + } + + Group { + condition: fdoModule._fdoSupported + fileTagsFilter: [ "freedesktop.appstream" ] + qbs.install: true + qbs.installDir: "share/metainfo" + } +} diff --git a/share/qbs/modules/freedesktop/freedesktop.js b/share/qbs/modules/freedesktop/freedesktop.js new file mode 100644 index 00000000..d3c60b19 --- /dev/null +++ b/share/qbs/modules/freedesktop/freedesktop.js @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Alberto Mardegan +** 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 TextFile = require("qbs.TextFile"); + +function parseDesktopFile(filePath) { + var file = new TextFile(filePath); + var fileValues = {}; + var currentSection = {}; + var sectionRex = /\[(.+)\]/g; + while (!file.atEof()) { + var line = file.readLine().trim(); + if (line.length == 0) continue; + if (line[0] == '#') continue; + var match = sectionRex.exec(line); + if (match) { + var currentSectionName = match[1]; + fileValues[currentSectionName] = {}; + currentSection = fileValues[currentSectionName]; + continue; + } + var parts = line.split('=', 2); + currentSection[parts[0]] = parts[1] + } + file.close(); + return fileValues +} + +function dumpDesktopFile(filePath, keys) { + var file = new TextFile(filePath, TextFile.WriteOnly); + for (var sectionName in keys) { + file.writeLine('[' + sectionName + ']'); + var section = keys[sectionName]; + for (var key in section) { + var line = key + '=' + section[key]; + file.writeLine(line); + } + // Write an empty line between sections (and before EOF) + file.writeLine(''); + } + file.close(); +} diff --git a/share/qbs/modules/ib/IBModule.qbs b/share/qbs/modules/ib/IBModule.qbs new file mode 100644 index 00000000..6af5e94e --- /dev/null +++ b/share/qbs/modules/ib/IBModule.qbs @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.BundleTools +import qbs.DarwinTools +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Process +import 'ib.js' as Ib + +Module { + Depends { name: "xcode"; required: false } + + Probe { + id: ibProbe + property string toolPath: ibtoolPath // input + property string toolVersion // output + configure: { + toolVersion = Ib.ibtoolVersion(toolPath); + found = true; + } + } + + condition: qbs.hostOS.contains("darwin") && qbs.targetOS.contains("darwin") + + property bool warnings: true + property bool errors: true + property bool notices: true + + property stringList flags + + // tiffutil specific + property string tiffutilName: "tiffutil" + property string tiffutilPath: FileInfo.joinPaths("/usr/bin", tiffutilName) + property bool combineHidpiImages: true + + // iconutil specific + property string iconutilName: "iconutil" + property string iconutilPath: FileInfo.joinPaths("/usr/bin", iconutilName) + + // XIB/NIB specific + property string ibtoolName: "ibtool" + property string ibtoolPath: FileInfo.joinPaths(xcode.developerPath, "/usr/bin", ibtoolName) + property bool flatten: true + property string module + property bool autoActivateCustomFonts: true + + // Asset catalog specific + property string actoolName: xcode.present ? "actool" : "ictool" + property string actoolPath: FileInfo.joinPaths(xcode.developerPath, "/usr/bin", actoolName) + property string appIconName + property string launchImageName + property bool compressPngs: true + + // private properties + property string outputFormat: "human-readable-text" + property string tiffSuffix: ".tiff" + property string appleIconSuffix: ".icns" + property string compiledAssetCatalogSuffix: ".car" + property string compiledNibSuffix: ".nib" + property string compiledStoryboardSuffix: ".storyboardc" + + version: ibtoolVersion + property string ibtoolVersion: ibProbe.toolVersion + property var ibtoolVersionParts: ibtoolVersion ? ibtoolVersion.split('.').map(function(item) { return parseInt(item, 10); }) : [] + property int ibtoolVersionMajor: ibtoolVersionParts[0] + property int ibtoolVersionMinor: ibtoolVersionParts[1] + property int ibtoolVersionPatch: ibtoolVersionParts[2] + + property stringList targetDevices: xcode.present + ? xcode.targetDevices + : DarwinTools.targetDevices(qbs.targetOS) + + validate: { + var validator = new ModUtils.PropertyValidator("ib"); + validator.setRequiredProperty("ibtoolVersion", ibtoolVersion); + validator.setRequiredProperty("ibtoolVersionMajor", ibtoolVersionMajor); + validator.setRequiredProperty("ibtoolVersionMinor", ibtoolVersionMinor); + validator.addVersionValidator("ibtoolVersion", ibtoolVersion, 2, 3); + validator.addRangeValidator("ibtoolVersionMajor", ibtoolVersionMajor, 1); + validator.addRangeValidator("ibtoolVersionMinor", ibtoolVersionMinor, 0); + if (ibtoolVersionPatch !== undefined) + validator.addRangeValidator("ibtoolVersionPatch", ibtoolVersionPatch, 0); + validator.validate(); + } + + FileTagger { + patterns: ["*.png"] + fileTags: ["png"] + } + + FileTagger { + patterns: ["*.iconset"] // bundle + fileTags: ["iconset"] + } + + FileTagger { + patterns: ["*.nib", "*.xib"] + fileTags: ["nib"] + } + + FileTagger { + patterns: ["*.storyboard"] + fileTags: ["storyboard"] + } + + FileTagger { + patterns: ["*.xcassets"] // bundle + fileTags: ["assetcatalog"] + } + + Rule { + multiplex: true + inputs: ["png"] + + outputFileTags: ["tiff"] + outputArtifacts: Ib.tiffutilArtifacts(product, inputs) + + prepare: Ib.prepareTiffutil.apply(Ib, arguments) + } + + Rule { + inputs: ["iconset"] + + outputFileTags: ["icns", "bundle.input"] + outputArtifacts: ([{ + filePath: FileInfo.joinPaths(product.destinationDirectory, input.completeBaseName + + ModUtils.moduleProperty(product, "appleIconSuffix")), + fileTags: ["icns", "bundle.input"], + bundle: { + _bundleFilePath: FileInfo.joinPaths(BundleTools.destinationDirectoryForResource(product, input), + input.completeBaseName + + ModUtils.moduleProperty(product, "appleIconSuffix")) + } + }]) + + prepare: { + var args = ["--convert", "icns", "--output", output.filePath, input.filePath]; + var cmd = new Command(ModUtils.moduleProperty(product, "iconutilPath"), args); + cmd.description = "compiling " + input.fileName; + return cmd; + } + } + + Rule { + inputs: ["nib", "storyboard"] + + outputFileTags: { + var tags = ["partial_infoplist"]; + for (var i = 0; i < inputs.length; ++i) + tags = tags.uniqueConcat(ModUtils.allFileTags(Ib.ibtoolFileTaggers(inputs[i]))); + return tags; + } + + outputArtifacts: Ib.ibtoolOutputArtifacts(product, inputs, input) + + prepare: { + var cmd = new Command(ModUtils.moduleProperty(product, "ibtoolPath"), + Ib.ibtooldArguments(product, inputs, input, outputs)); + cmd.description = "compiling " + input.fileName; + + // Also display the language name of the nib/storyboard being compiled if it has one + var localizationKey = DarwinTools.localizationKey(input.filePath); + if (localizationKey) + cmd.description += ' (' + localizationKey + ')'; + + cmd.highlight = 'compiler'; + + // May not be strictly needed, but is set by some versions of Xcode + if (input.fileTags.contains("storyboard")) + cmd.environment.push("IBSC_MINIMUM_COMPATIBILITY_VERSION=" + + (product.moduleProperty("cpp", "minimumDarwinVersion") || "")); + + cmd.stdoutFilterFunction = function(output) { + return ""; + }; + + return cmd; + } + } + + Rule { + inputs: ["assetcatalog"] + multiplex: true + + outputArtifacts: Ib.actoolOutputArtifacts(product, inputs) + outputFileTags: ["bundle.input", "compiled_assetcatalog", "partial_infoplist"] + + prepare: { + var mkdir = new JavaScriptCommand(); + mkdir.silent = true; + mkdir.sourceCode = function () { + File.makePath(FileInfo.joinPaths(product.buildDirectory, "actool.dir")); + }; + + var cmd = new Command(ModUtils.moduleProperty(product, "actoolPath"), + Ib.ibtooldArguments(product, inputs, input, outputs)); + cmd.description = inputs["assetcatalog"].map(function (input) { + return "compiling " + input.fileName; + }).join('\n'); + cmd.highlight = "compiler"; + + cmd.stdoutFilterFunction = function(output) { + return ""; + }; + + return [mkdir, cmd]; + } + } +} diff --git a/share/qbs/modules/ib/ib.js b/share/qbs/modules/ib/ib.js new file mode 100644 index 00000000..40bd1dc2 --- /dev/null +++ b/share/qbs/modules/ib/ib.js @@ -0,0 +1,367 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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 BundleTools = require("qbs.BundleTools"); +var DarwinTools = require("qbs.DarwinTools"); +var File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var ModUtils = require("qbs.ModUtils"); +var Process = require("qbs.Process"); +var PropertyList = require("qbs.PropertyList"); + +function artifactsFromInputs(inputs) { + var artifacts = []; + for (var tag in inputs) { + artifacts = artifacts.concat(inputs[tag]); + } + return artifacts; +} + +function tiffutilScalesMap(inputs) { + return artifactsFromInputs(inputs).map(function (a) { + var m = a.filePath.match(/^(.+?)(@(\d+)x)?(\..+?)$/); + var basePath = m[1]; + var scale = m[2] || ""; + var nscale = m[3]; + var extension = m[4]; + if (scale && scale < 1) + throw new Error("Invalid scale '" + nscale + "' for image '" + a.filePath + "'"); + return { + basePath: basePath, + extension: extension, + scale: scale + }; + }).reduce(function (previous, current) { + previous[current["basePath"]] = (previous[current["basePath"]] || []).concat([{ + extension: current["extension"], + scale: current["scale"] + }]); + return previous; + }, {}); +} + +function tiffutilOutputFilePath(product, basePath) { + return FileInfo.joinPaths(product.destinationDirectory, + "hidpi-images", + FileInfo.relativePath(product.sourceDirectory, basePath) + + product.ib.tiffSuffix); +} + +function tiffutilArtifacts(product, inputs) { + var artifacts = []; + var map = tiffutilScalesMap(inputs); + for (var key in map) { + artifacts.push({ + filePath: tiffutilOutputFilePath(product, key), + fileTags: ["tiff"] + }); + } + return artifacts; +} + +function prepareTiffutil(project, product, inputs, outputs, input, output) { + var cmds = []; + var map = tiffutilScalesMap(inputs); + for (var key in map) { + var args = ["-cat" + (product.ib.combineHidpiImages ? "hidpicheck" : "")]; + var count = 0; + map[key].forEach(function (obj) { + args.push(key + obj["scale"] + obj["extension"]); + ++count; + }); + args.push("-out", tiffutilOutputFilePath(product, key)); + var cmd = new Command(product.ib.tiffutilPath, args); + cmd.description = "creating " + output.fileName; + cmd.count = count; + cmd.outputFilePath = output.filePath; + cmd.stderrFilterFunction = function (output) { + return output.replace(count + " images written to " + outputFilePath + ".", ""); + }; + cmds.push(cmd); + } + return cmds; +} + +function ibtooldArguments(product, inputs, input, outputs, overrideOutput) { + var i; + var args = []; + var allInputs = artifactsFromInputs(inputs); + + var outputFormat = ModUtils.moduleProperty(product, "outputFormat"); + if (outputFormat) { + if (!["binary1", "xml1", "human-readable-text"].contains(outputFormat)) + throw("Invalid ibtoold output format: " + outputFormat + ". " + + "Must be in [binary1, xml1, human-readable-text]."); + + args.push("--output-format", outputFormat); + } + + var debugFlags = ["warnings", "errors", "notices"]; + for (var j in debugFlags) { + var flag = debugFlags[j]; + if (ModUtils.modulePropertyFromArtifacts(product, allInputs, product.moduleName, flag)) { + args.push("--" + flag); + } + } + + if (inputs.assetcatalog) { + args.push("--platform", DarwinTools.applePlatformName( + product.moduleProperty("qbs", "targetOS"), + product.moduleProperty("xcode", "platformType"))); + + var appIconName = ModUtils.modulePropertyFromArtifacts(product, inputs.assetcatalog, product.moduleName, "appIconName"); + if (appIconName) + args.push("--app-icon", appIconName); + + var launchImageName = ModUtils.modulePropertyFromArtifacts(product, inputs.assetcatalog, product.moduleName, "launchImageName"); + if (launchImageName) + args.push("--launch-image", launchImageName); + + // Undocumented but used by Xcode (only for iOS?), probably runs pngcrush or equivalent + if (ModUtils.modulePropertyFromArtifacts(product, inputs.assetcatalog, product.moduleName, "compressPngs")) + args.push("--compress-pngs"); + } else { + var sysroot = product.moduleProperty("qbs", "sysroot"); + if (sysroot) + args.push("--sdk", sysroot); + + args.push("--flatten", ModUtils.modulePropertyFromArtifacts(product, allInputs, product.moduleName, "flatten") ? 'YES' : 'NO'); + + // --module and --auto-activate-custom-fonts were introduced in Xcode 6.0 + if (ModUtils.moduleProperty(product, "ibtoolVersionMajor") >= 6) { + var module = ModUtils.moduleProperty(product, "module"); + if (module) + args.push("--module", module); + + if (ModUtils.modulePropertyFromArtifacts(product, allInputs, product.moduleName, "autoActivateCustomFonts")) + args.push("--auto-activate-custom-fonts"); + } + } + + // --minimum-deployment-target was introduced in Xcode 5.0 + var minimumDarwinVersion = product.moduleProperty("cpp", "minimumDarwinVersion"); + if (minimumDarwinVersion && ModUtils.moduleProperty(product, "ibtoolVersionMajor") >= 5) + args.push("--minimum-deployment-target", minimumDarwinVersion); + + // --target-device and -output-partial-info-plist were introduced in Xcode 6.0 for ibtool + if (ModUtils.moduleProperty(product, "ibtoolVersionMajor") >= 6 || inputs.assetcatalog) { + args.push("--output-partial-info-plist", (outputs && outputs.partial_infoplist) + ? outputs.partial_infoplist[0].filePath + : "/dev/null"); + + // For iOS, we'd normally only output the devices specified in TARGETED_DEVICE_FAMILY + // We can't get this info from Info.plist keys due to dependency order, so use the qbs prop + var targetDevices = ModUtils.moduleProperty(product, "targetDevices"); + for (i in targetDevices) { + args.push("--target-device", targetDevices[i]); + } + } + + args = args.concat(ModUtils.modulePropertiesFromArtifacts(product, allInputs, + product.moduleName, "flags")); + + if (overrideOutput) { + args.push("--compile", overrideOutput); + } else { + if (outputs.compiled_assetcatalog) + args.push("--compile", product.buildDirectory + "/actool.dir"); + else // compiled_ibdoc + args.push("--compile", product.buildDirectory + "/ibtool.dir/" + + ibtoolCompiledDirSuffix(product, input)); + } + + for (i in allInputs) + args.push(allInputs[i].filePath); + + return args; +} + +function ibtoolFileTaggers(fileTags) { + var ext; + if (fileTags.contains("nib") && !fileTags.contains("storyboard")) + ext = "nib"; + if (fileTags.contains("storyboard") && !fileTags.contains("nib")) + ext = "storyboard"; + + if (!ext) + throw "unknown ibtool input file tags: " + fileTags; + + var t = ["bundle.input", "compiled_ibdoc"]; + return { + ".nib": t.concat(["compiled_" + ext + (ext !== "nib" ? "_nib" : "")]), + ".plist": t.concat(["compiled_" + ext + "_infoplist"]), + ".storyboard": t.concat(["compiled_" + ext]) + }; +} + +function ibtoolCompiledDirSuffix(product, input) { + var suffix = input.completeBaseName; + if (input.fileTags.contains("nib")) + suffix += ModUtils.moduleProperty(product, "compiledNibSuffix"); + else if (input.fileTags.contains("storyboard")) + suffix += ModUtils.moduleProperty(product, "compiledStoryboardSuffix"); + return suffix; +} + +function ibtoolOutputArtifacts(product, inputs, input) { + var suffix = ibtoolCompiledDirSuffix(product, input); + var tracker = new ModUtils.BlackboxOutputArtifactTracker(); + tracker.hostOS = product.moduleProperty("qbs", "hostOS"); + tracker.shellPath = product.moduleProperty("qbs", "shellPath"); + tracker.fileTaggers = ibtoolFileTaggers(input.fileTags); + tracker.command = ModUtils.moduleProperty(product, "ibtoolPath"); + tracker.commandArgsFunction = function (outputDirectory) { + // Last --output-format argument overrides any previous ones + // Append the name of the base output since it can be either a file or a directory + // in the case of XIB compilations + return ibtooldArguments(product, inputs, input, + undefined, FileInfo.joinPaths(outputDirectory, suffix)) + .concat(["--output-format", "xml1"]); + }; + + var ibtoolBuildDirectory = product.buildDirectory + "/ibtool.dir"; + var main = BundleTools.destinationDirectoryForResource(product, input); + + var artifacts = tracker.artifacts(ibtoolBuildDirectory); + + if (product.moduleProperty("ib", "ibtoolVersionMajor") >= 6) { + var prefix = input.fileTags.contains("storyboard") ? "SB" : ""; + var path = FileInfo.joinPaths(product.destinationDirectory, input.completeBaseName + + "-" + prefix + "PartialInfo.plist"); + artifacts.push({ filePath: path, fileTags: ["partial_infoplist"] }); + } + + // Let the output artifacts known the "main" output + // This can be either a file or directory so the artifact might already exist in the output list + for (var i = 0; i < artifacts.length; ++i) { + if (artifacts[i].fileTags.contains("compiled_ibdoc")) + artifacts[i].bundle = { + _bundleFilePath: artifacts[i].filePath.replace(ibtoolBuildDirectory, main) + }; + } + + return artifacts; +} + +function actoolOutputArtifacts(product, inputs) { + // actool has no --dry-run option (rdar://21786925), + // so compile to a fake temporary directory in order to extract the list of output files + var tracker = new ModUtils.BlackboxOutputArtifactTracker(); + tracker.hostOS = product.moduleProperty("qbs", "hostOS"); + tracker.shellPath = product.moduleProperty("qbs", "shellPath"); + tracker.command = ModUtils.moduleProperty(product, "actoolPath"); + tracker.commandArgsFunction = function (outputDirectory) { + // Last --output-format argument overrides any previous ones + return ibtooldArguments(product, inputs, undefined, + undefined, outputDirectory).concat(["--output-format", "xml1"]); + }; + tracker.processStdOutFunction = parseActoolOutput; + var artifacts = tracker.artifacts(product.buildDirectory + "/actool.dir"); + + // Newer versions of actool don't generate *anything* if there's no input; + // in that case a partial Info.plist would not have been generated either + if (artifacts && artifacts.length > 0) { + artifacts.push({ + filePath: FileInfo.joinPaths(product.destinationDirectory, + "assetcatalog_generated_info.plist"), + fileTags: ["partial_infoplist"] + }); + } + + for (var i = 0; i < artifacts.length; ++i) { + if (artifacts[i].fileTags.contains("compiled_assetcatalog")) { + artifacts[i].bundle = { + _bundleFilePath: artifacts[i].filePath.replace( + product.buildDirectory + "/actool.dir", + BundleTools.destinationDirectoryForResource(product, inputs.assetcatalog[0])) + }; + } + } + + return artifacts; +} + +function parseActoolOutput(output) { + var propertyList = new PropertyList(); + try { + propertyList.readFromString(output); + + var plist = propertyList.toObject(); + if (plist) + plist = plist["com.apple.actool.compilation-results"]; + if (plist) { + var artifacts = []; + files = plist["output-files"]; + for (var i in files) { + if (files[i] === "/dev/null") + continue; + var tags = files[i].endsWith(".plist") + ? ["partial_infoplist"] + : ["bundle.input", "compiled_assetcatalog"]; + artifacts.push({ + // Even though we pass in a canonical base dir, the paths in the XML File + // are non-canonical. See QBS-1417. + filePath: FileInfo.canonicalPath(files[i]), + fileTags: tags + }); + } + + return artifacts; + } + } finally { + propertyList.clear(); + } +} + +function ibtoolVersion(ibtool) { + var process; + var version; + try { + process = new Process(); + if (process.exec(ibtool, ["--version", "--output-format", "xml1"], true) !== 0) + console.error(process.readStdErr()); + + var propertyList = new PropertyList(); + try { + propertyList.readFromString(process.readStdOut()); + + var plist = propertyList.toObject(); + if (plist) + plist = plist["com.apple.ibtool.version"]; + if (plist) + version = plist["short-bundle-version"]; + } finally { + propertyList.clear(); + } + } finally { + process.close(); + } + return version; +} diff --git a/share/qbs/modules/ico/IcoModule.qbs b/share/qbs/modules/ico/IcoModule.qbs new file mode 100644 index 00000000..7157aab0 --- /dev/null +++ b/share/qbs/modules/ico/IcoModule.qbs @@ -0,0 +1,101 @@ +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Probes +import qbs.Process +import qbs.Utilities +import "ico.js" as IcoUtils + +Module { + Probes.BinaryProbe { + id: icotoolProbe + names: ["icotool"] + } + + Probes.IcoUtilsVersionProbe { + id: icotoolVersionProbe + toolFilePath: icotoolFilePath + } + + version: icotoolVersionProbe.version + + property int alphaThreshold + property int cursorHotspotX + property int cursorHotspotY + property bool raw + + // private properties + property string icotoolFilePath: icotoolProbe.filePath + readonly property bool hasCursorHotspotBug: Utilities.versionCompare(version, "0.32") < 0 + + FileTagger { + patterns: ["*.png"] + fileTags: ["png"] + } + + FileTagger { + patterns: ["*.iconset"] // bundle + fileTags: ["iconset"] + } + + Rule { + inputs: ["iconset"] + + Artifact { + filePath: input.baseName + ".ico" + fileTags: ["ico"] + } + + prepare: IcoUtils.prepareIconset.apply(IcoUtils, arguments) + } + + Rule { + multiplex: true + inputs: ["png"] + + Artifact { + filePath: product.targetName + ".ico" + fileTags: ["ico"] + } + + prepare: IcoUtils.prepare.apply(IcoUtils, arguments) + } + + Rule { + inputs: ["iconset"] + + Artifact { + filePath: input.baseName + ".cur" + fileTags: ["cur"] + } + + prepare: IcoUtils.prepareIconset.apply(IcoUtils, arguments) + } + + Rule { + multiplex: true + inputs: ["png"] + + Artifact { + filePath: product.targetName + ".cur" + fileTags: ["cur"] + } + + prepare: IcoUtils.prepare.apply(IcoUtils, arguments) + } + + validate: { + if (!icotoolFilePath) + throw ModUtils.ModuleError("Could not find icotool in any of the following " + + "locations:\n\t" + icotoolProbe.candidatePaths.join("\n\t") + + "\nInstall the icoutils package on your platform."); + + if (!version) + throw ModUtils.ModuleError("Could not determine icoutils package version."); + + var validator = new ModUtils.PropertyValidator("ico"); + validator.setRequiredProperty("version", version); + validator.addVersionValidator("version", version, 2, 3); + return validator.validate() + } +} diff --git a/share/qbs/modules/ico/ico.js b/share/qbs/modules/ico/ico.js new file mode 100644 index 00000000..997a6dc2 --- /dev/null +++ b/share/qbs/modules/ico/ico.js @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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 File = require("qbs.File"); +var Process = require("qbs.Process"); + +function prepareIconset(project, product, inputs, outputs, input, output) { + inputs = File.directoryEntries(input.filePath, File.Files).map(function (p) { + return { + filePath: FileInfo.joinPaths(input.filePath, p), + fileTags: p.endsWith(".png") ? ["png"] : [], + ico: {} + }; + }); + inputs = {"png": inputs.filter(function (a) { return a.fileTags.contains("png"); })}; + input = undefined; + return prepare(project, product, inputs, outputs, input, output); +} + +function prepare(project, product, inputs, outputs, input, output) { + var args = ["--create", "--output=" + output.filePath]; + if (output.fileTags.contains("ico")) { + args.push("--icon"); + if (product.ico.alphaThreshold !== undefined) + args.push("--alpha-threshold=" + product.ico.alphaThreshold); + } + + var isCursor = output.fileTags.contains("cur"); + if (isCursor) + args.push("--cursor"); + + var hasMultipleImages = inputs["png"].length > 1; + inputs["png"].map(function(inp) { + if (isCursor) { + var hasX = inp.ico.cursorHotspotX !== undefined; + var hasY = inp.ico.cursorHotspotY !== undefined; + if (hasX || hasY) { + if (hasMultipleImages && product.ico.hasCursorHotspotBug) { + console.warn("icotool " + product.ico.version + " does not support setting " + + "the hotspot for cursor files with multiple images. Install " + + "icoutils 0.32.0 or newer to use this feature."); + } else { + if (hasX) + args.push("--hotspot-x=" + inp.ico.cursorHotspotX); + if (hasY) + args.push("--hotspot-y=" + inp.ico.cursorHotspotY); + } + } + } + if (inp.ico.raw) + args.push("-r"); + args.push(inp.filePath); + }); + + var cmd = new Command(product.ico.icotoolFilePath, args); + cmd.description = "creating " + output.fileName; + return [cmd]; +} + +function findIcoUtilsVersion(toolFilePath) { + var p = new Process(); + try { + p.exec(toolFilePath, ["--version"]); + var re = /^[a-z]+ \(icoutils\) ([0-9]+(?:\.[0-9]+){1,2})$/m; + var match = p.readStdOut().trim().match(re); + if (match !== null) + return match[1]; + } finally { + p.close(); + } +} diff --git a/share/qbs/modules/innosetup/InnoSetupModule.qbs b/share/qbs/modules/innosetup/InnoSetupModule.qbs new file mode 100644 index 00000000..1caf39dc --- /dev/null +++ b/share/qbs/modules/innosetup/InnoSetupModule.qbs @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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.FileInfo +import qbs.ModUtils +import qbs.Probes + +Module { + condition: qbs.targetOS.contains("windows") + + Probes.InnoSetupProbe { + id: innoSetupProbe + } + + property path toolchainInstallPath: innoSetupProbe.path + version: innoSetupProbe.version + property var versionParts: version ? version.split('.').map(function (item) { return parseInt(item, 10); }) : [] + property int versionMajor: versionParts[0] + property int versionMinor: versionParts[1] + property int versionPatch: versionParts[2] + + property string compilerName: "ISCC.exe" + property string compilerPath: FileInfo.joinPaths(toolchainInstallPath, compilerName) + + property bool verboseOutput: false + PropertyOptions { + name: "verboseOutput" + description: "display verbose output from the Inno Setup compiler" + } + + property pathList includePaths + PropertyOptions { + name: "includePaths" + description: "directories to add to the include search path" + } + + property stringList defines + PropertyOptions { + name: "defines" + description: "variables that are defined when using the Inno Setup compiler" + } + + property stringList compilerFlags + PropertyOptions { + name: "compilerFlags" + description: "additional flags for the Inno Setup compiler" + } + + readonly property string executableSuffix: ".exe" + + validate: { + var validator = new ModUtils.PropertyValidator("innosetup"); + validator.setRequiredProperty("toolchainInstallPath", toolchainInstallPath); + validator.setRequiredProperty("version", version); + validator.setRequiredProperty("versionMajor", versionMajor); + validator.setRequiredProperty("versionMinor", versionMinor); + validator.setRequiredProperty("versionPatch", versionPatch); + validator.addVersionValidator("version", version, 3, 3); + validator.addRangeValidator("versionMajor", versionMajor, 1); + validator.addRangeValidator("versionMinor", versionMinor, 0); + validator.addRangeValidator("versionPatch", versionPatch, 0); + validator.validate(); + } + + // Inno Setup Script + FileTagger { + patterns: ["*.iss"] + fileTags: ["innosetup.iss"] + } + + Rule { + id: innoSetupCompiler + inputs: ["innosetup.iss"] + auxiliaryInputs: ["installable"] + + Artifact { + fileTags: ["innosetup.exe", "application"] + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + + ModUtils.moduleProperty(product, "executableSuffix")) + } + + prepare: { + var i; + var args = [ + "/O" + FileInfo.toNativeSeparators(FileInfo.path(output.filePath)), + "/F" + FileInfo.toNativeSeparators(FileInfo.completeBaseName(output.fileName)) + ]; + + if (!ModUtils.moduleProperty(product, "verboseOutput")) + args.push("/Q"); + + var includePaths = ModUtils.moduleProperty(product, "includePaths"); + for (i in includePaths) + args.push("/I" + FileInfo.toNativeSeparators(includePaths[i])); + + // User-supplied defines + var defines = ModUtils.moduleProperty(product, "defines"); + for (i in defines) + args.push("/D" + defines[i]); + + // User-supplied flags + var flags = ModUtils.moduleProperty(product, "compilerFlags"); + for (i in flags) + args.push(flags[i]); + + args.push(FileInfo.toNativeSeparators(input.filePath)); + var cmd = new Command(ModUtils.moduleProperty(product, "compilerPath"), args); + cmd.description = "compiling " + input.fileName; + cmd.highlight = "compiler"; + cmd.workingDirectory = FileInfo.path(input.filePath); + return cmd; + } + } +} diff --git a/share/qbs/modules/java/JavaModule.qbs b/share/qbs/modules/java/JavaModule.qbs new file mode 100644 index 00000000..71f7d843 --- /dev/null +++ b/share/qbs/modules/java/JavaModule.qbs @@ -0,0 +1,356 @@ +/**************************************************************************** +** +** 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.FileInfo +import qbs.ModUtils +import qbs.Probes +import qbs.Process +import qbs.TextFile +import qbs.Utilities + +import "utils.js" as JavaUtils + +Module { + Probes.JdkProbe { + id: jdk + environmentPaths: (jdkPath ? [jdkPath] : []).concat(base) + } + + Probes.JdkVersionProbe { + id: jdkVersionProbe + javac: compilerFilePath + } + + property stringList additionalClassPaths + property stringList additionalCompilerFlags + property stringList additionalJarFlags + property stringList bootClassPaths + property string compilerFilePath: FileInfo.joinPaths(jdkPath, "bin", compilerName) + property string compilerName: "javac" + property bool enableWarnings: true + property string interpreterFilePath : FileInfo.joinPaths(jdkPath, "bin", interpreterName) + 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 bool _tagJniHeaders: true + + property string jdkPath: jdk.path + + version: [compilerVersionMajor, compilerVersionMinor, compilerVersionPatch].join(".") + property string compilerVersion: jdkVersionProbe.version + ? jdkVersionProbe.version[1] : undefined + property var compilerVersionParts: JavaUtils.splitVersionString(compilerVersion) + property int compilerVersionMajor: compilerVersionParts[0] + property int compilerVersionMinor: compilerVersionParts[1] + property int compilerVersionPatch: compilerVersionParts[2] + property int compilerVersionUpdate: compilerVersionParts[3] + + property string languageVersion + PropertyOptions { + name: "languageVersion" + description: "Java language version to interpret source code as" + } + + property string runtimeVersion + PropertyOptions { + name: "runtimeVersion" + description: "version of the Java runtime to generate compatible bytecode for" + } + + property var manifest: { + return { + "Manifest-Version": "1.0", + "Class-Path": manifestClassPath ? manifestClassPath.join(" ") : undefined + }; + } + + PropertyOptions { + name: "manifest" + description: "properties to add to the manifest file when building a JAR" + } + + PropertyOptions { + name: "manifestFile" + description: "Use files tagged \"java.manifest\" instead." + removalVersion: "1.9" + } + property stringList manifestClassPath + PropertyOptions { + name: "manifestClassPath" + description: "entries to add to the manifest's Class-Path when building a JAR" + } + + property bool warningsAsErrors: false + + property pathList jdkIncludePaths: { + var paths = []; + if (isAppleJava) { + paths.push(FileInfo.joinPaths(qbs.sysroot, + "/System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers")); + } else { + paths.push(FileInfo.joinPaths(jdkPath, "include")); + + var hostOS = qbs.hostOS.contains("windows") ? qbs.hostOS.concat(["win32"]) : qbs.hostOS; + var platforms = ["win32", "darwin", "linux", "bsd", "solaris"]; + for (var i = 0; i < platforms.length; ++i) { + if (hostOS.contains(platforms[i])) { + // Corresponds to JDK_INCLUDE_SUBDIR in the JDK Makefiles + paths.push(FileInfo.joinPaths(jdkPath, "include", platforms[i])); + break; + } + } + } + + return paths; + } + + // Internal properties + property path classFilesDir: FileInfo.joinPaths(product.buildDirectory, "classes") + property path internalClassFilesDir: FileInfo.joinPaths(product.buildDirectory, ".classes") + + property bool isAppleJava: qbs.hostOS.contains("darwin") + && (compilerVersionMajor < 1 + || (compilerVersionMajor === 1 && compilerVersionMinor < 7)) + + // https://developer.apple.com/library/content/documentation/Java/Conceptual/Java14Development/02-JavaDevTools/JavaDevTools.html + // tools.jar does not exist. Classes usually located here are instead included in classes.jar. + // The same is true for rt.jar, although not mentioned in the documentation + property path classesJarPath: { + if (isAppleJava) + return FileInfo.joinPaths(jdkPath, "bundle", "Classes", "classes.jar"); + } + + property path runtimeJarPath: { + if (compilerVersionMajor >= 9) + return undefined; + if (classesJarPath) + return classesJarPath; + return FileInfo.joinPaths(jdkPath, "jre", "lib", "rt.jar"); + } + + property path toolsJarPath: { + if (compilerVersionMajor >= 9) + return undefined; + if (classesJarPath) + return classesJarPath; + return FileInfo.joinPaths(jdkPath, "lib", "tools.jar"); + } + + validate: { + var validator = new ModUtils.PropertyValidator("java"); + validator.setRequiredProperty("jdkPath", jdkPath); + validator.setRequiredProperty("compilerVersion", compilerVersion); + validator.setRequiredProperty("compilerVersionParts", compilerVersionParts); + validator.setRequiredProperty("compilerVersionMajor", compilerVersionMajor); + validator.setRequiredProperty("compilerVersionMinor", compilerVersionMinor); + if (Utilities.versionCompare(version, "9") < 0) + validator.setRequiredProperty("compilerVersionUpdate", compilerVersionUpdate); + validator.addVersionValidator("compilerVersion", compilerVersion + ? compilerVersion.replace("_", ".") : undefined, 1, 4); + validator.addRangeValidator("compilerVersionMajor", compilerVersionMajor, 1); + validator.addRangeValidator("compilerVersionMinor", compilerVersionMinor, 0); + validator.addRangeValidator("compilerVersionPatch", compilerVersionPatch, 0); + if (Utilities.versionCompare(version, "9") < 0) + validator.addRangeValidator("compilerVersionUpdate", compilerVersionUpdate, 0); + validator.validate(); + } + + FileTagger { + patterns: "*.java" + fileTags: ["java.java"] + } + + FileTagger { + patterns: ["*.mf"] + fileTags: ["java.manifest"] + } + + Group { + name: "io.qt.qbs.internal.java-helper" + files: { + return JavaUtils.helperFullyQualifiedNames("java").map(function(name) { + return FileInfo.joinPaths(path, name + ".java"); + }); + } + + fileTags: ["java.java-internal"] + } + + Rule { + multiplex: true + inputs: ["java.java-internal"] + + outputFileTags: ["java.class-internal"] + outputArtifacts: { + return JavaUtils.helperOutputArtifacts(product); + } + + prepare: { + var cmd = new Command(ModUtils.moduleProperty(product, "compilerFilePath"), + JavaUtils.javacArguments(product, inputs, + JavaUtils.helperOverrideArgs(product, + "javac"))); + cmd.ignoreDryRun = true; + cmd.silent = true; + return [cmd]; + } + } + + Rule { + multiplex: true + inputs: ["java.java"] + inputsFromDependencies: ["java.jar"] + explicitlyDependsOn: ["java.class-internal"] + + outputFileTags: ["java.class"].concat(_tagJniHeaders ? ["hpp"] : []) // Annotations can produce additional java source files. Ignored for now. + outputArtifacts: { + var artifacts = JavaUtils.outputArtifacts(product, inputs); + if (!product.java._tagJniHeaders) { + for (var i = 0; i < artifacts.length; ++i) { + var a = artifacts[i]; + if (Array.isArray(a.fileTags)) + a.fileTags = a.fileTags.filter(function(tag) { return tag != "hpp"; }); + } + } + return artifacts; + } + prepare: { + var cmd = new Command(ModUtils.moduleProperty(product, "compilerFilePath"), + JavaUtils.javacArguments(product, inputs)); + cmd.description = "Compiling Java sources"; + cmd.highlight = "compiler"; + return [cmd]; + } + } + + Rule { + inputs: ["java.class", "java.manifest"] + multiplex: true + + Artifact { + fileTags: ["java.jar"] + filePath: FileInfo.joinPaths(product.destinationDirectory, product.targetName + ".jar") + } + + prepare: { + var i, key; + var flags = "cf"; + var args = [output.filePath]; + + var aggregateManifest = {}; + var manifestFiles = (inputs["java.manifest"] || []).map(function (a) { return a.filePath; }); + manifestFiles.forEach(function (manifestFile) { + var mf = JavaUtils.manifestContents(manifestFile); + for (key in mf) { + if (mf.hasOwnProperty(key)) { + var oldValue = aggregateManifest[key]; + var newValue = mf[key]; + if (oldValue !== undefined && oldValue !== newValue) { + throw new Error("Conflicting values '" + + oldValue + "' and '" + + newValue + "' for manifest file key '" + key + "'"); + } + + aggregateManifest[key] = newValue; + } + } + }); + + // Add local key-value pairs (overrides equivalent keys specified in the file if + // one was given) + var manifest = product.java.manifest; + for (key in manifest) { + if (manifest.hasOwnProperty(key)) + aggregateManifest[key] = manifest[key]; + } + + for (key in aggregateManifest) { + if (aggregateManifest.hasOwnProperty(key) && aggregateManifest[key] === undefined) + delete aggregateManifest[key]; + } + + // Use default manifest unless we actually have properties to set + var needsManifestFile = manifestFiles.length > 0 + || aggregateManifest !== {"Manifest-Version": "1.0"}; + + var manifestFile = FileInfo.joinPaths(product.buildDirectory, "manifest.mf"); + + var mf; + try { + mf = new TextFile(manifestFile, TextFile.WriteOnly); + + // Ensure that manifest version comes first + mf.write("Manifest-Version: " + (aggregateManifest["Manifest-Version"] || "1.0") + "\n"); + delete aggregateManifest["Manifest-Version"]; + + for (key in aggregateManifest) + mf.write(key + ": " + aggregateManifest[key] + "\n"); + + mf.write("\n"); + } finally { + if (mf) { + mf.close(); + } + } + + if (needsManifestFile) { + flags += "m"; + args.push(manifestFile); + } + + var entryPoint = ModUtils.moduleProperty(product, "entryPoint"); + var entryPoint = product.entryPoint; + if (entryPoint) { + flags += "e"; + args.push(entryPoint); + } + + args.unshift(flags); + + var otherFlags = ModUtils.moduleProperty(product, "additionalJarFlags"); + if (otherFlags) + args = args.concat(otherFlags); + + for (i in inputs["java.class"]) + args.push(FileInfo.relativePath(ModUtils.moduleProperty(product, "classFilesDir"), + inputs["java.class"][i].filePath)); + var cmd = new Command(ModUtils.moduleProperty(product, "jarFilePath"), args); + cmd.workingDirectory = ModUtils.moduleProperty(product, "classFilesDir"); + cmd.description = "building " + FileInfo.fileName(output.fileName); + cmd.highlight = "linker"; + return cmd; + } + } +} diff --git a/share/qbs/modules/java/io/qt/qbs/Artifact.java b/share/qbs/modules/java/io/qt/qbs/Artifact.java new file mode 100644 index 00000000..cc06d397 --- /dev/null +++ b/share/qbs/modules/java/io/qt/qbs/Artifact.java @@ -0,0 +1,76 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 Jake Petroules. + ** 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. + ** + ****************************************************************************/ + +package io.qt.qbs; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Artifact { + private String filePath; + private List fileTags; + + public Artifact(String filePath) { + if (filePath == null) + throw new IllegalArgumentException("filePath"); + + this.filePath = filePath; + this.fileTags = new ArrayList(); + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public List getFileTags() { + return Collections.unmodifiableList(fileTags); + } + + public void setFileTags(List fileTags) { + this.fileTags = new ArrayList(fileTags); + } + + public void addFileTag(String fileTag) { + this.fileTags.add(fileTag); + } + + public void removeFileTag(String fileTag) { + this.fileTags.remove(fileTag); + } + + public void clearFileTags() { + this.fileTags.clear(); + } +} diff --git a/share/qbs/modules/java/io/qt/qbs/ArtifactListJsonWriter.java b/share/qbs/modules/java/io/qt/qbs/ArtifactListJsonWriter.java new file mode 100644 index 00000000..d7f60ddc --- /dev/null +++ b/share/qbs/modules/java/io/qt/qbs/ArtifactListJsonWriter.java @@ -0,0 +1,153 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 Jake Petroules. + ** 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. + ** + ****************************************************************************/ + +package io.qt.qbs; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.List; + +/** + * This uses a custom JSON implementation because the Java Standard Library does + * not yet have native support for JSON, and only minimal support is required + * here. + */ +public class ArtifactListJsonWriter implements ArtifactListWriter { + private static final int TAB_WIDTH = 4; + + // based on escapeString from qtbase/qjsonwriter.cpp + private static String escapeString(String s) { + String out = ""; + for (int i = 0; i < s.length();) { + int u = s.codePointAt(i); + + // unpaired surrogate + if (u >= Character.MIN_SURROGATE && u <= Character.MAX_SURROGATE) { + out += "\ufffd"; + i += Character.charCount(u); + continue; + } + + if (u < 0x80) { + if (u < 0x20 || u == 0x22 || u == 0x5c) { + out += "\\"; + switch (u) { + case 0x22: + out += "\""; + break; + case 0x5c: + out += "\\"; + break; + case 0x8: + out += "b"; + break; + case 0xc: + out += "f"; + break; + case 0xa: + out += "n"; + break; + case 0xd: + out += "r"; + break; + case 0x9: + out += "t"; + break; + default: + out += "u"; + out += "0"; + out += "0"; + String hex = Integer.toHexString(u); + if (hex.length() == 1) + out += "0"; + out += hex; + break; + } + } else { + out += s.substring(i, i + Character.charCount(u)); + } + } else { + out += s.substring(i, i + Character.charCount(u)); + } + + i += Character.charCount(u); + } + + return out; + } + + private static void writeString(PrintStream printWriter, String s) { + printWriter.print("\""); + printWriter.print(escapeString(s)); + printWriter.print("\""); + } + + private static void writeIndent(PrintStream printWriter, int level) { + for (int i = 0; i < level * TAB_WIDTH; ++i) { + printWriter.print(" "); + } + } + + private static void writeArtifact(Artifact artifact, + PrintStream printWriter, int indentLevel, Boolean comma) { + writeIndent(printWriter, indentLevel++); + printWriter.print("{\n"); + writeIndent(printWriter, indentLevel); + writeString(printWriter, "filePath"); + printWriter.print(": "); + writeString(printWriter, artifact.getFilePath()); + printWriter.println(","); + writeIndent(printWriter, indentLevel); + writeString(printWriter, "fileTags"); + printWriter.print(": ["); + for (int i = 0; i < artifact.getFileTags().size(); ++i) { + writeString(printWriter, artifact.getFileTags().get(i)); + if (i != artifact.getFileTags().size() - 1) + printWriter.print(", "); + } + printWriter.println("]"); + writeIndent(printWriter, --indentLevel); + printWriter.println("}" + (comma ? "," : "")); + } + + @Override + public void write(List artifacts, OutputStream outputStream) + throws IOException { + PrintStream printWriter = new PrintStream(outputStream); + printWriter.print("[\n"); + for (int i = 0; i < artifacts.size(); ++i) { + writeArtifact(artifacts.get(i), printWriter, 1, + i != artifacts.size() - 1); + } + + printWriter.println("]"); + } +} diff --git a/share/qbs/modules/java/io/qt/qbs/ArtifactListWriter.java b/share/qbs/modules/java/io/qt/qbs/ArtifactListWriter.java new file mode 100644 index 00000000..a213207b --- /dev/null +++ b/share/qbs/modules/java/io/qt/qbs/ArtifactListWriter.java @@ -0,0 +1,39 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 Jake Petroules. + ** 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. + ** + ****************************************************************************/ + +package io.qt.qbs; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +public interface ArtifactListWriter { + void write(List artifacts, OutputStream outputStream) throws IOException; +} diff --git a/share/qbs/modules/java/io/qt/qbs/tools/JavaCompilerScannerTool.java b/share/qbs/modules/java/io/qt/qbs/tools/JavaCompilerScannerTool.java new file mode 100644 index 00000000..10df621e --- /dev/null +++ b/share/qbs/modules/java/io/qt/qbs/tools/JavaCompilerScannerTool.java @@ -0,0 +1,51 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 Jake Petroules. + ** 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. + ** + ****************************************************************************/ + +package io.qt.qbs.tools; + +import io.qt.qbs.tools.utils.JavaCompilerScanner; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; + +public class JavaCompilerScannerTool { + public static void main(String[] args) { + try { + JavaCompilerScanner scanner = new JavaCompilerScanner(); + int result = scanner.run(new ArrayList(Arrays.asList(args))); + scanner.write(System.out); + System.exit(result); + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + } + } +} diff --git a/share/qbs/modules/java/io/qt/qbs/tools/utils/JavaCompilerOptions.java b/share/qbs/modules/java/io/qt/qbs/tools/utils/JavaCompilerOptions.java new file mode 100644 index 00000000..7bb5b41b --- /dev/null +++ b/share/qbs/modules/java/io/qt/qbs/tools/utils/JavaCompilerOptions.java @@ -0,0 +1,102 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 Jake Petroules. + ** 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. + ** + ****************************************************************************/ + +package io.qt.qbs.tools.utils; + +import javax.lang.model.SourceVersion; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileManager; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class JavaCompilerOptions { + private final List recognizedOptions; + private final List classNames; + private final List files; + + private JavaCompilerOptions(List recognizedOptions, List classNames, List files) { + this.recognizedOptions = recognizedOptions; + this.classNames = classNames; + this.files = files; + } + + public static JavaCompilerOptions parse(JavaCompiler compiler, + JavaFileManager fileManager, String... arguments) { + List recognizedOptions = new ArrayList(); + List classNames = new ArrayList(); + List files = new ArrayList(); + + for (int i = 0; i < arguments.length; ++i) { + int argumentCount = compiler.isSupportedOption(arguments[i]); + if (argumentCount < 0) + argumentCount = fileManager.isSupportedOption(arguments[i]); + + if (argumentCount >= 0) { + + // isSupportedOption() returns 1 for -Xlint* in Java 9. Bug? + if (arguments[i].startsWith("-Xlint")) + argumentCount = 0; + + for (int j = 0; j < argumentCount + 1; ++j) { + if (i + j >= arguments.length) { + throw new IllegalArgumentException(arguments[i]); + } + recognizedOptions.add(arguments[i + j]); + } + + i += argumentCount; + } else { + File file = new File(arguments[i]); + if (file.exists()) + files.add(file); + else if (SourceVersion.isName(arguments[i])) + classNames.add(arguments[i]); + else + throw new IllegalArgumentException(arguments[i]); + } + } + + return new JavaCompilerOptions(recognizedOptions, classNames, files); + } + + public List getRecognizedOptions() { + return Collections.unmodifiableList(recognizedOptions); + } + + public List getFiles() { + return Collections.unmodifiableList(files); + } + + public List getClassNames() { + return Collections.unmodifiableList(classNames); + } +} diff --git a/share/qbs/modules/java/io/qt/qbs/tools/utils/JavaCompilerScanner.java b/share/qbs/modules/java/io/qt/qbs/tools/utils/JavaCompilerScanner.java new file mode 100644 index 00000000..ef655500 --- /dev/null +++ b/share/qbs/modules/java/io/qt/qbs/tools/utils/JavaCompilerScanner.java @@ -0,0 +1,107 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 Jake Petroules. + ** 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. + ** + ****************************************************************************/ + +package io.qt.qbs.tools.utils; + +import io.qt.qbs.*; + +import javax.tools.*; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.*; + +public class JavaCompilerScanner { + private List artifacts = new ArrayList(); + private ArtifactListWriter writer = new ArtifactListJsonWriter(); + + public List getArtifacts() { + return this.artifacts; + } + + public int run(List compilerArguments) { + artifacts.clear(); + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager standardFileManager = compiler + .getStandardFileManager(null, null, null); + final JavaFileManager fileManager = new ForwardingJavaFileManager( + standardFileManager) { + @Override + public JavaFileObject getJavaFileForOutput( + JavaFileManager.Location location, String className, + JavaFileObject.Kind kind, FileObject sibling) + throws IOException { + JavaFileObject o = super.getJavaFileForOutput(location, + className, kind, sibling); + Artifact artifact = new Artifact(new File(o.toUri() + .getSchemeSpecificPart()).toString()); + if (kind.equals(JavaFileObject.Kind.CLASS)) { + artifact.addFileTag("java.class"); + } else if (kind.equals(JavaFileObject.Kind.SOURCE)) { + artifact.addFileTag("java.java"); + } + artifacts.add(artifact); + return new NullFileObject(o); + } + + @Override + public FileObject getFileForOutput( + JavaFileManager.Location location, String packageName, + String relativeName, FileObject sibling) throws IOException { + FileObject o = super.getFileForOutput(location, packageName, + relativeName, sibling); + Artifact artifact = new Artifact(new File(o.toUri() + .getSchemeSpecificPart()).toString()); + if (o.getName().endsWith(".h")) { + artifact.addFileTag("hpp"); + } + artifacts.add(artifact); + return new NullFileObject(o); + } + }; + + final JavaCompilerOptions options = JavaCompilerOptions + .parse(compiler, standardFileManager, compilerArguments + .toArray(new String[compilerArguments.size()])); + + return compiler.getTask( + null, + fileManager, + null, + options.getRecognizedOptions(), + options.getClassNames(), + standardFileManager.getJavaFileObjectsFromFiles(options + .getFiles())).call() ? 0 : 1; + } + + public void write(OutputStream outputStream) throws IOException { + writer.write(getArtifacts(), outputStream); + } +} diff --git a/share/qbs/modules/java/io/qt/qbs/tools/utils/NullFileObject.java b/share/qbs/modules/java/io/qt/qbs/tools/utils/NullFileObject.java new file mode 100644 index 00000000..d74a69fb --- /dev/null +++ b/share/qbs/modules/java/io/qt/qbs/tools/utils/NullFileObject.java @@ -0,0 +1,147 @@ +/**************************************************************************** + ** + ** Copyright (C) 2015 Jake Petroules. + ** 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. + ** + ****************************************************************************/ + +package io.qt.qbs.tools.utils; + +import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; +import javax.tools.FileObject; +import javax.tools.JavaFileObject; +import java.io.*; +import java.net.URI; + +/** + * Represents a FileObject that discards its output when written. + */ +public class NullFileObject implements FileObject, JavaFileObject { + FileObject obj; + + public NullFileObject(FileObject obj) { + this.obj = obj; + } + + @Override + public URI toUri() { + return obj.toUri(); + } + + @Override + public String getName() { + return obj.getName(); + } + + @Override + public InputStream openInputStream() throws IOException { + return obj.openInputStream(); + } + + @Override + public OutputStream openOutputStream() throws IOException { + return new OutputStream() { + @Override + public void write(int b) throws IOException { + } + }; + } + + @Override + public Reader openReader(boolean ignoreEncodingErrors) throws IOException { + return obj.openReader(ignoreEncodingErrors); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) + throws IOException { + return obj.getCharContent(ignoreEncodingErrors); + } + + @Override + public Writer openWriter() throws IOException { + return new Writer() { + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + } + + @Override + public void flush() throws IOException { + } + + @Override + public void close() throws IOException { + } + }; + } + + @Override + public long getLastModified() { + return obj.getLastModified(); + } + + @Override + public boolean delete() { + return true; + } + + @Override + public Kind getKind() { + if (obj instanceof JavaFileObject) { + return ((JavaFileObject) obj).getKind(); + } + + throw new UnsupportedOperationException(); + } + + @Override + public boolean isNameCompatible(String simpleName, Kind kind) { + if (obj instanceof JavaFileObject) { + return ((JavaFileObject) obj).isNameCompatible(simpleName, kind); + } + + throw new UnsupportedOperationException(); + } + + @Override + public NestingKind getNestingKind() { + if (obj instanceof JavaFileObject) { + return ((JavaFileObject) obj).getNestingKind(); + } + + throw new UnsupportedOperationException(); + } + + @Override + public Modifier getAccessLevel() { + if (obj instanceof JavaFileObject) { + return ((JavaFileObject) obj).getAccessLevel(); + } + + throw new UnsupportedOperationException(); + } +} diff --git a/share/qbs/modules/java/utils.js b/share/qbs/modules/java/utils.js new file mode 100644 index 00000000..0e11b7d3 --- /dev/null +++ b/share/qbs/modules/java/utils.js @@ -0,0 +1,361 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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 File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var ModUtils = require("qbs.ModUtils"); +var Process = require("qbs.Process"); +var TextFile = require("qbs.TextFile"); +var Utilities = require("qbs.Utilities"); +var WindowsUtils = require("qbs.WindowsUtils"); + +function is64bitProcess() { + var y = jdkRootRegistryKey(true); + var n = jdkRootRegistryKey(false); + y = Utilities.getNativeSetting(y + "\\" + Utilities.getNativeSetting(y, "CurrentVersion"), "JavaHome"); + n = Utilities.getNativeSetting(n + "\\" + Utilities.getNativeSetting(n, "CurrentVersion"), "JavaHome"); + return y !== n; +} + +function useWow64Key(arch) { + var wow64 = false; + switch (arch) { + case "x86_64": + case "ia64": + // QTBUG-3845 + if (!is64bitProcess()) + return undefined; + break; + case "x86": + case "armv7": + wow64 = is64bitProcess(); + break; + } + return wow64; +} + +function jdkRootRegistryKey(wow64) { + // If an architecture is specified, search the appropriate key for that architecture, + // on this version of Windows (i.e. WOW64 or not) if compatible, + // otherwise get both keys since any JDK will be usable + if (wow64 === undefined) + return undefined; + return FileInfo.toWindowsSeparators(FileInfo.joinPaths("HKEY_LOCAL_MACHINE", "SOFTWARE", + (wow64 ? "Wow6432Node" : undefined), + "JavaSoft", "Java Development Kit")); +} + +function findJdkPath(hostOS, arch, environmentPaths, searchPaths) { + var i; + for (var key in environmentPaths) { + if (environmentPaths[key]) { + return environmentPaths[key]; + } + } + + if (hostOS.contains("windows")) { + var rootKey = jdkRootRegistryKey(useWow64Key(arch)); + if (rootKey) { + var current = Utilities.getNativeSetting(rootKey, "CurrentVersion"); // 1.8 etc. + if (current) { + var home = Utilities.getNativeSetting([rootKey, current].join("\\"), "JavaHome"); + if (home) { + return home; + } + } + } + + return undefined; + } + + if (hostOS.contains("macos")) { + var p = new Process(); + try { + // We filter by architecture here so that we'll get a compatible JVM for JNI use. + var args = []; + if (arch) { + // Hardcoding apple/macosx/macho here is fine because we know we're on macOS + args.push("--arch", + Utilities.canonicalTargetArchitecture(arch, undefined, + "apple", "macosx", "macho")); + } + + // --failfast doesn't print the default JVM if nothing matches the filter(s). + var status = p.exec("/usr/libexec/java_home", args.concat(["--failfast"])); + return status === 0 ? p.readStdOut().trim() : undefined; + } finally { + p.close(); + } + } + + if (hostOS.contains("unix")) { + var requiredTools = ["javac", "java", "jar"]; + for (i = 0; i < searchPaths.length; ++i) { + function fullToolPath(tool) { + return FileInfo.joinPaths(searchPaths[i], "bin", tool); + } + + if (requiredTools.map(function(p) { return fullToolPath(p); }) + .every(function(p) { return File.exists(p); })) { + return searchPaths[i]; + } + } + + return undefined; + } +} + +function findJdkVersion(compilerFilePath) { + var p = new Process(); + try { + p.exec(compilerFilePath, ["-version"]); + var re = /^javac (([0-9]+(?:\.[0-9]+){0,2})(_([0-9]+))?)(.*)?$/m; + var match = p.readStdErr().trim().match(re); + if (!match) + match = p.readStdOut().trim().match(re); + if (match !== null) + return match; + } finally { + p.close(); + } +} + +function splitVersionString(compilerVersion) { + if (!compilerVersion) + return []; + + var result = compilerVersion.split(/[\._]/).map(function(item) { + return parseInt(item, 10); + }); + // special case, if javac -version returned "12" instead of "12.0.0" + if (result.length === 1) + result.push(0, 0); + return result; +} + +function supportsGeneratedNativeHeaderFiles(product) { + var compilerVersionMajor = ModUtils.moduleProperty(product, "compilerVersionMajor"); + if (compilerVersionMajor === 1) { + if (ModUtils.moduleProperty(product, "compilerVersionMinor") >= 8) { + return true; + } + } + + return compilerVersionMajor > 1; +} + +function javacArguments(product, inputs, overrides) { + function getModuleProperty(product, propertyName, overrides) { + if (overrides && overrides[propertyName]) + return overrides[propertyName]; + return ModUtils.moduleProperty(product, propertyName); + } + + function getModuleProperties(product, propertyName, overrides) { + if (overrides && overrides[propertyName]) + return overrides[propertyName]; + return ModUtils.moduleProperty(product, propertyName); + } + + var i; + var outputDir = getModuleProperty(product, "classFilesDir", overrides); + var classPaths = [outputDir]; + var additionalClassPaths = getModuleProperties(product, "additionalClassPaths", overrides); + if (additionalClassPaths) + classPaths = classPaths.concat(additionalClassPaths); + for (i in inputs["java.jar"]) + classPaths.push(inputs["java.jar"][i].filePath); + var debugArg = product.moduleProperty("qbs", "buildVariant") === "debug" + ? "-g" : "-g:none"; + var pathListSeparator = product.moduleProperty("qbs", "pathListSeparator"); + var args = [ + "-classpath", classPaths.join(pathListSeparator), + "-s", product.buildDirectory, + debugArg, "-d", outputDir + ]; + if (supportsGeneratedNativeHeaderFiles(product)) + args.push("-h", product.buildDirectory); + var runtimeVersion = getModuleProperty(product, "runtimeVersion", overrides); + if (runtimeVersion) + args.push("-target", runtimeVersion); + var languageVersion = getModuleProperty(product, "languageVersion", overrides); + if (languageVersion) + args.push("-source", languageVersion); + var bootClassPaths = getModuleProperties(product, "bootClassPaths", overrides); + if (bootClassPaths && bootClassPaths.length > 0 + && (!runtimeVersion || Utilities.versionCompare(runtimeVersion, "9") < 0)) { + args.push("-bootclasspath", bootClassPaths.join(pathListSeparator)); + } + if (!getModuleProperty(product, "enableWarnings", overrides)) + args.push("-nowarn"); + if (getModuleProperty(product, "warningsAsErrors", overrides)) + args.push("-Werror"); + var otherFlags = getModuleProperties(product, "additionalCompilerFlags", overrides); + if (otherFlags) + args = args.concat(otherFlags); + for (i in inputs["java.java"]) + args.push(inputs["java.java"][i].filePath); + for (i in inputs["java.java-internal"]) + args.push(inputs["java.java-internal"][i].filePath); + return args; +} + +/** + * Returns a list of fully qualified Java class names for the compiler helper tool. + * + * @param type @c java to return names of sources, @c to return names of compiled classes + */ +function helperFullyQualifiedNames(type) { + var names = [ + "io/qt/qbs/Artifact", + "io/qt/qbs/ArtifactListJsonWriter", + "io/qt/qbs/ArtifactListWriter", + "io/qt/qbs/tools/JavaCompilerScannerTool", + "io/qt/qbs/tools/utils/JavaCompilerOptions", + "io/qt/qbs/tools/utils/JavaCompilerScanner", + "io/qt/qbs/tools/utils/JavaCompilerScanner$1", + "io/qt/qbs/tools/utils/NullFileObject", + "io/qt/qbs/tools/utils/NullFileObject$1", + "io/qt/qbs/tools/utils/NullFileObject$2", + ]; + if (type === "java") { + return names.filter(function (name) { + return !name.contains("$"); + }); + } else if (type === "class") { + return names; + } +} + +function helperOutputArtifacts(product) { + File.makePath(ModUtils.moduleProperty(product, "internalClassFilesDir")); + return helperFullyQualifiedNames("class").map(function (name) { + return { + filePath: FileInfo.joinPaths(ModUtils.moduleProperty(product, "internalClassFilesDir"), + name + ".class"), + fileTags: ["java.class-internal"] + }; + }); +} + +function helperOverrideArgs(product, tool) { + var overrides = {}; + if (tool === "javac") { + // Build the helper tool with the same source and target version as the JDK it's being + // compiled with. Both are irrelevant here since the resulting tool will only be run + // with the same JDK as it was built with, and we know in advance the source is + // compatible with all Java language versions from 1.6 and above. + var jdkVersionArray = [product.java.compilerVersionMajor]; + if (product.java.compilerVersionMajor < 9) + jdkVersionArray.push(product.java.compilerVersionMinor); + var jdkVersion = jdkVersionArray.join("."); + overrides["languageVersion"] = jdkVersion; + overrides["runtimeVersion"] = jdkVersion; + + // Build the helper tool's class files separately from the actual product's class files + overrides["classFilesDir"] = ModUtils.moduleProperty(product, "internalClassFilesDir"); + + // Add tools.jar to the classpath as required for the tree scanner API + var toolsJarPath = ModUtils.moduleProperty(product, "toolsJarPath"); + if (toolsJarPath) + overrides["additionalClassPaths"] = [toolsJarPath].concat( + ModUtils.moduleProperty(product, "additionalClassPaths")); + } + + // Inject the current JDK's runtime classes into the boot class path when building/running the + // dependency scanner. This is normally not necessary but is important for Android platforms + // where android.jar is the only JAR on the boot classpath and JSR 199 is unavailable. + var rtJarPath = product.java.runtimeJarPath; + overrides["bootClassPaths"] = (rtJarPath ? [rtJarPath] : []).concat( + ModUtils.moduleProperty(product, "bootClassPaths")); + return overrides; +} + +function outputArtifacts(product, inputs) { + // Handle the case where a product depends on Java but has no Java sources + if (!inputs["java.java"] || inputs["java.java"].length === 0) + return []; + + // We need to ensure that the output directory is created first, because the Java compiler + // internally checks that it is present before performing any actions + File.makePath(ModUtils.moduleProperty(product, "classFilesDir")); + + var process; + try { + process = new Process(); + process.setWorkingDirectory( + FileInfo.joinPaths(ModUtils.moduleProperty(product, "internalClassFilesDir"))); + + var sep = product.moduleProperty("qbs", "pathListSeparator"); + var toolsJarPath = ModUtils.moduleProperty(product, "toolsJarPath"); + var javaArgs = [ + "-classpath", process.workingDirectory() + (toolsJarPath ? (sep + toolsJarPath) : ""), + "io/qt/qbs/tools/JavaCompilerScannerTool", + ]; + process.exec(ModUtils.moduleProperty(product, "interpreterFilePath"), javaArgs + .concat(javacArguments(product, inputs, helperOverrideArgs(product))), true); + var out = JSON.parse(process.readStdOut()); + console.error(process.readStdErr()); + return out; + } finally { + if (process) + process.close(); + } +} + +function manifestContents(filePath) { + if (filePath === undefined) + return undefined; + + var contents, file; + try { + file = new TextFile(filePath); + contents = file.readAll(); + } finally { + if (file) { + file.close(); + } + } + + if (contents) { + var dict = {}; + var lines = contents.split(/\r?\n/g).filter(function (line) { return line.length > 0; }); + for (var i in lines) { + var kv = lines[i].split(":"); + if (kv.length !== 2) + throw new Error("Syntax error in manifest file '" + + filePath + "'; found \"" + lines[i] + "\" on line " + + parseInt(i, 10) + "; expected format \"Key: Value\""); + dict[kv[0].trim()] = kv[1].trim(); + } + + return dict; + } +} diff --git a/share/qbs/modules/lex_yacc/lexyacc.js b/share/qbs/modules/lex_yacc/lexyacc.js new file mode 100644 index 00000000..31474d32 --- /dev/null +++ b/share/qbs/modules/lex_yacc/lexyacc.js @@ -0,0 +1,84 @@ +var FileInfo = require("qbs.FileInfo"); +var TextFile = require("qbs.TextFile"); + +function unquote(s) +{ + return s.startsWith('"') && s.endsWith('"') ? s.substr(1, s.length - 2) : s; +} + +function readLexOptions(filePath) { + var result = {}; + var f = new TextFile(filePath, TextFile.ReadOnly); + var regex = /^%option\s+([^ \t=]+)(?:\s*=\s*(\S+))?/; + while (!f.atEof()) { + var line = f.readLine(); + var m = regex.exec(line); + if (!m) { + if (line === "%%") + break; + continue; + } + result[m[1]] = m[2] || true; + } + f.close(); + return result; +} + +function lexOutputFilePath(input, posixFileName, options) +{ + var outDir = input.lex_yacc.outputDir; + var fileName; + if (options.outfile) { + fileName = unquote(options.outfile); + } else if (options.prefix) { + fileName = FileInfo.baseName(posixFileName) + '.' + + unquote(options.prefix) + '.' + + FileInfo.suffix(posixFileName); + } else if (input.lex_yacc.uniqueSymbolPrefix) { + fileName = input.baseName; + fileName += posixFileName; + } else { + fileName = posixFileName; + } + return FileInfo.joinPaths(outDir, fileName); +} + +function readYaccOptions(filePath) { + var result = {}; + var f = new TextFile(filePath, TextFile.ReadOnly); + var regex = /^%output\s+(.+)/; + while (!f.atEof()) { + var line = f.readLine(); + var m = regex.exec(line); + if (!m) { + if (line === "%%") + break; + continue; + } + result.output = m[1]; + break; + } + f.close(); + return result; +} + +function yaccOutputFilePath(input, posixFileName, options) +{ + var outDir = input.lex_yacc.outputDir; + var fileName; + if (options.output) { + var outputFileName = unquote(options.output); + var suffix = FileInfo.suffix(posixFileName); + if (suffix === "c") { + fileName = outputFileName; + } else { + fileName = FileInfo.completeBaseName(outputFileName) + + '.' + suffix + FileInfo.suffix(outputFileName).slice(1); + } + } else if (input.lex_yacc.uniqueSymbolPrefix) { + fileName = input.baseName + posixFileName.slice(1); + } else { + fileName = posixFileName; + } + return FileInfo.joinPaths(outDir, fileName); +} diff --git a/share/qbs/modules/lex_yacc/lexyacc.qbs b/share/qbs/modules/lex_yacc/lexyacc.qbs new file mode 100644 index 00000000..48af1d79 --- /dev/null +++ b/share/qbs/modules/lex_yacc/lexyacc.qbs @@ -0,0 +1,102 @@ +import "lexyacc.js" as HelperFunctions + +Module { + Depends { name: "cpp" } + + property bool enableCompilerWarnings: false + property string lexBinary: "lex" + property string yaccBinary: "yacc" + property string outputTag: "c" + property bool uniqueSymbolPrefix: false + property string lexOutputFilePath + property string yaccOutputFilePath + property stringList lexFlags: [] + property stringList yaccFlags: [] + + readonly property string outputDir: product.buildDirectory + "/lexyacc" + + Rule { + inputs: ["lex.input"] + outputFileTags: [product.lex_yacc.outputTag] + outputArtifacts: { + var output = { + fileTags: [product.lex_yacc.outputTag], + lex_yacc: {}, + }; + var options = HelperFunctions.readLexOptions(input.filePath); + if (!options.outfile && input.lex_yacc.lexOutputFilePath) { + options.outfile = input.lex_yacc.lexOutputFilePath; + output.lex_yacc.useOutfileFromModule = true; + } + output.filePath = HelperFunctions.lexOutputFilePath(input, "lex.yy.c", options); + output.cpp = { + includePaths: [].concat(input.cpp.includePaths, input.lex_yacc.outputDir), + warningLevel: input.lex_yacc.enableCompilerWarnings ? "all" : "none", + }; + return [output]; + } + prepare: { + var args = input.lex_yacc.lexFlags; + if (output.lex_yacc.useOutfileFromModule) + args.push("-o" + input.lex_yacc.lexOutputFilePath); + else if (input.lex_yacc.uniqueSymbolPrefix) + args.push("-P" + input.baseName, "-o" + output.filePath); + args.push(input.filePath); + var cmd = new Command(input.lex_yacc.lexBinary, args); + cmd.workingDirectory = input.lex_yacc.outputDir; + cmd.description = "generating " + output.fileName; + return [cmd]; + } + } + + Rule { + inputs: ["yacc.input"] + outputFileTags: [product.lex_yacc.outputTag, "hpp"] + outputArtifacts: { + var src = { + fileTags: [product.lex_yacc.outputTag], + lex_yacc: {}, + }; + var options = HelperFunctions.readYaccOptions(input.filePath); + if (!options.output && input.lex_yacc.yaccOutputFilePath) { + options.output = input.lex_yacc.yaccOutputFilePath; + src.lex_yacc.useOutputFromModule = true; + } + var hdr = { + filePath: HelperFunctions.yaccOutputFilePath(input, "y.tab.h", options), + fileTags: ["hpp"], + }; + src.filePath = HelperFunctions.yaccOutputFilePath(input, "y.tab.c", options); + src.cpp = { + includePaths: [].concat(input.cpp.includePaths, input.lex_yacc.outputDir), + warningLevel: input.lex_yacc.enableCompilerWarnings ? "all" : "none", + }; + return [hdr, src]; + } + prepare: { + var args = input.lex_yacc.yaccFlags; + args.push("-d"); + var impl = outputs[input.lex_yacc.outputTag][0]; + if (impl.lex_yacc.useOutputFromModule) + args.push("-o" + input.lex_yacc.yaccOutputFilePath); + else if (input.lex_yacc.uniqueSymbolPrefix) + args.push("-b", input.baseName, "-p", input.baseName); + args.push(input.filePath); + var cmd = new Command(input.lex_yacc.yaccBinary, args); + cmd.workingDirectory = input.lex_yacc.outputDir; + cmd.description = "generating " + + impl.fileName + + " and " + outputs["hpp"][0].fileName; + return [cmd]; + } + } + + FileTagger { + patterns: ["*.l"] + fileTags: ["lex.input"]; + } + FileTagger { + patterns: ["*.y"] + fileTags: ["yacc.input"]; + } +} diff --git a/share/qbs/modules/nodejs/NodeJS.qbs b/share/qbs/modules/nodejs/NodeJS.qbs new file mode 100644 index 00000000..7fd992fd --- /dev/null +++ b/share/qbs/modules/nodejs/NodeJS.qbs @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.Environment +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Probes + +Module { + // JavaScript files which have been "processed" - currently this simply means "copied to output + // directory" but might later include minification and obfuscation processing + additionalProductTypes: ["nodejs_processed_js"].concat(applicationFile ? ["application"] : []) + + Probes.NodeJsProbe { + id: nodejs + searchPaths: toolchainInstallPath ? [toolchainInstallPath] : [] + } + + Probes.NpmProbe { + id: npm + searchPaths: toolchainInstallPath ? [toolchainInstallPath] : [] + interpreterPath: FileInfo.path(nodejs.filePath) + } + + property path applicationFile + PropertyOptions { + name: "applicationFile" + description: "file whose corresponding output will be executed when running the Node.js app" + } + + Group { + name: "Application file"; + prefix: product.sourceDirectory + '/' + files: nodejs.applicationFile ? [nodejs.applicationFile] : [] + } + + property path toolchainInstallPath: { + if (nodejs.path && npm.path && nodejs.path !== npm.path) + throw("node and npm binaries do not belong to the same installation (" + + nodejs.path + " vs " + npm.path + ")"); + return nodejs.path || npm.path; + } + + property path interpreterFileName: nodejs.fileName + property path interpreterFilePath: nodejs.filePath + + property path packageManagerFileName: npm.fileName + property path packageManagerFilePath: npm.filePath + + property path packageManagerBinPath: npm.npmBin + property path packageManagerRootPath: npm.npmRoot + property path packageManagerPrefixPath: npm.npmPrefix + + // private properties + readonly property path compiledIntermediateDir: FileInfo.joinPaths(product.buildDirectory, + "tmp", "nodejs.intermediate") + + setupBuildEnvironment: { + var v = new ModUtils.EnvironmentVariable("PATH", product.qbs.pathListSeparator, product.qbs.hostOS.contains("windows")); + v.prepend(product.nodejs.toolchainInstallPath); + v.set(); + } + + setupRunEnvironment: { + var v = new ModUtils.EnvironmentVariable("NODE_PATH", product.qbs.pathListSeparator, product.qbs.hostOS.contains("windows")); + v.prepend(FileInfo.path(Environment.getEnv("QBS_RUN_FILE_PATH"))); + v.set(); + } + + FileTagger { + patterns: ["*.js"] + fileTags: ["js"] + } + + validate: { + var validator = new ModUtils.PropertyValidator("nodejs"); + validator.setRequiredProperty("toolchainInstallPath", toolchainInstallPath); + validator.setRequiredProperty("interpreterFileName", interpreterFileName); + validator.setRequiredProperty("interpreterFilePath", interpreterFilePath); + validator.setRequiredProperty("packageManagerFileName", packageManagerFileName); + validator.setRequiredProperty("packageManagerFilePath", packageManagerFilePath); + validator.setRequiredProperty("packageManagerBinPath", packageManagerBinPath); + validator.setRequiredProperty("packageManagerRootPath", packageManagerRootPath); + validator.setRequiredProperty("packageManagerPrefixPath", packageManagerPrefixPath); + validator.validate(); + } + + Rule { + inputs: ["js"] + + outputArtifacts: { + var tags = ["nodejs_processed_js"]; + if (input.fileTags.contains("application_js") || + product.moduleProperty("nodejs", "applicationFile") === input.filePath) + tags.push("application"); + + // Preserve directory structure of input files + var intermediatePath = product.sourceDirectory; + + // Handle nodejs.compiledIntermediateDir (QBS-5 workaround) + var compiled = product.moduleProperty("nodejs", "compiledIntermediateDir"); + if (input.filePath.startsWith(compiled)) { + intermediatePath = compiled; + } + + intermediatePath = FileInfo.path(FileInfo.relativePath(intermediatePath, + input.filePath)); + + return [{ + filePath: FileInfo.joinPaths(product.destinationDirectory, intermediatePath, + input.fileName), + fileTags: tags + }]; + } + + outputFileTags: ["nodejs_processed_js", "application"] + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "copying " + input.fileName; + cmd.sourceCode = function() { + File.copy(input.filePath, output.filePath); + }; + return cmd; + } + } +} diff --git a/share/qbs/modules/nodejs/nodejs.js b/share/qbs/modules/nodejs/nodejs.js new file mode 100644 index 00000000..c329b7fc --- /dev/null +++ b/share/qbs/modules/nodejs/nodejs.js @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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 Process = require("qbs.Process"); + +function findLocation(packageManagerFilePath, location, nodejsPath) { + var p = new Process(); + try { + if (nodejsPath) + p.setEnv("PATH", nodejsPath); + if (p.exec(packageManagerFilePath, [location, "-g"]) !== 0) + console.error(p.readStdErr().trim()); + return p.readStdOut().trim(); + } finally { + p.close(); + } +} diff --git a/share/qbs/modules/nsis/NSISModule.qbs b/share/qbs/modules/nsis/NSISModule.qbs new file mode 100644 index 00000000..e426aee5 --- /dev/null +++ b/share/qbs/modules/nsis/NSISModule.qbs @@ -0,0 +1,242 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Utilities + +Module { + condition: qbs.targetOS.contains("windows") + + property path toolchainInstallPath: Utilities.getNativeSetting(registryKey) + + version: (versionMajor !== undefined && versionMinor !== undefined) ? (versionMajor + "." + versionMinor) : undefined + property var versionParts: [ versionMajor, versionMinor, versionPatch, versionBuild ] + property int versionMajor: Utilities.getNativeSetting(registryKey, "VersionMajor") + property int versionMinor: Utilities.getNativeSetting(registryKey, "VersionMinor") + property int versionPatch: Utilities.getNativeSetting(registryKey, "VersionBuild") + property int versionBuild: Utilities.getNativeSetting(registryKey, "VersionRevision") + + property string compilerName: "makensis" + property string compilerPath: compilerName + + property string warningLevel: "normal" + PropertyOptions { + name: "warningLevel" + allowedValues: ["none", "normal", "errors", "warnings", "info", "all"] + } + + property bool disableConfig: false + PropertyOptions { + name: "disableConfig" + description: "disable inclusion of nsisconf.nsh" + } + + property bool enableQbsDefines: true + PropertyOptions { + name: "enableQbsDefines" + description: "built-in variables that are defined when using the NSIS compiler" + } + + property stringList defines + PropertyOptions { + name: "defines" + description: "variables that are defined when using the NSIS compiler" + } + + property stringList compilerFlags + PropertyOptions { + name: "compilerFlags" + description: "additional flags for the NSIS compiler" + } + + property string compressor: "default" + PropertyOptions { + name: "compressor" + description: "the compression algorithm used to compress files/data in the installer" + allowedValues: ["default", "zlib", "zlib-solid", "bzip2", "bzip2-solid", "lzma", "lzma-solid"] + } + + property string executableSuffix: ".exe" + + // Private properties + property string registryKey: { + if (!qbs.hostOS.contains("windows")) + return undefined; + + var keys = [ "HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\NSIS" ]; + for (var i in keys) { + if (Utilities.getNativeSetting(keys[i])) + return keys[i]; + } + } + + validate: { + var validator = new ModUtils.PropertyValidator("nsis"); + + // Only *require* the toolchain install path on Windows + // On other (Unix-like) operating systems it'll probably be in the PATH + if (qbs.targetOS.contains("windows")) + validator.setRequiredProperty("toolchainInstallPath", toolchainInstallPath); + + validator.setRequiredProperty("versionMajor", versionMajor); + validator.setRequiredProperty("versionMinor", versionMinor); + validator.setRequiredProperty("versionPatch", versionPatch); + validator.setRequiredProperty("versionBuild", versionBuild); + validator.addVersionValidator("version", version, 2, 4); + validator.addRangeValidator("versionMajor", versionMajor, 1); + validator.addRangeValidator("versionMinor", versionMinor, 0); + validator.addRangeValidator("versionPatch", versionPatch, 0); + validator.addRangeValidator("versionBuild", versionBuild, 0); + validator.validate(); + } + + setupBuildEnvironment: { + if (toolchainInstallPath) { + var v = new ModUtils.EnvironmentVariable("PATH", ";", true); + v.prepend(product.nsis.toolchainInstallPath); + v.prepend(FileInfo.joinPaths(product.nsis.toolchainInstallPath, "bin")); + v.set(); + } + } + + // NSIS Script File + FileTagger { + patterns: ["*.nsi"] + fileTags: ["nsi"] + } + + // NSIS Header File + FileTagger { + patterns: ["*.nsh"] + fileTags: ["nsh"] + } + + Rule { + id: nsisCompiler + multiplex: true + inputs: ["nsi"] + auxiliaryInputs: ["installable"] + + Artifact { + fileTags: ["nsissetup", "application"] + filePath: product.destinationDirectory + "/" + product.targetName + ModUtils.moduleProperty(product, "executableSuffix") + } + + prepare: { + var i; + var args = []; + + // Prefix character for makensis options + var opt = product.moduleProperty("qbs", "hostOS").contains("windows") ? "/" : "-"; + + if (ModUtils.moduleProperty(product, "disableConfig")) { + args.push(opt + "NOCONFIG"); + } + + var warningLevel = ModUtils.moduleProperty(product, "warningLevel"); + var warningLevels = ["none", "errors", "warnings", "info", "all"]; + if (warningLevel !== "normal") { + var level = warningLevels.indexOf(warningLevel); + if (level < 0) { + throw("Unexpected warning level '" + warningLevel + "'."); + } else { + args.push(opt + "V" + level); + } + } + + var enableQbsDefines = ModUtils.moduleProperty(product, "enableQbsDefines") + if (enableQbsDefines) { + var map = { + "project.": project, + "product.": product + }; + + for (var prefix in map) { + var obj = map[prefix]; + for (var prop in obj) { + var val = obj[prop]; + if (typeof val !== 'function' && typeof val !== 'object' && !prop.startsWith("_")) { + args.push(opt + "D" + prefix + prop + "=" + val); + } + } + } + + // Users are likely to need this + var arch = product.moduleProperty("qbs", "architecture"); + args.push(opt + "Dqbs.architecture=" + arch); + + // Helper define for alternating between 32-bit and 64-bit logic + if (arch === "x86_64" || arch === "ia64") { + args.push(opt + "DWin64"); + } + } + + // User-supplied defines + var defines = ModUtils.moduleProperty(product, "defines"); + for (i in defines) { + args.push(opt + "D" + defines[i]); + } + + // User-supplied flags + var flags = ModUtils.moduleProperty(product, "compilerFlags"); + for (i in flags) { + args.push(flags[i]); + } + + // Override the compression algorithm if needed + var compressor = ModUtils.moduleProperty(product, "compressor"); + if (compressor !== "default") { + var compressorFlag = opt + "XSetCompressor /FINAL "; + if (compressor.endsWith("-solid")) { + compressorFlag += "/SOLID "; + } + args.push(compressorFlag + compressor.split('-')[0]); + } + + var inputFileNames = []; + for (i in inputs.nsi) { + inputFileNames.push(inputs.nsi[i].fileName); + args.push(FileInfo.toNativeSeparators(inputs.nsi[i].filePath, + product.moduleProperty("qbs", "hostOS"))); + } + + // Output file name - this goes last to override any OutFile command in the script + args.push(opt + "XOutFile " + output.filePath); + + var cmd = new Command(ModUtils.moduleProperty(product, "compilerPath"), args); + cmd.description = "compiling " + inputFileNames.join(", "); + cmd.highlight = "compiler"; + cmd.workingDirectory = FileInfo.path(output.filePath); + return cmd; + } + } +} diff --git a/share/qbs/modules/pkgconfig/pkgconfig.qbs b/share/qbs/modules/pkgconfig/pkgconfig.qbs new file mode 100644 index 00000000..ec4b5918 --- /dev/null +++ b/share/qbs/modules/pkgconfig/pkgconfig.qbs @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +import qbs +import qbs.File +import qbs.Probes + +Module { + Probes.BinaryProbe { + id: pkgconfigProbe + names: "pkg-config" + } + + property path sysroot: { + if (qbs.targetOS.contains("macos")) + return ""; + return qbs.sysroot; + } + property string executableFilePath: pkgconfigProbe.filePath + property stringList libDirs + property bool staticMode: false + + validate: { + if (!executableFilePath) { + throw "No pkg-config executable found. " + + "Please set modules.pkgconfig.executableFilePath."; + } + if (!File.exists(executableFilePath)) + throw "The pkg-config executable '" + executableFilePath + "' does not exist."; + } +} diff --git a/share/qbs/modules/protobuf/cpp/protobufcpp.qbs b/share/qbs/modules/protobuf/cpp/protobufcpp.qbs new file mode 100644 index 00000000..bd3d9492 --- /dev/null +++ b/share/qbs/modules/protobuf/cpp/protobufcpp.qbs @@ -0,0 +1,120 @@ +import qbs +import qbs.File +import qbs.FileInfo +import qbs.Probes +import qbs.ModUtils +import "../protobufbase.qbs" as ProtobufBase +import "../protobuf.js" as HelperFunctions + +ProtobufBase { + property string includePath: includeProbe.path + property string libraryPath: libraryProbe.path + + property bool useGrpc: false + + property string grpcIncludePath: grpcIncludeProbe.path + property string grpcLibraryPath: grpcLibraryProbe.path + + Depends { name: "cpp" } + + property path grpcPluginPath: grpcPluginProbe.filePath + + Probes.BinaryProbe { + condition: useGrpc + id: grpcPluginProbe + names: "grpc_cpp_plugin" + } + + cpp.libraryPaths: { + var result = []; + if (libraryPath) + result.push(libraryPath); + if (useGrpc && grpcLibraryPath) + result.push(grpcLibraryPath); + return result; + } + cpp.dynamicLibraries: { + var result = ["protobuf"]; + if (qbs.targetOS.contains("unix")) + result.push("pthread"); + if (useGrpc) + result.push("grpc++"); + return result; + } + cpp.includePaths: { + var result = [_outputDir]; + if (includePath) + result.push(includePath); + if (useGrpc && grpcIncludePath) + result.push(grpcIncludePath); + return result; + } + + Rule { + inputs: ["protobuf.input", "protobuf.grpc"] + outputFileTags: ["hpp", "cpp"] + outputArtifacts: { + var outputDir = HelperFunctions.getOutputDir(input.protobuf.cpp, input); + var result = [ + HelperFunctions.cppArtifact(outputDir, input, "hpp", ".pb.h"), + HelperFunctions.cppArtifact(outputDir, input, "cpp", ".pb.cc") + ]; + if (input.fileTags.contains("protobuf.grpc")) { + result.push( + HelperFunctions.cppArtifact(outputDir, input, "hpp", ".grpc.pb.h"), + HelperFunctions.cppArtifact(outputDir, input, "cpp", ".grpc.pb.cc")); + } + + return result; + } + + prepare: { + var result = HelperFunctions.doPrepare( + input.protobuf.cpp, product, input, outputs, "cpp"); + if (input.fileTags.contains("protobuf.grpc")) { + result = ModUtils.concatAll(result, HelperFunctions.doPrepareGrpc( + input.protobuf.cpp, product, input, outputs, "cpp")); + } + return result; + } + } + + Probes.IncludeProbe { + id: includeProbe + names: "google/protobuf/message.h" + } + + Probes.LibraryProbe { + id: libraryProbe + names: "protobuf" + } + + Probes.IncludeProbe { + id: grpcIncludeProbe + pathSuffixes: "grpc++" + names: "grpc++.h" + } + + Probes.LibraryProbe { + id: grpcLibraryProbe + names: "grpc++" + } + + validate: { + HelperFunctions.validateCompiler(compilerName, compilerPath); + + if (!HelperFunctions.checkPath(includePath)) + throw "Can't find cpp protobuf include files. Please set the includePath property."; + if (!HelperFunctions.checkPath(libraryPath)) + throw "Can't find cpp protobuf library. Please set the libraryPath property."; + + if (useGrpc) { + if (!File.exists(grpcPluginPath)) + throw "Can't find grpc_cpp_plugin plugin. Please set the grpcPluginPath property."; + if (!HelperFunctions.checkPath(grpcIncludePath)) + throw "Can't find grpc++ include files. Please set the grpcIncludePath property."; + if (!HelperFunctions.checkPath(grpcLibraryPath)) + throw "Can't find grpc++ library. Please set the grpcLibraryPath property."; + } + } +} diff --git a/share/qbs/modules/protobuf/objc/protobufobjc.qbs b/share/qbs/modules/protobuf/objc/protobufobjc.qbs new file mode 100644 index 00000000..9a910bb7 --- /dev/null +++ b/share/qbs/modules/protobuf/objc/protobufobjc.qbs @@ -0,0 +1,64 @@ +import qbs +import qbs.File +import qbs.FileInfo +import qbs.Probes +import "../protobufbase.qbs" as ProtobufBase +import "../protobuf.js" as HelperFunctions + +ProtobufBase { + property string includePath: includeProbe.path + property string libraryPath: libraryProbe.path + property string frameworkPath: frameworkProbe.path + + Depends { name: "cpp" } + + cpp.includePaths: [_outputDir].concat(!frameworkPath && includePath ? [includePath] : []) + cpp.libraryPaths: !frameworkPath && libraryPath ? [libraryPath] : [] + cpp.dynamicLibraries: !frameworkPath && libraryPath ? ["ProtocolBuffers"] : [] + cpp.frameworkPaths: frameworkPath ? [frameworkPath] : [] + cpp.frameworks: ["Foundation"].concat(frameworkPath ? ["Protobuf"] : []) + cpp.defines: frameworkPath ? ["GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS"] : [] + + Rule { + inputs: ["protobuf.input"] + outputFileTags: ["hpp", "objc"] + outputArtifacts: { + var outputDir = HelperFunctions.getOutputDir(input.protobuf.objc, input); + return [ + HelperFunctions.objcArtifact(outputDir, input, "hpp", ".pbobjc.h"), + HelperFunctions.objcArtifact(outputDir, input, "objc", ".pbobjc.m") + ]; + } + + prepare: HelperFunctions.doPrepare(input.protobuf.objc, product, input, outputs, "objc") + } + + Probes.IncludeProbe { + id: includeProbe + names: "GPBMessage.h" + } + + Probes.LibraryProbe { + id: libraryProbe + names: "ProtocolBuffers" + } + + Probes.FrameworkProbe { + id: frameworkProbe + names: ["Protobuf"] + } + + validate: { + HelperFunctions.validateCompiler(compilerName, compilerPath); + if (!HelperFunctions.checkPath(frameworkPath)) { + if (!HelperFunctions.checkPath(includePath)) { + throw "Can't find objective-c protobuf include files. Please set the includePath " + + "or frameworkPath property."; + } + if (!HelperFunctions.checkPath(libraryPath)) { + throw "Can't find objective-c protobuf library. Please set the libraryPath or " + + "frameworkPath property."; + } + } + } +} diff --git a/share/qbs/modules/protobuf/protobuf.js b/share/qbs/modules/protobuf/protobuf.js new file mode 100644 index 00000000..8ab535ab --- /dev/null +++ b/share/qbs/modules/protobuf/protobuf.js @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://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 File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); + +function validateCompiler(compilerName, compilerPath) { + if (!File.exists(compilerPath)) { + throw "Can't find '" + compilerName + "' binary. Please set the compilerPath property or " + + "make sure the compiler is found in PATH"; + } +} + +function checkPath(path) { + return path && File.exists(path); +}; + +function toCamelCase(str){ + return str.split('_').map(function(word, index) { + // If it is the first word make sure to lowercase all the chars. + if (index === 0) { + return word.toLowerCase(); + } + // If it is not the first word only upper case the first char and lowercase the rest. + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + }).join(''); +} + +function getOutputDir(module, input) { + var outputDir = module._outputDir; + var importPaths = module.importPaths; + if (importPaths.length !== 0) { + var canonicalInput = File.canonicalFilePath(FileInfo.path(input.filePath)); + for (var i = 0; i < importPaths.length; ++i) { + var path = File.canonicalFilePath(importPaths[i]); + + if (canonicalInput.startsWith(path)) { + return outputDir + "/" + FileInfo.relativePath(path, canonicalInput); + } + } + } + return outputDir; +} + +function cppArtifact(outputDir, input, tag, suffix) { + return { + fileTags: [tag], + filePath: FileInfo.joinPaths(outputDir, FileInfo.baseName(input.fileName) + suffix), + cpp: { + includePaths: [].concat(input.cpp.includePaths, outputDir), + warningLevel: "none", + } + }; +} + +function objcArtifact(outputDir, input, tag, suffix) { + return { + fileTags: [tag], + filePath: FileInfo.joinPaths( + outputDir, toCamelCase(FileInfo.baseName(input.fileName)) + suffix), + cpp: { + includePaths: [].concat(input.cpp.includePaths, outputDir), + warningLevel: "none", + } + } +} + +function doPrepare(module, product, input, outputs, lang) +{ + var outputDir = module._outputDir; + var args = []; + + args.push("--" + lang + "_out", outputDir); + + var importPaths = module.importPaths; + if (importPaths.length === 0) + importPaths = [FileInfo.path(input.filePath)]; + importPaths.forEach(function(path) { + if (!FileInfo.isAbsolutePath(path)) + path = FileInfo.joinPaths(product.sourceDirectory, path); + args.push("--proto_path", path); + }); + + args.push(input.filePath); + + var cmd = new Command(module._compilerPath, args); + cmd.highlight = "codegen"; + cmd.description = "generating " + lang + " files for " + input.fileName; + return [cmd]; +} + +function doPrepareGrpc(module, product, input, outputs, lang) +{ + var outputDir = module._outputDir; + var args = []; + + args.push("--grpc_out", outputDir); + args.push("--plugin=protoc-gen-grpc=" + module.grpcPluginPath); + + var importPaths = module.importPaths; + if (importPaths.length === 0) + importPaths = [FileInfo.path(input.filePath)]; + importPaths.forEach(function(path) { + if (!FileInfo.isAbsolutePath(path)) + path = FileInfo.joinPaths(product.sourceDirectory, path); + args.push("--proto_path", path); + }); + + args.push(input.filePath); + + var cmd = new Command(module._compilerPath, args); + cmd.highlight = "codegen"; + cmd.description = "generating " + lang + " files for " + input.fileName; + return [cmd]; +} diff --git a/share/qbs/modules/protobuf/protobufbase.qbs b/share/qbs/modules/protobuf/protobufbase.qbs new file mode 100644 index 00000000..cf050e34 --- /dev/null +++ b/share/qbs/modules/protobuf/protobufbase.qbs @@ -0,0 +1,31 @@ +import qbs +import qbs.File +import qbs.FileInfo +import qbs.Probes +import "protobuf.js" as HelperFunctions + +Module { + property string compilerName: "protoc" + property string compilerPath: compilerProbe.filePath + + property string protocBinary + PropertyOptions { + name: "protocBinary" + description: "The path to the protoc binary. Deprecated, use compilerPath instead." + removalVersion: "1.18" + } + readonly property string _compilerPath: protocBinary ? protocBinary : compilerPath + property pathList importPaths: [] + + property string _outputDir: product.buildDirectory + "/protobuf" + + FileTagger { + patterns: ["*.proto"] + fileTags: ["protobuf.input"]; + } + + Probes.BinaryProbe { + id: compilerProbe + names: [compilerName] + } +} diff --git a/share/qbs/modules/qbs/common.qbs b/share/qbs/modules/qbs/common.qbs new file mode 100644 index 00000000..8ddfc582 --- /dev/null +++ b/share/qbs/modules/qbs/common.qbs @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.Environment +import qbs.FileInfo +import qbs.ModUtils +import qbs.PathTools +import qbs.Utilities + +Module { + readonly property string configurationName: "default" + property string defaultBuildVariant: { + switch (configurationName.toLowerCase()) { + case "release": + return "release"; + default: + return "debug"; + } + } + + property string buildVariant: defaultBuildVariant + + property bool enableDebugCode: buildVariant == "debug" + property bool debugInformation: (buildVariant !== "release") + property string optimization: (buildVariant === "debug" ? "none" : "fast") + readonly property string hostPlatform: undefined // set internally + readonly property stringList hostOS: Utilities.canonicalPlatform(hostPlatform) + property string hostOSVersion: { + if (hostOS && hostOS.contains("macos")) { + return Utilities.getNativeSetting("/System/Library/CoreServices/ServerVersion.plist", "ProductVersion") || + Utilities.getNativeSetting("/System/Library/CoreServices/SystemVersion.plist", "ProductVersion"); + } else if (hostOS && hostOS.contains("windows")) { + var version = Utilities.getNativeSetting(windowsRegistryKey, "CurrentVersion"); + return version + "." + hostOSBuildVersion; + } + } + readonly property string hostArchitecture: undefined // set internally + + property string hostOSBuildVersion: { + if (hostOS.contains("macos")) { + return Utilities.getNativeSetting("/System/Library/CoreServices/ServerVersion.plist", "ProductBuildVersion") || + Utilities.getNativeSetting("/System/Library/CoreServices/SystemVersion.plist", "ProductBuildVersion"); + } else if (hostOS.contains("windows")) { + return Utilities.getNativeSetting(windowsRegistryKey, "CurrentBuildNumber"); + } + } + + readonly property var hostOSVersionParts: hostOSVersion ? hostOSVersion.split('.').map(function(item) { return parseInt(item, 10); }) : [] + readonly property int hostOSVersionMajor: hostOSVersionParts[0] || 0 + readonly property int hostOSVersionMinor: hostOSVersionParts[1] || 0 + readonly property int hostOSVersionPatch: hostOSVersionParts[2] || 0 + + property string targetPlatform: hostPlatform + readonly property stringList targetOS: Utilities.canonicalPlatform(targetPlatform) + property string pathListSeparator: hostOS.contains("windows") ? ";" : ":" + property string pathSeparator: hostOS.contains("windows") ? "\\" : "/" + property string nullDevice: hostOS.contains("windows") ? "NUL" : "/dev/null" + property path shellPath: hostOS.contains("windows") ? windowsShellPath : "/bin/sh" + property string profile: project.profile + property string toolchainType: { + if (targetOS.contains("windows")) + return hostOS.contains("windows") ? "msvc" : "mingw"; + if (targetOS.contains("darwin")) + return hostOS.contains("macos") ? "xcode" : "clang"; + if (targetOS.contains("freebsd")) + return "clang"; + if (targetOS.contains("qnx")) + return "qcc"; + if (targetOS.containsAny(["haiku", "vxworks", "unix"])) + return "gcc"; + } + readonly property stringList toolchain: Utilities.canonicalToolchain(toolchainType) + property string architecture + property bool install: false + property path installSourceBase + property string installRoot: project.buildDirectory + "/install-root" + property string installDir + property string installPrefix: qbs.targetOS.contains("unix") ? "/usr/local" : "" + property path sysroot + + PropertyOptions { + name: "buildVariant" + allowedValues: ['debug', 'release', 'profiling'] + description: "name of the build variant" + } + + PropertyOptions { + name: "optimization" + allowedValues: ['none', 'fast', 'small'] + description: "optimization level" + } + + validate: { + var validator = new ModUtils.PropertyValidator("qbs"); + validator.setRequiredProperty("hostOS", hostOS); + validator.setRequiredProperty("targetOS", targetOS); + validator.addCustomValidator("targetOS", targetOS, function (value) { + if (!value || (value.contains("osx") && !value.contains("macos"))) + return false; + return true; + }, "the value 'osx' has been replaced by 'macos'; use that instead and update " + + "hostOS and targetOS condition checks in your project accordingly"); + if (hostOS && (hostOS.contains("windows") || hostOS.contains("macos"))) { + validator.setRequiredProperty("hostOSVersion", hostOSVersion, + "could not detect host operating system version; " + + "verify that system files and registry keys have not " + + "been modified."); + if (hostOSVersion) + validator.addVersionValidator("hostOSVersion", hostOSVersion, 2, 4); + + validator.setRequiredProperty("hostOSBuildVersion", hostOSBuildVersion, + "could not detect host operating system build version; " + + "verify that system files or registry have not been " + + "tampered with."); + } + + validator.addCustomValidator("architecture", architecture, function (value) { + return !architecture || architecture === Utilities.canonicalArchitecture(architecture); + }, "'" + architecture + "' is invalid. You must use the canonical name '" + + Utilities.canonicalArchitecture(architecture) + "'"); + + validator.addCustomValidator("toolchain", toolchain, function (value) { + if (toolchain === undefined) + return false; // cannot have null toolchain, empty is valid... for now + var canonical = Utilities.canonicalToolchain.apply(Utilities, toolchain); + for (var i = 0; i < Math.max(canonical.length, toolchain.length); ++i) { + if (canonical[i] !== toolchain[i]) + return false; + } + return true; + }, "'" + toolchain + "' is invalid. You must use the canonical list '" + + Utilities.canonicalToolchain.apply(Utilities, toolchain) + "'"); + + validator.addCustomValidator("toolchain", toolchain, function (value) { + // None of the pairs listed here may appear in the same toolchain list. + // Note that this check is applied AFTER canonicalization, so for example + // {"clang", "msvc"} need not be checked, since a toolchain containing clang is + // guaranteed to also contain gcc. + var pairs = [ + ["gcc", "msvc"], + ["llvm", "mingw"] + ]; + var canonical = Utilities.canonicalToolchain.apply(Utilities, value); + for (var i = 0; i < pairs.length; ++i) { + if (canonical.contains(pairs[i][0]) && canonical.contains(pairs[i][1])) + return false; + } + return true; + }, "'" + toolchain + "' contains one or more mutually exclusive toolchain types."); + + validator.validate(); + } + + // private properties + property string windowsRegistryKey: "HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion" + property path windowsSystemRoot: FileInfo.fromWindowsSeparators(Utilities.getNativeSetting(windowsRegistryKey, "SystemRoot")) + property path windowsShellPath: FileInfo.fromWindowsSeparators(Environment.getEnv("COMSPEC")) || FileInfo.joinPaths(windowsSystemRoot, "System32", "cmd.exe") + property string windowsPathVariable: hostOS.contains("windows") ? "PATH" : "WINEPATH" + + property var commonRunEnvironment: ({}) + setupRunEnvironment: { + var env = product.qbs.commonRunEnvironment; + for (var i in env) { + var v = new ModUtils.EnvironmentVariable(i, product.qbs.pathListSeparator, + product.qbs.hostOS.contains("windows")); + v.value = env[i]; + v.set(); + } + } + + // Properties that can be set for multiplexing products. + property stringList profiles: [] + property stringList architectures: [] + property stringList buildVariants: [] + + // internal properties + readonly property string version: [versionMajor, versionMinor, versionPatch].join(".") + readonly property int versionMajor: undefined // set internally + readonly property int versionMinor: undefined // set internally + readonly property int versionPatch: undefined // set internally + readonly property var multiplexMap: ({ + profiles: "profile", + architectures: "architecture", + buildVariants: "buildVariant" + }) +} diff --git a/share/qbs/modules/qnx/qnx.qbs b/share/qbs/modules/qnx/qnx.qbs new file mode 100644 index 00000000..9cab5abb --- /dev/null +++ b/share/qbs/modules/qnx/qnx.qbs @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** 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.Environment +import qbs.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Probes +import qbs.Utilities + +Module { + Probes.PathProbe { + id: qnxSdkProbe + names: ["qnx700", "qnx660", "qnx650"] + searchPaths: qbs.hostOS.contains("windows") + ? [Environment.getEnv("USERPROFILE"), Environment.getEnv("SystemDrive")] + : [Environment.getEnv("HOME"), "/opt"] + } + + Probe { + id: qnxTargetOsProbe + property string qnxSdkDir: sdkDir + property stringList targets: [] + configure: { + if (qnxSdkDir) { + var validEntries = []; + var entries = File.directoryEntries( + FileInfo.joinPaths(qnxSdkDir, "target"), + File.Dirs | File.NoDotAndDotDot); + for (var i = 0; i < entries.length; ++i) { + if (/^qnx[0-9]$/.test(entries[i])) + validEntries.push(entries[i]); + } + validEntries.sort(); + validEntries.reverse(); + targets = validEntries; + found = targets.length > 0; + } else { + found = false; + } + } + } + + version: qnxSdkProbe.found ? qnxSdkProbe.fileName.substr(3, 3).split("").join(".") : undefined + + readonly property bool qnx7: version ? Utilities.versionCompare(version, "7") >= 0 : false + + property string sdkDir: qnxSdkProbe.filePath + + property string hostArch: qnx7 ? "x86_64" : "x86" + + property string hostOs: { + if (qbs.hostOS.contains("linux")) + return "linux"; + if (qbs.hostOS.contains("macos")) + return "darwin"; + if (qbs.hostOS.contains("windows")) + return qnx7 ? "win64" : "win32"; + } + + property string targetOs: qnxTargetOsProbe.targets[0] + + property string compilerName: "gcc" + + property string configurationDir: FileInfo.joinPaths(Environment.getEnv("HOME"), ".qnx") + property string hostDir: FileInfo.joinPaths(sdkDir, "host", hostOs, hostArch) + property string targetDir: FileInfo.joinPaths(sdkDir, "target", targetOs) + + property var buildEnv: ({ + "QNX_HOST": hostDir, + "QNX_TARGET": targetDir, + "QNX_CONFIGURATION": configurationDir + }) + + qbs.sysroot: targetDir + + validate: { + if (!sdkDir) { + throw ModUtils.ModuleError("Could not find a QNX SDK in any of the following " + + "locations:\n\t" + qnxSdkProbe.candidatePaths.join("\n\t") + + "\nInstall the QNX SDK to one of the above locations, " + + "or set the qnx.sdkDir property to a valid QNX SDK " + + "location."); + } + + if (!hostOs) { + throw ModUtils.ModuleError("Host operating system '" + qbs.hostOS + + "' is not supported by the QNX SDK."); + } else if (!File.exists(hostDir)) { + throw ModUtils.ModuleError("Detected host tools operating system '" + hostOs + + "' and architecture '" + hostArch + "' directory is not " + + "present in the QNX SDK installed at '" + sdkDir + + "' in the expected location '" + hostDir + + "'; did you forget to install it?"); + } + + if (!targetOs) + throw ModUtils.ModuleError("Could not find any QNX targets in '" + targetDir + "'"); + } +} diff --git a/share/qbs/modules/texttemplate/texttemplate.qbs b/share/qbs/modules/texttemplate/texttemplate.qbs new file mode 100644 index 00000000..c7292966 --- /dev/null +++ b/share/qbs/modules/texttemplate/texttemplate.qbs @@ -0,0 +1,64 @@ +import qbs.TextFile + +Module { + property var dict: ({}) + property string outputTag: "text" + property string outputFileName + FileTagger { + patterns: ["*.in"] + fileTags: ["texttemplate.input"] + } + Rule { + inputs: ["texttemplate.input"] + outputFileTags: [product.texttemplate.outputTag] + outputArtifacts: [ + { + fileTags: [product.texttemplate.outputTag], + filePath: input.texttemplate.outputFileName || input.completeBaseName + } + ] + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + try { + var src = new TextFile(input.filePath, TextFile.ReadOnly); + var dst = new TextFile(output.filePath, TextFile.WriteOnly); + var rex = /\${(\$|\w+)}/g; + var match; + while (!src.atEof()) { + rex.lastIndex = 0; + var line = src.readLine(); + var matches = []; + while (match = rex.exec(line)) + matches.push(match); + for (var i = matches.length; --i >= 0;) { + match = matches[i]; + var replacement; + if (match[1] === "$") { + replacement = "$"; + } else { + replacement = input.texttemplate.dict[match[1]]; + if (typeof replacement === "undefined") { + throw new Error("Placeholder '" + match[1] + + "' is not defined in textemplate.dict for '" + + input.fileName + "'."); + } + } + line = line.substr(0, match.index) + + replacement + + line.substr(match.index + match[0].length); + } + dst.writeLine(line); + } + } finally { + if (src) + src.close(); + if (dst) + dst.close(); + } + }; + return [cmd]; + } + } +} diff --git a/share/qbs/modules/typescript/TypeScriptModule.qbs b/share/qbs/modules/typescript/TypeScriptModule.qbs new file mode 100644 index 00000000..d8e61b17 --- /dev/null +++ b/share/qbs/modules/typescript/TypeScriptModule.qbs @@ -0,0 +1,294 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Probes +import qbs.Process +import "typescript.js" as TypeScript + +Module { + // Qbs does NOT support standalone TypeScript installations + // (for example, %PROGRAMFILES%\Microsoft SDKs\TypeScript and some Debian and RPM packages), + // because they do not include typescript.d.ts, which is necessary for building internal tools. + // Only npm-based installations of TypeScript are supported (this is also the most common). + Depends { name: "nodejs" } + + additionalProductTypes: ["compiled_typescript"] + + Probes.TypeScriptProbe { + id: tsc + interpreterPath: FileInfo.path(nodejs.interpreterFilePath) + packageManagerBinPath: nodejs.packageManagerBinPath + packageManagerRootPath: nodejs.packageManagerRootPath + } + + property path toolchainInstallPath: tsc.path + + property path toolchainLibDirName: (versionMajor > 1 || (versionMajor === 1 && versionMinor >= 6)) ? "lib" : "bin" + property path toolchainLibInstallPath: FileInfo.joinPaths(nodejs.packageManagerRootPath, "typescript", toolchainLibDirName) + + version: tsc.version ? tsc.version[2] : undefined + property var versionParts: version ? version.split('.').map(function(item) { return parseInt(item, 10); }) : [] + property int versionMajor: versionParts[0] + property int versionMinor: versionParts[1] + property int versionPatch: versionParts[2] + property int versionBuild: versionParts[3] + property string versionSuffix: tsc.version ? tsc.version[3] : undefined + + property string compilerName: tsc.fileName + property string compilerPath: tsc.filePath + + property string warningLevel: "normal" + PropertyOptions { + name: "warningLevel" + description: "pedantic to warn on expressions and declarations with an implied 'any' type" + allowedValues: ["normal", "pedantic"] + } + + property string targetVersion + PropertyOptions { + name: "targetVersion" + description: "ECMAScript target version" + allowedValues: ["ES3", "ES5", "ES2015"] + } + + property string moduleLoader + PropertyOptions { + name: "moduleLoader" + allowedValues: ["commonjs", "amd"] + } + + property bool stripComments: !qbs.debugInformation + PropertyOptions { + name: "stripComments" + description: "whether to remove comments from the generated output" + } + + property bool generateDeclarations: false + PropertyOptions { + name: "generateDeclarations" + description: "whether to generate corresponding .d.ts files during compilation" + } + + // In release mode, nodejs can/should default-enable minification and obfuscation, + // making the source maps useless, so these default settings work out fine + property bool generateSourceMaps: qbs.debugInformation + PropertyOptions { + name: "generateSourceMaps" + description: "whether to generate corresponding .map files during compilation" + } + + property stringList compilerFlags + PropertyOptions { + name: "compilerFlags" + description: "additional flags for the TypeScript compiler" + } + + property bool singleFile: false + PropertyOptions { + name: "singleFile" + description: "whether to compile all source files to a single output file" + } + + validate: { + var interpreterMessage = "TypeScript requires the Node.js interpreter to be called 'node'."; + if (File.exists("/etc/debian_version")) { + interpreterMessage += " Did you forget to install the nodejs-legacy package? " + + "See https://lists.debian.org/debian-devel-announce/2012/07/msg00002.html " + + "for more information."; + } + + var preValidator = new ModUtils.PropertyValidator("nodejs"); + preValidator.addCustomValidator("interpreterFileName", nodejs.interpreterFileName, function (value) { + return value === "node" + (qbs.hostOS.contains("windows") ? ".exe" : ""); + }, interpreterMessage); + preValidator.addCustomValidator("interpreterFilePath", nodejs.interpreterFilePath, function (value) { + return value.endsWith(nodejs.interpreterFileName); + }, interpreterMessage); + preValidator.validate(); + + var validator = new ModUtils.PropertyValidator("typescript"); + validator.setRequiredProperty("toolchainInstallPath", toolchainInstallPath); + validator.setRequiredProperty("compilerName", compilerName); + validator.setRequiredProperty("compilerPath", compilerPath); + validator.setRequiredProperty("version", version); + validator.setRequiredProperty("versionParts", versionParts); + validator.setRequiredProperty("versionMajor", versionMajor); + validator.setRequiredProperty("versionMinor", versionMinor); + validator.setRequiredProperty("versionPatch", versionPatch); + validator.addVersionValidator("version", version, 3, 4); + validator.addRangeValidator("versionMajor", versionMajor, 1); + validator.addRangeValidator("versionMinor", versionMinor, 0); + validator.addRangeValidator("versionPatch", versionPatch, 0); + + if (versionParts && versionParts.length >= 4) { + validator.setRequiredProperty("versionBuild", versionBuild); + validator.addRangeValidator("versionBuild", versionBuild, 0); + } + + validator.validate(); + } + + // TypeScript declaration files + FileTagger { + patterns: ["*.d.ts"] + fileTags: ["typescript_declaration"] + } + + // TypeScript source files + FileTagger { + patterns: ["*.ts"] + fileTags: ["typescript"] + } + + Group { + name: "io.qt.qbs.internal.typescript-helper" + files: [ + FileInfo.joinPaths(path, "qbs-tsc-scan", "qbs-tsc-scan.ts"), + FileInfo.joinPaths(typescript.toolchainLibInstallPath, "typescript.d.ts"), + FileInfo.joinPaths(typescript.toolchainLibInstallPath, "..", "package.json") + ] + fileTags: ["typescript.typescript-internal"] + } + + Rule { + multiplex: true + inputs: ["typescript.typescript-internal"] + + outputFileTags: ["typescript.compiled_typescript-internal"] + outputArtifacts: { + if (!TypeScript.supportsModernFeatures(product)) + return []; + return [{ + filePath: FileInfo.joinPaths(product.buildDirectory, + ".io.qt.qbs.internal.typescript", "qbs-tsc-scan.ts"), + fileTags: ["typescript.typescript-internal.copy"] + }, + { + filePath: FileInfo.joinPaths(product.buildDirectory, + ".io.qt.qbs.internal.typescript", + "node_modules", "typescript", "lib", "typescript.d.ts"), + fileTags: ["typescript.typescript-internal.copy"] + }, + { + filePath: FileInfo.joinPaths(product.buildDirectory, + ".io.qt.qbs.internal.typescript", + "node_modules", "typescript", "package.json"), + fileTags: ["typescript.typescript-internal.copy"] + }, + { + filePath: FileInfo.joinPaths(product.buildDirectory, + ".io.qt.qbs.internal.typescript", "qbs-tsc-scan.js"), + fileTags: ["typescript.compiled_typescript-internal"] + }]; + } + + prepare: { + var inputPaths = inputs["typescript.typescript-internal"].map(function (input) { + return input.filePath; + }); + + var outputPaths = outputs["typescript.typescript-internal.copy"].map(function (output) { + return output.filePath; + }); + + var sortFunc = function (a, b) { + return FileInfo.fileName(a).localeCompare(FileInfo.fileName(b)); + }; + + var jcmd = new JavaScriptCommand(); + jcmd.ignoreDryRun = true; + jcmd.silent = true; + jcmd.inputPaths = inputPaths.sort(sortFunc); + jcmd.outputPaths = outputPaths.sort(sortFunc); + jcmd.sourceCode = function() { + for (var i = 0; i < inputPaths.length; ++i) + File.copy(inputPaths[i], outputPaths[i]); + }; + + var outDir = FileInfo.path( + outputs["typescript.compiled_typescript-internal"][0].filePath); + var args = ["--module", "commonjs", + "--outDir", outDir].concat(outputPaths.filter(function (f) { return !f.endsWith(".json"); })); + var cmd = new Command(ModUtils.moduleProperty(product, "compilerPath"), args); + cmd.ignoreDryRun = true; + cmd.silent = true; + return [jcmd, cmd]; + } + } + + Rule { + id: typescriptCompiler + multiplex: true + inputs: ["typescript"] + inputsFromDependencies: ["typescript_declaration"] + explicitlyDependsOn: ["typescript.compiled_typescript-internal"] + + outputArtifacts: TypeScript.outputArtifacts(product, inputs) + + outputFileTags: ["application_js", "compiled_typescript", "js", "source_map", "typescript_declaration"] + + prepare: { + var cmd, cmds = []; + + cmd = new Command(ModUtils.moduleProperty(product, "compilerPath"), + TypeScript.tscArguments(product, inputs)); + cmd.description = "compiling " + (ModUtils.moduleProperty(product, "singleFile") + ? outputs.compiled_typescript[0].fileName + : inputs.typescript.map(function(obj) { + return obj.fileName; }).join(", ")); + cmd.highlight = "compiler"; + cmds.push(cmd); + + // QBS-5 + // Move the compiled JavaScript files compiled by TypeScript to an intermediate + // directory so that the nodejs module can perform any necessary postprocessing + // on the result. The nodejs module will move the files back to their original + // locations after postprocessing. + cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.outDir = product.buildDirectory; + cmd.sourceCode = function() { + for (var i = 0; i < outputs.compiled_typescript.length; ++i) { + var output = outputs.compiled_typescript[i]; + var intermediatePath = FileInfo.path(FileInfo.relativePath(product.moduleProperty("nodejs", "compiledIntermediateDir"), output.filePath)); + var originalFilePath = FileInfo.joinPaths(outDir, intermediatePath, output.fileName); + File.copy(originalFilePath, output.filePath); + File.remove(originalFilePath); + } + }; + cmds.push(cmd); + + return cmds; + } + } +} diff --git a/share/qbs/modules/typescript/qbs-tsc-scan/.gitignore b/share/qbs/modules/typescript/qbs-tsc-scan/.gitignore new file mode 100644 index 00000000..2ac7185a --- /dev/null +++ b/share/qbs/modules/typescript/qbs-tsc-scan/.gitignore @@ -0,0 +1,5 @@ +# Visual Studio Code IDE +tsconfig.json +qbs-tsc-scan.js +typings/node/node.d.ts +typings/typescript/typescript.d.ts diff --git a/share/qbs/modules/typescript/qbs-tsc-scan/qbs-tsc-scan.ts b/share/qbs/modules/typescript/qbs-tsc-scan/qbs-tsc-scan.ts new file mode 100644 index 00000000..ca5eb420 --- /dev/null +++ b/share/qbs/modules/typescript/qbs-tsc-scan/qbs-tsc-scan.ts @@ -0,0 +1,68 @@ +import ts = require("typescript"); + +declare var process: any; + +export namespace io.qt.qbs { + export class Artifact { + filePath: string; + fileTags: string[]; + } + + export namespace tools { + export namespace utils { + function stringEndsWith(s: string, e: string) { + return s.slice(-e.length) === e; + } + + export function artifactFromFilePath(filePath: string): Artifact { + var fileTags: string[] = []; + if (stringEndsWith(filePath, ".js.map")) { + fileTags.push("source_map"); + } else if (stringEndsWith(filePath, ".js")) { + fileTags.push("js", "compiled_typescript"); + } else if (stringEndsWith(filePath, ".d.ts")) { + fileTags.push("typescript_declaration"); + } + + return { filePath: filePath, fileTags: fileTags }; + } + } + + function compileInternal(fileNames: string[], options: ts.CompilerOptions): qbs.Artifact[] { + var outputArtifacts: qbs.Artifact[] = []; + var program = ts.createProgram(fileNames, options); + var emitResult = program.emit(undefined, filePath => { + outputArtifacts.push(utils.artifactFromFilePath(filePath)); + }); + + var allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics); + allDiagnostics.forEach(diagnostic => { + var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); + if (diagnostic.file) { + var { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); + console.error(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`); + } else { + console.error(message); + } + }); + + return emitResult.emitSkipped ? undefined : outputArtifacts; + } + + export function compile(commandLineArguments: string[]): qbs.Artifact[] { + var parsedCommandLine = ts.parseCommandLine(commandLineArguments); + return compileInternal(parsedCommandLine.fileNames, parsedCommandLine.options); + } + + export function TypeScriptCompilerScannerToolMain(): void { + var outputArtifacts = compile(process.argv.slice(2)); + if (outputArtifacts !== undefined) { + console.log(JSON.stringify(outputArtifacts)); + } else { + process.exit(1); + } + } + } +} + +io.qt.qbs.tools.TypeScriptCompilerScannerToolMain(); diff --git a/share/qbs/modules/typescript/typescript.js b/share/qbs/modules/typescript/typescript.js new file mode 100644 index 00000000..858041b3 --- /dev/null +++ b/share/qbs/modules/typescript/typescript.js @@ -0,0 +1,273 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jake Petroules. +** 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 ModUtils = require("qbs.ModUtils"); +var Process = require("qbs.Process"); + +function findTscVersion(compilerFilePath, nodejsPath) { + var p = new Process(); + try { + if (nodejsPath) + p.setEnv("PATH", nodejsPath); + p.exec(compilerFilePath, ["--version"]); + var re = /^(?:message TS6029: )?Version (([0-9]+(?:\.[0-9]+){1,3})(?:-(.+?))?)$/m; + var match = p.readStdOut().trim().match(re); + if (match !== null) + return match; + } finally { + p.close(); + } +} + +function tscArguments(product, inputs) { + var i; + var args = []; + + if (ModUtils.moduleProperty(product, "warningLevel") === "pedantic") { + args.push("--noImplicitAny"); + } + + var targetVersion = ModUtils.moduleProperty(product, "targetVersion"); + if (targetVersion) { + args.push("--target"); + args.push(targetVersion); + } + + var moduleLoader = ModUtils.moduleProperty(product, "moduleLoader"); + if (moduleLoader) { + args.push("--module"); + args.push(moduleLoader); + } + + if (ModUtils.moduleProperty(product, "stripComments")) { + args.push("--removeComments"); + } + + if (ModUtils.moduleProperty(product, "generateDeclarations")) { + args.push("--declaration"); + } + + if (ModUtils.moduleProperty(product, "generateSourceMaps")) { + args.push("--sourcemap"); + } + + // User-supplied flags + var flags = ModUtils.moduleProperty(product, "compilerFlags"); + for (i in flags) { + args.push(flags[i]); + } + + if (supportsModernFeatures(product)) { + args.push("--rootDir", product.sourceDirectory); + } + + args.push("--outDir", product.buildDirectory); + + if (ModUtils.moduleProperty(product, "singleFile")) { + args.push(outOption(product), + FileInfo.joinPaths(product.destinationDirectory, product.targetName) + ".js"); + } + + if (inputs.typescript_declaration) { + for (i = 0; i < inputs.typescript_declaration.length; ++i) { + args.push(inputs.typescript_declaration[i].filePath); + } + } + + if (inputs.typescript) { + for (i = 0; i < inputs.typescript.length; ++i) { + args.push(inputs.typescript[i].filePath); + } + } + + if (inputs["typescript.typescript-internal"]) { + for (i = 0; i < inputs["typescript.typescript-internal"].length; ++i) { + args.push(inputs["typescript.typescript-internal"][i].filePath); + } + } + + return args; +} + +function outputArtifacts(product, inputs) { + if (!supportsModernFeatures(product)) { + console.warn("Qbs does not properly support TypeScript versions prior to 1.5 due to " + + "severe limitations in dependency tracking. This is TypeScript version " + + ModUtils.moduleProperty(product, "version") + ". It is strongly recommended " + + "that you upgrade TypeScript, or continue at your own risk."); + return legacyOutputArtifacts(product, inputs); + } + + var process; + try { + process = new Process(); + process.setEnv("NODE_PATH", [ + ModUtils.moduleProperty(product, "toolchainInstallPath"), + product.moduleProperty("nodejs", "packageManagerRootPath") + ].join(product.moduleProperty("qbs", "pathListSeparator"))); + process.exec(product.moduleProperty("nodejs", "interpreterFilePath"), + [FileInfo.joinPaths(product.buildDirectory, + ".io.qt.qbs.internal.typescript", + "qbs-tsc-scan.js")] + .concat(tscArguments(product, inputs)), true); + var artifacts = JSON.parse(process.readStdOut()); + + // Find and tag the "main" output file + var applicationFile = product.moduleProperty("nodejs", "applicationFile"); + if (applicationFile) { + var i, appIndex = -1; + if (product.moduleProperty("typescript", "singleFile")) { + for (i = 0; i < artifacts.length; ++i) { + if (artifacts[i].fileTags.contains("compiled_typescript")) { + appIndex = i; + break; + } + } + } else { + var expected = FileInfo.relativePath(product.sourceDirectory, applicationFile); + if (!expected.endsWith(".ts")) + // tsc doesn't allow this anyways, so it's a perfectly reasonable restriction + throw "TypeScript source file name '" + applicationFile + + "' does not end with .ts"; + + expected = expected.slice(0, -2) + "js"; + + for (i = 0; i < artifacts.length; ++i) { + if (expected === FileInfo.relativePath(product.buildDirectory, + artifacts[i].filePath)) { + appIndex = i; + break; + } + } + } + + if (appIndex === -1 || !artifacts[appIndex].fileTags.contains("compiled_typescript")) + throw "nodejs.applicationFile was set, but Qbs couldn't find the compiled " + + "JavaScript file corresponding to '" + applicationFile + "'"; + + artifacts[appIndex].fileTags = artifacts[appIndex].fileTags.concat(["application_js"]); + } + + return artifacts; + } finally { + if (process) + process.close(); + } +} + +function legacyOutputArtifacts(product, inputs) { + var artifacts = []; + + if (!inputs.typescript) { + return artifacts; + } + + var jsTags = ["js", "compiled_typescript"]; + var filePath = FileInfo.joinPaths(product.destinationDirectory, product.targetName); + if (product.moduleProperty("typescript", "singleFile")) { + // We could check + // if (product.moduleProperty("nodejs", "applicationFile") === inputs.typescript[i].filePath) + // but since we're compiling to a single file there's no need to state it explicitly + jsTags.push("application_js"); + + artifacts.push({fileTags: jsTags, + filePath: FileInfo.joinPaths( + product.moduleProperty("nodejs", + "compiledIntermediateDir"), + product.targetName + ".js")}); + + if (product.moduleProperty("typescript", "generateDeclarations")) { + artifacts.push({fileTags: ["typescript_declaration"], + filePath: filePath + ".d.ts"}); + } + + if (product.moduleProperty("typescript", "generateSourceMaps")) { + artifacts.push({fileTags: ["source_map"], + filePath: filePath + ".js.map"}); + } + } else { + for (var i = 0; i < inputs.typescript.length; ++i) { + jsTags = ["js", "compiled_typescript"]; + if (product.moduleProperty("nodejs", "applicationFile") === inputs.typescript[i].filePath) + jsTags.push("application_js"); + + var intermediatePath = FileInfo.path(FileInfo.relativePath( + product.sourceDirectory, + inputs.typescript[i].filePath)); + + var baseName = FileInfo.baseName(inputs.typescript[i].fileName); + filePath = FileInfo.joinPaths(product.destinationDirectory, + intermediatePath, + baseName); + + artifacts.push({fileTags: jsTags, + filePath: FileInfo.joinPaths( + product.moduleProperty("nodejs", + "compiledIntermediateDir"), + intermediatePath, + baseName + ".js")}); + + if (product.moduleProperty("typescript", "generateDeclarations")) { + artifacts.push({fileTags: ["typescript_declaration"], + filePath: filePath + ".d.ts"}); + } + + if (product.moduleProperty("typescript", "generateSourceMaps")) { + artifacts.push({fileTags: ["source_map"], + filePath: filePath + ".js.map"}); + } + } + } + + return artifacts; +} + +function outOption(product) { + var compilerVersionMajor = ModUtils.moduleProperty(product, "versionMajor"); + if (compilerVersionMajor === 1) { + if (ModUtils.moduleProperty(product, "versionMinor") < 6) { + return "--out"; + } + } + + return "--outFile"; +} + +function supportsModernFeatures(product) { + var compilerVersionMajor = ModUtils.moduleProperty(product, "versionMajor"); + if (compilerVersionMajor === 1) { + if (ModUtils.moduleProperty(product, "versionMinor") >= 5) { + return true; + } + } + + return compilerVersionMajor > 1; +} diff --git a/share/qbs/modules/vcs/vcs-module.qbs b/share/qbs/modules/vcs/vcs-module.qbs new file mode 100644 index 00000000..f3a47d2a --- /dev/null +++ b/share/qbs/modules/vcs/vcs-module.qbs @@ -0,0 +1,154 @@ +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(); + if (!FileInfo.isAbsolutePath(metaDataBaseDir)) + metaDataBaseDir = FileInfo.joinPaths(theRepoDir, metaDataBaseDir); + 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().trim(); + if (repoState) + found = true; + } finally { + proc.close(); + } + } + } + + Rule { + condition: 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 ? repoState : "none") + '"') + f.writeLine("#endif"); + } finally { + f.close(); + } + }; + return [cmd]; + } + } +} diff --git a/share/qbs/modules/wix/WiXModule.qbs b/share/qbs/modules/wix/WiXModule.qbs new file mode 100644 index 00000000..ad8586da --- /dev/null +++ b/share/qbs/modules/wix/WiXModule.qbs @@ -0,0 +1,452 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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.File +import qbs.FileInfo +import qbs.ModUtils +import qbs.Probes +import qbs.Utilities + +Module { + condition: qbs.targetOS.contains("windows") + + Probes.WiXProbe { + id: wixProbe + } + + property path toolchainInstallPath: wixProbe.path + property path toolchainInstallRoot: wixProbe.root + version: wixProbe.version + property var versionParts: version ? version.split('.').map(function(item) { return parseInt(item, 10); }) : [] + property int versionMajor: versionParts[0] + property int versionMinor: versionParts[1] + property int versionPatch: versionParts[2] + property int versionBuild: versionParts[3] + + property string compilerName: "candle.exe" + property string compilerPath: FileInfo.joinPaths(toolchainInstallRoot, compilerName) + property string linkerName: "light.exe" + property string linkerPath: FileInfo.joinPaths(toolchainInstallRoot, linkerName) + + property string warningLevel: "normal" + PropertyOptions { + name: "warningLevel" + allowedValues: ["none", "normal", "pedantic"] + } + + property bool debugInformation: qbs.debugInformation + property bool treatWarningsAsErrors: false + property bool verboseOutput: false + PropertyOptions { + name: "verboseOutput" + description: "display verbose output from the compiler and linker" + } + + property bool visualStudioCompatibility: true + PropertyOptions { + name: "visualStudioCompatibility" + description: "whether to define most of the same variables as " + + "Visual Studio when using the Candle compiler" + } + + property bool enableQbsDefines: true + PropertyOptions { + name: "enableQbsDefines" + description: "built-in variables that are defined when using the Candle compiler" + } + + property pathList includePaths + PropertyOptions { + name: "includePaths" + description: "directories to add to the include search path" + } + + property stringList defines + PropertyOptions { + name: "defines" + description: "variables that are defined when using the Candle compiler" + } + + property stringList compilerFlags + PropertyOptions { + name: "compilerFlags" + description: "additional flags for the Candle compiler" + } + + property stringList linkerFlags + PropertyOptions { + name: "linkerFlags" + description: "additional flags for the Light linker" + } + + property stringList cultures + PropertyOptions { + name: "cultures" + description: "the list of localizations to build the MSI for; leave undefined to build all localizations" + } + + property stringList extensions: product.type.contains("wixsetup") ? ["WixBalExtension"] : [] // default to WiX Standard Bootstrapper extension + + // private properties + property string targetSuffix: { + if (product.type.contains("msi")) { + return windowsInstallerSuffix; + } else if (product.type.contains("wixsetup")) { + return executableSuffix; + } + } + + // MSI/MSM package validation only works natively on Windows + property bool enablePackageValidation: qbs.hostOS.contains("windows") + + property string executableSuffix: ".exe" + property string windowsInstallerSuffix: ".msi" + + validate: { + var validator = new ModUtils.PropertyValidator("wix"); + validator.setRequiredProperty("toolchainInstallPath", toolchainInstallPath); + validator.setRequiredProperty("toolchainInstallRoot", toolchainInstallRoot); + validator.setRequiredProperty("version", version); + validator.setRequiredProperty("versionMajor", versionMajor); + validator.setRequiredProperty("versionMinor", versionMinor); + validator.setRequiredProperty("versionPatch", versionPatch); + validator.addVersionValidator("version", version, 3, 4); + validator.addRangeValidator("versionMajor", versionMajor, 1); + validator.addRangeValidator("versionMinor", versionMinor, 0); + validator.addRangeValidator("versionPatch", versionPatch, 0); + + if (versionParts && versionParts.length >= 4) { + validator.setRequiredProperty("versionBuild", versionBuild); + validator.addRangeValidator("versionBuild", versionBuild, 0); + } + + validator.validate(); + } + + setupBuildEnvironment: { + var v = new ModUtils.EnvironmentVariable("PATH", product.qbs.pathListSeparator, true); + v.prepend(product.wix.toolchainInstallPath); + v.prepend(product.wix.toolchainInstallRoot); + v.set(); + } + + // WiX Include Files + FileTagger { + patterns: ["*.wxi"] + fileTags: ["wxi"] + } + + // WiX Localization Files + FileTagger { + patterns: ["*.wxl"] + fileTags: ["wxl"] + } + + // WiX Source Files + FileTagger { + patterns: ["*.wxs"] + fileTags: ["wxs"] + } + + Rule { + id: candleCompiler + inputs: ["wxs"] + auxiliaryInputs: ["wxi", "installable"] + + Artifact { + fileTags: ["wixobj"] + filePath: FileInfo.joinPaths(Utilities.getHash(input.baseDir), + FileInfo.baseName(input.fileName) + ".wixobj") + } + + prepare: { + var i; + var args = ["-nologo"]; + + if (ModUtils.moduleProperty(input, "warningLevel") === "none") { + args.push("-sw"); + } else { + if (ModUtils.moduleProperty(input, "warningLevel") === "pedantic") { + args.push("-pedantic"); + } + + if (ModUtils.moduleProperty(input, "treatWarningsAsErrors")) { + args.push("-wx"); + } + } + + if (ModUtils.moduleProperty(input, "verboseOutput")) { + args.push("-v"); + } + + var arch = product.moduleProperty("qbs", "architecture"); + + // http://wixtoolset.org/documentation/manual/v3/xsd/wix/package.html + switch (arch) { + case "x86_64": + arch = "x64"; + break; + case "armv7": + case "armv7a": + arch = "arm"; + break; + } + + // Visual Studio defines these variables along with various solution and project names and paths; + // we'll pass most of them to ease compatibility between QBS and WiX projects originally created + // using Visual Studio. The only definitions we don't pass are the ones which make no sense at all + // in QBS, like the solution and project directories since they do not exist. + if (ModUtils.moduleProperty(input, "visualStudioCompatibility")) { + var toolchain = product.moduleProperty("qbs", "toolchain"); + var toolchainInstallPath = product.moduleProperty("cpp", "toolchainInstallPath"); + if (toolchain && toolchain.contains("msvc") && toolchainInstallPath) { + var vcDir = toolchainInstallPath.replace(/[\\/]bin$/i, ""); + var vcRootDir = vcDir.replace(/[\\/]VC$/i, ""); + args.push("-dDevEnvDir=" + FileInfo.toWindowsSeparators(FileInfo.joinPaths(vcRootDir, 'Common7', 'IDE'))); + } + + var buildVariant = product.moduleProperty("qbs", "buildVariant"); + if (buildVariant === "debug") { + args.push("-dDebug"); + args.push("-dConfiguration=Debug"); + } else if (buildVariant === "release") { + // VS doesn't define "Release" + args.push("-dConfiguration=Release"); + } + + var productTargetExt = ModUtils.moduleProperty(input, "targetSuffix"); + if (!productTargetExt) { + throw("WiX: Unsupported product type '" + product.type + "'"); + } + + var builtBinaryFilePath = FileInfo.joinPaths(product.destinationDirectory, product.targetName + productTargetExt); + args.push("-dOutDir=" + FileInfo.toWindowsSeparators(FileInfo.path(builtBinaryFilePath))); // in VS, relative to the project file by default + + args.push("-dPlatform=" + (arch || "x86")); + + args.push("-dProjectName=" + project.name); + + args.push("-dTargetDir=" + FileInfo.toWindowsSeparators(FileInfo.path(builtBinaryFilePath))); // in VS, an absolute path + args.push("-dTargetExt=" + productTargetExt); + args.push("-dTargetFileName=" + product.targetName + productTargetExt); + args.push("-dTargetName=" + product.targetName); + args.push("-dTargetPath=" + FileInfo.toWindowsSeparators(builtBinaryFilePath)); + } + + var includePaths = ModUtils.moduleProperty(input, "includePaths"); + for (i in includePaths) { + args.push("-I" + includePaths[i]); + } + + var enableQbsDefines = ModUtils.moduleProperty(input, "enableQbsDefines") + if (enableQbsDefines) { + var map = { + "project.": project, + "product.": product + }; + + for (var prefix in map) { + var obj = map[prefix]; + for (var prop in obj) { + var val = obj[prop]; + if (typeof val !== 'function' && typeof val !== 'object' && !prop.startsWith("_")) { + args.push("-d" + prefix + prop + "=" + val); + } + } + } + + // Helper define for alternating between 32-bit and 64-bit logic + if (arch === "x64" || arch === "ia64") { + args.push("-dWin64"); + } + } + + // User-supplied defines + var defines = ModUtils.moduleProperty(input, "defines"); + for (i in defines) { + args.push("-d" + defines[i]); + } + + // User-supplied flags + var flags = ModUtils.moduleProperty(input, "compilerFlags"); + for (i in flags) { + args.push(flags[i]); + } + + args.push("-out"); + args.push(FileInfo.toWindowsSeparators(output.filePath)); + + if (arch) { + args.push("-arch"); + args.push(arch); + } + + var extensions = ModUtils.moduleProperty(input, "extensions"); + for (i in extensions) { + args.push("-ext"); + args.push(extensions[i]); + } + + args.push(FileInfo.toWindowsSeparators(input.filePath)); + + var cmd = new Command(ModUtils.moduleProperty(product, "compilerPath"), args); + cmd.description = "compiling " + input.fileName; + cmd.highlight = "compiler"; + cmd.workingDirectory = FileInfo.path(output.filePath); + // candle.exe outputs the file name. We filter that out. + cmd.inputFileName = input.fileName; + cmd.stdoutFilterFunction = function(output) { + return output.split(inputFileName + "\r\n").join(""); + }; + return cmd; + } + } + + Rule { + id: lightLinker + multiplex: true + inputs: ["wixobj", "wxl"] + auxiliaryInputs: ["installable"] + inputsFromDependencies: product.type.contains("wixsetup") ? ["msi"] : [] + + outputArtifacts: { + var artifacts = []; + + if (product.type.contains("wixsetup")) { + artifacts.push({ + fileTags: ["wixsetup", "application"], + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + + ModUtils.moduleProperty(product, + "executableSuffix")) + }); + } + + if (product.type.contains("msi")) { + artifacts.push({ + fileTags: ["msi"], + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + + ModUtils.moduleProperty(product, + "windowsInstallerSuffix")) + }); + } + + if (ModUtils.moduleProperty(product, "debugInformation")) { + artifacts.push({ + fileTags: ["wixpdb"], + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + ".wixpdb") + }); + } + + return artifacts; + } + + outputFileTags: ["application", "msi", "wixpdb", "wixsetup"] + + prepare: { + var i; + var primaryOutput; + if (product.type.contains("wixsetup")) { + primaryOutput = outputs.wixsetup[0]; + } else if (product.type.contains("msi")) { + primaryOutput = outputs.msi[0]; + } else { + throw("WiX: Unsupported product type '" + product.type + "'"); + } + + var args = ["-nologo"]; + + if (!ModUtils.moduleProperty(product, "enablePackageValidation")) { + args.push("-sval"); + } + + if (ModUtils.moduleProperty(product, "warningLevel") === "none") { + args.push("-sw"); + } else { + if (ModUtils.moduleProperty(product, "warningLevel") === "pedantic") { + args.push("-pedantic"); + } + + if (ModUtils.moduleProperty(product, "treatWarningsAsErrors")) { + args.push("-wx"); + } + } + + if (ModUtils.moduleProperty(product, "verboseOutput")) { + args.push("-v"); + } + + args.push("-out"); + args.push(FileInfo.toWindowsSeparators(primaryOutput.filePath)); + + if (ModUtils.moduleProperty(product, "debugInformation")) { + args.push("-pdbout"); + args.push(FileInfo.toWindowsSeparators(outputs.wixpdb[0].filePath)); + } else { + args.push("-spdb"); + } + + var extensions = ModUtils.moduleProperty(product, "extensions"); + for (i in extensions) { + args.push("-ext"); + args.push(extensions[i]); + } + + for (i in inputs.wxl) { + args.push("-loc"); + args.push(FileInfo.toWindowsSeparators(inputs.wxl[i].filePath)); + } + + if (product.type.contains("msi")) { + var cultures = ModUtils.moduleProperty(product, "cultures"); + args.push("-cultures:" + + (cultures && cultures.length > 0 ? cultures.join(";") : "null")); + } + + // User-supplied flags + var flags = ModUtils.moduleProperty(product, "linkerFlags"); + for (i in flags) { + args.push(flags[i]); + } + + for (i in inputs.wixobj) { + args.push(FileInfo.toWindowsSeparators(inputs.wixobj[i].filePath)); + } + + var cmd = new Command(ModUtils.moduleProperty(product, "linkerPath"), args); + cmd.description = "linking " + primaryOutput.fileName; + cmd.highlight = "linker"; + cmd.workingDirectory = FileInfo.path(primaryOutput.filePath); + return cmd; + } + } +} diff --git a/share/qbs/modules/xcode/xcode.js b/share/qbs/modules/xcode/xcode.js new file mode 100644 index 00000000..9c87e09d --- /dev/null +++ b/share/qbs/modules/xcode/xcode.js @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Jake Petroules. +** 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 DarwinTools = require("qbs.DarwinTools"); +var File = require("qbs.File"); +var FileInfo = require("qbs.FileInfo"); +var Process = require("qbs.Process"); +var PropertyList = require("qbs.PropertyList"); +var Utilities = require("qbs.Utilities"); + +var XcodeArchSpecsReader = (function () { + function XcodeArchSpecsReader(specsPath) { + var plist = new PropertyList(); + try { + plist.readFromFile(specsPath); + this.specsObject = plist.toObject(); + } finally { + plist.clear(); + } + } + XcodeArchSpecsReader.prototype.getArchitectureSettings = function () { + if (this.specsObject) { + var names = []; + for (var i = 0; i < this.specsObject.length; ++i) { + var dict = this.specsObject[i]; + var name = dict["ArchitectureSetting"]; + if (name) + names.push(name); + } + return names; + } + }; + XcodeArchSpecsReader.prototype.getArchitectureSettingValue = function (settingName) { + // settingName can be: ARCHS_STANDARD, ARCHS_STANDARD_32_BIT, ARCHS_STANDARD_64_BIT, + // ARCHS_STANDARD_32_64_BIT, ARCHS_STANDARD_INCLUDING_64_BIT, or ARCHS_UNIVERSAL_IPHONE_OS. + // NATIVE_ARCH_ACTUAL doesn't have a RealArchitectures property since it's determined by + // Xcode programmatically. + if (this.specsObject) { + for (var i = 0; i < this.specsObject.length; ++i) { + var dict = this.specsObject[i]; + if (dict["ArchitectureSetting"] === settingName) { + var realArchs = dict["RealArchitectures"]; + if (realArchs) { + var effectiveRealArchs = []; + for (var j = 0; j < realArchs.length; ++j) { + var re = /^\$\(([A-Za-z0-9_]+)\)$/; + var match = realArchs[j].match(re); + if (match) { + var val = this.getArchitectureSettingValue(match[1]); + // Don't silently omit values if missing. For example, if + // ARCHS_FOO=[x86_64, $(ARCHS_BAR)], return 'undefined' instead of + // simply [x86_64]. Not known to have any real world occurrences. + if (!val) + return undefined; + Array.prototype.push.apply(effectiveRealArchs, val); + } else { + effectiveRealArchs.push(realArchs[j]); + } + } + return effectiveRealArchs; + } + } + } + } + }; + return XcodeArchSpecsReader; +}()); + +function sdkInfoList(sdksPath) { + var sdkInfo = []; + var sdks = File.directoryEntries(sdksPath, File.Dirs | File.NoDotAndDotDot); + for (var i in sdks) { + // SDK directory name must contain a version number; + // we don't want the versionless iPhoneOS.sdk directory for example + if (!sdks[i].match(/[0-9]+/)) + continue; + + if (sdks[i].startsWith("DriverKit")) + continue; + + var settingsPlist = FileInfo.joinPaths(sdksPath, sdks[i], "SDKSettings.plist"); + var propertyList = new PropertyList(); + try { + propertyList.readFromFile(settingsPlist); + + function checkPlist(plist) { + if (!plist || !plist["CanonicalName"] || !plist["Version"]) + return false; + + var re = /^[0-9]+\.[0-9]+(\.[0-9]+)?$/; + return plist["Version"].match(re); + } + + var plist = propertyList.toObject(); + if (!checkPlist(plist)) { + console.warn("Skipping corrupted SDK installation: " + + FileInfo.joinPaths(sdksPath, sdks[i])); + continue; + } + + sdkInfo.push(plist); + } finally { + propertyList.clear(); + } + } + + // Sort by SDK version number + sdkInfo.sort(function (a, b) { return Utilities.versionCompare(a["Version"], b["Version"]); }); + + return sdkInfo; +} + +function findSigningIdentities(security, searchString) { + var process; + var identities; + if (searchString) { + try { + process = new Process(); + if (process.exec(security, ["find-identity", "-p", "codesigning", "-v"], true) !== 0) + console.error(process.readStdErr()); + + var lines = process.readStdOut().split("\n"); + for (var i in lines) { + // e.g. 1) AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA "Mac Developer: John Doe (XXXXXXXXXX) john.doe@example.org" + var match = lines[i].match(/^\s*[0-9]+\)\s+([A-Fa-f0-9]{40})\s+"([^"]+)"$/); + if (match !== null) { + var hexId = match[1]; + var displayName = match[2]; + if (hexId === searchString || displayName.startsWith(searchString)) { + if (!identities) + identities = []; + identities.push([hexId, displayName]); + break; + } + } + } + } finally { + process.close(); + } + } + return identities; +} + +function provisioningProfilePlistContents(filePath) { + if (filePath === undefined || !File.exists(filePath)) + return undefined; + + var plist = new PropertyList(); + try { + plist.readFromFile(filePath); + return plist.toObject(); + } finally { + plist.clear(); + } +} + +function archsSpecsPath(version, targetOS, platformType, platformPath, devicePlatformPath) { + var _specsPluginBaseName; + if (Utilities.versionCompare(version, "12") >= 0) { + if (targetOS.contains("macos")) + _specsPluginBaseName = "OSX"; + } + if (Utilities.versionCompare(version, "7") >= 0) { + if (targetOS.contains("ios")) + _specsPluginBaseName = "iOSPlatform"; + if (targetOS.contains("tvos")) + _specsPluginBaseName = "AppleTV"; + if (targetOS.contains("watchos")) + _specsPluginBaseName = "Watch"; + } + + var _archSpecsDir = _specsPluginBaseName + ? FileInfo.joinPaths(devicePlatformPath, "Developer", "Library", "Xcode", + "PrivatePlugIns", + "IDE" + _specsPluginBaseName + "SupportCore.ideplugin", "Contents", + "Resources") + : FileInfo.joinPaths(platformPath, "Developer", "Library", "Xcode", "Specifications"); + + var _archSpecsFileBaseName = targetOS.contains("ios") + ? (targetOS.contains("ios-simulator") ? "iPhone Simulator " : "iPhoneOS") + : DarwinTools.applePlatformDirectoryName(targetOS, platformType) + " "; + + if (_specsPluginBaseName) { + switch (platformType) { + case "device": + return FileInfo.joinPaths(_archSpecsDir, "Device.xcspec"); + case "simulator": + return FileInfo.joinPaths(_archSpecsDir, "Simulator.xcspec"); + } + } + + return FileInfo.joinPaths(_archSpecsDir, _archSpecsFileBaseName + "Architectures.xcspec"); +} diff --git a/share/qbs/modules/xcode/xcode.qbs b/share/qbs/modules/xcode/xcode.qbs new file mode 100644 index 00000000..e4df1f20 --- /dev/null +++ b/share/qbs/modules/xcode/xcode.qbs @@ -0,0 +1,444 @@ +import qbs.BundleTools +import qbs.Environment +import qbs.File +import qbs.FileInfo +import qbs.DarwinTools +import qbs.ModUtils +import qbs.Probes +import qbs.PropertyList +import qbs.Utilities +import 'xcode.js' as Xcode + +Module { + id: xcodeModule + + Probes.XcodeProbe { + id: xcodeProbe + developerPath: xcodeModule.developerPath + platformType: xcodeModule.platformType + platformPath: xcodeModule.platformPath + devicePlatformPath: xcodeModule.devicePlatformPath + xcodebuildPath: xcodeModule.xcodebuildPath + sdksPath: xcodeModule.sdksPath + } + + condition: qbs.targetOS.contains("darwin") && + qbs.toolchain && qbs.toolchain.contains("xcode") + + version: xcodeProbe.xcodeVersion + + property path developerPath: "/Applications/Xcode.app/Contents/Developer" + property string sdk: DarwinTools.applePlatformName(qbs.targetOS, platformType) + property stringList targetDevices: DarwinTools.targetDevices(qbs.targetOS) + + property string platformType: { + if (qbs.targetOS.containsAny(["ios-simulator", "tvos-simulator", "watchos-simulator"])) + return "simulator"; + if (qbs.targetOS.containsAny(["ios", "tvos", "watchos"])) + return "device"; + } + + readonly property string sdkName: { + if (_sdkSettings) { + return _sdkSettings["CanonicalName"]; + } + } + + readonly property string sdkVersion: { + if (_sdkSettings) { + return _sdkSettings["Version"]; + } + } + readonly property string shortSdkVersion: { + var v = sdkVersion; + if (v && v.split('.').length > 2) + v = v.slice(0, v.lastIndexOf('.')); + return v; + } + + readonly property string latestSdkName: { + if (_latestSdk) { + return _latestSdk["CanonicalName"]; + } + } + + readonly property string latestSdkVersion: { + if (_latestSdk) { + return _latestSdk["Version"]; + } + } + + readonly property stringList availableSdkNames: { + if (_availableSdks) { + return _availableSdks.map(function (obj) { return obj["CanonicalName"]; }); + } + } + + readonly property stringList availableSdkVersions: { + if (_availableSdks) { + return _availableSdks.map(function (obj) { return obj["Version"]; }); + } + } + + property string signingIdentity + readonly property string actualSigningIdentity: { + if (_actualSigningIdentity && _actualSigningIdentity.length === 2) + return _actualSigningIdentity[0]; + } + + readonly property string actualSigningIdentityDisplayName: { + if (_actualSigningIdentity && _actualSigningIdentity.length === 2) + return _actualSigningIdentity[1]; + } + + property string signingTimestamp: "none" + + property string provisioningProfile + + property string xcodebuildName: "xcodebuild" + property string xcodebuildPath: FileInfo.joinPaths(developerPath, "usr", "bin", xcodebuildName) + + property string securityName: "security" + property string securityPath: securityName + + property string codesignName: "codesign" + property string codesignPath: codesignName + property stringList codesignFlags + + readonly property path toolchainPath: FileInfo.joinPaths(toolchainsPath, + "XcodeDefault" + ".xctoolchain") + readonly property path platformPath: FileInfo.joinPaths(platformsPath, + DarwinTools.applePlatformDirectoryName( + qbs.targetOS, platformType) + + ".platform") + readonly property path devicePlatformPath: FileInfo.joinPaths( + platformsPath, + DarwinTools.applePlatformDirectoryName( + qbs.targetOS, "device") + + ".platform") + readonly property path simulatorPlatformPath: FileInfo.joinPaths( + platformsPath, + DarwinTools.applePlatformDirectoryName( + qbs.targetOS, "simulator") + + ".platform") + readonly property path sdkPath: FileInfo.joinPaths(sdksPath, + DarwinTools.applePlatformDirectoryName( + qbs.targetOS, platformType, + shortSdkVersion) + ".sdk") + + // private properties + readonly property path toolchainsPath: FileInfo.joinPaths(developerPath, "Toolchains") + readonly property path platformsPath: FileInfo.joinPaths(developerPath, "Platforms") + readonly property path sdksPath: FileInfo.joinPaths(platformPath, "Developer", "SDKs") + + readonly property path platformInfoPlist: FileInfo.joinPaths(platformPath, "Info.plist") + readonly property path sdkSettingsPlist: FileInfo.joinPaths(sdkPath, "SDKSettings.plist") + readonly property path toolchainInfoPlist: FileInfo.joinPaths(toolchainPath, + "ToolchainInfo.plist") + + readonly property stringList _actualSigningIdentity: { + if (/^[A-Fa-f0-9]{40}$/.test(signingIdentity)) { + return [signingIdentity, signingIdentity]; + } + + var result = []; + + if (signingIdentity) { + var identities = Utilities.signingIdentities(); + for (var key in identities) { + if (identities[key].subjectInfo.CN === signingIdentity) { + result.push([key, signingIdentity]); + } + } + + if (result.length == 0) { + throw "Unable to find signingIdentity '" + signingIdentity + "'"; + } + + if (result.length > 1) { + throw "Signing identity '" + signingIdentity + "' is ambiguous"; + } + } + + return result[0]; + } + + property path provisioningProfilesPath: { + return FileInfo.joinPaths(Environment.getEnv("HOME"), "Library/MobileDevice/Provisioning Profiles"); + } + + readonly property stringList standardArchitectures: _architectureSettings["ARCHS_STANDARD"] + + readonly property var _architectureSettings: xcodeProbe.architectureSettings + + readonly property var _availableSdks: xcodeProbe.availableSdks + + readonly property var _latestSdk: _availableSdks[_availableSdks.length - 1] + + readonly property var _sdkSettings: { + if (_availableSdks) { + for (var i in _availableSdks) { + if (_availableSdks[i]["Version"] === sdk) + return _availableSdks[i]; + if (_availableSdks[i]["CanonicalName"] === sdk) + return _availableSdks[i]; + } + + // Latest SDK available for the platform + if (DarwinTools.applePlatformName(qbs.targetOS, platformType) === sdk) + return _latestSdk; + } + } + + qbs.sysroot: sdkPath + + validate: { + if (!_availableSdks) { + throw "There are no SDKs available for this platform in the Xcode installation."; + } + + if (!_sdkSettings) { + throw "There is no matching SDK available for " + sdk + "."; + } + + var validator = new ModUtils.PropertyValidator("xcode"); + validator.setRequiredProperty("developerPath", developerPath); + validator.setRequiredProperty("sdk", sdk); + validator.setRequiredProperty("sdkName", sdkName); + validator.setRequiredProperty("sdkVersion", sdkVersion); + validator.setRequiredProperty("toolchainsPath", toolchainsPath); + validator.setRequiredProperty("toolchainPath", toolchainPath); + validator.setRequiredProperty("platformsPath", platformsPath); + validator.setRequiredProperty("platformPath", platformPath); + validator.setRequiredProperty("sdksPath", sdkPath); + validator.setRequiredProperty("sdkPath", sdkPath); + validator.addVersionValidator("sdkVersion", sdkVersion, 2, 3); + validator.addCustomValidator("sdkName", sdkName, function (value) { + return value === DarwinTools.applePlatformDirectoryName( + qbs.targetOS, platformType, shortSdkVersion, false).toLowerCase(); + }, "is '" + sdkName + "', but target OS is [" + qbs.targetOS.join(",") + + "] and Xcode SDK version is '" + sdkVersion + "'"); + validator.addCustomValidator("sdk", sdk, function (value) { + return value === sdkName || (value + shortSdkVersion) === sdkName; + }, "is '" + sdk + "', but canonical SDK name is '" + sdkName + "'"); + validator.validate(); + } + + property var buildEnv: { + var env = { + "DEVELOPER_DIR": developerPath, + "SDKROOT": sdkPath + }; + + var prefixes = [platformPath + "/Developer", toolchainPath, developerPath]; + for (var i = 0; i < prefixes.length; ++i) { + var codesign_allocate = prefixes[i] + "/usr/bin/codesign_allocate"; + if (File.exists(codesign_allocate)) { + env["CODESIGN_ALLOCATE"] = codesign_allocate; + break; + } + } + + return env; + } + + setupBuildEnvironment: { + var v = new ModUtils.EnvironmentVariable("PATH", product.qbs.pathListSeparator, false); + v.prepend(product.xcode.platformPath + "/Developer/usr/bin"); + v.prepend(product.xcode.developerPath + "/usr/bin"); + v.set(); + + for (var key in product.xcode.buildEnv) { + v = new ModUtils.EnvironmentVariable(key); + v.value = product.xcode.buildEnv[key]; + v.set(); + } + } + + Group { + name: "Provisioning Profiles" + prefix: xcode.provisioningProfilesPath + "/" + files: ["*.mobileprovision", "*.provisionprofile"] + fileTags: ["xcode.provisioningprofile"] + } + + FileTagger { + fileTags: ["xcode.entitlements"] + patterns: ["*.entitlements"] + } + + FileTagger { + fileTags: ["xcode.provisioningprofile"] + patterns: ["*.mobileprovision", "*.provisionprofile"] + } + + Rule { + inputs: ["xcode.provisioningprofile"] + + Artifact { + filePath: FileInfo.joinPaths("provisioning-profiles", input.fileName + ".xml") + fileTags: ["xcode.provisioningprofile.data"] + } + + prepare: { + var cmds = []; + + var cmd = new Command("openssl", ["smime", "-verify", "-noverify", "-inform", "DER", + "-in", input.filePath, "-out", output.filePath]); + cmd.silent = true; + cmd.stderrFilterFunction = function (output) { + return output.replace("Verification successful\n", ""); + }; + cmds.push(cmd); + + cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.inputFilePath = input.filePath; + cmd.outputFilePath = output.filePath; + cmd.sourceCode = function() { + var propertyList = new PropertyList(); + try { + propertyList.readFromFile(outputFilePath); + propertyList.readFromObject({ + data: propertyList.toObject(), + fileName: FileInfo.fileName(inputFilePath), + filePath: inputFilePath + }); + propertyList.writeToFile(outputFilePath, "xml1"); + } finally { + propertyList.clear(); + } + }; + cmds.push(cmd); + + return cmds; + } + } + + Rule { + multiplex: true + inputs: ["xcode.provisioningprofile.data"] + outputFileTags: ["xcode.provisioningprofile.main", "xcode.provisioningprofile.data.main"] + + outputArtifacts: { + var artifacts = []; + for (var i = 0; i < inputs["xcode.provisioningprofile.data"].length; ++i) { + var dataFile = inputs["xcode.provisioningprofile.data"][i].filePath; + var query = product.moduleProperty("xcode", "provisioningProfile"); + var obj = Xcode.provisioningProfilePlistContents(dataFile); + if (obj && obj.data && (obj.data.UUID === query || obj.data.Name === query)) { + console.log("Using provisioning profile: " + obj.filePath); + artifacts.push({ + filePath: obj.fileName, + fileTags: ["xcode.provisioningprofile.main"], + qbs: { _inputFilePath: obj.filePath } + }); + + artifacts.push({ + filePath: obj.fileName + ".xml", + fileTags: ["xcode.provisioningprofile.data.main"], + qbs: { _inputFilePath: dataFile } + }); + } + } + return artifacts; + } + + prepare: { + var cmds = []; + for (var tag in outputs) { + for (var i = 0; i < outputs[tag].length; ++i) { + var output = outputs[tag][i]; + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.inputFilePath = output.qbs._inputFilePath; // there's no such prop in qbs, see QBS-754 + cmd.outputFilePath = output.filePath; + cmd.sourceCode = function() { + File.copy(inputFilePath, outputFilePath); + }; + cmds.push(cmd); + } + } + return cmds; + } + } + + Rule { + inputs: ["xcode.entitlements", "xcode.provisioningprofile.data.main"] + + Artifact { + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + ".xcent") + fileTags: ["xcent"] + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating entitlements"; + cmd.highlight = "codegen"; + cmd.bundleIdentifier = product.moduleProperty("bundle", "identifier"); + cmd.signingEntitlements = inputs["xcode.entitlements"] + ? inputs["xcode.entitlements"].map(function (a) { return a.filePath; }) + : []; + cmd.platformPath = ModUtils.moduleProperty(product, "platformPath"); + cmd.sdkPath = ModUtils.moduleProperty(product, "sdkPath"); + cmd.sourceCode = function() { + var i; + var provData = Xcode.provisioningProfilePlistContents(input.filePath); + if (provData) + provData = provData.data; + + var aggregateEntitlements = {}; + + // Start building up an aggregate entitlements plist from the files in the SDKs, + // which contain placeholders in the same manner as Info.plist + function entitlementsFileContents(path) { + return File.exists(path) ? BundleTools.infoPlistContents(path) : undefined; + } + var entitlementsSources = [ + entitlementsFileContents(FileInfo.joinPaths(platformPath, "Entitlements.plist")), + entitlementsFileContents(FileInfo.joinPaths(sdkPath, "Entitlements.plist")) + ]; + + for (i = 0; i < signingEntitlements.length; ++i) { + entitlementsSources.push(entitlementsFileContents(signingEntitlements[i])); + } + + for (i = 0; i < entitlementsSources.length; ++i) { + var contents = entitlementsSources[i]; + for (var key in contents) { + if (contents.hasOwnProperty(key)) + aggregateEntitlements[key] = contents[key]; + } + } + + contents = provData["Entitlements"]; + for (key in contents) { + if (contents.hasOwnProperty(key) && !aggregateEntitlements.hasOwnProperty(key)) + aggregateEntitlements[key] = contents[key]; + } + + // Expand entitlements variables with data from the provisioning profile + var env = { + "AppIdentifierPrefix": provData["ApplicationIdentifierPrefix"] + ".", + "CFBundleIdentifier": bundleIdentifier + }; + DarwinTools.expandPlistEnvironmentVariables(aggregateEntitlements, env, true); + + // Anything with an undefined or otherwise empty value should be removed + // Only JSON-formatted plists can have null values, other formats error out + // This also follows Xcode behavior + DarwinTools.cleanPropertyList(aggregateEntitlements); + + var plist = new PropertyList(); + try { + plist.readFromObject(aggregateEntitlements); + plist.writeToFile(outputs.xcent[0].filePath, "xml1"); + } finally { + plist.clear(); + } + }; + return [cmd]; + } + } +} diff --git a/share/share.qbs b/share/share.qbs new file mode 100644 index 00000000..9349d5c8 --- /dev/null +++ b/share/share.qbs @@ -0,0 +1,146 @@ +import qbs +import qbs.File +import qbs.FileInfo +import qbs.TextFile +import qbs.Utilities + +Product { + name: "qbs resources" + type: ["qbs qml type descriptions", "qbs qml type bundle"] + Depends { name: "qbsbuildconfig" } + + Group { + name: "Incredibuild" + prefix: "../bin/" + files: ["ibmsvc.xml", "ibqbs.bat"] + fileTags: [] + qbs.install: qbs.targetOS.contains("windows") + qbs.installDir: qbsbuildconfig.appInstallDir + } + + Group { + name: "Python executables" + files: ["../src/3rdparty/python/bin/dmgbuild"] + fileTags: ["qbs resources"] + qbs.install: true + qbs.installDir: qbsbuildconfig.libexecInstallDir + qbs.installSourceBase: "../src/3rdparty/python/bin" + } + + Group { + name: "Python packages" + prefix: "../src/3rdparty/python/**/" + files: ["*.py"] + fileTags: ["qbs resources"] + qbs.install: true + qbs.installDir: qbsbuildconfig.resourcesInstallDir + "/share/qbs/python" + qbs.installSourceBase: "../src/3rdparty/python/lib/python2.7/site-packages" + } + + Group { + name: "Imports" + files: ["qbs/imports/qbs/**/*"] + fileTags: ["qbs resources"] + qbs.install: true + qbs.installDir: qbsbuildconfig.resourcesInstallDir + "/share" + qbs.installSourceBase: "." + } + + Group { + name: "Modules" + files: ["qbs/modules/**/*"] + fileTags: ["qbs resources"] + qbs.install: true + qbs.installDir: qbsbuildconfig.resourcesInstallDir + "/share" + qbs.installSourceBase: "." + } + + Group { + name: "Module providers" + files: ["qbs/module-providers/**/*"] + fileTags: ["qbs resources"] + qbs.install: true + qbs.installDir: qbsbuildconfig.resourcesInstallDir + "/share" + qbs.installSourceBase: "." + } + + Group { + name: "Examples as resources" + files: ["../examples/**/*"] + fileTags: [] + qbs.install: true + qbs.installDir: qbsbuildconfig.resourcesInstallDir + "/share/qbs" + qbs.installSourceBase: ".." + } + + Rule { + condition: Utilities.versionCompare(product.qbs.version, "1.9.1") >= 0 + multiplex: true + Artifact { + filePath: "qbs.qmltypes" + fileTags: ["qbs qml type descriptions"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Generating " + output.fileName; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + var tf; + try { + tf = new TextFile(output.filePath, TextFile.WriteOnly); + tf.writeLine(Utilities.qmlTypeInfo()); + } finally { + if (tf) + tf.close(); + } + }; + return cmd; + } + } + + Rule { + condition: Utilities.versionCompare(product.qbs.version, "1.9.1") >= 0 + multiplex: true + Artifact { + filePath: "qbs-bundle.json" + fileTags: ["qbs qml type bundle"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Generating " + output.fileName; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + var tf; + try { + var imports = File.directoryEntries(FileInfo.joinPaths(product.sourceDirectory, + "qbs", "imports", "qbs"), + File.Dirs | File.NoDotAndDotDot).filter( + function(i) { return i !== "base"; }).concat( + Utilities.builtinExtensionNames()).map( + function(i) { return "qbs." + i; }); + imports.sort(); + var obj = { + "name": "qbs", + "searchPaths": ["$(QBS_IMPORT_PATH)"], + "installPaths": ["$(QBS_IMPORT_PATH)"], + "implicitImports": ["__javascriptQt5__"], + "supportedImports": ["qbs"].concat(imports) + }; + tf = new TextFile(output.filePath, TextFile.WriteOnly); + tf.writeLine(JSON.stringify(obj, undefined, 4)); + } finally { + if (tf) + tf.close(); + } + }; + return cmd; + } + } + + Group { + name: "QML Type Info" + fileTagsFilter: ["qbs qml type descriptions", "qbs qml type bundle"] + qbs.install: true + qbs.installDir: qbsbuildconfig.qmlTypeDescriptionsInstallDir + } +} diff --git a/src/3rdparty/python/.gitignore b/src/3rdparty/python/.gitignore new file mode 100644 index 00000000..3a4de078 --- /dev/null +++ b/src/3rdparty/python/.gitignore @@ -0,0 +1,3 @@ +*.pyc +*.dist-info +*.egg-info diff --git a/src/3rdparty/python/bin/dmgbuild b/src/3rdparty/python/bin/dmgbuild new file mode 100755 index 00000000..4647448d --- /dev/null +++ b/src/3rdparty/python/bin/dmgbuild @@ -0,0 +1,41 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals +from __future__ import print_function + +import dmgbuild +import sys +import argparse + +parser = argparse.ArgumentParser(description='Construct a disk image file.') +parser.add_argument('volume_name', metavar='volume-name', + help='The name to give to the volume (this will appear in the title bar when the user mounts the disk image).') +parser.add_argument('filename', metavar='output.dmg', + help='The filename of the disk image to create.') +parser.add_argument('-s', '--settings', + help='The path of the settings file.') +parser.add_argument('-D', dest='defines', action='append', default=[], + help='Define a value for the settings file (e.g. -Dfoo=bar).') +parser.add_argument('--no-hidpi', dest='lookForHiDPI', action='store_false', default=True, + help='Do not search for HiDPI versions of the background image (if specified)') + + +args = parser.parse_args() + +defines = {} +for d in args.defines: + k,v = d.split('=', 1) + k = k.strip() + v = v.strip() + if (v.startswith("'") and v.endswith("'")) \ + or (v.startswith('"') and v.endswith('"')): + v = v[1:-1] + defines[k] = v + +dmgbuild.build_dmg( + args.filename.decode('utf_8'), + args.volume_name.decode('utf_8'), + args.settings.decode('utf_8'), + defines=defines, + lookForHiDPI=args.lookForHiDPI) diff --git a/src/3rdparty/python/lib/python2.7/site-packages/biplist/LICENSE b/src/3rdparty/python/lib/python2.7/site-packages/biplist/LICENSE new file mode 100644 index 00000000..1c7ba6cc --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/biplist/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2010, Andrew Wooster +All rights reserved. + +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 biplist 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 HOLDER 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. diff --git a/src/3rdparty/python/lib/python2.7/site-packages/biplist/__init__.py b/src/3rdparty/python/lib/python2.7/site-packages/biplist/__init__.py new file mode 100644 index 00000000..f9d5836d --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/biplist/__init__.py @@ -0,0 +1,977 @@ +"""biplist -- a library for reading and writing binary property list files. + +Binary Property List (plist) files provide a faster and smaller serialization +format for property lists on OS X. This is a library for generating binary +plists which can be read by OS X, iOS, or other clients. + +The API models the plistlib API, and will call through to plistlib when +XML serialization or deserialization is required. + +To generate plists with UID values, wrap the values with the Uid object. The +value must be an int. + +To generate plists with NSData/CFData values, wrap the values with the +Data object. The value must be a string. + +Date values can only be datetime.datetime objects. + +The exceptions InvalidPlistException and NotBinaryPlistException may be +thrown to indicate that the data cannot be serialized or deserialized as +a binary plist. + +Plist generation example: + + from biplist import * + from datetime import datetime + plist = {'aKey':'aValue', + '0':1.322, + 'now':datetime.now(), + 'list':[1,2,3], + 'tuple':('a','b','c') + } + try: + writePlist(plist, "example.plist") + except (InvalidPlistException, NotBinaryPlistException), e: + print "Something bad happened:", e + +Plist parsing example: + + from biplist import * + try: + plist = readPlist("example.plist") + print plist + except (InvalidPlistException, NotBinaryPlistException), e: + print "Not a plist:", e +""" + +from collections import namedtuple +import datetime +import io +import math +import plistlib +from struct import pack, unpack, unpack_from +from struct import error as struct_error +import sys +import time + +try: + unicode + unicodeEmpty = r'' +except NameError: + unicode = str + unicodeEmpty = '' +try: + long +except NameError: + long = int +try: + {}.iteritems + iteritems = lambda x: x.iteritems() +except AttributeError: + iteritems = lambda x: x.items() + +__all__ = [ + 'Uid', 'Data', 'readPlist', 'writePlist', 'readPlistFromString', + 'writePlistToString', 'InvalidPlistException', 'NotBinaryPlistException' +] + +# Apple uses Jan 1, 2001 as a base for all plist date/times. +apple_reference_date = datetime.datetime.utcfromtimestamp(978307200) + +class Uid(object): + """Wrapper around integers for representing UID values. This + is used in keyed archiving.""" + integer = 0 + def __init__(self, integer): + self.integer = integer + + def __repr__(self): + return "Uid(%d)" % self.integer + + def __eq__(self, other): + if isinstance(self, Uid) and isinstance(other, Uid): + return self.integer == other.integer + return False + + def __cmp__(self, other): + return self.integer - other.integer + + def __lt__(self, other): + return self.integer < other.integer + + def __hash__(self): + return self.integer + + def __int__(self): + return int(self.integer) + +class Data(bytes): + """Wrapper around bytes to distinguish Data values.""" + +class InvalidPlistException(Exception): + """Raised when the plist is incorrectly formatted.""" + +class NotBinaryPlistException(Exception): + """Raised when a binary plist was expected but not encountered.""" + +def readPlist(pathOrFile): + """Raises NotBinaryPlistException, InvalidPlistException""" + didOpen = False + result = None + if isinstance(pathOrFile, (bytes, unicode)): + pathOrFile = open(pathOrFile, 'rb') + didOpen = True + try: + reader = PlistReader(pathOrFile) + result = reader.parse() + except NotBinaryPlistException as e: + try: + pathOrFile.seek(0) + result = None + if hasattr(plistlib, 'loads'): + contents = None + if isinstance(pathOrFile, (bytes, unicode)): + with open(pathOrFile, 'rb') as f: + contents = f.read() + else: + contents = pathOrFile.read() + result = plistlib.loads(contents) + else: + result = plistlib.readPlist(pathOrFile) + result = wrapDataObject(result, for_binary=True) + except Exception as e: + raise InvalidPlistException(e) + finally: + if didOpen: + pathOrFile.close() + return result + +def wrapDataObject(o, for_binary=False): + if isinstance(o, Data) and not for_binary: + v = sys.version_info + if not (v[0] >= 3 and v[1] >= 4): + o = plistlib.Data(o) + elif isinstance(o, (bytes, plistlib.Data)) and for_binary: + if hasattr(o, 'data'): + o = Data(o.data) + elif isinstance(o, tuple): + o = wrapDataObject(list(o), for_binary) + o = tuple(o) + elif isinstance(o, list): + for i in range(len(o)): + o[i] = wrapDataObject(o[i], for_binary) + elif isinstance(o, dict): + for k in o: + o[k] = wrapDataObject(o[k], for_binary) + return o + +def writePlist(rootObject, pathOrFile, binary=True): + if not binary: + rootObject = wrapDataObject(rootObject, binary) + if hasattr(plistlib, "dump"): + if isinstance(pathOrFile, (bytes, unicode)): + with open(pathOrFile, 'wb') as f: + return plistlib.dump(rootObject, f) + else: + return plistlib.dump(rootObject, pathOrFile) + else: + return plistlib.writePlist(rootObject, pathOrFile) + else: + didOpen = False + if isinstance(pathOrFile, (bytes, unicode)): + pathOrFile = open(pathOrFile, 'wb') + didOpen = True + writer = PlistWriter(pathOrFile) + result = writer.writeRoot(rootObject) + if didOpen: + pathOrFile.close() + return result + +def readPlistFromString(data): + return readPlist(io.BytesIO(data)) + +def writePlistToString(rootObject, binary=True): + if not binary: + rootObject = wrapDataObject(rootObject, binary) + if hasattr(plistlib, "dumps"): + return plistlib.dumps(rootObject) + elif hasattr(plistlib, "writePlistToBytes"): + return plistlib.writePlistToBytes(rootObject) + else: + return plistlib.writePlistToString(rootObject) + else: + ioObject = io.BytesIO() + writer = PlistWriter(ioObject) + writer.writeRoot(rootObject) + return ioObject.getvalue() + +def is_stream_binary_plist(stream): + stream.seek(0) + header = stream.read(7) + if header == b'bplist0': + return True + else: + return False + +PlistTrailer = namedtuple('PlistTrailer', 'offsetSize, objectRefSize, offsetCount, topLevelObjectNumber, offsetTableOffset') +PlistByteCounts = namedtuple('PlistByteCounts', 'nullBytes, boolBytes, intBytes, realBytes, dateBytes, dataBytes, stringBytes, uidBytes, arrayBytes, setBytes, dictBytes') + +class PlistReader(object): + file = None + contents = '' + offsets = None + trailer = None + currentOffset = 0 + # Used to detect recursive object references. + offsetsStack = [] + + def __init__(self, fileOrStream): + """Raises NotBinaryPlistException.""" + self.reset() + self.file = fileOrStream + + def parse(self): + return self.readRoot() + + def reset(self): + self.trailer = None + self.contents = '' + self.offsets = [] + self.currentOffset = 0 + self.offsetsStack = [] + + def readRoot(self): + result = None + self.reset() + # Get the header, make sure it's a valid file. + if not is_stream_binary_plist(self.file): + raise NotBinaryPlistException() + self.file.seek(0) + self.contents = self.file.read() + if len(self.contents) < 32: + raise InvalidPlistException("File is too short.") + trailerContents = self.contents[-32:] + try: + self.trailer = PlistTrailer._make(unpack("!xxxxxxBBQQQ", trailerContents)) + + if pow(2, self.trailer.offsetSize*8) < self.trailer.offsetTableOffset: + raise InvalidPlistException("Offset size insufficient to reference all objects.") + + if pow(2, self.trailer.objectRefSize*8) < self.trailer.offsetCount: + raise InvalidPlistException("Too many offsets to represent in size of object reference representation.") + + offset_size = self.trailer.offsetSize * self.trailer.offsetCount + offset = self.trailer.offsetTableOffset + + if offset + offset_size > pow(2, 64): + raise InvalidPlistException("Offset table is excessively long.") + + if self.trailer.offsetSize > 16: + raise InvalidPlistException("Offset size is greater than maximum integer size.") + + if self.trailer.objectRefSize == 0: + raise InvalidPlistException("Object reference size is zero.") + + if offset >= len(self.contents) - 32: + raise InvalidPlistException("Offset table offset is too large.") + + if offset < len("bplist00x"): + raise InvalidPlistException("Offset table offset is too small.") + + if self.trailer.topLevelObjectNumber >= self.trailer.offsetCount: + raise InvalidPlistException("Top level object number is larger than the number of objects.") + + offset_contents = self.contents[offset:offset+offset_size] + offset_i = 0 + offset_table_length = len(offset_contents) + + while offset_i < self.trailer.offsetCount: + begin = self.trailer.offsetSize*offset_i + end = begin+self.trailer.offsetSize + if end > offset_table_length: + raise InvalidPlistException("End of object is at invalid offset %d in offset table of length %d" % (end, offset_table_length)) + tmp_contents = offset_contents[begin:end] + tmp_sized = self.getSizedInteger(tmp_contents, self.trailer.offsetSize) + self.offsets.append(tmp_sized) + offset_i += 1 + self.setCurrentOffsetToObjectNumber(self.trailer.topLevelObjectNumber) + result = self.readObject() + except TypeError as e: + raise InvalidPlistException(e) + return result + + def setCurrentOffsetToObjectNumber(self, objectNumber): + if objectNumber > len(self.offsets) - 1: + raise InvalidPlistException("Invalid offset number: %d" % objectNumber) + self.currentOffset = self.offsets[objectNumber] + if self.currentOffset in self.offsetsStack: + raise InvalidPlistException("Recursive data structure detected in object: %d" % objectNumber) + + def beginOffsetProtection(self): + self.offsetsStack.append(self.currentOffset) + return self.currentOffset + + def endOffsetProtection(self, offset): + try: + index = self.offsetsStack.index(offset) + self.offsetsStack = self.offsetsStack[:index] + except ValueError as e: + pass + + def readObject(self): + protection = self.beginOffsetProtection() + result = None + tmp_byte = self.contents[self.currentOffset:self.currentOffset+1] + if len(tmp_byte) != 1: + raise InvalidPlistException("No object found at offset: %d" % self.currentOffset) + marker_byte = unpack("!B", tmp_byte)[0] + format = (marker_byte >> 4) & 0x0f + extra = marker_byte & 0x0f + self.currentOffset += 1 + + def proc_extra(extra): + if extra == 0b1111: + extra = self.readObject() + return extra + + # bool, null, or fill byte + if format == 0b0000: + if extra == 0b0000: + result = None + elif extra == 0b1000: + result = False + elif extra == 0b1001: + result = True + elif extra == 0b1111: + pass # fill byte + else: + raise InvalidPlistException("Invalid object found at offset: %d" % (self.currentOffset - 1)) + # int + elif format == 0b0001: + result = self.readInteger(pow(2, extra)) + # real + elif format == 0b0010: + result = self.readReal(extra) + # date + elif format == 0b0011 and extra == 0b0011: + result = self.readDate() + # data + elif format == 0b0100: + extra = proc_extra(extra) + result = self.readData(extra) + # ascii string + elif format == 0b0101: + extra = proc_extra(extra) + result = self.readAsciiString(extra) + # Unicode string + elif format == 0b0110: + extra = proc_extra(extra) + result = self.readUnicode(extra) + # uid + elif format == 0b1000: + result = self.readUid(extra) + # array + elif format == 0b1010: + extra = proc_extra(extra) + result = self.readArray(extra) + # set + elif format == 0b1100: + extra = proc_extra(extra) + result = set(self.readArray(extra)) + # dict + elif format == 0b1101: + extra = proc_extra(extra) + result = self.readDict(extra) + else: + raise InvalidPlistException("Invalid object found: {format: %s, extra: %s}" % (bin(format), bin(extra))) + self.endOffsetProtection(protection) + return result + + def readContents(self, length, description="Object contents"): + end = self.currentOffset + length + if end >= len(self.contents) - 32: + raise InvalidPlistException("%s extends into trailer" % description) + elif length < 0: + raise InvalidPlistException("%s length is less than zero" % length) + data = self.contents[self.currentOffset:end] + return data + + def readInteger(self, byteSize): + data = self.readContents(byteSize, "Integer") + self.currentOffset = self.currentOffset + byteSize + return self.getSizedInteger(data, byteSize, as_number=True) + + def readReal(self, length): + to_read = pow(2, length) + data = self.readContents(to_read, "Real") + if length == 2: # 4 bytes + result = unpack('>f', data)[0] + elif length == 3: # 8 bytes + result = unpack('>d', data)[0] + else: + raise InvalidPlistException("Unknown Real of length %d bytes" % to_read) + return result + + def readRefs(self, count): + refs = [] + i = 0 + while i < count: + fragment = self.readContents(self.trailer.objectRefSize, "Object reference") + ref = self.getSizedInteger(fragment, len(fragment)) + refs.append(ref) + self.currentOffset += self.trailer.objectRefSize + i += 1 + return refs + + def readArray(self, count): + if not isinstance(count, (int, long)): + raise InvalidPlistException("Count of entries in dict isn't of integer type.") + result = [] + values = self.readRefs(count) + i = 0 + while i < len(values): + self.setCurrentOffsetToObjectNumber(values[i]) + value = self.readObject() + result.append(value) + i += 1 + return result + + def readDict(self, count): + if not isinstance(count, (int, long)): + raise InvalidPlistException("Count of keys/values in dict isn't of integer type.") + result = {} + keys = self.readRefs(count) + values = self.readRefs(count) + i = 0 + while i < len(keys): + self.setCurrentOffsetToObjectNumber(keys[i]) + key = self.readObject() + self.setCurrentOffsetToObjectNumber(values[i]) + value = self.readObject() + result[key] = value + i += 1 + return result + + def readAsciiString(self, length): + if not isinstance(length, (int, long)): + raise InvalidPlistException("Length of ASCII string isn't of integer type.") + data = self.readContents(length, "ASCII string") + result = unpack("!%ds" % length, data)[0] + self.currentOffset += length + return str(result.decode('ascii')) + + def readUnicode(self, length): + if not isinstance(length, (int, long)): + raise InvalidPlistException("Length of Unicode string isn't of integer type.") + actual_length = length*2 + data = self.readContents(actual_length, "Unicode string") + self.currentOffset += actual_length + return data.decode('utf_16_be') + + def readDate(self): + data = self.readContents(8, "Date") + x = unpack(">d", data)[0] + if math.isnan(x): + raise InvalidPlistException("Date is NaN") + # Use timedelta to workaround time_t size limitation on 32-bit python. + try: + result = datetime.timedelta(seconds=x) + apple_reference_date + except OverflowError: + if x > 0: + result = datetime.datetime.max + else: + result = datetime.datetime.min + self.currentOffset += 8 + return result + + def readData(self, length): + if not isinstance(length, (int, long)): + raise InvalidPlistException("Length of data isn't of integer type.") + result = self.readContents(length, "Data") + self.currentOffset += length + return Data(result) + + def readUid(self, length): + if not isinstance(length, (int, long)): + raise InvalidPlistException("Uid length isn't of integer type.") + return Uid(self.readInteger(length+1)) + + def getSizedInteger(self, data, byteSize, as_number=False): + """Numbers of 8 bytes are signed integers when they refer to numbers, but unsigned otherwise.""" + result = 0 + if byteSize == 0: + raise InvalidPlistException("Encountered integer with byte size of 0.") + # 1, 2, and 4 byte integers are unsigned + elif byteSize == 1: + result = unpack('>B', data)[0] + elif byteSize == 2: + result = unpack('>H', data)[0] + elif byteSize == 4: + result = unpack('>L', data)[0] + elif byteSize == 8: + if as_number: + result = unpack('>q', data)[0] + else: + result = unpack('>Q', data)[0] + elif byteSize <= 16: + # Handle odd-sized or integers larger than 8 bytes + # Don't naively go over 16 bytes, in order to prevent infinite loops. + result = 0 + if hasattr(int, 'from_bytes'): + result = int.from_bytes(data, 'big') + else: + for byte in data: + if not isinstance(byte, int): # Python3.0-3.1.x return ints, 2.x return str + byte = unpack_from('>B', byte)[0] + result = (result << 8) | byte + else: + raise InvalidPlistException("Encountered integer longer than 16 bytes.") + return result + +class HashableWrapper(object): + def __init__(self, value): + self.value = value + def __repr__(self): + return "" % [self.value] + +class BoolWrapper(object): + def __init__(self, value): + self.value = value + def __repr__(self): + return "" % self.value + +class FloatWrapper(object): + _instances = {} + def __new__(klass, value): + # Ensure FloatWrapper(x) for a given float x is always the same object + wrapper = klass._instances.get(value) + if wrapper is None: + wrapper = object.__new__(klass) + wrapper.value = value + klass._instances[value] = wrapper + return wrapper + def __repr__(self): + return "" % self.value + +class StringWrapper(object): + __instances = {} + + encodedValue = None + encoding = None + + def __new__(cls, value): + '''Ensure we only have a only one instance for any string, + and that we encode ascii as 1-byte-per character when possible''' + + encodedValue = None + + for encoding in ('ascii', 'utf_16_be'): + try: + encodedValue = value.encode(encoding) + except: pass + if encodedValue is not None: + if encodedValue not in cls.__instances: + cls.__instances[encodedValue] = super(StringWrapper, cls).__new__(cls) + cls.__instances[encodedValue].encodedValue = encodedValue + cls.__instances[encodedValue].encoding = encoding + return cls.__instances[encodedValue] + + raise ValueError('Unable to get ascii or utf_16_be encoding for %s' % repr(value)) + + def __len__(self): + '''Return roughly the number of characters in this string (half the byte length)''' + if self.encoding == 'ascii': + return len(self.encodedValue) + else: + return len(self.encodedValue)//2 + + def __lt__(self, other): + return self.encodedValue < other.encodedValue + + @property + def encodingMarker(self): + if self.encoding == 'ascii': + return 0b0101 + else: + return 0b0110 + + def __repr__(self): + return '' % (self.encoding, self.encodedValue) + +class PlistWriter(object): + header = b'bplist00bybiplist1.0' + file = None + byteCounts = None + trailer = None + computedUniques = None + writtenReferences = None + referencePositions = None + wrappedTrue = None + wrappedFalse = None + # Used to detect recursive object references. + objectsStack = [] + + def __init__(self, file): + self.reset() + self.file = file + self.wrappedTrue = BoolWrapper(True) + self.wrappedFalse = BoolWrapper(False) + + def reset(self): + self.byteCounts = PlistByteCounts(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + self.trailer = PlistTrailer(0, 0, 0, 0, 0) + + # A set of all the uniques which have been computed. + self.computedUniques = set() + # A list of all the uniques which have been written. + self.writtenReferences = {} + # A dict of the positions of the written uniques. + self.referencePositions = {} + + self.objectsStack = [] + + def positionOfObjectReference(self, obj): + """If the given object has been written already, return its + position in the offset table. Otherwise, return None.""" + return self.writtenReferences.get(obj) + + def writeRoot(self, root): + """ + Strategy is: + - write header + - wrap root object so everything is hashable + - compute size of objects which will be written + - need to do this in order to know how large the object refs + will be in the list/dict/set reference lists + - write objects + - keep objects in writtenReferences + - keep positions of object references in referencePositions + - write object references with the length computed previously + - computer object reference length + - write object reference positions + - write trailer + """ + output = self.header + wrapped_root = self.wrapRoot(root) + self.computeOffsets(wrapped_root, asReference=True, isRoot=True) + self.trailer = self.trailer._replace(**{'objectRefSize':self.intSize(len(self.computedUniques))}) + self.writeObjectReference(wrapped_root, output) + output = self.writeObject(wrapped_root, output, setReferencePosition=True) + + # output size at this point is an upper bound on how big the + # object reference offsets need to be. + self.trailer = self.trailer._replace(**{ + 'offsetSize':self.intSize(len(output)), + 'offsetCount':len(self.computedUniques), + 'offsetTableOffset':len(output), + 'topLevelObjectNumber':0 + }) + + output = self.writeOffsetTable(output) + output += pack('!xxxxxxBBQQQ', *self.trailer) + self.file.write(output) + + def beginRecursionProtection(self, obj): + if not isinstance(obj, (set, dict, list, tuple)): + return + if id(obj) in self.objectsStack: + raise InvalidPlistException("Recursive containers are not allowed in plists.") + self.objectsStack.append(id(obj)) + + def endRecursionProtection(self, obj): + if not isinstance(obj, (set, dict, list, tuple)): + return + try: + index = self.objectsStack.index(id(obj)) + self.objectsStack = self.objectsStack[:index] + except ValueError as e: + pass + + def wrapRoot(self, root): + result = None + self.beginRecursionProtection(root) + + if isinstance(root, bool): + if root is True: + result = self.wrappedTrue + else: + result = self.wrappedFalse + elif isinstance(root, float): + result = FloatWrapper(root) + elif isinstance(root, set): + n = set() + for value in root: + n.add(self.wrapRoot(value)) + result = HashableWrapper(n) + elif isinstance(root, dict): + n = {} + for key, value in iteritems(root): + n[self.wrapRoot(key)] = self.wrapRoot(value) + result = HashableWrapper(n) + elif isinstance(root, list): + n = [] + for value in root: + n.append(self.wrapRoot(value)) + result = HashableWrapper(n) + elif isinstance(root, tuple): + n = tuple([self.wrapRoot(value) for value in root]) + result = HashableWrapper(n) + elif isinstance(root, (str, unicode)) and not isinstance(root, Data): + result = StringWrapper(root) + elif isinstance(root, bytes): + result = Data(root) + else: + result = root + + self.endRecursionProtection(root) + return result + + def incrementByteCount(self, field, incr=1): + self.byteCounts = self.byteCounts._replace(**{field:self.byteCounts.__getattribute__(field) + incr}) + + def computeOffsets(self, obj, asReference=False, isRoot=False): + def check_key(key): + if key is None: + raise InvalidPlistException('Dictionary keys cannot be null in plists.') + elif isinstance(key, Data): + raise InvalidPlistException('Data cannot be dictionary keys in plists.') + elif not isinstance(key, StringWrapper): + raise InvalidPlistException('Keys must be strings.') + + def proc_size(size): + if size > 0b1110: + size += self.intSize(size) + return size + # If this should be a reference, then we keep a record of it in the + # uniques table. + if asReference: + if obj in self.computedUniques: + return + else: + self.computedUniques.add(obj) + + if obj is None: + self.incrementByteCount('nullBytes') + elif isinstance(obj, BoolWrapper): + self.incrementByteCount('boolBytes') + elif isinstance(obj, Uid): + size = self.intSize(obj.integer) + self.incrementByteCount('uidBytes', incr=1+size) + elif isinstance(obj, (int, long)): + size = self.intSize(obj) + self.incrementByteCount('intBytes', incr=1+size) + elif isinstance(obj, FloatWrapper): + size = self.realSize(obj) + self.incrementByteCount('realBytes', incr=1+size) + elif isinstance(obj, datetime.datetime): + self.incrementByteCount('dateBytes', incr=2) + elif isinstance(obj, Data): + size = proc_size(len(obj)) + self.incrementByteCount('dataBytes', incr=1+size) + elif isinstance(obj, StringWrapper): + size = proc_size(len(obj)) + self.incrementByteCount('stringBytes', incr=1+size) + elif isinstance(obj, HashableWrapper): + obj = obj.value + if isinstance(obj, set): + size = proc_size(len(obj)) + self.incrementByteCount('setBytes', incr=1+size) + for value in obj: + self.computeOffsets(value, asReference=True) + elif isinstance(obj, (list, tuple)): + size = proc_size(len(obj)) + self.incrementByteCount('arrayBytes', incr=1+size) + for value in obj: + asRef = True + self.computeOffsets(value, asReference=True) + elif isinstance(obj, dict): + size = proc_size(len(obj)) + self.incrementByteCount('dictBytes', incr=1+size) + for key, value in iteritems(obj): + check_key(key) + self.computeOffsets(key, asReference=True) + self.computeOffsets(value, asReference=True) + else: + raise InvalidPlistException("Unknown object type: %s (%s)" % (type(obj).__name__, repr(obj))) + + def writeObjectReference(self, obj, output): + """Tries to write an object reference, adding it to the references + table. Does not write the actual object bytes or set the reference + position. Returns a tuple of whether the object was a new reference + (True if it was, False if it already was in the reference table) + and the new output. + """ + position = self.positionOfObjectReference(obj) + if position is None: + self.writtenReferences[obj] = len(self.writtenReferences) + output += self.binaryInt(len(self.writtenReferences) - 1, byteSize=self.trailer.objectRefSize) + return (True, output) + else: + output += self.binaryInt(position, byteSize=self.trailer.objectRefSize) + return (False, output) + + def writeObject(self, obj, output, setReferencePosition=False): + """Serializes the given object to the output. Returns output. + If setReferencePosition is True, will set the position the + object was written. + """ + def proc_variable_length(format, length): + result = b'' + if length > 0b1110: + result += pack('!B', (format << 4) | 0b1111) + result = self.writeObject(length, result) + else: + result += pack('!B', (format << 4) | length) + return result + + def timedelta_total_seconds(td): + # Shim for Python 2.6 compatibility, which doesn't have total_seconds. + # Make one argument a float to ensure the right calculation. + return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10.0**6) / 10.0**6 + + if setReferencePosition: + self.referencePositions[obj] = len(output) + + if obj is None: + output += pack('!B', 0b00000000) + elif isinstance(obj, BoolWrapper): + if obj.value is False: + output += pack('!B', 0b00001000) + else: + output += pack('!B', 0b00001001) + elif isinstance(obj, Uid): + size = self.intSize(obj.integer) + output += pack('!B', (0b1000 << 4) | size - 1) + output += self.binaryInt(obj.integer) + elif isinstance(obj, (int, long)): + byteSize = self.intSize(obj) + root = math.log(byteSize, 2) + output += pack('!B', (0b0001 << 4) | int(root)) + output += self.binaryInt(obj, as_number=True) + elif isinstance(obj, FloatWrapper): + # just use doubles + output += pack('!B', (0b0010 << 4) | 3) + output += self.binaryReal(obj) + elif isinstance(obj, datetime.datetime): + try: + timestamp = (obj - apple_reference_date).total_seconds() + except AttributeError: + timestamp = timedelta_total_seconds(obj - apple_reference_date) + output += pack('!B', 0b00110011) + output += pack('!d', float(timestamp)) + elif isinstance(obj, Data): + output += proc_variable_length(0b0100, len(obj)) + output += obj + elif isinstance(obj, StringWrapper): + output += proc_variable_length(obj.encodingMarker, len(obj)) + output += obj.encodedValue + elif isinstance(obj, bytes): + output += proc_variable_length(0b0101, len(obj)) + output += obj + elif isinstance(obj, HashableWrapper): + obj = obj.value + if isinstance(obj, (set, list, tuple)): + if isinstance(obj, set): + output += proc_variable_length(0b1100, len(obj)) + else: + output += proc_variable_length(0b1010, len(obj)) + + objectsToWrite = [] + for objRef in sorted(obj) if isinstance(obj, set) else obj: + (isNew, output) = self.writeObjectReference(objRef, output) + if isNew: + objectsToWrite.append(objRef) + for objRef in objectsToWrite: + output = self.writeObject(objRef, output, setReferencePosition=True) + elif isinstance(obj, dict): + output += proc_variable_length(0b1101, len(obj)) + keys = [] + values = [] + objectsToWrite = [] + for key, value in sorted(iteritems(obj)): + keys.append(key) + values.append(value) + for key in keys: + (isNew, output) = self.writeObjectReference(key, output) + if isNew: + objectsToWrite.append(key) + for value in values: + (isNew, output) = self.writeObjectReference(value, output) + if isNew: + objectsToWrite.append(value) + for objRef in objectsToWrite: + output = self.writeObject(objRef, output, setReferencePosition=True) + return output + + def writeOffsetTable(self, output): + """Writes all of the object reference offsets.""" + all_positions = [] + writtenReferences = list(self.writtenReferences.items()) + writtenReferences.sort(key=lambda x: x[1]) + for obj,order in writtenReferences: + # Porting note: Elsewhere we deliberately replace empty unicdoe strings + # with empty binary strings, but the empty unicode string + # goes into writtenReferences. This isn't an issue in Py2 + # because u'' and b'' have the same hash; but it is in + # Py3, where they don't. + if bytes != str and obj == unicodeEmpty: + obj = b'' + position = self.referencePositions.get(obj) + if position is None: + raise InvalidPlistException("Error while writing offsets table. Object not found. %s" % obj) + output += self.binaryInt(position, self.trailer.offsetSize) + all_positions.append(position) + return output + + def binaryReal(self, obj): + # just use doubles + result = pack('>d', obj.value) + return result + + def binaryInt(self, obj, byteSize=None, as_number=False): + result = b'' + if byteSize is None: + byteSize = self.intSize(obj) + if byteSize == 1: + result += pack('>B', obj) + elif byteSize == 2: + result += pack('>H', obj) + elif byteSize == 4: + result += pack('>L', obj) + elif byteSize == 8: + if as_number: + result += pack('>q', obj) + else: + result += pack('>Q', obj) + elif byteSize <= 16: + try: + result = pack('>Q', 0) + pack('>Q', obj) + except struct_error as e: + raise InvalidPlistException("Unable to pack integer %d: %s" % (obj, e)) + else: + raise InvalidPlistException("Core Foundation can't handle integers with size greater than 16 bytes.") + return result + + def intSize(self, obj): + """Returns the number of bytes necessary to store the given integer.""" + # SIGNED + if obj < 0: # Signed integer, always 8 bytes + return 8 + # UNSIGNED + elif obj <= 0xFF: # 1 byte + return 1 + elif obj <= 0xFFFF: # 2 bytes + return 2 + elif obj <= 0xFFFFFFFF: # 4 bytes + return 4 + # SIGNED + # 0x7FFFFFFFFFFFFFFF is the max. + elif obj <= 0x7FFFFFFFFFFFFFFF: # 8 bytes signed + return 8 + elif obj <= 0xffffffffffffffff: # 8 bytes unsigned + return 16 + else: + raise InvalidPlistException("Core Foundation can't handle integers with size greater than 8 bytes.") + + def realSize(self, obj): + return 8 diff --git a/src/3rdparty/python/lib/python2.7/site-packages/biplist/qt_attribution.json b/src/3rdparty/python/lib/python2.7/site-packages/biplist/qt_attribution.json new file mode 100644 index 00000000..266e9dd8 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/biplist/qt_attribution.json @@ -0,0 +1,13 @@ +{ + "Id": "biplist", + "Name": "biplist", + "QDocModule": "qbs", + "QtUsage": "Used in the qbs dmg module for building Apple disk images.", + "Description": "biplist is a library for reading/writing binary plists.", + "Homepage": "https://bitbucket.org/wooster/biplist", + "Version": "1.0.2", + "License": "BSD 3-clause \"New\" or \"Revised\" License", + "LicenseId": "BSD-3-Clause", + "LicenseFile": "LICENSE", + "Copyright": "Copyright (c) 2010, Andrew Wooster" +} diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/LICENSE b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/LICENSE new file mode 100644 index 00000000..e91f4eb3 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Alastair Houghton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/__init__.py b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/__init__.py new file mode 100644 index 00000000..e7f985c3 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/__init__.py @@ -0,0 +1,3 @@ +from .core import build_dmg + +__all__ = ['dmgbuild'] diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/badge.py b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/badge.py new file mode 100644 index 00000000..159a5370 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/badge.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from Quartz import * +import math + +_REMOVABLE_DISK_PATH = '/System/Library/Extensions/IOStorageFamily.kext/Contents/Resources/Removable.icns' + +def badge_disk_icon(badge_file, output_file): + # Load the Removable disk icon + url = CFURLCreateWithFileSystemPath(None, _REMOVABLE_DISK_PATH, + kCFURLPOSIXPathStyle, False) + backdrop = CGImageSourceCreateWithURL(url, None) + backdropCount = CGImageSourceGetCount(backdrop) + + # Load the badge + url = CFURLCreateWithFileSystemPath(None, badge_file, + kCFURLPOSIXPathStyle, False) + badge = CGImageSourceCreateWithURL(url, None) + assert badge is not None, 'Unable to process image file: %s' % badge_file + badgeCount = CGImageSourceGetCount(badge) + + # Set up a destination for our target + url = CFURLCreateWithFileSystemPath(None, output_file, + kCFURLPOSIXPathStyle, False) + target = CGImageDestinationCreateWithURL(url, 'com.apple.icns', + backdropCount, None) + + # Get the RGB colorspace + rgbColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB) + + # Scale + scale = 1.0 + + # Perspective transform + corners = ((0.2, 0.95), (0.8, 0.95), (0.85, 0.35), (0.15, 0.35)) + + # Translation + position = (0.5, 0.5) + + for n in range(backdropCount): + props = CGImageSourceCopyPropertiesAtIndex(backdrop, n, None) + width = props['PixelWidth'] + height = props['PixelHeight'] + dpi = props['DPIWidth'] + depth = props['Depth'] + + # Choose the best sized badge image + bestWidth = None + bestHeight = None + bestBadge = None + bestDepth = None + bestDPI = None + for m in range(badgeCount): + badgeProps = CGImageSourceCopyPropertiesAtIndex(badge, m, None) + badgeWidth = badgeProps['PixelWidth'] + badgeHeight = badgeProps['PixelHeight'] + badgeDPI = badgeProps['DPIWidth'] + badgeDepth = badgeProps['Depth'] + + if bestBadge is None or (badgeWidth <= width + and (bestWidth > width + or badgeWidth > bestWidth + or (badgeWidth == bestWidth + and badgeDPI == dpi + and badgeDepth <= depth + and (bestDepth is None + or badgeDepth > bestDepth)))): + bestBadge = m + bestWidth = badgeWidth + bestHeight = badgeHeight + bestDPI = badgeDPI + bestDepth = badgeDepth + + badgeImage = CGImageSourceCreateImageAtIndex(badge, bestBadge, None) + badgeCI = CIImage.imageWithCGImage_(badgeImage) + + backgroundImage = CGImageSourceCreateImageAtIndex(backdrop, n, None) + backgroundCI = CIImage.imageWithCGImage_(backgroundImage) + + compositor = CIFilter.filterWithName_('CISourceOverCompositing') + lanczos = CIFilter.filterWithName_('CILanczosScaleTransform') + perspective = CIFilter.filterWithName_('CIPerspectiveTransform') + transform = CIFilter.filterWithName_('CIAffineTransform') + + lanczos.setValue_forKey_(badgeCI, kCIInputImageKey) + lanczos.setValue_forKey_(scale * float(width)/bestWidth, kCIInputScaleKey) + lanczos.setValue_forKey_(1.0, kCIInputAspectRatioKey) + + topLeft = (width * scale * corners[0][0], + width * scale * corners[0][1]) + topRight = (width * scale * corners[1][0], + width * scale * corners[1][1]) + bottomRight = (width * scale * corners[2][0], + width * scale * corners[2][1]) + bottomLeft = (width * scale * corners[3][0], + width * scale * corners[3][1]) + + out = lanczos.valueForKey_(kCIOutputImageKey) + if width >= 16: + perspective.setValue_forKey_(out, kCIInputImageKey) + perspective.setValue_forKey_(CIVector.vectorWithX_Y_(*topLeft), + 'inputTopLeft') + perspective.setValue_forKey_(CIVector.vectorWithX_Y_(*topRight), + 'inputTopRight') + perspective.setValue_forKey_(CIVector.vectorWithX_Y_(*bottomRight), + 'inputBottomRight') + perspective.setValue_forKey_(CIVector.vectorWithX_Y_(*bottomLeft), + 'inputBottomLeft') + out = perspective.valueForKey_(kCIOutputImageKey) + + tfm = NSAffineTransform.transform() + tfm.translateXBy_yBy_(math.floor((position[0] - 0.5 * scale) * width), + math.floor((position[1] - 0.5 * scale) * height)) + + transform.setValue_forKey_(out, kCIInputImageKey) + transform.setValue_forKey_(tfm, 'inputTransform') + out = transform.valueForKey_(kCIOutputImageKey) + + compositor.setValue_forKey_(out, kCIInputImageKey) + compositor.setValue_forKey_(backgroundCI, kCIInputBackgroundImageKey) + + result = compositor.valueForKey_(kCIOutputImageKey) + + cgContext = CGBitmapContextCreate(None, + width, + height, + 8, + 0, + rgbColorSpace, + kCGImageAlphaPremultipliedLast) + context = CIContext.contextWithCGContext_options_(cgContext, None) + + context.drawImage_inRect_fromRect_(result, + ((0, 0), (width, height)), + ((0, 0), (width, height))) + + image = CGBitmapContextCreateImage(cgContext) + + CGImageDestinationAddImage(target, image, props) + + CGImageDestinationFinalize(target) + diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/colors.py b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/colors.py new file mode 100644 index 00000000..1d252a6b --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/colors.py @@ -0,0 +1,494 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import re +import math + +class Color (object): + def to_rgb(self): + raise Exception('Must implement to_rgb() in subclasses') + +class RGB (Color): + def __init__(self, r, g, b): + self.r = r + self.g = g + self.b = b + + def to_rgb(self): + return self + +class HSL (Color): + def __init__(self, h, s, l): + self.h = h + self.s = s + self.l = l + + @staticmethod + def _hue_to_rgb(t1, t2, hue): + if hue < 0: + hue += 6 + elif hue >= 6: + hue -= 6 + + if hue < 1: + return (t2 - t1) * hue + t1 + elif hue < 3: + return t2 + elif hue < 4: + return (t2 - t1) * (4 - hue) + t1 + else: + return t1 + + def to_rgb(self): + hue = self.h / 60.0 + if self.l <= 0.5: + t2 = self.l * (self.s + 1) + else: + t2 = self.l + self.s - (self.l * self.s) + t1 = self.l * 2 - t2 + r = self._hue_to_rgb(t1, t2, hue + 2) + g = self._hue_to_rgb(t1, t2, hue) + b = self._hue_to_rgb(t1, t2, hue - 2) + return RGB(r, g, b) + +class HWB (Color): + def __init__(self, h, w, b): + self.h = h + self.w = w + self.b = b + + @staticmethod + def _hue_to_rgb(hue): + if hue < 0: + hue += 6 + elif hue >= 6: + hue -= 6 + + if hue < 1: + return hue + elif hue < 3: + return 1 + elif hue < 4: + return (4 - hue) + else: + return 0 + + def to_rgb(self): + hue = self.h / 60.0 + t1 = 1 - self.w - self.b + r = self._hue_to_rgb(hue + 2) * t1 + self.w + g = self._hue_to_rgb(hue) * t1 + self.w + b = self._hue_to_rgb(hue - 2) * t1 + self.w + return RGB(r, g, b) + +class CMYK (Color): + def __init__(self, c, m, y, k): + self.c = c + self.m = m + self.y = y + self.k = k + + def to_rgb(self): + r = 1.0 - min(1.0, self.c + self.k) + g = 1.0 - min(1.0, self.m + self.k) + b = 1.0 - min(1.0, self.y + self.k) + return RGB(r, g, b) + +class Gray (Color): + def __init__(self, g): + self.g = g + + def to_rgb(self): + return RGB(g, g, g) + +_x11_colors = { + 'aliceblue': (240, 248, 255), + 'antiquewhite': (250, 235, 215), + 'aqua': ( 0, 255, 255), + 'aquamarine': (127, 255, 212), + 'azure': (240, 255, 255), + 'beige': (245, 245, 220), + 'bisque': (255, 228, 196), + 'black': ( 0, 0, 0), + 'blanchedalmond': (255, 235, 205), + 'blue': ( 0, 0, 255), + 'blueviolet': (138, 43, 226), + 'brown': (165, 42, 42), + 'burlywood': (222, 184, 135), + 'cadetblue': ( 95, 158, 160), + 'chartreuse': (127, 255, 0), + 'chocolate': (210, 105, 30), + 'coral': (255, 127, 80), + 'cornflowerblue': (100, 149, 237), + 'cornsilk': (255, 248, 220), + 'crimson': (220, 20, 60), + 'cyan': ( 0, 255, 255), + 'darkblue': ( 0, 0, 139), + 'darkcyan': ( 0, 139, 139), + 'darkgoldenrod': (184, 134, 11), + 'darkgray': (169, 169, 169), + 'darkgreen': ( 0, 100, 0), + 'darkgrey': (169, 169, 169), + 'darkkhaki': (189, 183, 107), + 'darkmagenta': (139, 0, 139), + 'darkolivegreen': ( 85, 107, 47), + 'darkorange': (255, 140, 0), + 'darkorchid': (153, 50, 204), + 'darkred': (139, 0, 0), + 'darksalmon': (233, 150, 122), + 'darkseagreen': (143, 188, 143), + 'darkslateblue': ( 72, 61, 139), + 'darkslategray': ( 47, 79, 79), + 'darkslategrey': ( 47, 79, 79), + 'darkturquoise': ( 0, 206, 209), + 'darkviolet': (148, 0, 211), + 'deeppink': (255, 20, 147), + 'deepskyblue': ( 0, 191, 255), + 'dimgray': (105, 105, 105), + 'dimgrey': (105, 105, 105), + 'dodgerblue': ( 30, 144, 255), + 'firebrick': (178, 34, 34), + 'floralwhite': (255, 250, 240), + 'forestgreen': ( 34, 139, 34), + 'fuchsia': (255, 0, 255), + 'gainsboro': (220, 220, 220), + 'ghostwhite': (248, 248, 255), + 'gold': (255, 215, 0), + 'goldenrod': (218, 165, 32), + 'gray': (128, 128, 128), + 'grey': (128, 128, 128), + 'green': ( 0, 128, 0), + 'greenyellow': (173, 255, 47), + 'honeydew': (240, 255, 240), + 'hotpink': (255, 105, 180), + 'indianred': (205, 92, 92), + 'indigo': ( 75, 0, 130), + 'ivory': (255, 255, 240), + 'khaki': (240, 230, 140), + 'lavender': (230, 230, 250), + 'lavenderblush': (255, 240, 245), + 'lawngreen': (124, 252, 0), + 'lemonchiffon': (255, 250, 205), + 'lightblue': (173, 216, 230), + 'lightcoral': (240, 128, 128), + 'lightcyan': (224, 255, 255), + 'lightgoldenrodyellow': (250, 250, 210), + 'lightgray': (211, 211, 211), + 'lightgreen': (144, 238, 144), + 'lightgrey': (211, 211, 211), + 'lightpink': (255, 182, 193), + 'lightsalmon': (255, 160, 122), + 'lightseagreen': ( 32, 178, 170), + 'lightskyblue': (135, 206, 250), + 'lightslategray': (119, 136, 153), + 'lightslategrey': (119, 136, 153), + 'lightsteelblue': (176, 196, 222), + 'lightyellow': (255, 255, 224), + 'lime': ( 0, 255, 0), + 'limegreen': ( 50, 205, 50), + 'linen': (250, 240, 230), + 'magenta': (255, 0, 255), + 'maroon': (128, 0, 0), + 'mediumaquamarine': (102, 205, 170), + 'mediumblue': ( 0, 0, 205), + 'mediumorchid': (186, 85, 211), + 'mediumpurple': (147, 112, 219), + 'mediumseagreen': ( 60, 179, 113), + 'mediumslateblue': (123, 104, 238), + 'mediumspringgreen': ( 0, 250, 154), + 'mediumturquoise': ( 72, 209, 204), + 'mediumvioletred': (199, 21, 133), + 'midnightblue': ( 25, 25, 112), + 'mintcream': (245, 255, 250), + 'mistyrose': (255, 228, 225), + 'moccasin': (255, 228, 181), + 'navajowhite': (255, 222, 173), + 'navy': ( 0, 0, 128), + 'oldlace': (253, 245, 230), + 'olive': (128, 128, 0), + 'olivedrab': (107, 142, 35), + 'orange': (255, 165, 0), + 'orangered': (255, 69, 0), + 'orchid': (218, 112, 214), + 'palegoldenrod': (238, 232, 170), + 'palegreen': (152, 251, 152), + 'paleturquoise': (175, 238, 238), + 'palevioletred': (219, 112, 147), + 'papayawhip': (255, 239, 213), + 'peachpuff': (255, 218, 185), + 'peru': (205, 133, 63), + 'pink': (255, 192, 203), + 'plum': (221, 160, 221), + 'powderblue': (176, 224, 230), + 'purple': (128, 0, 128), + 'red': (255, 0, 0), + 'rosybrown': (188, 143, 143), + 'royalblue': ( 65, 105, 225), + 'saddlebrown': (139, 69, 19), + 'salmon': (250, 128, 114), + 'sandybrown': (244, 164, 96), + 'seagreen': ( 46, 139, 87), + 'seashell': (255, 245, 238), + 'sienna': (160, 82, 45), + 'silver': (192, 192, 192), + 'skyblue': (135, 206, 235), + 'slateblue': (106, 90, 205), + 'slategray': (112, 128, 144), + 'slategrey': (112, 128, 144), + 'snow': (255, 250, 250), + 'springgreen': ( 0, 255, 127), + 'steelblue': ( 70, 130, 180), + 'tan': (210, 180, 140), + 'teal': ( 0, 128, 128), + 'thistle': (216, 191, 216), + 'tomato': (255, 99, 71), + 'turquoise': ( 64, 224, 208), + 'violet': (238, 130, 238), + 'wheat': (245, 222, 179), + 'white': (255, 255, 255), + 'whitesmoke': (245, 245, 245), + 'yellow': (255, 255, 0), + 'yellowgreen': (154, 205, 50) + } + +_ws_re = re.compile('\s+') +_token_re = re.compile('[A-Za-z_][A-Za-z0-9_]*') +_hex_re = re.compile('#([0-9a-f]{3}(?:[0-9a-f]{3})?)$') +_number_re = re.compile('[0-9]*(\.[0-9]*)') + +class ColorParser (object): + def __init__(self, s): + self._string = s + self._pos = 0 + + def skipws(self): + m = _ws_re.match(self._string, self._pos) + if m: + self._pos = m.end(0) + + def expect(self, s, context=''): + if len(self._string) - self._pos < len(s) \ + or self._string[self._pos:self._pos + len(s)] != s: + raise ValueError('bad color "%s" - expected "%s"%s' + % (self._string, s, context)) + self._pos += len(s) + + def expectEnd(self): + if self._pos != len(self._string): + raise ValueError('junk at end of color "%s"' % self._string) + + def getToken(self): + m = _token_re.match(self._string, self._pos) + if m: + token = m.group(0) + + self._pos = m.end(0) + return token + return None + + def parseNumber(self, context=''): + m = _number_re.match(self._string, self._pos) + if m: + self._pos = m.end(0) + return float(m.group(0)) + raise ValueError('bad color "%s" - expected a number%s' + % (self._string, context)) + + def parseColor(self): + self.skipws() + + token = self.getToken() + if token: + if token == 'rgb': + return self.parseRGB() + elif token == 'hsl': + return self.parseHSL() + elif token == 'hwb': + return self.parseHWB() + elif token == 'cmyk': + return self.parseCMYK() + elif token == 'gray' or token == 'grey': + return self.parseGray() + + try: + r, g, b = _x11_colors[token] + except KeyError: + raise ValueError('unknown color name "%s"' % token) + + self.expectEnd() + + return RGB(r / 255.0, g / 255.0, b / 255.0) + + m = _hex_re.match(self._string, self._pos) + if m: + hrgb = m.group(1) + + if len(hrgb) == 3: + r = int('0x' + 2 * hrgb[0], 16) + g = int('0x' + 2 * hrgb[1], 16) + b = int('0x' + 2 * hrgb[2], 16) + else: + r = int('0x' + hrgb[0:2], 16) + g = int('0x' + hrgb[2:4], 16) + b = int('0x' + hrgb[4:6], 16) + + self._pos = m.end(0) + self.skipws() + + self.expectEnd() + + return RGB(r / 255.0, g / 255.0, b / 255.0) + + raise ValueError('bad color syntax "%s"' % self._string) + + def parseRGB(self): + self.expect('(', 'after "rgb"') + self.skipws() + + r = self.parseValue() + + self.skipws() + self.expect(',', 'in "rgb"') + self.skipws() + + g = self.parseValue() + + self.skipws() + self.expect(',', 'in "rgb"') + self.skipws() + + b = self.parseValue() + + self.skipws() + self.expect(')', 'at end of "rgb"') + + self.skipws() + self.expectEnd() + + return RGB(r, g, b) + + def parseHSL(self): + self.expect('(', 'after "hsl"') + self.skipws() + + h = self.parseAngle() + + self.skipws() + self.expect(',', 'in "hsl"') + self.skipws() + + s = self.parseValue() + + self.skipws() + self.expect(',', 'in "hsl"') + self.skipws() + + l = self.parseValue() + + self.skipws() + self.expect(')', 'at end of "hsl"') + + self.skipws() + self.expectEnd() + + return HSL(h, s, l) + + def parseHWB(self): + self.expect('(', 'after "hwb"') + self.skipws() + + h = self.parseAngle() + + self.skipws() + self.expect(',', 'in "hwb"') + self.skipws() + + w = self.parseValue() + + self.skipws() + self.expect(',', 'in "hwb"') + self.skipws() + + b = self.parseValue() + + self.skipws() + self.expect(')', 'at end of "hwb"') + + self.skipws() + self.expectEnd() + + return HWB(h, w, b) + + def parseCMYK(self): + self.expect('(', 'after "cmyk"') + self.skipws() + + c = self.parseValue() + + self.skipws() + self.expect(',', 'in "cmyk"') + self.skipws() + + m = self.parseValue() + + self.skipws() + self.expect(',', 'in "cmyk"') + self.skipws() + + y = self.parseValue() + + self.skipws() + self.expect(',', 'in "cmyk"') + self.skipws() + + k = self.parseValue() + + self.skipws() + self.expect(')', 'at end of "cmyk"') + + self.skipws() + self.expectEnd() + + return CMYK(c, m, y, k) + + def parseGray(self): + self.expect('(', 'after "gray"') + self.skipws() + + g = self.parseValue() + + self.skipws() + self.expect(')', 'at end of "gray') + + self.skipws() + self.expectEnd() + + return Gray(g) + + def parseValue(self): + n = self.parseNumber() + self.skipws() + if self._string[self._pos] == '%': + n = n / 100.0 + self.pos += 1 + return n + + def parseAngle(self): + n = self.parseNumber() + self.skipws() + tok = self.getToken() + if tok == 'rad': + n = n * 180.0 / math.pi + elif tok == 'grad' or tok == 'gon': + n = n * 0.9 + elif tok != 'deg': + raise ValueError('bad angle unit "%s"' % tok) + return n + +_color_re = re.compile('\s*(#|rgb|hsl|hwb|cmyk|gray|grey|%s)' + % '|'.join(_x11_colors.keys())) +def isAColor(s): + return _color_re.match(s) + +def parseColor(s): + return ColorParser(s).parseColor() diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/core.py b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/core.py new file mode 100644 index 00000000..463730b6 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/core.py @@ -0,0 +1,597 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import os +import pkg_resources +import re +import shutil +import stat +import subprocess +import sys +import tempfile +import tokenize +import json + +try: + {}.iteritems + iteritems = lambda x: x.iteritems() + iterkeys = lambda x: x.iterkeys() +except AttributeError: + iteritems = lambda x: x.items() + iterkeys = lambda x: x.keys() +try: + unicode +except NameError: + unicode = str + +import biplist +from mac_alias import * +from ds_store import * + +from . import colors +from . import licensing + +try: + from . import badge +except ImportError: + badge = None + +_hexcolor_re = re.compile(r'#[0-9a-f]{3}(?:[0-9a-f]{3})?') + +class DMGError(Exception): + pass + +def hdiutil(cmd, *args, **kwargs): + plist = kwargs.get('plist', True) + all_args = ['/usr/bin/hdiutil', cmd] + all_args.extend(args) + if plist: + all_args.append('-plist') + p = subprocess.Popen(all_args, stdout=subprocess.PIPE, close_fds=True) + output, errors = p.communicate() + if plist: + results = biplist.readPlistFromString(output) + else: + results = output + retcode = p.wait() + return retcode, results + +# On Python 2 we can just execfile() it, but Python 3 deprecated that +def load_settings(filename, settings): + if sys.version_info[0] == 2: + execfile(filename, settings, settings) + else: + encoding = 'utf-8' + with open(filename, 'rb') as fp: + try: + encoding = tokenize.detect_encoding(fp.readline)[0] + except SyntaxError: + pass + + with open(filename, 'r', encoding=encoding) as fp: + exec(compile(fp.read(), filename, 'exec'), settings, settings) + +def load_json(filename, settings): + """Read an appdmg .json spec. Uses the defaults for appdmg, rather than + the usual defaults for dmgbuild. """ + + with open(filename, 'r') as fp: + json_data = json.load(fp) + + if 'title' not in json_data: + raise ValueError('missing \'title\' in JSON settings file') + if 'contents' not in json_data: + raise ValueError('missing \'contents\' in JSON settings file') + + settings['volume_name'] = json_data['title'] + settings['icon'] = json_data.get('icon', None) + settings['badge_icon'] = json_data.get('badge-icon', None) + bk = json_data.get('background', None) + if bk is None: + bk = json_data.get('background-color', None) + if bk is not None: + settings['background'] = bk + settings['icon_size'] = json_data.get('icon-size', 80) + wnd = json_data.get('window', { 'position': (100, 100), + 'size': (640, 480) }) + pos = wnd.get('position', { 'x': 100, 'y': 100 }) + siz = wnd.get('size', { 'width': 640, 'height': 480 }) + settings['window_rect'] = ((pos.get('x', 100), pos.get('y', 100)), + (siz.get('width', 640), siz.get('height', 480))) + settings['format'] = json_data.get('format', 'UDZO') + settings['compression_level'] = json_data.get('compression-level', None) + settings['license'] = json_data.get('license', None) + files = [] + symlinks = {} + icon_locations = {} + for fileinfo in json_data.get('contents', []): + if 'path' not in fileinfo: + raise ValueError('missing \'path\' in contents in JSON settings file') + if 'x' not in fileinfo: + raise ValueError('missing \'x\' in contents in JSON settings file') + if 'y' not in fileinfo: + raise ValueError('missing \'y\' in contents in JSON settings file') + + kind = fileinfo.get('type', 'file') + path = fileinfo['path'] + name = fileinfo.get('name', os.path.basename(path.rstrip('/'))) + if kind == 'file': + files.append((path, name)) + elif kind == 'link': + symlinks[name] = path + elif kind == 'position': + pass + icon_locations[name] = (fileinfo['x'], fileinfo['y']) + + settings['files'] = files + settings['symlinks'] = symlinks + settings['icon_locations'] = icon_locations + +def build_dmg(filename, volume_name, settings_file=None, settings={}, + defines={}, lookForHiDPI=True): + options = { + # Default settings + 'filename': filename, + 'volume_name': volume_name, + 'format': 'UDBZ', + 'compression_level': None, + 'size': None, + 'files': [], + 'symlinks': {}, + 'icon': None, + 'badge_icon': None, + 'background': None, + 'show_status_bar': False, + 'show_tab_view': False, + 'show_toolbar': False, + 'show_pathbar': False, + 'show_sidebar': False, + 'sidebar_width': 180, + 'arrange_by': None, + 'grid_offset': (0, 0), + 'grid_spacing': 100.0, + 'scroll_position': (0.0, 0.0), + 'show_icon_preview': False, + 'show_item_info': False, + 'label_pos': 'bottom', + 'text_size': 16.0, + 'icon_size': 128.0, + 'include_icon_view_settings': 'auto', + 'include_list_view_settings': 'auto', + 'list_icon_size': 16.0, + 'list_text_size': 12.0, + 'list_scroll_position': (0, 0), + 'list_sort_by': 'name', + 'list_use_relative_dates': True, + 'list_calculate_all_sizes': False, + 'list_columns': ('name', 'date-modified', 'size', 'kind', 'date-added'), + 'list_column_widths': { + 'name': 300, + 'date-modified': 181, + 'date-created': 181, + 'date-added': 181, + 'date-last-opened': 181, + 'size': 97, + 'kind': 115, + 'label': 100, + 'version': 75, + 'comments': 300, + }, + 'list_column_sort_directions': { + 'name': 'ascending', + 'date-modified': 'descending', + 'date-created': 'descending', + 'date-added': 'descending', + 'date-last-opened': 'descending', + 'size': 'descending', + 'kind': 'ascending', + 'label': 'ascending', + 'version': 'ascending', + 'comments': 'ascending', + }, + 'window_rect': ((100, 100), (640, 280)), + 'default_view': 'icon-view', + 'icon_locations': {}, + 'license': None, + 'defines': defines + } + + # Execute the settings file + if settings_file: + # We now support JSON settings files using appdmg's format + if settings_file.endswith('.json'): + load_json(settings_file, options) + else: + load_settings(settings_file, options) + + # Add any overrides + options.update(settings) + + # Set up the finder data + bounds = options['window_rect'] + + bounds_string = '{{%s, %s}, {%s, %s}}' % (bounds[0][0], + bounds[0][1], + bounds[1][0], + bounds[1][1]) + bwsp = { + 'ShowStatusBar': options['show_status_bar'], + 'WindowBounds': bounds_string, + 'ContainerShowSidebar': False, + 'PreviewPaneVisibility': False, + 'SidebarWidth': options['sidebar_width'], + 'ShowTabView': options['show_tab_view'], + 'ShowToolbar': options['show_toolbar'], + 'ShowPathbar': options['show_pathbar'], + 'ShowSidebar': options['show_sidebar'] + } + + arrange_options = { + 'name': 'name', + 'date-modified': 'dateModified', + 'date-created': 'dateCreated', + 'date-added': 'dateAdded', + 'date-last-opened': 'dateLastOpened', + 'size': 'size', + 'kind': 'kind', + 'label': 'label', + } + + icvp = { + 'viewOptionsVersion': 1, + 'backgroundType': 0, + 'backgroundColorRed': 1.0, + 'backgroundColorGreen': 1.0, + 'backgroundColorBlue': 1.0, + 'gridOffsetX': float(options['grid_offset'][0]), + 'gridOffsetY': float(options['grid_offset'][1]), + 'gridSpacing': float(options['grid_spacing']), + 'arrangeBy': str(arrange_options.get(options['arrange_by'], 'none')), + 'showIconPreview': options['show_icon_preview'] == True, + 'showItemInfo': options['show_item_info'] == True, + 'labelOnBottom': options['label_pos'] == 'bottom', + 'textSize': float(options['text_size']), + 'iconSize': float(options['icon_size']), + 'scrollPositionX': float(options['scroll_position'][0]), + 'scrollPositionY': float(options['scroll_position'][1]) + } + + background = options['background'] + + columns = { + 'name': 'name', + 'date-modified': 'dateModified', + 'date-created': 'dateCreated', + 'date-added': 'dateAdded', + 'date-last-opened': 'dateLastOpened', + 'size': 'size', + 'kind': 'kind', + 'label': 'label', + 'version': 'version', + 'comments': 'comments' + } + + default_widths = { + 'name': 300, + 'date-modified': 181, + 'date-created': 181, + 'date-added': 181, + 'date-last-opened': 181, + 'size': 97, + 'kind': 115, + 'label': 100, + 'version': 75, + 'comments': 300, + } + + default_sort_directions = { + 'name': 'ascending', + 'date-modified': 'descending', + 'date-created': 'descending', + 'date-added': 'descending', + 'date-last-opened': 'descending', + 'size': 'descending', + 'kind': 'ascending', + 'label': 'ascending', + 'version': 'ascending', + 'comments': 'ascending', + } + + lsvp = { + 'viewOptionsVersion': 1, + 'sortColumn': columns.get(options['list_sort_by'], 'name'), + 'textSize': float(options['list_text_size']), + 'iconSize': float(options['list_icon_size']), + 'showIconPreview': options['show_icon_preview'], + 'scrollPositionX': options['list_scroll_position'][0], + 'scrollPositionY': options['list_scroll_position'][1], + 'useRelativeDates': options['list_use_relative_dates'], + 'calculateAllSizes': options['list_calculate_all_sizes'], + } + + lsvp['columns'] = {} + cndx = {} + + for n, column in enumerate(options['list_columns']): + cndx[column] = n + width = options['list_column_widths'].get(column, + default_widths[column]) + asc = 'ascending' == options['list_column_sort_directions'].get(column, + default_sort_directions[column]) + + lsvp['columns'][columns[column]] = { + 'index': n, + 'width': width, + 'identifier': columns[column], + 'visible': True, + 'ascending': asc + } + + n = len(options['list_columns']) + for k in iterkeys(columns): + if cndx.get(k, None) is None: + cndx[k] = n + width = default_widths[k] + asc = 'ascending' == default_sort_directions[k] + + lsvp['columns'][columns[column]] = { + 'index': n, + 'width': width, + 'identifier': columns[column], + 'visible': False, + 'ascending': asc + } + + n += 1 + + default_view = options['default_view'] + views = { + 'icon-view': b'icnv', + 'column-view': b'clmv', + 'list-view': b'Nlsv', + 'coverflow': b'Flwv' + } + + icvl = (b'type', views.get(default_view, 'icnv')) + + include_icon_view_settings = default_view == 'icon-view' \ + or options['include_icon_view_settings'] not in \ + ('auto', 'no', 0, False, None) + include_list_view_settings = default_view in ('list-view', 'coverflow') \ + or options['include_list_view_settings'] not in \ + ('auto', 'no', 0, False, None) + + filename = options['filename'] + volume_name = options['volume_name'] + + # Construct a writeable image to start with + dirname, basename = os.path.split(os.path.realpath(filename)) + if not basename.endswith('.dmg'): + basename += '.dmg' + writableFile = tempfile.NamedTemporaryFile(dir=dirname, prefix='.temp', + suffix=basename) + + total_size = options['size'] + if total_size == None: + # Start with a size of 128MB - this way we don't need to calculate the + # size of the background image, volume icon, and .DS_Store file (and + # 128 MB should be well sufficient for even the most outlandish image + # sizes, like an uncompressed 5K multi-resolution TIFF) + total_size = 128 * 1024 * 1024 + + def roundup(x, n): + return x if x % n == 0 else x + n - x % n + + for path in options['files']: + if isinstance(path, tuple): + path = path[0] + + if not os.path.islink(path) and os.path.isdir(path): + for dirpath, dirnames, filenames in os.walk(path): + for f in filenames: + fp = os.path.join(dirpath, f) + total_size += roundup(os.lstat(fp).st_size, 4096) + else: + total_size += roundup(os.lstat(path).st_size, 4096) + + for name,target in iteritems(options['symlinks']): + total_size += 4096 + + total_size = str(max(total_size / 1024, 1024)) + 'K' + + ret, output = hdiutil('create', + '-ov', + '-volname', volume_name, + '-fs', 'HFS+', + '-fsargs', '-c c=64,a=16,e=16', + '-size', total_size, + writableFile.name) + + if ret: + raise DMGError('Unable to create disk image') + + ret, output = hdiutil('attach', + '-nobrowse', + '-owners', 'off', + '-noidme', + writableFile.name) + + if ret: + raise DMGError('Unable to attach disk image') + + try: + for info in output['system-entities']: + if info.get('mount-point', None): + device = info['dev-entry'] + mount_point = info['mount-point'] + + icon = options['icon'] + if badge: + badge_icon = options['badge_icon'] + else: + badge_icon = None + icon_target_path = os.path.join(mount_point, '.VolumeIcon.icns') + if icon: + shutil.copyfile(icon, icon_target_path) + elif badge_icon: + badge.badge_disk_icon(badge_icon, icon_target_path) + + if icon or badge_icon: + subprocess.call(['/usr/bin/SetFile', '-a', 'C', mount_point]) + + background_bmk = None + + if not isinstance(background, (str, unicode)): + pass + elif colors.isAColor(background): + c = colors.parseColor(background).to_rgb() + + icvp['backgroundType'] = 1 + icvp['backgroundColorRed'] = float(c.r) + icvp['backgroundColorGreen'] = float(c.g) + icvp['backgroundColorBlue'] = float(c.b) + else: + if os.path.isfile(background): + # look to see if there are HiDPI resources available + + if lookForHiDPI is True: + name, extension = os.path.splitext(os.path.basename(background)) + orderedImages = [background] + imageDirectory = os.path.dirname(background) + if imageDirectory == '': + imageDirectory = '.' + for candidateName in os.listdir(imageDirectory): + hasScale = re.match( + '^(?P.+)@(?P\d+)x(?P\.\w+)$', + candidateName) + if hasScale and name == hasScale.group('name') and \ + extension == hasScale.group('extension'): + scale = int(hasScale.group('scale')) + if len(orderedImages) < scale: + orderedImages += [None] * (scale - len(orderedImages)) + orderedImages[scale - 1] = os.path.join(imageDirectory, candidateName) + + if len(orderedImages) > 1: + # compile the grouped tiff + backgroundFile = tempfile.NamedTemporaryFile(suffix='.tiff') + background = backgroundFile.name + output = tempfile.TemporaryFile(mode='w+') + try: + subprocess.check_call( + ['/usr/bin/tiffutil', '-cathidpicheck'] + + list(filter(None, orderedImages)) + + ['-out', background], stdout=output, stderr=output) + except Exception as e: + output.seek(0) + raise ValueError( + 'unable to compile combined HiDPI file "%s" got error: %s\noutput: %s' + % (background, str(e), output.read())) + + _, kind = os.path.splitext(background) + path_in_image = os.path.join(mount_point, '.background' + kind) + shutil.copyfile(background, path_in_image) + elif pkg_resources.resource_exists('dmgbuild', 'resources/' + background + '.tiff'): + tiffdata = pkg_resources.resource_string( + 'dmgbuild', + 'resources/' + background + '.tiff') + path_in_image = os.path.join(mount_point, '.background.tiff') + + with open(path_in_image, 'wb') as f: + f.write(tiffdata) + else: + raise ValueError('background file "%s" not found' % background) + + alias = Alias.for_file(path_in_image) + background_bmk = Bookmark.for_file(path_in_image) + + icvp['backgroundType'] = 2 + icvp['backgroundImageAlias'] = biplist.Data(alias.to_bytes()) + + for f in options['files']: + if isinstance(f, tuple): + f_in_image = os.path.join(mount_point, f[1]) + f = f[0] + else: + basename = os.path.basename(f.rstrip('/')) + f_in_image = os.path.join(mount_point, basename) + + # use system ditto command to preserve code signing, etc. + subprocess.call(['/usr/bin/ditto', f, f_in_image]) + + for name,target in iteritems(options['symlinks']): + name_in_image = os.path.join(mount_point, name) + os.symlink(target, name_in_image) + + userfn = options.get('create_hook', None) + if callable(userfn): + userfn(mount_point, options) + + image_dsstore = os.path.join(mount_point, '.DS_Store') + + with DSStore.open(image_dsstore, 'w+') as d: + d['.']['vSrn'] = ('long', 1) + d['.']['bwsp'] = bwsp + if include_icon_view_settings: + d['.']['icvp'] = icvp + if background_bmk: + d['.']['pBBk'] = background_bmk + if include_list_view_settings: + d['.']['lsvp'] = lsvp + d['.']['icvl'] = icvl + + for k,v in iteritems(options['icon_locations']): + d[k]['Iloc'] = v + + # Delete .Trashes, if it gets created + shutil.rmtree(os.path.join(mount_point, '.Trashes'), True) + except: + # Always try to detach + hdiutil('detach', '-force', device, plist=False) + raise + + ret, output = hdiutil('detach', device, plist=False) + + if ret: + hdiutil('detach', '-force', device, plist=False) + raise DMGError('Unable to detach device cleanly') + + # Shrink the output to the minimum possible size + ret, output = hdiutil('resize', + '-quiet', + '-sectors', 'min', + writableFile.name, + plist=False) + + if ret: + raise DMGError('Unable to shrink') + + key_prefix = {'UDZO': 'zlib', 'UDBZ': 'bzip2', 'ULFO': 'lzfse'} + compression_level = options['compression_level'] + if options['format'] in key_prefix and compression_level: + compression_args = [ + '-imagekey', + key_prefix[options['format']] + '-level=' + str(compression_level) + ] + else: + compression_args = [] + + ret, output = hdiutil('convert', writableFile.name, + '-format', options['format'], + '-ov', + '-o', filename, *compression_args) + + if ret: + raise DMGError('Unable to convert') + + if options['license']: + ret, output = hdiutil('unflatten', '-quiet', filename, plist=False) + + if ret: + raise DMGError('Unable to unflatten to add license') + + licensing.add_license(filename, options['license']) + + ret, output = hdiutil('flatten', '-quiet', filename, plist=False) + + if ret: + raise DMGError('Unable to flatten after adding license') diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/licensing.py b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/licensing.py new file mode 100644 index 00000000..5c267909 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/licensing.py @@ -0,0 +1,461 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import os +import struct + +from .resources import * + +# ISO language and country codes to Macintosh Region codes (from Script.h) +# == CFLocaleCreateCanonicalLocaleIdentifierFromScriptManagerCodes(NULL, +# kTextLanguageDontCare, +# ) +region_codes = { + "en_US": 0, + "fr_FR": 1, + "en_GB": 2, + "de_DE": 3, + "it_IT": 4, + "nl_NL": 5, + "nl_BE": 6, + "sv_SE": 7, + "es_ES": 8, + "da_DK": 9, + "pt_PT": 10, + "fr_CA": 11, + "nb_NO": 12, + "he_IL": 13, + "ja_JP": 14, + "en_AU": 15, + "ar": 16, + "fi_FI": 17, + "fr_CH": 18, + "de_CH": 19, + "el_GR": 20, + "is_IS": 21, + "mt_MT": 22, + "el_CY": 23, + "tr_TR": 24, + "hi_IN": 33, + "ur_PK": 34, + "it_CH": 36, + "ro_RO": 39, + "grc": 40, + "lt_LT": 41, + "pl_PL": 42, + "hu_HU": 43, + "et_EE": 44, + "lv_LV": 45, + "se": 46, + "fo_FO": 47, + "fa_IR": 48, + "ru_RU": 49, + "ga_IE": 50, + "ko_KR": 51, + "zh_CN": 52, + "zh_TW": 53, + "th_TH": 54, + "cs_CZ": 56, + "sk_SK": 57, + "bn": 60, + "be_BY": 61, + "uk_UA": 62, + "sr_RS": 65, + "sl_SI": 66, + "mk_MK": 67, + "hr_HR": 68, + "pt_BR": 71, + "bg_BG": 72, + "ca_ES": 73, + "gd": 75, + "gv": 76, + "br": 77, + "iu_CA": 78, + "cy": 79, + "ga-Latg_IE": 81, + "en_CA": 82, + "dz_BT": 83, + "hy_AM": 84, + "ka_GE": 85, + "es_419": 86, + "to_TO": 88, + "fr_001": 91, + "de_AT": 92, + "gu_IN": 94, + "pa": 95, + "ur_IN": 96, + "vi_VN": 97, + "fr_BE": 98, + "uz_UZ": 99, + "en_SG": 100, + "nn_NO": 101, + "af_ZA": 102, + "eo": 103, + "mr_IN": 104, + "bo": 105, + "ne_NP": 106, + "kl": 107, + "en_IE": 108 +} + +# Map of region constants to script constants (from Script.h) +# TextEncoding textEncoding; +# GetTextEncodingFromScriptInfo(kTextScriptDontCare, kTextLanguageDontCare, , &textEncoding); +# == GetTextEncodingBase(textEncoding); +script_codes = { + 0: 0, + 1: 0, + 2: 0, + 3: 0, + 4: 0, + 5: 0, + 6: 0, + 7: 0, + 8: 0, + 9: 0, + 10: 0, + 11: 0, + 12: 0, + 13: 5, + 14: 1, + 15: 0, + 16: 4, + 17: 0, + 18: 0, + 19: 0, + 20: 6, + 21: 37, + 22: 0, + 23: 6, + 24: 35, + 25: 36, + 26: 0, + 27: 0, + 30: 0, + 31: 0, + 32: 0, + 33: 9, + 34: 4, + 35: 35, + 36: 0, + 37: 0, + 39: 38, + 40: 6, + 41: 29, + 42: 29, + 43: 29, + 44: 29, + 45: 29, + 46: 0, + 47: 37, + 48: 140, + 49: 7, + 50: 39, + 51: 3, + 52: 25, + 53: 2, + 54: 21, + 56: 29, + 57: 29, + 59: 29, + 60: 13, + 61: 7, + 62: 7, + 64: 6, + 65: 7, + 66: 36, + 67: 7, + 68: 36, + 70: 0, + 71: 0, + 72: 7, + 73: 0, + 75: 39, + 76: 39, + 77: 39, + 78: 236, + 79: 39, + 81: 40, + 82: 0, + 83: 26, + 84: 24, + 85: 23, + 86: 0, + 88: 0, + 91: 0, + 92: 0, + 94: 11, + 95: 10, + 96: 4, + 97: 30, + 98: 0, + 99: 7, + 100: 0, + 101: 0, + 102: 0, + 103: 0, + 104: 9, + 105: 26, + 106: 9, + 107: 0, + 108: 0 +} + +# Map of TextEncodingBase constants to Python encoder names (from TextCommon.h) +encodings_map = { + 0: 'mac_roman', # kTextEncodingMacRoman + 1: 'shift_jis', # kTextEncodingMacJapanese + 2: 'big5', # kTextEncodingMacChineseTrad + 3: 'euc_kr', # kTextEncodingMacKorean + 4: 'mac_arabic', # kTextEncodingMacArabic + 6: 'mac_greek', # kTextEncodingMacGreek + 7: 'mac_cyrillic', # kTextEncodingMacCyrillic + 21: 'iso8859_11', # kTextEncodingMacThai + 25: 'euc-cn', # kTextEncodingMacChineseSimp + 29: 'mac_centeuro', # kTextEncodingMacCentralEurRoman + 35: 'mac_turkish', # kTextEncodingMacTurkish + 36: 'mac_croatian', # kTextEncodingMacCroatian + 37: 'mac_iceland', # kTextEncodingMacIcelandic + 38: 'mac_romanian', # kTextEncodingMacRomanian + 140: 'mac_farsi' # kTextEncodingMacFarsi +} + +# Standard fonts +fonts = { + 'New York': 2, + 'Geneva': 3, + 'Monaco': 4, + 'Venice': 5, + 'London': 6, + 'Athens': 7, + 'San Francisco': 8, + 'Toronto': 9, + 'Cairo': 11, + 'Los Angeles': 12, + 'Times': 20, + 'Helvetica': 21, + 'Courier': 22, + 'Symbol': 23, + 'Mobile': 24 +} + +# Buttons (these come from the SLAResources file which you can find in the SLA +# SDK on developer.apple.com) +default_buttons = { + 0: ( + b'English', + b'Agree', + b'Disagree', + b'Print', + b'Save', + b'If you agree with the terms of this license, press "Agree" to ' + b'install the software. If you do not agree, press "Disagree".' + ), + + 3: ( + b'Deutsch', + b'Akzeptieren', + b'Ablehnen', + b'Drucken', + b'Sichern...', + b'Klicken Sie in \xd2Akzeptieren\xd3, wenn Sie mit den Bestimmungen des Software-Lizenzvertrags einverstanden sind. Falls nicht, bitte \xd2Ablehnen\xd3 anklicken. Sie k\x9annen die Software nur installieren, wenn Sie \xd2Akzeptieren\xd3 angeklickt haben.' + ), + + 8: ( + b'Espa\x96ol', + b'Aceptar', + b'No aceptar', + b'Imprimir', + b'Guardar...', + b'Si est\x87 de acuerdo con los t\x8erminos de esta licencia, pulse "Aceptar" para instalar el software. En el supuesto de que no est\x8e de acuerdo con los t\x8erminos de esta licencia, pulse "No aceptar."' + ), + + 1: ( + b'Fran\x8dais', + b'Accepter', + b'Refuser', + b'Imprimer', + b'Enregistrer...', + b'Si vous acceptez les termes de la pr\x8esente licence, cliquez sur "Accepter" afin d\'installer le logiciel. Si vous n\'\x90tes pas d\'accord avec les termes de la licence, cliquez sur "Refuser".' + ), + + 4: ( + b'Italiano', + b'Accetto', + b'Rifiuto', + b'Stampa', + b'Registra...', + b'Se accetti le condizioni di questa licenza, fai clic su "Accetto" per installare il software. Altrimenti fai clic su "Rifiuto".' + ), + + 14: ( + b'Japanese', + b'\x93\xaf\x88\xd3\x82\xb5\x82\xdc\x82\xb7', + b'\x93\xaf\x88\xd3\x82\xb5\x82\xdc\x82\xb9\x82\xf1', + b'\x88\xf3\x8d\xfc\x82\xb7\x82\xe9', + b'\x95\xdb\x91\xb6...', + b'\x96{\x83\\\x83t\x83g\x83E\x83G\x83A\x8eg\x97p\x8b\x96\x91\xf8\x8c_\x96\xf1\x82\xcc\x8f\xf0\x8c\x8f\x82\xc9\x93\xaf\x88\xd3\x82\xb3\x82\xea\x82\xe9\x8f\xea\x8d\x87\x82\xc9\x82\xcd\x81A\x83\\\x83t\x83g\x83E\x83G\x83A\x82\xf0\x83C\x83\x93\x83X\x83g\x81[\x83\x8b\x82\xb7\x82\xe9\x82\xbd\x82\xdf\x82\xc9\x81u\x93\xaf\x88\xd3\x82\xb5\x82\xdc\x82\xb7\x81v\x82\xf0\x89\x9f\x82\xb5\x82\xc4\x82\xad\x82\xbe\x82\xb3\x82\xa2\x81B\x81@\x93\xaf\x88\xd3\x82\xb3\x82\xea\x82\xc8\x82\xa2\x8f\xea\x8d\x87\x82\xc9\x82\xcd\x81A\x81u\x93\xaf\x88\xd3\x82\xb5\x82\xdc\x82\xb9\x82\xf1\x81v\x82\xf0\x89\x9f\x82\xb5\x82\xc4\x82\xad\x82\xbe\x82\xb3\x82\xa2\x81B' + ), + + 5: ( + b'Nederlands', + b'Ja', + b'Nee', + b'Print', + b'Bewaar...', + b'Indien u akkoord gaat met de voorwaarden van deze licentie, kunt u op \'Ja\' klikken om de programmatuur te installeren. Indien u niet akkoord gaat, klikt u op \'Nee\'.' + ), + + 7: ( + b'Svensk', + b'Godk\x8anns', + b'Avb\x9ajs', + b'Skriv ut', + b'Spara...', + b'Om Du godk\x8anner licensvillkoren klicka p\x8c "Godk\x8anns" f\x9ar att installera programprodukten. Om Du inte godk\x8anner licensvillkoren, klicka p\x8c "Avb\x9ajs".' + ), + + 71: ( + b'Portugu\x90s', + b'Concordar', + b'Discordar', + b'Imprimir', + b'Salvar...', + b'Se est\x87 de acordo com os termos desta licen\x8da, pressione "Concordar" para instalar o software. Se n\x8bo est\x87 de acordo, pressione "Discordar".' + ), + + 52: ( + b'Simplified Chinese', + b'\xcd\xac\xd2\xe2', + b'\xb2\xbb\xcd\xac\xd2\xe2', + b'\xb4\xf2\xd3\xa1', + b'\xb4\xe6\xb4\xa2\xa1\xad', + b'\xc8\xe7\xb9\xfb\xc4\xfa\xcd\xac\xd2\xe2\xb1\xbe\xd0\xed\xbf\xc9\xd0\xad\xd2\xe9\xb5\xc4\xcc\xf5\xbf\xee\xa3\xac\xc7\xeb\xb0\xb4\xa1\xb0\xcd\xac\xd2\xe2\xa1\xb1\xc0\xb4\xb0\xb2\xd7\xb0\xb4\xcb\xc8\xed\xbc\xfe\xa1\xa3\xc8\xe7\xb9\xfb\xc4\xfa\xb2\xbb\xcd\xac\xd2\xe2\xa3\xac\xc7\xeb\xb0\xb4\xa1\xb0\xb2\xbb\xcd\xac\xd2\xe2\xa1\xb1\xa1\xa3' + ), + + 53: ( + b'Traditional Chinese', + b'\xa6P\xb7N', + b'\xa4\xa3\xa6P\xb7N', + b'\xa6C\xa6L', + b'\xc0x\xa6s\xa1K', + b'\xa6p\xaaG\xb1z\xa6P\xb7N\xa5\xbb\xb3\\\xa5i\xc3\xd2\xb8\xcc\xaa\xba\xb1\xf8\xb4\xda\xa1A\xbd\xd0\xab\xf6\xa1\xa7\xa6P\xb7N\xa1\xa8\xa5H\xa6w\xb8\xcb\xb3n\xc5\xe9\xa1C\xa6p\xaaG\xa4\xa3\xa6P\xb7N\xa1A\xbd\xd0\xab\xf6\xa1\xa7\xa4\xa3\xa6P\xb7N\xa1\xa8\xa1C' + ), + + 9: ( + b'Dansk', + b'Enig', + b'Uenig', + b'Udskriv', + b'Arkiver...', + b'Hvis du accepterer betingelserne i licensaftalen, skal du klikke p\x8c \xd2Enig\xd3 for at installere softwaren. Klik p\x8c \xd2Uenig\xd3 for at annullere installeringen.' + ), + + 17: ( + b'Suomi', + b'Hyv\x8aksyn', + b'En hyv\x8aksy', + b'Tulosta', + b'Tallenna\xc9', + b'Hyv\x8aksy lisenssisopimuksen ehdot osoittamalla \xd5Hyv\x8aksy\xd5. Jos et hyv\x8aksy sopimuksen ehtoja, osoita \xd5En hyv\x8aksy\xd5.' + ), + + 51: ( + b'Korean', + b'\xb5\xbf\xc0\xc7', + b'\xb5\xbf\xc0\xc7 \xbe\xc8\xc7\xd4', + b'\xc7\xc1\xb8\xb0\xc6\xae', + b'\xc0\xfa\xc0\xe5...', + b'\xbb\xe7\xbf\xeb \xb0\xe8\xbe\xe0\xbc\xad\xc0\xc7 \xb3\xbb\xbf\xeb\xbf\xa1 \xb5\xbf\xc0\xc7\xc7\xcf\xb8\xe9, "\xb5\xbf\xc0\xc7" \xb4\xdc\xc3\xdf\xb8\xa6 \xb4\xad\xb7\xaf \xbc\xd2\xc7\xc1\xc6\xae\xbf\xfe\xbe\xee\xb8\xa6 \xbc\xb3\xc4\xa1\xc7\xcf\xbd\xca\xbd\xc3\xbf\xc0. \xb5\xbf\xc0\xc7\xc7\xcf\xc1\xf6 \xbe\xca\xb4\xc2\xb4\xd9\xb8\xe9, "\xb5\xbf\xc0\xc7 \xbe\xc8\xc7\xd4" \xb4\xdc\xc3\xdf\xb8\xa6 \xb4\xa9\xb8\xa3\xbd\xca\xbd\xc3\xbf\xc0.' + ), + + 12: ( + b'Norsk', + b'Enig', + b'Ikke enig', + b'Skriv ut', + b'Arkiver...', + b'Hvis De er enig i bestemmelsene i denne lisensavtalen, klikker De p\x8c "Enig"-knappen for \x8c installere programvaren. Hvis De ikke er enig, klikker De p\x8c "Ikke enig".' + ), +} + +class LPicResource (Resource): + def __init__(self, res_id, res_name, default_lang, lpic, res_attrs=0): + data = [] + data.append(struct.pack(b'>HH', default_lang, len(lpic))) + for lang,rid,two_byte in lpic: + data.append(struct.pack(b'>HHH', lang, rid, int(two_byte))) + super(LPicResource, self).__init__(b'LPic', res_id, res_name, + b''.join(data), res_attrs) + +def get_encoder_name(locale): + if locale not in region_codes: + raise Exception("Cannot determine region code for locale '%s'" % locale) + region_code = region_codes[locale] + + if region_code not in script_codes: + raise Exception("Cannot determine script code for locale '%s'" % locale) + script_code = script_codes[region_code] + + if script_code not in encodings_map: + raise Exception("Cannot determine Python encoder name for locale '%s' - " + "encode the string data manually as a byte array instead" % locale) + return encodings_map[script_code] + +def maybe_encode(s, encoding): + if isinstance(s, bytes): + return s + return s.encode(encoding) + +def add_license(filename, license_info): + """Add a license agreement to the specified disk image file, which should + have been unflattened first.""" + + fork = ResourceFork.from_file(filename) + + default_lang = license_info.get('default-language', 'en_US') + default_lang_id = region_codes.get(default_lang, 0) + + lpic = [] + ndx = 1 + for language,license_data in license_info['licenses'].items(): + if language not in region_codes: + raise Exception("Unknown language '" + language + "'. Valid languages are: " + + ", ".join(sorted(region_codes.keys()))) + encoding_name = get_encoder_name(language) + lang_id = region_codes[language] + + is_two_byte = lang_id in (14, 51, 52, 53) # Japanese, Korean, SimpChinese, TradChinese + + if os.path.isfile(license_data): + with open(license_data) as f: + license_data = f.read() + + if license_data.startswith('{\\rtf1'): + fork.add(Resource(b'RTF ', 5000 + ndx, language + ' SLA', + str(license_data))) + else: + fork.add(TextResource(5000 + ndx, language + ' SLA', + maybe_encode(license_data, encoding_name))) + fork.add(StyleResource(5000 + ndx, language + ' SLA', + [Style(0, 12, 9, Style.Helvetica, + 0, 0, (0, 0, 0))])) + + buttons = license_info.get('buttons', {}).get(language, None) + if buttons is None: + buttons = default_buttons.get(lang_id, None) + if buttons is None: + buttons = default_buttons[0] + + buttons = [maybe_encode(b, encoding_name) for b in buttons] + + fork.add(StringListResource(5000 + ndx, language + ' Buttons', + buttons)) + + lpic.append((lang_id, ndx, is_two_byte)) + + ndx += 1 + + fork.add(LPicResource(5000, None, default_lang_id, lpic)) + + fork.write_to_file(filename) diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/qt_attribution.json b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/qt_attribution.json new file mode 100644 index 00000000..d9f97ae2 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/qt_attribution.json @@ -0,0 +1,13 @@ +{ + "Id": "dmgbuild", + "Name": "dmgbuild", + "QDocModule": "qbs", + "QtUsage": "Used in the qbs dmg module for building Apple disk images.", + "Description": "macOS command line utility to build disk images", + "Homepage": "https://github.com/al45tair/dmgbuild", + "Version": "1.3.1", + "License": "MIT License", + "LicenseId": "MIT", + "LicenseFile": "LICENSE", + "Copyright": "Copyright (c) 2014 Alastair Houghton" +} diff --git a/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/resources.py b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/resources.py new file mode 100644 index 00000000..d2f58e64 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/dmgbuild/resources.py @@ -0,0 +1,355 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import struct + +class Resource (object): + def __init__(self, res_type, res_id, res_name, data=None, res_attrs=0): + self.res_type = str(res_type) + self.res_id = res_id + if isinstance(res_name, basestring): + res_name = str(res_name) + self.res_name = res_name + self.res_attrs = res_attrs + if data is None: + self.data = None + self.data = str(data) + + self.data_offset = None + self.name_offset = None + + def __repr__(self): + return 'Resource(%r, %r, %r, data=%r, res_attrs=%r)' % (self.res_type, + self.res_id, + self.res_name, + self.data, + self.res_attrs) + +class TMPLResource (Resource): + def __init__(self, res_id, res_name, tmpl, res_attrs=0): + data = [] + for name,typecode in tmpl: + data.append(struct.pack(b'B', len(name))) + data.append(str(name)) + data.append(str(typecode)) + super(TMPLResource, self).__init__(b'TMPL', res_id, res_name, + b''.join(data), res_attrs) + +class StringListResource (Resource): + def __init__(self, res_id, res_name, strings, res_attrs=0): + data = [] + data.append(struct.pack(b'>H', len(strings))) + for s in strings: + data.append(struct.pack(b'B', len(s))) + data.append(str(s)) + super(StringListResource, self).__init__(b'STR#', res_id, res_name, + b''.join(data), res_attrs) + +class TextResource (Resource): + def __init__(self, res_id, res_name, string, res_attrs=0): + super(TextResource, self).__init__(b'TEXT', res_id, res_name, + str(string), res_attrs) + +class Style (object): + # Fonts + NewYork = 2 + Geneva = 3 + Monaco = 4 + Venice = 5 + London = 6 + Athens = 7 + SanFrancisco = 8 + Toronto = 9 + Cairo = 11 + LosAngeles = 12 + Times = 20 + Helvetica = 21 + Courier = 22 + Symbol = 23 + Mobile = 24 + + # Styles + Bold = 0x0100 + Italic = 0x0200 + Underline = 0x0400 + Outline = 0x0800 + Shadow = 0x1000 + Condense = 0x2000 + Expand = 0x4000 + + def __init__(self, start_character, height, ascent, font_id, face, + size, color): + self.start_character = start_character + self.height = height + self.ascent = ascent + self.font_id = font_id + self.face = face + self.size = size + self.color = color + + def __repr__(self): + styles = [] + if self.face & Style.Bold: + styles.append('Style.Bold') + if self.face & Style.Italic: + styles.append('Style.Italic') + if self.face & Style.Underline: + styles.append('Style.Underline') + if self.face & Style.Outline: + styles.append('Style.Outline') + if self.face & Style.Shadow: + styles.append('Style.Shadow') + if self.face & Style.Condense: + styles.append('Style.Condense') + if self.face & Style.Expand: + styles.append('Style.Expand') + if self.face & ~0x4f00: + styles.append('%#06x' % (self.face & ~0x4f00)) + if styles: + styles = '|'.join(styles) + else: + styles = '0' + + font_revmap = { + 2: 'Style.NewYork', + 3: 'Style.Geneva', + 4: 'Style.Monaco', + 5: 'Style.Venice', + 6: 'Style.London', + 7: 'Style.Athens', + 8: 'Style.SanFrancisco', + 9: 'Style.Toronto', + 11: 'Style.Cairo', + 12: 'Style.LosAngeles', + 20: 'Style.Times', + 21: 'Style.Helvetica', + 22: 'Style.Courier', + 23: 'Style.Symbol', + 24: 'Style.Mobile' + } + + font = font_revmap.get(self.font_id, '%s' % self.font_id) + + return 'Style(%r, %r, %r, %s, %s, %r, %r)' % ( + self.start_character, + self.height, + self.ascent, + font, + styles, + self.size, + self.color) + +class StyleResource (Resource): + def __init__(self, res_id, res_name, styles, res_attrs=0): + data = [] + data.append(struct.pack(b'>H', len(styles))) + for style in styles: + data.append(struct.pack(b'>LHHHHHHHH', + style.start_character, + style.height, + style.ascent, + style.font_id, + style.face, + style.size, + style.color[0], + style.color[1], + style.color[2])) + super(StyleResource, self).__init__(b'styl', res_id, res_name, + b''.join(data), res_attrs) + +class ResourceFork (object): + def __init__(self, resources=None): + self.types = {} + self.attrs = 0 + if resources is not None: + for res in resources: + self.add(res) + + @classmethod + def from_data(clss, data): + if len(data) < 16: + raise ValueError('Bad resource data - data too short') + + # Read the header + data_start, map_start, data_len, map_len = struct.unpack(b'>LLLL', + data[0:16]) + + if data_start + data_len > len(data): + raise ValueError('Bad resource data - data out of range') + if map_start + map_len > len(data): + raise ValueError('Bad resource data - map out of range') + if map_len < 30: + raise ValueError('Bad resource data - map too short') + + # Read the map header + fork_attrs, type_offset, name_offset, max_type_ndx \ + = struct.unpack(b'>HHHH', data[map_start + 22:map_start + 30]) + num_types = max_type_ndx + 1 + + if type_offset + 8 * num_types > map_len: + raise ValueError('Bad resource data - type data outside map') + + if name_offset > map_len: + raise ValueError('Bad resource data - names outside map') + + type_offset += map_start + name_offset += map_start + + result = ResourceFork() + + # Now read the type list + for ntype in range(0, num_types): + type_pos = 2 + type_offset + 8 * ntype + res_type, max_item_ndx, ref_offset \ + = struct.unpack(b'>4sHH', data[type_pos:type_pos+8]) + num_items = max_item_ndx + 1 + + result.types[res_type] = [] + + ref_list_offset = type_offset + ref_offset + if ref_list_offset + 12 * num_items > map_start + map_len: + raise ValueError('Bad resource data - ref list outside map') + + for nitem in range(0, num_items): + ref_elt = ref_list_offset + 12 * nitem + res_id, res_name_offset, data_offset \ + = struct.unpack(b'>hHL', data[ref_elt:ref_elt+8]) + + res_attrs = data_offset >> 24 + data_offset &= 0xffffff + + if data_offset >= data_len: + raise ValueError('Bad resource data - item data out of range') + + data_offset += data_start + res_len = struct.unpack(b'>L', data[data_offset:data_offset+4])[0] + if data_offset + res_len >= data_start + data_len: + raise ValueError('Bad resource data - item data too large') + + res_data = data[data_offset + 4:data_offset + res_len + 4] + + if res_name_offset == 0xffff: + res_name = None + else: + res_name_offset += name_offset + if res_name_offset >= map_start + map_len: + raise ValueError('Bad resource data - name out of range') + res_name_len = struct.unpack(b'B', data[res_name_offset])[0] + res_name = data[res_name_offset + 1:res_name_offset + res_name_len + 1] + + result.types[res_type].append(Resource(res_type, res_id, + res_name, + res_data, res_attrs)) + + return result + + @classmethod + def from_file(clss, filename): + with open(filename + '/..namedfork/rsrc', 'rb') as f: + data = f.read() + return clss.from_data(data) + + def to_data(self): + data = [] + data_len = 0 + names = [] + names_len = 0 + types_len = len(self.types) * 8 + types_data = [] + reflist_data = [] + reflist_len = 0 + + for res_type, items in self.types.items(): + types_data.append(struct.pack(b'>4sHH', + res_type, + len(items) - 1, + 2 + types_len + reflist_len)) + for item in items: + data_offset = data_len + + if item.res_name is None: + name_offset = 65535 + else: + name_offset = names_len + n = str(item.res_name) + names.append(struct.pack(b'B', len(n)) + n) + names_len += 1 + len(n) + + if item.data is None: + data_len += 4 + else: + data_len += 4 + (len(item.data) + 3) & ~3 + + reflist_len += 12 + reflist_data.append(struct.pack(b'>hHLL', + item.res_id, + name_offset, + (item.res_attrs << 24) \ + | data_offset, + 0)) + + # Header + data.append(struct.pack(b'>LLLL240s', 256, 256 + data_len, data_len, + 30 + types_len + reflist_len + names_len, + b'')) + + # Resource data + for res_type, items in self.types.items(): + for item in items: + if item.data is None: + dlen = 0 + else: + dlen = len(item.data) + plen = (dlen + 3) & ~3 + data.append(struct.pack(b'>L', dlen)) + if item.data is not None: + data.append(item.data) + if plen != dlen: + data.append(b'\0' * (plen - dlen)) + + # Resource map header + data.append(struct.pack(b'>16sLHHHHH', + b'', 0, 0, + self.attrs, 28, 30 + types_len + reflist_len, + len(self.types) - 1)) + + # Type list + data.append(b''.join(types_data)) + + # Reference lists + data.append(b''.join(reflist_data)) + + # Name list + data.append(b''.join(names)) + + return b''.join(data) + + def write_to_file(self, filename): + with open(filename + '/..namedfork/rsrc', 'wb') as f: + f.write(self.to_data()) + + def __len__(self): + return len(self.types) + + def __getitem__(self, key): + return self.types[key] + + def __iter__(self): + for res_type, items in self.types.items(): + for item in items: + yield item + + def __repr__(self): + output = [] + for item in self: + output.append(repr(item)) + return 'ResourceFork([%s])' % ', '.join(output) + + def add(self, res): + if res.res_type in self.types: + self.types[res.res_type].append(res) + else: + self.types[res.res_type] = [res] + + def remove(self, res): + self.types[res.res_type].remove(res) diff --git a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/LICENSE b/src/3rdparty/python/lib/python2.7/site-packages/ds_store/LICENSE new file mode 100644 index 00000000..e91f4eb3 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/ds_store/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Alastair Houghton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/__init__.py b/src/3rdparty/python/lib/python2.7/site-packages/ds_store/__init__.py new file mode 100644 index 00000000..a6b81210 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/ds_store/__init__.py @@ -0,0 +1,3 @@ +from .store import DSStore, DSStoreEntry + +__all__ = ['DSStore', 'DSStoreEntry'] diff --git a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/buddy.py b/src/3rdparty/python/lib/python2.7/site-packages/ds_store/buddy.py new file mode 100644 index 00000000..320768cd --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/ds_store/buddy.py @@ -0,0 +1,478 @@ +# -*- coding: utf-8 -*- +import os +import bisect +import struct +import binascii + +try: + {}.iterkeys + iterkeys = lambda x: x.iterkeys() +except AttributeError: + iterkeys = lambda x: x.keys() +try: + unicode +except NameError: + unicode = str + +class BuddyError(Exception): + pass + +class Block(object): + def __init__(self, allocator, offset, size): + self._allocator = allocator + self._offset = offset + self._size = size + self._value = bytearray(allocator.read(offset, size)) + self._pos = 0 + self._dirty = False + + def __len__(self): + return self._size + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + def close(self): + if self._dirty: + self.flush() + + def flush(self): + if self._dirty: + self._dirty = False + self._allocator.write(self._offset, self._value) + + def invalidate(self): + self._dirty = False + + def zero_fill(self): + len = self._size - self._pos + zeroes = b'\0' * len + self._value[self._pos:self._size] = zeroes + self._dirty = True + + def tell(self): + return self._pos + + def seek(self, pos, whence=os.SEEK_SET): + if whence == os.SEEK_CUR: + pos += self._pos + elif whence == os.SEEK_END: + pos = self._size - pos + + if pos < 0 or pos > self._size: + raise ValueError('Seek out of range in Block instance') + + self._pos = pos + + def read(self, size_or_format): + if isinstance(size_or_format, (str, unicode, bytes)): + size = struct.calcsize(size_or_format) + fmt = size_or_format + else: + size = size_or_format + fmt = None + + if self._size - self._pos < size: + raise BuddyError('Unable to read %lu bytes in block' % size) + + data = self._value[self._pos:self._pos + size] + self._pos += size + + if fmt is not None: + if isinstance(data, bytearray): + return struct.unpack_from(fmt, bytes(data)) + else: + return struct.unpack(fmt, data) + else: + return data + + def write(self, data_or_format, *args): + if len(args): + data = struct.pack(data_or_format, *args) + else: + data = data_or_format + + if self._pos + len(data) > self._size: + raise ValueError('Attempt to write past end of Block') + + self._value[self._pos:self._pos + len(data)] = data + self._pos += len(data) + + self._dirty = True + + def insert(self, data_or_format, *args): + if len(args): + data = struct.pack(data_or_format, *args) + else: + data = data_or_format + + del self._value[-len(data):] + self._value[self._pos:self._pos] = data + self._pos += len(data) + + self._dirty = True + + def delete(self, size): + if self._pos + size > self._size: + raise ValueError('Attempt to delete past end of Block') + del self._value[self._pos:self._pos + size] + self._value += b'\0' * size + self._dirty = True + + def __str__(self): + return binascii.b2a_hex(self._value) + +class Allocator(object): + def __init__(self, the_file): + self._file = the_file + self._dirty = False + + self._file.seek(0) + + # Read the header + magic1, magic2, offset, size, offset2, self._unknown1 \ + = self.read(-4, '>I4sIII16s') + + if magic2 != b'Bud1' or magic1 != 1: + raise BuddyError('Not a buddy file') + + if offset != offset2: + raise BuddyError('Root addresses differ') + + self._root = Block(self, offset, size) + + # Read the block offsets + count, self._unknown2 = self._root.read('>II') + self._offsets = [] + c = (count + 255) & ~255 + while c: + self._offsets += self._root.read('>256I') + c -= 256 + self._offsets = self._offsets[:count] + + # Read the TOC + self._toc = {} + count = self._root.read('>I')[0] + for n in range(count): + nlen = self._root.read('B')[0] + name = bytes(self._root.read(nlen)) + value = self._root.read('>I')[0] + self._toc[name] = value + + # Read the free lists + self._free = [] + for n in range(32): + count = self._root.read('>I') + self._free.append(list(self._root.read('>%uI' % count))) + + @classmethod + def open(cls, file_or_name, mode='r+'): + if isinstance(file_or_name, (str, unicode)): + if not 'b' in mode: + mode = mode[:1] + 'b' + mode[1:] + f = open(file_or_name, mode) + else: + f = file_or_name + + if 'w' in mode: + # Create an empty file in this case + f.truncate() + + # An empty root block needs 1264 bytes: + # + # 0 4 offset count + # 4 4 unknown + # 8 4 root block offset (2048) + # 12 255 * 4 padding (offsets are in multiples of 256) + # 1032 4 toc count (0) + # 1036 228 free list + # total 1264 + + # The free list will contain the following: + # + # 0 5 * 4 no blocks of width less than 5 + # 20 6 * 8 1 block each of widths 5 to 10 + # 68 4 no blocks of width 11 (allocated for the root) + # 72 19 * 8 1 block each of widths 12 to 30 + # 224 4 no blocks of width 31 + # total 228 + # + # (The reason for this layout is that we allocate 2**5 bytes for + # the header, which splits the initial 2GB region into every size + # below 2**31, including *two* blocks of size 2**5, one of which + # we take. The root block itself then needs a block of size + # 2**11. Conveniently, each of these initial blocks will be + # located at offset 2**n where n is its width.) + + # Write the header + header = struct.pack(b'>I4sIII16s', + 1, b'Bud1', + 2048, 1264, 2048, + b'\x00\x00\x10\x0c' + b'\x00\x00\x00\x87' + b'\x00\x00\x20\x0b' + b'\x00\x00\x00\x00') + f.write(header) + f.write(b'\0' * 2016) + + # Write the root block + free_list = [struct.pack(b'>5I', 0, 0, 0, 0, 0)] + for n in range(5, 11): + free_list.append(struct.pack(b'>II', 1, 2**n)) + free_list.append(struct.pack(b'>I', 0)) + for n in range(12, 31): + free_list.append(struct.pack(b'>II', 1, 2**n)) + free_list.append(struct.pack(b'>I', 0)) + + root = b''.join([struct.pack(b'>III', 1, 0, 2048 | 5), + struct.pack(b'>I', 0) * 255, + struct.pack(b'>I', 0)] + free_list) + f.write(root) + + return Allocator(f) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + def close(self): + self.flush() + self._file.close() + + def flush(self): + if self._dirty: + size = self._root_block_size() + self.allocate(size, 0) + with self.get_block(0) as rblk: + self._write_root_block_into(rblk) + + addr = self._offsets[0] + offset = addr & ~0x1f + size = 1 << (addr & 0x1f) + + self._file.seek(0, os.SEEK_SET) + self._file.write(struct.pack(b'>I4sIII16s', + 1, b'Bud1', + offset, size, offset, + self._unknown1)) + + self._dirty = False + + self._file.flush() + + def read(self, offset, size_or_format): + """Read data at `offset', or raise an exception. `size_or_format' + may either be a byte count, in which case we return raw data, + or a format string for `struct.unpack', in which case we + work out the size and unpack the data before returning it.""" + # N.B. There is a fixed offset of four bytes(!) + self._file.seek(offset + 4, os.SEEK_SET) + + if isinstance(size_or_format, (str, unicode)): + size = struct.calcsize(size_or_format) + fmt = size_or_format + else: + size = size_or_format + fmt = None + + ret = self._file.read(size) + if len(ret) < size: + ret += b'\0' * (size - len(ret)) + + if fmt is not None: + if isinstance(ret, bytearray): + ret = struct.unpack_from(fmt, bytes(ret)) + else: + ret = struct.unpack(fmt, ret) + + return ret + + def write(self, offset, data_or_format, *args): + """Write data at `offset', or raise an exception. `data_or_format' + may either be the data to write, or a format string for `struct.pack', + in which case we pack the additional arguments and write the + resulting data.""" + # N.B. There is a fixed offset of four bytes(!) + self._file.seek(offset + 4, os.SEEK_SET) + + if len(args): + data = struct.pack(data_or_format, *args) + else: + data = data_or_format + + self._file.write(data) + + def get_block(self, block): + try: + addr = self._offsets[block] + except IndexError: + return None + + offset = addr & ~0x1f + size = 1 << (addr & 0x1f) + + return Block(self, offset, size) + + def _root_block_size(self): + """Return the number of bytes required by the root block.""" + # Offsets + size = 8 + size += 4 * ((len(self._offsets) + 255) & ~255) + + # TOC + size += 4 + size += sum([5 + len(s) for s in self._toc]) + + # Free list + size += sum([4 + 4 * len(fl) for fl in self._free]) + + return size + + def _write_root_block_into(self, block): + # Offsets + block.write('>II', len(self._offsets), self._unknown2) + block.write('>%uI' % len(self._offsets), *self._offsets) + extra = len(self._offsets) & 255 + if extra: + block.write(b'\0\0\0\0' * (256 - extra)) + + # TOC + keys = list(self._toc.keys()) + keys.sort() + + block.write('>I', len(keys)) + for k in keys: + block.write('B', len(k)) + block.write(k) + block.write('>I', self._toc[k]) + + # Free list + for w, f in enumerate(self._free): + block.write('>I', len(f)) + if len(f): + block.write('>%uI' % len(f), *f) + + def _buddy(self, offset, width): + f = self._free[width] + b = offset ^ (1 << width) + + try: + ndx = f.index(b) + except ValueError: + ndx = None + + return (f, b, ndx) + + def _release(self, offset, width): + # Coalesce + while True: + f,b,ndx = self._buddy(offset, width) + + if ndx is None: + break + + offset &= b + width += 1 + del f[ndx] + + # Add to the list + bisect.insort(f, offset) + + # Mark as dirty + self._dirty = True + + def _alloc(self, width): + w = width + while not self._free[w]: + w += 1 + while w > width: + offset = self._free[w].pop(0) + w -= 1 + self._free[w] = [offset, offset ^ (1 << w)] + self._dirty = True + return self._free[width].pop(0) + + def allocate(self, bytes, block=None): + """Allocate or reallocate a block such that it has space for at least + `bytes' bytes.""" + if block is None: + # Find the first unused block + try: + block = self._offsets.index(0) + except ValueError: + block = len(self._offsets) + self._offsets.append(0) + + # Compute block width + width = max(bytes.bit_length(), 5) + + addr = self._offsets[block] + offset = addr & ~0x1f + + if addr: + blkwidth = addr & 0x1f + if blkwidth == width: + return block + self._release(offset, width) + self._offsets[block] = 0 + + offset = self._alloc(width) + self._offsets[block] = offset | width + return block + + def release(self, block): + addr = self._offsets[block] + + if addr: + width = addr & 0x1f + offset = addr & ~0x1f + self._release(offset, width) + + if block == len(self._offsets): + del self._offsets[block] + else: + self._offsets[block] = 0 + + def __len__(self): + return len(self._toc) + + def __getitem__(self, key): + if not isinstance(key, (str, unicode)): + raise TypeError('Keys must be of string type') + if not isinstance(key, bytes): + key = key.encode('latin_1') + return self._toc[key] + + def __setitem__(self, key, value): + if not isinstance(key, (str, unicode)): + raise TypeError('Keys must be of string type') + if not isinstance(key, bytes): + key = key.encode('latin_1') + self._toc[key] = value + self._dirty = True + + def __delitem__(self, key): + if not isinstance(key, (str, unicode)): + raise TypeError('Keys must be of string type') + if not isinstance(key, bytes): + key = key.encode('latin_1') + del self._toc[key] + self._dirty = True + + def iterkeys(self): + return iterkeys(self._toc) + + def keys(self): + return iterkeys(self._toc) + + def __iter__(self): + return iterkeys(self._toc) + + def __contains__(self, key): + return key in self._toc + diff --git a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/qt_attribution.json b/src/3rdparty/python/lib/python2.7/site-packages/ds_store/qt_attribution.json new file mode 100644 index 00000000..a4854d1e --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/ds_store/qt_attribution.json @@ -0,0 +1,13 @@ +{ + "Id": "ds_store", + "Name": "ds_store", + "QDocModule": "qbs", + "QtUsage": "Used in the qbs dmg module for building Apple disk images.", + "Description": "Manipulate Finder .DS_Store files from Python", + "Homepage": "https://github.com/al45tair/ds_store", + "Version": "1.1.2", + "License": "MIT License", + "LicenseId": "MIT", + "LicenseFile": "LICENSE", + "Copyright": "Copyright (c) 2014 Alastair Houghton" +} diff --git a/src/3rdparty/python/lib/python2.7/site-packages/ds_store/store.py b/src/3rdparty/python/lib/python2.7/site-packages/ds_store/store.py new file mode 100644 index 00000000..b6f805b2 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/ds_store/store.py @@ -0,0 +1,1251 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import division + +import binascii +import struct +import biplist +import mac_alias + +try: + next +except NameError: + next = lambda x: x.next() +try: + unicode +except NameError: + unicode = str + +from . import buddy + +class ILocCodec(object): + @staticmethod + def encode(point): + return struct.pack(b'>IIII', point[0], point[1], + 0xffffffff, 0xffff0000) + + @staticmethod + def decode(bytesData): + if isinstance(bytesData, bytearray): + x, y = struct.unpack_from(b'>II', bytes(bytesData[:8])) + else: + x, y = struct.unpack(b'>II', bytesData[:8]) + return (x, y) + +class PlistCodec(object): + @staticmethod + def encode(plist): + return biplist.writePlistToString(plist) + + @staticmethod + def decode(bytes): + return biplist.readPlistFromString(bytes) + +class BookmarkCodec(object): + @staticmethod + def encode(bmk): + return bmk.to_bytes() + + @staticmethod + def decode(bytes): + return mac_alias.Bookmark.from_bytes(bytes) + +# This list tells the code how to decode particular kinds of entry in the +# .DS_Store file. This is really a convenience, and we currently only +# support a tiny subset of the possible entry types. +codecs = { + b'Iloc': ILocCodec, + b'bwsp': PlistCodec, + b'lsvp': PlistCodec, + b'lsvP': PlistCodec, + b'icvp': PlistCodec, + b'pBBk': BookmarkCodec + } + +class DSStoreEntry(object): + """Holds the data from an entry in a ``.DS_Store`` file. Note that this is + not meant to represent the entry itself---i.e. if you change the type + or value, your changes will *not* be reflected in the underlying file. + + If you want to make a change, you should either use the :class:`DSStore` + object's :meth:`DSStore.insert` method (which will replace a key if it + already exists), or the mapping access mode for :class:`DSStore` (often + simpler anyway). + """ + def __init__(self, filename, code, typecode, value=None): + if str != bytes and type(filename) == bytes: + filename = filename.decode('utf-8') + + if not isinstance(code, bytes): + code = code.encode('latin_1') + + self.filename = filename + self.code = code + self.type = typecode + self.value = value + + @classmethod + def read(cls, block): + """Read a ``.DS_Store`` entry from the containing Block""" + # First read the filename + nlen = block.read(b'>I')[0] + filename = block.read(2 * nlen).decode('utf-16be') + + # Next, read the code and type + code, typecode = block.read(b'>4s4s') + + # Finally, read the data + if typecode == b'bool': + value = block.read(b'>?')[0] + elif typecode == b'long' or typecode == b'shor': + value = block.read(b'>I')[0] + elif typecode == b'blob': + vlen = block.read(b'>I')[0] + value = block.read(vlen) + + codec = codecs.get(code, None) + if codec: + value = codec.decode(value) + typecode = codec + elif typecode == b'ustr': + vlen = block.read(b'>I')[0] + value = block.read(2 * vlen).decode('utf-16be') + elif typecode == b'type': + value = block.read(b'>4s')[0] + elif typecode == b'comp' or typecode == b'dutc': + value = block.read(b'>Q')[0] + else: + raise ValueError('Unknown type code "%s"' % typecode) + + return DSStoreEntry(filename, code, typecode, value) + + def __lt__(self, other): + if not isinstance(other, DSStoreEntry): + raise TypeError('Can only compare against other DSStoreEntry objects') + sfl = self.filename.lower() + ofl = other.filename.lower() + return (sfl < ofl + or (self.filename == other.filename + and self.code < other.code)) + + def __le__(self, other): + if not isinstance(other, DSStoreEntry): + raise TypeError('Can only compare against other DSStoreEntry objects') + sfl = self.filename.lower() + ofl = other.filename.lower() + return (sfl < ofl + or (sfl == ofl + and self.code <= other.code)) + + def __eq__(self, other): + if not isinstance(other, DSStoreEntry): + raise TypeError('Can only compare against other DSStoreEntry objects') + sfl = self.filename.lower() + ofl = other.filename.lower() + return (sfl == ofl + and self.code == other.code) + + def __ne__(self, other): + if not isinstance(other, DSStoreEntry): + raise TypeError('Can only compare against other DSStoreEntry objects') + sfl = self.filename.lower() + ofl = other.filename.lower() + return (sfl != ofl + or self.code != other.code) + + def __gt__(self, other): + if not isinstance(other, DSStoreEntry): + raise TypeError('Can only compare against other DSStoreEntry objects') + sfl = self.filename.lower() + ofl = other.filename.lower() + + selfCode = self.code + if str != bytes and type(selfCode) is bytes: + selfCode = selfCode.decode('utf-8') + otherCode = other.code + if str != bytes and type(otherCode) is bytes: + otherCode = otherCode.decode('utf-8') + + return (sfl > ofl or (sfl == ofl and selfCode > otherCode)) + + def __ge__(self, other): + if not isinstance(other, DSStoreEntry): + raise TypeError('Can only compare against other DSStoreEntry objects') + sfl = self.filename.lower() + ofl = other.filename.lower() + return (sfl > ofl + or (sfl == ofl + and self.code >= other.code)) + + def __cmp__(self, other): + if not isinstance(other, DSStoreEntry): + raise TypeError('Can only compare against other DSStoreEntry objects') + r = cmp(self.filename.lower(), other.filename.lower()) + if r: + return r + return cmp(self.code, other.code) + + def byte_length(self): + """Compute the length of this entry, in bytes""" + utf16 = self.filename.encode('utf-16be') + l = 4 + len(utf16) + 8 + + if isinstance(self.type, unicode): + entry_type = self.type.encode('latin_1') + value = self.value + elif isinstance(self.type, (bytes, str)): + entry_type = self.type + value = self.value + else: + entry_type = b'blob' + value = self.type.encode(self.value) + + if entry_type == b'bool': + l += 1 + elif entry_type == b'long' or entry_type == b'shor': + l += 4 + elif entry_type == b'blob': + l += 4 + len(value) + elif entry_type == b'ustr': + utf16 = value.encode('utf-16be') + l += 4 + len(utf16) + elif entry_type == b'type': + l += 4 + elif entry_type == b'comp' or entry_type == b'dutc': + l += 8 + else: + raise ValueError('Unknown type code "%s"' % entry_type) + + return l + + def write(self, block, insert=False): + """Write this entry to the specified Block""" + if insert: + w = block.insert + else: + w = block.write + + if isinstance(self.type, unicode): + entry_type = self.type.encode('latin_1') + value = self.value + elif isinstance(self.type, (bytes, str)): + entry_type = self.type + value = self.value + else: + entry_type = b'blob' + value = self.type.encode(self.value) + + utf16 = self.filename.encode('utf-16be') + w(b'>I', len(utf16) // 2) + w(utf16) + w(b'>4s4s', self.code, entry_type) + + if entry_type == b'bool': + w(b'>?', value) + elif entry_type == b'long' or entry_type == b'shor': + w(b'>I', value) + elif entry_type == b'blob': + w(b'>I', len(value)) + w(value) + elif entry_type == b'ustr': + utf16 = value.encode('utf-16be') + w(b'>I', len(utf16) // 2) + w(utf16) + elif entry_type == b'type': + if isinstance(value, unicode): + value = value.encode('latin_1') + w(b'>4s', value) + elif entry_type == b'comp' or entry_type == b'dutc': + w(b'>Q', value) + else: + raise ValueError('Unknown type code "%s"' % entry_type) + + def __repr__(self): + return '<%s %s>' % (self.filename, self.code) + +class DSStore(object): + """Python interface to a ``.DS_Store`` file. Works by manipulating the file + on the disk---so this code will work with ``.DS_Store`` files for *very* + large directories. + + A :class:`DSStore` object can be used as if it was a mapping, e.g.:: + + d['foobar.dat']['Iloc'] + + will fetch the "Iloc" record for "foobar.dat", or raise :class:`KeyError` if + there is no such record. If used in this manner, the :class:`DSStore` object + will return (type, value) tuples, unless the type is "blob" and the module + knows how to decode it. + + Currently, we know how to decode "Iloc", "bwsp", "lsvp", "lsvP" and "icvp" + blobs. "Iloc" decodes to an (x, y) tuple, while the others are all decoded + using ``biplist``. + + Assignment also works, e.g.:: + + d['foobar.dat']['note'] = ('ustr', u'Hello World!') + + as does deletion with ``del``:: + + del d['foobar.dat']['note'] + + This is usually going to be the most convenient interface, though + occasionally (for instance when creating a new ``.DS_Store`` file) you + may wish to drop down to using :class:`DSStoreEntry` objects directly.""" + def __init__(self, store): + self._store = store + self._superblk = self._store['DSDB'] + with self._get_block(self._superblk) as s: + self._rootnode, self._levels, self._records, \ + self._nodes, self._page_size = s.read(b'>IIIII') + self._min_usage = 2 * self._page_size // 3 + self._dirty = False + + @classmethod + def open(cls, file_or_name, mode='r+', initial_entries=None): + """Open a ``.DS_Store`` file; pass either a Python file object, or a + filename in the ``file_or_name`` argument and a file access mode in + the ``mode`` argument. If you are creating a new file using the "w" + or "w+" modes, you may also specify a list of entries with which + to initialise the file.""" + store = buddy.Allocator.open(file_or_name, mode) + + if mode == 'w' or mode == 'w+': + superblk = store.allocate(20) + store['DSDB'] = superblk + page_size = 4096 + + if not initial_entries: + root = store.allocate(page_size) + + with store.get_block(root) as rootblk: + rootblk.zero_fill() + + with store.get_block(superblk) as s: + s.write(b'>IIIII', root, 0, 0, 1, page_size) + else: + # Make sure they're in sorted order + initial_entries = list(initial_entries) + initial_entries.sort() + + # Construct the tree + current_level = initial_entries + next_level = [] + levels = [] + ptr_size = 0 + node_count = 0 + while True: + total = 8 + nodes = [] + node = [] + for e in current_level: + new_total = total + ptr_size + e.byte_length() + if new_total > page_size: + nodes.append(node) + next_level.append(e) + total = 8 + node = [] + else: + total = new_total + node.append(e) + if node: + nodes.append(node) + + node_count += len(nodes) + levels.append(nodes) + + if len(nodes) == 1: + break + + current_level = next_level + next_level = [] + ptr_size = 4 + + # Allocate nodes + ptrs = [store.allocate(page_size) for n in range(node_count)] + + # Generate nodes + pointers = [] + prev_pointers = None + for level in levels: + ppndx = 0 + lptrs = ptrs[-len(level):] + del ptrs[-len(level):] + for node in level: + ndx = lptrs.pop(0) + if prev_pointers is None: + with store.get_block(ndx) as block: + block.write(b'>II', 0, len(node)) + for e in node: + e.write(block) + else: + next_node = prev_pointers[ppndx + len(node)] + node_ptrs = prev_pointers[ppndx:ppndx+len(node)] + + with store.get_block(ndx) as block: + block.write(b'>II', next_node, len(node)) + for ptr, e in zip(node_ptrs, node): + block.write(b'>I', ptr) + e.write(block) + + pointers.append(ndx) + prev_pointers = pointers + pointers = [] + + root = prev_pointers[0] + + with store.get_block(superblk) as s: + s.write(b'>IIIII', root, len(levels), len(initial_entries), + node_count, page_size) + + return DSStore(store) + + def _get_block(self, number): + return self._store.get_block(number) + + def flush(self): + """Flush any dirty data back to the file.""" + if self._dirty: + self._dirty = False + + with self._get_block(self._superblk) as s: + s.write(b'>IIIII', self._rootnode, self._levels, self._records, + self._nodes, self._page_size) + self._store.flush() + + def close(self): + """Flush dirty data and close the underlying file.""" + self.flush() + self._store.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + + # Internal B-Tree nodes look like this: + # + # [ next | count | (ptr0 | rec0) | (ptr1 | rec1) ... (ptrN | recN) ] + + # Leaf nodes look like this: + # + # [ 0 | count | rec0 | rec1 ... recN ] + + # Iterate over the tree, starting at `node' + def _traverse(self, node): + if node is None: + node = self._rootnode + with self._get_block(node) as block: + next_node, count = block.read(b'>II') + if next_node: + for n in range(count): + ptr = block.read(b'>I')[0] + for e in self._traverse(ptr): + yield e + e = DSStoreEntry.read(block) + yield e + for e in self._traverse(next_node): + yield e + else: + for n in range(count): + e = DSStoreEntry.read(block) + yield e + + # Display the data in `node' + def _dump_node(self, node): + with self._get_block(node) as block: + next_node, count = block.read(b'>II') + print('next: %u\ncount: %u\n' % (next_node, count)) + for n in range(count): + if next_node: + ptr = block.read(b'>I')[0] + print('%8u ' % ptr, end=' ') + else: + print(' ', end=' ') + e = DSStoreEntry.read(block) + print(e, ' (%u)' % e.byte_length()) + print('used: %u' % block.tell()) + + # Display the data in the super block + def _dump_super(self): + print('root: %u\nlevels: %u\nrecords: %u\nnodes: %u\npage-size: %u' \ + % (self._rootnode, self._levels, self._records, + self._nodes, self._page_size)) + + # Splits entries across two blocks, returning one pivot + # + # Tries to balance the block usage across the two as best it can + def _split2(self, blocks, entries, pointers, before, internal): + left_block = blocks[0] + right_block = blocks[1] + + count = len(entries) + + # Find the feasible splits + best_split = None + best_diff = None + total = before[count] + + if 8 + total <= self._page_size: + # We can use a *single* node for this + best_split = count + else: + # Split into two nodes + for n in range(1, count - 1): + left_size = 8 + before[n] + right_size = 8 + total - before[n + 1] + + if left_size > self._page_size: + break + if right_size > self._page_size: + continue + + diff = abs(left_size - right_size) + + if best_split is None or diff < best_diff: + best_split = n + best_diff = diff + + if best_split is None: + return None + + # Write the nodes + left_block.seek(0) + if internal: + next_node = pointers[best_split] + else: + next_node = 0 + left_block.write(b'>II', next_node, best_split) + + for n in range(best_split): + if internal: + left_block.write(b'>I', pointers[n]) + entries[n].write(left_block) + + left_block.zero_fill() + + if best_split == count: + return [] + + right_block.seek(0) + if internal: + next_node = pointers[count] + else: + next_node = 0 + right_block.write(b'>II', next_node, count - best_split - 1) + + for n in range(best_split + 1, count): + if internal: + right_block.write(b'>I', pointers[n]) + entries[n].write(right_block) + + right_block.zero_fill() + + pivot = entries[best_split] + + return [pivot] + + def _split(self, node, entry, right_ptr=0): + self._nodes += 1 + self._dirty = True + new_right = self._store.allocate(self._page_size) + with self._get_block(node) as block, \ + self._get_block(new_right) as right_block: + + # First, measure and extract all the elements + entry_size = entry.byte_length() + entry_pos = None + next_node, count = block.read(b'>II') + if next_node: + entry_size += 4 + pointers = [] + entries = [] + before = [] + total = 0 + for n in range(count): + pos = block.tell() + if next_node: + ptr = block.read(b'>I')[0] + pointers.append(ptr) + e = DSStoreEntry.read(block) + if e > entry: + entry_pos = n + entries.append(entry) + pointers.append(right_ptr) + before.append(total) + total += entry_size + entries.append(e) + before.append(total) + total += block.tell() - pos + before.append(total) + if next_node: + pointers.append(next_node) + + pivot = self._split2([block, right_block], + entries, pointers, before, + bool(next_node))[0] + + self._records += 1 + self._nodes += 1 + self._dirty = True + + return (pivot, new_right) + + # Allocate a new root node containing the element `pivot' and the pointers + # `left' and `right' + def _new_root(self, left, pivot, right): + new_root = self._store.allocate(self._page_size) + with self._get_block(new_root) as block: + block.write(b'>III', right, 1, left) + pivot.write(block) + self._rootnode = new_root + self._levels += 1 + self._nodes += 1 + self._dirty = True + + # Insert an entry into an inner node; `path' is the path from the root + # to `node', not including `node' itself. `right_ptr' is the new node + # pointer (inserted to the RIGHT of `entry') + def _insert_inner(self, path, node, entry, right_ptr): + with self._get_block(node) as block: + next_node, count = block.read(b'>II') + insert_pos = None + insert_ndx = None + n = 0 + while n < count: + pos = block.tell() + ptr = block.read(b'>I')[0] + e = DSStoreEntry.read(block) + if e == entry: + if n == count - 1: + right_ptr = next_node + next_node = ptr + block_seek(pos) + else: + right_ptr = block.read(b'>I')[0] + block.seek(pos + 4) + insert_pos = pos + insert_ndx = n + block.delete(e.byte_length() + 4) + count -= 1 + self._records += 1 + self._dirty = True + continue + elif insert_pos is None and e > entry: + insert_pos = pos + insert_ndx = n + n += 1 + if insert_pos is None: + insert_pos = block.tell() + insert_ndx = count + remaining = self._page_size - block.tell() + + if remaining < entry.byte_length() + 4: + pivot, new_right = self._split(node, entry, right_ptr) + if path: + self._insert_inner(path[:-1], path[-1], pivot, new_right) + else: + self._new_root(node, pivot, new_right) + else: + if insert_ndx == count: + block.seek(insert_pos) + block.write(b'>I', next_node) + entry.write(block) + next_node = right_ptr + else: + block.seek(insert_pos + 4) + entry.write(block, True) + block.insert('>I', right_ptr) + block.seek(0) + count += 1 + block.write(b'>II', next_node, count) + self._records += 1 + self._dirty = True + + # Insert `entry' into the leaf node `node' + def _insert_leaf(self, path, node, entry): + with self._get_block(node) as block: + next_node, count = block.read(b'>II') + insert_pos = None + insert_ndx = None + n = 0 + while n < count: + pos = block.tell() + e = DSStoreEntry.read(block) + if e == entry: + insert_pos = pos + insert_ndx = n + block.seek(pos) + block.delete(e.byte_length()) + count -= 1 + self._records += 1 + self._dirty = True + continue + elif insert_pos is None and e > entry: + insert_pos = pos + insert_ndx = n + n += 1 + if insert_pos is None: + insert_pos = block.tell() + insert_ndx = count + remaining = self._page_size - block.tell() + + if remaining < entry.byte_length(): + pivot, new_right = self._split(node, entry) + if path: + self._insert_inner(path[:-1], path[-1], pivot, new_right) + else: + self._new_root(node, pivot, new_right) + else: + block.seek(insert_pos) + entry.write(block, True) + block.seek(0) + count += 1 + block.write(b'>II', next_node, count) + self._records += 1 + self._dirty = True + + def insert(self, entry): + """Insert ``entry`` (which should be a :class:`DSStoreEntry`) + into the B-Tree.""" + path = [] + node = self._rootnode + while True: + with self._get_block(node) as block: + next_node, count = block.read(b'>II') + if next_node: + for n in range(count): + ptr = block.read(b'>I')[0] + e = DSStoreEntry.read(block) + if entry < e: + next_node = ptr + break + elif entry == e: + # If we find an existing entry the same, replace it + self._insert_inner(path, node, entry, None) + return + path.append(node) + node = next_node + else: + self._insert_leaf(path, node, entry) + return + + # Return usage information for the specified `node' + def _block_usage(self, node): + with self._get_block(node) as block: + next_node, count = block.read(b'>II') + + for n in range(count): + if next_node: + ptr = block.read(b'>I')[0] + e = DSStoreEntry.read(block) + + used = block.tell() + + return (count, used) + + # Splits entries across three blocks, returning two pivots + def _split3(self, blocks, entries, pointers, before, internal): + count = len(entries) + + # Find the feasible splits + best_split = None + best_diff = None + total = before[count] + for n in range(1, count - 3): + left_size = 8 + before[n] + remaining = 16 + total - before[n + 1] + + if left_size > self._page_size: + break + if remaining > 2 * self._page_size: + continue + + for m in range(n + 2, count - 1): + mid_size = 8 + before[m] - before[n + 1] + right_size = 8 + total - before[m + 1] + + if mid_size > self._page_size: + break + if right_size > self._page_size: + continue + + diff = abs(left_size - mid_size) * abs(right_size - mid_size) + + if best_split is None or diff < best_diff: + best_split = (n, m, count) + best_diff = diff + + if best_split is None: + return None + + # Write the nodes + prev_split = -1 + for block, split in zip(blocks, best_split): + block.seek(0) + if internal: + next_node = pointers[split] + else: + next_node = 0 + block.write(b'>II', next_node, split) + + for n in range(prev_split + 1, split): + if internal: + block.write(b'>I', pointers[n]) + entries[n].write(block) + + block.zero_fill() + + prev_split = split + + return (entries[best_split[0]], entries[best_split[1]]) + + # Extract all of the entries from the specified list of `blocks', + # separating them by the specified `pivots'. Also computes the + # amount of space used before each entry. + def _extract(self, blocks, pivots): + pointers = [] + entries = [] + before = [] + total = 0 + ppivots = pivots + [None] + for b,p in zip(blocks, ppivots): + b.seek(0) + next_node, count = b.read(b'>II') + for n in range(count): + pos = b.tell() + if next_node: + ptr = b.read(b'>I')[0] + pointers.append(ptr) + e = DSStoreEntry.read(b) + entries.append(e) + before.append(total) + total += b.tell() - pos + if next_node: + pointers.append(next_node) + if p: + entries.append(p) + before.append(total) + total += p.byte_length() + if next_node: + total += 4 + before.append(total) + + return (entries, pointers, before) + + # Rebalance the specified `node', whose path from the root is `path'. + def _rebalance(self, path, node): + # Can't rebalance the root + if not path: + return + + with self._get_block(node) as block: + next_node, count = block.read(b'>II') + + with self._get_block(path[-1]) as parent: + # Find the left and right siblings and respective pivots + parent_next, parent_count = parent.read(b'>II') + left_pos = None + left_node = None + left_pivot = None + node_pos = None + right_pos = None + right_node = None + right_pivot = None + prev_e = prev_ptr = prev_pos = None + for n in range(parent_count): + pos = parent.tell() + ptr = parent.read(b'>I')[0] + e = DSStoreEntry.read(parent) + + if ptr == node: + node_pos = pos + right_pivot = e + left_pos = prev_pos + left_pivot = prev_e + left_node = prev_ptr + elif prev_ptr == node: + right_node = ptr + right_pos = pos + break + + prev_e = e + prev_ptr = ptr + prev_pos = pos + + if parent_next == node: + node_pos = parent.tell() + left_pos = prev_pos + left_pivot = prev_e + left_node = prev_ptr + elif right_node is None: + right_node = parent_next + right_pos = parent.tell() + + parent_used = parent.tell() + + if left_node and right_node: + with self._get_block(left_node) as left, \ + self._get_block(right_node) as right: + blocks = [left, block, right] + pivots = [left_pivot, right_pivot] + + entries, pointers, before = self._extract(blocks, pivots) + + # If there's a chance that we could use two pages instead + # of three, go for it + pivots = self._split2(blocks, entries, pointers, + before, bool(next_node)) + if pivots is None: + ptrs = [left_node, node, right_node] + pivots = self._split3(blocks, entries, pointers, + before, bool(next_node)) + else: + if pivots: + ptrs = [left_node, node] + else: + ptrs = [left_node] + self._store.release(node) + self._nodes -= 1 + node = left_node + self._store.release(right_node) + self._nodes -= 1 + self._dirty = True + + # Remove the pivots from the parent + with self._get_block(path[-1]) as parent: + if right_node == parent_next: + parent.seek(left_pos) + parent.delete(right_pos - left_pos) + parent_next = left_node + else: + parent.seek(left_pos + 4) + parent.delete(right_pos - left_pos) + parent.seek(0) + parent_count -= 2 + parent.write(b'>II', parent_next, parent_count) + self._records -= 2 + + # Replace with those in pivots + for e,rp in zip(pivots, ptrs[1:]): + self._insert_inner(path[:-1], path[-1], e, rp) + elif left_node: + with self._get_block(left_node) as left: + blocks = [left, block] + pivots = [left_pivot] + + entries, pointers, before = self._extract(blocks, pivots) + + pivots = self._split2(blocks, entries, pointers, + before, bool(next_node)) + + # Remove the pivot from the parent + with self._get_block(path[-1]) as parent: + if node == parent_next: + parent.seek(left_pos) + parent.delete(node_pos - left_pos) + parent_next = left_node + else: + parent.seek(left_pos + 4) + parent.delete(node_pos - left_pos) + parent.seek(0) + parent_count -= 1 + parent.write(b'>II', parent_next, parent_count) + self._records -= 1 + + # Replace the pivot + if pivots: + self._insert_inner(path[:-1], path[-1], pivots[0], node) + elif right_node: + with self._get_block(right_node) as right: + blocks = [block, right] + pivots = [right_pivot] + + entries, pointers, before = self._extract(blocks, pivots) + + pivots = self._split2(blocks, entries, pointers, + before, bool(next_node)) + + # Remove the pivot from the parent + with self._get_block(path[-1]) as parent: + if right_node == parent_next: + parent.seek(pos) + parent.delete(right_pos - node_pos) + parent_next = node + else: + parent.seek(pos + 4) + parent.delete(right_pos - node_pos) + parent.seek(0) + parent_count -= 1 + parent.write(b'>II', parent_next, parent_count) + self._records -= 1 + + # Replace the pivot + if pivots: + self._insert_inner(path[:-1], path[-1], pivots[0], + right_node) + + if not path and not parent_count: + self._store.release(path[-1]) + self._nodes -= 1 + self._dirty = True + self._rootnode = node + else: + count, used = self._block_usage(path[-1]) + + if used < self._page_size // 2: + self._rebalance(path[:-1], path[-1]) + + # Delete from the leaf node `node'. `filename_lc' has already been + # lower-cased. + def _delete_leaf(self, node, filename_lc, code): + found = False + + with self._get_block(node) as block: + next_node, count = block.read(b'>II') + + for n in range(count): + pos = block.tell() + e = DSStoreEntry.read(block) + if e.filename.lower() == filename_lc \ + and (code is None or e.code == code): + block.seek(pos) + block.delete(e.byte_length()) + found = True + + # This does not affect the loop; THIS IS NOT A BUG + count -= 1 + + self._records -= 1 + self._dirty = True + + if found: + used = block.tell() + + block.seek(0) + block.write(b'>II', next_node, count) + + return used < self._page_size // 2 + else: + return False + + # Remove the largest entry from the subtree starting at `node' (with + # path from root `path'). Returns a tuple (rebalance, entry) where + # rebalance is either None if no rebalancing is required, or a + # (path, node) tuple giving the details of the node to rebalance. + def _take_largest(self, path, node): + path = list(path) + rebalance = None + while True: + with self._get_block(node) as block: + next_node, count = block.read(b'>II') + + if next_node: + path.append(node) + node = next_node + continue + + for n in range(count): + pos = block.tell() + e = DSStoreEntry.read(block) + + count -= 1 + block.seek(0) + block.write(b'>II', next_node, count) + + if pos < self._page_size // 2: + rebalance = (path, node) + break + + return rebalance, e + + # Delete an entry from an inner node, `node' + def _delete_inner(self, path, node, filename_lc, code): + rebalance = False + + with self._get_block(node) as block: + next_node, count = block.read(b'>II') + + for n in range(count): + pos = block.tell() + ptr = block.read(b'>I')[0] + e = DSStoreEntry.read(block) + if e.filename.lower() == filename_lc \ + and (code is None or e.code == code): + # Take the largest from the left subtree + rebalance, largest = self._take_largest(path, ptr) + + # Delete this entry + if n == count - 1: + right_ptr = next_node + next_node = ptr + block.seek(pos) + else: + right_ptr = block.read(b'>I')[0] + block.seek(pos + 4) + + block.delete(e.byte_length() + 4) + + count -= 1 + block.seek(0) + block.write(b'>II', next_node, count) + + self._records -= 1 + self._dirty = True + + break + + # Replace the pivot value + self._insert_inner(path, node, largest, right_ptr) + + # Rebalance from the node we stole from + if rebalance: + self._rebalance(rebalance[0], rebalance[1]) + return True + return False + + def delete(self, filename, code): + """Delete an item, identified by ``filename`` and ``code`` + from the B-Tree.""" + if isinstance(filename, DSStoreEntry): + code = filename.code + filename = filename.filename + + # If we're deleting *every* node for "filename", we must recurse + if code is None: + ###TODO: Fix this so we can do bulk deletes + raise ValueError('You must delete items individually. Sorry') + + # Otherwise, we're deleting *one* specific node + filename_lc = filename.lower() + path = [] + node = self._rootnode + while True: + with self._get_block(node) as block: + next_node, count = block.read(b'>II') + if next_node: + for n in range(count): + ptr = block.read(b'>I')[0] + e = DSStoreEntry.read(block) + e_lc = e.filename.lower() + if filename_lc < e_lc \ + or (filename_lc == e_lc and code < e.code): + next_node = ptr + break + elif filename_lc == e_lc and code == e.code: + self._delete_inner(path, node, filename_lc, code) + return + path.append(node) + node = next_node + else: + if self._delete_leaf(node, filename_lc, code): + self._rebalance(path, node) + return + + # Find implementation + def _find(self, node, filename_lc, code=None): + if not isinstance(code, bytes): + code = code.encode('latin_1') + with self._get_block(node) as block: + next_node, count = block.read(b'>II') + if next_node: + for n in range(count): + ptr = block.read(b'>I')[0] + e = DSStoreEntry.read(block) + if filename_lc < e.filename.lower(): + for e in self._find(ptr, filename_lc, code): + yield e + return + elif filename_lc == e.filename.lower(): + if code is None or (code and code < e.code): + for e in self._find(ptr, filename_lc, code): + yield e + if code is None or code == e.code: + yield e + elif code < e.code: + return + for e in self._find(next_node, filename_lc, code): + yield e + else: + for n in range(count): + e = DSStoreEntry.read(block) + if filename_lc == e.filename.lower(): + if code is None or code == e.code: + yield e + elif code < e.code: + return + + def find(self, filename, code=None): + """Returns a generator that will iterate over matching entries in + the B-Tree.""" + if isinstance(filename, DSStoreEntry): + code = filename.code + filename = filename.filename + + filename_lc = filename.lower() + + return self._find(self._rootnode, filename_lc, code) + + def __len__(self): + return self._records + + def __iter__(self): + return self._traverse(self._rootnode) + + class Partial(object): + """This is used to implement indexing.""" + def __init__(self, store, filename): + self._store = store + self._filename = filename + + def __getitem__(self, code): + if code is None: + raise KeyError('no such key - [%s][None]' % self._filename) + + if not isinstance(code, bytes): + code = code.encode('latin_1') + + try: + item = next(self._store.find(self._filename, code)) + except StopIteration: + raise KeyError('no such key - [%s][%s]' % (self._filename, + code)) + + if not isinstance(item.type, (bytes, str, unicode)): + return item.value + + return (item.type, item.value) + + def __setitem__(self, code, value): + if code is None: + raise KeyError('bad key - [%s][None]' % self._filename) + + if not isinstance(code, bytes): + code = code.encode('latin_1') + + codec = codecs.get(code, None) + if codec: + entry_type = codec + entry_value = value + else: + entry_type = value[0] + entry_value = value[1] + + self._store.insert(DSStoreEntry(self._filename, code, + entry_type, entry_value)) + + def __delitem__(self, code): + if code is None: + raise KeyError('no such key - [%s][None]' % self._filename) + + self._store.delete(self._filename, code) + + def __iter__(self): + for item in self._store.find(self._filename): + yield item + + def __getitem__(self, filename): + return self.Partial(self, filename) + diff --git a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/LICENSE b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/LICENSE new file mode 100644 index 00000000..e91f4eb3 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Alastair Houghton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/__init__.py b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/__init__.py new file mode 100644 index 00000000..7eb31410 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/__init__.py @@ -0,0 +1,27 @@ +from .alias import * +from .bookmark import * + +__all__ = [ 'ALIAS_KIND_FILE', 'ALIAS_KIND_FOLDER', + 'ALIAS_HFS_VOLUME_SIGNATURE', + 'ALIAS_FIXED_DISK', 'ALIAS_NETWORK_DISK', 'ALIAS_400KB_FLOPPY_DISK', + 'ALIAS_800KB_FLOPPY_DISK', 'ALIAS_1_44MB_FLOPPY_DISK', + 'ALIAS_EJECTABLE_DISK', + 'ALIAS_NO_CNID', + 'kBookmarkPath', 'kBookmarkCNIDPath', 'kBookmarkFileProperties', + 'kBookmarkFileName', 'kBookmarkFileID', 'kBookmarkFileCreationDate', + 'kBookmarkTOCPath', 'kBookmarkVolumePath', + 'kBookmarkVolumeURL', 'kBookmarkVolumeName', 'kBookmarkVolumeUUID', + 'kBookmarkVolumeSize', 'kBookmarkVolumeCreationDate', + 'kBookmarkVolumeProperties', 'kBookmarkContainingFolder', + 'kBookmarkUserName', 'kBookmarkUID', 'kBookmarkWasFileReference', + 'kBookmarkCreationOptions', 'kBookmarkURLLengths', + 'kBookmarkSecurityExtension', + 'AppleShareInfo', + 'VolumeInfo', + 'TargetInfo', + 'Alias', + 'Bookmark', + 'Data', + 'URL' ] + + diff --git a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/alias.py b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/alias.py new file mode 100644 index 00000000..f74b949e --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/alias.py @@ -0,0 +1,607 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from __future__ import division + +import struct +import datetime +import io +import re +import os +import os.path +import stat +import sys + +if sys.platform == 'darwin': + from . import osx + +try: + long +except NameError: + long = int + +from .utils import * + +ALIAS_KIND_FILE = 0 +ALIAS_KIND_FOLDER = 1 + +ALIAS_HFS_VOLUME_SIGNATURE = b'H+' + +ALIAS_FIXED_DISK = 0 +ALIAS_NETWORK_DISK = 1 +ALIAS_400KB_FLOPPY_DISK = 2 +ALIAS_800KB_FLOPPY_DISK = 3 +ALIAS_1_44MB_FLOPPY_DISK = 4 +ALIAS_EJECTABLE_DISK = 5 + +ALIAS_NO_CNID = 0xffffffff + +def encode_utf8(s): + if isinstance(s, bytes): + return s + return s.encode('utf-8') + +def decode_utf8(s): + if isinstance(s, bytes): + return s.decode('utf-8') + return s + +class AppleShareInfo (object): + def __init__(self, zone=None, server=None, user=None): + #: The AppleShare zone + self.zone = zone + #: The AFP server + self.server = server + #: The username + self.user = user + + def __repr__(self): + return 'AppleShareInfo(%r,%r,%r)' % (self.zone, self.server, self.user) + +class VolumeInfo (object): + def __init__(self, name, creation_date, fs_type, disk_type, + attribute_flags, fs_id, appleshare_info=None, + driver_name=None, posix_path=None, disk_image_alias=None, + dialup_info=None, network_mount_info=None): + #: The name of the volume on which the target resides + self.name = name + + #: The creation date of the target's volume + self.creation_date = creation_date + + #: The filesystem type (a two character code, e.g. ``b'H+'`` for HFS+) + self.fs_type = fs_type + + #: The type of disk; should be one of + #: + #: * ALIAS_FIXED_DISK + #: * ALIAS_NETWORK_DISK + #: * ALIAS_400KB_FLOPPY_DISK + #: * ALIAS_800KB_FLOPPY_DISK + #: * ALIAS_1_44MB_FLOPPY_DISK + #: * ALIAS_EJECTABLE_DISK + self.disk_type = disk_type + + #: Filesystem attribute flags (from HFS volume header) + self.attribute_flags = attribute_flags + + #: Filesystem identifier + self.fs_id = fs_id + + #: AppleShare information (for automatic remounting of network shares) + #: *(optional)* + self.appleshare_info = appleshare_info + + #: Driver name (*probably* contains a disk driver name on older Macs) + #: *(optional)* + self.driver_name = driver_name + + #: POSIX path of the mount point of the target's volume + #: *(optional)* + self.posix_path = posix_path + + #: :class:`Alias` object pointing at the disk image on which the + #: target's volume resides *(optional)* + self.disk_image_alias = disk_image_alias + + #: Dialup information (for automatic establishment of dialup connections) + self.dialup_info = dialup_info + + #: Network mount information (for automatic remounting) + self.network_mount_info = network_mount_info + + def __repr__(self): + args = ['name', 'creation_date', 'fs_type', 'disk_type', + 'attribute_flags', 'fs_id'] + values = [] + for a in args: + v = getattr(self, a) + values.append(repr(v)) + + kwargs = ['appleshare_info', 'driver_name', 'posix_path', + 'disk_image_alias', 'dialup_info', 'network_mount_info'] + for a in kwargs: + v = getattr(self, a) + if v is not None: + values.append('%s=%r' % (a, v)) + return 'VolumeInfo(%s)' % ','.join(values) + +class TargetInfo (object): + def __init__(self, kind, filename, folder_cnid, cnid, creation_date, + creator_code, type_code, levels_from=-1, levels_to=-1, + folder_name=None, cnid_path=None, carbon_path=None, + posix_path=None, user_home_prefix_len=None): + #: Either ALIAS_KIND_FILE or ALIAS_KIND_FOLDER + self.kind = kind + + #: The filename of the target + self.filename = filename + + #: The CNID (Catalog Node ID) of the target's containing folder; + #: CNIDs are similar to but different than traditional UNIX inode + #: numbers + self.folder_cnid = folder_cnid + + #: The CNID (Catalog Node ID) of the target + self.cnid = cnid + + #: The target's *creation* date. + self.creation_date = creation_date + + #: The target's Mac creator code (a four-character binary string) + self.creator_code = creator_code + + #: The target's Mac type code (a four-character binary string) + self.type_code = type_code + + #: The depth of the alias? Always seems to be -1 on OS X. + self.levels_from = levels_from + + #: The depth of the target? Always seems to be -1 on OS X. + self.levels_to = levels_to + + #: The (POSIX) name of the target's containing folder. *(optional)* + self.folder_name = folder_name + + #: The path from the volume root as a sequence of CNIDs. *(optional)* + self.cnid_path = cnid_path + + #: The Carbon path of the target *(optional)* + self.carbon_path = carbon_path + + #: The POSIX path of the target relative to the volume root. Note + #: that this may or may not have a leading '/' character, but it is + #: always relative to the containing volume. *(optional)* + self.posix_path = posix_path + + #: If the path points into a user's home folder, the number of folders + #: deep that we go before we get to that home folder. *(optional)* + self.user_home_prefix_len = user_home_prefix_len + + def __repr__(self): + args = ['kind', 'filename', 'folder_cnid', 'cnid', 'creation_date', + 'creator_code', 'type_code'] + values = [] + for a in args: + v = getattr(self, a) + values.append(repr(v)) + + if self.levels_from != -1: + values.append('levels_from=%r' % self.levels_from) + if self.levels_to != -1: + values.append('levels_to=%r' % self.levels_to) + + kwargs = ['folder_name', 'cnid_path', 'carbon_path', + 'posix_path', 'user_home_prefix_len'] + for a in kwargs: + v = getattr(self, a) + values.append('%s=%r' % (a, v)) + + return 'TargetInfo(%s)' % ','.join(values) + +TAG_CARBON_FOLDER_NAME = 0 +TAG_CNID_PATH = 1 +TAG_CARBON_PATH = 2 +TAG_APPLESHARE_ZONE = 3 +TAG_APPLESHARE_SERVER_NAME = 4 +TAG_APPLESHARE_USERNAME = 5 +TAG_DRIVER_NAME = 6 +TAG_NETWORK_MOUNT_INFO = 9 +TAG_DIALUP_INFO = 10 +TAG_UNICODE_FILENAME = 14 +TAG_UNICODE_VOLUME_NAME = 15 +TAG_HIGH_RES_VOLUME_CREATION_DATE = 16 +TAG_HIGH_RES_CREATION_DATE = 17 +TAG_POSIX_PATH = 18 +TAG_POSIX_PATH_TO_MOUNTPOINT = 19 +TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE = 20 +TAG_USER_HOME_LENGTH_PREFIX = 21 + +class Alias (object): + def __init__(self, appinfo=b'\0\0\0\0', version=2, volume=None, + target=None, extra=[]): + """Construct a new :class:`Alias` object with the specified + contents.""" + + #: Application specific information (four byte byte-string) + self.appinfo = appinfo + + #: Version (we support only version 2) + self.version = version + + #: A :class:`VolumeInfo` object describing the target's volume + self.volume = volume + + #: A :class:`TargetInfo` object describing the target + self.target = target + + #: A list of extra `(tag, value)` pairs + self.extra = list(extra) + + @classmethod + def _from_fd(cls, b): + appinfo, recsize, version = struct.unpack(b'>4shh', b.read(8)) + + if recsize < 150: + raise ValueError('Incorrect alias length') + + if version != 2: + raise ValueError('Unsupported alias version %u' % version) + + kind, volname, voldate, fstype, disktype, \ + folder_cnid, filename, cnid, crdate, creator_code, type_code, \ + levels_from, levels_to, volattrs, volfsid, reserved = \ + struct.unpack(b'>h28pI2shI64pII4s4shhI2s10s', b.read(142)) + + voldate = mac_epoch + datetime.timedelta(seconds=voldate) + crdate = mac_epoch + datetime.timedelta(seconds=crdate) + + alias = Alias() + alias.appinfo = appinfo + + alias.volume = VolumeInfo (volname.decode().replace('/',':'), + voldate, fstype, disktype, + volattrs, volfsid) + alias.target = TargetInfo (kind, filename.decode().replace('/',':'), + folder_cnid, cnid, + crdate, creator_code, type_code) + alias.target.levels_from = levels_from + alias.target.levels_to = levels_to + + tag = struct.unpack(b'>h', b.read(2))[0] + + while tag != -1: + length = struct.unpack(b'>h', b.read(2))[0] + value = b.read(length) + if length & 1: + b.read(1) + + if tag == TAG_CARBON_FOLDER_NAME: + alias.target.folder_name = value.decode().replace('/',':') + elif tag == TAG_CNID_PATH: + alias.target.cnid_path = struct.unpack('>%uI' % (length // 4), + value) + elif tag == TAG_CARBON_PATH: + alias.target.carbon_path = value + elif tag == TAG_APPLESHARE_ZONE: + if alias.volume.appleshare_info is None: + alias.volume.appleshare_info = AppleShareInfo() + alias.volume.appleshare_info.zone = value + elif tag == TAG_APPLESHARE_SERVER_NAME: + if alias.volume.appleshare_info is None: + alias.volume.appleshare_info = AppleShareInfo() + alias.volume.appleshare_info.server = value + elif tag == TAG_APPLESHARE_USERNAME: + if alias.volume.appleshare_info is None: + alias.volume.appleshare_info = AppleShareInfo() + alias.volume.appleshare_info.user = value + elif tag == TAG_DRIVER_NAME: + alias.volume.driver_name = value + elif tag == TAG_NETWORK_MOUNT_INFO: + alias.volume.network_mount_info = value + elif tag == TAG_DIALUP_INFO: + alias.volume.dialup_info = value + elif tag == TAG_UNICODE_FILENAME: + alias.target.filename = value[2:].decode('utf-16be') + elif tag == TAG_UNICODE_VOLUME_NAME: + alias.volume.name = value[2:].decode('utf-16be') + elif tag == TAG_HIGH_RES_VOLUME_CREATION_DATE: + seconds = struct.unpack(b'>Q', value)[0] / 65536.0 + alias.volume.creation_date \ + = mac_epoch + datetime.timedelta(seconds=seconds) + elif tag == TAG_HIGH_RES_CREATION_DATE: + seconds = struct.unpack(b'>Q', value)[0] / 65536.0 + alias.target.creation_date \ + = mac_epoch + datetime.timedelta(seconds=seconds) + elif tag == TAG_POSIX_PATH: + alias.target.posix_path = value.decode() + elif tag == TAG_POSIX_PATH_TO_MOUNTPOINT: + alias.volume.posix_path = value.decode() + elif tag == TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE: + alias.volume.disk_image_alias = Alias.from_bytes(value) + elif tag == TAG_USER_HOME_LENGTH_PREFIX: + alias.target.user_home_prefix_len = struct.unpack(b'>h', value)[0] + else: + alias.extra.append((tag, value)) + + tag = struct.unpack(b'>h', b.read(2))[0] + + return alias + + @classmethod + def from_bytes(cls, bytes): + """Construct an :class:`Alias` object given binary Alias data.""" + with io.BytesIO(bytes) as b: + return cls._from_fd(b) + + @classmethod + def for_file(cls, path): + """Create an :class:`Alias` that points at the specified file.""" + if sys.platform != 'darwin': + raise Exception('Not implemented (requires special support)') + + path = encode_utf8(path) + + a = Alias() + + # Find the filesystem + st = osx.statfs(path) + vol_path = st.f_mntonname + + # Grab its attributes + attrs = [osx.ATTR_CMN_CRTIME, + osx.ATTR_VOL_NAME, + 0, 0, 0] + volinfo = osx.getattrlist(vol_path, attrs, 0) + + vol_crtime = volinfo[0] + vol_name = encode_utf8(volinfo[1]) + + # Also grab various attributes of the file + attrs = [(osx.ATTR_CMN_OBJTYPE + | osx.ATTR_CMN_CRTIME + | osx.ATTR_CMN_FNDRINFO + | osx.ATTR_CMN_FILEID + | osx.ATTR_CMN_PARENTID), 0, 0, 0, 0] + info = osx.getattrlist(path, attrs, osx.FSOPT_NOFOLLOW) + + if info[0] == osx.VDIR: + kind = ALIAS_KIND_FOLDER + else: + kind = ALIAS_KIND_FILE + + cnid = info[3] + folder_cnid = info[4] + + dirname, filename = os.path.split(path) + + if dirname == b'' or dirname == b'.': + dirname = os.getcwd() + + foldername = os.path.basename(dirname) + + creation_date = info[1] + + if kind == ALIAS_KIND_FILE: + creator_code = struct.pack(b'I', info[2].fileInfo.fileCreator) + type_code = struct.pack(b'I', info[2].fileInfo.fileType) + else: + creator_code = b'\0\0\0\0' + type_code = b'\0\0\0\0' + + a.target = TargetInfo(kind, filename, folder_cnid, cnid, creation_date, + creator_code, type_code) + a.volume = VolumeInfo(vol_name, vol_crtime, b'H+', + ALIAS_FIXED_DISK, 0, b'\0\0') + + a.target.folder_name = foldername + a.volume.posix_path = vol_path + + rel_path = os.path.relpath(path, vol_path) + + # Leave off the initial '/' if vol_path is '/' (no idea why) + if vol_path == b'/': + a.target.posix_path = rel_path + else: + a.target.posix_path = b'/' + rel_path + + # Construct the Carbon and CNID paths + carbon_path = [] + cnid_path = [] + head, tail = os.path.split(rel_path) + if not tail: + head, tail = os.path.split(head) + while head or tail: + if head: + attrs = [osx.ATTR_CMN_FILEID, 0, 0, 0, 0] + info = osx.getattrlist(os.path.join(vol_path, head), attrs, 0) + cnid_path.append(info[0]) + carbon_tail = tail.replace(b':',b'/') + carbon_path.insert(0, carbon_tail) + head, tail = os.path.split(head) + + carbon_path = vol_name + b':' + b':\0'.join(carbon_path) + + a.target.carbon_path = carbon_path + a.target.cnid_path = cnid_path + + return a + + def _to_fd(self, b): + # We'll come back and fix the length when we're done + pos = b.tell() + b.write(struct.pack(b'>4shh', self.appinfo, 0, self.version)) + + carbon_volname = encode_utf8(self.volume.name).replace(b':',b'/') + carbon_filename = encode_utf8(self.target.filename).replace(b':',b'/') + voldate = (self.volume.creation_date - mac_epoch).total_seconds() + crdate = (self.target.creation_date - mac_epoch).total_seconds() + + # NOTE: crdate should be in local time, but that's system dependent + # (so doing so is ridiculous, and nothing could rely on it). + b.write(struct.pack(b'>h28pI2shI64pII4s4shhI2s10s', + self.target.kind, + carbon_volname, int(voldate), + self.volume.fs_type, + self.volume.disk_type, + self.target.folder_cnid, + carbon_filename, + self.target.cnid, + int(crdate), + self.target.creator_code, + self.target.type_code, + self.target.levels_from, + self.target.levels_to, + self.volume.attribute_flags, + self.volume.fs_id, + b'\0'*10)) + + # Excuse the odd order; we're copying Finder + if self.target.folder_name: + carbon_foldername = encode_utf8(self.target.folder_name)\ + .replace(b':',b'/') + b.write(struct.pack(b'>hh', TAG_CARBON_FOLDER_NAME, + len(carbon_foldername))) + b.write(carbon_foldername) + if len(carbon_foldername) & 1: + b.write(b'\0') + + b.write(struct.pack(b'>hhQhhQ', + TAG_HIGH_RES_VOLUME_CREATION_DATE, + 8, int(voldate * 65536), + TAG_HIGH_RES_CREATION_DATE, + 8, int(crdate * 65536))) + + if self.target.cnid_path: + cnid_path = struct.pack('>%uI' % len(self.target.cnid_path), + *self.target.cnid_path) + b.write(struct.pack(b'>hh', TAG_CNID_PATH, + len(cnid_path))) + b.write(cnid_path) + + if self.target.carbon_path: + carbon_path=encode_utf8(self.target.carbon_path) + b.write(struct.pack(b'>hh', TAG_CARBON_PATH, + len(carbon_path))) + b.write(carbon_path) + if len(carbon_path) & 1: + b.write(b'\0') + + if self.volume.appleshare_info: + ai = self.volume.appleshare_info + if ai.zone: + b.write(struct.pack(b'>hh', TAG_APPLESHARE_ZONE, + len(ai.zone))) + b.write(ai.zone) + if len(ai.zone) & 1: + b.write(b'\0') + if ai.server: + b.write(struct.pack(b'>hh', TAG_APPLESHARE_SERVER_NAME, + len(ai.server))) + b.write(ai.server) + if len(ai.server) & 1: + b.write(b'\0') + if ai.username: + b.write(struct.pack(b'>hh', TAG_APPLESHARE_USERNAME, + len(ai.username))) + b.write(ai.username) + if len(ai.username) & 1: + b.write(b'\0') + + if self.volume.driver_name: + driver_name = encode_utf8(self.volume.driver_name) + b.write(struct.pack(b'>hh', TAG_DRIVER_NAME, + len(driver_name))) + b.write(driver_name) + if len(driver_name) & 1: + b.write(b'\0') + + if self.volume.network_mount_info: + b.write(struct.pack(b'>hh', TAG_NETWORK_MOUNT_INFO, + len(self.volume.network_mount_info))) + b.write(self.volume.network_mount_info) + if len(self.volume.network_mount_info) & 1: + b.write(b'\0') + + if self.volume.dialup_info: + b.write(struct.pack(b'>hh', TAG_DIALUP_INFO, + len(self.volume.network_mount_info))) + b.write(self.volume.network_mount_info) + if len(self.volume.network_mount_info) & 1: + b.write(b'\0') + + utf16 = decode_utf8(self.target.filename)\ + .replace(':','/').encode('utf-16-be') + b.write(struct.pack(b'>hhh', TAG_UNICODE_FILENAME, + len(utf16) + 2, + len(utf16) // 2)) + b.write(utf16) + + utf16 = decode_utf8(self.volume.name)\ + .replace(':','/').encode('utf-16-be') + b.write(struct.pack(b'>hhh', TAG_UNICODE_VOLUME_NAME, + len(utf16) + 2, + len(utf16) // 2)) + b.write(utf16) + + if self.target.posix_path: + posix_path = encode_utf8(self.target.posix_path) + b.write(struct.pack(b'>hh', TAG_POSIX_PATH, + len(posix_path))) + b.write(posix_path) + if len(posix_path) & 1: + b.write(b'\0') + + if self.volume.posix_path: + posix_path = encode_utf8(self.volume.posix_path) + b.write(struct.pack(b'>hh', TAG_POSIX_PATH_TO_MOUNTPOINT, + len(posix_path))) + b.write(posix_path) + if len(posix_path) & 1: + b.write(b'\0') + + if self.volume.disk_image_alias: + d = self.volume.disk_image_alias.to_bytes() + b.write(struct.pack(b'>hh', TAG_RECURSIVE_ALIAS_OF_DISK_IMAGE, + len(d))) + b.write(d) + if len(d) & 1: + b.write(b'\0') + + if self.target.user_home_prefix_len is not None: + b.write(struct.pack(b'>hhh', TAG_USER_HOME_LENGTH_PREFIX, + 2, self.target.user_home_prefix_len)) + + for t,v in self.extra: + b.write(struct.pack(b'>hh', t, len(v))) + b.write(v) + if len(v) & 1: + b.write(b'\0') + + b.write(struct.pack(b'>hh', -1, 0)) + + blen = b.tell() - pos + b.seek(pos + 4, os.SEEK_SET) + b.write(struct.pack(b'>h', blen)) + + def to_bytes(self): + """Returns the binary representation for this :class:`Alias`.""" + with io.BytesIO() as b: + self._to_fd(b) + return b.getvalue() + + def __str__(self): + return '' % self.target.filename + + def __repr__(self): + values = [] + if self.appinfo != b'\0\0\0\0': + values.append('appinfo=%r' % self.appinfo) + if self.version != 2: + values.append('version=%r' % self.version) + if self.volume is not None: + values.append('volume=%r' % self.volume) + if self.target is not None: + values.append('target=%r' % self.target) + if self.extra: + values.append('extra=%r' % self.extra) + return 'Alias(%s)' % ','.join(values) diff --git a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/bookmark.py b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/bookmark.py new file mode 100644 index 00000000..0de6b940 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/bookmark.py @@ -0,0 +1,665 @@ +# -*- coding: utf-8 -*- +# +# This file implements the Apple "bookmark" format, which is the replacement +# for the old-fashioned alias format. The details of this format were +# reverse engineered; some things are still not entirely clear. +# +from __future__ import unicode_literals, print_function + +import struct +import uuid +import datetime +import os +import sys +import pprint + +try: + from urlparse import urljoin +except ImportError: + from urllib.parse import urljoin + +if sys.platform == 'darwin': + from . import osx + +def iteritems(x): + return x.iteritems() + +try: + unicode +except NameError: + unicode = str + long = int + xrange = range + def iteritems(x): + return x.items() + +from .utils import * + +BMK_DATA_TYPE_MASK = 0xffffff00 +BMK_DATA_SUBTYPE_MASK = 0x000000ff + +BMK_STRING = 0x0100 +BMK_DATA = 0x0200 +BMK_NUMBER = 0x0300 +BMK_DATE = 0x0400 +BMK_BOOLEAN = 0x0500 +BMK_ARRAY = 0x0600 +BMK_DICT = 0x0700 +BMK_UUID = 0x0800 +BMK_URL = 0x0900 +BMK_NULL = 0x0a00 + +BMK_ST_ZERO = 0x0000 +BMK_ST_ONE = 0x0001 + +BMK_BOOLEAN_ST_FALSE = 0x0000 +BMK_BOOLEAN_ST_TRUE = 0x0001 + +# Subtypes for BMK_NUMBER are really CFNumberType values +kCFNumberSInt8Type = 1 +kCFNumberSInt16Type = 2 +kCFNumberSInt32Type = 3 +kCFNumberSInt64Type = 4 +kCFNumberFloat32Type = 5 +kCFNumberFloat64Type = 6 +kCFNumberCharType = 7 +kCFNumberShortType = 8 +kCFNumberIntType = 9 +kCFNumberLongType = 10 +kCFNumberLongLongType = 11 +kCFNumberFloatType = 12 +kCFNumberDoubleType = 13 +kCFNumberCFIndexType = 14 +kCFNumberNSIntegerType = 15 +kCFNumberCGFloatType = 16 + +# Resource property flags (from CFURLPriv.h) +kCFURLResourceIsRegularFile = 0x00000001 +kCFURLResourceIsDirectory = 0x00000002 +kCFURLResourceIsSymbolicLink = 0x00000004 +kCFURLResourceIsVolume = 0x00000008 +kCFURLResourceIsPackage = 0x00000010 +kCFURLResourceIsSystemImmutable = 0x00000020 +kCFURLResourceIsUserImmutable = 0x00000040 +kCFURLResourceIsHidden = 0x00000080 +kCFURLResourceHasHiddenExtension = 0x00000100 +kCFURLResourceIsApplication = 0x00000200 +kCFURLResourceIsCompressed = 0x00000400 +kCFURLResourceIsSystemCompressed = 0x00000400 +kCFURLCanSetHiddenExtension = 0x00000800 +kCFURLResourceIsReadable = 0x00001000 +kCFURLResourceIsWriteable = 0x00002000 +kCFURLResourceIsExecutable = 0x00004000 +kCFURLIsAliasFile = 0x00008000 +kCFURLIsMountTrigger = 0x00010000 + +# Volume property flags (from CFURLPriv.h) +kCFURLVolumeIsLocal = 0x1 # +kCFURLVolumeIsAutomount = 0x2 # +kCFURLVolumeDontBrowse = 0x4 # +kCFURLVolumeIsReadOnly = 0x8 # +kCFURLVolumeIsQuarantined = 0x10 +kCFURLVolumeIsEjectable = 0x20 # +kCFURLVolumeIsRemovable = 0x40 # +kCFURLVolumeIsInternal = 0x80 # +kCFURLVolumeIsExternal = 0x100 # +kCFURLVolumeIsDiskImage = 0x200 # +kCFURLVolumeIsFileVault = 0x400 +kCFURLVolumeIsLocaliDiskMirror = 0x800 +kCFURLVolumeIsiPod = 0x1000 # +kCFURLVolumeIsiDisk = 0x2000 +kCFURLVolumeIsCD = 0x4000 +kCFURLVolumeIsDVD = 0x8000 +kCFURLVolumeIsDeviceFileSystem = 0x10000 +kCFURLVolumeSupportsPersistentIDs = 0x100000000 +kCFURLVolumeSupportsSearchFS = 0x200000000 +kCFURLVolumeSupportsExchange = 0x400000000 +# reserved 0x800000000 +kCFURLVolumeSupportsSymbolicLinks = 0x1000000000 +kCFURLVolumeSupportsDenyModes = 0x2000000000 +kCFURLVolumeSupportsCopyFile = 0x4000000000 +kCFURLVolumeSupportsReadDirAttr = 0x8000000000 +kCFURLVolumeSupportsJournaling = 0x10000000000 +kCFURLVolumeSupportsRename = 0x20000000000 +kCFURLVolumeSupportsFastStatFS = 0x40000000000 +kCFURLVolumeSupportsCaseSensitiveNames = 0x80000000000 +kCFURLVolumeSupportsCasePreservedNames = 0x100000000000 +kCFURLVolumeSupportsFLock = 0x200000000000 +kCFURLVolumeHasNoRootDirectoryTimes = 0x400000000000 +kCFURLVolumeSupportsExtendedSecurity = 0x800000000000 +kCFURLVolumeSupports2TBFileSize = 0x1000000000000 +kCFURLVolumeSupportsHardLinks = 0x2000000000000 +kCFURLVolumeSupportsMandatoryByteRangeLocks = 0x4000000000000 +kCFURLVolumeSupportsPathFromID = 0x8000000000000 +# reserved 0x10000000000000 +kCFURLVolumeIsJournaling = 0x20000000000000 +kCFURLVolumeSupportsSparseFiles = 0x40000000000000 +kCFURLVolumeSupportsZeroRuns = 0x80000000000000 +kCFURLVolumeSupportsVolumeSizes = 0x100000000000000 +kCFURLVolumeSupportsRemoteEvents = 0x200000000000000 +kCFURLVolumeSupportsHiddenFiles = 0x400000000000000 +kCFURLVolumeSupportsDecmpFSCompression = 0x800000000000000 +kCFURLVolumeHas64BitObjectIDs = 0x1000000000000000 +kCFURLVolumePropertyFlagsAll = 0xffffffffffffffff + +BMK_URL_ST_ABSOLUTE = 0x0001 +BMK_URL_ST_RELATIVE = 0x0002 + +# Bookmark keys +# = 0x1003 +kBookmarkPath = 0x1004 # Array of path components +kBookmarkCNIDPath = 0x1005 # Array of CNIDs +kBookmarkFileProperties = 0x1010 # (CFURL rp flags, + # CFURL rp flags asked for, + # 8 bytes NULL) +kBookmarkFileName = 0x1020 +kBookmarkFileID = 0x1030 +kBookmarkFileCreationDate = 0x1040 +# = 0x1054 # ? +# = 0x1055 # ? +# = 0x1056 # ? +# = 0x1101 # ? +# = 0x1102 # ? +kBookmarkTOCPath = 0x2000 # A list of (TOC id, ?) pairs +kBookmarkVolumePath = 0x2002 +kBookmarkVolumeURL = 0x2005 +kBookmarkVolumeName = 0x2010 +kBookmarkVolumeUUID = 0x2011 # Stored (perversely) as a string +kBookmarkVolumeSize = 0x2012 +kBookmarkVolumeCreationDate = 0x2013 +kBookmarkVolumeProperties = 0x2020 # (CFURL vp flags, + # CFURL vp flags asked for, + # 8 bytes NULL) +kBookmarkVolumeIsRoot = 0x2030 # True if volume is FS root +kBookmarkVolumeBookmark = 0x2040 # Embedded bookmark for disk image (TOC id) +kBookmarkVolumeMountPoint = 0x2050 # A URL +# = 0x2070 +kBookmarkContainingFolder = 0xc001 # Index of containing folder in path +kBookmarkUserName = 0xc011 # User that created bookmark +kBookmarkUID = 0xc012 # UID that created bookmark +kBookmarkWasFileReference = 0xd001 # True if the URL was a file reference +kBookmarkCreationOptions = 0xd010 +kBookmarkURLLengths = 0xe003 # See below +# = 0xf017 # Localized name? +# = 0xf022 +kBookmarkSecurityExtension = 0xf080 +# = 0xf081 + +# kBookmarkURLLengths is an array that is set if the URL encoded by the +# bookmark had a base URL; in that case, each entry is the length of the +# base URL in question. Thus a URL +# +# file:///foo/bar/baz blam/blat.html +# +# will result in [3, 2], while the URL +# +# file:///foo bar/baz blam blat.html +# +# would result in [1, 2, 1, 1] + + +class Data (object): + def __init__(self, bytedata=None): + #: The bytes, stored as a byte string + self.bytes = bytes(bytedata) + + def __repr__(self): + return 'Data(%r)' % self.bytes + +class URL (object): + def __init__(self, base, rel=None): + if rel is not None: + #: The base URL, if any (a :class:`URL`) + self.base = base + #: The rest of the URL (a string) + self.relative = rel + else: + self.base = None + self.relative = base + + @property + def absolute(self): + """Return an absolute URL.""" + if self.base is None: + return self.relative + else: + base_abs = self.base.absolute + return urljoin(self.base.absolute, self.relative) + + def __repr__(self): + return 'URL(%r)' % self.absolute + +class Bookmark (object): + def __init__(self, tocs=None): + if tocs is None: + #: The TOCs for this Bookmark + self.tocs = [] + else: + self.tocs = tocs + + @classmethod + def _get_item(cls, data, hdrsize, offset): + offset += hdrsize + if offset > len(data) - 8: + raise ValueError('Offset out of range') + + length,typecode = struct.unpack(b'd', databytes)[0]) + return osx_epoch + secs + elif dtype == BMK_BOOLEAN: + if dsubtype == BMK_BOOLEAN_ST_TRUE: + return True + elif dsubtype == BMK_BOOLEAN_ST_FALSE: + return False + elif dtype == BMK_UUID: + return uuid.UUID(bytes=databytes) + elif dtype == BMK_URL: + if dsubtype == BMK_URL_ST_ABSOLUTE: + return URL(databytes.decode('utf-8')) + elif dsubtype == BMK_URL_ST_RELATIVE: + baseoff,reloff = struct.unpack(b' size: + raise ValueError('Not a bookmark file (header size too large)') + + if size != len(data): + raise ValueError('Not a bookmark file (truncated)') + + tocoffset, = struct.unpack(b' size - hdrsize \ + or size - tocbase < 20: + raise ValueError('TOC offset out of range') + + tocsize,tocmagic,tocid,nexttoc,toccount \ + = struct.unpack(b' -0x80000000 and item < 0x7fffffff: + result = struct.pack(b'd', float(secs.total_seconds())) + elif isinstance(item, uuid.UUID): + result = struct.pack(b' -1: + sz = sz[:nul] + return sz.decode('utf-8') + +def _decode_attrlist_result(buf, attrs, options): + result = [] + + assert len(buf) >= 4 + total_size = uint32_t.from_buffer(buf, 0).value + assert total_size <= len(buf) + + offset = 4 + + # Common attributes + if attrs[0] & ATTR_CMN_RETURNED_ATTRS: + a = attribute_set_t.from_buffer(buf, offset) + result.append(a) + offset += sizeof (attribute_set_t) + if not (options & FSOPT_PACK_INVAL_ATTRS): + attrs = [a.commonattr, a.volattr, a.dirattr, a.fileattr, a.forkattr] + if attrs[0] & ATTR_CMN_NAME: + a = attrreference_t.from_buffer(buf, offset) + ofs = offset + a.attr_dataoffset + name = _decode_utf8_nul(buf[ofs:ofs+a.attr_length]) + offset += sizeof (attrreference_t) + result.append(name) + if attrs[0] & ATTR_CMN_DEVID: + a = dev_t.from_buffer(buf, offset) + offset += sizeof(dev_t) + result.append(a.value) + if attrs[0] & ATTR_CMN_FSID: + a = fsid_t.from_buffer(buf, offset) + offset += sizeof(fsid_t) + result.append(a) + if attrs[0] & ATTR_CMN_OBJTYPE: + a = fsobj_type_t.from_buffer(buf, offset) + offset += sizeof(fsobj_type_t) + result.append(a.value) + if attrs[0] & ATTR_CMN_OBJTAG: + a = fsobj_tag_t.from_buffer(buf, offset) + offset += sizeof(fsobj_tag_t) + result.append(a.value) + if attrs[0] & ATTR_CMN_OBJID: + a = fsobj_id_t.from_buffer(buf, offset) + offset += sizeof(fsobj_id_t) + result.append(a) + if attrs[0] & ATTR_CMN_OBJPERMANENTID: + a = fsobj_id_t.from_buffer(buf, offset) + offset += sizeof(fsobj_id_t) + result.append(a) + if attrs[0] & ATTR_CMN_PAROBJID: + a = fsobj_id_t.from_buffer(buf, offset) + offset += sizeof(fsobj_id_t) + result.append(a) + if attrs[0] & ATTR_CMN_SCRIPT: + a = text_encoding_t.from_buffer(buf, offset) + offset += sizeof(text_encoding_t) + result.append(a.value) + if attrs[0] & ATTR_CMN_CRTIME: + a = timespec.from_buffer(buf, offset) + offset += sizeof(timespec) + result.append(_datetime_from_timespec(a)) + if attrs[0] & ATTR_CMN_MODTIME: + a = timespec.from_buffer(buf, offset) + offset += sizeof(timespec) + result.append(_datetime_from_timespec(a)) + if attrs[0] & ATTR_CMN_CHGTIME: + a = timespec.from_buffer(buf, offset) + offset += sizeof(timespec) + result.append(_datetime_from_timespec(a)) + if attrs[0] & ATTR_CMN_ACCTIME: + a = timespec.from_buffer(buf, offset) + offset += sizeof(timespec) + result.append(_datetime_from_timespec(a)) + if attrs[0] & ATTR_CMN_BKUPTIME: + a = timespec.from_buffer(buf, offset) + offset += sizeof(timespec) + result.append(_datetime_from_timespec(a)) + if attrs[0] & ATTR_CMN_FNDRINFO: + a = FinderInfo.from_buffer(buf, offset) + offset += sizeof(FinderInfo) + result.append(a) + if attrs[0] & ATTR_CMN_OWNERID: + a = uid_t.from_buffer(buf, offset) + offset += sizeof(uid_t) + result.append(a.value) + if attrs[0] & ATTR_CMN_GRPID: + a = gid_t.from_buffer(buf, offset) + offset += sizeof(gid_t) + result.append(a.value) + if attrs[0] & ATTR_CMN_ACCESSMASK: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[0] & ATTR_CMN_FLAGS: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[0] & ATTR_CMN_USERACCESS: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[0] & ATTR_CMN_EXTENDED_SECURITY: + a = attrreference_t.from_buffer(buf, offset) + ofs = offset + a.attr_dataoffset + offset += sizeof(attrreference_t) + ec = uint32_t.from_buffer(buf, ofs + 36).value + class kauth_acl(Structure): + _fields_ = [('acl_entrycount', c_uint), + ('acl_flags', c_uint), + ('acl_ace', kauth_ace * ec)] + class kauth_filesec(Structure): + _fields_ = [('fsec_magic', c_uint), + ('fsec_owner', guid_t), + ('fsec_group', guid_t), + ('fsec_acl', kauth_acl)] + a = kauth_filesec.from_buffer(buf, ofs) + result.append(a) + if attrs[0] & ATTR_CMN_UUID: + result.append(uuid.UUID(bytes=buf[offset:offset+16])) + offset += sizeof(guid_t) + if attrs[0] & ATTR_CMN_GRPUUID: + result.append(uuid.UUID(bytes=buf[offset:offset+16])) + offset += sizeof(guid_t) + if attrs[0] & ATTR_CMN_FILEID: + a = uint64_t.from_buffer(buf, offset) + offset += sizeof(uint64_t) + result.append(a.value) + if attrs[0] & ATTR_CMN_PARENTID: + a = uint64_t.from_buffer(buf, offset) + offset += sizeof(uint64_t) + result.append(a.value) + if attrs[0] & ATTR_CMN_FULLPATH: + a = attrreference_t.from_buffer(buf, offset) + ofs = offset + a.attr_dataoffset + path = _decode_utf8_nul(buf[ofs:ofs+a.attr_length]) + offset += sizeof (attrreference_t) + result.append(path) + if attrs[0] & ATTR_CMN_ADDEDTIME: + a = timespec.from_buffer(buf, offset) + offset += sizeof(timespec) + result.append(_datetime_from_timespec(a)) + + # Volume attributes + if attrs[1] & ATTR_VOL_FSTYPE: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[1] & ATTR_VOL_SIGNATURE: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[1] & ATTR_VOL_SIZE: + a = off_t.from_buffer(buf, offset) + offset += sizeof(off_t) + result.append(a.value) + if attrs[1] & ATTR_VOL_SPACEFREE: + a = off_t.from_buffer(buf, offset) + offset += sizeof(off_t) + result.append(a.value) + if attrs[1] & ATTR_VOL_SPACEAVAIL: + a = off_t.from_buffer(buf, offset) + offset += sizeof(off_t) + result.append(a.value) + if attrs[1] & ATTR_VOL_MINALLOCATION: + a = off_t.from_buffer(buf, offset) + offset += sizeof(off_t) + result.append(a.value) + if attrs[1] & ATTR_VOL_ALLOCATIONCLUMP: + a = off_t.from_buffer(buf, offset) + offset += sizeof(off_t) + result.append(a.value) + if attrs[1] & ATTR_VOL_IOBLOCKSIZE: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[1] & ATTR_VOL_OBJCOUNT: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[1] & ATTR_VOL_FILECOUNT: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[1] & ATTR_VOL_DIRCOUNT: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[1] & ATTR_VOL_MAXOBJCOUNT: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[1] & ATTR_VOL_MOUNTPOINT: + a = attrreference_t.from_buffer(buf, offset) + ofs = offset + a.attr_dataoffset + path = _decode_utf8_nul(buf[ofs:ofs+a.attr_length]) + offset += sizeof (attrreference_t) + result.append(path) + if attrs[1] & ATTR_VOL_NAME: + a = attrreference_t.from_buffer(buf, offset) + ofs = offset + a.attr_dataoffset + name = _decode_utf8_nul(buf[ofs:ofs+a.attr_length]) + offset += sizeof (attrreference_t) + result.append(name) + if attrs[1] & ATTR_VOL_MOUNTFLAGS: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[1] & ATTR_VOL_MOUNTEDDEVICE: + a = attrreference_t.from_buffer(buf, offset) + ofs = offset + a.attr_dataoffset + path = _decode_utf8_nul(buf[ofs:ofs+a.attr_length]) + offset += sizeof (attrreference_t) + result.append(path) + if attrs[1] & ATTR_VOL_ENCODINGSUSED: + a = c_ulonglong.from_buffer(buf, offset) + offset += sizeof(c_ulonglong) + result.append(a.value) + if attrs[1] & ATTR_VOL_CAPABILITIES: + a = vol_capabilities_attr_t.from_buffer(buf, offset) + offset += sizeof(vol_capabilities_attr_t) + result.append(a) + if attrs[1] & ATTR_VOL_UUID: + result.append(uuid.UUID(bytes=buf[offset:offset+16])) + offset += sizeof(uuid_t) + if attrs[1] & ATTR_VOL_ATTRIBUTES: + a = vol_attributes_attr_t.from_buffer(buf, offset) + offset += sizeof(vol_attributes_attr_t) + result.append(a) + + # Directory attributes + if attrs[2] & ATTR_DIR_LINKCOUNT: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[2] & ATTR_DIR_ENTRYCOUNT: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[2] & ATTR_DIR_MOUNTSTATUS: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + + # File attributes + if attrs[3] & ATTR_FILE_LINKCOUNT: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[3] & ATTR_FILE_TOTALSIZE: + a = off_t.from_buffer(buf, offset) + offset += sizeof(off_t) + result.append(a.value) + if attrs[3] & ATTR_FILE_ALLOCSIZE: + a = off_t.from_buffer(buf, offset) + offset += sizeof(off_t) + result.append(a.value) + if attrs[3] & ATTR_FILE_IOBLOCKSIZE: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[3] & ATTR_FILE_CLUMPSIZE: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[3] & ATTR_FILE_DEVTYPE: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[3] & ATTR_FILE_FILETYPE: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[3] & ATTR_FILE_FORKCOUNT: + a = uint32_t.from_buffer(buf, offset) + offset += sizeof(uint32_t) + result.append(a.value) + if attrs[3] & ATTR_FILE_DATALENGTH: + a = off_t.from_buffer(buf, offset) + offset += sizeof(off_t) + result.append(a.value) + if attrs[3] & ATTR_FILE_DATAALLOCSIZE: + a = off_t.from_buffer(buf, offset) + offset += sizeof(off_t) + result.append(a.value) + if attrs[3] & ATTR_FILE_DATAEXTENTS: + a = extentrecord.from_buffer(buf, offset) + offset += sizeof(extentrecord) + result.append(a.value) + if attrs[3] & ATTR_FILE_RSRCLENGTH: + a = off_t.from_buffer(buf, offset) + offset += sizeof(off_t) + result.append(a.value) + if attrs[3] & ATTR_FILE_RSRCALLOCSIZE: + a = off_t.from_buffer(buf, offset) + offset += sizeof(off_t) + result.append(a.value) + if attrs[3] & ATTR_FILE_RSRCEXTENTS: + a = extentrecord.from_buffer(buf, offset) + offset += sizeof(extentrecord) + result.append(a.value) + + # Fork attributes + if attrs[4] & ATTR_FORK_TOTALSIZE: + a = off_t.from_buffer(buf, offset) + offset += sizeof(off_t) + result.append(a.value) + if attrs[4] & ATTR_FORK_ALLOCSIZE: + a = off_t.from_buffer(buf, offset) + offset += sizeof(off_t) + result.append(a.value) + + return result + +# Sadly, ctypes.get_errno() seems not to work +__error = libc.__error +__error.restype = POINTER(c_int) + +def _get_errno(): + return __error().contents.value + +def getattrlist(path, attrs, options): + if not isinstance(path, bytes): + path = path.encode('utf-8') + attrs = list(attrs) + if attrs[1]: + attrs[1] |= ATTR_VOL_INFO + alist = attrlist(bitmapcount=5, + commonattr=attrs[0], + volattr=attrs[1], + dirattr=attrs[2], + fileattr=attrs[3], + forkattr=attrs[4]) + + bufsize = _attrbuf_size(attrs) + buf = create_string_buffer(bufsize) + + ret = _getattrlist(path, byref(alist), buf, bufsize, + options | FSOPT_REPORT_FULLSIZE) + + if ret < 0: + err = _get_errno() + raise OSError(err, os.strerror(err), path) + + return _decode_attrlist_result(buf, attrs, options) + +def fgetattrlist(fd, attrs, options): + if hasattr(fd, 'fileno'): + fd = fd.fileno() + attrs = list(attrs) + if attrs[1]: + attrs[1] |= ATTR_VOL_INFO + alist = attrlist(bitmapcount=5, + commonattr=attrs[0], + volattr=attrs[1], + dirattr=attrs[2], + fileattr=attrs[3], + forkattr=attrs[4]) + + bufsize = _attrbuf_size(attrs) + buf = create_string_buffer(bufsize) + + ret = _fgetattrlist(fd, byref(alist), buf, bufsize, + options | FSOPT_REPORT_FULLSIZE) + + if ret < 0: + err = _get_errno() + raise OSError(err, os.strerror(err)) + + return _decode_attrlist_result(buf, attrs, options) + +def statfs(path): + if not isinstance(path, bytes): + path = path.encode('utf-8') + result = struct_statfs() + ret = _statfs(path, byref(result)) + if ret < 0: + err = _get_errno() + raise OSError(err, os.strerror(err), path) + return result + +def fstatfs(fd): + if hasattr(fd, 'fileno'): + fd = fd.fileno() + result = struct_statfs() + ret = _fstatfs(fd, byref(result)) + if ret < 0: + err = _get_errno() + raise OSError(err, os.strerror(err)) + return result diff --git a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/qt_attribution.json b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/qt_attribution.json new file mode 100644 index 00000000..968b6318 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/qt_attribution.json @@ -0,0 +1,13 @@ +{ + "Id": "mac_alias", + "Name": "mac_alias", + "QDocModule": "qbs", + "QtUsage": "Used in the qbs dmg module for building Apple disk images.", + "Description": "Generate/parse Mac OS Alias records from Python", + "Homepage": "https://github.com/al45tair/mac_alias", + "Version": "2.0.6", + "License": "MIT License", + "LicenseId": "MIT", + "LicenseFile": "LICENSE", + "Copyright": "Copyright (c) 2014 Alastair Houghton" +} diff --git a/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/utils.py b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/utils.py new file mode 100644 index 00000000..6a7d0a12 --- /dev/null +++ b/src/3rdparty/python/lib/python2.7/site-packages/mac_alias/utils.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import datetime + +ZERO = datetime.timedelta(0) +class UTC (datetime.tzinfo): + def utcoffset(self, dt): + return ZERO + def dst(self, dt): + return ZERO + def tzname(self, dt): + return 'UTC' + +utc = UTC() +mac_epoch = datetime.datetime(1904,1,1,0,0,0,0,utc) +unix_epoch = datetime.datetime(1970,1,1,0,0,0,0,utc) +osx_epoch = datetime.datetime(2001,1,1,0,0,0,0,utc) diff --git a/src/app/app.pri b/src/app/app.pri new file mode 100644 index 00000000..d0c94a77 --- /dev/null +++ b/src/app/app.pri @@ -0,0 +1,23 @@ +include(../install_prefix.pri) + +QT = core +TEMPLATE = app +DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_PROCESS_COMBINED_ARGUMENT_START +!isEmpty(QBS_APPS_DESTDIR):DESTDIR = $${QBS_APPS_DESTDIR} +else:DESTDIR = ../../../bin + +!isEmpty(QBS_APPS_RPATH_DIR) { + linux-*:QMAKE_LFLAGS += -Wl,-z,origin \'-Wl,-rpath,$${QBS_APPS_RPATH_DIR}\' + macx:QMAKE_LFLAGS += -Wl,-rpath,$${QBS_APPS_RPATH_DIR} +} + +CONFIG += console +CONFIG -= app_bundle +CONFIG += c++14 + +include($${PWD}/../lib/corelib/use_corelib.pri) +include($${PWD}/shared/logging/logging.pri) + +!isEmpty(QBS_APPS_INSTALL_DIR):target.path = $${QBS_APPS_INSTALL_DIR} +else:target.path = $${QBS_INSTALL_PREFIX}/bin +INSTALLS += target diff --git a/src/app/app.pro b/src/app/app.pro new file mode 100644 index 00000000..935ce177 --- /dev/null +++ b/src/app/app.pro @@ -0,0 +1,10 @@ +TEMPLATE = subdirs +SUBDIRS =\ + qbs\ + qbs-create-project \ + qbs-setup-android \ + qbs-setup-toolchains \ + qbs-setup-qt \ + config + +!isEmpty(QT.widgets.name):SUBDIRS += config-ui diff --git a/src/app/apps.qbs b/src/app/apps.qbs new file mode 100644 index 00000000..1fcb15e4 --- /dev/null +++ b/src/app/apps.qbs @@ -0,0 +1,13 @@ +import qbs + +Project { + references: [ + "config/config.qbs", + "config-ui/config-ui.qbs", + "qbs/qbs.qbs", + "qbs-create-project/qbs-create-project.qbs", + "qbs-setup-android/qbs-setup-android.qbs", + "qbs-setup-qt/qbs-setup-qt.qbs", + "qbs-setup-toolchains/qbs-setup-toolchains.qbs", + ] +} diff --git a/src/app/config-ui/Info.plist b/src/app/config-ui/Info.plist new file mode 100644 index 00000000..6b74be4a --- /dev/null +++ b/src/app/config-ui/Info.plist @@ -0,0 +1,14 @@ + + + + + CFBundleIdentifier + org.qt-project.qbs-config-ui + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Qbs Settings + LSUIElement + 1 + + diff --git a/src/app/config-ui/commandlineparser.cpp b/src/app/config-ui/commandlineparser.cpp new file mode 100644 index 00000000..5f726504 --- /dev/null +++ b/src/app/config-ui/commandlineparser.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** 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 "commandlineparser.h" + +#include +#include + +#include + +using qbs::Internal::Tr; + +static QString helpOptionShort() { return QStringLiteral("-h"); } +static QString helpOptionLong() { return QStringLiteral("--help"); } +static QString settingsDirOption() { return QStringLiteral("--settings-dir"); } +static QString systemOption() { return QStringLiteral("--system"); } + +void CommandLineParser::parse(const QStringList &commandLine) +{ + m_commandLine = commandLine; + Q_ASSERT(!m_commandLine.empty()); + m_command = QFileInfo(m_commandLine.takeFirst()).fileName(); + m_helpRequested = false; + m_settingsDir.clear(); + + if (m_commandLine.empty()) + return; + const QString &arg = m_commandLine.front(); + if (arg == helpOptionShort() || arg == helpOptionLong()) { + m_commandLine.removeFirst(); + m_helpRequested = true; + } else if (arg == systemOption()) { + m_commandLine.removeFirst(); + m_settingsScope = qbs::Settings::SystemScope; + } else if (arg == settingsDirOption()) { + m_commandLine.removeFirst(); + assignOptionArgument(settingsDirOption(), m_settingsDir); + } + + if (!m_commandLine.empty()) + complainAboutExtraArguments(); +} + +void CommandLineParser::throwError(const QString &message) +{ + qbs::ErrorInfo error(Tr::tr("Syntax error: %1").arg(message)); + error.append(usageString()); + throw error; +} + +QString CommandLineParser::usageString() const +{ + QString s = Tr::tr("This tool displays qbs settings in a GUI.\n" + "If you have more than a few settings, this might be preferable to " + "plain \"qbs config\", as it presents a hierarchical view.\n"); + s += Tr::tr("Usage:\n"); + s += Tr::tr(" %1 [%2 ] [%5] [%3|%4]\n") + .arg(m_command, settingsDirOption(), helpOptionShort(), helpOptionLong(), + systemOption()); + return s; +} + +void CommandLineParser::assignOptionArgument(const QString &option, QString &argument) +{ + if (m_commandLine.empty()) + throwError(Tr::tr("Option '%1' needs an argument.").arg(option)); + argument = m_commandLine.takeFirst(); + if (argument.isEmpty()) + throwError(Tr::tr("Argument for option '%1' must not be empty.").arg(option)); +} + +void CommandLineParser::complainAboutExtraArguments() +{ + throwError(Tr::tr("Extraneous command-line arguments '%1'.") + .arg(m_commandLine.join(QLatin1Char(' ')))); +} diff --git a/src/app/config-ui/commandlineparser.h b/src/app/config-ui/commandlineparser.h new file mode 100644 index 00000000..b8ab1a9f --- /dev/null +++ b/src/app/config-ui/commandlineparser.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 QBS_CONFIGUI_COMMANDLINEPARSER_H +#define QBS_CONFIGUI_COMMANDLINEPARSER_H + +#include + +#include + +class CommandLineParser +{ +public: + void parse(const QStringList &commandLine); + + bool helpRequested() const { return m_helpRequested; } + QString settingsDir() const { return m_settingsDir; } + qbs::Settings::Scope settingsScope() const { return m_settingsScope; } + + QString usageString() const; + +private: + [[noreturn]] void throwError(const QString &message); + void assignOptionArgument(const QString &option, QString &argument); + [[noreturn]] void complainAboutExtraArguments(); + + bool m_helpRequested = false; + qbs::Settings::Scope m_settingsScope = qbs::Settings::UserScope; + QString m_settingsDir; + QStringList m_commandLine; + QString m_command; +}; + +#endif // Include guard diff --git a/src/app/config-ui/config-ui.pro b/src/app/config-ui/config-ui.pro new file mode 100644 index 00000000..84d3b3e4 --- /dev/null +++ b/src/app/config-ui/config-ui.pro @@ -0,0 +1,26 @@ +include(../app.pri) + +CONFIG -= console +QT += gui widgets + +TARGET = qbs-config-ui + +HEADERS += \ + commandlineparser.h \ + mainwindow.h + +SOURCES += \ + commandlineparser.cpp \ + main.cpp \ + mainwindow.cpp + +OTHER_FILES += \ + Info.plist + +osx { + QMAKE_LFLAGS += -sectcreate __TEXT __info_plist $$shell_quote($$PWD/Info.plist) + OBJECTIVE_SOURCES += fgapp.mm + LIBS += -framework ApplicationServices -framework Cocoa +} + +FORMS += mainwindow.ui diff --git a/src/app/config-ui/config-ui.qbs b/src/app/config-ui/config-ui.qbs new file mode 100644 index 00000000..8b5e3755 --- /dev/null +++ b/src/app/config-ui/config-ui.qbs @@ -0,0 +1,34 @@ +import qbs 1.0 + +QbsApp { + Depends { name: "Qt.widgets" } + name: "qbs-config-ui" + consoleApplication: false + files: [ + "commandlineparser.cpp", + "commandlineparser.h", + "main.cpp", + "mainwindow.cpp", + "mainwindow.h", + "mainwindow.ui", + ] + + Group { + condition: qbs.targetOS.contains("macos") + files: [ + "fgapp.mm", + "Info.plist" + ] + } + + Properties { + condition: qbs.targetOS.contains("macos") + cpp.frameworks: ["ApplicationServices", "Cocoa"] + bundle.isBundle: false + } + + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } +} diff --git a/src/app/config-ui/fgapp.mm b/src/app/config-ui/fgapp.mm new file mode 100644 index 00000000..5ddd1e8a --- /dev/null +++ b/src/app/config-ui/fgapp.mm @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#import +#include + +extern "C" void qt_macos_forceTransformProcessToForegroundApplicationAndActivate() +{ + [[NSApplication sharedApplication] setActivationPolicy:NSApplicationActivationPolicyRegular]; + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; +} diff --git a/src/app/config-ui/main.cpp b/src/app/config-ui/main.cpp new file mode 100644 index 00000000..dcf53898 --- /dev/null +++ b/src/app/config-ui/main.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 "mainwindow.h" + +#include "commandlineparser.h" + +#include +#include + +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + CommandLineParser clParser; + try { + clParser.parse(app.arguments()); + if (clParser.helpRequested()) { + std::cout << qPrintable(clParser.usageString()); + return EXIT_SUCCESS; + } + } catch (const qbs::ErrorInfo &error) { + std::cerr << qPrintable(error.toString()); + return EXIT_FAILURE; + } + + MainWindow mw(clParser.settingsDir(), clParser.settingsScope()); + mw.show(); + return app.exec(); +} diff --git a/src/app/config-ui/mainwindow.cpp b/src/app/config-ui/mainwindow.cpp new file mode 100644 index 00000000..febf170a --- /dev/null +++ b/src/app/config-ui/mainwindow.cpp @@ -0,0 +1,252 @@ +/**************************************************************************** +** +** 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 "mainwindow.h" +#include "ui_mainwindow.h" + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace { + +class TrimValidator : public QValidator +{ +public: + explicit TrimValidator(QObject *parent = nullptr) : QValidator(parent) {} + +public: // QValidator interface + State validate(QString &input, int &pos) const override + { + Q_UNUSED(pos); + if (input.startsWith(QLatin1Char(' ')) || input.endsWith(QLatin1Char(' '))) + return State::Intermediate; + return State::Acceptable; + } + + void fixup(QString &input) const override + { + input = input.trimmed(); + } +}; + +class SettingsItemDelegate: public QStyledItemDelegate +{ +public: + explicit SettingsItemDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {} + + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override + { + const auto editor = QStyledItemDelegate::createEditor(parent, option, index); + const auto lineEdit = qobject_cast(editor); + if (lineEdit) + lineEdit->setValidator(new TrimValidator(lineEdit)); + return editor; + } +}; + +} // namespace + +MainWindow::MainWindow(const QString &settingsDir, qbs::Settings::Scope scope, QWidget *parent) + : QMainWindow(parent), ui(new Ui::MainWindow) +{ + ui->setupUi(this); + m_model = new qbs::SettingsModel(settingsDir, scope, this); + ui->treeView->setModel(m_model); + ui->treeView->setItemDelegate(new SettingsItemDelegate(ui->treeView)); + ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->treeView, &QTreeView::expanded, this, &MainWindow::adjustColumns); + connect(ui->treeView, &QWidget::customContextMenuRequested, + this, &MainWindow::provideContextMenu); + adjustColumns(); + + QMenu * const fileMenu = menuBar()->addMenu(tr("&File")); + QMenu * const viewMenu = menuBar()->addMenu(tr("&View")); + + const auto reloadAction = new QAction(tr("&Reload"), this); + reloadAction->setShortcut(QKeySequence::Refresh); + connect(reloadAction, &QAction::triggered, this, &MainWindow::reloadSettings); + const auto saveAction = new QAction(tr("&Save"), this); + saveAction->setShortcut(QKeySequence::Save); + connect(saveAction, &QAction::triggered, this, &MainWindow::saveSettings); + const auto expandAllAction = new QAction(tr("&Expand All"), this); + expandAllAction->setShortcut(int(Qt::CTRL) | int(Qt::Key_E)); + connect(expandAllAction, &QAction::triggered, this, &MainWindow::expandAll); + const auto collapseAllAction = new QAction(tr("C&ollapse All"), this); + collapseAllAction->setShortcut(int(Qt::CTRL) | int(Qt::Key_O)); + connect(collapseAllAction, &QAction::triggered, this, &MainWindow::collapseAll); + const auto exitAction = new QAction(tr("E&xit"), this); + exitAction->setShortcut(QKeySequence::Quit); + exitAction->setMenuRole(QAction::QuitRole); + connect(exitAction, &QAction::triggered, this, &MainWindow::exit); + + fileMenu->addAction(reloadAction); + fileMenu->addAction(saveAction); + fileMenu->addSeparator(); + fileMenu->addAction(exitAction); + + viewMenu->addAction(expandAllAction); + viewMenu->addAction(collapseAllAction); + + ui->treeView->installEventFilter(this); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::adjustColumns() +{ + for (int column = 0; column < m_model->columnCount(); ++column) + ui->treeView->resizeColumnToContents(column); +} + +void MainWindow::expandAll() +{ + ui->treeView->expandAll(); + adjustColumns(); +} + +void MainWindow::collapseAll() +{ + ui->treeView->collapseAll(); + adjustColumns(); +} + +void MainWindow::reloadSettings() +{ + if (m_model->hasUnsavedChanges()) { + const QMessageBox::StandardButton answer = QMessageBox::question(this, + tr("Unsaved Changes"), + tr("You have unsaved changes. Do you want to discard them?")); + if (answer != QMessageBox::Yes) + return; + } + m_model->reload(); +} + +void MainWindow::saveSettings() +{ + m_model->save(); +} + +void MainWindow::exit() +{ + if (m_model->hasUnsavedChanges()) { + const QMessageBox::StandardButton answer = QMessageBox::question(this, + tr("Unsaved Changes"), + tr("You have unsaved changes. Do you want to save them now?")); + if (answer == QMessageBox::Yes) + m_model->save(); + } + qApp->quit(); +} + +void MainWindow::provideContextMenu(const QPoint &pos) +{ + const QModelIndex index = ui->treeView->indexAt(pos); + if (index.isValid() && index.column() != m_model->keyColumn()) + return; + const QString settingsKey = m_model->data(index).toString(); + + QMenu contextMenu; + QAction addKeyAction(this); + QAction removeKeyAction(this); + if (index.isValid()) { + addKeyAction.setText(tr("Add new key below '%1'").arg(settingsKey)); + removeKeyAction.setText(tr("Remove key '%1' and all its sub-keys").arg(settingsKey)); + contextMenu.addAction(&addKeyAction); + contextMenu.addAction(&removeKeyAction); + } else { + addKeyAction.setText(tr("Add new top-level key")); + contextMenu.addAction(&addKeyAction); + } + + const QAction *action = contextMenu.exec(ui->treeView->mapToGlobal(pos)); + if (action == &addKeyAction) + m_model->addNewKey(index); + else if (action == &removeKeyAction) + m_model->removeKey(index); +} + +extern "C" void qt_macos_forceTransformProcessToForegroundApplicationAndActivate(); + +bool MainWindow::eventFilter(QObject *watched, QEvent *event) +{ + if (ui->treeView->hasFocus() && event->type() == QEvent::KeyPress) { + const auto keyEvent = static_cast(event); + if (keyEvent->matches(QKeySequence::Delete)) { + const QModelIndexList indexes = ui->treeView->selectionModel()->selectedRows(); + if (indexes.size() == 1) { + const QModelIndex index = indexes.front(); + if (index.isValid()) { + m_model->removeKey(index); + return true; + } + } + } + } + + if (event->type() == QEvent::WindowActivate) { + // Effectively delay the foreground process transformation from QApplication construction to + // when the UI is shown - this prevents the application icon from popping up in the Dock + // when running `qbs help`, and QCoreApplication::arguments() requires the application + // object to be constructed, so it is not easily worked around + #if defined(Q_OS_MACOS) || defined(Q_OS_OSX) + qt_macos_forceTransformProcessToForegroundApplicationAndActivate(); + #endif + } + + + return QMainWindow::eventFilter(watched, event); +} diff --git a/src/app/config-ui/mainwindow.h b/src/app/config-ui/mainwindow.h new file mode 100644 index 00000000..b8a12d61 --- /dev/null +++ b/src/app/config-ui/mainwindow.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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 MAINWINDOW_H +#define MAINWINDOW_H + +#include + +#include + +namespace qbs { class SettingsModel; } + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +class QPoint; +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(const QString &settingsDir, qbs::Settings::Scope scope, + QWidget *parent = nullptr); + ~MainWindow() override; + + bool eventFilter(QObject *watched, QEvent *event) override; + +private: + void adjustColumns(); + void expandAll(); + void collapseAll(); + void reloadSettings(); + void saveSettings(); + void exit(); + void provideContextMenu(const QPoint &pos); + + Ui::MainWindow *ui; + qbs::SettingsModel *m_model; +}; + +#endif // MAINWINDOW_H diff --git a/src/app/config-ui/mainwindow.ui b/src/app/config-ui/mainwindow.ui new file mode 100644 index 00000000..7a3416ce --- /dev/null +++ b/src/app/config-ui/mainwindow.ui @@ -0,0 +1,37 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + Qbs Settings + + + + + + + + + + + + 0 + 0 + 800 + 27 + + + + + + + + diff --git a/src/app/config/config.pro b/src/app/config/config.pro new file mode 100644 index 00000000..278c481d --- /dev/null +++ b/src/app/config/config.pro @@ -0,0 +1,13 @@ +include(../app.pri) + +TARGET = qbs-config + +SOURCES += \ + configcommandexecutor.cpp \ + configcommandlineparser.cpp \ + configmain.cpp + +HEADERS += \ + configcommand.h \ + configcommandexecutor.h \ + configcommandlineparser.h diff --git a/src/app/config/config.qbs b/src/app/config/config.qbs new file mode 100644 index 00000000..9daa701f --- /dev/null +++ b/src/app/config/config.qbs @@ -0,0 +1,14 @@ +import qbs 1.0 + +QbsApp { + name: "qbs-config" + files: [ + "configcommand.h", + "configcommandexecutor.cpp", + "configcommandexecutor.h", + "configcommandlineparser.cpp", + "configcommandlineparser.h", + "configmain.cpp" + ] +} + diff --git a/src/app/config/configcommand.h b/src/app/config/configcommand.h new file mode 100644 index 00000000..1574e374 --- /dev/null +++ b/src/app/config/configcommand.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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 CONFIGCOMMAND_H +#define CONFIGCOMMAND_H + +#include + +#include +#include + +class ConfigCommand +{ +public: + enum Command { CfgSet, CfgUnset, CfgList, CfgExport, CfgImport, CfgNone }; + + ConfigCommand() : command(CfgNone) {} + + Command command; + QStringList varNames; + QString varValue; + QString fileName; +}; + +#endif // CONFIGCOMMAND_H diff --git a/src/app/config/configcommandexecutor.cpp b/src/app/config/configcommandexecutor.cpp new file mode 100644 index 00000000..f2d9fc59 --- /dev/null +++ b/src/app/config/configcommandexecutor.cpp @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** 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 "configcommandexecutor.h" + +#include "configcommand.h" +#include "../shared/logging/consolelogger.h" + +#include +#include +#include + +#include +#include +#include + +#include + +using namespace qbs; + +ConfigCommandExecutor::ConfigCommandExecutor(Settings *settings, Settings::Scopes scope) + : m_settings(settings), m_scope(scope) +{ + if (m_scope == qbs::Settings::SystemScope) + m_settings->setScopeForWriting(qbs::Settings::SystemScope); +} + +void ConfigCommandExecutor::execute(const ConfigCommand &command) +{ + switch (command.command) { + case ConfigCommand::CfgList: + printSettings(command); + break; + case ConfigCommand::CfgSet: + setValue(command.varNames.front(), command.varValue); + break; + case ConfigCommand::CfgUnset: + for (const QString &varName : command.varNames) + m_settings->remove(varName); + break; + case ConfigCommand::CfgExport: + exportSettings(command.fileName); + break; + case ConfigCommand::CfgImport: + // Display old and new settings, in case import fails or user accidentally nukes everything + printf("old "); // Will end up as "old settings:" + printSettings(command); + importSettings(command.fileName); + printf("\nnew "); + printSettings(command); + break; + case ConfigCommand::CfgNone: + qFatal("%s: Impossible command value.", Q_FUNC_INFO); + break; + } +} + +void ConfigCommandExecutor::setValue(const QString &key, const QString &rawInput) +{ + m_settings->setValue(key, representationToSettingsValue(rawInput)); +} + +void ConfigCommandExecutor::printSettings(const ConfigCommand &command) +{ + if (command.varNames.empty()) { + const auto keys = m_settings->allKeys(m_scope); + for (const QString &key : keys) + printOneSetting(key); + } else { + for (const QString &parentKey : command.varNames) { + if (m_settings->value(parentKey, m_scope).isValid()) { // Key is a leaf. + printOneSetting(parentKey); + } else { // Key is a node. + const auto keys = m_settings->allKeysWithPrefix(parentKey, m_scope); + for (const QString &key : keys) + printOneSetting(parentKey + QLatin1Char('.') + key); + } + } + } +} + +void ConfigCommandExecutor::printOneSetting(const QString &key) +{ + printf("%s: %s\n", qPrintable(key), + qPrintable(qbs::settingsValueToRepresentation(m_settings->value(key, m_scope)))); + } + +void ConfigCommandExecutor::exportSettings(const QString &filename) +{ + QFile file(filename); + if (!file.open(QFile::Truncate | QFile::WriteOnly | QFile::Text)) { + throw ErrorInfo(tr("Could not open file '%1' for writing: %2") + .arg(QDir::toNativeSeparators(filename), file.errorString())); + } + QTextStream stream(&file); + stream.setCodec("UTF-8"); + const auto keys = m_settings->allKeys(m_scope); + for (const QString &key : keys) + stream << key << ": " << qbs::settingsValueToRepresentation(m_settings->value(key, m_scope)) + << Qt::endl; +} + +void ConfigCommandExecutor::importSettings(const QString &filename) +{ + QFile file(filename); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + throw ErrorInfo(tr("Could not open file '%1' for reading: %2") + .arg(QDir::toNativeSeparators(filename), file.errorString())); + } + // Remove all current settings + const auto keys = m_settings->allKeys(m_scope); + for (const QString &key : keys) + m_settings->remove(key); + + QTextStream stream(&file); + stream.setCodec("UTF-8"); + while (!stream.atEnd()) { + QString line = stream.readLine(); + int colon = line.indexOf(QLatin1Char(':')); + if (colon >= 0 && !line.startsWith(QLatin1Char('#'))) { + const QString key = line.left(colon).trimmed(); + const QString value = line.mid(colon + 1).trimmed(); + m_settings->setValue(key, representationToSettingsValue(value)); + } + } +} diff --git a/src/app/config/configcommandexecutor.h b/src/app/config/configcommandexecutor.h new file mode 100644 index 00000000..463818a7 --- /dev/null +++ b/src/app/config/configcommandexecutor.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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 CONFIGCOMMANDEXECUTOR_H +#define CONFIGCOMMANDEXECUTOR_H + +#include + +#include + +class ConfigCommand; + +class ConfigCommandExecutor +{ + Q_DECLARE_TR_FUNCTIONS(ConfigCommandExecutor) +public: + ConfigCommandExecutor(qbs::Settings *settings, qbs::Settings::Scopes scope); + + void execute(const ConfigCommand &command); + +private: + void setValue(const QString &key, const QString &rawInput); + void printSettings(const ConfigCommand &command); + void printOneSetting(const QString &key); + void exportSettings(const QString &filename); + void importSettings(const QString &filename); + + qbs::Settings *m_settings; + const qbs::Settings::Scopes m_scope; +}; + +#endif // CONFIGCOMMANDEXECUTOR_H diff --git a/src/app/config/configcommandlineparser.cpp b/src/app/config/configcommandlineparser.cpp new file mode 100644 index 00000000..2f2d1610 --- /dev/null +++ b/src/app/config/configcommandlineparser.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** 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 "configcommandlineparser.h" + +#include +#include + +#include + +using namespace qbs; +using namespace Internal; + +void ConfigCommandLineParser::parse(const QStringList &commandLine) +{ + m_command = ConfigCommand(); + m_helpRequested = false; + m_settingsDir.clear(); + + m_commandLine = commandLine; + if (m_commandLine.empty()) + throw Error(Tr::tr("No parameters supplied.")); + if (m_commandLine.size() == 1 && (m_commandLine.front() == QLatin1String("--help") + || m_commandLine.front() == QLatin1String("-h"))) { + m_helpRequested = true; + return; + } + + while (!m_commandLine.empty() && m_commandLine.front().startsWith(QLatin1String("--"))) { + const QString arg = m_commandLine.takeFirst().mid(2); + if (arg == QLatin1String("list")) + setCommand(ConfigCommand::CfgList); + else if (arg == QLatin1String("unset")) + setCommand(ConfigCommand::CfgUnset); + else if (arg == QLatin1String("export")) + setCommand(ConfigCommand::CfgExport); + else if (arg == QLatin1String("import")) + setCommand(ConfigCommand::CfgImport); + else if (arg == QLatin1String("settings-dir")) + assignOptionArgument(arg, m_settingsDir); + else if (arg == QLatin1String("user")) + setScope(qbs::Settings::UserScope); + else if (arg == QLatin1String("system")) + setScope(qbs::Settings::SystemScope); + else + throw Error(Tr::tr("Unknown option for config command.")); + } + + switch (command().command) { + case ConfigCommand::CfgNone: + if (m_commandLine.empty()) + throw Error(Tr::tr("No parameters supplied.")); + if (m_commandLine.size() > 2) + throw Error(Tr::tr("Too many arguments.")); + m_command.varNames << m_commandLine.front(); + if (m_commandLine.size() == 1) { + setCommand(ConfigCommand::CfgList); + } else { + m_command.varValue = m_commandLine.at(1); + setCommand(ConfigCommand::CfgSet); + } + break; + case ConfigCommand::CfgUnset: + if (m_commandLine.empty()) + throw Error(Tr::tr("Need name of variable to unset.")); + m_command.varNames = m_commandLine; + break; + case ConfigCommand::CfgExport: + if (m_commandLine.size() != 1) + throw Error(Tr::tr("Need name of file to which to export.")); + m_command.fileName = m_commandLine.front(); + break; + case ConfigCommand::CfgImport: + if (m_commandLine.size() != 1) + throw Error(Tr::tr("Need name of file from which to import.")); + m_command.fileName = m_commandLine.front(); + break; + case ConfigCommand::CfgList: + m_command.varNames = m_commandLine; + break; + default: + break; + } +} + +void ConfigCommandLineParser::setCommand(ConfigCommand::Command command) +{ + if (m_command.command != ConfigCommand::CfgNone) + throw Error(Tr::tr("You cannot specify more than one command.")); + m_command.command = command; +} + +void ConfigCommandLineParser::setScope(Settings::Scope scope) +{ + if (m_scope != qbs::Settings::allScopes()) + throw Error(Tr::tr("The --user and --system options can only appear once.")); + m_scope = scope; +} + +void ConfigCommandLineParser::printUsage() const +{ + puts("Usage:\n" + " qbs config [--settings-dir \n" + " qbs config [--settings-dir \n" + " qbs config [--settings-dir " + "\n" + "Options:\n" + " --list [ ...] list keys under key or all keys\n" + " --user consider only user-level settings\n" + " --system consider only system-level settings\n" + " --unset remove key with given name\n" + " --import import settings from given file\n" + " --export export settings to given file\n"); +} + +void ConfigCommandLineParser::assignOptionArgument(const QString &option, QString &argument) +{ + if (m_commandLine.empty()) + throw Error(Tr::tr("Option '%1' needs an argument.").arg(option)); + argument = m_commandLine.takeFirst(); + if (argument.isEmpty()) + throw Error(Tr::tr("Argument for option '%1' must not be empty.").arg(option)); +} diff --git a/src/app/config/configcommandlineparser.h b/src/app/config/configcommandlineparser.h new file mode 100644 index 00000000..f1f02667 --- /dev/null +++ b/src/app/config/configcommandlineparser.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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 COMMANDLINEPARSER_H +#define COMMANDLINEPARSER_H + +#include "configcommand.h" + +#include + +#include + +class ConfigCommandLineParser +{ +public: + void parse(const QStringList &commandLine); + + ConfigCommand command() const { return m_command; } + + qbs::Settings::Scopes scope() const { return m_scope; } + QString settingsDir() const { return m_settingsDir; } + bool helpRequested() const { return m_helpRequested; } + void printUsage() const; + + class Error + { + public: + Error(QString message) : m_message(std::move(message)) { } + QString message() const { return m_message; } + private: + QString m_message; + }; + +private: + void assignOptionArgument(const QString &option, QString &argument); + void setCommand(ConfigCommand::Command command); + void setScope(qbs::Settings::Scope scope); + + ConfigCommand m_command; + qbs::Settings::Scopes m_scope = qbs::Settings::allScopes(); + bool m_helpRequested = false; + QString m_settingsDir; + QStringList m_commandLine; +}; + +#endif // COMMANDLINEPARSER_H diff --git a/src/app/config/configmain.cpp b/src/app/config/configmain.cpp new file mode 100644 index 00000000..29cfaf9c --- /dev/null +++ b/src/app/config/configmain.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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 "configcommandlineparser.h" +#include "configcommandexecutor.h" + +#include +#include +#include + +#include + +#include +#include + +using qbs::Internal::Tr; +using qbs::Settings; + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + ConfigCommandLineParser parser; + try { + parser.parse(app.arguments().mid(1)); + if (parser.helpRequested()) { + std::cout << qPrintable(Tr::tr("This tool manages qbs settings.")) << std::endl; + parser.printUsage(); + return EXIT_SUCCESS; + } + Settings settings(parser.settingsDir()); + ConfigCommandExecutor(&settings, parser.scope()).execute(parser.command()); + } catch (const ConfigCommandLineParser::Error &e) { + std::cerr << qPrintable(e.message()) << std::endl; + parser.printUsage(); + return EXIT_FAILURE; + } catch (const qbs::ErrorInfo &e) { + std::cerr << qPrintable(e.toString()) << std::endl; + return EXIT_FAILURE; + } +} diff --git a/src/app/qbs-create-project/create-project-main.cpp b/src/app/qbs-create-project/create-project-main.cpp new file mode 100644 index 00000000..bb5d1a6b --- /dev/null +++ b/src/app/qbs-create-project/create-project-main.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** 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 "createproject.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +int main(int argc, char *argv[]) +{ + using qbs::ErrorInfo; + using qbs::Internal::Tr; + + QCoreApplication app(argc, argv); + const QCommandLineOption flatOpt(QStringLiteral("flat"), + Tr::tr("Do not create nested project files, even if there are subdirectories and " + "the top-level directory does not contain any files.")); + const QCommandLineOption whiteListOpt(QStringLiteral("whitelist"), + Tr::tr("Only consider files whose names match these patterns. The list entries " + "can contain wildcards and are separated by commas. By default, all files " + "are considered."), QStringLiteral("whitelist")); + const QCommandLineOption blackListOpt(QStringLiteral("blacklist"), + Tr::tr("Ignore files whose names match these patterns. The list entries " + "can contain wildcards and are separated by commas. By default, no files " + "are ignored."), QStringLiteral("blacklist")); + QCommandLineParser parser; + parser.setApplicationDescription(Tr::tr("This tool creates a qbs project from an existing " + "source tree.\nNote: The resulting project file(s) " + "will likely require manual editing.")); + parser.addOption(flatOpt); + parser.addOption(whiteListOpt); + parser.addOption(blackListOpt); + parser.addHelpOption(); + parser.process(app); + const ProjectStructure projectStructure = parser.isSet(flatOpt) + ? ProjectStructure::Flat : ProjectStructure::Composite; + const QStringList whiteList = parser.value(whiteListOpt).split(QLatin1Char(','), + QBS_SKIP_EMPTY_PARTS); + const QStringList blackList = parser.value(blackListOpt).split(QLatin1Char(','), + QBS_SKIP_EMPTY_PARTS); + try { + ProjectCreator().run(QDir::currentPath(), projectStructure, whiteList, blackList); + } catch (const ErrorInfo &e) { + std::cerr << qPrintable(Tr::tr("Error creating project: %1").arg(e.toString())) + << std::endl; + return 1; + } +} diff --git a/src/app/qbs-create-project/createproject.cpp b/src/app/qbs-create-project/createproject.cpp new file mode 100644 index 00000000..26147b48 --- /dev/null +++ b/src/app/qbs-create-project/createproject.cpp @@ -0,0 +1,233 @@ +/**************************************************************************** +** +** 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 "createproject.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +using qbs::ErrorInfo; +using qbs::Internal::Tr; + +static const char *indent = " "; + +void ProjectCreator::run(const QString &topLevelDir, ProjectStructure projectStructure, + const QStringList &whiteList, const QStringList &blackList) +{ + m_projectStructure = projectStructure; + for (const QString &s : whiteList) + m_whiteList.push_back(QRegExp(s, Qt::CaseSensitive, QRegExp::Wildcard)); + for (const QString &s : blackList) + m_blackList.push_back(QRegExp(s, Qt::CaseSensitive, QRegExp::Wildcard)); + m_topLevelProject.dirPath = topLevelDir; + setupProject(&m_topLevelProject); + serializeProject(m_topLevelProject); +} + +void ProjectCreator::setupProject(Project *project) +{ + QDirIterator dit(project->dirPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + while (dit.hasNext()) { + dit.next(); + if (dit.fileInfo().isFile()) { + if (dit.fileName().endsWith(QLatin1String(".qbs"))) + throw ErrorInfo(Tr::tr("Project already contains qbs files, aborting.")); + if (isSourceFile(dit.fileName())) + project->fileNames << dit.fileName(); + } else if (dit.fileInfo().isDir()) { + ProjectPtr subProject(new Project); + subProject->dirName = dit.fileName(); + subProject->dirPath = dit.filePath(); + setupProject(subProject.get()); + if (!subProject->fileNames.empty() || !subProject->subProjects.empty()) + project->subProjects.push_back(std::move(subProject)); + } + } + project->fileNames.sort(); + std::sort(project->subProjects.begin(), project->subProjects.end(), + [](const ProjectPtr &p1, const ProjectPtr &p2) { return p1->dirName < p2->dirName; }); +} + +void ProjectCreator::serializeProject(const ProjectCreator::Project &project) +{ + const QString fileName = QFileInfo(project.dirPath).baseName() + QLatin1String(".qbs"); + QFile projectFile(project.dirPath + QLatin1Char('/') + fileName); + if (!projectFile.open(QIODevice::WriteOnly)) { + throw ErrorInfo(Tr::tr("Failed to open '%1' for writing: %2") + .arg(projectFile.fileName(), projectFile.errorString())); + } + QTextStream fileContents(&projectFile); + fileContents.setCodec("UTF-8"); + fileContents << "import qbs\n\n"; + if (!project.fileNames.empty() || m_projectStructure == ProjectStructure::Flat) { + fileContents << "Product {\n"; + const ProductFlags productFlags = getFlags(project); + if (productFlags.testFlag(IsApp)) { + fileContents << indent << "type: [\"application\"]\n"; + } else { + fileContents << indent << "type: [\"unknown\"] // E.g. \"application\", " + "\"dynamiclibrary\", \"staticlibrary\"\n"; + } + if (productFlags.testFlag(NeedsQt)) { + fileContents << indent << "Depends {\n"; + fileContents << indent << indent << "name: \"Qt\"\n"; + fileContents << indent << indent << "submodules: [\"core\"] " + "// Add more here if needed\n"; + fileContents << indent << "}\n"; + } else if (productFlags.testFlag(NeedsCpp)) { + fileContents << indent << "Depends { name: \"cpp\" }\n"; + } + fileContents << indent << "files: [\n"; + for (const QString &fileName : qAsConst(project.fileNames)) + fileContents << indent << indent << qbs::toJSLiteral(fileName) << ",\n"; + fileContents << indent << "]\n"; + for (const ProjectPtr &p : project.subProjects) + addGroups(fileContents, QDir(project.dirPath), *p); + } else { + fileContents << "Project {\n"; + fileContents << indent << "references: [\n"; + for (const ProjectPtr &p : project.subProjects) { + serializeProject(*p); + fileContents << indent << indent + << qbs::toJSLiteral(QFileInfo(p->dirPath).fileName()) << ",\n"; + } + fileContents << indent << "]\n"; + } + fileContents << "}\n"; +} + +void ProjectCreator::addGroups(QTextStream &stream, const QDir &baseDir, + const ProjectCreator::Project &subProject) +{ + stream << indent << "Group {\n"; + stream << indent << indent << "name: " + << qbs::toJSLiteral(QFileInfo(subProject.dirPath).fileName()) << "\n"; + stream << indent << indent << "prefix: " + << qbs::toJSLiteral(baseDir.relativeFilePath(subProject.dirPath) + QLatin1Char('/')) + << '\n'; + stream << indent << indent << "files: [\n"; + for (const QString &fileName : qAsConst(subProject.fileNames)) + stream << indent << indent << indent << qbs::toJSLiteral(fileName) << ",\n"; + stream << indent << indent << "]\n"; + stream << indent << "}\n"; + for (const ProjectPtr &p : subProject.subProjects) + addGroups(stream, baseDir, *p); +} + +bool ProjectCreator::isSourceFile(const QString &fileName) +{ + const auto isMatch = [fileName](const QRegExp &rex) { return rex.exactMatch(fileName); }; + return !std::any_of(m_blackList.cbegin(), m_blackList.cend(), isMatch) + && (m_whiteList.empty() + || std::any_of(m_whiteList.cbegin(), m_whiteList.cend(), isMatch)); +} + +ProjectCreator::ProductFlags ProjectCreator::getFlags(const ProjectCreator::Project &project) +{ + ProductFlags flags; + getFlagsFromFileNames(project, flags); + if (flags.testFlag(IsApp) && flags.testFlag(NeedsQt)) + return flags; + if (!flags.testFlag(NeedsCpp)) + return flags; + getFlagsFromFileContents(project, flags); + return flags; +} + +void ProjectCreator::getFlagsFromFileNames(const ProjectCreator::Project &project, + ProductFlags &flags) +{ + for (const QString &fileName : qAsConst(project.fileNames)) { + if (flags.testFlag(IsApp) && flags.testFlag(NeedsQt)) + return; + const QFileInfo fi(project.dirPath + QLatin1Char('/') + fileName); + const QString &suffix = fi.suffix(); + if (suffix == QLatin1String("qrc")) { + flags |= NeedsQt; + continue; + } + if (suffix == QLatin1String("cpp") || suffix == QLatin1String("c") + || suffix == QLatin1String("m") || suffix == QLatin1String("mm")) { + flags |= NeedsCpp; + } + if (flags.testFlag(NeedsCpp) && fi.completeBaseName() == QLatin1String("main")) + flags |= IsApp; + } + for (const ProjectPtr &p : project.subProjects) { + getFlagsFromFileNames(*p, flags); + if (flags.testFlag(IsApp) && flags.testFlag(NeedsQt)) + return; + } +} + +void ProjectCreator::getFlagsFromFileContents(const ProjectCreator::Project &project, + ProductFlags &flags) +{ + for (const QString &fileName : qAsConst(project.fileNames)) { + QFile f (project.dirPath + QLatin1Char('/') + fileName); + if (!f.open(QIODevice::ReadOnly)) { + qDebug() << "Ignoring failure to read" << f.fileName(); + continue; + } + while (!f.atEnd()) { + const QByteArray &line = f.readLine(); + if (line.contains("#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE +class QDir; +class QTextStream; +QT_END_NAMESPACE + +enum class ProjectStructure { Flat, Composite }; + +class ProjectCreator +{ +public: + void run(const QString &topLevelDir, ProjectStructure projectStructure, + const QStringList &whiteList, const QStringList &blacklist); + +private: + enum ProductFlag { IsApp = 1, NeedsCpp = 2, NeedsQt = 4 }; + Q_DECLARE_FLAGS(ProductFlags, ProductFlag) + + struct Project; + void setupProject(Project *project); + void serializeProject(const Project &project); + void addGroups(QTextStream &stream, const QDir &baseDir, const Project &subProject); + bool isSourceFile(const QString &fileName); + ProductFlags getFlags(const Project &project); + void getFlagsFromFileNames(const Project &project, ProductFlags &flags); + void getFlagsFromFileContents(const Project &project, ProductFlags &flags); + + using ProjectPtr = std::unique_ptr; + struct Project { + QString dirPath; + QString dirName; + QStringList fileNames; + std::vector subProjects; + }; + Project m_topLevelProject; + ProjectStructure m_projectStructure = ProjectStructure::Flat; + QList m_whiteList; + QList m_blackList; +}; + +#endif // QBS_CREATEPROJECT_H diff --git a/src/app/qbs-create-project/qbs-create-project.pro b/src/app/qbs-create-project/qbs-create-project.pro new file mode 100644 index 00000000..1edb8521 --- /dev/null +++ b/src/app/qbs-create-project/qbs-create-project.pro @@ -0,0 +1,9 @@ +include(../app.pri) + +TARGET = qbs-create-project + +HEADERS += \ + createproject.h +SOURCES += \ + createproject.cpp \ + create-project-main.cpp diff --git a/src/app/qbs-create-project/qbs-create-project.qbs b/src/app/qbs-create-project/qbs-create-project.qbs new file mode 100644 index 00000000..b65f9177 --- /dev/null +++ b/src/app/qbs-create-project/qbs-create-project.qbs @@ -0,0 +1,10 @@ +import qbs + +QbsApp { + name: "qbs-create-project" + files: [ + "createproject.cpp", + "createproject.h", + "create-project-main.cpp", + ] +} diff --git a/src/app/qbs-setup-android/android-setup.cpp b/src/app/qbs-setup-android/android-setup.cpp new file mode 100644 index 00000000..329bd005 --- /dev/null +++ b/src/app/qbs-setup-android/android-setup.cpp @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** 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 "android-setup.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace qbs; +using qbs::Internal::Tr; + +static QString qls(const char *s) { return QLatin1String(s); } + +static QStringList expectedArchs() +{ + return {QStringLiteral("arm64"), QStringLiteral("armv7a"), + QStringLiteral("x86"), QStringLiteral("x86_64")}; +} + +void setupSdk(qbs::Settings *settings, const QString &profileName, const QString &sdkDirPath) +{ + if (!QDir(sdkDirPath).exists()) { + throw ErrorInfo(Tr::tr("SDK directory '%1' does not exist.") + .arg(QDir::toNativeSeparators(sdkDirPath))); + } + + Profile profile(profileName, settings); + profile.removeProfile(); + if (!sdkDirPath.isEmpty()) + profile.setValue(qls("Android.sdk.sdkDir"), QDir::cleanPath(sdkDirPath)); + profile.setValue(qls("qbs.targetPlatform"), qls("android")); +} + +static QString mapArch(const QString &androidName) +{ + if (androidName == qls("arm64-v8a")) + return qls("arm64"); + if (androidName == qls("armeabi")) + return qls("armv5te"); + if (androidName == qls("armeabi-v7a")) + return qls("armv7a"); + return androidName; +} + +struct QtAndroidInfo { + bool isValid() const { return !archs.isEmpty(); } + + QString qmakePath; + QStringList archs; + QString platform; +}; + +static QtAndroidInfo getInfoForQtDir(const QString &qtDir) +{ + QtAndroidInfo info; + info.qmakePath = qbs::Internal::HostOsInfo::appendExecutableSuffix(qtDir + qls("/bin/qmake")); + if (!QFile::exists(info.qmakePath)) + return info; + QFile qdevicepri(qtDir + qls("/mkspecs/qdevice.pri")); + if (!qdevicepri.open(QIODevice::ReadOnly)) + return info; + while (!qdevicepri.atEnd()) { + // For Qt < 5.14 use DEFAULT_ANDROID_TARGET_ARCH (which is the abi) to compute + // the architecture + // DEFAULT_ANDROID_ABIS doesn't exit + // For Qt >= 5.14: + // DEFAULT_ANDROID_TARGET_ARCH doesn't exist, use DEFAULT_ANDROID_ABIS to compute + // the architectures + const QByteArray line = qdevicepri.readLine().simplified(); + const bool isArchLine = line.startsWith("DEFAULT_ANDROID_TARGET_ARCH"); + const bool isAbisLine = line.startsWith("DEFAULT_ANDROID_ABIS"); + const bool isPlatformLine = line.startsWith("DEFAULT_ANDROID_PLATFORM"); + if (!isArchLine && !isPlatformLine && !isAbisLine) + continue; + const QList elems = line.split('='); + if (elems.size() != 2) + continue; + const QString rhs = QString::fromLatin1(elems.at(1).trimmed()); + if (isArchLine) { + info.archs << mapArch(rhs); + } else if (isAbisLine) { + const auto abis = rhs.split(QLatin1Char(' ')); + for (const QString &abi: abis) + info.archs << mapArch(abi); + } else { + info.platform = rhs; + } + } + return info; +} + +using QtInfoPerArch = QHash; +static QtInfoPerArch getQtAndroidInfo(const QString &qtSdkDir) +{ + QtInfoPerArch archs; + if (qtSdkDir.isEmpty()) + return archs; + + QStringList qtDirs(qtSdkDir); + const QStringList nameFilters{QStringLiteral("android_*"), QStringLiteral("android")}; + QDirIterator dit(qtSdkDir, nameFilters, QDir::Dirs); + while (dit.hasNext()) + qtDirs << dit.next(); + for (const auto &qtDir : qAsConst(qtDirs)) { + const QtAndroidInfo info = getInfoForQtDir(qtDir); + if (info.isValid()) { + for (const QString &arch: info.archs) + archs.insert(arch, info); + } + } + return archs; +} + +static QString maximumPlatform(const QString &platform1, const QString &platform2) +{ + if (platform1.isEmpty()) + return platform2; + if (platform2.isEmpty()) + return platform1; + static const QString prefix = qls("android-"); + const QString numberString1 = platform1.mid(prefix.size()); + const QString numberString2 = platform2.mid(prefix.size()); + bool ok; + const int value1 = numberString1.toInt(&ok); + if (!ok) { + qWarning("Ignoring malformed Android platform string '%s'.", qPrintable(platform1)); + return platform2; + } + const int value2 = numberString2.toInt(&ok); + if (!ok) { + qWarning("Ignoring malformed Android platform string '%s'.", qPrintable(platform2)); + return platform1; + } + return prefix + QString::number(std::max(value1, value2)); +} + +static QString getToolchainType(const QString &ndkDirPath) +{ + QFile sourceProperties(ndkDirPath + qls("/source.properties")); + if (!sourceProperties.open(QIODevice::ReadOnly)) + return QStringLiteral("gcc"); // <= r10 + while (!sourceProperties.atEnd()) { + const QByteArray curLine = sourceProperties.readLine().simplified(); + static const QByteArray prefix = "Pkg.Revision = "; + if (!curLine.startsWith(prefix)) + continue; + qbs::Version ndkVersion = qbs::Version::fromString( + QString::fromLatin1(curLine.mid(prefix.size()))); + if (!ndkVersion.isValid()) { + qWarning("Unexpected format of NDK revision string in '%s'", + qPrintable(sourceProperties.fileName())); + return QStringLiteral("clang"); + } + return qls(ndkVersion.majorVersion() >= 18 ? "clang" : "gcc"); + } + qWarning("No revision entry found in '%s'", qPrintable(sourceProperties.fileName())); + return QStringLiteral("clang"); +} + +static void setupNdk(qbs::Settings *settings, const QString &profileName, const QString &ndkDirPath, + const QString &qtSdkDirPath) +{ + if (!QDir(ndkDirPath).exists()) { + throw ErrorInfo(Tr::tr("NDK directory '%1' does not exist.") + .arg(QDir::toNativeSeparators(ndkDirPath))); + } + + Profile mainProfile(profileName, settings); + if (!ndkDirPath.isEmpty()) { + mainProfile.setValue(qls("Android.ndk.ndkDir"), QDir::cleanPath(ndkDirPath)); + mainProfile.setValue(qls("Android.sdk.ndkDir"), QDir::cleanPath(ndkDirPath)); + } + mainProfile.setValue(qls("qbs.toolchainType"), getToolchainType(ndkDirPath)); + const QStringList archs = expectedArchs(); + const QtInfoPerArch infoPerArch = getQtAndroidInfo(qtSdkDirPath); + const QStringList archsForProfile = infoPerArch.empty() + ? archs : QStringList(infoPerArch.keys()); + if (archsForProfile.size() == 1) + mainProfile.setValue(qls("qbs.architecture"), archsForProfile.front()); + else + mainProfile.setValue(qls("qbs.architectures"), archsForProfile); + QStringList qmakeFilePaths; + QString platform; + for (const QString &arch : archs) { + const QtAndroidInfo qtAndroidInfo = infoPerArch.value(arch); + if (!qtAndroidInfo.isValid()) + continue; + qmakeFilePaths << qtAndroidInfo.qmakePath; + platform = maximumPlatform(platform, qtAndroidInfo.platform); + } + if (!qmakeFilePaths.empty()) { + qmakeFilePaths.removeDuplicates(); + mainProfile.setValue(qls("moduleProviders.Qt.qmakeFilePaths"), qmakeFilePaths); + } + if (!platform.isEmpty()) + mainProfile.setValue(qls("Android.ndk.platform"), platform); +} + +void setupAndroid(Settings *settings, const QString &profileName, const QString &sdkDirPath, + const QString &ndkDirPath, const QString &qtSdkDirPath) +{ + setupSdk(settings, profileName, sdkDirPath); + setupNdk(settings, profileName, ndkDirPath, qtSdkDirPath); +} diff --git a/src/app/qbs-setup-android/android-setup.h b/src/app/qbs-setup-android/android-setup.h new file mode 100644 index 00000000..55aeedb2 --- /dev/null +++ b/src/app/qbs-setup-android/android-setup.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** 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 QBS_SETUP_ANDROID_SDKSETUP_H +#define QBS_SETUP_ANDROID_SDKSETUP_H + +#include + +namespace qbs { class Settings; } + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +void setupAndroid(qbs::Settings *settings, const QString &profileName, const QString &sdkDirPath, + const QString &ndkDirPath, const QString &qtSdkDirPath); + +#endif // Include guard. + diff --git a/src/app/qbs-setup-android/commandlineparser.cpp b/src/app/qbs-setup-android/commandlineparser.cpp new file mode 100644 index 00000000..c4ff0994 --- /dev/null +++ b/src/app/qbs-setup-android/commandlineparser.cpp @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** 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 "commandlineparser.h" + +#include +#include + +#include + +CommandLineParser::CommandLineParser() = default; + +using qbs::Internal::Tr; + +static QString helpOptionShort() { return QStringLiteral("-h"); } +static QString helpOptionLong() { return QStringLiteral("--help"); } +static QString settingsDirOption() { return QStringLiteral("--settings-dir"); } +static QString sdkDirOption() { return QStringLiteral("--sdk-dir"); } +static QString ndkDirOption() { return QStringLiteral("--ndk-dir"); } +static QString qtSdkDirOption() { return QStringLiteral("--qt-dir"); } +static QString systemOption() { return QStringLiteral("--system"); } + +void CommandLineParser::parse(const QStringList &commandLine) +{ + m_commandLine = commandLine; + Q_ASSERT(!m_commandLine.empty()); + m_command = QFileInfo(m_commandLine.takeFirst()).fileName(); + m_helpRequested = false; + m_sdkDir.clear(); + m_ndkDir.clear(); + m_profileName.clear(); + m_settingsDir.clear(); + + if (m_commandLine.empty()) + throwError(Tr::tr("No command-line arguments provided.")); + + while (!m_commandLine.empty()) { + const QString arg = m_commandLine.front(); + if (!arg.startsWith(QLatin1Char('-'))) + break; + m_commandLine.removeFirst(); + if (arg == helpOptionShort() || arg == helpOptionLong()) + m_helpRequested = true; + else if (arg == settingsDirOption()) + assignOptionArgument(settingsDirOption(), m_settingsDir); + else if (arg == systemOption()) + m_settingsScope = qbs::Settings::SystemScope; + else if (arg == sdkDirOption()) + assignOptionArgument(sdkDirOption(), m_sdkDir); + else if (arg == ndkDirOption()) + assignOptionArgument(ndkDirOption(), m_ndkDir); + else if (arg == qtSdkDirOption()) + assignOptionArgument(arg, m_qtSdkDir); + else + throwError(Tr::tr("Unknown option '%1'.").arg(arg)); + } + + if (m_helpRequested) { + if (!m_commandLine.empty()) + complainAboutExtraArguments(); + return; + } + + switch (m_commandLine.size()) { + case 0: + throwError(Tr::tr("No profile name supplied.")); + case 1: + m_profileName = m_commandLine.takeFirst(); + m_profileName.replace(QLatin1Char('.'), QLatin1Char('-')); + break; + default: + complainAboutExtraArguments(); + } +} + +void CommandLineParser::throwError(const QString &message) +{ + qbs::ErrorInfo error(Tr::tr("Syntax error: %1").arg(message)); + error.append(usageString()); + throw error; +} + +QString CommandLineParser::usageString() const +{ + QString s = Tr::tr("This tool creates qbs profiles from Android SDK and NDK installations.\n"); + s += Tr::tr("Usage:\n"); + s += Tr::tr(" %1 [%2 ] [%6] [%3 ] [%4 ] [%5 ] " + "\n") + .arg(m_command, settingsDirOption(), ndkDirOption(), sdkDirOption(), qtSdkDirOption(), systemOption()); + s += Tr::tr(" %1 %2|%3\n").arg(m_command, helpOptionShort(), helpOptionLong()); + s += Tr::tr("If an NDK path is given, the profile will be suitable for use with Android " + "projects that contain native C/C++ code.\n"); + s += Tr::tr("If a Qt path is also given, the profile will be suitable for developing " + "Qt applications for Android.\n"); + return s; +} + +void CommandLineParser::assignOptionArgument(const QString &option, QString &argument) +{ + if (m_commandLine.empty()) + throwError(Tr::tr("Option '%1' needs an argument.").arg(option)); + argument = m_commandLine.takeFirst(); + if (argument.isEmpty()) + throwError(Tr::tr("Argument for option '%1' must not be empty.").arg(option)); +} + +void CommandLineParser::complainAboutExtraArguments() +{ + throwError(Tr::tr("Extraneous command-line arguments '%1'.") + .arg(m_commandLine.join(QLatin1Char(' ')))); +} + diff --git a/src/app/qbs-setup-android/commandlineparser.h b/src/app/qbs-setup-android/commandlineparser.h new file mode 100644 index 00000000..47f42463 --- /dev/null +++ b/src/app/qbs-setup-android/commandlineparser.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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 QBS_SETUP_ANDROID_COMMANDLINEPARSER_H +#define QBS_SETUP_ANDROID_COMMANDLINEPARSER_H + +#include + +#include + +class CommandLineParser +{ +public: + CommandLineParser(); + + void parse(const QStringList &commandLine); + + bool helpRequested() const { return m_helpRequested; } + + QString sdkDir() const { return m_sdkDir; } + QString ndkDir() const { return m_ndkDir; } + QString qtSdkDir() const { return m_qtSdkDir; } + QString profileName() const { return m_profileName; } + QString settingsDir() const { return m_settingsDir; } + qbs::Settings::Scope settingsScope() const { return m_settingsScope; } + + QString usageString() const; + +private: + [[noreturn]] void throwError(const QString &message); + void assignOptionArgument(const QString &option, QString &argument); + [[noreturn]] void complainAboutExtraArguments(); + + bool m_helpRequested = false; + qbs::Settings::Scope m_settingsScope = qbs::Settings::UserScope; + QString m_sdkDir; + QString m_ndkDir; + QString m_qtSdkDir; + QString m_profileName; + QString m_settingsDir; + QStringList m_commandLine; + QString m_command; +}; + +#endif // Include guard. diff --git a/src/app/qbs-setup-android/main.cpp b/src/app/qbs-setup-android/main.cpp new file mode 100644 index 00000000..18e5dbe4 --- /dev/null +++ b/src/app/qbs-setup-android/main.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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 "commandlineparser.h" +#include "android-setup.h" + +#include +#include +#include + +#include + +#include +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + CommandLineParser clParser; + try { + clParser.parse(app.arguments()); + if (clParser.helpRequested()) { + std::cout << qPrintable(clParser.usageString()) << std::endl; + return EXIT_SUCCESS; + } + qbs::Settings settings(clParser.settingsDir()); + settings.setScopeForWriting(clParser.settingsScope()); + setupAndroid(&settings, clParser.profileName(), clParser.sdkDir(), clParser.ndkDir(), + clParser.qtSdkDir()); + } catch (const qbs::ErrorInfo &e) { + std::cerr << qPrintable(qbs::Internal::Tr::tr("Error: %1").arg(e.toString())) << std::endl; + return EXIT_FAILURE; + } +} diff --git a/src/app/qbs-setup-android/qbs-setup-android.exe.manifest b/src/app/qbs-setup-android/qbs-setup-android.exe.manifest new file mode 100644 index 00000000..6b425b15 --- /dev/null +++ b/src/app/qbs-setup-android/qbs-setup-android.exe.manifest @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/src/app/qbs-setup-android/qbs-setup-android.pro b/src/app/qbs-setup-android/qbs-setup-android.pro new file mode 100644 index 00000000..b5e57761 --- /dev/null +++ b/src/app/qbs-setup-android/qbs-setup-android.pro @@ -0,0 +1,12 @@ +include(../app.pri) + +TARGET = qbs-setup-android + +SOURCES += \ + android-setup.cpp \ + commandlineparser.cpp \ + main.cpp + +HEADERS += \ + android-setup.h \ + commandlineparser.h diff --git a/src/app/qbs-setup-android/qbs-setup-android.qbs b/src/app/qbs-setup-android/qbs-setup-android.qbs new file mode 100644 index 00000000..949c07c4 --- /dev/null +++ b/src/app/qbs-setup-android/qbs-setup-android.qbs @@ -0,0 +1,22 @@ +import qbs + +QbsApp { + name: "qbs-setup-android" + files: [ + "android-setup.cpp", + "android-setup.h", + "commandlineparser.cpp", + "commandlineparser.h", + "main.cpp", + ] + Group { + name: "MinGW specific files" + condition: qbs.toolchain.contains("mingw") + files: "qbs-setup-android.rc" + Group { + name: "qbs-setup-android manifest" + files: "qbs-setup-android.exe.manifest" + fileTags: [] // the manifest is referenced by the rc file + } + } +} diff --git a/src/app/qbs-setup-android/qbs-setup-android.rc b/src/app/qbs-setup-android/qbs-setup-android.rc new file mode 100644 index 00000000..20cd1ab1 --- /dev/null +++ b/src/app/qbs-setup-android/qbs-setup-android.rc @@ -0,0 +1,4 @@ +#define RT_MANIFEST 24 +#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 + +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "qbs-setup-android.exe.manifest" diff --git a/src/app/qbs-setup-qt/commandlineparser.cpp b/src/app/qbs-setup-qt/commandlineparser.cpp new file mode 100644 index 00000000..3bfe9bb6 --- /dev/null +++ b/src/app/qbs-setup-qt/commandlineparser.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** 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 "commandlineparser.h" + +#include +#include + +#include + +using qbs::Internal::Tr; + +static QString helpOptionShort() { return QStringLiteral("-h"); } +static QString helpOptionLong() { return QStringLiteral("--help"); } +static QString detectOption() { return QStringLiteral("--detect"); } +static QString settingsDirOption() { return QStringLiteral("--settings-dir"); } +static QString systemOption() { return QStringLiteral("--system"); } + +void CommandLineParser::parse(const QStringList &commandLine) +{ + m_commandLine = commandLine; + Q_ASSERT(!m_commandLine.empty()); + m_command = QFileInfo(m_commandLine.takeFirst()).fileName(); + m_helpRequested = false; + m_autoDetectionMode = false; + m_qmakePath.clear(); + m_profileName.clear(); + m_settingsDir.clear(); + + if (m_commandLine.empty()) + throwError(Tr::tr("No command-line arguments provided.")); + + while (!m_commandLine.empty()) { + const QString arg = m_commandLine.front(); + if (!arg.startsWith(QLatin1Char('-'))) + break; + m_commandLine.removeFirst(); + if (arg == helpOptionShort() || arg == helpOptionLong()) + m_helpRequested = true; + else if (arg == detectOption()) + m_autoDetectionMode = true; + else if (arg == systemOption()) + m_settingsScope = qbs::Settings::SystemScope; + else if (arg == settingsDirOption()) + assignOptionArgument(settingsDirOption(), m_settingsDir); + } + + if (m_helpRequested || m_autoDetectionMode) { + if (!m_commandLine.empty()) + complainAboutExtraArguments(); + return; + } + + switch (m_commandLine.size()) { + case 0: + case 1: + throwError(Tr::tr("Not enough command-line arguments provided.")); + case 2: + m_qmakePath = m_commandLine.at(0); + m_profileName = m_commandLine.at(1); + break; + default: + complainAboutExtraArguments(); + } +} + +void CommandLineParser::throwError(const QString &message) +{ + qbs::ErrorInfo error(Tr::tr("Syntax error: %1").arg(message)); + error.append(usageString()); + throw error; +} + +QString CommandLineParser::usageString() const +{ + QString s = Tr::tr("This tool creates qbs profiles from Qt versions.\n"); + s += Tr::tr("Usage:\n"); + s += Tr::tr(" %1 [%2 ] [%4] %3\n") + .arg(m_command, settingsDirOption(), detectOption(), systemOption()); + s += Tr::tr(" %1 [%2 ] [%4] \n") + .arg(m_command, settingsDirOption(), systemOption()); + s += Tr::tr(" %1 %2|%3\n").arg(m_command, helpOptionShort(), helpOptionLong()); + s += Tr::tr("The first form tries to auto-detect all known Qt versions, looking them up " + "via the PATH environment variable.\n"); + s += Tr::tr("The second form creates one profile for one Qt version."); + return s; +} + +void CommandLineParser::assignOptionArgument(const QString &option, QString &argument) +{ + if (m_commandLine.empty()) + throwError(Tr::tr("Option '%1' needs an argument.").arg(option)); + argument = m_commandLine.takeFirst(); + if (argument.isEmpty()) + throwError(Tr::tr("Argument for option '%1' must not be empty.").arg(option)); +} + +void CommandLineParser::complainAboutExtraArguments() +{ + throwError(Tr::tr("Extraneous command-line arguments '%1'.") + .arg(m_commandLine.join(QLatin1Char(' ')))); +} diff --git a/src/app/qbs-setup-qt/commandlineparser.h b/src/app/qbs-setup-qt/commandlineparser.h new file mode 100644 index 00000000..c766a9df --- /dev/null +++ b/src/app/qbs-setup-qt/commandlineparser.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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 QBS_SETUPTOOLCHAINS_COMMANDLINEPARSER_H +#define QBS_SETUPTOOLCHAINS_COMMANDLINEPARSER_H + +#include + +#include + +class CommandLineParser +{ +public: + void parse(const QStringList &commandLine); + + bool helpRequested() const { return m_helpRequested; } + bool autoDetectionMode() const { return m_autoDetectionMode; } + + QString qmakePath() const { return m_qmakePath; } + QString profileName() const { return m_profileName; } + QString settingsDir() const { return m_settingsDir; } + qbs::Settings::Scope settingsScope() const { return m_settingsScope; } + + QString usageString() const; + +private: + [[noreturn]] void throwError(const QString &message); + void assignOptionArgument(const QString &option, QString &argument); + [[noreturn]] void complainAboutExtraArguments(); + + bool m_helpRequested = false; + bool m_autoDetectionMode = false; + qbs::Settings::Scope m_settingsScope = qbs::Settings::UserScope; + QString m_qmakePath; + QString m_profileName; + QString m_settingsDir; + QStringList m_commandLine; + QString m_command; +}; + +#endif // Include guard diff --git a/src/app/qbs-setup-qt/main.cpp b/src/app/qbs-setup-qt/main.cpp new file mode 100644 index 00000000..bef95eee --- /dev/null +++ b/src/app/qbs-setup-qt/main.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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 "setupqt.h" + +#include "commandlineparser.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include + +using namespace qbs; +using Internal::Tr; + +int main(int argc, char *argv[]) +{ + QCoreApplication application(argc, argv); + + try { + CommandLineParser clParser; + clParser.parse(application.arguments()); + + if (clParser.helpRequested()) { + std::cout << qPrintable(clParser.usageString()) << std::endl; + return EXIT_SUCCESS; + } + + Settings settings(clParser.settingsDir()); + settings.setScopeForWriting(clParser.settingsScope()); + + if (clParser.autoDetectionMode()) { + // search all Qt's in path and dump their settings + const std::vector qtEnvironments = SetupQt::fetchEnvironments(); + if (qtEnvironments.empty()) { + std::cout << qPrintable(Tr::tr("No Qt installations detected. " + "No profiles created.")) + << std::endl; + } + for (const QtEnvironment &qtEnvironment : qtEnvironments) { + QString profileName = QLatin1String("qt-") + qtEnvironment.qtVersion.toString(); + if (SetupQt::checkIfMoreThanOneQtWithTheSameVersion(qtEnvironment.qtVersion, qtEnvironments)) { + QStringList prefixPathParts = QFileInfo(qtEnvironment.qmakeFilePath).path() + .split(QLatin1Char('/'), QBS_SKIP_EMPTY_PARTS); + if (!prefixPathParts.empty()) + profileName += QLatin1String("-") + prefixPathParts.last(); + } + SetupQt::saveToQbsSettings(profileName, qtEnvironment, &settings); + } + return EXIT_SUCCESS; + } + + if (!SetupQt::isQMakePathValid(clParser.qmakePath())) { + std::cerr << qPrintable(Tr::tr("'%1' does not seem to be a qmake executable.") + .arg(clParser.qmakePath())) << std::endl; + return EXIT_FAILURE; + } + + const QtEnvironment qtEnvironment = SetupQt::fetchEnvironment(clParser.qmakePath()); + QString profileName = clParser.profileName(); + profileName.replace(QLatin1Char('.'), QLatin1Char('-')); + SetupQt::saveToQbsSettings(profileName, qtEnvironment, &settings); + return EXIT_SUCCESS; + } catch (const ErrorInfo &e) { + std::cerr << qPrintable(e.toString()) << std::endl; + return EXIT_FAILURE; + } +} diff --git a/src/app/qbs-setup-qt/qbs-setup-qt.exe.manifest b/src/app/qbs-setup-qt/qbs-setup-qt.exe.manifest new file mode 100644 index 00000000..a0b8dbac --- /dev/null +++ b/src/app/qbs-setup-qt/qbs-setup-qt.exe.manifest @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/src/app/qbs-setup-qt/qbs-setup-qt.pro b/src/app/qbs-setup-qt/qbs-setup-qt.pro new file mode 100644 index 00000000..a5694d6b --- /dev/null +++ b/src/app/qbs-setup-qt/qbs-setup-qt.pro @@ -0,0 +1,16 @@ +include(../app.pri) + +TARGET = qbs-setup-qt + +SOURCES += \ + commandlineparser.cpp \ + main.cpp \ + setupqt.cpp + +HEADERS += \ + commandlineparser.h \ + setupqt.h + +mingw { + RC_FILE = qbs-setup-qt.rc +} diff --git a/src/app/qbs-setup-qt/qbs-setup-qt.qbs b/src/app/qbs-setup-qt/qbs-setup-qt.qbs new file mode 100644 index 00000000..44f2b7f0 --- /dev/null +++ b/src/app/qbs-setup-qt/qbs-setup-qt.qbs @@ -0,0 +1,23 @@ +import qbs 1.0 + +QbsApp { + name: "qbs-setup-qt" + files: [ + "commandlineparser.cpp", + "commandlineparser.h", + "main.cpp", + "setupqt.cpp", + "setupqt.h" + ] + Group { + name: "MinGW specific files" + condition: qbs.toolchain.contains("mingw") + files: "qbs-setup-qt.rc" + Group { + name: "qbs-setup-qt manifest" + files: "qbs-setup-qt.exe.manifest" + fileTags: [] // the manifest is referenced by the rc file + } + } +} + diff --git a/src/app/qbs-setup-qt/qbs-setup-qt.rc b/src/app/qbs-setup-qt/qbs-setup-qt.rc new file mode 100644 index 00000000..ad2507e4 --- /dev/null +++ b/src/app/qbs-setup-qt/qbs-setup-qt.rc @@ -0,0 +1,4 @@ +#define RT_MANIFEST 24 +#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 + +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "qbs-setup-qt.exe.manifest" diff --git a/src/app/qbs-setup-qt/setupqt.cpp b/src/app/qbs-setup-qt/setupqt.cpp new file mode 100644 index 00000000..07e1a81b --- /dev/null +++ b/src/app/qbs-setup-qt/setupqt.cpp @@ -0,0 +1,391 @@ +/**************************************************************************** +** +** 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 "setupqt.h" + +#include "../shared/logging/consolelogger.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace qbs { +using Internal::none_of; +using Internal::contains; +using Internal::HostOsInfo; +using Internal::Tr; + +static QStringList qmakeExecutableNames() +{ + const QString baseName = HostOsInfo::appendExecutableSuffix(QStringLiteral("qmake")); + QStringList lst(baseName); + if (HostOsInfo::isLinuxHost()) { + // Some distributions ship binaries called qmake-qt5 or qmake-qt4. + lst << baseName + QLatin1String("-qt5") << baseName + QLatin1String("-qt4"); + } + return lst; +} + +static QStringList collectQmakePaths() +{ + const QStringList qmakeExeNames = qmakeExecutableNames(); + QStringList qmakePaths; + QByteArray environmentPath = qgetenv("PATH"); + const QList environmentPaths + = environmentPath.split(HostOsInfo::pathListSeparator().toLatin1()); + for (const QByteArray &path : environmentPaths) { + for (const QString &qmakeExecutableName : qmakeExeNames) { + QFileInfo pathFileInfo(QDir(QLatin1String(path)), qmakeExecutableName); + if (pathFileInfo.exists()) { + QString qmakePath = pathFileInfo.absoluteFilePath(); + if (!qmakePaths.contains(qmakePath)) + qmakePaths.push_back(qmakePath); + } + } + } + + return qmakePaths; +} + +bool SetupQt::isQMakePathValid(const QString &qmakePath) +{ + QFileInfo qmakeFileInfo(qmakePath); + return qmakeFileInfo.exists() && qmakeFileInfo.isFile() && qmakeFileInfo.isExecutable(); +} + +std::vector SetupQt::fetchEnvironments() +{ + std::vector qtEnvironments; + const auto qmakePaths = collectQmakePaths(); + for (const QString &qmakePath : qmakePaths) { + const QtEnvironment env = fetchEnvironment(qmakePath); + if (none_of(qtEnvironments, [&env](const QtEnvironment &otherEnv) { + return env.qmakeFilePath == otherEnv.qmakeFilePath; + })) { + qtEnvironments.push_back(env); + } + } + return qtEnvironments; +} + +// These functions work only for Qt from installer. +static QStringList qbsToolchainFromDirName(const QString &dir) +{ + if (dir.startsWith(QLatin1String("msvc"))) + return {QStringLiteral("msvc")}; + if (dir.startsWith(QLatin1String("mingw"))) + return {QStringLiteral("mingw"), QStringLiteral("gcc")}; + if (dir.startsWith(QLatin1String("clang"))) + return {QStringLiteral("clang"), QStringLiteral("llvm"), QStringLiteral("gcc")}; + if (dir.startsWith(QLatin1String("gcc"))) + return {QStringLiteral("gcc")}; + return {}; +} + +static Version msvcVersionFromDirName(const QString &dir) +{ + static const std::regex regexp("^msvc(\\d\\d\\d\\d).*$"); + std::smatch match; + const std::string dirString = dir.toStdString(); + if (!std::regex_match(dirString, match, regexp)) + return Version{}; + QMap mapping{ + std::make_pair("2005", "14"), std::make_pair("2008", "15"), std::make_pair("2010", "16"), + std::make_pair("2012", "17"), std::make_pair("2013", "18"), std::make_pair("2015", "19"), + std::make_pair("2017", "19.1"), std::make_pair("2019", "19.2") + }; + return Version::fromString(QString::fromStdString(mapping.value(match[1].str()))); +} + +static QString archFromDirName(const QString &dir) +{ + static const std::regex regexp("^[^_]+_(.*).*$"); + std::smatch match; + const std::string dirString = dir.toStdString(); + if (!std::regex_match(dirString, match, regexp)) + return {}; + const QString arch = QString::fromStdString(match[1]); + if (arch == QLatin1String("32")) + return QStringLiteral("x86"); + if (arch == QLatin1String("64")) + return QStringLiteral("x86_64"); + if (arch.contains(QLatin1String("arm64"))) + return QStringLiteral("arm64"); + return arch; +} + +static QString platformFromDirName(const QString &dir) +{ + if (dir.startsWith(QLatin1String("android"))) + return QStringLiteral("android"); + if (dir == QLatin1String("Boot2Qt")) + return QStringLiteral("linux"); + return QString::fromStdString(HostOsInfo::hostOSIdentifier()); +} + +QtEnvironment SetupQt::fetchEnvironment(const QString &qmakePath) +{ + QtEnvironment env; + env.qmakeFilePath = qmakePath; + QDir qtDir = QFileInfo(qmakePath).dir(); + if (qtDir.dirName() == QLatin1String("bin")) { + qtDir.cdUp(); + env.qbsToolchain = qbsToolchainFromDirName(qtDir.dirName()); + env.msvcVersion = msvcVersionFromDirName(qtDir.dirName()); + env.architecture = archFromDirName(qtDir.dirName()); + if (env.msvcVersion.isValid() && env.architecture.isEmpty()) + env.architecture = QStringLiteral("x86"); + env.targetPlatform = platformFromDirName(qtDir.dirName()); + qtDir.cdUp(); + env.qtVersion = Version::fromString(qtDir.dirName()); + } + return env; +} + +static bool isToolchainProfile(const Profile &profile) +{ + const auto actual = Internal::Set::fromList( + profile.allKeys(Profile::KeySelectionRecursive)); + Internal::Set expected{ QStringLiteral("qbs.toolchainType") }; + if (HostOsInfo::isMacosHost()) + expected.insert(QStringLiteral("qbs.targetPlatform")); // match only Xcode profiles + return Internal::Set(actual).unite(expected) == actual; +} + +static bool isQtProfile(const Profile &profile) +{ + if (!profile.value(QStringLiteral("moduleProviders.Qt.qmakeFilePaths")).toStringList() + .empty()) { + return true; + } + + // For Profiles created with setup-qt < 5.13. + const QStringList searchPaths + = profile.value(QStringLiteral("preferences.qbsSearchPaths")).toStringList(); + return std::any_of(searchPaths.cbegin(), searchPaths.cend(), [] (const QString &path) { + return QFileInfo(path + QStringLiteral("/modules/Qt")).isDir(); + }); +} + +template bool areProfilePropertiesIncompatible(const T &set1, const T &set2) +{ + // Two objects are only considered incompatible if they are both non empty and compare inequal + // This logic is used for comparing target OS, toolchain lists, and architectures + return set1.size() > 0 && set2.size() > 0 && set1 != set2; +} + +enum Match { MatchFull, MatchPartial, MatchNone }; + +static Match compatibility(const QtEnvironment &env, const Profile &toolchainProfile) +{ + Match match = MatchFull; + + const auto toolchainType = + toolchainProfile.value(QStringLiteral("qbs.toolchainType")).toString(); + const auto toolchain = !toolchainType.isEmpty() + ? canonicalToolchain(toolchainType) + : toolchainProfile.value(QStringLiteral("qbs.toolchain")).toStringList(); + + const auto toolchainNames = Internal::Set::fromList(toolchain); + const auto qtToolchainNames = Internal::Set::fromList(env.qbsToolchain); + if (areProfilePropertiesIncompatible(toolchainNames, qtToolchainNames)) { + auto intersection = toolchainNames; + intersection.intersect(qtToolchainNames); + if (!intersection.empty()) + match = MatchPartial; + else + return MatchNone; + } + + const auto targetPlatform = toolchainProfile.value( + QStringLiteral("qbs.targetPlatform")).toString(); + if (!targetPlatform.isEmpty() && targetPlatform != env.targetPlatform) + return MatchNone; + + const QString toolchainArchitecture = toolchainProfile.value(QStringLiteral("qbs.architecture")) + .toString(); + if (areProfilePropertiesIncompatible(canonicalArchitecture(env.architecture), + canonicalArchitecture(toolchainArchitecture))) + return MatchNone; + + if (env.msvcVersion.isValid()) { + // We want to know for sure that MSVC compiler versions match, + // because it's especially important for this toolchain + const Version compilerVersion = Version::fromString( + toolchainProfile.value(QStringLiteral("cpp.compilerVersion")).toString()); + + static const Version vs2017Version{19, 1}; + if (env.msvcVersion >= vs2017Version) { + if (env.msvcVersion.majorVersion() != compilerVersion.majorVersion() + || compilerVersion < vs2017Version) { + return MatchNone; + } + } else if (env.msvcVersion.majorVersion() != compilerVersion.majorVersion() + || env.msvcVersion.minorVersion() != compilerVersion.minorVersion()) { + return MatchNone; + } + } + + return match; +} + +QString profileNameWithoutHostArch(const QString &profileName) +{ + QString result; + int i = profileName.indexOf(QLatin1Char('-')); + if (i == -1) + return result; + ++i; + int j = profileName.indexOf(QLatin1Char('_'), i); + if (j == -1) + return result; + result = profileName.mid(0, i) + profileName.mid(j + 1); + return result; +} + +// "Compressing" MSVC profiles means that if MSVC2017-x64 and MSVC2017-x86_x64 fully match, +// then we drop the crosscompiling toolchain MSVC2017-x86_x64. +static void compressMsvcProfiles(QStringList &profiles) +{ + auto it = std::remove_if(profiles.begin(), profiles.end(), + [&profiles] (const QString &profileName) { + int idx = profileName.indexOf(QLatin1Char('_')); + if (idx == -1) + return false; + return contains(profiles, profileNameWithoutHostArch(profileName)); + }); + if (it != profiles.end()) + profiles.erase(it, profiles.end()); +} + +void SetupQt::saveToQbsSettings(const QString &qtVersionName, + const QtEnvironment &qtEnvironment, + Settings *settings) +{ + const QString cleanQtVersionName = Profile::cleanName(qtVersionName); + QString msg = QCoreApplication::translate("SetupQt", "Creating profile '%1'.") + .arg(cleanQtVersionName); + printf("%s\n", qPrintable(msg)); + + Profile profile(cleanQtVersionName, settings); + profile.removeProfile(); + profile.setValue(QStringLiteral("moduleProviders.Qt.qmakeFilePaths"), + QStringList(qtEnvironment.qmakeFilePath)); + if (!profile.baseProfile().isEmpty()) + return; + if (isToolchainProfile(profile)) + return; + + QStringList fullMatches; + QStringList partialMatches; + const auto profileNames = settings->profiles(); + for (const QString &profileName : profileNames) { + const Profile otherProfile(profileName, settings); + if (profileName == profile.name() + || !isToolchainProfile(otherProfile) + || isQtProfile(otherProfile)) + continue; + + switch (compatibility(qtEnvironment, otherProfile)) { + case MatchFull: + fullMatches << profileName; + break; + case MatchPartial: + partialMatches << profileName; + break; + default: + break; + } + } + + if (fullMatches.size() > 1) + compressMsvcProfiles(fullMatches); + + QString bestMatch; + if (fullMatches.size() == 1) + bestMatch = fullMatches.front(); + else if (fullMatches.empty() && partialMatches.size() == 1) + bestMatch = partialMatches.front(); + if (bestMatch.isEmpty()) { + QString message = Tr::tr("You may want to set up toolchain information " + "for the generated Qt profile. "); + if (!fullMatches.empty() || !partialMatches.empty()) { + message += Tr::tr("Consider setting one of these profiles as this profile's base " + "profile: %1.").arg((fullMatches + partialMatches) + .join(QLatin1String(", "))); + } + qbsInfo() << message; + } else { + profile.setBaseProfile(bestMatch); + qbsInfo() << Tr::tr("Setting profile '%1' as the base profile for this profile.") + .arg(bestMatch); + } +} + +bool SetupQt::checkIfMoreThanOneQtWithTheSameVersion(const Version &qtVersion, + const std::vector &qtEnvironments) +{ + bool foundOneVersion = false; + for (const QtEnvironment &qtEnvironment : qtEnvironments) { + if (qtEnvironment.qtVersion == qtVersion) { + if (foundOneVersion) + return true; + foundOneVersion = true; + } + } + + return false; +} + +} // namespace qbs diff --git a/src/app/qbs-setup-qt/setupqt.h b/src/app/qbs-setup-qt/setupqt.h new file mode 100644 index 00000000..c10ae637 --- /dev/null +++ b/src/app/qbs-setup-qt/setupqt.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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 QBS_SETUPQT_H +#define QBS_SETUPQT_H + +#include +#include + +#include +#include + +#include + +namespace qbs { + +class Settings; + +class QtEnvironment +{ +public: + QString qmakeFilePath; + QStringList qbsToolchain; + QString architecture; + QString targetPlatform; + Version qtVersion; + Version msvcVersion; +}; + +class SetupQt +{ + Q_DECLARE_TR_FUNCTIONS(SetupQt) +public: + static bool isQMakePathValid(const QString &qmakePath); + static std::vector fetchEnvironments(); + static QtEnvironment fetchEnvironment(const QString &qmakePath); + static bool checkIfMoreThanOneQtWithTheSameVersion(const Version &qtVersion, + const std::vector &qtEnvironments); + static void saveToQbsSettings(const QString &qtVersionName, const QtEnvironment &qtEnvironment, + Settings *settings); +}; + +} // namespace qbs + +#endif // QBS_SETUPQT_H diff --git a/src/app/qbs-setup-toolchains/clangclprobe.cpp b/src/app/qbs-setup-toolchains/clangclprobe.cpp new file mode 100644 index 00000000..d1a3a9ac --- /dev/null +++ b/src/app/qbs-setup-toolchains/clangclprobe.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +** Contact: http://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 "clangclprobe.h" +#include "msvcprobe.h" +#include "probe.h" + +#include "../shared/logging/consolelogger.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using qbs::Settings; +using qbs::Profile; +using qbs::Internal::ClangClInfo; +using qbs::Internal::HostOsInfo; + +using qbs::Internal::Tr; + +namespace { + +Profile createProfileHelper( + Settings *settings, + const QString &profileName, + const QString &toolchainInstallPath, + const QString &vcvarsallPath, + const QString &architecture) +{ + Profile profile(profileName, settings); + profile.removeProfile(); + profile.setValue(QStringLiteral("qbs.architecture"), architecture); + profile.setValue(QStringLiteral("qbs.toolchainType"), QStringLiteral("clang-cl")); + profile.setValue(QStringLiteral("cpp.toolchainInstallPath"), toolchainInstallPath); + profile.setValue(QStringLiteral("cpp.vcvarsallPath"), vcvarsallPath); + qbsInfo() << Tr::tr("Profile '%1' created for '%2'.") + .arg(profile.name(), QDir::toNativeSeparators(toolchainInstallPath)); + return profile; +} + +QString findClangCl() +{ + const auto compilerName = HostOsInfo::appendExecutableSuffix(QStringLiteral("clang-cl")); + const auto compilerFromPath = findExecutable(compilerName); + if (!compilerFromPath.isEmpty()) + return compilerFromPath; + + return {}; +} + +} // namespace + +void createClangClProfile(const QFileInfo &compiler, Settings *settings, + const QString &profileName) +{ + const auto clangCl = ClangClInfo::fromCompilerFilePath( + compiler.filePath(), ConsoleLogger::instance()); + if (clangCl.isEmpty()) + return; + const auto hostArch = QString::fromStdString(HostOsInfo::hostOSArchitecture()); + createProfileHelper( + settings, profileName, clangCl.toolchainInstallPath, clangCl.vcvarsallPath, hostArch); +} + +/*! + \brief Creates a clang-cl profile based on auto-detected vsversion. + \internal +*/ +void clangClProbe(Settings *settings, std::vector &profiles) +{ + const auto compilerName = QStringLiteral("clang-cl"); + qbsInfo() << Tr::tr("Trying to detect %1...").arg(compilerName); + const auto clangCls = ClangClInfo::installedCompilers( + {findClangCl()}, ConsoleLogger::instance()); + if (clangCls.empty()) { + qbsInfo() << Tr::tr("%1 was not found.").arg(compilerName); + return; + } + + const auto clangCl = clangCls.front(); + const QString architectures[] = { + QStringLiteral("x86_64"), + QStringLiteral("x86") + }; + for (const auto &arch: architectures) { + const auto profileName = QStringLiteral("clang-cl-%1").arg(arch); + auto profile = createProfileHelper( + settings, profileName, clangCl.toolchainInstallPath, clangCl.vcvarsallPath, arch); + profiles.push_back(std::move(profile)); + } +} diff --git a/src/app/qbs-setup-toolchains/clangclprobe.h b/src/app/qbs-setup-toolchains/clangclprobe.h new file mode 100644 index 00000000..2de1c0a6 --- /dev/null +++ b/src/app/qbs-setup-toolchains/clangclprobe.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +** Contact: http://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_SETUPTOOLCHAINS_CLANGCLPROBE_H +#define QBS_SETUPTOOLCHAINS_CLANGCLPROBE_H + +#include + +#include + +QT_BEGIN_NAMESPACE +class QFileInfo; +QT_END_NAMESPACE + +namespace qbs { +class Profile; +class Settings; +} + +void createClangClProfile(const QFileInfo &compiler, qbs::Settings *settings, + const QString &profileName); + +void clangClProbe(qbs::Settings *settings, std::vector &profiles); + +#endif // Header guard diff --git a/src/app/qbs-setup-toolchains/commandlineparser.cpp b/src/app/qbs-setup-toolchains/commandlineparser.cpp new file mode 100644 index 00000000..bf3937a2 --- /dev/null +++ b/src/app/qbs-setup-toolchains/commandlineparser.cpp @@ -0,0 +1,145 @@ +/**************************************************************************** +** +** 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 "commandlineparser.h" + +#include +#include + +#include + +using qbs::Internal::Tr; + +static QString helpOptionShort() { return QStringLiteral("-h"); } +static QString helpOptionLong() { return QStringLiteral("--help"); } +static QString detectOption() { return QStringLiteral("--detect"); } +static QString typeOption() { return QStringLiteral("--type"); } +static QString settingsDirOption() { return QStringLiteral("--settings-dir"); } +static QString systemOption() { return QStringLiteral("--system"); } + +void CommandLineParser::parse(const QStringList &commandLine) +{ + m_commandLine = commandLine; + Q_ASSERT(!m_commandLine.empty()); + m_command = QFileInfo(m_commandLine.takeFirst()).fileName(); + m_helpRequested = false; + m_autoDetectionMode = false; + m_compilerPath.clear(); + m_toolchainType.clear(); + m_profileName.clear(); + m_settingsDir.clear(); + + if (m_commandLine.empty()) + throwError(Tr::tr("No command-line arguments provided.")); + + while (!m_commandLine.empty()) { + const QString arg = m_commandLine.front(); + if (!arg.startsWith(QLatin1Char('-'))) + break; + m_commandLine.removeFirst(); + if (arg == helpOptionShort() || arg == helpOptionLong()) + m_helpRequested = true; + else if (arg == detectOption()) + m_autoDetectionMode = true; + else if (arg == systemOption()) + m_settingsScope = qbs::Settings::SystemScope; + else if (arg == typeOption()) + assignOptionArgument(typeOption(), m_toolchainType); + else if (arg == settingsDirOption()) + assignOptionArgument(settingsDirOption(), m_settingsDir); + } + + if (m_helpRequested || m_autoDetectionMode) { + if (!m_commandLine.empty()) + complainAboutExtraArguments(); + return; + } + + switch (m_commandLine.size()) { + case 0: + case 1: + throwError(Tr::tr("Not enough command-line arguments provided.")); + case 2: + m_compilerPath = m_commandLine.at(0); + m_profileName = m_commandLine.at(1); + m_profileName.replace(QLatin1Char('.'), QLatin1Char('-')); + break; + default: + complainAboutExtraArguments(); + } +} + +void CommandLineParser::throwError(const QString &message) +{ + qbs::ErrorInfo error(Tr::tr("Syntax error: %1").arg(message)); + error.append(usageString()); + throw error; +} + +QString CommandLineParser::usageString() const +{ + QString s = Tr::tr("This tool creates qbs profiles from toolchains.\n"); + s += Tr::tr("Usage:\n"); + s += Tr::tr(" %1 [%2 ] [%4] %3\n") + .arg(m_command, settingsDirOption(), detectOption(), systemOption()); + s += Tr::tr(" %1 [%3 ] [%4] [%2 ] " + " \n") + .arg(m_command, typeOption(), settingsDirOption(), systemOption()); + s += Tr::tr(" %1 %2|%3\n").arg(m_command, helpOptionShort(), helpOptionLong()); + s += Tr::tr("The first form tries to auto-detect all known toolchains, looking them up " + "via the PATH environment variable.\n"); + s += Tr::tr("The second form creates one profile for one toolchain. It will attempt " + "to find out the toolchain type automatically.\nIn case the compiler has " + "an unusual file name, you may need to provide the '--type' option."); + return s; +} + +void CommandLineParser::assignOptionArgument(const QString &option, QString &argument) +{ + if (m_commandLine.empty()) + throwError(Tr::tr("Option '%1' needs an argument.").arg(option)); + argument = m_commandLine.takeFirst(); + if (argument.isEmpty()) + throwError(Tr::tr("Argument for option '%1' must not be empty.").arg(option)); +} + +void CommandLineParser::complainAboutExtraArguments() +{ + throwError(Tr::tr("Extraneous command-line arguments '%1'.") + .arg(m_commandLine.join(QLatin1Char(' ')))); +} diff --git a/src/app/qbs-setup-toolchains/commandlineparser.h b/src/app/qbs-setup-toolchains/commandlineparser.h new file mode 100644 index 00000000..0616f068 --- /dev/null +++ b/src/app/qbs-setup-toolchains/commandlineparser.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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 QBS_SETUPTOOLCHAINS_COMMANDLINEPARSER_H +#define QBS_SETUPTOOLCHAINS_COMMANDLINEPARSER_H + +#include + +#include + +class CommandLineParser +{ +public: + void parse(const QStringList &commandLine); + + bool helpRequested() const { return m_helpRequested; } + bool autoDetectionMode() const { return m_autoDetectionMode; } + + QString compilerPath() const { return m_compilerPath; } + QString toolchainType() const { return m_toolchainType; } + QString profileName() const { return m_profileName; } + QString settingsDir() const { return m_settingsDir; } + qbs::Settings::Scope settingsScope() const { return m_settingsScope; } + + QString usageString() const; + +private: + [[noreturn]] void throwError(const QString &message); + void assignOptionArgument(const QString &option, QString &argument); + [[noreturn]] void complainAboutExtraArguments(); + + bool m_helpRequested = false; + bool m_autoDetectionMode = false; + qbs::Settings::Scope m_settingsScope = qbs::Settings::UserScope; + QString m_compilerPath; + QString m_toolchainType; + QString m_profileName; + QString m_settingsDir; + QStringList m_commandLine; + QString m_command; +}; + +#endif // Header guard diff --git a/src/app/qbs-setup-toolchains/gccprobe.cpp b/src/app/qbs-setup-toolchains/gccprobe.cpp new file mode 100644 index 00000000..c521a3fe --- /dev/null +++ b/src/app/qbs-setup-toolchains/gccprobe.cpp @@ -0,0 +1,596 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "probe.h" +#include "gccprobe.h" + +#include "../shared/logging/consolelogger.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace qbs; +using Internal::HostOsInfo; +using Internal::Tr; + +constexpr char kUninstallRegistryKey[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\" \ + "Windows\\CurrentVersion\\Uninstall\\"; + +static QString qsystem(const QString &exe, const QStringList &args = QStringList()) +{ + QProcess p; + p.setProcessChannelMode(QProcess::MergedChannels); + p.start(exe, args); + if (!p.waitForStarted()) { + throw qbs::ErrorInfo(Tr::tr("Failed to start compiler '%1': %2") + .arg(exe, p.errorString())); + } + if (!p.waitForFinished(-1) || p.exitCode() != 0) + throw qbs::ErrorInfo(Tr::tr("Failed to run compiler '%1': %2") + .arg(exe, p.errorString())); + return QString::fromLocal8Bit(p.readAll()); +} + +static QStringList validMinGWMachines() +{ + // List of MinGW machine names (gcc -dumpmachine) recognized by Qbs + return {QStringLiteral("mingw32"), + QStringLiteral("mingw64"), + QStringLiteral("i686-w64-mingw32"), + QStringLiteral("x86_64-w64-mingw32"), + QStringLiteral("i686-w64-mingw32.shared"), + QStringLiteral("x86_64-w64-mingw32.shared"), + QStringLiteral("i686-w64-mingw32.static"), + QStringLiteral("x86_64-w64-mingw32.static"), + QStringLiteral("i586-mingw32msvc"), + QStringLiteral("amd64-mingw32msvc")}; +} + +static QString gccMachineName(const QFileInfo &compiler) +{ + return qsystem(compiler.absoluteFilePath(), {QStringLiteral("-dumpmachine")}) + .trimmed(); +} + +static QStringList standardCompilerFileNames() +{ + return {HostOsInfo::appendExecutableSuffix(QStringLiteral("gcc")), + HostOsInfo::appendExecutableSuffix(QStringLiteral("g++")), + HostOsInfo::appendExecutableSuffix(QStringLiteral("clang")), + HostOsInfo::appendExecutableSuffix(QStringLiteral("clang++"))}; +} + +class ToolchainDetails +{ +public: + explicit ToolchainDetails(const QFileInfo &compiler) + { + auto baseName = HostOsInfo::stripExecutableSuffix(compiler.fileName()); + // Extract the version sub-string if it exists. We assume that a version + // sub-string located after the compiler prefix && suffix. E.g. this code + // parses a version from the compiler names, like this: + // - avr-gcc-4.9.2.exe + // - arm-none-eabi-gcc-8.2.1 + // - rl78-elf-gcc-4.9.2.201902-GNURL78 + const QRegularExpression re(QLatin1String("-(\\d+|\\d+\\.\\d+|" \ + "\\d+\\.\\d+\\.\\d+|" \ + "\\d+\\.\\d+\\.\\d+\\.\\d+)" \ + "[-[0-9a-zA-Z]*]?$")); + const QRegularExpressionMatch match = re.match(baseName); + if (match.hasMatch()) { + version = match.captured(1); + baseName = baseName.left(match.capturedStart()); + } + const auto dashIndex = baseName.lastIndexOf(QLatin1Char('-')); + suffix = baseName.mid(dashIndex + 1); + prefix = baseName.left(dashIndex + 1); + } + + QString prefix; + QString suffix; + QString version; +}; + +static void setCommonProperties(Profile &profile, const QFileInfo &compiler, + const QString &toolchainType, const ToolchainDetails &details) +{ + if (toolchainType == QStringLiteral("mingw")) + profile.setValue(QStringLiteral("qbs.targetPlatform"), + QStringLiteral("windows")); + + if (!details.prefix.isEmpty()) + profile.setValue(QStringLiteral("cpp.toolchainPrefix"), details.prefix); + + profile.setValue(QStringLiteral("cpp.toolchainInstallPath"), + compiler.absolutePath()); + profile.setValue(QStringLiteral("qbs.toolchainType"), toolchainType); + + if (!standardCompilerFileNames().contains( + HostOsInfo::appendExecutableSuffix(details.suffix))) { + qWarning("%s", qPrintable( + QStringLiteral("'%1' is not a standard compiler file name; " + "you must set the cpp.cCompilerName and " + "cpp.cxxCompilerName properties of this profile " + "manually").arg(compiler.fileName()))); + } +} + +class ToolPathSetup +{ +public: + explicit ToolPathSetup(Profile *profile, QString path, ToolchainDetails details) + : m_profile(profile), + m_compilerDirPath(std::move(path)), + m_details(std::move(details)) + { + } + + void apply(const QString &toolName, const QString &propertyName) const + { + // Check for full tool name at first (includes suffix and version). + QString filePath = toolFilePath(toolName, UseFullToolName); + if (filePath.isEmpty()) { + // Check for base tool name at second (without of suffix and version). + filePath = toolFilePath(toolName, UseBaseToolName); + if (filePath.isEmpty()) { + // Check for short tool name at third (only a tool name). + filePath = toolFilePath(toolName, UseOnlyShortToolName); + } + } + + if (filePath.isEmpty()) { + qWarning("%s", qPrintable( + QStringLiteral("'%1' not found in '%2'. " + "Qbs will try to find it in PATH at build time.") + .arg(toolName, m_compilerDirPath))); + } else { + m_profile->setValue(propertyName, filePath); + } + } + +private: + enum ToolNameParts : quint8 { + UseOnlyShortToolName = 0x0, + UseToolPrefix = 0x01, + UseToolSuffix = 0x02, + UseToolVersion = 0x04, + UseFullToolName = UseToolPrefix | UseToolSuffix | UseToolVersion, + UseBaseToolName = UseToolPrefix, + }; + + QString toolFilePath(const QString &toolName, int parts) const + { + QString fileName; + if ((parts & UseToolPrefix) && !m_details.prefix.isEmpty()) + fileName += m_details.prefix; + if ((parts & UseToolSuffix) && !m_details.suffix.isEmpty()) + fileName += m_details.suffix + QLatin1Char('-'); + fileName += toolName; + if ((parts & UseToolVersion) && !m_details.version.isEmpty()) + fileName += QLatin1Char('-') + m_details.version; + + fileName = HostOsInfo::appendExecutableSuffix(fileName); + QString filePath = QDir(m_compilerDirPath).absoluteFilePath(fileName); + if (QFile::exists(filePath)) + return filePath; + return {}; + } + + Profile * const m_profile; + QString m_compilerDirPath; + ToolchainDetails m_details; +}; + +static bool doesProfileTargetOS(const Profile &profile, const QString &os) +{ + const auto target = profile.value(QStringLiteral("qbs.targetPlatform")); + if (target.isValid()) { + return Internal::contains(HostOsInfo::canonicalOSIdentifiers( + target.toString().toStdString()), + os.toStdString()); + } + return Internal::contains(HostOsInfo::hostOSIdentifiers(), os.toStdString()); +} + +static QString buildProfileName(const QFileInfo &cfi) +{ + // We need to replace a dot-separated compiler version string + // with a underscore-separated string, because the profile + // name does not allow a dots. + auto result = cfi.completeBaseName(); + result.replace(QLatin1Char('.'), QLatin1Char('_')); + return result; +} + +static QStringList buildCompilerNameFilters(const QString &compilerName) +{ + QStringList filters = { + // "clang", "gcc" + compilerName, + // "clang-8", "gcc-5" + compilerName + QLatin1String("-[1-9]*"), + // "avr-gcc" + QLatin1String("*-") + compilerName, + // "avr-gcc-5.4.0" + QLatin1String("*-") + compilerName + QLatin1String("-[1-9]*"), + // "arm-none-eabi-gcc" + QLatin1String("*-*-*-") + compilerName, + // "arm-none-eabi-gcc-9.1.0" + QLatin1String("*-*-*-") + compilerName + QLatin1String("-[1-9]*"), + // "x86_64-pc-linux-gnu-gcc" + QLatin1String("*-*-*-*-") + compilerName, + // "x86_64-pc-linux-gnu-gcc-7.4.1" + QLatin1String("*-*-*-*-") + compilerName + QLatin1String("-[1-9]*") + }; + + std::transform(filters.begin(), filters.end(), filters.begin(), + [](const auto &filter) { + return HostOsInfo::appendExecutableSuffix(filter); + }); + + return filters; +} + +static QStringList gnuRegistrySearchPaths() +{ + if (!HostOsInfo::isWindowsHost()) + return {}; + + QStringList searchPaths; + + QSettings registry(QLatin1String(kUninstallRegistryKey), QSettings::NativeFormat); + const auto productGroups = registry.childGroups(); + for (const QString &productKey : productGroups) { + // Registry token for the "GNU Tools for ARM Embedded Processors". + if (!productKey.startsWith( + QLatin1String("GNU Tools for ARM Embedded Processors"))) { + continue; + } + registry.beginGroup(productKey); + QString uninstallFilePath = registry.value( + QLatin1String("UninstallString")).toString(); + if (uninstallFilePath.startsWith(QLatin1Char('"'))) + uninstallFilePath.remove(0, 1); + if (uninstallFilePath.endsWith(QLatin1Char('"'))) + uninstallFilePath.remove(uninstallFilePath.size() - 1, 1); + registry.endGroup(); + + const QString toolkitRootPath = QFileInfo(uninstallFilePath).path(); + const QString toolchainPath = toolkitRootPath + QLatin1String("/bin"); + searchPaths.push_back(toolchainPath); + } + + return searchPaths; +} + +static QStringList atmelRegistrySearchPaths() +{ + if (!HostOsInfo::isWindowsHost()) + return {}; + + // Registry token for the "Atmel" toolchains, e.g. provided by installed + // "Atmel Studio" IDE. + static const char kRegistryToken[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Atmel\\"; + + QStringList searchPaths; + QSettings registry(QLatin1String(kRegistryToken), QSettings::NativeFormat); + + // This code enumerate the installed toolchains provided + // by the Atmel Studio v6.x. + const auto toolchainGroups = registry.childGroups(); + for (const QString &toolchainKey : toolchainGroups) { + if (!toolchainKey.endsWith(QLatin1String("GCC"))) + continue; + registry.beginGroup(toolchainKey); + const auto entries = registry.childGroups(); + for (const auto &entryKey : entries) { + registry.beginGroup(entryKey); + const QString installDir = registry.value( + QStringLiteral("Native/InstallDir")).toString(); + const QString version = registry.value( + QStringLiteral("Native/Version")).toString(); + registry.endGroup(); + + QString toolchainPath = installDir + + QLatin1String("/Atmel Toolchain/") + + toolchainKey + QLatin1String("/Native/") + + version; + if (toolchainKey.startsWith(QLatin1String("ARM"))) + toolchainPath += QLatin1String("/arm-gnu-toolchain"); + else if (toolchainKey.startsWith(QLatin1String("AVR32"))) + toolchainPath += QLatin1String("/avr32-gnu-toolchain"); + else if (toolchainKey.startsWith(QLatin1String("AVR8)"))) + toolchainPath += QLatin1String("/avr8-gnu-toolchain"); + else + break; + + toolchainPath += QLatin1String("/bin"); + + if (QFileInfo::exists(toolchainPath)) { + searchPaths.push_back(toolchainPath); + break; + } + } + registry.endGroup(); + } + + // This code enumerate the installed toolchains provided + // by the Atmel Studio v7. + registry.beginGroup(QStringLiteral("AtmelStudio")); + const auto productVersions = registry.childGroups(); + for (const auto &productVersionKey : productVersions) { + registry.beginGroup(productVersionKey); + const QString installDir = registry.value( + QStringLiteral("InstallDir")).toString(); + registry.endGroup(); + + const QStringList knownToolchainSubdirs = { + QStringLiteral("/toolchain/arm/arm-gnu-toolchain/bin/"), + QStringLiteral("/toolchain/avr8/avr8-gnu-toolchain/bin/"), + QStringLiteral("/toolchain/avr32/avr32-gnu-toolchain/bin/"), + }; + + for (const auto &subdir : knownToolchainSubdirs) { + const QString toolchainPath = installDir + subdir; + if (!QFileInfo::exists(toolchainPath)) + continue; + searchPaths.push_back(toolchainPath); + } + } + registry.endGroup(); + + return searchPaths; +} + +static QStringList renesasRl78RegistrySearchPaths() +{ + if (!HostOsInfo::isWindowsHost()) + return {}; + + QStringList searchPaths; + + QSettings registry(QLatin1String(kUninstallRegistryKey), QSettings::NativeFormat); + const auto productGroups = registry.childGroups(); + for (const QString &productKey : productGroups) { + // Registry token for the "Renesas RL78" toolchain. + if (!productKey.startsWith( + QLatin1String("GCC for Renesas RL78"))) { + continue; + } + registry.beginGroup(productKey); + const QString installLocation = registry.value( + QLatin1String("InstallLocation")).toString(); + registry.endGroup(); + if (installLocation.isEmpty()) + continue; + + const QFileInfo toolchainPath = QDir(installLocation).absolutePath() + + QLatin1String("/rl78-elf/rl78-elf/bin"); + if (!toolchainPath.exists()) + continue; + searchPaths.push_back(toolchainPath.absoluteFilePath()); + } + + return searchPaths; +} + +static QStringList mplabX32RegistrySearchPaths() +{ + if (!HostOsInfo::isWindowsHost()) + return {}; + + QStringList searchPaths; + + QSettings registry(QLatin1String(kUninstallRegistryKey), QSettings::NativeFormat); + const auto productGroups = registry.childGroups(); + for (const QString &productKey : productGroups) { + // Registry token for the "MPLAB X32" toolchain. + if (!productKey.startsWith( + QLatin1String("MPLAB XC32 Compiler"))) { + continue; + } + registry.beginGroup(productKey); + const QString installLocation = registry.value( + QLatin1String("InstallLocation")).toString(); + registry.endGroup(); + if (installLocation.isEmpty()) + continue; + + const QFileInfo toolchainPath = QDir(installLocation).absolutePath() + + QLatin1String("/bin"); + if (!toolchainPath.exists()) + continue; + searchPaths.push_back(toolchainPath.absoluteFilePath()); + } + + return searchPaths; +} + +Profile createGccProfile(const QFileInfo &compiler, Settings *settings, + const QString &toolchainType, + const QString &profileName) +{ + const QString machineName = gccMachineName(compiler); + + if (toolchainType == QLatin1String("mingw")) { + if (!validMinGWMachines().contains(machineName)) { + throw ErrorInfo(Tr::tr("Detected gcc platform '%1' is not supported.") + .arg(machineName)); + } + } + + Profile profile(!profileName.isEmpty() ? profileName : machineName, settings); + profile.removeProfile(); + + const ToolchainDetails details(compiler); + + setCommonProperties(profile, compiler, toolchainType, details); + + if (HostOsInfo::isWindowsHost() && toolchainType == QLatin1String("clang")) { + const QStringList profileNames = settings->profiles(); + bool foundMingw = false; + for (const QString &profileName : profileNames) { + const Profile otherProfile(profileName, settings); + if (otherProfile.value(QLatin1String("qbs.toolchainType")).toString() + == QLatin1String("mingw") + || otherProfile.value(QLatin1String("qbs.toolchain")) + .toStringList().contains(QLatin1String("mingw"))) { + const QFileInfo tcDir(otherProfile.value(QLatin1String("cpp.toolchainInstallPath")) + .toString()); + if (!tcDir.fileName().isEmpty() && tcDir.exists()) { + profile.setValue(QLatin1String("qbs.sysroot"), tcDir.path()); + foundMingw = true; + break; + } + } + } + if (!foundMingw) { + qbsWarning() << Tr::tr("Using clang on Windows requires a mingw installation. " + "Please set qbs.sysroot accordingly for profile '%1'.") + .arg(profile.name()); + } + } + + if (toolchainType != QLatin1String("clang")) { + // Check whether auxiliary tools reside within the toolchain's install path. + // This might not be the case when using icecc or another compiler wrapper. + const QString compilerDirPath = compiler.absolutePath(); + const ToolPathSetup toolPathSetup(&profile, compilerDirPath, details); + toolPathSetup.apply(QStringLiteral("ar"), QStringLiteral("cpp.archiverPath")); + toolPathSetup.apply(QStringLiteral("as"), QStringLiteral("cpp.assemblerPath")); + toolPathSetup.apply(QStringLiteral("nm"), QStringLiteral("cpp.nmPath")); + if (doesProfileTargetOS(profile, QStringLiteral("darwin"))) + toolPathSetup.apply(QStringLiteral("dsymutil"), + QStringLiteral("cpp.dsymutilPath")); + else + toolPathSetup.apply(QStringLiteral("objcopy"), + QStringLiteral("cpp.objcopyPath")); + toolPathSetup.apply(QStringLiteral("strip"), + QStringLiteral("cpp.stripPath")); + } + + qbsInfo() << Tr::tr("Profile '%1' created for '%2'.") + .arg(profile.name(), compiler.absoluteFilePath()); + return profile; +} + +void gccProbe(Settings *settings, std::vector &profiles, const QString &compilerName) +{ + qbsInfo() << Tr::tr("Trying to detect %1...").arg(compilerName); + + QStringList searchPaths; + searchPaths << systemSearchPaths() + << gnuRegistrySearchPaths() + << atmelRegistrySearchPaths() + << renesasRl78RegistrySearchPaths() + << mplabX32RegistrySearchPaths(); + + std::vector candidates; + const auto filters = buildCompilerNameFilters(compilerName); + for (const auto &searchPath : qAsConst(searchPaths)) { + const QDir dir(searchPath); + const QStringList fileNames = dir.entryList( + filters, QDir::Files | QDir::Executable); + for (const QString &fileName : fileNames) { + // Ignore unexpected compiler names. + if (fileName.startsWith(QLatin1String("c89-gcc")) + || fileName.startsWith(QLatin1String("c99-gcc"))) { + continue; + } + const QFileInfo candidate = dir.filePath(fileName); + // Filter duplicates. + const auto existingEnd = candidates.end(); + const auto existingIt = std::find_if( + candidates.begin(), existingEnd, + [candidate](const QFileInfo &existing) { + return isSameExecutable(candidate.absoluteFilePath(), + existing.absoluteFilePath()); + }); + if (existingIt == existingEnd) { + // No duplicates are found, just add a new candidate. + candidates.push_back(candidate); + } else { + // Replace the existing entry if a candidate name more than + // an existing name. + const auto candidateName = candidate.completeBaseName(); + const auto existingName = existingIt->completeBaseName(); + if (candidateName > existingName) + *existingIt = candidate; + } + } + } + + if (candidates.empty()) { + qbsInfo() << Tr::tr("No %1 toolchains found.").arg(compilerName); + return; + } + + // Sort candidates so that mingw comes first. Information from mingw profiles is potentially + // used for setting up clang profiles. + if (HostOsInfo::isWindowsHost()) { + std::sort(candidates.begin(), candidates.end(), + [](const QFileInfo &fi1, const QFileInfo &fi2) { + return fi1.absoluteFilePath().contains(QLatin1String("mingw")) + && !fi2.absoluteFilePath().contains(QLatin1String("mingw")); + }); + } + + for (const auto &candidate : qAsConst(candidates)) { + const QString toolchainType = toolchainTypeFromCompilerName( + candidate.baseName()); + const QString profileName = buildProfileName(candidate); + try { + auto profile = createGccProfile(candidate, settings, + toolchainType, profileName); + profiles.push_back(std::move(profile)); + } catch (const qbs::ErrorInfo &info) { + qbsWarning() << Tr::tr("Skipping %1: %2").arg(profileName, info.toString()); + } + } +} diff --git a/src/app/qbs-setup-toolchains/gccprobe.h b/src/app/qbs-setup-toolchains/gccprobe.h new file mode 100644 index 00000000..98e7eaa1 --- /dev/null +++ b/src/app/qbs-setup-toolchains/gccprobe.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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_SETUPTOOLCHAINS_GCCPROBE_H +#define QBS_SETUPTOOLCHAINS_GCCPROBE_H + +#include + +QT_BEGIN_NAMESPACE +class QFileInfo; +QT_END_NAMESPACE + +namespace qbs { +class Profile; +class Settings; +} + +qbs::Profile createGccProfile(const QFileInfo &compiler, + qbs::Settings *settings, + const QString &toolchainType, + const QString &profileName = QString()); + +void gccProbe(qbs::Settings *settings, std::vector &profiles, + const QString &compilerName); + +#endif // Header guard diff --git a/src/app/qbs-setup-toolchains/iarewprobe.cpp b/src/app/qbs-setup-toolchains/iarewprobe.cpp new file mode 100644 index 00000000..4f87590a --- /dev/null +++ b/src/app/qbs-setup-toolchains/iarewprobe.cpp @@ -0,0 +1,324 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "probe.h" +#include "iarewprobe.h" + +#include "../shared/logging/consolelogger.h" + +#include + +#include +#include + +#include +#include +#include + +using namespace qbs; +using Internal::Tr; +using Internal::HostOsInfo; + +static QStringList knownIarCompilerNames() +{ + return {QStringLiteral("icc8051"), QStringLiteral("iccarm"), + QStringLiteral("iccavr"), QStringLiteral("iccstm8"), + QStringLiteral("icc430"), QStringLiteral("iccrl78"), + QStringLiteral("iccrx"), QStringLiteral("iccrh850"), + QStringLiteral("iccv850"), QStringLiteral("icc78k"), + QStringLiteral("iccavr32"), QStringLiteral("iccsh"), + QStringLiteral("iccriscv"), QStringLiteral("icccf"), + QStringLiteral("iccm32c"), QStringLiteral("iccr32c"), + QStringLiteral("iccm16c"), QStringLiteral("icccr16c")}; +} + +static QString guessIarArchitecture(const QFileInfo &compiler) +{ + const auto baseName = compiler.baseName(); + if (baseName == QLatin1String("icc8051")) + return QStringLiteral("mcs51"); + if (baseName == QLatin1String("iccarm")) + return QStringLiteral("arm"); + if (baseName == QLatin1String("iccavr")) + return QStringLiteral("avr"); + if (baseName == QLatin1String("iccstm8")) + return QStringLiteral("stm8"); + if (baseName == QLatin1String("icc430")) + return QStringLiteral("msp430"); + if (baseName == QLatin1String("iccrl78")) + return QStringLiteral("rl78"); + if (baseName == QLatin1String("iccrx")) + return QStringLiteral("rx"); + if (baseName == QLatin1String("iccrh850")) + return QStringLiteral("rh850"); + if (baseName == QLatin1String("iccv850")) + return QStringLiteral("v850"); + if (baseName == QLatin1String("icc78k")) + return QStringLiteral("78k"); + if (baseName == QLatin1String("iccavr32")) + return QStringLiteral("avr32"); + if (baseName == QLatin1String("iccsh")) + return QStringLiteral("sh"); + if (baseName == QLatin1String("iccriscv")) + return QStringLiteral("riscv"); + if (baseName == QLatin1String("icccf")) + return QStringLiteral("m68k"); + if (baseName == QLatin1String("iccm32c")) + return QStringLiteral("m32c"); + if (baseName == QLatin1String("iccr32c")) + return QStringLiteral("r32c"); + if (baseName == QLatin1String("iccm16c")) + return QStringLiteral("m16c"); + if (baseName == QLatin1String("icccr16c")) + return QStringLiteral("cr16"); + return {}; +} + +static Profile createIarProfileHelper(const ToolchainInstallInfo &info, + Settings *settings, + QString profileName = QString()) +{ + const QFileInfo compiler = info.compilerPath; + const QString architecture = guessIarArchitecture(compiler); + + // In case the profile is auto-detected. + if (profileName.isEmpty()) { + if (!info.compilerVersion.isValid()) { + profileName = QStringLiteral("iar-unknown-%1").arg(architecture); + } else { + const QString version = info.compilerVersion.toString(QLatin1Char('_'), + QLatin1Char('_')); + profileName = QStringLiteral("iar-%1-%2").arg( + version, architecture); + } + } + + Profile profile(profileName, settings); + profile.setValue(QLatin1String("cpp.toolchainInstallPath"), compiler.absolutePath()); + profile.setValue(QLatin1String("qbs.toolchainType"), QLatin1String("iar")); + if (!architecture.isEmpty()) + profile.setValue(QLatin1String("qbs.architecture"), architecture); + + qbsInfo() << Tr::tr("Profile '%1' created for '%2'.").arg( + profile.name(), compiler.absoluteFilePath()); + return profile; +} + +static Version dumpIarCompilerVersion(const QFileInfo &compiler) +{ + const QString outFilePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + + QLatin1String("/macros.dump"); + const QStringList args = {QStringLiteral("."), + QStringLiteral("--predef_macros"), + outFilePath}; + QProcess p; + p.start(compiler.absoluteFilePath(), args); + p.waitForFinished(3000); + const auto es = p.exitStatus(); + if (es != QProcess::NormalExit) { + const QByteArray out = p.readAll(); + qbsWarning() << Tr::tr("Compiler dumping failed:\n%1") + .arg(QString::fromUtf8(out)); + return Version{}; + } + + QByteArray dump; + QFile out(outFilePath); + if (out.open(QIODevice::ReadOnly)) + dump = out.readAll(); + out.remove(); + + const int verCode = extractVersion(dump, "__VER__ "); + if (verCode < 0) { + qbsWarning() << Tr::tr("No '__VER__' token was found in a compiler dump:\n%1") + .arg(QString::fromUtf8(dump)); + return Version{}; + } + + const QString arch = guessIarArchitecture(compiler); + if (arch == QLatin1String("arm")) { + return Version{verCode / 1000000, (verCode / 1000) % 1000, verCode % 1000}; + } else if (arch == QLatin1String("avr") + || arch == QLatin1String("mcs51") + || arch == QLatin1String("stm8") + || arch == QLatin1String("msp430") + || arch == QLatin1String("rl78") + || arch == QLatin1String("rx") + || arch == QLatin1String("rh850") + || arch == QLatin1String("v850") + || arch == QLatin1String("78k") + || arch == QLatin1String("avr32") + || arch == QLatin1String("sh") + || arch == QLatin1String("riscv") + || arch == QLatin1String("m68k") + || arch == QLatin1String("m32c") + || arch == QLatin1String("r32c") + || arch == QLatin1String("m16c") + || arch == QLatin1String("rc16")) { + return Version{verCode / 100, verCode % 100}; + } + + return Version{}; +} + +static std::vector installedIarsFromPath() +{ + std::vector infos; + const auto compilerNames = knownIarCompilerNames(); + for (const QString &compilerName : compilerNames) { + const QFileInfo iarPath( + findExecutable( + HostOsInfo::appendExecutableSuffix(compilerName))); + if (!iarPath.exists()) + continue; + const Version version = dumpIarCompilerVersion(iarPath); + infos.push_back({iarPath, version}); + } + std::sort(infos.begin(), infos.end()); + return infos; +} + +static std::vector installedIarsFromRegistry() +{ + std::vector infos; + + if (HostOsInfo::isWindowsHost()) { + +#ifdef Q_OS_WIN64 + static const char kRegistryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\IAR Systems\\Embedded Workbench"; +#else + static const char kRegistryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\IAR Systems\\Embedded Workbench"; +#endif + + // Dictionary for know toolchains. + static const struct Entry { + QString registryKey; + QString subExePath; + } knowToolchains[] = { + {QStringLiteral("EWARM"), QStringLiteral("/arm/bin/iccarm.exe")}, + {QStringLiteral("EWAVR"), QStringLiteral("/avr/bin/iccavr.exe")}, + {QStringLiteral("EW8051"), QStringLiteral("/8051/bin/icc8051.exe")}, + {QStringLiteral("EWSTM8"), QStringLiteral("/stm8/bin/iccstm8.exe")}, + {QStringLiteral("EW430"), QStringLiteral("/430/bin/icc430.exe")}, + {QStringLiteral("EWRL78"), QStringLiteral("/rl78/bin/iccrl78.exe")}, + {QStringLiteral("EWRX"), QStringLiteral("/rx/bin/iccrx.exe")}, + {QStringLiteral("EWRH850"), QStringLiteral("/rh850/bin/iccrh850.exe")}, + {QStringLiteral("EWV850"), QStringLiteral("/v850/bin/iccv850.exe")}, + {QStringLiteral("EW78K"), QStringLiteral("/78k/bin/icc78k.exe")}, + {QStringLiteral("EWAVR32"), QStringLiteral("/avr32/bin/iccavr32.exe")}, + {QStringLiteral("EWSH"), QStringLiteral("/sh/bin/iccsh.exe")}, + {QStringLiteral("EWRISCV"), QStringLiteral("/riscv/bin/iccriscv.exe")}, + {QStringLiteral("EWCF"), QStringLiteral("/cf/bin/icccf.exe")}, + {QStringLiteral("EWM32C"), QStringLiteral("/m32c/bin/iccm32c.exe")}, + {QStringLiteral("EWR32C"), QStringLiteral("/r32c/bin/iccr32c.exe")}, + {QStringLiteral("EWM16C"), QStringLiteral("/m16c/bin/iccm16c.exe")}, + {QStringLiteral("EWCR16C"), QStringLiteral("/cr16c/bin/icccr16c.exe")}, + }; + + QSettings registry(QLatin1String(kRegistryNode), QSettings::NativeFormat); + const auto oneLevelGroups = registry.childGroups(); + for (const QString &oneLevelKey : oneLevelGroups) { + registry.beginGroup(oneLevelKey); + const auto twoLevelGroups = registry.childGroups(); + for (const Entry &entry : knowToolchains) { + if (twoLevelGroups.contains(entry.registryKey)) { + registry.beginGroup(entry.registryKey); + const auto threeLevelGroups = registry.childGroups(); + for (const QString &threeLevelKey : threeLevelGroups) { + registry.beginGroup(threeLevelKey); + const QString rootPath = registry.value( + QStringLiteral("InstallPath")).toString(); + if (!rootPath.isEmpty()) { + // Build full compiler path. + const QFileInfo iarPath(rootPath + entry.subExePath); + if (iarPath.exists()) { + // Note: threeLevelKey is a guessed toolchain version. + infos.push_back({iarPath, Version::fromString(threeLevelKey)}); + } + } + registry.endGroup(); + } + registry.endGroup(); + } + } + registry.endGroup(); + } + + } + + std::sort(infos.begin(), infos.end()); + return infos; +} + +bool isIarCompiler(const QString &compilerName) +{ + return Internal::any_of(knownIarCompilerNames(), [compilerName]( + const QString &knownName) { + return compilerName.contains(knownName); + }); +} + +void createIarProfile(const QFileInfo &compiler, Settings *settings, + QString profileName) +{ + const ToolchainInstallInfo info = {compiler, Version{}}; + createIarProfileHelper(info, settings, std::move(profileName)); +} + +void iarProbe(Settings *settings, std::vector &profiles) +{ + qbsInfo() << Tr::tr("Trying to detect IAR toolchains..."); + + // Make sure that a returned infos are sorted before using the std::set_union! + const std::vector regInfos = installedIarsFromRegistry(); + const std::vector pathInfos = installedIarsFromPath(); + std::vector allInfos; + allInfos.reserve(regInfos.size() + pathInfos.size()); + std::set_union(regInfos.cbegin(), regInfos.cend(), + pathInfos.cbegin(), pathInfos.cend(), + std::back_inserter(allInfos)); + + for (const ToolchainInstallInfo &info : allInfos) { + const auto profile = createIarProfileHelper(info, settings); + profiles.push_back(profile); + } + + if (allInfos.empty()) + qbsInfo() << Tr::tr("No IAR toolchains found."); +} diff --git a/src/app/qbs-setup-toolchains/iarewprobe.h b/src/app/qbs-setup-toolchains/iarewprobe.h new file mode 100644 index 00000000..a1a51daa --- /dev/null +++ b/src/app/qbs-setup-toolchains/iarewprobe.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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_SETUPTOOLCHAINS_IAREWPROBE_H +#define QBS_SETUPTOOLCHAINS_IAREWPROBE_H + +#include + +QT_BEGIN_NAMESPACE +class QFileInfo; +QT_END_NAMESPACE + +namespace qbs { +class Profile; +class Settings; +} + +bool isIarCompiler(const QString &compilerName); + +void createIarProfile(const QFileInfo &compiler, qbs::Settings *settings, + QString profileName); + +void iarProbe(qbs::Settings *settings, std::vector &profiles); + +#endif // Header guard diff --git a/src/app/qbs-setup-toolchains/keilprobe.cpp b/src/app/qbs-setup-toolchains/keilprobe.cpp new file mode 100644 index 00000000..ee0144c7 --- /dev/null +++ b/src/app/qbs-setup-toolchains/keilprobe.cpp @@ -0,0 +1,481 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "probe.h" +#include "keilprobe.h" + +#include "../shared/logging/consolelogger.h" + +#include + +#include +#include + +#include +#include +#include +#include + +using namespace qbs; +using Internal::Tr; +using Internal::HostOsInfo; + +static QStringList knownKeilCompilerNames() +{ + return {QStringLiteral("c51"), QStringLiteral("c251"), + QStringLiteral("c166"), QStringLiteral("armcc"), + QStringLiteral("armclang")}; +} + +static QString guessKeilArchitecture(const QFileInfo &compiler) +{ + const auto baseName = compiler.baseName(); + if (baseName == QLatin1String("c51")) + return QStringLiteral("mcs51"); + if (baseName == QLatin1String("c251")) + return QStringLiteral("mcs251"); + if (baseName == QLatin1String("c166")) + return QStringLiteral("c166"); + if (baseName == QLatin1String("armcc")) + return QStringLiteral("arm"); + if (baseName == QLatin1String("armclang")) + return QStringLiteral("arm"); + return {}; +} + +static bool isArmClangCompiler(const QFileInfo &compiler) +{ + return compiler.baseName() == QLatin1String("armclang"); +} + +static Profile createKeilProfileHelper(const ToolchainInstallInfo &info, + Settings *settings, + QString profileName = QString()) +{ + const QFileInfo compiler = info.compilerPath; + const QString architecture = guessKeilArchitecture(compiler); + + // In case the profile is auto-detected. + if (profileName.isEmpty()) { + if (!info.compilerVersion.isValid()) { + profileName = QStringLiteral("keil-unknown-%1").arg(architecture); + } else { + const QString version = info.compilerVersion.toString(QLatin1Char('_'), + QLatin1Char('_')); + if (architecture == QLatin1String("arm") && isArmClangCompiler(compiler)) { + profileName = QStringLiteral("keil-llvm-%1-%2").arg( + version, architecture); + } else { + profileName = QStringLiteral("keil-%1-%2").arg( + version, architecture); + } + } + } + + Profile profile(profileName, settings); + profile.setValue(QStringLiteral("cpp.toolchainInstallPath"), compiler.absolutePath()); + profile.setValue(QStringLiteral("cpp.compilerName"), compiler.fileName()); + profile.setValue(QStringLiteral("qbs.toolchainType"), QStringLiteral("keil")); + if (!architecture.isEmpty()) + profile.setValue(QStringLiteral("qbs.architecture"), architecture); + + qbsInfo() << Tr::tr("Profile '%1' created for '%2'.").arg( + profile.name(), compiler.absoluteFilePath()); + return profile; +} + +static Version dumpMcsCompilerVersion(const QFileInfo &compiler) +{ + QTemporaryFile fakeIn; + if (!fakeIn.open()) { + qbsWarning() << Tr::tr("Unable to open temporary file %1 for output: %2") + .arg(fakeIn.fileName(), fakeIn.errorString()); + return Version{}; + } + + fakeIn.write("#define VALUE_TO_STRING(x) #x\n"); + fakeIn.write("#define VALUE(x) VALUE_TO_STRING(x)\n"); + + // Prepare for C51 compiler. + fakeIn.write("#if defined(__C51__) || defined(__CX51__)\n"); + fakeIn.write("# define VAR_NAME_VALUE(var) \"(\"\"\"\"|\"#var\"|\"VALUE(var)\"|\"\"\"\")\"\n"); + fakeIn.write("# if defined (__C51__)\n"); + fakeIn.write("# pragma message (VAR_NAME_VALUE(__C51__))\n"); + fakeIn.write("# endif\n"); + fakeIn.write("# if defined(__CX51__)\n"); + fakeIn.write("# pragma message (VAR_NAME_VALUE(__CX51__))\n"); + fakeIn.write("# endif\n"); + fakeIn.write("#endif\n"); + + // Prepare for C251 compiler. + fakeIn.write("#if defined(__C251__)\n"); + fakeIn.write("# define VAR_NAME_VALUE(var) \"\"|#var|VALUE(var)|\"\"\n"); + fakeIn.write("# if defined(__C251__)\n"); + fakeIn.write("# warning (VAR_NAME_VALUE(__C251__))\n"); + fakeIn.write("# endif\n"); + fakeIn.write("#endif\n"); + + fakeIn.close(); + + const QStringList args = {fakeIn.fileName()}; + QProcess p; + p.start(compiler.absoluteFilePath(), args); + p.waitForFinished(3000); + + const QStringList knownKeys = {QStringLiteral("__C51__"), + QStringLiteral("__CX51__"), + QStringLiteral("__C251__")}; + + auto extractVersion = [&knownKeys](const QByteArray &output) { + QTextStream stream(output); + QString line; + while (stream.readLineInto(&line)) { + if (!line.startsWith(QLatin1String("***"))) + continue; + enum { KEY_INDEX = 1, VALUE_INDEX = 2, ALL_PARTS = 4 }; + const QStringList parts = line.split(QLatin1String("\"|\"")); + if (parts.count() != ALL_PARTS) + continue; + if (!knownKeys.contains(parts.at(KEY_INDEX))) + continue; + return parts.at(VALUE_INDEX).toInt(); + } + return -1; + }; + + const QByteArray dump = p.readAllStandardOutput(); + const int verCode = extractVersion(dump); + if (verCode < 0) { + qbsWarning() << Tr::tr("No %1 tokens was found" + " in the compiler dump:\n%2") + .arg(knownKeys.join(QLatin1Char(','))) + .arg(QString::fromUtf8(dump)); + return Version{}; + } + return Version{verCode / 100, verCode % 100}; +} + +static Version dumpC166CompilerVersion(const QFileInfo &compiler) +{ + QTemporaryFile fakeIn; + if (!fakeIn.open()) { + qbsWarning() << Tr::tr("Unable to open temporary file %1 for output: %2") + .arg(fakeIn.fileName(), fakeIn.errorString()); + return Version{}; + } + + fakeIn.write("#if defined(__C166__)\n"); + fakeIn.write("# warning __C166__\n"); + fakeIn.write("# pragma __C166__\n"); + fakeIn.write("#endif\n"); + + fakeIn.close(); + + const QStringList args = {fakeIn.fileName()}; + QProcess p; + p.start(compiler.absoluteFilePath(), args); + p.waitForFinished(3000); + + // Extract the compiler version pattern in the form, like: + // + // *** WARNING C320 IN LINE 41 OF c51.c: __C166__ + // *** WARNING C2 IN LINE 42 OF c51.c: '757': unknown #pragma/control, line ignored + // + // where the '__C166__' is a key, and the '757' is a value (aka version). + auto extractVersion = [](const QString &output) { + const QStringList lines = output.split(QStringLiteral("\r\n")); + for (auto it = lines.cbegin(); it != lines.cend();) { + if (it->startsWith(QLatin1String("***")) && it->endsWith(QLatin1String("__C166__"))) { + ++it; + if (it == lines.cend() || !it->startsWith(QLatin1String("***"))) + break; + const int startIndex = it->indexOf(QLatin1Char('\'')); + if (startIndex == -1) + break; + const int stopIndex = it->indexOf(QLatin1Char('\''), startIndex + 1); + if (stopIndex == -1) + break; + const QString v = it->mid(startIndex + 1, stopIndex - startIndex - 1); + return v.toInt(); + } + ++it; + } + return -1; + }; + + const QByteArray dump = p.readAllStandardOutput(); + const int verCode = extractVersion(QString::fromUtf8(dump)); + if (verCode < 0) { + qbsWarning() << Tr::tr("No __C166__ token was found" + " in the compiler dump:\n%1") + .arg(QString::fromUtf8(dump)); + return Version{}; + } + return Version{verCode / 100, verCode % 100}; +} + +static Version dumpArmCCCompilerVersion(const QFileInfo &compiler) +{ + const QStringList args = {QStringLiteral("-E"), + QStringLiteral("--list-macros"), + QStringLiteral("nul")}; + QProcess p; + p.start(compiler.absoluteFilePath(), args); + p.waitForFinished(3000); + const auto es = p.exitStatus(); + if (es != QProcess::NormalExit) { + const QByteArray out = p.readAll(); + qbsWarning() << Tr::tr("Compiler dumping failed:\n%1") + .arg(QString::fromUtf8(out)); + return Version{}; + } + + const QByteArray dump = p.readAll(); + const int verCode = extractVersion(dump, "__ARMCC_VERSION "); + if (verCode < 0) { + qbsWarning() << Tr::tr("No '__ARMCC_VERSION' token was found " + "in the compiler dump:\n%1") + .arg(QString::fromUtf8(dump)); + return Version{}; + } + return Version{verCode / 1000000, (verCode / 10000) % 100, verCode % 10000}; +} + +static Version dumpArmClangCompilerVersion(const QFileInfo &compiler) +{ + const QStringList args = {QStringLiteral("-dM"), QStringLiteral("-E"), + QStringLiteral("-xc"), + QStringLiteral("--target=arm-arm-none-eabi"), + QStringLiteral("-mcpu=cortex-m0"), + QStringLiteral("nul")}; + QProcess p; + p.start(compiler.absoluteFilePath(), args); + p.waitForFinished(3000); + const auto es = p.exitStatus(); + if (es != QProcess::NormalExit) { + const QByteArray out = p.readAll(); + qbsWarning() << Tr::tr("Compiler dumping failed:\n%1") + .arg(QString::fromUtf8(out)); + return Version{}; + } + const QByteArray dump = p.readAll(); + const int verCode = extractVersion(dump, "__ARMCC_VERSION "); + if (verCode < 0) { + qbsWarning() << Tr::tr("No '__ARMCC_VERSION' token was found " + "in the compiler dump:\n%1") + .arg(QString::fromUtf8(dump)); + return Version{}; + } + return Version{verCode / 1000000, (verCode / 10000) % 100, verCode % 10000}; +} + +static Version dumpKeilCompilerVersion(const QFileInfo &compiler) +{ + const QString arch = guessKeilArchitecture(compiler); + if (arch == QLatin1String("mcs51") || arch == QLatin1String("mcs251")) { + return dumpMcsCompilerVersion(compiler); + } else if (arch == QLatin1String("c166")) { + return dumpC166CompilerVersion(compiler); + } else if (arch == QLatin1String("arm")) { + if (isArmClangCompiler(compiler)) + return dumpArmClangCompilerVersion(compiler); + return dumpArmCCCompilerVersion(compiler); + } + return Version{}; +} + +static std::vector installedKeilsFromPath() +{ + std::vector infos; + const auto compilerNames = knownKeilCompilerNames(); + for (const QString &compilerName : compilerNames) { + const QFileInfo keilPath( + findExecutable( + HostOsInfo::appendExecutableSuffix(compilerName))); + if (!keilPath.exists()) + continue; + const Version version = dumpKeilCompilerVersion(keilPath); + infos.push_back({keilPath, version}); + } + std::sort(infos.begin(), infos.end()); + return infos; +} + +// Parse the 'tools.ini' file to fetch a toolchain version. +// Note: We can't use QSettings here! +static QString extractVersion(const QString &toolsIniFile, const QString §ion) +{ + QFile f(toolsIniFile); + if (!f.open(QIODevice::ReadOnly)) + return {}; + QTextStream in(&f); + enum State { Enter, Lookup, Exit } state = Enter; + while (!in.atEnd()) { + const QString line = in.readLine().trimmed(); + // Search for section. + const int firstBracket = line.indexOf(QLatin1Char('[')); + const int lastBracket = line.lastIndexOf(QLatin1Char(']')); + const bool hasSection = (firstBracket == 0 && lastBracket != -1 + && (lastBracket + 1) == line.size()); + switch (state) { + case Enter: { + if (hasSection) { + const auto content = line.midRef(firstBracket + 1, + lastBracket - firstBracket - 1); + if (content == section) + state = Lookup; + } + } + break; + case Lookup: { + if (hasSection) + return {}; // Next section found. + const int versionIndex = line.indexOf(QLatin1String("VERSION=")); + if (versionIndex < 0) + continue; + QString version = line.mid(8); + if (version.startsWith(QLatin1Char('V'))) + version.remove(0, 1); + return version; + } + break; + default: + return {}; + } + } + return {}; +} + +static std::vector installedKeilsFromRegistry() +{ + std::vector infos; + + if (HostOsInfo::isWindowsHost()) { + +#ifdef Q_OS_WIN64 + static const char kRegistryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Microsoft\\" \ + "Windows\\CurrentVersion\\Uninstall\\Keil \u00B5Vision4"; +#else + static const char kRegistryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\" \ + "Windows\\CurrentVersion\\Uninstall\\Keil \u00B5Vision4"; +#endif + + QSettings registry(QLatin1String(kRegistryNode), QSettings::NativeFormat); + const auto productGroups = registry.childGroups(); + for (const QString &productKey : productGroups) { + if (!productKey.startsWith(QStringLiteral("App"))) + continue; + registry.beginGroup(productKey); + const QString productPath = registry.value(QStringLiteral("ProductDir")) + .toString(); + // Fetch the toolchain executable path. + QVector keilPaths; + if (productPath.endsWith(QStringLiteral("ARM"))) { + keilPaths << QFileInfo(productPath + QStringLiteral("/ARMCC/bin/armcc.exe")); + keilPaths << QFileInfo(productPath + QStringLiteral("/ARMCLANG/bin/armclang.exe")); + } else if (productPath.endsWith(QStringLiteral("C51"))) { + keilPaths << QFileInfo(productPath + QStringLiteral("/BIN/c51.exe")); + } else if (productPath.endsWith(QStringLiteral("C251"))) { + keilPaths << QFileInfo(productPath + QStringLiteral("/BIN/c251.exe")); + } else if (productPath.endsWith(QStringLiteral("C166"))) { + keilPaths << QFileInfo(productPath + QStringLiteral("/BIN/c166.exe")); + } + + // Fetch the toolchain version. + const QDir rootPath(registry.value(QStringLiteral("Directory")).toString()); + const QString toolsIniFilePath = rootPath.absoluteFilePath( + QStringLiteral("tools.ini")); + + for (const QFileInfo &keilPath : qAsConst(keilPaths)) { + if (keilPath.exists()) { + for (auto index = 1; index <= 2; ++index) { + const QString section = registry.value( + QStringLiteral("Section %1").arg(index)).toString(); + const QString version = extractVersion(toolsIniFilePath, section); + if (!version.isEmpty()) { + infos.push_back({keilPath, Version::fromString(version)}); + break; + } + } + } + } + registry.endGroup(); + } + } + + std::sort(infos.begin(), infos.end()); + return infos; +} + +bool isKeilCompiler(const QString &compilerName) +{ + return Internal::any_of(knownKeilCompilerNames(), [compilerName]( + const QString &knownName) { + return compilerName.contains(knownName); + }); +} + +void createKeilProfile(const QFileInfo &compiler, Settings *settings, + QString profileName) +{ + const ToolchainInstallInfo info = {compiler, Version{}}; + createKeilProfileHelper(info, settings, std::move(profileName)); +} + +void keilProbe(Settings *settings, std::vector &profiles) +{ + qbsInfo() << Tr::tr("Trying to detect KEIL toolchains..."); + + // Make sure that a returned infos are sorted before using the std::set_union! + const std::vector regInfos = installedKeilsFromRegistry(); + const std::vector pathInfos = installedKeilsFromPath(); + std::vector allInfos; + allInfos.reserve(regInfos.size() + pathInfos.size()); + std::set_union(regInfos.cbegin(), regInfos.cend(), + pathInfos.cbegin(), pathInfos.cend(), + std::back_inserter(allInfos)); + + for (const ToolchainInstallInfo &info : allInfos) { + const auto profile = createKeilProfileHelper(info, settings); + profiles.push_back(profile); + } + + if (allInfos.empty()) + qbsInfo() << Tr::tr("No KEIL toolchains found."); +} diff --git a/src/app/qbs-setup-toolchains/keilprobe.h b/src/app/qbs-setup-toolchains/keilprobe.h new file mode 100644 index 00000000..fec650ab --- /dev/null +++ b/src/app/qbs-setup-toolchains/keilprobe.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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_SETUPTOOLCHAINS_KEILPROBE_H +#define QBS_SETUPTOOLCHAINS_KEILPROBE_H + +#include + +QT_BEGIN_NAMESPACE +class QFileInfo; +QT_END_NAMESPACE + +namespace qbs { +class Profile; +class Settings; +} + +bool isKeilCompiler(const QString &compilerName); + +void createKeilProfile(const QFileInfo &compiler, qbs::Settings *settings, + QString profileName); + +void keilProbe(qbs::Settings *settings, std::vector &profiles); + +#endif // Header guard diff --git a/src/app/qbs-setup-toolchains/main.cpp b/src/app/qbs-setup-toolchains/main.cpp new file mode 100644 index 00000000..475bcf07 --- /dev/null +++ b/src/app/qbs-setup-toolchains/main.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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 "commandlineparser.h" +#include "probe.h" + +#include +#include +#include + +#include + +#include +#include + +using qbs::Settings; + +static void printUsage(const QString &usageString) +{ + std::cout << qPrintable(usageString) << std::endl; +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + + CommandLineParser clParser; + try { + clParser.parse(app.arguments()); + if (clParser.helpRequested()) { + printUsage(clParser.usageString()); + return EXIT_SUCCESS; + } + Settings settings(clParser.settingsDir()); + settings.setScopeForWriting(clParser.settingsScope()); + if (clParser.autoDetectionMode()) { + probe(&settings); + return EXIT_SUCCESS; + } + createProfile(clParser.profileName(), clParser.toolchainType(), clParser.compilerPath(), + &settings); + } catch (const qbs::ErrorInfo &e) { + std::cerr << qPrintable(e.toString()) << std::endl; + return EXIT_FAILURE; + } +} diff --git a/src/app/qbs-setup-toolchains/msvcprobe.cpp b/src/app/qbs-setup-toolchains/msvcprobe.cpp new file mode 100644 index 00000000..bb54add9 --- /dev/null +++ b/src/app/qbs-setup-toolchains/msvcprobe.cpp @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** 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 "msvcprobe.h" + +#include "probe.h" +#include "../shared/logging/consolelogger.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +using namespace qbs; +using namespace qbs::Internal; +using Internal::Tr; + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(WinSDK, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(MSVC, Q_MOVABLE_TYPE); +QT_END_NAMESPACE + +// Not necessary but helps setup-qt automatically associate base profiles +static void setQtHelperProperties(Profile &p, const MSVC *msvc) +{ + QString targetArch = msvc->architecture.split(QLatin1Char('_')).last(); + if (targetArch.isEmpty()) + targetArch = QStringLiteral("x86"); + if (targetArch == QStringLiteral("arm")) + targetArch = QStringLiteral("armv7"); + + p.setValue(QStringLiteral("qbs.architecture"), canonicalArchitecture(targetArch)); + p.setValue(QStringLiteral("cpp.compilerVersion"), msvc->compilerVersion.toString()); +} + +static void addMSVCPlatform(Settings *settings, std::vector &profiles, QString name, MSVC *msvc) +{ + qbsInfo() << Tr::tr("Setting up profile '%1'.").arg(name); + Profile p(std::move(name), settings); + p.removeProfile(); + p.setValue(QStringLiteral("qbs.targetPlatform"), QStringLiteral("windows")); + p.setValue(QStringLiteral("qbs.toolchainType"), QStringLiteral("msvc")); + p.setValue(QStringLiteral("cpp.toolchainInstallPath"), msvc->binPath); + setQtHelperProperties(p, msvc); + profiles.push_back(p); +} + +static QString wow6432Key() +{ +#ifdef Q_OS_WIN64 + return QStringLiteral("\\Wow6432Node"); +#else + return {}; +#endif +} + +void msvcProbe(Settings *settings, std::vector &profiles) +{ + qbsInfo() << Tr::tr("Detecting MSVC toolchains..."); + + // 1) Installed SDKs preferred over standalone Visual studio + std::vector winSDKs; + WinSDK defaultWinSDK; + + const QSettings sdkRegistry(QLatin1String("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key() + + QLatin1String("\\Microsoft\\Microsoft SDKs\\Windows"), + QSettings::NativeFormat); + const QString defaultSdkPath = sdkRegistry.value(QStringLiteral("CurrentInstallFolder")).toString(); + if (!defaultSdkPath.isEmpty()) { + const auto sdkKeys = sdkRegistry.childGroups(); + for (const QString &sdkKey : sdkKeys) { + WinSDK sdk; + sdk.version = sdkKey; + sdk.vcInstallPath = sdkRegistry.value(sdkKey + QLatin1String("/InstallationFolder")).toString(); + sdk.isDefault = (sdk.vcInstallPath == defaultSdkPath); + if (sdk.vcInstallPath.isEmpty()) + continue; + if (sdk.vcInstallPath.endsWith(QLatin1Char('\\'))) + sdk.vcInstallPath.chop(1); + if (sdk.isDefault) + defaultWinSDK = sdk; + const auto ais = MSVC::findSupportedArchitectures(sdk); + for (const MSVCArchInfo &ai : ais) { + WinSDK specificSDK = sdk; + specificSDK.architecture = ai.arch; + specificSDK.binPath = ai.binPath; + winSDKs.push_back(specificSDK); + } + } + } + + for (const WinSDK &sdk : qAsConst(winSDKs)) { + qbsInfo() << Tr::tr(" Windows SDK %1 detected:\n" + " installed in %2").arg(sdk.version, sdk.vcInstallPath); + if (sdk.isDefault) + qbsInfo() << Tr::tr(" This is the default SDK on this machine."); + } + + // 2) Installed MSVCs + std::vector msvcs = MSVC::installedCompilers(ConsoleLogger::instance()); + + for (const MSVC &msvc : qAsConst(msvcs)) { + qbsInfo() << Tr::tr(" MSVC %1 (%2) detected in\n" + " %3").arg(msvc.version, msvc.architecture, + QDir::toNativeSeparators(msvc.binPath)); + } + + if (winSDKs.empty() && msvcs.empty()) { + qbsInfo() << Tr::tr("Could not detect an installation of " + "the Windows SDK or Visual Studio."); + return; + } + + qbsInfo() << Tr::tr("Detecting build environment..."); + std::vector msvcPtrs; + msvcPtrs.resize(winSDKs.size() + msvcs.size()); + std::transform(winSDKs.begin(), winSDKs.end(), msvcPtrs.begin(), + [] (WinSDK &sdk) -> MSVC * { return &sdk; }); + std::transform(msvcs.begin(), msvcs.end(), msvcPtrs.begin() + winSDKs.size(), + [] (MSVC &msvc) -> MSVC * { return &msvc; }); + + VsEnvironmentDetector envDetector; + envDetector.start(msvcPtrs); + + for (WinSDK &sdk : winSDKs) { + const QString name = QLatin1String("WinSDK") + sdk.version + QLatin1Char('-') + + sdk.architecture; + try { + sdk.init(); + addMSVCPlatform(settings, profiles, name, &sdk); + } catch (const ErrorInfo &error) { + qbsWarning() << Tr::tr("Failed to set up %1: %2").arg(name, error.toString()); + } + } + + for (MSVC &msvc : msvcs) { + const QString name = QLatin1String("MSVC") + msvc.version + QLatin1Char('-') + + msvc.architecture; + try { + msvc.init(); + addMSVCPlatform(settings, profiles, name, &msvc); + } catch (const ErrorInfo &error) { + qbsWarning() << Tr::tr("Failed to set up %1: %2").arg(name, error.toString()); + } + } +} + +void createMsvcProfile(const QFileInfo &compiler, Settings *settings, + const QString &profileName) +{ + const auto compilerFilePath = compiler.absoluteFilePath(); + MSVC msvc(compilerFilePath, MSVC::architectureFromClPath(compilerFilePath)); + msvc.init(); + std::vector dummy; + addMSVCPlatform(settings, dummy, profileName, &msvc); + qbsInfo() << Tr::tr("Profile '%1' created for '%2'.") + .arg(profileName, QDir::toNativeSeparators(compilerFilePath)); +} diff --git a/src/app/qbs-setup-toolchains/msvcprobe.h b/src/app/qbs-setup-toolchains/msvcprobe.h new file mode 100644 index 00000000..981bbc56 --- /dev/null +++ b/src/app/qbs-setup-toolchains/msvcprobe.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** 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 QBS_SETUPTOOLCHAINS_MSVCPROBE_H +#define QBS_SETUPTOOLCHAINS_MSVCPROBE_H + +#include + +#include + +QT_BEGIN_NAMESPACE +class QFileInfo; +QT_END_NAMESPACE + +namespace qbs { +class Profile; +class Settings; +} + +void createMsvcProfile(const QFileInfo &compiler, qbs::Settings *settings, + const QString &profileName); + +void msvcProbe(qbs::Settings *settings, std::vector &profiles); + +#endif // Header guard diff --git a/src/app/qbs-setup-toolchains/probe.cpp b/src/app/qbs-setup-toolchains/probe.cpp new file mode 100644 index 00000000..add7ba05 --- /dev/null +++ b/src/app/qbs-setup-toolchains/probe.cpp @@ -0,0 +1,303 @@ +/**************************************************************************** +** +** 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 "probe.h" + +#include "clangclprobe.h" +#include "gccprobe.h" +#include "iarewprobe.h" +#include "keilprobe.h" +#include "msvcprobe.h" +#include "sdccprobe.h" +#include "xcodeprobe.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef Q_OS_WIN +// We need defines for Windows 8. +#undef _WIN32_WINNT +#define _WIN32_WINNT _WIN32_WINNT_WIN8 + +#include +#include +#else +#include +#endif // Q_OS_WIN + +using namespace qbs; +using Internal::HostOsInfo; +using Internal::Tr; + +static QTextStream qStdout(stdout); +static QTextStream qStderr(stderr); + +QStringList systemSearchPaths() +{ + return QString::fromLocal8Bit(qgetenv("PATH")).split(HostOsInfo::pathListSeparator()); +} + +QString findExecutable(const QString &fileName) +{ + QString fullFileName = fileName; + if (HostOsInfo::isWindowsHost() + && !fileName.endsWith(QLatin1String(".exe"), Qt::CaseInsensitive)) { + fullFileName += QLatin1String(".exe"); + } + const auto ppaths = systemSearchPaths(); + for (const QString &ppath : ppaths) { + const QString fullPath = ppath + QLatin1Char('/') + fullFileName; + if (QFileInfo::exists(fullPath)) + return QDir::cleanPath(fullPath); + } + return {}; +} + +QString toolchainTypeFromCompilerName(const QString &compilerName) +{ + if (compilerName == QLatin1String("cl.exe")) + return QStringLiteral("msvc"); + if (compilerName == QLatin1String("clang-cl.exe")) + return QStringLiteral("clang-cl"); + const auto types = { QStringLiteral("clang"), QStringLiteral("llvm"), + QStringLiteral("mingw"), QStringLiteral("gcc") }; + for (const auto &type : types) { + if (compilerName.contains(type)) + return type; + } + if (compilerName == QLatin1String("g++")) + return QStringLiteral("gcc"); + if (isIarCompiler(compilerName)) + return QStringLiteral("iar"); + if (isKeilCompiler(compilerName)) + return QStringLiteral("keil"); + if (isSdccCompiler(compilerName)) + return QStringLiteral("sdcc"); + return {}; +} + +void probe(Settings *settings) +{ + std::vector profiles; + if (HostOsInfo::isWindowsHost()) { + msvcProbe(settings, profiles); + clangClProbe(settings, profiles); + } else if (HostOsInfo::isMacosHost()) { + xcodeProbe(settings, profiles); + } + + gccProbe(settings, profiles, QStringLiteral("gcc")); + gccProbe(settings, profiles, QStringLiteral("clang")); + + iarProbe(settings, profiles); + keilProbe(settings, profiles); + sdccProbe(settings, profiles); + + if (profiles.empty()) { + qStderr << Tr::tr("Could not detect any toolchains. No profile created.") << Qt::endl; + } else if (profiles.size() == 1 && settings->defaultProfile().isEmpty()) { + const QString profileName = profiles.front().name(); + qStdout << Tr::tr("Making profile '%1' the default.").arg(profileName) << Qt::endl; + settings->setValue(QStringLiteral("defaultProfile"), profileName); + } +} + +void createProfile(const QString &profileName, const QString &toolchainType, + const QString &compilerFilePath, Settings *settings) +{ + QFileInfo compiler(compilerFilePath); + if (compilerFilePath == compiler.fileName() && !compiler.exists()) + compiler = QFileInfo(findExecutable(compilerFilePath)); + + if (!compiler.exists()) { + throw qbs::ErrorInfo(Tr::tr("Compiler '%1' not found") + .arg(compilerFilePath)); + } + + const QString realToolchainType = !toolchainType.isEmpty() + ? toolchainType + : toolchainTypeFromCompilerName(compiler.fileName()); + const QStringList toolchain = canonicalToolchain(realToolchainType); + + if (toolchain.contains(QLatin1String("msvc"))) + createMsvcProfile(compiler, settings, profileName); + else if (toolchain.contains(QLatin1String("clang-cl"))) + createClangClProfile(compiler, settings, profileName); + else if (toolchain.contains(QLatin1String("gcc"))) + createGccProfile(compiler, settings, realToolchainType, profileName); + else if (toolchain.contains(QLatin1String("iar"))) + createIarProfile(compiler, settings, profileName); + else if (toolchain.contains(QLatin1String("keil"))) + createKeilProfile(compiler, settings, profileName); + else if (toolchain.contains(QLatin1String("sdcc"))) + createSdccProfile(compiler, settings, profileName); + else + throw qbs::ErrorInfo(Tr::tr("Cannot create profile: Unknown toolchain type.")); +} + +int extractVersion(const QByteArray ¯oDump, const QByteArray &keyToken) +{ + const int startIndex = macroDump.indexOf(keyToken); + if (startIndex == -1) + return -1; + const int endIndex = macroDump.indexOf('\n', startIndex); + if (endIndex == -1) + return -1; + const auto keyLength = keyToken.length(); + const int version = macroDump.mid(startIndex + keyLength, + endIndex - startIndex - keyLength) + .toInt(); + return version; +} + +static QString resolveSymlinks(const QString &filePath) +{ + QFileInfo fi(filePath); + int links = 16; + while (links-- && fi.isSymLink()) + fi.setFile(fi.dir(), fi.symLinkTarget()); + if (links <= 0) + return {}; + return fi.filePath(); +} + +// Copied from qfilesystemengine_win.cpp. +#ifdef Q_OS_WIN + +// File ID for Windows up to version 7. +static inline QByteArray fileIdWin7(HANDLE handle) +{ + BY_HANDLE_FILE_INFORMATION info; + if (GetFileInformationByHandle(handle, &info)) { + char buffer[sizeof "01234567:0123456701234567\0"]; + qsnprintf(buffer, sizeof(buffer), "%lx:%08lx%08lx", + info.dwVolumeSerialNumber, + info.nFileIndexHigh, + info.nFileIndexLow); + return QByteArray(buffer); + } + return {}; +} + +// File ID for Windows starting from version 8. +static QByteArray fileIdWin8(HANDLE handle) +{ + QByteArray result; + FILE_ID_INFO infoEx = {}; + if (::GetFileInformationByHandleEx( + handle, + static_cast(18), // FileIdInfo in Windows 8 + &infoEx, sizeof(FILE_ID_INFO))) { + result = QByteArray::number(infoEx.VolumeSerialNumber, 16); + result += ':'; + // Note: MinGW-64's definition of FILE_ID_128 differs from the MSVC one. + result += QByteArray(reinterpret_cast(&infoEx.FileId), + int(sizeof(infoEx.FileId))).toHex(); + } + return result; +} + +static QByteArray fileIdWin(HANDLE fHandle) +{ + return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8 + ? fileIdWin8(HANDLE(fHandle)) + : fileIdWin7(HANDLE(fHandle)); +} + +static QByteArray fileId(const QString &filePath) +{ + QByteArray result; + const HANDLE handle = ::CreateFile( + reinterpret_cast(filePath.utf16()), 0, + FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (handle != INVALID_HANDLE_VALUE) { + result = fileIdWin(handle); + ::CloseHandle(handle); + } + return result; +} + +static qint64 fileSize(const QString &filePath) +{ + return QFileInfo(filePath).size(); +} + +#else + +static QByteArray fileId(const QString &filePath) +{ + QByteArray result; + if (Q_UNLIKELY(filePath.isEmpty())) + return {}; + QT_STATBUF statResult = {}; + if (QT_STAT(filePath.toLocal8Bit().constData(), &statResult)) + return {}; + result = QByteArray::number(quint64(statResult.st_dev), 16); + result += ':'; + result += QByteArray::number(quint64(statResult.st_ino)); + return result; +} + +#endif // Q_OS_WIN + +bool isSameExecutable(const QString &filePath1, const QString &filePath2) +{ + if (filePath1 == filePath2) + return true; + if (resolveSymlinks(filePath1) == resolveSymlinks(filePath2)) + return true; + if (fileId(filePath1) == fileId(filePath2)) + return true; + +#ifdef Q_OS_WIN + if (fileSize(filePath1) == fileSize(filePath2)) + return true; +#endif + + return false; +} diff --git a/src/app/qbs-setup-toolchains/probe.h b/src/app/qbs-setup-toolchains/probe.h new file mode 100644 index 00000000..bce150bd --- /dev/null +++ b/src/app/qbs-setup-toolchains/probe.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** 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 QBS_SETUPTOOLCHAINS_PROBE_H +#define QBS_SETUPTOOLCHAINS_PROBE_H + +#include + +#include + +#include // for std::tie + +QT_BEGIN_NAMESPACE +class QString; +class QStringList; +QT_END_NAMESPACE + +namespace qbs { class Settings; } + +QStringList systemSearchPaths(); + +QString findExecutable(const QString &fileName); + +QString toolchainTypeFromCompilerName(const QString &compilerName); + +void createProfile(const QString &profileName, const QString &toolchainType, + const QString &compilerFilePath, qbs::Settings *settings); + +void probe(qbs::Settings *settings); + +struct ToolchainInstallInfo +{ + QFileInfo compilerPath; + qbs::Version compilerVersion; +}; + +inline bool operator<(const ToolchainInstallInfo &lhs, const ToolchainInstallInfo &rhs) +{ + const auto lp = lhs.compilerPath.absoluteFilePath(); + const auto rp = rhs.compilerPath.absoluteFilePath(); + return std::tie(lp, lhs.compilerVersion) < std::tie(rp, rhs.compilerVersion); +} + +int extractVersion(const QByteArray ¯oDump, const QByteArray &keyToken); + +bool isSameExecutable(const QString &exe1, const QString &exe2); + +#endif // Header guard diff --git a/src/app/qbs-setup-toolchains/qbs-setup-toolchains.exe.manifest b/src/app/qbs-setup-toolchains/qbs-setup-toolchains.exe.manifest new file mode 100644 index 00000000..c2114cc8 --- /dev/null +++ b/src/app/qbs-setup-toolchains/qbs-setup-toolchains.exe.manifest @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/src/app/qbs-setup-toolchains/qbs-setup-toolchains.pro b/src/app/qbs-setup-toolchains/qbs-setup-toolchains.pro new file mode 100644 index 00000000..6581bec6 --- /dev/null +++ b/src/app/qbs-setup-toolchains/qbs-setup-toolchains.pro @@ -0,0 +1,30 @@ +include(../app.pri) + +TARGET = qbs-setup-toolchains + +HEADERS += \ + clangclprobe.h \ + commandlineparser.h \ + gccprobe.h \ + iarewprobe.h \ + keilprobe.h \ + msvcprobe.h \ + probe.h \ + sdccprobe.h \ + xcodeprobe.h \ + +SOURCES += \ + clangclprobe.cpp \ + commandlineparser.cpp \ + gccprobe.cpp \ + iarewprobe.cpp \ + keilprobe.cpp \ + main.cpp \ + msvcprobe.cpp \ + probe.cpp \ + sdccprobe.cpp \ + xcodeprobe.cpp \ + +mingw { + RC_FILE = qbs-setup-toolchains.rc +} diff --git a/src/app/qbs-setup-toolchains/qbs-setup-toolchains.qbs b/src/app/qbs-setup-toolchains/qbs-setup-toolchains.qbs new file mode 100644 index 00000000..3bc72700 --- /dev/null +++ b/src/app/qbs-setup-toolchains/qbs-setup-toolchains.qbs @@ -0,0 +1,37 @@ +import qbs 1.0 + +QbsApp { + name: "qbs-setup-toolchains" + cpp.dynamicLibraries: qbs.targetOS.contains("windows") ? base.concat("shell32") : base + files: [ + "clangclprobe.cpp", + "clangclprobe.h", + "commandlineparser.cpp", + "commandlineparser.h", + "gccprobe.cpp", + "gccprobe.h", + "iarewprobe.cpp", + "iarewprobe.h", + "keilprobe.cpp", + "keilprobe.h", + "main.cpp", + "msvcprobe.cpp", + "msvcprobe.h", + "probe.cpp", + "probe.h", + "sdccprobe.cpp", + "sdccprobe.h", + "xcodeprobe.cpp", + "xcodeprobe.h", + ] + Group { + name: "MinGW specific files" + condition: qbs.toolchain.contains("mingw") + files: "qbs-setup-toolchains.rc" + Group { + name: "qbs-setup-toolchains manifest" + files: "qbs-setup-toolchains.exe.manifest" + fileTags: [] // the manifest is referenced by the rc file + } + } +} diff --git a/src/app/qbs-setup-toolchains/qbs-setup-toolchains.rc b/src/app/qbs-setup-toolchains/qbs-setup-toolchains.rc new file mode 100644 index 00000000..0f53403f --- /dev/null +++ b/src/app/qbs-setup-toolchains/qbs-setup-toolchains.rc @@ -0,0 +1,4 @@ +#define RT_MANIFEST 24 +#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 + +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "qbs-setup-toolchains.exe.manifest" diff --git a/src/app/qbs-setup-toolchains/sdccprobe.cpp b/src/app/qbs-setup-toolchains/sdccprobe.cpp new file mode 100644 index 00000000..977d834c --- /dev/null +++ b/src/app/qbs-setup-toolchains/sdccprobe.cpp @@ -0,0 +1,302 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "probe.h" +#include "sdccprobe.h" + +#include "../shared/logging/consolelogger.h" + +#include + +#include +#include + +#include +#include +#include + +using namespace qbs; +using Internal::Tr; +using Internal::HostOsInfo; + +static QStringList knownSdccCompilerNames() +{ + return {QStringLiteral("sdcc")}; +} + +static QByteArray dumpSdccMacros(const QFileInfo &compiler, + const QString &targetFlag = QString()) +{ + QTemporaryFile fakeIn(QStringLiteral("XXXXXX.c")); + if (!fakeIn.open()) { + qbsWarning() << Tr::tr("Unable to open temporary file %1 for output: %2") + .arg(fakeIn.fileName(), fakeIn.errorString()); + return {}; + } + fakeIn.close(); + + const QStringList args = {QStringLiteral("-dM"), + QStringLiteral("-E"), + targetFlag, + fakeIn.fileName()}; + QProcess p; + p.start(compiler.absoluteFilePath(), args); + p.waitForFinished(3000); + const auto es = p.exitStatus(); + if (es != QProcess::NormalExit) { + const QByteArray out = p.readAll(); + qbsWarning() << Tr::tr("Compiler dumping failed:\n%1") + .arg(QString::fromUtf8(out)); + return {}; + } + + return p.readAllStandardOutput(); +} + +static QString dumpSdccArchitecture(const QFileInfo &compiler, + const QString &arch) +{ + const auto targetFlag = QStringLiteral("-m%1").arg(arch); + const auto token = QStringLiteral("__SDCC_%1").arg(arch); + const auto macros = dumpSdccMacros(compiler, targetFlag); + return macros.contains(token.toLatin1()) ? arch : QString(); +} + +static std::vector createSdccProfileHelper( + const ToolchainInstallInfo &info, + Settings *settings, + const QString &profileName = QString()) +{ + const QFileInfo compiler = info.compilerPath; + + std::vector profiles; + + const char *knownArchs[] = {"mcs51", "stm8"}; + for (const auto &knownArch : knownArchs) { + const auto actualArch = dumpSdccArchitecture( + compiler, QString::fromLatin1(knownArch)); + // Don't create a profile in case the compiler does + // not support the proposed architecture. + if (actualArch != QString::fromLatin1(knownArch)) + continue; + + QString fullProfileName; + if (profileName.isEmpty()) { + // Create a full profile name in case we is + // in auto-detecting mode. + if (!info.compilerVersion.isValid()) { + fullProfileName = QStringLiteral("sdcc-unknown-%1") + .arg(actualArch); + } else { + const QString version = info.compilerVersion.toString( + QLatin1Char('_'), QLatin1Char('_')); + fullProfileName = QStringLiteral("sdcc-%1-%2").arg( + version, actualArch); + } + } else { + // Append the detected actual architecture name + // to the proposed profile name. + fullProfileName = QStringLiteral("%1-%2").arg( + profileName, actualArch); + } + + Profile profile(fullProfileName, settings); + profile.setValue(QStringLiteral("cpp.toolchainInstallPath"), + compiler.absolutePath()); + profile.setValue(QStringLiteral("qbs.toolchainType"), + QStringLiteral("sdcc")); + if (!actualArch.isEmpty()) + profile.setValue(QStringLiteral("qbs.architecture"), actualArch); + + qbsInfo() << Tr::tr("Profile '%1' created for '%2'.").arg( + profile.name(), compiler.absoluteFilePath()); + } + + return profiles; +} + +static Version dumpOldSddcCompilerVersion(const QByteArray ¯oDump) +{ + const auto keyToken = QByteArrayLiteral("__SDCC "); + const int startIndex = macroDump.indexOf(keyToken); + if (startIndex == -1) + return Version{}; + const int endIndex = macroDump.indexOf('\n', startIndex); + if (endIndex == -1) + return Version{}; + const auto keyLength = keyToken.length(); + return Version::fromString(QString::fromLatin1( + macroDump.mid(startIndex + keyLength, + endIndex - startIndex - keyLength).replace('_', '.'))); +} + +static Version dumpSdccCompilerVersion(const QFileInfo &compiler) +{ + const QByteArray dump = dumpSdccMacros(compiler); + if (dump.isEmpty()) + return Version{}; + + const int major = extractVersion(dump, "__SDCC_VERSION_MAJOR "); + const int minor = extractVersion(dump, "__SDCC_VERSION_MINOR "); + const int patch = extractVersion(dump, "__SDCC_VERSION_PATCH "); + if (major < 0 || minor < 0 || patch < 0) { + const auto version = dumpOldSddcCompilerVersion(dump); + if (!version.isValid()) { + qbsWarning() << Tr::tr("No '__SDCC_VERSION_xxx' or '__SDCC' token was found " + "in the compiler dump:\n%1") + .arg(QString::fromUtf8(dump)); + return Version{}; + } + return version; + } + + return Version{major, minor, patch}; +} + +static std::vector installedSdccsFromPath() +{ + std::vector infos; + const auto compilerNames = knownSdccCompilerNames(); + for (const QString &compilerName : compilerNames) { + const QFileInfo sdccPath( + findExecutable( + HostOsInfo::appendExecutableSuffix(compilerName))); + if (!sdccPath.exists()) + continue; + const Version version = dumpSdccCompilerVersion(sdccPath); + infos.push_back({sdccPath, version}); + } + std::sort(infos.begin(), infos.end()); + return infos; +} + +static std::vector installedSdccsFromRegistry() +{ + std::vector infos; + + if (HostOsInfo::isWindowsHost()) { + // Tries to detect the candidate from the 32-bit + // or 64-bit system registry format. + auto probeSdccToolchainInfo = [](QSettings::Format format) { + SdccInstallInfo info; + QSettings registry(QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\SDCC"), + format); + const QString rootPath = registry.value(QStringLiteral("Default")) + .toString(); + if (rootPath.isEmpty()) + return info; + // Build full compiler path. + const QFileInfo sdccPath(rootPath + QLatin1String("\\bin\\sdcc.exe")); + if (!sdccPath.exists()) + return info; + info.compilerPath = sdccPath.filePath(); + // Build compiler version. + const QString version = QStringLiteral("%1.%2.%3").arg( + registry.value(QStringLiteral("VersionMajor")).toString(), + registry.value(QStringLiteral("VersionMinor")).toString(), + registry.value(QStringLiteral("VersionRevision")).toString()); + info.version = version; + return info; + }; + + static constexpr QSettings::Format allowedFormats[] = { + QSettings::NativeFormat, +#ifdef Q_OS_WIN + QSettings::Registry32Format, + QSettings::Registry64Format, +#endif + }; + + for (const QSettings::Format format : allowedFormats) { + const SdccInstallInfo candidate = probeSdccToolchainInfo(format); + if (candidate.compilerPath.isEmpty()) + continue; + const auto infosEnd = infos.cend(); + const auto infosIt = std::find_if(infos.cbegin(), infosEnd, + [candidate](const ToolchainInstallInfo &info) { + return candidate == SdccInstallInfo{ + info.compilerPath.filePath(), info.compilerVersion.toString()}; + }); + if (infosIt == infosEnd) + infos.push_back({candidate.compilerPath, Version::fromString(candidate.version)}); + } + } + + std::sort(infos.begin(), infos.end()); + return infos; +} + +bool isSdccCompiler(const QString &compilerName) +{ + return Internal::any_of(knownSdccCompilerNames(), [compilerName]( + const QString &knownName) { + return compilerName.contains(knownName); + }); +} + +void createSdccProfile(const QFileInfo &compiler, Settings *settings, + const QString &profileName) +{ + const ToolchainInstallInfo info = {compiler, Version{}}; + createSdccProfileHelper(info, settings, profileName); +} + +void sdccProbe(Settings *settings, std::vector &profiles) +{ + qbsInfo() << Tr::tr("Trying to detect SDCC toolchains..."); + + // Make sure that a returned infos are sorted before using the std::set_union! + const std::vector regInfos = installedSdccsFromRegistry(); + const std::vector pathInfos = installedSdccsFromPath(); + std::vector allInfos; + allInfos.reserve(regInfos.size() + pathInfos.size()); + std::set_union(regInfos.cbegin(), regInfos.cend(), + pathInfos.cbegin(), pathInfos.cend(), + std::back_inserter(allInfos)); + + for (const ToolchainInstallInfo &info : allInfos) { + const auto newProfiles = createSdccProfileHelper(info, settings); + profiles.reserve(profiles.size() + int(newProfiles.size())); + std::copy(newProfiles.cbegin(), newProfiles.cend(), + std::back_inserter(profiles)); + } + + if (allInfos.empty()) + qbsInfo() << Tr::tr("No SDCC toolchains found."); +} diff --git a/src/app/qbs-setup-toolchains/sdccprobe.h b/src/app/qbs-setup-toolchains/sdccprobe.h new file mode 100644 index 00000000..4c913dde --- /dev/null +++ b/src/app/qbs-setup-toolchains/sdccprobe.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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_SETUPTOOLCHAINS_SDCCPROBE_H +#define QBS_SETUPTOOLCHAINS_SDCCPROBE_H + +#include + +#include + +QT_BEGIN_NAMESPACE +class QFileInfo; +QT_END_NAMESPACE + +namespace qbs { +class Profile; +class Settings; +} + +struct SdccInstallInfo +{ + QString compilerPath; + QString version; +}; + +inline bool operator==(const SdccInstallInfo &lhs, const SdccInstallInfo &rhs) +{ + return std::tie(lhs.compilerPath, lhs.version) + == std::tie(rhs.compilerPath, rhs.version); +} + +bool isSdccCompiler(const QString &compilerName); + +void createSdccProfile(const QFileInfo &compiler, qbs::Settings *settings, + const QString &profileName); + +void sdccProbe(qbs::Settings *settings, std::vector &profiles); + +#endif // Header guard diff --git a/src/app/qbs-setup-toolchains/xcodeprobe.cpp b/src/app/qbs-setup-toolchains/xcodeprobe.cpp new file mode 100644 index 00000000..6c3d8a1b --- /dev/null +++ b/src/app/qbs-setup-toolchains/xcodeprobe.cpp @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** 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 "probe.h" +#include "xcodeprobe.h" + +#include "../shared/logging/consolelogger.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace qbs; +using Internal::Tr; + +static const QString defaultDeveloperPath = + QStringLiteral("/Applications/Xcode.app/Contents/Developer"); +static const std::regex defaultDeveloperPathRegex( + "^/Applications/Xcode([a-zA-Z0-9 _-]+)\\.app/Contents/Developer$"); + +static QString targetOS(const QString &applePlatformName) +{ + if (applePlatformName == QStringLiteral("macosx")) + return QStringLiteral("macos"); + if (applePlatformName == QStringLiteral("iphoneos")) + return QStringLiteral("ios"); + if (applePlatformName == QStringLiteral("iphonesimulator")) + return QStringLiteral("ios-simulator"); + if (applePlatformName == QStringLiteral("appletvos")) + return QStringLiteral("tvos"); + if (applePlatformName == QStringLiteral("appletvsimulator")) + return QStringLiteral("tvos-simulator"); + if (applePlatformName == QStringLiteral("watchos")) + return QStringLiteral("watchos"); + if (applePlatformName == QStringLiteral("watchsimulator")) + return QStringLiteral("watchos-simulator"); + return {}; +} + +static QStringList archList(const QString &applePlatformName) +{ + QStringList archs; + if (applePlatformName == QStringLiteral("macosx") + || applePlatformName == QStringLiteral("iphonesimulator") + || applePlatformName == QStringLiteral("appletvsimulator") + || applePlatformName == QStringLiteral("watchsimulator")) { + if (applePlatformName != QStringLiteral("appletvsimulator")) + archs << QStringLiteral("x86"); + if (applePlatformName != QStringLiteral("watchsimulator")) + archs << QStringLiteral("x86_64"); + if (applePlatformName == QStringLiteral("macosx")) + archs << QStringLiteral("arm64"); + } else if (applePlatformName == QStringLiteral("iphoneos") + || applePlatformName == QStringLiteral("appletvos")) { + if (applePlatformName != QStringLiteral("appletvos")) + archs << QStringLiteral("armv7a"); + archs << QStringLiteral("arm64"); + } else if (applePlatformName == QStringLiteral("watchos")) { + archs << QStringLiteral("armv7k"); + } + + return archs; +} + +namespace { +class XcodeProbe +{ +public: + XcodeProbe(qbs::Settings *settings, std::vector &profiles) + : settings(settings), profiles(profiles) + { } + + bool addDeveloperPath(const QString &path); + void detectDeveloperPaths(); + void setupDefaultToolchains(const QString &devPath, const QString &xCodeName); + void detectAll(); +private: + qbs::Settings *settings; + std::vector &profiles; + QStringList developerPaths; +}; + +bool XcodeProbe::addDeveloperPath(const QString &path) +{ + if (path.isEmpty()) + return false; + QFileInfo pInfo(path); + if (!pInfo.exists() || !pInfo.isDir()) + return false; + if (developerPaths.contains(path)) + return false; + developerPaths.push_back(path); + qbsInfo() << Tr::tr("Added developer path %1").arg(path); + return true; +} + +void XcodeProbe::detectDeveloperPaths() +{ + QProcess selectedXcode; + QString program = QStringLiteral("/usr/bin/xcode-select"); + QStringList arguments(QStringLiteral("--print-path")); + selectedXcode.start(program, arguments, QProcess::ReadOnly); + if (!selectedXcode.waitForFinished(-1) || selectedXcode.exitCode()) { + qbsInfo() << Tr::tr("Could not detect selected Xcode with /usr/bin/xcode-select"); + } else { + QString path = QString::fromLocal8Bit(selectedXcode.readAllStandardOutput()); + addDeveloperPath(path); + } + addDeveloperPath(defaultDeveloperPath); + + QProcess launchServices; + program = QStringLiteral("/usr/bin/mdfind"); + arguments = QStringList(QStringLiteral("kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'")); + launchServices.start(program, arguments, QProcess::ReadOnly); + if (!launchServices.waitForFinished(-1) || launchServices.exitCode()) { + qbsInfo() << Tr::tr("Could not detect additional Xcode installations with /usr/bin/mdfind"); + } else { + const auto paths = QString::fromLocal8Bit(launchServices.readAllStandardOutput()) + .split(QLatin1Char('\n'), QBS_SKIP_EMPTY_PARTS); + for (const QString &path : paths) + addDeveloperPath(path + QStringLiteral("/Contents/Developer")); + } +} + +void XcodeProbe::setupDefaultToolchains(const QString &devPath, const QString &xcodeName) +{ + qbsInfo() << Tr::tr("Profile '%1' created for '%2'.").arg(xcodeName).arg(devPath); + + Profile installationProfile(xcodeName, settings); + installationProfile.removeProfile(); + installationProfile.setValue(QStringLiteral("qbs.toolchainType"), QStringLiteral("xcode")); + if (devPath != defaultDeveloperPath) + installationProfile.setValue(QStringLiteral("xcode.developerPath"), devPath); + profiles.push_back(installationProfile); + + QStringList platforms; + platforms << QStringLiteral("macosx") + << QStringLiteral("iphoneos") + << QStringLiteral("iphonesimulator") + << QStringLiteral("appletvos") + << QStringLiteral("appletvsimulator") + << QStringLiteral("watchos") + << QStringLiteral("watchsimulator"); + for (const QString &platform : qAsConst(platforms)) { + Profile platformProfile(xcodeName + QLatin1Char('-') + platform, settings); + platformProfile.removeProfile(); + platformProfile.setBaseProfile(installationProfile.name()); + platformProfile.setValue(QStringLiteral("qbs.targetPlatform"), targetOS(platform)); + profiles.push_back(platformProfile); + + const auto architectures = archList(platform); + for (const QString &arch : architectures) { + Profile archProfile(xcodeName + QLatin1Char('-') + platform + QLatin1Char('-') + arch, + settings); + archProfile.removeProfile(); + archProfile.setBaseProfile(platformProfile.name()); + archProfile.setValue(QStringLiteral("qbs.architecture"), + qbs::canonicalArchitecture(arch)); + profiles.push_back(archProfile); + } + } +} + +void XcodeProbe::detectAll() +{ + int i = 1; + detectDeveloperPaths(); + for (const QString &developerPath : qAsConst(developerPaths)) { + QString profileName = QStringLiteral("xcode"); + if (developerPath != defaultDeveloperPath) { + const auto devPath = developerPath.toStdString(); + std::smatch match; + if (std::regex_match(devPath, match, defaultDeveloperPathRegex)) + profileName += QString::fromStdString(match[1]).toLower().replace(QLatin1Char(' '), + QLatin1Char('-')); + else + profileName += QString::number(i++); + } + setupDefaultToolchains(developerPath, profileName); + } +} +} // end anonymous namespace + +void xcodeProbe(qbs::Settings *settings, std::vector &profiles) +{ + XcodeProbe probe(settings, profiles); + probe.detectAll(); +} diff --git a/src/app/qbs-setup-toolchains/xcodeprobe.h b/src/app/qbs-setup-toolchains/xcodeprobe.h new file mode 100644 index 00000000..fc15d177 --- /dev/null +++ b/src/app/qbs-setup-toolchains/xcodeprobe.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** 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 QBS_SETUPTOOLCHAINS_XCODEPROBE_H +#define QBS_SETUPTOOLCHAINS_XCODEPROBE_H + +#include + +namespace qbs { +class Profile; +class Settings; +} + +void xcodeProbe(qbs::Settings *settings, std::vector &profiles); + +#endif // Header guard diff --git a/src/app/qbs/application.cpp b/src/app/qbs/application.cpp new file mode 100644 index 00000000..2e654324 --- /dev/null +++ b/src/app/qbs/application.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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 "application.h" + +#include "commandlinefrontend.h" +#include "ctrlchandler.h" + +namespace qbs { + +Application::Application(int &argc, char **argv) + : QCoreApplication(argc, argv), m_clFrontend(nullptr), m_canceled(false) +{ + setApplicationName(QStringLiteral("qbs")); + setOrganizationName(QStringLiteral("QtProject")); + setOrganizationDomain(QStringLiteral("qt-project.org")); +} + +Application *Application::instance() +{ + return qobject_cast(QCoreApplication::instance()); +} + +void Application::setCommandLineFrontend(CommandLineFrontend *clFrontend) +{ + installCtrlCHandler(); + m_clFrontend = clFrontend; +} + +/** + * Interrupt the application. This is directly called from a signal handler. + */ +void Application::userInterrupt() +{ + if (m_canceled) + return; + Q_ASSERT(m_clFrontend); + m_canceled = true; + m_clFrontend->cancel(); +} + +} // namespace qbs diff --git a/src/app/qbs/application.h b/src/app/qbs/application.h new file mode 100644 index 00000000..747c2f7e --- /dev/null +++ b/src/app/qbs/application.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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 APPLICATION_H +#define APPLICATION_H + +#include + +namespace qbs { +class CommandLineFrontend; + +class Application : public QCoreApplication +{ + Q_OBJECT +public: + Application(int &argc, char **argv); + + static Application *instance(); + + void setCommandLineFrontend(CommandLineFrontend *clFrontend); + void userInterrupt(); + +private: + CommandLineFrontend *m_clFrontend; + bool m_canceled; +}; + +} // namespace qbs + +#endif // APPLICATION_H diff --git a/src/app/qbs/commandlinefrontend.cpp b/src/app/qbs/commandlinefrontend.cpp new file mode 100644 index 00000000..4a28b93e --- /dev/null +++ b/src/app/qbs/commandlinefrontend.cpp @@ -0,0 +1,704 @@ +/**************************************************************************** +** +** 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 "commandlinefrontend.h" + +#include "application.h" +#include "consoleprogressobserver.h" +#include "session.h" +#include "status.h" +#include "parser/commandlineoption.h" +#include "../shared/logging/consolelogger.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace qbs { +using namespace Internal; + +CommandLineFrontend::CommandLineFrontend(const CommandLineParser &parser, Settings *settings, + QObject *parent) + : QObject(parent) + , m_parser(parser) + , m_settings(settings) + , m_observer(nullptr) + , m_cancelStatus(CancelStatusNone) + , m_cancelTimer(new QTimer(this)) +{ +} + +CommandLineFrontend::~CommandLineFrontend() +{ + m_cancelTimer->stop(); + delete m_observer; +} + +// Called from interrupt handler. Don't do anything non-trivial here. +void CommandLineFrontend::cancel() +{ + m_cancelStatus = CancelStatusRequested; +} + +void CommandLineFrontend::checkCancelStatus() +{ + switch (m_cancelStatus) { + case CancelStatusNone: + break; + case CancelStatusRequested: + m_cancelStatus = CancelStatusCanceling; + m_cancelTimer->stop(); + if (m_resolveJobs.empty() && m_buildJobs.empty()) + std::exit(EXIT_FAILURE); + for (AbstractJob * const job : qAsConst(m_resolveJobs)) + job->cancel(); + for (AbstractJob * const job : qAsConst(m_buildJobs)) + job->cancel(); + break; + case CancelStatusCanceling: + QBS_ASSERT(false, return); + break; + } +} + +void CommandLineFrontend::start() +{ + try { + switch (m_parser.command()) { + case RunCommandType: + case ShellCommandType: + if (m_parser.products().size() > 1) { + throw ErrorInfo(Tr::tr("Invalid use of command '%1': Cannot use more than one " + "product.\nUsage: %2") + .arg(m_parser.commandName(), m_parser.commandDescription())); + } + Q_FALLTHROUGH(); + case StatusCommandType: + case InstallCommandType: + case DumpNodesTreeCommandType: + case ListProductsCommandType: + if (m_parser.buildConfigurations().size() > 1) { + QString error = Tr::tr("Invalid use of command '%1': There can be only one " + "build configuration.\n").arg(m_parser.commandName()); + error += Tr::tr("Usage: %1").arg(m_parser.commandDescription()); + throw ErrorInfo(error); + } + break; + case SessionCommandType: { + startSession(); + return; + } + default: + break; + } + + if (m_parser.showVersion()) { + puts(QBS_VERSION); + qApp->exit(EXIT_SUCCESS); + return; + } + if (m_parser.showProgress()) + m_observer = new ConsoleProgressObserver; + SetupProjectParameters params; + params.setEnvironment(QProcessEnvironment::systemEnvironment()); + params.setProjectFilePath(m_parser.projectFilePath()); + params.setDryRun(m_parser.dryRun()); + params.setForceProbeExecution(m_parser.forceProbesExecution()); + params.setWaitLockBuildGraph(m_parser.waitLockBuildGraph()); + params.setFallbackProviderEnabled(!m_parser.disableFallbackProvider()); + params.setLogElapsedTime(m_parser.logTime()); + params.setSettingsDirectory(m_settings->baseDirectory()); + params.setOverrideBuildGraphData(m_parser.command() == ResolveCommandType); + params.setPropertyCheckingMode(ErrorHandlingMode::Strict); + if (!m_parser.buildBeforeInstalling() || !m_parser.commandCanResolve()) + params.setRestoreBehavior(SetupProjectParameters::RestoreOnly); + const auto buildConfigs = m_parser.buildConfigurations(); + for (const QVariantMap &buildConfig : buildConfigs) { + QVariantMap userConfig = buildConfig; + const QString configurationKey = QStringLiteral("qbs.configurationName"); + const QString profileKey = QStringLiteral("qbs.profile"); + const QString installRootKey = QStringLiteral("qbs.installRoot"); + QString installRoot = userConfig.value(installRootKey).toString(); + if (!installRoot.isEmpty() && QFileInfo(installRoot).isRelative()) { + installRoot.prepend(QLatin1Char('/')).prepend(QDir::currentPath()); + userConfig.insert(installRootKey, installRoot); + } + const QString configurationName = userConfig.take(configurationKey).toString(); + const QString profileName = userConfig.take(profileKey).toString(); + const Preferences prefs(m_settings); + params.setSearchPaths(prefs.searchPaths(QDir::cleanPath(QCoreApplication::applicationDirPath() + + QLatin1String("/" QBS_RELATIVE_SEARCH_PATH)))); + params.setPluginPaths(prefs.pluginPaths(QDir::cleanPath(QCoreApplication::applicationDirPath() + + QLatin1String("/" QBS_RELATIVE_PLUGINS_PATH)))); + params.setLibexecPath(QDir::cleanPath(QCoreApplication::applicationDirPath() + + QLatin1String("/" QBS_RELATIVE_LIBEXEC_PATH))); + params.setTopLevelProfile(profileName); + params.setConfigurationName(configurationName); + params.setBuildRoot(buildDirectory(profileName)); + params.setOverriddenValues(userConfig); + SetupProjectJob * const job = Project().setupProject(params, + ConsoleLogger::instance().logSink(), this); + connectJob(job); + m_resolveJobs.push_back(job); + } + + /* + * Progress reporting on the terminal gets a bit tricky when resolving several projects + * concurrently, since we cannot show multiple progress bars at the same time. Instead, + * we just set the total effort to the number of projects and increase the progress + * every time one of them finishes, ignoring the progress reports from the jobs themselves. + * (Yes, that does mean it will take disproportionately long for the first progress + * notification to arrive.) + */ + if (m_parser.showProgress() && resolvingMultipleProjects()) + m_observer->initialize(tr("Setting up projects"), m_resolveJobs.size()); + + // Check every two seconds whether we received a cancel request. This value has been + // experimentally found to be acceptable. + // Note that this polling approach is not problematic here, since we are doing work anyway, + // so there's no danger of waking up the processor for no reason. + connect(m_cancelTimer, &QTimer::timeout, this, &CommandLineFrontend::checkCancelStatus); + m_cancelTimer->start(2000); + } catch (const ErrorInfo &error) { + qbsError() << error.toString(); + if (m_buildJobs.empty() && m_resolveJobs.empty()) { + qApp->exit(EXIT_FAILURE); + } else { + cancel(); + checkCancelStatus(); + } + } +} + +void CommandLineFrontend::handleCommandDescriptionReport(const QString &highlight, + const QString &message) +{ + qbsInfo() << MessageTag(highlight) << message; +} + +void CommandLineFrontend::handleJobFinished(bool success, AbstractJob *job) +{ + try { + job->deleteLater(); + if (!success) { + qbsError() << job->error().toString(); + m_resolveJobs.removeOne(job); + m_buildJobs.removeOne(job); + if (m_resolveJobs.empty() && m_buildJobs.empty()) { + qApp->exit(EXIT_FAILURE); + return; + } + cancel(); + } else if (const auto setupJob = qobject_cast(job)) { + m_resolveJobs.removeOne(job); + m_projects.push_back(setupJob->project()); + if (m_observer && resolvingMultipleProjects()) + m_observer->incrementProgressValue(); + if (m_resolveJobs.empty()) + handleProjectsResolved(); + } else if (qobject_cast(job)) { + if (m_parser.command() == RunCommandType) + qApp->exit(runTarget()); + else + qApp->quit(); + } else { // Build or clean. + m_buildJobs.removeOne(job); + if (m_buildJobs.empty()) { + switch (m_parser.command()) { + case RunCommandType: + case InstallCommandType: + install(); + break; + case GenerateCommandType: + generate(); + // fall through + case BuildCommandType: + case CleanCommandType: + qApp->exit(m_cancelStatus == CancelStatusNone ? EXIT_SUCCESS : EXIT_FAILURE); + break; + default: + Q_ASSERT_X(false, Q_FUNC_INFO, "Missing case in switch statement"); + } + } + } + } catch (const ErrorInfo &error) { + qbsError() << error.toString(); + qApp->exit(EXIT_FAILURE); + } +} + +void CommandLineFrontend::handleNewTaskStarted(const QString &description, int totalEffort) +{ + // If the user does not want a progress bar, we just print the current activity. + if (!m_parser.showProgress()) { + if (!m_parser.logTime()) + qbsInfo() << description; + return; + } + if (isBuilding()) { + m_totalBuildEffort += totalEffort; + if (++m_buildEffortsRetrieved == m_buildEffortsNeeded) { + m_observer->initialize(tr("Building"), m_totalBuildEffort); + if (m_currentBuildEffort > 0) + m_observer->setProgressValue(m_currentBuildEffort); + } + } else if (!resolvingMultipleProjects()) { + m_observer->initialize(description, totalEffort); + } +} + +void CommandLineFrontend::handleTotalEffortChanged(int totalEffort) +{ + // Can only happen when resolving. + if (m_parser.showProgress() && !isBuilding() && !resolvingMultipleProjects()) + m_observer->setMaximum(totalEffort); +} + +void CommandLineFrontend::handleTaskProgress(int value, AbstractJob *job) +{ + if (isBuilding()) { + int ¤tJobEffort = m_buildEfforts[job]; + m_currentBuildEffort += value - currentJobEffort; + currentJobEffort = value; + if (m_buildEffortsRetrieved == m_buildEffortsNeeded) + m_observer->setProgressValue(m_currentBuildEffort); + } else if (!resolvingMultipleProjects()) { + m_observer->setProgressValue(value); + } +} + +void CommandLineFrontend::handleProcessResultReport(const qbs::ProcessResult &result) +{ + bool hasOutput = !result.stdOut().empty() || !result.stdErr().empty(); + if (!hasOutput && result.success()) + return; + + LogWriter w = result.success() ? qbsInfo() : qbsError(); + w << shellQuote(QDir::toNativeSeparators(result.executableFilePath()), result.arguments()) + << (hasOutput ? QStringLiteral("\n") : QString()) + << (result.stdOut().empty() ? QString() : result.stdOut().join(QLatin1Char('\n'))); + if (!result.stdErr().empty()) + w << result.stdErr().join(QLatin1Char('\n')) << MessageTag(QStringLiteral("stdErr")); +} + +bool CommandLineFrontend::resolvingMultipleProjects() const +{ + return isResolving() && m_resolveJobs.size() + m_projects.size() > 1; +} + +bool CommandLineFrontend::isResolving() const +{ + return !m_resolveJobs.empty(); +} + +bool CommandLineFrontend::isBuilding() const +{ + return !m_buildJobs.empty(); +} + +CommandLineFrontend::ProductMap CommandLineFrontend::productsToUse() const +{ + ProductMap products; + QStringList productNames; + const bool useAll = m_parser.products().empty(); + for (const Project &project : qAsConst(m_projects)) { + QList &productList = products[project]; + const ProjectData projectData = project.projectData(); + for (const ProductData &product : projectData.allProducts()) { + productNames << product.name(); + if (useAll || m_parser.products().contains(product.name())) { + productList.push_back(product); + } + } + } + + const auto parsedProductNames = m_parser.products(); + for (const QString &productName : parsedProductNames) { + if (!productNames.contains(productName)) { + QString msg; + if (productNames.size() <= 10) { + productNames.sort(); + const QString available = productNames.join(QLatin1String("', '")); + msg = Tr::tr("No such product '%1'. " + "Available products: '%2'").arg(productName, available); + } else { + msg = Tr::tr("No such product '%1'. Use 'list-products' to see " + "all available products.").arg(productName); + } + throw ErrorInfo(msg); + } + } + + return products; +} + +void CommandLineFrontend::handleProjectsResolved() +{ + if (m_cancelStatus != CancelStatusNone) + throw ErrorInfo(Tr::tr("Execution canceled.")); + switch (m_parser.command()) { + case ResolveCommandType: + qApp->quit(); + break; + case CleanCommandType: + makeClean(); + break; + case ShellCommandType: + qApp->exit(runShell()); + break; + case StatusCommandType: + qApp->exit(printStatus(m_projects.front().projectData())); + break; + case GenerateCommandType: + checkGeneratorName(); + Q_FALLTHROUGH(); + case BuildCommandType: + build(); + break; + case InstallCommandType: + case RunCommandType: + if (m_parser.buildBeforeInstalling()) + build(); + else + install(); + break; + case UpdateTimestampsCommandType: + updateTimestamps(); + qApp->quit(); + break; + case DumpNodesTreeCommandType: + dumpNodesTree(); + qApp->quit(); + break; + case ListProductsCommandType: + listProducts(); + qApp->quit(); + break; + case HelpCommandType: + case VersionCommandType: + case SessionCommandType: + Q_ASSERT_X(false, Q_FUNC_INFO, "Impossible."); + } +} + +void CommandLineFrontend::makeClean() +{ + if (m_parser.products().empty()) { + for (const Project &project : qAsConst(m_projects)) { + m_buildJobs << project.cleanAllProducts(m_parser.cleanOptions(project.profile()), this); + } + } else { + const ProductMap &products = productsToUse(); + for (ProductMap::ConstIterator it = products.begin(); it != products.end(); ++it) { + m_buildJobs.push_back(it.key().cleanSomeProducts( + it.value(), + m_parser.cleanOptions(it.key().profile()), + this)); + } + } + connectBuildJobs(); +} + +int CommandLineFrontend::runShell() +{ + ProductData productToRun; + switch (m_parser.products().size()) { + case 0: // No specific product, use project-global environment. + break; + case 1: + productToRun = productsToUse().values().front().front(); + break; + default: + throw ErrorInfo(Tr::tr("The command '%1' cannot take more than one product.")); + } + RunEnvironment runEnvironment = m_projects.front().getRunEnvironment(productToRun, + m_parser.installOptions(m_projects.front().profile()), + QProcessEnvironment::systemEnvironment(), QStringList(), m_settings); + return runEnvironment.doRunShell(); +} + +BuildOptions CommandLineFrontend::buildOptions(const Project &project) const +{ + BuildOptions options = m_parser.buildOptions(m_projects.front().profile()); + if (options.maxJobCount() <= 0) { + const QString profileName = project.profile(); + QBS_CHECK(!profileName.isEmpty()); + options.setMaxJobCount(Preferences(m_settings, profileName).jobs()); + } + return options; +} + +QString CommandLineFrontend::buildDirectory(const QString &profileName) const +{ + QString buildDir = m_parser.projectBuildDirectory(); + if (buildDir.isEmpty()) { + buildDir = Preferences(m_settings, profileName).defaultBuildDirectory(); + if (buildDir.isEmpty()) { + qbsDebug() << "No project build directory given; using current directory."; + buildDir = QDir::currentPath(); + } else { + qbsDebug() << "No project build directory given; using directory from preferences."; + } + } + + QString projectName(QFileInfo(m_parser.projectFilePath()).baseName()); + buildDir.replace(BuildDirectoryOption::magicProjectString(), projectName); + QString projectDir(QFileInfo(m_parser.projectFilePath()).path()); + buildDir.replace(BuildDirectoryOption::magicProjectDirString(), projectDir); + if (!QFileInfo(buildDir).isAbsolute()) + buildDir = QDir::currentPath() + QLatin1Char('/') + buildDir; + buildDir = QDir::cleanPath(buildDir); + return buildDir; +} + +void CommandLineFrontend::build() +{ + if (m_parser.products().empty()) { + const Project::ProductSelection productSelection = m_parser.withNonDefaultProducts() + ? Project::ProductSelectionWithNonDefault : Project::ProductSelectionDefaultOnly; + for (const Project &project : qAsConst(m_projects)) + m_buildJobs << project.buildAllProducts(buildOptions(project), productSelection, this); + } else { + const ProductMap &products = productsToUse(); + for (ProductMap::ConstIterator it = products.begin(); it != products.end(); ++it) + m_buildJobs.push_back(it.key().buildSomeProducts(it.value(), + buildOptions(it.key()), this)); + } + connectBuildJobs(); + + /* + * Progress reporting for the build jobs works as follows: We know that for every job, + * the newTaskStarted() signal is emitted exactly once (unless there's an error). So we add up + * the respective total efforts as they come in. Once all jobs have reported their total + * efforts, we can start the overall progress report. + */ + m_buildEffortsNeeded = m_buildJobs.size(); + m_buildEffortsRetrieved = 0; + m_totalBuildEffort = 0; + m_currentBuildEffort = 0; +} + +void CommandLineFrontend::checkGeneratorName() +{ + const QString generatorName = m_parser.generateOptions().generatorName(); + m_generator = ProjectGeneratorManager::findGenerator(generatorName); + if (m_generator) + return; + const auto generatorNames = ProjectGeneratorManager::loadedGeneratorNames(); + if (!generatorNames.empty()) { + const QString generatorNamesString = generatorNames.join(QLatin1String("\n\t")); + if (!generatorName.isEmpty()) { + throw ErrorInfo(Tr::tr("No generator named '%1'. Available generators:\n\t%2") + .arg(generatorName, generatorNamesString)); + } + + throw ErrorInfo(Tr::tr("No generator specified. Available generators:\n\t%1") + .arg(generatorNamesString)); + } + + throw ErrorInfo(Tr::tr("No generator specified or no generators are available.")); +} + +void CommandLineFrontend::generate() +{ + QBS_CHECK(!!m_generator); + const ErrorInfo error = m_generator->generate(m_projects, + m_parser.buildConfigurations(), + m_parser.installOptions(QString()), + m_parser.settingsDir(), + ConsoleLogger::instance(m_settings)); + if (error.hasError()) + throw error; +} + +int CommandLineFrontend::runTarget() +{ + const ProductData productToRun = getTheOneRunnableProduct(); + const QString executableFilePath = productToRun.targetExecutable(); + if (executableFilePath.isEmpty()) { + throw ErrorInfo(Tr::tr("Cannot run: Product '%1' is not an application.") + .arg(productToRun.name())); + } + RunEnvironment runEnvironment = m_projects.front().getRunEnvironment(productToRun, + m_parser.installOptions(m_projects.front().profile()), + QProcessEnvironment::systemEnvironment(), m_parser.runEnvConfig(), m_settings); + return runEnvironment.doRunTarget(executableFilePath, m_parser.runArgs(), m_parser.dryRun()); +} + +void CommandLineFrontend::updateTimestamps() +{ + const ProductMap &products = productsToUse(); + for (ProductMap::ConstIterator it = products.constBegin(); it != products.constEnd(); ++it) { + Project p = it.key(); + p.updateTimestamps(it.value()); + } +} + +void CommandLineFrontend::dumpNodesTree() +{ + QFile stdOut; + stdOut.open(stdout, QIODevice::WriteOnly); + const ErrorInfo error = m_projects.front().dumpNodesTree(stdOut, productsToUse() + .value(m_projects.front())); + if (error.hasError()) + throw error; +} + +void CommandLineFrontend::listProducts() +{ + const QList products = productsToUse().constBegin().value(); + QStringList output; + for (const ProductData &p : products) { + QString productInfo = p.fullDisplayName(); + if (!p.isEnabled()) + productInfo.append(QLatin1Char(' ')).append(Tr::tr("[disabled]")); + else if (!p.properties().value(QStringLiteral("builtByDefault")).toBool()) + productInfo.append(QLatin1Char(' ')).append(Tr::tr("[not built by default]")); + output += productInfo; + } + output.sort(); + qbsInfo() << output.join(QLatin1Char('\n')); +} + +void CommandLineFrontend::connectBuildJobs() +{ + for (AbstractJob * const job : qAsConst(m_buildJobs)) + connectBuildJob(job); +} + +void CommandLineFrontend::connectBuildJob(AbstractJob *job) +{ + connectJob(job); + + const auto bjob = qobject_cast(job); + if (!bjob) + return; + + connect(bjob, &BuildJob::reportCommandDescription, + this, &CommandLineFrontend::handleCommandDescriptionReport); + connect(bjob, &BuildJob::reportProcessResult, + this, &CommandLineFrontend::handleProcessResultReport); +} + +void CommandLineFrontend::connectJob(AbstractJob *job) +{ + connect(job, &AbstractJob::finished, + this, &CommandLineFrontend::handleJobFinished); + connect(job, &AbstractJob::taskStarted, + this, &CommandLineFrontend::handleNewTaskStarted); + connect(job, &AbstractJob::totalEffortChanged, + this, &CommandLineFrontend::handleTotalEffortChanged); + if (m_parser.showProgress()) { + connect(job, &AbstractJob::taskProgress, + this, &CommandLineFrontend::handleTaskProgress); + } +} + +ProductData CommandLineFrontend::getTheOneRunnableProduct() +{ + QBS_CHECK(m_projects.size() == 1); // Has been checked earlier. + + if (m_parser.products().size() == 1) { + for (const ProductData &p : m_projects.front().projectData().allProducts()) { + if (p.name() == m_parser.products().constFirst()) + return p; + } + QBS_CHECK(false); + } + QBS_CHECK(m_parser.products().isEmpty()); + + QList runnableProducts; + for (const ProductData &p : m_projects.front().projectData().allProducts()) { + if (p.isRunnable()) + runnableProducts.push_back(p); + } + + if (runnableProducts.size() == 1) + return runnableProducts.front(); + + if (runnableProducts.empty()) { + throw ErrorInfo(Tr::tr("Cannot execute command '%1': Project has no runnable product.") + .arg(m_parser.commandName())); + } + + ErrorInfo error(Tr::tr("Ambiguous use of command '%1': No product given, but project " + "has more than one runnable product.").arg(m_parser.commandName())); + error.append(Tr::tr("Use the '--products' option with one of the following products:")); + for (const ProductData &p : qAsConst(runnableProducts)) { + QString productRepr = QLatin1String("\t") + p.name(); + if (p.profile() != m_projects.front().profile()) { + productRepr.append(QLatin1String(" [")).append(p.profile()) + .append(QLatin1Char(']')); + } + error.append(productRepr); + } + throw error; +} + +void CommandLineFrontend::install() +{ + Q_ASSERT(m_projects.size() == 1); + const Project project = m_projects.front(); + InstallJob *installJob; + if (m_parser.products().empty()) { + const Project::ProductSelection productSelection = m_parser.withNonDefaultProducts() + ? Project::ProductSelectionWithNonDefault : Project::ProductSelectionDefaultOnly; + installJob = project.installAllProducts(m_parser.installOptions(project.profile()), + productSelection); + } else { + const Project project = m_projects.front(); + const ProductMap products = productsToUse(); + installJob = project.installSomeProducts(products.value(project), + m_parser.installOptions(project.profile())); + } + connectJob(installJob); +} + +} // namespace qbs diff --git a/src/app/qbs/commandlinefrontend.h b/src/app/qbs/commandlinefrontend.h new file mode 100644 index 00000000..5f598cb3 --- /dev/null +++ b/src/app/qbs/commandlinefrontend.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** 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 COMMANDLINEFRONTEND_H +#define COMMANDLINEFRONTEND_H + +#include "parser/commandlineparser.h" +#include +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE +class QTimer; +QT_END_NAMESPACE + +namespace qbs { +class AbstractJob; +class ConsoleProgressObserver; +class ErrorInfo; +class ProcessResult; +class ProjectGenerator; +class Settings; + +class CommandLineFrontend : public QObject +{ + Q_OBJECT +public: + explicit CommandLineFrontend(const CommandLineParser &parser, Settings *settings, + QObject *parent = nullptr); + ~CommandLineFrontend() override; + + void cancel(); + void start(); + +private: + void handleCommandDescriptionReport(const QString &highlight, const QString &message); + void handleJobFinished(bool success, qbs::AbstractJob *job); + void handleNewTaskStarted(const QString &description, int totalEffort); + void handleTotalEffortChanged(int totalEffort); + void handleTaskProgress(int value, qbs::AbstractJob *job); + void handleProcessResultReport(const qbs::ProcessResult &result); + void checkCancelStatus(); + + using ProductMap = QHash>; + ProductMap productsToUse() const; + + bool resolvingMultipleProjects() const; + bool isResolving() const; + bool isBuilding() const; + void handleProjectsResolved(); + void makeClean(); + int runShell(); + void build(); + void checkGeneratorName(); + void generate(); + int runTarget(); + void updateTimestamps(); + void dumpNodesTree(); + void listProducts(); + void connectBuildJobs(); + void connectBuildJob(AbstractJob *job); + void connectJob(AbstractJob *job); + ProductData getTheOneRunnableProduct(); + void install(); + BuildOptions buildOptions(const Project &project) const; + QString buildDirectory(const QString &profileName) const; + + const CommandLineParser &m_parser; + Settings * const m_settings; + QList m_resolveJobs; + QList m_buildJobs; + QList m_projects; + + ConsoleProgressObserver *m_observer = nullptr; + + enum CancelStatus { CancelStatusNone, CancelStatusRequested, CancelStatusCanceling }; + CancelStatus m_cancelStatus = CancelStatus::CancelStatusNone; + QTimer * const m_cancelTimer; + + int m_buildEffortsNeeded = 0; + int m_buildEffortsRetrieved = 0; + int m_totalBuildEffort = 0; + int m_currentBuildEffort = 0; + QHash m_buildEfforts; + std::shared_ptr m_generator; +}; + +} // namespace qbs + +#endif // COMMANDLINEFRONTEND_H diff --git a/src/app/qbs/consoleprogressobserver.cpp b/src/app/qbs/consoleprogressobserver.cpp new file mode 100644 index 00000000..e6ab9db7 --- /dev/null +++ b/src/app/qbs/consoleprogressobserver.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** 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 "consoleprogressobserver.h" + +#include +#include + +#include + +namespace qbs { + +void ConsoleProgressObserver::initialize(const QString &task, int max) +{ + m_maximum = max; + m_value = 0; + m_percentage = 0; + m_hashesPrinted = 0; + std::cout << task.toLocal8Bit().constData() << ": 0%" << std::flush; + setMaximum(max); +} + +void ConsoleProgressObserver::setMaximum(int maximum) +{ + m_maximum = maximum; + if (maximum == 0) { + m_percentage = 100; + updateProgressBarIfNecessary(); + writePercentageString(); + std::cout << std::endl; + } +} + +void ConsoleProgressObserver::setProgressValue(int value) +{ + if (value > m_maximum || value <= m_value) + return; // TODO: Should be an assertion, but the executor currently breaks it. + m_value = value; + const int newPercentage = (100 * m_value) / m_maximum; + if (newPercentage == m_percentage) + return; + eraseCurrentPercentageString(); + m_percentage = newPercentage; + updateProgressBarIfNecessary(); + writePercentageString(); + if (m_value == m_maximum) + std::cout << std::endl; + else + std::cout << std::flush; +} + +void ConsoleProgressObserver::eraseCurrentPercentageString() +{ + const int charsToErase = m_percentage == 0 ? 2 : m_percentage < 10 ? 3 : 4; + const QByteArray backspaceCommand(charsToErase, '\b'); + + // Move cursor before the old percentage string. + std::cout << backspaceCommand.constData(); + + // Erase old percentage string. + std::cout << QByteArray(charsToErase, ' ').constData(); + + // Move cursor before the erased string. + std::cout << backspaceCommand.constData(); +} + +void ConsoleProgressObserver::updateProgressBarIfNecessary() +{ + static const int TotalHashCount = 50; // Should fit on most terminals without a line break. + const int hashesNeeded = (m_percentage * TotalHashCount) / 100; + if (m_hashesPrinted < hashesNeeded) { + std::cout << QByteArray(hashesNeeded - m_hashesPrinted, '#').constData(); + m_hashesPrinted = hashesNeeded; + } +} + +void ConsoleProgressObserver::writePercentageString() +{ + std::cout << QStringLiteral(" %1%").arg(m_percentage).toLocal8Bit().constData(); +} + +} // namespace qbs diff --git a/src/app/qbs/consoleprogressobserver.h b/src/app/qbs/consoleprogressobserver.h new file mode 100644 index 00000000..f14234ef --- /dev/null +++ b/src/app/qbs/consoleprogressobserver.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 CONSOLEPROGRESSOBSERVER_H +#define CONSOLEPROGRESSOBSERVER_H + +#include + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace qbs { + +class ConsoleProgressObserver +{ +public: + void initialize(const QString &task, int max); + void setMaximum(int maximum); + void setProgressValue(int value); + void incrementProgressValue() { setProgressValue(m_value + 1); } + +private: + void eraseCurrentPercentageString(); + void updateProgressBarIfNecessary(); + void writePercentageString(); + + int m_maximum; + int m_value; + int m_percentage; + int m_hashesPrinted; +}; + +} // namespace qbs + +#endif // CONSOLEPROGRESSOBSERVER_H diff --git a/src/app/qbs/ctrlchandler.cpp b/src/app/qbs/ctrlchandler.cpp new file mode 100644 index 00000000..b2e293d1 --- /dev/null +++ b/src/app/qbs/ctrlchandler.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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 "application.h" + +static void cancel() +{ + qbs::Application * const app = qbs::Application::instance(); + if (app) + app->userInterrupt(); +} + +#if defined(Q_OS_WIN) && defined(Q_CC_MSVC) + +#include + +static BOOL WINAPI consoleCtrlHandlerRoutine(__in DWORD dwCtrlType) +{ + Q_UNUSED(dwCtrlType); + cancel(); + return TRUE; +} + +void installCtrlCHandler() +{ + SetConsoleCtrlHandler(&consoleCtrlHandlerRoutine, TRUE); +} + +#else + +#include + +static void sigIntHandler(int sig) +{ + Q_UNUSED(sig); + cancel(); +} + +void installCtrlCHandler() +{ + signal(SIGINT, sigIntHandler); +} + +#endif diff --git a/src/app/qbs/ctrlchandler.h b/src/app/qbs/ctrlchandler.h new file mode 100644 index 00000000..463b6f45 --- /dev/null +++ b/src/app/qbs/ctrlchandler.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** 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 CTRLCHANDLER_H +#define CTRLCHANDLER_H + +void installCtrlCHandler(); + +#endif // CTRLCHANDLER_H diff --git a/src/app/qbs/main.cpp b/src/app/qbs/main.cpp new file mode 100644 index 00000000..ca7c48a8 --- /dev/null +++ b/src/app/qbs/main.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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 "application.h" +#include "commandlinefrontend.h" +#include "qbstool.h" +#include "parser/commandlineparser.h" +#include "../shared/logging/consolelogger.h" + +#include + +#include +#include + +using namespace qbs; + +static bool tryToRunTool(const QStringList &arguments, int &exitCode) +{ + if (arguments.empty()) + return false; + QStringList toolArgs = arguments; + const QString toolName = toolArgs.takeFirst(); + if (toolName.startsWith(QLatin1Char('-'))) + return false; + return QbsTool::tryToRunTool(toolName, toolArgs, &exitCode); +} + +int main(int argc, char *argv[]) +{ + ConsoleLogger::instance(); + + try { + Application app(argc, argv); + QStringList arguments = app.arguments(); + arguments.removeFirst(); + + int toolExitCode = 0; + if (tryToRunTool(arguments, toolExitCode)) + return toolExitCode; + + CommandLineParser parser; + if (!parser.parseCommandLine(arguments)) + return EXIT_FAILURE; + + if (parser.command() == HelpCommandType) { + parser.printHelp(); + return 0; + } + + Settings settings(parser.settingsDir()); + ConsoleLogger::instance().setSettings(&settings); + CommandLineFrontend clFrontend(parser, &settings); + app.setCommandLineFrontend(&clFrontend); + QTimer::singleShot(0, &clFrontend, &CommandLineFrontend::start); + return app.exec(); + } catch (const ErrorInfo &error) { + qbsError() << error.toString(); + return EXIT_FAILURE; + } +} diff --git a/src/app/qbs/parser/commandlineoption.cpp b/src/app/qbs/parser/commandlineoption.cpp new file mode 100644 index 00000000..a09f36c2 --- /dev/null +++ b/src/app/qbs/parser/commandlineoption.cpp @@ -0,0 +1,701 @@ +/**************************************************************************** +** +** 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 "commandlineoption.h" + +#include +#include +#include +#include +#include + +namespace qbs { +using namespace Internal; + +CommandLineOption::~CommandLineOption() = default; + +void CommandLineOption::parse(CommandType command, const QString &representation, QStringList &input) +{ + m_command = command; + doParse(representation, input); +} + +CommandLineOption::CommandLineOption() + : m_command(static_cast(-1)) +{ +} + +QString CommandLineOption::getArgument(const QString &representation, QStringList &input) +{ + if (input.empty()) { + throw ErrorInfo(Tr::tr("Invalid use of option '%1': Missing argument.\nUsage: %2") + .arg(representation, description(command()))); + } + return input.takeFirst(); +} + +QString FileOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1|%2 \n" + "\tUse as the project file.\n" + "\tIf is a directory and it contains a single file ending in '.qbs',\n" + "\tthat file will be used.\n" + "\tIf this option is not given at all, behavior is the same as for '-f .'.\n") + .arg(longRepresentation(), shortRepresentation()); +} + +QString FileOption::shortRepresentation() const +{ + return QStringLiteral("-f"); +} + +QString FileOption::longRepresentation() const +{ + return QStringLiteral("--file"); +} + +void FileOption::doParse(const QString &representation, QStringList &input) +{ + m_projectFilePath = getArgument(representation, input); +} + +QString BuildDirectoryOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1|%2 \n" + "\tBuild in the given directory. The default value is the current directory\n" + "\tunless preferences.defaultBuildDirectory is set.\n" + "\tRelative paths will be interpreted relative to the current directory.\n" + "\tIf the directory does not exist, it will be created. Use the following\n" + "\tspecial values as placeholders:\n" + "\t%3: name of the project file excluding the extension\n" + "\t%4: directory containing the project file\n") + .arg(longRepresentation(), shortRepresentation(), + magicProjectString(), magicProjectDirString()); +} + +QString BuildDirectoryOption::shortRepresentation() const +{ + return QStringLiteral("-d"); +} + +QString BuildDirectoryOption::longRepresentation() const +{ + return QStringLiteral("--build-directory"); +} + +QString BuildDirectoryOption::magicProjectString() +{ + return QStringLiteral("@project"); +} + +QString BuildDirectoryOption::magicProjectDirString() +{ + return QStringLiteral("@path"); +} + +void BuildDirectoryOption::doParse(const QString &representation, QStringList &input) +{ + m_projectBuildDirectory = getArgument(representation, input); +} + +QString GeneratorOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1|%2 \n" + "\tUse the given build system generator.\n") + .arg(longRepresentation(), shortRepresentation()); +} + +QString GeneratorOption::shortRepresentation() const +{ + return QStringLiteral("-g"); +} + +QString GeneratorOption::longRepresentation() const +{ + return QStringLiteral("--generator"); +} + +void GeneratorOption::doParse(const QString &representation, QStringList &input) +{ + m_generatorName = getArgument(representation, input); + if (m_generatorName.isEmpty()) { + throw ErrorInfo(Tr::tr("Invalid use of option '%1': No generator given.\nUsage: %2") + .arg(representation, description(command()))); + } +} + +static QString loglevelLongRepresentation() { return QStringLiteral("--log-level"); } + +QString VerboseOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1|%2\n" + "\tBe more verbose. Increases the log level by one.\n" + "\tThis option can be given more than once.\n" + "\tExcessive occurrences have no effect.\n" + "\tIf option '%3' appears anywhere on the command line in addition\n" + "\tto this option, its value is taken as the base which to increase.\n") + .arg(longRepresentation(), shortRepresentation(), loglevelLongRepresentation()); +} + +QString VerboseOption::shortRepresentation() const +{ + return QStringLiteral("-v"); +} + +QString VerboseOption::longRepresentation() const +{ + return QStringLiteral("--more-verbose"); +} + +QString QuietOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1|%2\n" + "\tBe more quiet. Decreases the log level by one.\n" + "\tThis option can be given more than once.\n" + "\tExcessive occurrences have no effect.\n" + "\tIf option '%3' appears anywhere on the command line in addition\n" + "\tto this option, its value is taken as the base which to decrease.\n") + .arg(longRepresentation(), shortRepresentation(), loglevelLongRepresentation()); +} + +QString QuietOption::shortRepresentation() const +{ + return QStringLiteral("-q"); +} + +QString QuietOption::longRepresentation() const +{ + return QStringLiteral("--less-verbose"); +} + +QString JobsOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1|%2 \n" + "\tUse concurrent build jobs. must be an integer greater than zero.\n" + "\tThe default is the number of cores.\n") + .arg(longRepresentation(), shortRepresentation()); +} + +QString JobsOption::shortRepresentation() const +{ + return QStringLiteral("-j"); +} + +QString JobsOption::longRepresentation() const +{ + return QStringLiteral("--jobs"); +} + +void JobsOption::doParse(const QString &representation, QStringList &input) +{ + const QString jobCountString = getArgument(representation, input); + bool stringOk; + m_jobCount = jobCountString.toInt(&stringOk); + if (!stringOk || m_jobCount <= 0) + throw ErrorInfo(Tr::tr("Invalid use of option '%1': Illegal job count '%2'.\nUsage: %3") + .arg(representation, jobCountString, description(command()))); +} + +QString KeepGoingOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1|%2\n" + "\tKeep going when errors occur (if at all possible).\n") + .arg(longRepresentation(), shortRepresentation()); +} + +QString KeepGoingOption::shortRepresentation() const +{ + return QStringLiteral("-k"); +} + +QString KeepGoingOption::longRepresentation() const +{ + return QStringLiteral("--keep-going"); +} + +QString DryRunOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1|%2\n" + "\tDry run. No commands will be executed and no permanent changes to the\n" + "\tbuild graph will be done.\n") + .arg(longRepresentation(), shortRepresentation()); +} + +QString DryRunOption::shortRepresentation() const +{ + return QStringLiteral("-n"); +} + +QString DryRunOption::longRepresentation() const +{ + return QStringLiteral("--dry-run"); +} + +QString ForceProbesOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1\n" + "\tForce re-execution of all Probe items' configure scripts, rather than using the\n" + "\tcached data.\n") + .arg(longRepresentation()); +} + +QString ForceProbesOption::longRepresentation() const +{ + return QStringLiteral("--force-probe-execution"); +} + +QString NoInstallOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1\n" + "\tDo not install any artifacts as part of the build process.\n") + .arg(longRepresentation()); +} + +QString NoInstallOption::longRepresentation() const +{ + return QStringLiteral("--no-install"); +} + + +static QString logTimeRepresentation() +{ + return QStringLiteral("--log-time"); +} + +QString ShowProgressOption::description(CommandType command) const +{ + Q_UNUSED(command); + QString desc = Tr::tr("%1\n" + "\tShow a progress bar. Implies '%2=%3'.\n").arg(longRepresentation(), + loglevelLongRepresentation(), logLevelName(LoggerMinLevel)); + return desc += Tr::tr("\tThis option is mutually exclusive with '%1'.\n") + .arg(logTimeRepresentation()); +} + +static QString showProgressRepresentation() +{ + return QStringLiteral("--show-progress"); +} + +QString ShowProgressOption::longRepresentation() const +{ + return showProgressRepresentation(); +} + +void StringListOption::doParse(const QString &representation, QStringList &input) +{ + m_arguments = getArgument(representation, input).split(QLatin1Char(',')); + if (m_arguments.empty()) { + throw ErrorInfo(Tr::tr("Invalid use of option '%1': Argument list must not be empty.\n" + "Usage: %2").arg(representation, description(command()))); + } + for (const QString &element : qAsConst(m_arguments)) { + if (element.isEmpty()) { + throw ErrorInfo(Tr::tr("Invalid use of option '%1': Argument list must not contain " + "empty elements.\nUsage: %2") + .arg(representation, description(command()))); + } + } +} + +QString ChangedFilesOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1 [,...]\n" + "\tAssume these and only these files have changed.\n").arg(longRepresentation()); +} + +QString ChangedFilesOption::longRepresentation() const +{ + return QStringLiteral("--changed-files"); +} + +QString ProductsOption::description(CommandType command) const +{ + const QString prefix = Tr::tr("%1|%2").arg(longRepresentation(), shortRepresentation()); + switch (command) { + case InstallCommandType: + case RunCommandType: + case ShellCommandType: + return Tr::tr("%1 \n\tUse the specified product.\n").arg(prefix); + default: + return Tr::tr("%1 [,...]\n" + "\tTake only the specified products into account.\n").arg(prefix); + } +} + +QString ProductsOption::shortRepresentation() const +{ + return QStringLiteral("-p"); +} + +QString ProductsOption::longRepresentation() const +{ + return QStringLiteral("--products"); +} + +static QStringList allLogLevelStrings() +{ + QStringList result; + for (int i = static_cast(LoggerMinLevel); i <= static_cast(LoggerMaxLevel); ++i) + result << logLevelName(static_cast(i)); + return result; +} + +LogLevelOption::LogLevelOption() : m_logLevel(defaultLogLevel()) +{ +} + +QString LogLevelOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1 \n" + "\tUse the specified log level.\n" + "\tPossible values are '%2'.\n" + "\tThe default is '%3'.\n").arg(longRepresentation(), + allLogLevelStrings().join(QLatin1String("', '")), logLevelName(defaultLogLevel())); +} + +QString LogLevelOption::longRepresentation() const +{ + return loglevelLongRepresentation(); +} + +void LogLevelOption::doParse(const QString &representation, QStringList &input) +{ + const QString levelString = getArgument(representation, input); + const QList levels = QList() << LoggerError << LoggerWarning + << LoggerInfo << LoggerDebug << LoggerTrace; + for (const LoggerLevel &l : levels) { + if (logLevelName(l) == levelString) { + m_logLevel = l; + return; + } + } + throw ErrorInfo(Tr::tr("Invalid use of option '%1': Unknown log level '%2'.\nUsage: %3") + .arg(representation, levelString, description(command()))); +} + +QString ForceTimeStampCheckOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1\n\tForce timestamp checks.\n" + "\tInstead of using the file timestamps that are stored in the build graph,\n" + "\tretrieve the timestamps from the file system.\n").arg(longRepresentation()); +} + +QString ForceTimeStampCheckOption::longRepresentation() const +{ + return QStringLiteral("--check-timestamps"); +} + +QString ForceOutputCheckOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1\n\tForce transformer output artifact checks.\n" + "\tVerify that the output artifacts declared by rules in the\n" + "\tproject are actually created.\n").arg(longRepresentation()); +} + +QString ForceOutputCheckOption::longRepresentation() const +{ + return QStringLiteral("--check-outputs"); +} + +QString BuildNonDefaultOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1\n\tBuild all products, even if their builtByDefault property is false.\n") + .arg(longRepresentation()); +} + +QString BuildNonDefaultOption::longRepresentation() const +{ + return QStringLiteral("--all-products"); +} + + +InstallRootOption::InstallRootOption() : m_useSysroot(false) +{ +} + +static QString magicSysrootString() { return QStringLiteral("@sysroot"); } + +QString InstallRootOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1 \n" + "\tInstall into the given directory.\n" + "\tThe default value is '/%2'.\n" + "\tIf the directory does not exist, it will be created. Use the special\n" + "\tvalue '%3' to install into the sysroot (i.e. the value of the\n" + "\tproperty qbs.sysroot).\n") + .arg(longRepresentation(), InstallOptions::defaultInstallRoot(), magicSysrootString()); +} + +QString InstallRootOption::longRepresentation() const +{ + return QStringLiteral("--install-root"); +} + +void InstallRootOption::doParse(const QString &representation, QStringList &input) +{ + if (input.empty()) { + throw ErrorInfo(Tr::tr("Invalid use of option '%1: Argument expected.\n" + "Usage: %2").arg(representation, description(command()))); + } + const QString installRoot = input.takeFirst(); + if (installRoot == magicSysrootString()) + m_useSysroot = true; + else + m_installRoot = installRoot; +} + +QString RemoveFirstOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1\n\tRemove the installation base directory before installing.\n") + .arg(longRepresentation()); +} + +QString RemoveFirstOption::longRepresentation() const +{ + return QStringLiteral("--clean-install-root"); +} + + +QString NoBuildOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1\n\tDo not build before installing.\n") + .arg(longRepresentation()); +} + +QString NoBuildOption::longRepresentation() const +{ + return QStringLiteral("--no-build"); +} + + +QString LogTimeOption::description(CommandType command) const +{ + Q_UNUSED(command); + QString desc = Tr::tr("%1\n\tLog the time that the operations involved in this command take.\n") + .arg(longRepresentation()); + desc += Tr::tr("\tThis option is implied in log levels '%1' and higher.\n") + .arg(logLevelName(LoggerDebug)); + return desc += Tr::tr("\tThis option is mutually exclusive with '%1'.\n") + .arg(showProgressRepresentation()); +} + +QString LogTimeOption::shortRepresentation() const +{ + return QStringLiteral("-t"); +} + +QString LogTimeOption::longRepresentation() const +{ + return logTimeRepresentation(); +} + + +SettingsDirOption::SettingsDirOption() = default; + +QString SettingsDirOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1 \n" + "\tRead all settings (such as profile information) from the given directory.\n" + "\tThe default value is system-specific (see the QSettings documentation).\n" + "\tIf the directory does not exist, it will be created.\n") + .arg(longRepresentation()); +} + +QString SettingsDirOption::longRepresentation() const +{ + return QStringLiteral("--settings-dir"); +} + +void SettingsDirOption::doParse(const QString &representation, QStringList &input) +{ + if (input.empty()) { + throw ErrorInfo(Tr::tr("Invalid use of option '%1: Argument expected.\n" + "Usage: %2").arg(representation, description(command()))); + } + m_settingsDir = input.takeFirst(); +} + +QString JobLimitsOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1 :[,:...]\n" + "\tSet pool-specific job limits.\n").arg(longRepresentation()); +} + +QString JobLimitsOption::longRepresentation() const +{ + return QStringLiteral("--job-limits"); +} + +void JobLimitsOption::doParse(const QString &representation, QStringList &input) +{ + if (input.empty()) { + throw ErrorInfo(Tr::tr("Invalid use of option '%1: Argument expected.\n" + "Usage: %2").arg(representation, description(command()))); + } + const QString jobLimitsSpec = input.takeFirst(); + const QStringList jobLimitStrings = jobLimitsSpec.split(QLatin1Char(',')); + for (const QString &jobLimitString : jobLimitStrings) { + const int sepIndex = jobLimitString.indexOf(QLatin1Char(':')); + if (sepIndex <= 0 || sepIndex == jobLimitString.size() - 1) { + throw ErrorInfo(Tr::tr("Invalid use of option '%1: " + "Invalid job limits specification '%2'.\n" + "Usage: %3").arg(representation, jobLimitsSpec, + description(command()))); + } + const QString pool = jobLimitString.left(sepIndex); + const QString limitString = jobLimitString.mid(sepIndex + 1); + bool isValidNumber; + const int limit = limitString.toInt(&isValidNumber); + if (!isValidNumber) { + throw ErrorInfo(Tr::tr("Invalid use of option '%1: '%2' is not a number.\n" + "Usage: %3").arg(representation, limitString, + description(command()))); + } + m_jobLimits.setJobLimit(pool, limit); + } +} + +QString RespectProjectJobLimitsOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1\n\tGive maximum priority to job limits defined inside the project.\n") + .arg(longRepresentation()); +} + +QString RespectProjectJobLimitsOption::longRepresentation() const +{ + return QStringLiteral("--enforce-project-job-limits"); +} + +CommandEchoModeOption::CommandEchoModeOption() = default; + +QString CommandEchoModeOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1 \n" + "\tKind of output to show when executing commands.\n" + "\tPossible values are '%2'.\n" + "\tThe default is '%3'.\n") + .arg(longRepresentation(), allCommandEchoModeStrings().join(QLatin1String("', '")), + commandEchoModeName(defaultCommandEchoMode())); +} + +QString CommandEchoModeOption::longRepresentation() const +{ + return QStringLiteral("--command-echo-mode"); +} + +CommandEchoMode CommandEchoModeOption::commandEchoMode() const +{ + return m_echoMode; +} + +void CommandEchoModeOption::doParse(const QString &representation, QStringList &input) +{ + const QString mode = getArgument(representation, input); + if (mode.isEmpty()) { + throw ErrorInfo(Tr::tr("Invalid use of option '%1': No command echo mode given.\nUsage: %2") + .arg(representation, description(command()))); + } + + if (!allCommandEchoModeStrings().contains(mode)) { + throw ErrorInfo(Tr::tr("Invalid use of option '%1': " + "Invalid command echo mode '%2' given.\nUsage: %3") + .arg(representation, mode, description(command()))); + } + + m_echoMode = commandEchoModeFromName(mode); +} + +QString WaitLockOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1\n\tWait indefinitely for other processes to release the build graph lock.\n") + .arg(longRepresentation()); +} + +QString WaitLockOption::longRepresentation() const +{ + return QStringLiteral("--wait-lock"); +} + +QString DisableFallbackProviderOption::description(CommandType) const +{ + return Tr::tr("%1\n\tDo not fall back to pkg-config if a dependency is not found.\n") + .arg(longRepresentation()); +} + +QString DisableFallbackProviderOption::longRepresentation() const +{ + return QStringLiteral("--no-fallback-module-provider"); +} + +QString RunEnvConfigOption::description(CommandType command) const +{ + Q_UNUSED(command); + return Tr::tr("%1\n\tComma-separated list of strings to pass to all modules' " + "setupRunEnvironment scripts.\n").arg(longRepresentation()); +} + +QString RunEnvConfigOption::longRepresentation() const +{ + return QStringLiteral("--setup-run-env-config"); +} + +} // namespace qbs diff --git a/src/app/qbs/parser/commandlineoption.h b/src/app/qbs/parser/commandlineoption.h new file mode 100644 index 00000000..f8ec1c73 --- /dev/null +++ b/src/app/qbs/parser/commandlineoption.h @@ -0,0 +1,428 @@ +/**************************************************************************** +** +** 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 QBS_COMMANDLINEOPTION_H +#define QBS_COMMANDLINEOPTION_H + +#include "commandtype.h" + +#include +#include + +#include + +namespace qbs { + +class CommandLineOption +{ +public: + enum Type { + FileOptionType, + BuildDirectoryOptionType, + LogLevelOptionType, VerboseOptionType, QuietOptionType, + JobsOptionType, + KeepGoingOptionType, + DryRunOptionType, + ForceProbesOptionType, + ShowProgressOptionType, + ChangedFilesOptionType, + ProductsOptionType, + NoInstallOptionType, + InstallRootOptionType, RemoveFirstOptionType, NoBuildOptionType, + ForceTimestampCheckOptionType, + ForceOutputCheckOptionType, + BuildNonDefaultOptionType, + LogTimeOptionType, + CommandEchoModeOptionType, + SettingsDirOptionType, + JobLimitsOptionType, + RespectProjectJobLimitsOptionType, + GeneratorOptionType, + WaitLockOptionType, + RunEnvConfigOptionType, + DisableFallbackProviderType, + }; + + virtual ~CommandLineOption(); + virtual QString description(CommandType command) const = 0; + virtual QString shortRepresentation() const = 0; + virtual QString longRepresentation() const = 0; + virtual bool canAppearMoreThanOnce() const { return false; } + + void parse(CommandType command, const QString &representation, QStringList &input); + +protected: + CommandLineOption(); + QString getArgument(const QString &representation, QStringList &input); + CommandType command() const { return m_command; } + +private: + virtual void doParse(const QString &representation, QStringList &input) = 0; + + CommandType m_command; +}; + +class FileOption : public CommandLineOption +{ +public: + QString projectFilePath() const { return m_projectFilePath; } + +private: + QString description(CommandType command) const override; + QString shortRepresentation() const override; + QString longRepresentation() const override; + void doParse(const QString &representation, QStringList &input) override; + +private: + QString m_projectFilePath; +}; + +class BuildDirectoryOption : public CommandLineOption +{ +public: + QString projectBuildDirectory() const { return m_projectBuildDirectory; } + static QString magicProjectString(); + static QString magicProjectDirString(); + +private: + QString description(CommandType command) const override; + QString shortRepresentation() const override; + QString longRepresentation() const override; + void doParse(const QString &representation, QStringList &input) override; + +private: + QString m_projectBuildDirectory; +}; + +class GeneratorOption : public CommandLineOption +{ +public: + QString generatorName() const { return m_generatorName; } + +private: + QString description(CommandType command) const override; + QString shortRepresentation() const override; + QString longRepresentation() const override; + void doParse(const QString &representation, QStringList &input) override; + +private: + QString m_generatorName; +}; + +class CountingOption : public CommandLineOption +{ +public: + int count() const { return m_count; } + +protected: + CountingOption() : m_count(0) {} + +private: + bool canAppearMoreThanOnce() const override{ return true; } + void doParse(const QString &, QStringList &) override { ++m_count; } + + int m_count; +}; + +class VerboseOption : public CountingOption +{ + QString description(CommandType command) const override; + QString shortRepresentation() const override; + QString longRepresentation() const override; +}; + +class QuietOption : public CountingOption +{ + QString description(CommandType command) const override; + QString shortRepresentation() const override; + QString longRepresentation() const override; +}; + +class JobsOption : public CommandLineOption +{ +public: + JobsOption() : m_jobCount(0) {} + int jobCount() const { return m_jobCount; } + +private: + QString description(CommandType command) const override; + QString shortRepresentation() const override; + QString longRepresentation() const override; + void doParse(const QString &representation, QStringList &input) override; + + int m_jobCount; +}; + +class OnOffOption : public CommandLineOption +{ +public: + bool enabled() const { return m_enabled; } + +protected: + OnOffOption() : m_enabled(false) {} + +private: + void doParse(const QString &, QStringList &) override { m_enabled = true; } + + bool m_enabled; +}; + +class KeepGoingOption : public OnOffOption +{ + QString description(CommandType command) const override; + QString shortRepresentation() const override; + QString longRepresentation() const override; +}; + +class DryRunOption : public OnOffOption +{ + QString description(CommandType command) const override; + QString shortRepresentation() const override; + QString longRepresentation() const override; +}; + +class ForceProbesOption : public OnOffOption +{ + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; +}; + +class NoInstallOption : public OnOffOption +{ + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; +}; + +class ShowProgressOption : public OnOffOption +{ +public: + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; +}; + +class ForceTimeStampCheckOption : public OnOffOption +{ + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; +}; + +class ForceOutputCheckOption : public OnOffOption +{ + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; +}; + +class BuildNonDefaultOption : public OnOffOption +{ + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; +}; + + +class StringListOption : public CommandLineOption +{ +public: + QStringList arguments() const { return m_arguments; } + +private: + void doParse(const QString &representation, QStringList &input) override; + + QStringList m_arguments; +}; + +class ChangedFilesOption : public StringListOption +{ + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; +}; + +class ProductsOption : public StringListOption +{ +public: + QString description(CommandType command) const override; + QString shortRepresentation() const override; + QString longRepresentation() const override; +}; + +class RunEnvConfigOption : public StringListOption +{ + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; +}; + +class LogLevelOption : public CommandLineOption +{ +public: + LogLevelOption(); + int logLevel() const { return m_logLevel; } + +private: + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; + void doParse(const QString &representation, QStringList &input) override; + + int m_logLevel; +}; + +class InstallRootOption : public CommandLineOption +{ +public: + InstallRootOption(); + + QString installRoot() const { return m_installRoot; } + bool useSysroot() const { return m_useSysroot; } + + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; + +private: + void doParse(const QString &representation, QStringList &input) override; + + QString m_installRoot; + bool m_useSysroot; +}; + +class RemoveFirstOption : public OnOffOption +{ +public: + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; +}; + +class NoBuildOption : public OnOffOption +{ +public: + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; +}; + +class LogTimeOption : public OnOffOption +{ +public: + QString description(CommandType command) const override; + QString shortRepresentation() const override; + QString longRepresentation() const override; +}; + +class CommandEchoModeOption : public CommandLineOption +{ +public: + CommandEchoModeOption(); + + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; + CommandEchoMode commandEchoMode() const; + +private: + void doParse(const QString &representation, QStringList &input) override; + + CommandEchoMode m_echoMode = CommandEchoModeInvalid; +}; + +class SettingsDirOption : public CommandLineOption +{ +public: + SettingsDirOption(); + + QString settingsDir() const { return m_settingsDir; } + + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; + +private: + void doParse(const QString &representation, QStringList &input) override; + + QString m_settingsDir; +}; + +class JobLimitsOption : public CommandLineOption +{ +public: + JobLimits jobLimits() const { return m_jobLimits; } + + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; + +private: + void doParse(const QString &representation, QStringList &input) override; + + JobLimits m_jobLimits; +}; + +class RespectProjectJobLimitsOption : public OnOffOption +{ +public: + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; +}; + +class WaitLockOption : public OnOffOption +{ +public: + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; +}; + +class DisableFallbackProviderOption : public OnOffOption +{ +public: + QString description(CommandType command) const override; + QString shortRepresentation() const override { return {}; } + QString longRepresentation() const override; +}; + +} // namespace qbs + +#endif // QBS_COMMANDLINEOPTION_H diff --git a/src/app/qbs/parser/commandlineoptionpool.cpp b/src/app/qbs/parser/commandlineoptionpool.cpp new file mode 100644 index 00000000..63711f62 --- /dev/null +++ b/src/app/qbs/parser/commandlineoptionpool.cpp @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** 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 "commandlineoptionpool.h" + +namespace qbs { + +CommandLineOptionPool::~CommandLineOptionPool() +{ + qDeleteAll(m_options); +} + +CommandLineOption *CommandLineOptionPool::getOption(CommandLineOption::Type type) const +{ + CommandLineOption *& option = m_options[type]; + if (!option) { + switch (type) { + case CommandLineOption::FileOptionType: + option = new FileOption; + break; + case CommandLineOption::BuildDirectoryOptionType: + option = new BuildDirectoryOption; + break; + case CommandLineOption::LogLevelOptionType: + option = new LogLevelOption; + break; + case CommandLineOption::VerboseOptionType: + option = new VerboseOption; + break; + case CommandLineOption::QuietOptionType: + option = new QuietOption; + break; + case CommandLineOption::JobsOptionType: + option = new JobsOption; + break; + case CommandLineOption::KeepGoingOptionType: + option = new KeepGoingOption; + break; + case CommandLineOption::DryRunOptionType: + option = new DryRunOption; + break; + case CommandLineOption::ForceProbesOptionType: + option = new ForceProbesOption; + break; + case CommandLineOption::ShowProgressOptionType: + option = new ShowProgressOption; + break; + case CommandLineOption::ChangedFilesOptionType: + option = new ChangedFilesOption; + break; + case CommandLineOption::ProductsOptionType: + option = new ProductsOption; + break; + case CommandLineOption::NoInstallOptionType: + option = new NoInstallOption; + break; + case CommandLineOption::InstallRootOptionType: + option = new InstallRootOption; + break; + case CommandLineOption::RemoveFirstOptionType: + option = new RemoveFirstOption; + break; + case CommandLineOption::NoBuildOptionType: + option = new NoBuildOption; + break; + case CommandLineOption::ForceTimestampCheckOptionType: + option = new ForceTimeStampCheckOption; + break; + case CommandLineOption::ForceOutputCheckOptionType: + option = new ForceOutputCheckOption; + break; + case CommandLineOption::BuildNonDefaultOptionType: + option = new BuildNonDefaultOption; + break; + case CommandLineOption::LogTimeOptionType: + option = new LogTimeOption; + break; + case CommandLineOption::CommandEchoModeOptionType: + option = new CommandEchoModeOption; + break; + case CommandLineOption::SettingsDirOptionType: + option = new SettingsDirOption; + break; + case CommandLineOption::JobLimitsOptionType: + option = new JobLimitsOption; + break; + case CommandLineOption::RespectProjectJobLimitsOptionType: + option = new RespectProjectJobLimitsOption; + break; + case CommandLineOption::GeneratorOptionType: + option = new GeneratorOption; + break; + case CommandLineOption::WaitLockOptionType: + option = new WaitLockOption; + break; + case CommandLineOption::DisableFallbackProviderType: + option = new DisableFallbackProviderOption; + break; + case CommandLineOption::RunEnvConfigOptionType: + option = new RunEnvConfigOption; + break; + default: + qFatal("Unknown option type %d", type); + } + } + return option; +} + +FileOption *CommandLineOptionPool::fileOption() const +{ + return static_cast(getOption(CommandLineOption::FileOptionType)); +} + +BuildDirectoryOption *CommandLineOptionPool::buildDirectoryOption() const +{ + return static_cast(getOption(CommandLineOption::BuildDirectoryOptionType)); +} + +LogLevelOption *CommandLineOptionPool::logLevelOption() const +{ + return static_cast(getOption(CommandLineOption::LogLevelOptionType)); +} + +VerboseOption *CommandLineOptionPool::verboseOption() const +{ + return static_cast(getOption(CommandLineOption::VerboseOptionType)); +} + +QuietOption *CommandLineOptionPool::quietOption() const +{ + return static_cast(getOption(CommandLineOption::QuietOptionType)); +} + +ShowProgressOption *CommandLineOptionPool::showProgressOption() const +{ + return static_cast(getOption(CommandLineOption::ShowProgressOptionType)); +} + +DryRunOption *CommandLineOptionPool::dryRunOption() const +{ + return static_cast(getOption(CommandLineOption::DryRunOptionType)); +} + +ForceProbesOption *CommandLineOptionPool::forceProbesOption() const +{ + return static_cast(getOption(CommandLineOption::ForceProbesOptionType)); +} + +ChangedFilesOption *CommandLineOptionPool::changedFilesOption() const +{ + return static_cast(getOption(CommandLineOption::ChangedFilesOptionType)); +} + +KeepGoingOption *CommandLineOptionPool::keepGoingOption() const +{ + return static_cast(getOption(CommandLineOption::KeepGoingOptionType)); +} + +JobsOption *CommandLineOptionPool::jobsOption() const +{ + return static_cast(getOption(CommandLineOption::JobsOptionType)); +} + +ProductsOption *CommandLineOptionPool::productsOption() const +{ + return static_cast(getOption(CommandLineOption::ProductsOptionType)); +} + +NoInstallOption *CommandLineOptionPool::noInstallOption() const +{ + return static_cast(getOption(CommandLineOption::NoInstallOptionType)); +} + +InstallRootOption *CommandLineOptionPool::installRootOption() const +{ + return static_cast(getOption(CommandLineOption::InstallRootOptionType)); +} + +RemoveFirstOption *CommandLineOptionPool::removeFirstoption() const +{ + return static_cast(getOption(CommandLineOption::RemoveFirstOptionType)); +} + +NoBuildOption *CommandLineOptionPool::noBuildOption() const +{ + return static_cast(getOption(CommandLineOption::NoBuildOptionType)); +} + +ForceTimeStampCheckOption *CommandLineOptionPool::forceTimestampCheckOption() const +{ + return static_cast( + getOption(CommandLineOption::ForceTimestampCheckOptionType)); +} + +ForceOutputCheckOption *CommandLineOptionPool::forceOutputCheckOption() const +{ + return static_cast( + getOption(CommandLineOption::ForceOutputCheckOptionType)); +} + +BuildNonDefaultOption *CommandLineOptionPool::buildNonDefaultOption() const +{ + return static_cast( + getOption(CommandLineOption::BuildNonDefaultOptionType)); +} + +LogTimeOption *CommandLineOptionPool::logTimeOption() const +{ + return static_cast(getOption(CommandLineOption::LogTimeOptionType)); +} + +CommandEchoModeOption *CommandLineOptionPool::commandEchoModeOption() const +{ + return static_cast( + getOption(CommandLineOption::CommandEchoModeOptionType)); +} + +SettingsDirOption *CommandLineOptionPool::settingsDirOption() const +{ + return static_cast(getOption(CommandLineOption::SettingsDirOptionType)); +} + +JobLimitsOption *CommandLineOptionPool::jobLimitsOption() const +{ + return static_cast(getOption(CommandLineOption::JobLimitsOptionType)); +} + +RespectProjectJobLimitsOption *CommandLineOptionPool::respectProjectJobLimitsOption() const +{ + return static_cast( + getOption(CommandLineOption::RespectProjectJobLimitsOptionType)); +} + +GeneratorOption *CommandLineOptionPool::generatorOption() const +{ + return static_cast(getOption(CommandLineOption::GeneratorOptionType)); +} + +WaitLockOption *CommandLineOptionPool::waitLockOption() const +{ + return static_cast(getOption(CommandLineOption::WaitLockOptionType)); +} + +DisableFallbackProviderOption *CommandLineOptionPool::disableFallbackProviderOption() const +{ + return static_cast( + getOption(CommandLineOption::DisableFallbackProviderType)); +} + +RunEnvConfigOption *CommandLineOptionPool::runEnvConfigOption() const +{ + return static_cast(getOption(CommandLineOption::RunEnvConfigOptionType)); +} + +} // namespace qbs diff --git a/src/app/qbs/parser/commandlineoptionpool.h b/src/app/qbs/parser/commandlineoptionpool.h new file mode 100644 index 00000000..c7ac263e --- /dev/null +++ b/src/app/qbs/parser/commandlineoptionpool.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 QBS_COMMANDLINEOPTIONPOOL_H +#define QBS_COMMANDLINEOPTIONPOOL_H + +#include "commandlineoption.h" + +#include + +namespace qbs { + +class CommandLineOptionPool +{ +public: + ~CommandLineOptionPool(); + + CommandLineOption *getOption(CommandLineOption::Type type) const; + FileOption *fileOption() const; + BuildDirectoryOption *buildDirectoryOption() const; + LogLevelOption *logLevelOption() const; + VerboseOption *verboseOption() const; + QuietOption *quietOption() const; + ShowProgressOption *showProgressOption() const; + DryRunOption *dryRunOption() const; + ForceProbesOption *forceProbesOption() const; + ChangedFilesOption *changedFilesOption() const; + KeepGoingOption *keepGoingOption() const; + JobsOption *jobsOption() const; + ProductsOption *productsOption() const; + NoInstallOption *noInstallOption() const; + InstallRootOption *installRootOption() const; + RemoveFirstOption *removeFirstoption() const; + NoBuildOption *noBuildOption() const; + ForceTimeStampCheckOption *forceTimestampCheckOption() const; + ForceOutputCheckOption *forceOutputCheckOption() const; + BuildNonDefaultOption *buildNonDefaultOption() const; + LogTimeOption *logTimeOption() const; + CommandEchoModeOption *commandEchoModeOption() const; + SettingsDirOption *settingsDirOption() const; + JobLimitsOption *jobLimitsOption() const; + RespectProjectJobLimitsOption *respectProjectJobLimitsOption() const; + GeneratorOption *generatorOption() const; + WaitLockOption *waitLockOption() const; + DisableFallbackProviderOption *disableFallbackProviderOption() const; + RunEnvConfigOption *runEnvConfigOption() const; + +private: + mutable QHash m_options; +}; + +} // namespace qbs + +#endif // QBS_COMMANDLINEOPTIONPOOL_H diff --git a/src/app/qbs/parser/commandlineparser.cpp b/src/app/qbs/parser/commandlineparser.cpp new file mode 100644 index 00000000..052f6b92 --- /dev/null +++ b/src/app/qbs/parser/commandlineparser.cpp @@ -0,0 +1,630 @@ +/**************************************************************************** +** +** 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 "commandlineparser.h" + +#include "commandlineoption.h" +#include "commandlineoptionpool.h" +#include "commandpool.h" +#include "parsercommand.h" +#include "../qbstool.h" +#include "../../shared/logging/consolelogger.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#ifdef Q_OS_UNIX +#include +#endif + +namespace qbs { +using Internal::Tr; + +class CommandLineParser::CommandLineParserPrivate +{ +public: + CommandLineParserPrivate(); + + void doParse(); + Command *commandFromString(const QString &commandString) const; + QList allCommands() const; + QString generalHelp() const; + + void setupProjectFile(); + void setupBuildDirectory(); + void setupProgress(); + void setupLogLevel(); + void setupBuildOptions(); + void setupBuildConfigurations(); + bool checkForExistingBuildConfiguration(const QList &buildConfigs, + const QString &configurationName); + bool withNonDefaultProducts() const; + bool dryRun() const; + QString settingsDir() const { return optionPool.settingsDirOption()->settingsDir(); } + + CommandEchoMode echoMode() const; + + QString propertyName(const QString &aCommandLineName) const; + + QStringList commandLine; + Command *command; + QString projectFilePath; + QString projectBuildDirectory; + BuildOptions buildOptions; + QList buildConfigurations; + CommandLineOptionPool optionPool; + CommandPool commandPool; + bool showProgress; + bool logTime; +}; + +CommandLineParser::CommandLineParser() : d(nullptr) +{ +} + +CommandLineParser::~CommandLineParser() +{ + delete d; +} + +void CommandLineParser::printHelp() const +{ + QTextStream stream(stdout); + + Q_ASSERT(d->command == d->commandPool.getCommand(HelpCommandType)); + const auto helpCommand = static_cast(d->command); + if (helpCommand->commandToDescribe().isEmpty()) { + stream << "Qbs " QBS_VERSION ", a cross-platform build tool.\n"; + stream << d->generalHelp(); + } else { + const Command * const commandToDescribe + = d->commandFromString(helpCommand->commandToDescribe()); + if (commandToDescribe) { + stream << commandToDescribe->longDescription(); + } else if (!QbsTool::tryToRunTool(helpCommand->commandToDescribe(), + QStringList(QStringLiteral("--help")))) { + throw ErrorInfo(Tr::tr("No such command '%1'.\n%2") + .arg(helpCommand->commandToDescribe(), d->generalHelp())); + } + } +} + +CommandType CommandLineParser::command() const +{ + return d->command->type(); +} + +QString CommandLineParser::projectFilePath() const +{ + return d->projectFilePath; +} + +QString CommandLineParser::projectBuildDirectory() const +{ + return d->projectBuildDirectory; +} + +BuildOptions CommandLineParser::buildOptions(const QString &profile) const +{ + Settings settings(settingsDir()); + Preferences preferences(&settings, profile); + + if (d->buildOptions.maxJobCount() <= 0) { + d->buildOptions.setMaxJobCount(preferences.jobs()); + } + + if (d->buildOptions.echoMode() < 0) { + d->buildOptions.setEchoMode(preferences.defaultEchoMode()); + } + + return d->buildOptions; +} + +CleanOptions CommandLineParser::cleanOptions(const QString &profile) const +{ + CleanOptions options; + options.setDryRun(buildOptions(profile).dryRun()); + options.setKeepGoing(buildOptions(profile).keepGoing()); + options.setLogElapsedTime(logTime()); + return options; +} + +GenerateOptions CommandLineParser::generateOptions() const +{ + GenerateOptions options; + options.setGeneratorName(d->optionPool.generatorOption()->generatorName()); + return options; +} + +InstallOptions CommandLineParser::installOptions(const QString &profile) const +{ + InstallOptions options; + options.setRemoveExistingInstallation(d->optionPool.removeFirstoption()->enabled()); + options.setInstallRoot(d->optionPool.installRootOption()->installRoot()); + options.setInstallIntoSysroot(d->optionPool.installRootOption()->useSysroot()); + if (!options.installRoot().isEmpty()) { + QFileInfo fi(options.installRoot()); + if (!fi.isAbsolute()) + options.setInstallRoot(fi.absoluteFilePath()); + } + options.setDryRun(buildOptions(profile).dryRun()); + options.setKeepGoing(buildOptions(profile).keepGoing()); + options.setLogElapsedTime(logTime()); + return options; +} + +bool CommandLineParser::forceTimestampCheck() const +{ + return d->optionPool.forceTimestampCheckOption()->enabled(); +} + +bool CommandLineParser::forceOutputCheck() const +{ + return d->optionPool.forceOutputCheckOption()->enabled(); +} + +bool CommandLineParser::dryRun() const +{ + return d->dryRun(); +} + +bool CommandLineParser::forceProbesExecution() const +{ + return d->optionPool.forceProbesOption()->enabled(); +} + +bool CommandLineParser::waitLockBuildGraph() const +{ + return d->optionPool.waitLockOption()->enabled(); +} + +bool CommandLineParser::disableFallbackProvider() const +{ + return d->optionPool.disableFallbackProviderOption()->enabled(); +} + +bool CommandLineParser::logTime() const +{ + return d->logTime; +} + +bool CommandLineParser::withNonDefaultProducts() const +{ + return d->withNonDefaultProducts(); +} + +bool CommandLineParser::buildBeforeInstalling() const +{ + return !d->optionPool.noBuildOption()->enabled(); +} + +QStringList CommandLineParser::runArgs() const +{ + Q_ASSERT(d->command->type() == RunCommandType); + return static_cast(d->command)->targetParameters(); +} + +QStringList CommandLineParser::products() const +{ + return d->optionPool.productsOption()->arguments(); +} + +QStringList CommandLineParser::runEnvConfig() const +{ + return d->optionPool.runEnvConfigOption()->arguments(); +} + +bool CommandLineParser::showProgress() const +{ + return d->showProgress; +} + +bool CommandLineParser::showVersion() const +{ + return d->command->type() == VersionCommandType; +} + +QString CommandLineParser::settingsDir() const +{ + return d->settingsDir(); +} + +QString CommandLineParser::commandName() const +{ + return d->command->representation(); +} + +bool CommandLineParser::commandCanResolve() const +{ + return d->command->canResolve(); +} + +QString CommandLineParser::commandDescription() const +{ + return d->command->longDescription(); +} + +static QString getBuildConfigurationName(const QVariantMap &buildConfig) +{ + return buildConfig.value(QStringLiteral("qbs.configurationName")).toString(); +} + +QList CommandLineParser::buildConfigurations() const +{ + return d->buildConfigurations; +} + +bool CommandLineParser::parseCommandLine(const QStringList &args) +{ + delete d; + d = new CommandLineParserPrivate; + d->commandLine = args; + try { + d->doParse(); + return true; + } catch (const ErrorInfo &error) { + qbsError() << error.toString(); + return false; + } +} + + +CommandLineParser::CommandLineParserPrivate::CommandLineParserPrivate() + : command(nullptr), commandPool(optionPool), showProgress(false), logTime(false) +{ +} + +void CommandLineParser::CommandLineParserPrivate::doParse() +{ + if (commandLine.empty()) { // No command given, use default. + command = commandPool.getCommand(BuildCommandType); + } else { + command = commandFromString(commandLine.front()); + if (command) { + commandLine.removeFirst(); + } else { // No command given. + if (commandLine.front() == QLatin1String("-h") + || commandLine.front() == QLatin1String("--help")) { + command = commandPool.getCommand(HelpCommandType); + commandLine.takeFirst(); + } else if (commandLine.front() == QLatin1String("-V") + || commandLine.front() == QLatin1String("--version")) { + command = commandPool.getCommand(VersionCommandType); + commandLine.takeFirst(); + } else { + command = commandPool.getCommand(BuildCommandType); + } + } + } + command->parse(commandLine); + + if (command->type() == HelpCommandType || command->type() == VersionCommandType) + return; + + setupBuildDirectory(); + setupBuildConfigurations(); + setupProjectFile(); + setupProgress(); + setupLogLevel(); + setupBuildOptions(); +} + +Command *CommandLineParser::CommandLineParserPrivate::commandFromString(const QString &commandString) const +{ + const auto commands = allCommands(); + for (Command * const command : commands) { + if (command->representation() == commandString) + return command; + } + return nullptr; +} + +QList CommandLineParser::CommandLineParserPrivate::allCommands() const +{ + return {commandPool.getCommand(GenerateCommandType), + commandPool.getCommand(ResolveCommandType), + commandPool.getCommand(BuildCommandType), + commandPool.getCommand(CleanCommandType), + commandPool.getCommand(RunCommandType), + commandPool.getCommand(ShellCommandType), + commandPool.getCommand(StatusCommandType), + commandPool.getCommand(UpdateTimestampsCommandType), + commandPool.getCommand(InstallCommandType), + commandPool.getCommand(DumpNodesTreeCommandType), + commandPool.getCommand(ListProductsCommandType), + commandPool.getCommand(VersionCommandType), + commandPool.getCommand(SessionCommandType), + commandPool.getCommand(HelpCommandType)}; +} + +static QString extractToolDescription(const QString &tool, const QString &output) +{ + if (tool == QLatin1String("create-project")) { + // This command uses QCommandLineParser, where the description is not in the first line. + const int eol1Pos = output.indexOf(QLatin1Char('\n')); + const int eol2Pos = output.indexOf(QLatin1Char('\n'), eol1Pos + 1); + return output.mid(eol1Pos + 1, eol2Pos - eol1Pos - 1); + } + return output.left(output.indexOf(QLatin1Char('\n'))); +} + +QString CommandLineParser::CommandLineParserPrivate::generalHelp() const +{ + QString help = Tr::tr("Usage: qbs [command] [command parameters]\n"); + help += Tr::tr("Built-in commands:\n"); + const int rhsIndentation = 30; + + // Sorting the commands by name is nicer for the user. + QMap commandMap; + const auto commands = allCommands(); + for (const Command * command : commands) + commandMap.insert(command->representation(), command); + + for (const Command * command : qAsConst(commandMap)) { + help.append(QLatin1String(" ")).append(command->representation()); + const QString whitespace + = QString(rhsIndentation - 2 - command->representation().size(), QLatin1Char(' ')); + help.append(whitespace).append(command->shortDescription()).append(QLatin1Char('\n')); + } + + QStringList toolNames = QbsTool::allToolNames(); + toolNames.sort(); + if (!toolNames.empty()) { + help.append(QLatin1Char('\n')).append(Tr::tr("Auxiliary commands:\n")); + for (const QString &toolName : qAsConst(toolNames)) { + help.append(QLatin1String(" ")).append(toolName); + const QString whitespace = QString(rhsIndentation - 2 - toolName.size(), + QLatin1Char(' ')); + QbsTool tool; + tool.runTool(toolName, QStringList(QStringLiteral("--help"))); + if (tool.exitCode() != 0) + continue; + const QString shortDescription = extractToolDescription(toolName, tool.stdOut()); + help.append(whitespace).append(shortDescription).append(QLatin1Char('\n')); + } + } + + return help; +} + +void CommandLineParser::CommandLineParserPrivate::setupProjectFile() +{ + projectFilePath = optionPool.fileOption()->projectFilePath(); +} + +void CommandLineParser::CommandLineParserPrivate::setupBuildDirectory() +{ + projectBuildDirectory = optionPool.buildDirectoryOption()->projectBuildDirectory(); +} + +void CommandLineParser::CommandLineParserPrivate::setupBuildOptions() +{ + buildOptions.setDryRun(dryRun()); + QStringList changedFiles = optionPool.changedFilesOption()->arguments(); + QDir currentDir; + for (QString &file : changedFiles) + file = QDir::fromNativeSeparators(currentDir.absoluteFilePath(file)); + buildOptions.setChangedFiles(changedFiles); + buildOptions.setKeepGoing(optionPool.keepGoingOption()->enabled()); + buildOptions.setForceTimestampCheck(optionPool.forceTimestampCheckOption()->enabled()); + buildOptions.setForceOutputCheck(optionPool.forceOutputCheckOption()->enabled()); + const JobsOption * jobsOption = optionPool.jobsOption(); + buildOptions.setMaxJobCount(jobsOption->jobCount()); + buildOptions.setLogElapsedTime(logTime); + buildOptions.setEchoMode(echoMode()); + buildOptions.setInstall(!optionPool.noInstallOption()->enabled()); + buildOptions.setRemoveExistingInstallation(optionPool.removeFirstoption()->enabled()); + buildOptions.setJobLimits(optionPool.jobLimitsOption()->jobLimits()); + buildOptions.setProjectJobLimitsTakePrecedence( + optionPool.respectProjectJobLimitsOption()->enabled()); + buildOptions.setSettingsDirectory(settingsDir()); +} + +void CommandLineParser::CommandLineParserPrivate::setupBuildConfigurations() +{ + // first: configuration name, second: properties. + // Empty configuration name used for global properties. + using PropertyListItem = std::pair; + QList propertiesPerConfiguration; + + const QString configurationNameKey = QStringLiteral("qbs.configurationName"); + QString currentConfigurationName; + QVariantMap currentProperties; + const auto args = command->additionalArguments(); + for (const QString &arg : args) { + const int sepPos = arg.indexOf(QLatin1Char(':')); + QBS_CHECK(sepPos > 0); + const QString key = arg.left(sepPos); + const QString rawValue = arg.mid(sepPos + 1); + if (key == QLatin1String("config") || key == configurationNameKey) { + propertiesPerConfiguration.push_back(std::make_pair(currentConfigurationName, + currentProperties)); + currentConfigurationName = rawValue; + currentProperties.clear(); + continue; + } + currentProperties.insert(propertyName(key), representationToSettingsValue(rawValue)); + } + propertiesPerConfiguration.push_back(std::make_pair(currentConfigurationName, + currentProperties)); + + if (propertiesPerConfiguration.size() == 1) // No configuration name specified on command line. + propertiesPerConfiguration.push_back(PropertyListItem(QStringLiteral("default"), + QVariantMap())); + + const QVariantMap globalProperties = propertiesPerConfiguration.takeFirst().second; + QList buildConfigs; + for (const PropertyListItem &item : qAsConst(propertiesPerConfiguration)) { + QVariantMap properties = item.second; + for (QVariantMap::ConstIterator globalPropIt = globalProperties.constBegin(); + globalPropIt != globalProperties.constEnd(); ++globalPropIt) { + if (!properties.contains(globalPropIt.key())) + properties.insert(globalPropIt.key(), globalPropIt.value()); + } + + const QString configurationName = item.first; + if (checkForExistingBuildConfiguration(buildConfigs, configurationName)) { + qbsWarning() << Tr::tr("Ignoring redundant request to build for configuration '%1'.") + .arg(configurationName); + continue; + } + + properties.insert(configurationNameKey, configurationName); + buildConfigs.push_back(properties); + } + + buildConfigurations = buildConfigs; +} + +void CommandLineParser::CommandLineParserPrivate::setupProgress() +{ + const ShowProgressOption * const option = optionPool.showProgressOption(); + showProgress = option->enabled(); +#ifdef Q_OS_UNIX + if (showProgress && !isatty(STDOUT_FILENO)) { + showProgress = false; + qbsWarning() << Tr::tr("Ignoring option '%1', because standard output is " + "not connected to a terminal.").arg(option->longRepresentation()); + } +#endif +} + +void CommandLineParser::CommandLineParserPrivate::setupLogLevel() +{ + const LogLevelOption * const logLevelOption = optionPool.logLevelOption(); + const VerboseOption * const verboseOption = optionPool.verboseOption(); + const QuietOption * const quietOption = optionPool.quietOption(); + int logLevel = logLevelOption->logLevel(); + logLevel += verboseOption->count(); + logLevel -= quietOption->count(); + + if (showProgress && logLevel != LoggerMinLevel) { + const bool logLevelWasSetByUser + = logLevelOption->logLevel() != defaultLogLevel() + || verboseOption->count() > 0 || quietOption->count() > 0; + if (logLevelWasSetByUser) { + qbsInfo() << Tr::tr("Setting log level to '%1', because option '%2'" + " has been given.").arg(logLevelName(LoggerMinLevel), + optionPool.showProgressOption()->longRepresentation()); + } + logLevel = LoggerMinLevel; + } + if (logLevel < LoggerMinLevel) { + qbsWarning() << Tr::tr("Cannot decrease log level as much as specified; using '%1'.") + .arg(logLevelName(LoggerMinLevel)); + logLevel = LoggerMinLevel; + } else if (logLevel > LoggerMaxLevel) { + qbsWarning() << Tr::tr("Cannot increase log level as much as specified; using '%1'.") + .arg(logLevelName(LoggerMaxLevel)); + logLevel = LoggerMaxLevel; + } + + logTime = optionPool.logTimeOption()->enabled(); + if (showProgress && logTime) { + qbsWarning() << Tr::tr("Options '%1' and '%2' are incompatible. Ignoring '%2'.") + .arg(optionPool.showProgressOption()->longRepresentation(), + optionPool.logTimeOption()->longRepresentation()); + logTime = false; + } + + ConsoleLogger::instance().logSink()->setLogLevel(static_cast(logLevel)); +} + +QString CommandLineParser::CommandLineParserPrivate::propertyName(const QString &aCommandLineName) const +{ + // Make fully-qualified, ie "platform" -> "qbs.platform" + if (aCommandLineName.contains(QLatin1Char('.'))) + return aCommandLineName; + else + return QLatin1String("qbs.") + aCommandLineName; +} + +bool CommandLineParser::CommandLineParserPrivate::checkForExistingBuildConfiguration( + const QList &buildConfigs, const QString &configurationName) +{ + for (const QVariantMap &buildConfig : buildConfigs) { + if (configurationName == getBuildConfigurationName(buildConfig)) + return true; + } + return false; +} + +bool CommandLineParser::CommandLineParserPrivate::withNonDefaultProducts() const +{ + if (command->type() == GenerateCommandType) + return true; + return optionPool.buildNonDefaultOption()->enabled(); +} + +bool CommandLineParser::CommandLineParserPrivate::dryRun() const +{ + if (command->type() == GenerateCommandType || command->type() == ListProductsCommandType) + return true; + return optionPool.dryRunOption()->enabled(); +} + +CommandEchoMode CommandLineParser::CommandLineParserPrivate::echoMode() const +{ + if (command->type() == GenerateCommandType) + return CommandEchoModeSilent; + + if (optionPool.commandEchoModeOption()->commandEchoMode() < CommandEchoModeInvalid) + return optionPool.commandEchoModeOption()->commandEchoMode(); + + return defaultCommandEchoMode(); +} + +} // namespace qbs diff --git a/src/app/qbs/parser/commandlineparser.h b/src/app/qbs/parser/commandlineparser.h new file mode 100644 index 00000000..d47657b1 --- /dev/null +++ b/src/app/qbs/parser/commandlineparser.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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 QBS_COMMANDLINEPARSER_H +#define QBS_COMMANDLINEPARSER_H + +#include "commandtype.h" + +#include +#include + +namespace qbs { +class BuildOptions; +class CleanOptions; +class GenerateOptions; +class InstallOptions; +class Settings; + +class CommandLineParser +{ + Q_DISABLE_COPY(CommandLineParser) +public: + CommandLineParser(); + ~CommandLineParser(); + + bool parseCommandLine(const QStringList &args); + void printHelp() const; + + CommandType command() const; + QString commandName() const; + bool commandCanResolve() const; + QString commandDescription() const; + QString projectFilePath() const; + QString projectBuildDirectory() const; + BuildOptions buildOptions(const QString &profile) const; + CleanOptions cleanOptions(const QString &profile) const; + GenerateOptions generateOptions() const; + InstallOptions installOptions(const QString &profile) const; + bool forceTimestampCheck() const; + bool forceOutputCheck() const; + bool dryRun() const; + bool forceProbesExecution() const; + bool waitLockBuildGraph() const; + bool disableFallbackProvider() const; + bool logTime() const; + bool withNonDefaultProducts() const; + bool buildBeforeInstalling() const; + QStringList runArgs() const; + QStringList products() const; + QStringList runEnvConfig() const; + QList buildConfigurations() const; + bool showProgress() const; + bool showVersion() const; + QString settingsDir() const; + +private: + class CommandLineParserPrivate; + CommandLineParserPrivate *d; +}; + +} // namespace qbs + +#endif // QBS_COMMANDLINEPARSER_H diff --git a/src/app/qbs/parser/commandpool.cpp b/src/app/qbs/parser/commandpool.cpp new file mode 100644 index 00000000..1362a563 --- /dev/null +++ b/src/app/qbs/parser/commandpool.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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 "commandpool.h" + +#include "parsercommand.h" + +namespace qbs { + +CommandPool::CommandPool(CommandLineOptionPool &optionPool) : m_optionPool(optionPool) +{ +} + +CommandPool::~CommandPool() +{ + qDeleteAll(m_commands); +} + +qbs::Command *CommandPool::getCommand(CommandType type) const +{ + Command *& command = m_commands[type]; + if (!command) { + switch (type) { + case ResolveCommandType: + command = new ResolveCommand(m_optionPool); + break; + case GenerateCommandType: + command = new GenerateCommand(m_optionPool); + break; + case BuildCommandType: + command = new BuildCommand(m_optionPool); + break; + case CleanCommandType: + command = new CleanCommand(m_optionPool); + break; + case RunCommandType: + command = new RunCommand(m_optionPool); + break; + case ShellCommandType: + command = new ShellCommand(m_optionPool); + break; + case StatusCommandType: + command = new StatusCommand(m_optionPool); + break; + case UpdateTimestampsCommandType: + command = new UpdateTimestampsCommand(m_optionPool); + break; + case InstallCommandType: + command = new InstallCommand(m_optionPool); + break; + case DumpNodesTreeCommandType: + command = new DumpNodesTreeCommand(m_optionPool); + break; + case ListProductsCommandType: + command = new ListProductsCommand(m_optionPool); + break; + case HelpCommandType: + command = new HelpCommand(m_optionPool); + break; + case VersionCommandType: + command = new VersionCommand(m_optionPool); + break; + case SessionCommandType: + command = new SessionCommand(m_optionPool); + break; + } + } + return command; +} + +} // namespace qbs diff --git a/src/app/qbs/parser/commandpool.h b/src/app/qbs/parser/commandpool.h new file mode 100644 index 00000000..c42017d0 --- /dev/null +++ b/src/app/qbs/parser/commandpool.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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 QBS_COMMANDPOOL_H +#define QBS_COMMANDPOOL_H + +#include "commandtype.h" + +#include + +namespace qbs { +class Command; +class CommandLineOptionPool; + +class CommandPool +{ + Q_DISABLE_COPY(CommandPool) +public: + CommandPool(CommandLineOptionPool &optionPool); + ~CommandPool(); + + Command *getCommand(CommandType type) const; + +private: + CommandLineOptionPool &m_optionPool; + mutable QHash m_commands; +}; + +} // namespace qbs + +#endif // QBS_COMMANDPOOL_H diff --git a/src/app/qbs/parser/commandtype.h b/src/app/qbs/parser/commandtype.h new file mode 100644 index 00000000..7d70356e --- /dev/null +++ b/src/app/qbs/parser/commandtype.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** 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 COMMANDTYPE_H +#define COMMANDTYPE_H + +namespace qbs { + +enum CommandType { + ResolveCommandType, BuildCommandType, CleanCommandType, RunCommandType, ShellCommandType, + StatusCommandType, UpdateTimestampsCommandType, DumpNodesTreeCommandType, + InstallCommandType, HelpCommandType, GenerateCommandType, ListProductsCommandType, + VersionCommandType, SessionCommandType, +}; + +} // namespace qbs + +#endif // COMMANDTYPE_H diff --git a/src/app/qbs/parser/parser.pri b/src/app/qbs/parser/parser.pri new file mode 100644 index 00000000..f708f113 --- /dev/null +++ b/src/app/qbs/parser/parser.pri @@ -0,0 +1,16 @@ +SOURCES += \ + $$PWD/commandlineparser.cpp \ + $$PWD/commandpool.cpp \ + $$PWD/commandlineoption.cpp \ + $$PWD/commandlineoptionpool.cpp \ + $$PWD/parsercommand.cpp + +HEADERS += \ + $$PWD/commandlineparser.h \ + $$PWD/commandpool.h \ + $$PWD/commandlineoption.h \ + $$PWD/commandlineoptionpool.h \ + $$PWD/commandtype.h \ + $$PWD/parsercommand.h + +include(../../../../qbs_version.pri) diff --git a/src/app/qbs/parser/parsercommand.cpp b/src/app/qbs/parser/parsercommand.cpp new file mode 100644 index 00000000..799bf5dc --- /dev/null +++ b/src/app/qbs/parser/parsercommand.cpp @@ -0,0 +1,617 @@ +/**************************************************************************** +** +** 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 "parsercommand.h" + +#include "commandlineoption.h" +#include "commandlineoptionpool.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace qbs { +using namespace Internal; + +Command::~Command() = default; + +void Command::parse(QStringList &input) +{ + while (!input.empty()) + parseNext(input); +} + +bool Command::canResolve() const +{ + return supportedOptions().contains(CommandLineOption::FileOptionType); +} + +void Command::parsePropertyAssignment(const QString &argument) +{ + const auto throwError = [argument](const QString &msgTemplate) { + ErrorInfo error(msgTemplate.arg(argument)); + error.append(QLatin1String("Expected an assignment of the form :, " + "profile: or config:.")); + throw error; + }; + if (argument.startsWith(QLatin1Char('-'))) + throwError(Tr::tr("Unexpected option '%1'.")); + const int sepPos = argument.indexOf(QLatin1Char(':')); + if (sepPos == -1) + throwError(Tr::tr("Unexpected command line parameter '%1'.")); + if (sepPos == 0) + throwError(Tr::tr("Empty key not allowed in assignment '%1'.")); + if (!canResolve() && argument.contains(QLatin1Char(':')) + && !argument.startsWith(QLatin1String("config:"))) { + throw ErrorInfo(Tr::tr("The '%1' command does not support property assignments.") + .arg(representation())); + } + m_additionalArguments << argument; +} + +QList Command::actualSupportedOptions() const +{ + QList options = supportedOptions(); + if (type() != HelpCommandType) + options.push_back(CommandLineOption::SettingsDirOptionType); // Valid for almost all commands. + return options; +} + +void Command::parseOption(QStringList &input) +{ + const QString optionString = input.front(); + QBS_CHECK(optionString.startsWith(QLatin1Char('-'))); + input.removeFirst(); + if (optionString.size() == 1) + throwError(Tr::tr("Empty options are not allowed.")); + + // Split up grouped short options. + if (optionString.at(1) != QLatin1Char('-') && optionString.size() > 2) { + QString parameter; + for (int i = optionString.size(); --i > 0;) { + const QChar c = optionString.at(i); + if (c.isDigit()) { + parameter.prepend(c); + } else { + if (!parameter.isEmpty()) { + input.prepend(parameter); + parameter.clear(); + } + input.prepend(QLatin1Char('-') + c); + } + } + if (!parameter.isEmpty()) + throwError(Tr::tr("Unknown numeric option '%1'.").arg(parameter)); + return; + } + + bool matchFound = false; + const auto optionTypes = actualSupportedOptions(); + for (const CommandLineOption::Type optionType : optionTypes) { + CommandLineOption * const option = optionPool().getOption(optionType); + if (option->shortRepresentation() != optionString + && option->longRepresentation() != optionString) { + continue; + } + if (contains(m_usedOptions, option) && !option->canAppearMoreThanOnce()) + throwError(Tr::tr("Option '%1' cannot appear more than once.").arg(optionString)); + option->parse(type(), optionString, input); + m_usedOptions.insert(option); + matchFound = true; + break; + } + if (!matchFound) + throwError(Tr::tr("Unknown option '%1'.").arg(optionString)); +} + +void Command::parseNext(QStringList &input) +{ + QBS_CHECK(!input.empty()); + if (input.front().startsWith(QLatin1Char('-'))) + parseOption(input); + else + parsePropertyAssignment(input.takeFirst()); +} + +QString Command::supportedOptionsDescription() const +{ + // Sorting the options by name is nicer for the user. + QMap optionMap; + const auto opTypes = actualSupportedOptions(); + for (const CommandLineOption::Type opType : opTypes) { + const CommandLineOption * const option = optionPool().getOption(opType); + optionMap.insert(option->longRepresentation(), option); + } + + QString s = Tr::tr("The possible options are:\n"); + for (const CommandLineOption *option : qAsConst(optionMap)) + s += option->description(type()); + return s; +} + +void Command::throwError(const QString &reason) +{ + ErrorInfo error(Tr::tr("Invalid use of command '%1': %2").arg(representation(), reason)); + error.append(Tr::tr("Type 'qbs help %1' to see how to use this command.") + .arg(representation())); + throw error; +} + + +QString ResolveCommand::shortDescription() const +{ + return Tr::tr("Resolve a project without building it."); +} + +QString ResolveCommand::longDescription() const +{ + QString description = Tr::tr( + "qbs %1 [options] [[config:] [:] ...] ...\n") + .arg(representation()); + description += Tr::tr("Resolves a project in one or more configuration(s).\n"); + return description += supportedOptionsDescription(); +} + +QString ResolveCommand::representation() const +{ + return QStringLiteral("resolve"); +} + +static QList resolveOptions() +{ + return {CommandLineOption::FileOptionType, + CommandLineOption::BuildDirectoryOptionType, + CommandLineOption::LogLevelOptionType, + CommandLineOption::VerboseOptionType, + CommandLineOption::QuietOptionType, + CommandLineOption::ShowProgressOptionType, + CommandLineOption::DryRunOptionType, + CommandLineOption::ForceProbesOptionType, + CommandLineOption::LogTimeOptionType, + CommandLineOption::DisableFallbackProviderType}; +} + +QList ResolveCommand::supportedOptions() const +{ + return resolveOptions(); +} + +QString GenerateCommand::shortDescription() const +{ + return Tr::tr("Generate project files for another build tool."); +} + +QString GenerateCommand::longDescription() const +{ + QString description = Tr::tr( + "qbs %1 [options] [[config:] [:] ...] ...\n") + .arg(representation()); + description += Tr::tr("Generates files to build the project using another build tool.\n"); + return description += supportedOptionsDescription(); +} + +QString GenerateCommand::representation() const +{ + return QStringLiteral("generate"); +} + +QList GenerateCommand::supportedOptions() const +{ + return {CommandLineOption::FileOptionType, + CommandLineOption::BuildDirectoryOptionType, + CommandLineOption::LogLevelOptionType, + CommandLineOption::VerboseOptionType, + CommandLineOption::QuietOptionType, + CommandLineOption::ShowProgressOptionType, + CommandLineOption::InstallRootOptionType, + CommandLineOption::LogTimeOptionType, + CommandLineOption::GeneratorOptionType}; +} + +QString BuildCommand::shortDescription() const +{ + return Tr::tr("Build (parts of) a project. This is the default command."); +} + +QString BuildCommand::longDescription() const +{ + QString description = Tr::tr( + "qbs %1 [options] [[config:] [:] ...] ...\n") + .arg(representation()); + description += Tr::tr("Builds a project in one or more configuration(s).\n"); + return description += supportedOptionsDescription(); +} + +static QString buildCommandRepresentation() { return QStringLiteral("build"); } + +QString BuildCommand::representation() const +{ + return buildCommandRepresentation(); +} + +static QList buildOptions() +{ + QList options = resolveOptions(); + return options << CommandLineOption::KeepGoingOptionType + << CommandLineOption::ProductsOptionType + << CommandLineOption::ChangedFilesOptionType + << CommandLineOption::ForceTimestampCheckOptionType + << CommandLineOption::ForceOutputCheckOptionType + << CommandLineOption::BuildNonDefaultOptionType + << CommandLineOption::JobsOptionType + << CommandLineOption::CommandEchoModeOptionType + << CommandLineOption::NoInstallOptionType + << CommandLineOption::RemoveFirstOptionType + << CommandLineOption::JobLimitsOptionType + << CommandLineOption::RespectProjectJobLimitsOptionType + << CommandLineOption::WaitLockOptionType; +} + +QList BuildCommand::supportedOptions() const +{ + return buildOptions(); +} + +QString CleanCommand::shortDescription() const +{ + return Tr::tr("Remove the files generated during a build."); +} + +QString CleanCommand::longDescription() const +{ + QString description = Tr::tr( + "qbs %1 [options] [config:] ...\n") + .arg(representation()); + description += Tr::tr("Removes build artifacts for the given configuration(s).\n"); + return description += supportedOptionsDescription(); +} + +QString CleanCommand::representation() const +{ + return QStringLiteral("clean"); +} + +QList CleanCommand::supportedOptions() const +{ + return {CommandLineOption::BuildDirectoryOptionType, + CommandLineOption::DryRunOptionType, + CommandLineOption::KeepGoingOptionType, + CommandLineOption::LogTimeOptionType, + CommandLineOption::ProductsOptionType, + CommandLineOption::QuietOptionType, + CommandLineOption::SettingsDirOptionType, + CommandLineOption::ShowProgressOptionType, + CommandLineOption::VerboseOptionType}; +} + +QString InstallCommand::shortDescription() const +{ + return Tr::tr("Install (parts of) a project."); +} + +QString InstallCommand::longDescription() const +{ + QString description = Tr::tr( + "qbs %1 [options] [[config:] [:] ...]\n") + .arg(representation()); + description += Tr::tr("Install all files marked as installable " + "to their respective destinations.\n" + "The project is built first, if necessary, unless the '%1' option " + "is given.\n").arg(optionPool().noBuildOption()->longRepresentation()); + return description += supportedOptionsDescription(); +} + +QString InstallCommand::representation() const +{ + return QStringLiteral("install"); +} + +QList installOptions() +{ + QList options = buildOptions() + << CommandLineOption::InstallRootOptionType + << CommandLineOption::NoBuildOptionType; + options.removeOne(CommandLineOption::NoInstallOptionType); + return options; +} + +QList InstallCommand::supportedOptions() const +{ + return installOptions(); +} + +QString RunCommand::shortDescription() const +{ + return QStringLiteral("Run an executable generated by building a project."); +} + +QString RunCommand::longDescription() const +{ + QString description = Tr::tr( + "qbs %1 [options] [config:] [:] ... " + "[ -- ]\n").arg(representation()); + description += Tr::tr("Run the specified product's executable with the specified arguments.\n"); + description += Tr::tr("If the project has only one product, the '%1' option may be omitted.\n") + .arg(optionPool().productsOption()->longRepresentation()); + description += Tr::tr("The product will be built if it is not up to date; " + "see the '%2' command.\n").arg(buildCommandRepresentation()); + return description += supportedOptionsDescription(); +} + +QString RunCommand::representation() const +{ + return QStringLiteral("run"); +} + +QList RunCommand::supportedOptions() const +{ + return QList() << installOptions() + << CommandLineOption::RunEnvConfigOptionType; +} + +void RunCommand::parseNext(QStringList &input) +{ + QBS_CHECK(!input.empty()); + if (input.front() != QLatin1String("--")) { + Command::parseNext(input); + return; + } + input.removeFirst(); + m_targetParameters = input; + input.clear(); +} + +QString ShellCommand::shortDescription() const +{ + return Tr::tr("Open a shell with a product's environment."); +} + +QString ShellCommand::longDescription() const +{ + QString description = Tr::tr( + "qbs %1 [options] [config:] [:] ...\n") + .arg(representation()); + description += Tr::tr("Opens a shell in the same environment that a build with the given " + "parameters would use.\n"); + return description += supportedOptionsDescription(); +} + +QString ShellCommand::representation() const +{ + return QStringLiteral("shell"); +} + +QList ShellCommand::supportedOptions() const +{ + return {CommandLineOption::FileOptionType, + CommandLineOption::BuildDirectoryOptionType, + CommandLineOption::ProductsOptionType}; +} + +QString StatusCommand::shortDescription() const +{ + return Tr::tr("Show the status of files in the project directory."); +} + +QString StatusCommand::longDescription() const +{ + QString description = Tr::tr("qbs %1 [options] [config:]\n") + .arg(representation()); + description += Tr::tr("Lists all the files in the project directory and shows whether " + "they are known to qbs in the respective configuration.\n"); + return description += supportedOptionsDescription(); +} + +QString StatusCommand::representation() const +{ + return QStringLiteral("status"); +} + +QList StatusCommand::supportedOptions() const +{ + return {CommandLineOption::BuildDirectoryOptionType}; +} + +QString UpdateTimestampsCommand::shortDescription() const +{ + return Tr::tr("Mark the build as up to date."); +} + +QString UpdateTimestampsCommand::longDescription() const +{ + QString description = Tr::tr( + "qbs %1 [options] [config:] ...\n") + .arg(representation()); + description += Tr::tr("Update the timestamps of all build artifacts, causing the next " + "builds of the project to do nothing if no updates to source files happen in between.\n" + "This functionality is useful if you know that the current changes to source files " + "are irrelevant to the build.\n" + "NOTE: Doing this causes a discrepancy between the \"real world\" and the information " + "in the build graph, so use with care.\n"); + return description += supportedOptionsDescription(); +} + +QString UpdateTimestampsCommand::representation() const +{ + return QStringLiteral("update-timestamps"); +} + +QList UpdateTimestampsCommand::supportedOptions() const +{ + return {CommandLineOption::BuildDirectoryOptionType, + CommandLineOption::LogLevelOptionType, + CommandLineOption::VerboseOptionType, + CommandLineOption::QuietOptionType, + CommandLineOption::ProductsOptionType}; +} + +QString DumpNodesTreeCommand::shortDescription() const +{ + return Tr::tr("Dumps the nodes in the build graph to stdout."); +} + +QString DumpNodesTreeCommand::longDescription() const +{ + QString description = Tr::tr("qbs %1 [options] [config:] ...\n") + .arg(representation()); + description += Tr::tr("Internal command; for debugging purposes only.\n"); + return description += supportedOptionsDescription(); +} + +QString DumpNodesTreeCommand::representation() const +{ + return QStringLiteral("dump-nodes-tree"); +} + +QList DumpNodesTreeCommand::supportedOptions() const +{ + return {CommandLineOption::BuildDirectoryOptionType, + 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] [[config:] " + "[:] ...] ...\n").arg(representation()); + return description += supportedOptionsDescription(); +} + +QString ListProductsCommand::representation() const +{ + return QStringLiteral("list-products"); +} + +QList ListProductsCommand::supportedOptions() const +{ + return {CommandLineOption::FileOptionType, + CommandLineOption::BuildDirectoryOptionType}; +} + +QString HelpCommand::shortDescription() const +{ + return Tr::tr("Show general or command-specific help."); +} + +QString HelpCommand::longDescription() const +{ + QString description = Tr::tr("qbs %1 []\n").arg(representation()); + return description += Tr::tr("Shows either the general help or a description of " + "the given command.\n"); +} + +QString HelpCommand::representation() const +{ + return QStringLiteral("help"); +} + +QList HelpCommand::supportedOptions() const +{ + return {}; +} + +void HelpCommand::parseNext(QStringList &input) +{ + if (input.empty()) + return; + if (input.size() > 1) + throwError(Tr::tr("Cannot describe more than one command.")); + m_command = input.takeFirst(); + QBS_CHECK(input.empty()); +} + +QString VersionCommand::shortDescription() const +{ + return Tr::tr("Print the Qbs version number to stdout."); +} + +QString VersionCommand::longDescription() const +{ + QString description = Tr::tr("qbs %1\n").arg(representation()); + return description += Tr::tr("%1\n").arg(shortDescription()); +} + +QString VersionCommand::representation() const +{ + return QStringLiteral("show-version"); +} + +QList VersionCommand::supportedOptions() const +{ + return {}; +} + +void VersionCommand::parseNext(QStringList &input) +{ + QBS_CHECK(!input.empty()); + throwError(Tr::tr("This command takes no arguments.")); +} + +QString SessionCommand::shortDescription() const +{ + return Tr::tr("Starts a session for an IDE."); +} + +QString SessionCommand::longDescription() const +{ + QString description = Tr::tr("qbs %1\n").arg(representation()); + return description += Tr::tr("Communicates on stdin and stdout via a JSON-based API.\n" + "Intended for use with other tools, such as IDEs.\n"); +} + +QString SessionCommand::representation() const +{ + return QLatin1String("session"); +} + +void SessionCommand::parseNext(QStringList &input) +{ + QBS_CHECK(!input.empty()); + throwError(Tr::tr("This command takes no arguments.")); +} + +} // namespace qbs diff --git a/src/app/qbs/parser/parsercommand.h b/src/app/qbs/parser/parsercommand.h new file mode 100644 index 00000000..8998d38e --- /dev/null +++ b/src/app/qbs/parser/parsercommand.h @@ -0,0 +1,280 @@ +/**************************************************************************** +** +** 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 QBS_PARSER_COMMAND_H +#define QBS_PARSER_COMMAND_H + +#include "commandlineoption.h" +#include "commandtype.h" + +#include + +namespace qbs { +class CommandLineOptionPool; + +class Command +{ +public: + virtual ~Command(); + + virtual CommandType type() const = 0; + virtual QString shortDescription() const = 0; + virtual QString longDescription() const = 0; + virtual QString representation() const = 0; + + void parse(QStringList &input); + QStringList additionalArguments() const { return m_additionalArguments; } + bool canResolve() const; + +protected: + Command(CommandLineOptionPool &optionPool) : m_optionPool(optionPool) {} + + const CommandLineOptionPool &optionPool() const { return m_optionPool; } + QString supportedOptionsDescription() const; + [[noreturn]] void throwError(const QString &reason); + + virtual void parseNext(QStringList &input); + +private: + QList actualSupportedOptions() const; + void parseOption(QStringList &input); + void parsePropertyAssignment(const QString &argument); + + virtual QList supportedOptions() const = 0; + + QStringList m_additionalArguments; + std::set m_usedOptions; + const CommandLineOptionPool &m_optionPool; +}; + +class ResolveCommand : public Command +{ +public: + ResolveCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {} + +private: + CommandType type() const override { return ResolveCommandType; } + QString shortDescription() const override; + QString longDescription() const override; + QString representation() const override; + QList supportedOptions() const override; +}; + +class GenerateCommand : public Command +{ +public: + GenerateCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {} + +private: + CommandType type() const override { return GenerateCommandType; } + QString shortDescription() const override; + QString longDescription() const override; + QString representation() const override; + QList supportedOptions() const override; +}; + +class BuildCommand : public Command +{ +public: + BuildCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {} + +private: + CommandType type() const override { return BuildCommandType; } + QString shortDescription() const override; + QString longDescription() const override; + QString representation() const override; + QList supportedOptions() const override; +}; + +class CleanCommand : public Command +{ +public: + CleanCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {} + +private: + CommandType type() const override { return CleanCommandType; } + QString shortDescription() const override; + QString longDescription() const override; + QString representation() const override; + QList supportedOptions() const override; +}; + +class InstallCommand : public Command +{ +public: + InstallCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {} + +private: + CommandType type() const override { return InstallCommandType; } + QString shortDescription() const override; + QString longDescription() const override; + QString representation() const override; + QList supportedOptions() const override; +}; + +class RunCommand : public Command +{ +public: + RunCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {} + QStringList targetParameters() const { return m_targetParameters; } + +private: + CommandType type() const override { return RunCommandType; } + QString shortDescription() const override; + QString longDescription() const override; + QString representation() const override; + QList supportedOptions() const override; + void parseNext(QStringList &input) override; + + QStringList m_targetParameters; +}; + +class ShellCommand : public Command +{ +public: + ShellCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {} + +private: + CommandType type() const override { return ShellCommandType; } + QString shortDescription() const override; + QString longDescription() const override; + QString representation() const override; + QList supportedOptions() const override; +}; + +// TODO: It seems wrong that a configuration has to be given here. Ideally, this command would just track *all* files regardless of conditions. Is that possible? +class StatusCommand : public Command +{ +public: + StatusCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {} + +private: + CommandType type() const override { return StatusCommandType; } + QString shortDescription() const override; + QString longDescription() const override; + QString representation() const override; + QList supportedOptions() const override; +}; + +class UpdateTimestampsCommand : public Command +{ +public: + UpdateTimestampsCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {} + +private: + CommandType type() const override { return UpdateTimestampsCommandType; } + QString shortDescription() const override; + QString longDescription() const override; + QString representation() const override; + QList supportedOptions() const override; +}; + +class DumpNodesTreeCommand : public Command +{ +public: + DumpNodesTreeCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {} + +private: + CommandType type() const override{ return DumpNodesTreeCommandType; } + QString shortDescription() const override; + QString longDescription() const override; + QString representation() const override; + QList supportedOptions() const 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 supportedOptions() const override; +}; + +class HelpCommand : public Command +{ +public: + HelpCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {} + QString commandToDescribe() const { return m_command; } + +private: + CommandType type() const override { return HelpCommandType; } + QString shortDescription() const override; + QString longDescription() const override; + QString representation() const override; + QList supportedOptions() const override; + void parseNext(QStringList &input) override; + + QString m_command; +}; + +class VersionCommand : public Command +{ +public: + VersionCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {} + +private: + CommandType type() const override { return VersionCommandType; } + QString shortDescription() const override; + QString longDescription() const override; + QString representation() const override; + QList supportedOptions() const override; + void parseNext(QStringList &input) override; +}; + +class SessionCommand : public Command +{ +public: + SessionCommand(CommandLineOptionPool &optionPool) : Command(optionPool) {} + +private: + CommandType type() const override { return SessionCommandType; } + QString shortDescription() const override; + QString longDescription() const override; + QString representation() const override; + QList supportedOptions() const override { return {}; } + void parseNext(QStringList &input) override; +}; + +} // namespace qbs + +#endif // QBS_PARSER_COMMAND_H diff --git a/src/app/qbs/qbs.pro b/src/app/qbs/qbs.pro new file mode 100644 index 00000000..6ce449aa --- /dev/null +++ b/src/app/qbs/qbs.pro @@ -0,0 +1,54 @@ +include(../app.pri) +include(parser/parser.pri) + +TARGET = qbs + +SOURCES += main.cpp \ + ctrlchandler.cpp \ + application.cpp \ + session.cpp \ + sessionpacket.cpp \ + sessionpacketreader.cpp \ + stdinreader.cpp \ + status.cpp \ + consoleprogressobserver.cpp \ + commandlinefrontend.cpp \ + qbstool.cpp + +HEADERS += \ + ctrlchandler.h \ + application.h \ + session.h \ + sessionpacket.h \ + sessionpacketreader.h \ + stdinreader.h \ + status.h \ + consoleprogressobserver.h \ + commandlinefrontend.h \ + qbstool.h + +include(../../library_dirname.pri) +isEmpty(QBS_RELATIVE_LIBEXEC_PATH) { + win32:QBS_RELATIVE_LIBEXEC_PATH=. + else:QBS_RELATIVE_LIBEXEC_PATH=../libexec/qbs +} +isEmpty(QBS_RELATIVE_PLUGINS_PATH):QBS_RELATIVE_PLUGINS_PATH=../$${QBS_LIBRARY_DIRNAME} +isEmpty(QBS_RELATIVE_SEARCH_PATH):QBS_RELATIVE_SEARCH_PATH=.. +DEFINES += QBS_RELATIVE_LIBEXEC_PATH=\\\"$${QBS_RELATIVE_LIBEXEC_PATH}\\\" +DEFINES += QBS_RELATIVE_PLUGINS_PATH=\\\"$${QBS_RELATIVE_PLUGINS_PATH}\\\" +DEFINES += QBS_RELATIVE_SEARCH_PATH=\\\"$${QBS_RELATIVE_SEARCH_PATH}\\\" + +CONFIG(static, static|shared) { + include(../../plugins/qbs_plugin_common.pri) + LIBS += -L$$qbsPluginDestDir + scannerPlugins = cpp qt + for (scannerPlugin, scannerPlugins) { + include(../../plugins/scanner/$$scannerPlugin/$${scannerPlugin}.pri) \ + include(../../plugins/use_plugin.pri) + } + generatorPlugins = clangcompilationdb iarew keiluv makefilegenerator visualstudio + for (generatorPlugin, generatorPlugins) { + include(../../plugins/generator/$$generatorPlugin/$${generatorPlugin}.pri) \ + include(../../plugins/use_plugin.pri) + } +} diff --git a/src/app/qbs/qbs.qbs b/src/app/qbs/qbs.qbs new file mode 100644 index 00000000..530036f3 --- /dev/null +++ b/src/app/qbs/qbs.qbs @@ -0,0 +1,59 @@ +import qbs 1.0 +import qbs.Utilities + +QbsApp { + name: "qbs_app" + Depends { name: "qbs resources" } + targetName: "qbs" + Depends { + condition: Qt.core.staticBuild || qbsbuildconfig.staticBuild + productTypes: ["qbsplugin"] + } + cpp.defines: base.concat([ + "QBS_VERSION=" + Utilities.cStringQuote(qbsversion.version), + "QBS_RELATIVE_LIBEXEC_PATH=" + Utilities.cStringQuote(qbsbuildconfig.relativeLibexecPath), + "QBS_RELATIVE_SEARCH_PATH=" + Utilities.cStringQuote(qbsbuildconfig.relativeSearchPath), + "QBS_RELATIVE_PLUGINS_PATH=" + Utilities.cStringQuote(qbsbuildconfig.relativePluginsPath), + ]) + files: [ + "application.cpp", + "application.h", + "commandlinefrontend.cpp", + "commandlinefrontend.h", + "consoleprogressobserver.cpp", + "consoleprogressobserver.h", + "ctrlchandler.cpp", + "ctrlchandler.h", + "main.cpp", + "qbstool.cpp", + "qbstool.h", + "session.cpp", + "session.h", + "sessionpacket.cpp", + "sessionpacket.h", + "sessionpacketreader.cpp", + "sessionpacketreader.h", + "status.cpp", + "status.h", + "stdinreader.cpp", + "stdinreader.h", + ] + Group { + name: "parser" + prefix: name + '/' + files: [ + "commandlineoption.cpp", + "commandlineoption.h", + "commandlineoptionpool.cpp", + "commandlineoptionpool.h", + "commandlineparser.cpp", + "commandlineparser.h", + "commandpool.cpp", + "commandpool.h", + "commandtype.h", + "parsercommand.cpp", + "parsercommand.h", + ] + } +} + diff --git a/src/app/qbs/qbstool.cpp b/src/app/qbs/qbstool.cpp new file mode 100644 index 00000000..318b7963 --- /dev/null +++ b/src/app/qbs/qbstool.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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 "qbstool.h" + +#include + +#include +#include +#include +#include + +#include + +static QString toolPrefix() { return QStringLiteral("qbs-"); } +static QString qbsBinDir() { return QCoreApplication::applicationDirPath(); } + +static QString qbsToolFilePath(const QString &toolName) +{ + return qbsBinDir() + QLatin1Char('/') + toolPrefix() + + qbs::Internal::HostOsInfo::appendExecutableSuffix(toolName); +} + +void QbsTool::runTool(const QString &toolName, const QStringList &arguments) +{ + m_failedToStart = false; + m_exitCode = -1; + const QString filePath = qbsToolFilePath(toolName); + const QFileInfo fi(filePath); + if (!fi.exists() || !fi.isFile() || !fi.isExecutable()) { + m_failedToStart = true; + return; + } + QProcess toolProc; + toolProc.start(filePath, arguments); + if (!toolProc.waitForStarted()) + m_failedToStart = true; + toolProc.waitForFinished(-1); + m_exitCode = toolProc.exitCode(); + m_stdout = QString::fromLocal8Bit(toolProc.readAllStandardOutput()); + m_stderr = QString::fromLocal8Bit(toolProc.readAllStandardError()); +} + +bool QbsTool::tryToRunTool(const QString &toolName, const QStringList &arguments, int *exitCode) +{ + QbsTool tool; + tool.runTool(toolName, arguments); + if (exitCode) + *exitCode = tool.exitCode(); + if (tool.failedToStart()) + return false; + std::cout << qPrintable(tool.stdOut()); + std::cerr << qPrintable(tool.stdErr()); + return true; +} + +QStringList QbsTool::allToolNames() +{ + const QString suffix = QLatin1String(QBS_HOST_EXE_SUFFIX); + const QStringList toolFileNames = QDir(qbsBinDir()).entryList(QStringList(toolPrefix() + + QStringLiteral("*%1").arg(suffix)), QDir::Files, QDir::Name); + QStringList toolNames; + const int prefixLength = toolPrefix().size(); + for (const QString &toolFileName : toolFileNames) { + toolNames << toolFileName.mid(prefixLength, + toolFileName.size() - prefixLength - suffix.size()); + } + return toolNames; +} diff --git a/src/app/qbs/qbstool.h b/src/app/qbs/qbstool.h new file mode 100644 index 00000000..e6d23036 --- /dev/null +++ b/src/app/qbs/qbstool.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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 QBS_QBSTOOL_H +#define QBS_QBSTOOL_H + +#include + +class QbsTool +{ +public: + void runTool(const QString &toolName, const QStringList &arguments); + + bool failedToStart() const { return m_failedToStart; } + int exitCode() const { return m_exitCode; } + QString stdOut() const { return m_stdout; } + QString stdErr() const { return m_stderr; } + + static QStringList allToolNames(); + static bool tryToRunTool(const QString &toolName, const QStringList &arguments, + int *exitCode = 0); + +private: + bool m_failedToStart = false; + int m_exitCode = 0; + QString m_stdout; + QString m_stderr; +}; + +#endif // QBS_QBSTOOL_H diff --git a/src/app/qbs/session.cpp b/src/app/qbs/session.cpp new file mode 100644 index 00000000..75f0e3bc --- /dev/null +++ b/src/app/qbs/session.cpp @@ -0,0 +1,769 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "session.h" + +#include "sessionpacket.h" +#include "sessionpacketreader.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef Q_OS_WIN32 +#include +#include +#include +#include +#include +#endif + +namespace qbs { +namespace Internal { + +class SessionLogSink : public QObject, public ILogSink +{ + Q_OBJECT +signals: + void newMessage(const QJsonObject &msg); + +private: + void doPrintMessage(LoggerLevel, const QString &message, const QString &) override + { + QJsonObject msg; + msg.insert(StringConstants::type(), QLatin1String("log-data")); + msg.insert(StringConstants::messageKey(), message); + emit newMessage(msg); + } + + void doPrintWarning(const ErrorInfo &warning) override + { + QJsonObject msg; + static const QString warningString(QLatin1String("warning")); + msg.insert(StringConstants::type(), warningString); + msg.insert(warningString, warning.toJson()); + emit newMessage(msg); + } +}; + +class Session : public QObject +{ + Q_OBJECT +public: + Session(); + +private: + enum class ProjectDataMode { Never, Always, OnlyIfChanged }; + ProjectDataMode dataModeFromRequest(const QJsonObject &request); + QStringList modulePropertiesFromRequest(const QJsonObject &request); + void insertProjectDataIfNecessary( + QJsonObject &reply, + ProjectDataMode dataMode, + const ProjectData &oldProjectData, + bool includeTopLevelData + ); + void setLogLevelFromRequest(const QJsonObject &request); + bool checkNormalRequestPrerequisites(const char *replyType); + + void sendPacket(const QJsonObject &message); + void setupProject(const QJsonObject &request); + void buildProject(const QJsonObject &request); + void cleanProject(const QJsonObject &request); + void installProject(const QJsonObject &request); + void addFiles(const QJsonObject &request); + void removeFiles(const QJsonObject &request); + void getRunEnvironment(const QJsonObject &request); + void getGeneratedFilesForSources(const QJsonObject &request); + void releaseProject(); + void cancelCurrentJob(); + void quitSession(); + + void sendErrorReply(const char *replyType, const QString &message); + void sendErrorReply(const char *replyType, const ErrorInfo &error); + void insertErrorInfoIfNecessary(QJsonObject &reply, const ErrorInfo &error); + void connectProgressSignals(AbstractJob *job); + QList getProductsByName(const QStringList &productNames) const; + ProductData getProductByName(const QString &productName) const; + + struct ProductSelection { + ProductSelection(Project::ProductSelection s) : selection(s) {} + ProductSelection(QList p) : products(std::move(p)) {} + + Project::ProductSelection selection = Project::ProductSelectionDefaultOnly; + QList products; + }; + ProductSelection getProductSelection(const QJsonObject &request); + + struct FileUpdateData { + QJsonObject createErrorReply(const char *type, const QString &mainMessage) const; + + ProductData product; + GroupData group; + QStringList filePaths; + ErrorInfo error; + }; + FileUpdateData prepareFileUpdate(const QJsonObject &request); + + SessionPacketReader m_packetReader; + Project m_project; + ProjectData m_projectData; + SessionLogSink m_logSink; + std::unique_ptr m_settings; + QJsonObject m_resolveRequest; + QStringList m_moduleProperties; + AbstractJob *m_currentJob = nullptr; +}; + +void startSession() +{ + const auto session = new Session; + QObject::connect(qApp, &QCoreApplication::aboutToQuit, session, [session] { delete session; }); +} + +Session::Session() +{ +#ifdef Q_OS_WIN32 + // Make sure the line feed character appears as itself. + if (_setmode(_fileno(stdout), _O_BINARY) == -1) { + constexpr size_t errmsglen = FILENAME_MAX; + char errmsg[errmsglen]; + strerror_s(errmsg, errmsglen, errno); + std::cerr << "Failed to set stdout to binary mode: " << errmsg << std::endl; + qApp->exit(EXIT_FAILURE); + } +#endif + sendPacket(SessionPacket::helloMessage()); + connect(&m_logSink, &SessionLogSink::newMessage, this, &Session::sendPacket); + connect(&m_packetReader, &SessionPacketReader::errorOccurred, + this, [](const QString &msg) { + std::cerr << qPrintable(tr("Error: %1").arg(msg)); + qApp->exit(EXIT_FAILURE); + }); + connect(&m_packetReader, &SessionPacketReader::packetReceived, this, [this](const QJsonObject &packet) { + // qDebug() << "got packet:" << packet; // Uncomment for debugging. + const QString type = packet.value(StringConstants::type()).toString(); + if (type == QLatin1String("resolve-project")) + setupProject(packet); + else if (type == QLatin1String("build-project")) + buildProject(packet); + else if (type == QLatin1String("clean-project")) + cleanProject(packet); + else if (type == QLatin1String("install-project")) + installProject(packet); + else if (type == QLatin1String("add-files")) + addFiles(packet); + else if (type == QLatin1String("remove-files")) + removeFiles(packet); + else if (type == QLatin1String("get-run-environment")) + getRunEnvironment(packet); + else if (type == QLatin1String("get-generated-files-for-sources")) + getGeneratedFilesForSources(packet); + else if (type == QLatin1String("release-project")) + releaseProject(); + else if (type == QLatin1String("quit")) + quitSession(); + else if (type == QLatin1String("cancel-job")) + cancelCurrentJob(); + else + sendErrorReply("protocol-error", tr("Unknown request type '%1'.").arg(type)); + }); + m_packetReader.start(); +} + +Session::ProjectDataMode Session::dataModeFromRequest(const QJsonObject &request) +{ + const QString modeString = request.value(QLatin1String("data-mode")).toString(); + if (modeString == QLatin1String("only-if-changed")) + return ProjectDataMode::OnlyIfChanged; + if (modeString == QLatin1String("always")) + return ProjectDataMode::Always; + return ProjectDataMode::Never; +} + +void Session::sendPacket(const QJsonObject &message) +{ + std::cout << SessionPacket::createPacket(message).constData() << std::flush; +} + +void Session::setupProject(const QJsonObject &request) +{ + if (m_currentJob) { + if (qobject_cast(m_currentJob) + && m_currentJob->state() == AbstractJob::StateCanceling) { + m_resolveRequest = request; + return; + } + sendErrorReply("project-resolved", + tr("Cannot start resolving while another job is still running.")); + return; + } + m_moduleProperties = modulePropertiesFromRequest(request); + auto params = SetupProjectParameters::fromJson(request); + const ProjectDataMode dataMode = dataModeFromRequest(request); + m_settings = std::make_unique(params.settingsDirectory()); + const Preferences prefs(m_settings.get()); + const QString appDir = QDir::cleanPath(QCoreApplication::applicationDirPath()); + params.setSearchPaths(prefs.searchPaths(appDir + QLatin1String( + "/" QBS_RELATIVE_SEARCH_PATH))); + params.setPluginPaths(prefs.pluginPaths(appDir + QLatin1String( + "/" QBS_RELATIVE_PLUGINS_PATH))); + params.setLibexecPath(appDir + QLatin1String("/" QBS_RELATIVE_LIBEXEC_PATH)); + params.setOverrideBuildGraphData(true); + setLogLevelFromRequest(request); + SetupProjectJob * const setupJob = m_project.setupProject(params, &m_logSink, this); + m_currentJob = setupJob; + connectProgressSignals(setupJob); + connect(setupJob, &AbstractJob::finished, this, + [this, setupJob, dataMode](bool success) { + if (!m_resolveRequest.isEmpty()) { // Canceled job was superseded. + const QJsonObject newRequest = std::move(m_resolveRequest); + m_resolveRequest = QJsonObject(); + m_currentJob->deleteLater(); + m_currentJob = nullptr; + setupProject(newRequest); + return; + } + const ProjectData oldProjectData = m_projectData; + m_project = setupJob->project(); + m_projectData = m_project.projectData(); + QJsonObject reply; + reply.insert(StringConstants::type(), QLatin1String("project-resolved")); + if (success) + insertProjectDataIfNecessary(reply, dataMode, oldProjectData, true); + else + insertErrorInfoIfNecessary(reply, setupJob->error()); + sendPacket(reply); + m_currentJob->deleteLater(); + m_currentJob = nullptr; + }); +} + +void Session::buildProject(const QJsonObject &request) +{ + if (!checkNormalRequestPrerequisites("build-done")) + return; + const ProductSelection productSelection = getProductSelection(request); + setLogLevelFromRequest(request); + auto options = BuildOptions::fromJson(request); + options.setSettingsDirectory(m_settings->baseDirectory()); + BuildJob * const buildJob = productSelection.products.empty() + ? m_project.buildAllProducts(options, productSelection.selection, this) + : m_project.buildSomeProducts(productSelection.products, options, this); + m_currentJob = buildJob; + m_moduleProperties = modulePropertiesFromRequest(request); + const ProjectDataMode dataMode = dataModeFromRequest(request); + connectProgressSignals(buildJob); + connect(buildJob, &BuildJob::reportCommandDescription, this, + [this](const QString &highlight, const QString &message) { + QJsonObject descData; + descData.insert(StringConstants::type(), QLatin1String("command-description")); + descData.insert(QLatin1String("highlight"), highlight); + descData.insert(StringConstants::messageKey(), message); + sendPacket(descData); + }); + connect(buildJob, &BuildJob::reportProcessResult, this, [this](const ProcessResult &result) { + if (result.success() && result.stdOut().isEmpty() && result.stdErr().isEmpty()) + return; + QJsonObject resultData = result.toJson(); + resultData.insert(StringConstants::type(), QLatin1String("process-result")); + sendPacket(resultData); + }); + connect(buildJob, &BuildJob::finished, this, + [this, dataMode](bool success) { + QJsonObject reply; + reply.insert(StringConstants::type(), QLatin1String("project-built")); + const ProjectData oldProjectData = m_projectData; + m_projectData = m_project.projectData(); + if (success) + insertProjectDataIfNecessary(reply, dataMode, oldProjectData, false); + else + insertErrorInfoIfNecessary(reply, m_currentJob->error()); + sendPacket(reply); + m_currentJob->deleteLater(); + m_currentJob = nullptr; + }); +} + +void Session::cleanProject(const QJsonObject &request) +{ + if (!checkNormalRequestPrerequisites("project-cleaned")) + return; + setLogLevelFromRequest(request); + const ProductSelection productSelection = getProductSelection(request); + const auto options = CleanOptions::fromJson(request); + m_currentJob = productSelection.products.empty() + ? m_project.cleanAllProducts(options, this) + : m_project.cleanSomeProducts(productSelection.products, options, this); + connectProgressSignals(m_currentJob); + connect(m_currentJob, &AbstractJob::finished, this, [this](bool success) { + QJsonObject reply; + reply.insert(StringConstants::type(), QLatin1String("project-cleaned")); + if (!success) + insertErrorInfoIfNecessary(reply, m_currentJob->error()); + sendPacket(reply); + m_currentJob->deleteLater(); + m_currentJob = nullptr; + }); +} + +void Session::installProject(const QJsonObject &request) +{ + if (!checkNormalRequestPrerequisites("install-done")) + return; + setLogLevelFromRequest(request); + const ProductSelection productSelection = getProductSelection(request); + const auto options = InstallOptions::fromJson(request); + m_currentJob = productSelection.products.empty() + ? m_project.installAllProducts(options, productSelection.selection, this) + : m_project.installSomeProducts(productSelection.products, options, this); + connectProgressSignals(m_currentJob); + connect(m_currentJob, &AbstractJob::finished, this, [this](bool success) { + QJsonObject reply; + reply.insert(StringConstants::type(), QLatin1String("install-done")); + if (!success) + insertErrorInfoIfNecessary(reply, m_currentJob->error()); + sendPacket(reply); + m_currentJob->deleteLater(); + m_currentJob = nullptr; + }); +} + +void Session::addFiles(const QJsonObject &request) +{ + const FileUpdateData data = prepareFileUpdate(request); + if (data.error.hasError()) { + sendPacket(data.createErrorReply("files-added", tr("Failed to add files to project: %1") + .arg(data.error.toString()))); + return; + } + ErrorInfo error; + QStringList failedFiles; +#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES + for (const QString &filePath : data.filePaths) { + const ErrorInfo e = m_project.addFiles(data.product, data.group, {filePath}); + if (e.hasError()) { + for (const ErrorItem &ei : e.items()) + error.append(ei); + failedFiles.push_back(filePath); + } + } +#endif + QJsonObject reply; + reply.insert(StringConstants::type(), QLatin1String("files-added")); + insertErrorInfoIfNecessary(reply, error); + + if (failedFiles.size() != data.filePaths.size()) { + // Note that Project::addFiles() directly changes the existing project data object, so + // there's no need to retrieve it from m_project. + insertProjectDataIfNecessary(reply, ProjectDataMode::Always, {}, false); + } + + if (!failedFiles.isEmpty()) + reply.insert(QLatin1String("failed-files"), QJsonArray::fromStringList(failedFiles)); + sendPacket(reply); +} + +void Session::removeFiles(const QJsonObject &request) +{ + const FileUpdateData data = prepareFileUpdate(request); + if (data.error.hasError()) { + sendPacket(data.createErrorReply("files-removed", + tr("Failed to remove files from project: %1") + .arg(data.error.toString()))); + return; + } + ErrorInfo error; + QStringList failedFiles; +#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES + for (const QString &filePath : data.filePaths) { + const ErrorInfo e = m_project.removeFiles(data.product, data.group, {filePath}); + if (e.hasError()) { + for (const ErrorItem &ei : e.items()) + error.append(ei); + failedFiles.push_back(filePath); + } + } +#endif + QJsonObject reply; + reply.insert(StringConstants::type(), QLatin1String("files-removed")); + insertErrorInfoIfNecessary(reply, error); + if (failedFiles.size() != data.filePaths.size()) + insertProjectDataIfNecessary(reply, ProjectDataMode::Always, {}, false); + if (!failedFiles.isEmpty()) + reply.insert(QLatin1String("failed-files"), QJsonArray::fromStringList(failedFiles)); + sendPacket(reply); +} + +void Session::connectProgressSignals(AbstractJob *job) +{ + static QString maxProgressString(QLatin1String("max-progress")); + connect(job, &AbstractJob::taskStarted, this, + [this](const QString &description, int maxProgress) { + QJsonObject msg; + msg.insert(StringConstants::type(), QLatin1String("task-started")); + msg.insert(StringConstants::descriptionProperty(), description); + msg.insert(maxProgressString, maxProgress); + sendPacket(msg); + }); + connect(job, &AbstractJob::totalEffortChanged, this, [this](int maxProgress) { + QJsonObject msg; + msg.insert(StringConstants::type(), QLatin1String("new-max-progress")); + msg.insert(maxProgressString, maxProgress); + sendPacket(msg); + }); + connect(job, &AbstractJob::taskProgress, this, [this](int progress) { + QJsonObject msg; + msg.insert(StringConstants::type(), QLatin1String("task-progress")); + msg.insert(QLatin1String("progress"), progress); + sendPacket(msg); + }); +} + +static QList getProductsByNameForProject(const ProjectData &project, + QStringList &productNames) +{ + QList products; + if (productNames.empty()) + return products; + for (const ProductData &p : project.products()) { + for (auto it = productNames.begin(); it != productNames.end(); ++it) { + if (*it == p.fullDisplayName()) { + products << p; + productNames.erase(it); + if (productNames.empty()) + return products; + break; + } + } + } + for (const ProjectData &p : project.subProjects()) { + products << getProductsByNameForProject(p, productNames); + if (productNames.empty()) + break; + } + return products; +} + +QList Session::getProductsByName(const QStringList &productNames) const +{ + QStringList remainingNames = productNames; + return getProductsByNameForProject(m_projectData, remainingNames); +} + +ProductData Session::getProductByName(const QString &productName) const +{ + const QList products = getProductsByName({productName}); + return products.empty() ? ProductData() : products.first(); +} + +void Session::getRunEnvironment(const QJsonObject &request) +{ + const char * const replyType = "run-environment"; + if (!checkNormalRequestPrerequisites(replyType)) + return; + const QString productName = request.value(QLatin1String("product")).toString(); + const ProductData product = getProductByName(productName); + if (!product.isValid()) { + sendErrorReply(replyType, tr("No such product '%1'.").arg(productName)); + return; + } + const auto inEnv = fromJson( + request.value(QLatin1String("base-environment"))); + const QStringList config = fromJson(request.value(QLatin1String("config"))); + const RunEnvironment runEnv = m_project.getRunEnvironment(product, InstallOptions(), inEnv, + config, m_settings.get()); + ErrorInfo error; + const QProcessEnvironment outEnv = runEnv.runEnvironment(&error); + if (error.hasError()) { + sendErrorReply(replyType, error); + return; + } + QJsonObject reply; + reply.insert(StringConstants::type(), QLatin1String(replyType)); + QJsonObject outEnvObj; + const QStringList keys = outEnv.keys(); + for (const QString &key : keys) + outEnvObj.insert(key, outEnv.value(key)); + reply.insert(QLatin1String("full-environment"), outEnvObj); + sendPacket(reply); +} + +void Session::getGeneratedFilesForSources(const QJsonObject &request) +{ + const char * const replyType = "generated-files-for-sources"; + if (!checkNormalRequestPrerequisites(replyType)) + return; + QJsonObject reply; + reply.insert(StringConstants::type(), QLatin1String(replyType)); + const QJsonArray specs = request.value(StringConstants::productsKey()).toArray(); + QJsonArray resultProducts; + for (const QJsonValue &p : specs) { + const QJsonObject productObject = p.toObject(); + const ProductData product = getProductByName( + productObject.value(StringConstants::fullDisplayNameKey()).toString()); + if (!product.isValid()) + continue; + QJsonObject resultProduct; + resultProduct.insert(StringConstants::fullDisplayNameKey(), product.fullDisplayName()); + QJsonArray results; + const QJsonArray requests = productObject.value(QLatin1String("requests")).toArray(); + for (const QJsonValue &r : requests) { + const QJsonObject request = r.toObject(); + const QString filePath = request.value(QLatin1String("source-file")).toString(); + const QStringList tags = fromJson(request.value(QLatin1String("tags"))); + const bool recursive = request.value(QLatin1String("recursive")).toBool(); + const QStringList generatedFiles = m_project.generatedFiles(product, filePath, + recursive, tags); + if (!generatedFiles.isEmpty()) { + QJsonObject result; + result.insert(QLatin1String("source-file"), filePath); + result.insert(QLatin1String("generated-files"), + QJsonArray::fromStringList(generatedFiles)); + results << result; + } + } + if (!results.isEmpty()) { + resultProduct.insert(QLatin1String("results"), results); + resultProducts << resultProduct; + } + } + reply.insert(StringConstants::productsKey(), resultProducts); + sendPacket(reply); +} + +void Session::releaseProject() +{ + const char * const replyType = "project-released"; + if (!m_project.isValid()) { + sendErrorReply(replyType, tr("No open project.")); + return; + } + if (m_currentJob) { + m_currentJob->disconnect(this); + m_currentJob->cancel(); + m_currentJob = nullptr; + } + m_project = Project(); + m_projectData = ProjectData(); + m_resolveRequest = QJsonObject(); + QJsonObject reply; + reply.insert(StringConstants::type(), QLatin1String(replyType)); + sendPacket(reply); +} + +void Session::cancelCurrentJob() +{ + if (m_currentJob) { + if (!m_resolveRequest.isEmpty()) + m_resolveRequest = QJsonObject(); + m_currentJob->cancel(); + } +} + +Session::ProductSelection Session::getProductSelection(const QJsonObject &request) +{ + const QJsonValue productSelection = request.value(StringConstants::productsKey()); + if (productSelection.isArray()) + return ProductSelection(getProductsByName(fromJson(productSelection))); + return ProductSelection(productSelection.toString() == QLatin1String("all") + ? Project::ProductSelectionWithNonDefault + : Project::ProductSelectionDefaultOnly); +} + +Session::FileUpdateData Session::prepareFileUpdate(const QJsonObject &request) +{ + FileUpdateData data; + const QString productName = request.value(QLatin1String("product")).toString(); + data.product = getProductByName(productName); + if (data.product.isValid()) { + const QString groupName = request.value(QLatin1String("group")).toString(); + for (const GroupData &g : data.product.groups()) { + if (g.name() == groupName) { + data.group = g; + break; + } + } + if (!data.group.isValid()) + data.error = tr("Group '%1' not found in product '%2'.").arg(groupName, productName); + } else { + data.error = tr("Product '%1' not found in project.").arg(productName); + } + const QJsonArray filesArray = request.value(QLatin1String("files")).toArray(); + for (const QJsonValue &v : filesArray) + data.filePaths << v.toString(); + if (m_currentJob) + data.error = tr("Cannot update the list of source files while a job is running."); + if (!m_project.isValid()) + data.error = tr("No valid project. You need to resolve first."); +#ifndef QBS_ENABLE_PROJECT_FILE_UPDATES + data.error = ErrorInfo(tr("Project file updates are not enabled in this build of qbs.")); +#endif + return data; +} + +void Session::insertProjectDataIfNecessary(QJsonObject &reply, ProjectDataMode dataMode, + const ProjectData &oldProjectData, bool includeTopLevelData) +{ + const bool sendProjectData = dataMode == ProjectDataMode::Always + || (dataMode == ProjectDataMode::OnlyIfChanged && m_projectData != oldProjectData); + if (!sendProjectData) + return; + QJsonObject projectData = m_projectData.toJson(m_moduleProperties); + if (includeTopLevelData) { + QJsonArray buildSystemFiles; + for (const QString &f : m_project.buildSystemFiles()) + buildSystemFiles.push_back(f); + projectData.insert(StringConstants::buildDirectoryKey(), m_projectData.buildDirectory()); + projectData.insert(QLatin1String("build-system-files"), buildSystemFiles); + const Project::BuildGraphInfo bgInfo = m_project.getBuildGraphInfo(); + projectData.insert(QLatin1String("build-graph-file-path"), bgInfo.bgFilePath); + projectData.insert(QLatin1String("profile-data"), + QJsonObject::fromVariantMap(bgInfo.profileData)); + projectData.insert(QLatin1String("overridden-properties"), + QJsonObject::fromVariantMap(bgInfo.overriddenProperties)); + } + reply.insert(QLatin1String("project-data"), projectData); +} + +void Session::setLogLevelFromRequest(const QJsonObject &request) +{ + const QString logLevelString = request.value(QLatin1String("log-level")).toString(); + if (logLevelString.isEmpty()) + return; + for (const LoggerLevel l : {LoggerError, LoggerWarning, LoggerInfo, LoggerDebug, LoggerTrace}) { + if (logLevelString == logLevelName(l)) { + m_logSink.setLogLevel(l); + return; + } + } +} + +bool Session::checkNormalRequestPrerequisites(const char *replyType) +{ + if (m_currentJob) { + sendErrorReply(replyType, tr("Another job is still running.")); + return false; + } + if (!m_project.isValid()) { + sendErrorReply(replyType, tr("No valid project. You need to resolve first.")); + return false; + } + return true; +} + +QStringList Session::modulePropertiesFromRequest(const QJsonObject &request) +{ + return fromJson(request.value(StringConstants::modulePropertiesKey())); +} + +void Session::sendErrorReply(const char *replyType, const ErrorInfo &error) +{ + QJsonObject reply; + reply.insert(StringConstants::type(), QLatin1String(replyType)); + insertErrorInfoIfNecessary(reply, error); + sendPacket(reply); +} + +void Session::sendErrorReply(const char *replyType, const QString &message) +{ + sendErrorReply(replyType, ErrorInfo(message)); +} + +void Session::insertErrorInfoIfNecessary(QJsonObject &reply, const ErrorInfo &error) +{ + if (error.hasError()) + reply.insert(QLatin1String("error"), error.toJson()); +} + +void Session::quitSession() +{ + m_logSink.disconnect(this); + m_packetReader.disconnect(this); + if (m_currentJob) { + m_currentJob->disconnect(this); + connect(m_currentJob, &AbstractJob::finished, qApp, QCoreApplication::quit); + m_currentJob->cancel(); + } else { + qApp->quit(); + } +} + +QJsonObject Session::FileUpdateData::createErrorReply(const char *type, + const QString &mainMessage) const +{ + QBS_ASSERT(error.hasError(), return QJsonObject()); + ErrorInfo error(mainMessage); + for (const ErrorItem &ei : error.items()) + error.append(ei); + QJsonObject reply; + reply.insert(StringConstants::type(), QLatin1String(type)); + reply.insert(QLatin1String("error"), error.toJson()); + reply.insert(QLatin1String("failed-files"), QJsonArray::fromStringList(filePaths)); + return reply; +} + +} // namespace Internal +} // namespace qbs + +#include diff --git a/src/app/qbs/session.h b/src/app/qbs/session.h new file mode 100644 index 00000000..ebbc93b1 --- /dev/null +++ b/src/app/qbs/session.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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_SESSION_H +#define QBS_SESSION_H + +namespace qbs { +namespace Internal { + +void startSession(); + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/app/qbs/sessionpacket.cpp b/src/app/qbs/sessionpacket.cpp new file mode 100644 index 00000000..dd6d1726 --- /dev/null +++ b/src/app/qbs/sessionpacket.cpp @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "sessionpacket.h" + +#include +#include +#include + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +const QByteArray packetStart = "qbsmsg:"; + +SessionPacket::Status SessionPacket::parseInput(QByteArray &input) +{ + //qDebug() << m_expectedPayloadLength << m_payload << input; + if (m_expectedPayloadLength == -1) { + const int packetStartOffset = input.indexOf(packetStart); + if (packetStartOffset == -1) + return Status::Incomplete; + const int numberOffset = packetStartOffset + packetStart.length(); + const int newLineOffset = input.indexOf('\n', numberOffset); + if (newLineOffset == -1) + return Status::Incomplete; + const QByteArray sizeString = input.mid(numberOffset, newLineOffset - numberOffset); + bool isNumber; + const int payloadLen = sizeString.toInt(&isNumber); + if (!isNumber || payloadLen < 0) + return Status::Invalid; + m_expectedPayloadLength = payloadLen; + input.remove(0, newLineOffset + 1); + } + const int bytesToAdd = m_expectedPayloadLength - m_payload.length(); + QBS_ASSERT(bytesToAdd >= 0, return Status::Invalid); + m_payload += input.left(bytesToAdd); + input.remove(0, bytesToAdd); + return isComplete() ? Status::Complete : Status::Incomplete; +} + +QJsonObject SessionPacket::retrievePacket() +{ + QBS_ASSERT(isComplete(), return QJsonObject()); + const auto packet = QJsonDocument::fromJson(QByteArray::fromBase64(m_payload)).object(); + m_payload.clear(); + m_expectedPayloadLength = -1; + return packet; +} + +QByteArray SessionPacket::createPacket(const QJsonObject &packet) +{ + const QByteArray jsonData = QJsonDocument(packet).toJson(QJsonDocument::Compact).toBase64(); + return QByteArray(packetStart).append(QByteArray::number(jsonData.length())).append('\n') + .append(jsonData); +} + +QJsonObject SessionPacket::helloMessage() +{ + return QJsonObject{ + {StringConstants::type(), QLatin1String("hello")}, + {QLatin1String("api-level"), 2}, + {QLatin1String("api-compat-level"), 2} + }; +} + +bool SessionPacket::isComplete() const +{ + return m_payload.length() == m_expectedPayloadLength; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/app/qbs/sessionpacket.h b/src/app/qbs/sessionpacket.h new file mode 100644 index 00000000..d919ff34 --- /dev/null +++ b/src/app/qbs/sessionpacket.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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_SESSIONPACKET_H +#define QBS_SESSIONPACKET_H + +#include +#include + +namespace qbs { +namespace Internal { + +class SessionPacket +{ +public: + enum class Status { Incomplete, Complete, Invalid }; + Status parseInput(QByteArray &input); + + QJsonObject retrievePacket(); + + static QByteArray createPacket(const QJsonObject &packet); + static QJsonObject helloMessage(); + +private: + bool isComplete() const; + + QByteArray m_payload; + int m_expectedPayloadLength = -1; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/app/qbs/sessionpacketreader.cpp b/src/app/qbs/sessionpacketreader.cpp new file mode 100644 index 00000000..e99ea01e --- /dev/null +++ b/src/app/qbs/sessionpacketreader.cpp @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "sessionpacketreader.h" + +#include "sessionpacket.h" +#include "stdinreader.h" + +namespace qbs { +namespace Internal { + +class SessionPacketReader::Private +{ +public: + QByteArray incomingData; + SessionPacket currentPacket; +}; + +SessionPacketReader::SessionPacketReader(QObject *parent) + : QObject(parent) + , d(std::make_unique()) +{ } + +SessionPacketReader::~SessionPacketReader() = default; + +void SessionPacketReader::start() +{ + StdinReader * const stdinReader = StdinReader::create(this); + connect(stdinReader, &StdinReader::errorOccurred, this, &SessionPacketReader::errorOccurred); + connect(stdinReader, &StdinReader::dataAvailable, this, [this](const QByteArray &data) { + d->incomingData += data; + while (!d->incomingData.isEmpty()) { + switch (d->currentPacket.parseInput(d->incomingData)) { + case SessionPacket::Status::Invalid: + emit errorOccurred(tr("Received invalid input.")); + return; + case SessionPacket::Status::Complete: + emit packetReceived(d->currentPacket.retrievePacket()); + break; + case SessionPacket::Status::Incomplete: + return; + } + } + }); + stdinReader->start(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/app/qbs/sessionpacketreader.h b/src/app/qbs/sessionpacketreader.h new file mode 100644 index 00000000..f186fbc8 --- /dev/null +++ b/src/app/qbs/sessionpacketreader.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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_SESSIONPACKETREADER_H +#define QBS_SESSIONPACKETREADER_H + +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class SessionPacketReader : public QObject +{ + Q_OBJECT +public: + explicit SessionPacketReader(QObject *parent = nullptr); + ~SessionPacketReader() override; + + void start(); + +signals: + void packetReceived(const QJsonObject &packet); + void errorOccurred(const QString &msg); + +private: + class Private; + const std::unique_ptr d; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/app/qbs/status.cpp b/src/app/qbs/status.cpp new file mode 100644 index 00000000..01d3451f --- /dev/null +++ b/src/app/qbs/status.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** 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 "status.h" + +#include "../shared/logging/consolelogger.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace qbs { + +static QList createIgnoreList(const QString &projectRootPath) +{ + QList ignoreRegularExpressionList { + QRegExp(projectRootPath + QLatin1String("/build.*")), + QRegExp(QStringLiteral("*.qbs"), Qt::CaseSensitive, QRegExp::Wildcard), + QRegExp(QStringLiteral("*.pro"), Qt::CaseSensitive, QRegExp::Wildcard), + QRegExp(QStringLiteral("*Makefile"), Qt::CaseSensitive, QRegExp::Wildcard), + QRegExp(QStringLiteral("*.so*"), Qt::CaseSensitive, QRegExp::Wildcard), + QRegExp(QStringLiteral("*.o"), Qt::CaseSensitive, QRegExp::Wildcard) + }; + QString ignoreFilePath = projectRootPath + QLatin1String("/.qbsignore"); + + QFile ignoreFile(ignoreFilePath); + + if (ignoreFile.open(QFile::ReadOnly)) { + const QList ignoreTokenList = ignoreFile.readAll().split('\n'); + for (const QByteArray &btoken : ignoreTokenList) { + const QString token = QString::fromLatin1(btoken); + if (token.startsWith(QLatin1String("/"))) + ignoreRegularExpressionList.push_back(QRegExp(projectRootPath + + token + QLatin1String(".*"), + Qt::CaseSensitive, QRegExp::RegExp2)); + else if (!token.isEmpty()) + ignoreRegularExpressionList.push_back(QRegExp(token, Qt::CaseSensitive, QRegExp::RegExp2)); + + } + } + + return ignoreRegularExpressionList; +} + +static QStringList allFilesInDirectoryRecursive(const QDir &rootDirecory, const QList &ignoreRegularExpressionList) +{ + QStringList fileList; + + const auto fileInfos = rootDirecory.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDir::Name); + for (const QFileInfo &fileInfo : fileInfos) { + QString absoluteFilePath = fileInfo.absoluteFilePath(); + bool inIgnoreList = false; + for (const QRegExp &ignoreRegularExpression : ignoreRegularExpressionList) { + if (ignoreRegularExpression.exactMatch(absoluteFilePath)) { + inIgnoreList = true; + break; + } + } + + if (!inIgnoreList) { + if (fileInfo.isFile()) + fileList.push_back(absoluteFilePath); + else if (fileInfo.isDir()) + fileList << allFilesInDirectoryRecursive(QDir(absoluteFilePath), ignoreRegularExpressionList); + + } + } + + return fileList; +} + +static QStringList allFilesInProject(const QString &projectRootPath) +{ + QList ignoreRegularExpressionList = createIgnoreList(projectRootPath); + + return allFilesInDirectoryRecursive(QDir(projectRootPath), ignoreRegularExpressionList); +} + +QStringList allFiles(const ProductData &product) +{ + QStringList files; + for (const GroupData &group : product.groups()) + files += group.allFilePaths(); + return files; +} + +int printStatus(const ProjectData &project) +{ + const QString projectFilePath = project.location().filePath(); + QString projectDirectory = QFileInfo(projectFilePath).dir().path(); + int projectDirectoryPathLength = projectDirectory.length(); + + QStringList untrackedFilesInProject = allFilesInProject(projectDirectory); + QStringList missingFiles; + for (const ProductData &product : project.allProducts()) { + qbsInfo() << "\nProduct: " << product.name() + << " (" << product.location().filePath() << ":" + << product.location().line() << ")"; + for (const GroupData &group : product.groups()) { + qbsInfo() << " Group: " << group.name() + << " (" << group.location().filePath() << ":" + << group.location().line() << ")"; + QStringList sourceFiles = group.allFilePaths(); + std::sort(sourceFiles.begin(), sourceFiles.end()); + for (const QString &sourceFile : qAsConst(sourceFiles)) { + if (!QFileInfo(sourceFile).exists()) + missingFiles.push_back(sourceFile); + qbsInfo() << " " << sourceFile.mid(projectDirectoryPathLength + 1); + untrackedFilesInProject.removeOne(sourceFile); + } + } + } + + qbsInfo() << "\nMissing files:"; + for (const QString &untrackedFile : qAsConst(missingFiles)) + qbsInfo() << " " << untrackedFile.mid(projectDirectoryPathLength + 1); + + qbsInfo() << "\nUntracked files:"; + for (const QString &missingFile : qAsConst(untrackedFilesInProject)) + qbsInfo() << " " << missingFile.mid(projectDirectoryPathLength + 1); + + return 0; +} + +} // namespace qbs diff --git a/src/app/qbs/status.h b/src/app/qbs/status.h new file mode 100644 index 00000000..301363d7 --- /dev/null +++ b/src/app/qbs/status.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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 STATUS_H +#define STATUS_H + +namespace qbs { +class ProjectData; + +int printStatus(const ProjectData &project); + +} // namespace qbs + +#endif // STATUS_H diff --git a/src/app/qbs/stdinreader.cpp b/src/app/qbs/stdinreader.cpp new file mode 100644 index 00000000..5f00d7de --- /dev/null +++ b/src/app/qbs/stdinreader.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "stdinreader.h" + +#include + +#include +#include +#include + +#include +#include + +#ifdef Q_OS_WIN32 +#include +#else +#include +#endif + +namespace qbs { +namespace Internal { + +class UnixStdinReader : public StdinReader +{ +public: + UnixStdinReader(QObject *parent) : StdinReader(parent), m_notifier(0, QSocketNotifier::Read) {} + +private: + void start() override + { + if (!m_stdIn.open(stdin, QIODevice::ReadOnly)) { + emit errorOccurred(tr("Cannot read from standard input.")); + return; + } +#ifdef Q_OS_UNIX + const auto emitError = [this] { + emit errorOccurred(tr("Failed to make standard input non-blocking: %1") + .arg(QLatin1String(std::strerror(errno)))); + }; + const int flags = fcntl(0, F_GETFL, 0); + if (flags == -1) { + emitError(); + return; + } + if (fcntl(0, F_SETFL, flags | O_NONBLOCK)) { + emitError(); + return; + } +#endif + connect(&m_notifier, &QSocketNotifier::activated, this, [this] { + emit dataAvailable(m_stdIn.readAll()); + }); + + // Neither the aboutToClose() nor the readChannelFinished() signals + // are triggering, so we need a timer to check whether the controlling + // process disappeared. + const auto stdinClosedChecker = new QTimer(this); + connect(stdinClosedChecker, &QTimer::timeout, this, [this, stdinClosedChecker] { + if (m_stdIn.atEnd()) { + stdinClosedChecker->stop(); + emit errorOccurred(tr("Input channel closed unexpectedly.")); + } + }); + stdinClosedChecker->start(1000); + } + + QFile m_stdIn; + QSocketNotifier m_notifier; +}; + +class WindowsStdinReader : public StdinReader +{ +public: + WindowsStdinReader(QObject *parent) : StdinReader(parent) {} + +private: + void start() override + { +#ifdef Q_OS_WIN32 + m_stdinHandle = GetStdHandle(STD_INPUT_HANDLE); + if (!m_stdinHandle) { + emit errorOccurred(tr("Failed to create handle for standard input.")); + return; + } + + // A timer seems slightly less awful than to block in a thread + // (how would we abort that one?), but ideally we'd like + // to have a signal-based approach like in the Unix variant. + const auto timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, [this, timer] { + char buf[1024]; + DWORD bytesAvail; + if (!PeekNamedPipe(m_stdinHandle, nullptr, 0, nullptr, &bytesAvail, nullptr)) { + timer->stop(); + emit errorOccurred(tr("Failed to read from input channel.")); + return; + } + while (bytesAvail > 0) { + DWORD bytesRead; + if (!ReadFile(m_stdinHandle, buf, std::min(bytesAvail, sizeof buf), + &bytesRead, nullptr)) { + timer->stop(); + emit errorOccurred(tr("Failed to read from input channel.")); + return; + } + emit dataAvailable(QByteArray(buf, bytesRead)); + bytesAvail -= bytesRead; + } + }); + timer->start(10); +#endif + } + +#ifdef Q_OS_WIN32 + HANDLE m_stdinHandle; +#endif +}; + +StdinReader *StdinReader::create(QObject *parent) +{ + if (HostOsInfo::isWindowsHost()) + return new WindowsStdinReader(parent); + return new UnixStdinReader(parent); +} + +StdinReader::StdinReader(QObject *parent) : QObject(parent) { } + +} // namespace Internal +} // namespace qbs diff --git a/src/app/qbs/stdinreader.h b/src/app/qbs/stdinreader.h new file mode 100644 index 00000000..b3737e5a --- /dev/null +++ b/src/app/qbs/stdinreader.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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_STDINREADER_H +#define QBS_STDINREADER_H + +#include + +namespace qbs { +namespace Internal { + +class StdinReader : public QObject +{ + Q_OBJECT +public: + static StdinReader *create(QObject *parent); + virtual void start() = 0; + +signals: + void errorOccurred(const QString &error); + void dataAvailable(const QByteArray &data); + +protected: + explicit StdinReader(QObject *parent); +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/app/shared/logging/coloredoutput.cpp b/src/app/shared/logging/coloredoutput.cpp new file mode 100644 index 00000000..0f1bb26d --- /dev/null +++ b/src/app/shared/logging/coloredoutput.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** 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 "coloredoutput.h" +#include +#ifdef Q_OS_WIN32 +# include +#endif + +#include + +#ifdef Q_OS_UNIX +#include +#endif + +void printfColored(TextColor color, const char *str, va_list vl) +{ + fprintfColored(color, stdout, str, vl); +} + +void printfColored(TextColor color, const char *str, ...) +{ + va_list vl; + va_start(vl, str); + printfColored(color, str, vl); + va_end(vl); +} + +void fprintfColored(TextColor color, FILE *file, const char *str, va_list vl) +{ +#if defined(Q_OS_UNIX) + if (color != TextColorDefault && isatty(fileno(file))) { + unsigned char bright = (color & TextColorBright) >> 3; + fprintf(file, "\033[%d;%dm", bright, 30 + (color & ~TextColorBright)); + vfprintf(file, str, vl); + fprintf(stdout, "\033[0m"); + fprintf(stderr, "\033[0m"); + } else +#elif defined(Q_OS_WIN32) + HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + if (color != TextColorDefault + && hStdout != INVALID_HANDLE_VALUE + && GetConsoleScreenBufferInfo(hStdout, &csbiInfo)) + { + WORD bgrColor = ((color & 1) << 2) | (color & 2) | ((color & 4) >> 2); // BGR instead of RGB. + if (color & TextColorBright) + bgrColor += FOREGROUND_INTENSITY; + SetConsoleTextAttribute(hStdout, (csbiInfo.wAttributes & 0xf0) | bgrColor); + vfprintf(file, str, vl); + SetConsoleTextAttribute(hStdout, csbiInfo.wAttributes); + } else +#endif + { + vfprintf(file, str, vl); + } +} + +void fprintfColored(TextColor color, FILE *file, const char *str, ...) +{ + va_list vl; + va_start(vl, str); + fprintfColored(color, file, str, vl); + va_end(vl); +} + +bool terminalSupportsColor() +{ +#if defined(Q_OS_UNIX) + const QByteArray &term = qgetenv("TERM"); + return !term.isEmpty() && term != "dumb"; +#else + return true; +#endif +} diff --git a/src/app/shared/logging/coloredoutput.h b/src/app/shared/logging/coloredoutput.h new file mode 100644 index 00000000..a7b145ba --- /dev/null +++ b/src/app/shared/logging/coloredoutput.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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 QBS_COLOREDOUTPUT_H +#define QBS_COLOREDOUTPUT_H + +#include +#include + +// http://en.wikipedia.org/wiki/ANSI_escape_code#Colors +enum TextColor { + TextColorDefault = -1, + TextColorBlack = 0, + TextColorDarkRed = 1, + TextColorDarkGreen = 2, + TextColorDarkBlue = 4, + TextColorDarkCyan = TextColorDarkGreen | TextColorDarkBlue, + TextColorDarkMagenta = TextColorDarkRed | TextColorDarkBlue, + TextColorDarkYellow = TextColorDarkRed | TextColorDarkGreen, + TextColorGray = 7, + TextColorBright = 8, + TextColorRed = TextColorDarkRed | TextColorBright, + TextColorGreen = TextColorDarkGreen | TextColorBright, + TextColorBlue = TextColorDarkBlue | TextColorBright, + TextColorCyan = TextColorDarkCyan | TextColorBright, + TextColorMagenta = TextColorDarkMagenta | TextColorBright, + TextColorYellow = TextColorDarkYellow | TextColorBright, + TextColorWhite = TextColorGray | TextColorBright +}; + +void printfColored(TextColor color, const char *str, va_list vl); +void printfColored(TextColor color, const char *str, ...); +void fprintfColored(TextColor color, FILE *file, const char *str, va_list vl); +void fprintfColored(TextColor color, FILE *file, const char *str, ...); +bool terminalSupportsColor(); + +#endif // QBS_COLOREDOUTPUT_H diff --git a/src/app/shared/logging/consolelogger.cpp b/src/app/shared/logging/consolelogger.cpp new file mode 100644 index 00000000..63b7dc9e --- /dev/null +++ b/src/app/shared/logging/consolelogger.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** 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 "consolelogger.h" + +#include +#include + +#include + +static QHash setupColorTable() +{ + QHash colorTable; + colorTable[QStringLiteral("compiler")] = TextColorDefault; + colorTable[QStringLiteral("linker")] = TextColorDarkGreen; + colorTable[QStringLiteral("codegen")] = TextColorDarkYellow; + colorTable[QStringLiteral("filegen")] = TextColorDarkYellow; + return colorTable; +} + +ConsoleLogSink::ConsoleLogSink() : m_coloredOutputEnabled(true), m_enabled(true) +{ +} + +void ConsoleLogSink::doPrintMessage(qbs::LoggerLevel level, const QString &message, + const QString &tag) +{ + if (!m_enabled) + return; + + FILE * const file = level == qbs::LoggerInfo && tag != QStringLiteral("stdErr") + ? stdout : stderr; + + const QString levelTag = logLevelTag(level); + TextColor color = TextColorDefault; + switch (level) { + case qbs::LoggerError: + color = TextColorRed; + break; + case qbs::LoggerWarning: + color = TextColorYellow; + break; + default: + break; + } + + fprintfWrapper(color, file, levelTag.toLocal8Bit().constData()); + static QHash colorTable = setupColorTable(); + fprintfWrapper(colorTable.value(tag, TextColorDefault), file, "%s\n", + message.toLocal8Bit().constData()); + fflush(file); +} + +void ConsoleLogSink::fprintfWrapper(TextColor color, FILE *file, const char *str, ...) +{ + va_list vl; + va_start(vl, str); + if (m_coloredOutputEnabled && terminalSupportsColor()) + fprintfColored(color, file, str, vl); + else + vfprintf(file, str, vl); + va_end(vl); +} + + +ConsoleLogger &ConsoleLogger::instance(qbs::Settings *settings) +{ + static ConsoleLogger logger(settings); + return logger; +} + +void ConsoleLogger::setSettings(qbs::Settings *settings) +{ + if (settings) + m_logSink.setColoredOutputEnabled(qbs::Preferences(settings).useColoredOutput()); +} + +ConsoleLogger::ConsoleLogger(qbs::Settings *settings) : Logger(&m_logSink) +{ + setSettings(settings); +} diff --git a/src/app/shared/logging/consolelogger.h b/src/app/shared/logging/consolelogger.h new file mode 100644 index 00000000..db7a705f --- /dev/null +++ b/src/app/shared/logging/consolelogger.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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 QBS_LOGSINK_H +#define QBS_LOGSINK_H + +#include "coloredoutput.h" + +#include + +namespace qbs { class Settings; } + +class ConsoleLogSink : public qbs::ILogSink +{ +public: + ConsoleLogSink(); + + void setColoredOutputEnabled(bool enabled) { m_coloredOutputEnabled = enabled; } + void setEnabled(bool enabled) { m_enabled = enabled; } + +private: + void doPrintMessage(qbs::LoggerLevel level, const QString &message, const QString &tag) override; + void fprintfWrapper(TextColor color, FILE *file, const char *str, ...); + +private: + bool m_coloredOutputEnabled; + bool m_enabled; +}; + + +class ConsoleLogger : public qbs::Internal::Logger +{ +public: + static ConsoleLogger &instance(qbs::Settings *settings = 0); + ConsoleLogSink *logSink() { return &m_logSink; } + void setSettings(qbs::Settings *settings); + +private: + ConsoleLogger(qbs::Settings *settings); + + ConsoleLogSink m_logSink; +}; + +inline qbs::Internal::LogWriter qbsError() { + return ConsoleLogger::instance().qbsLog(qbs::LoggerError); +} +inline qbs::Internal::LogWriter qbsWarning() { return ConsoleLogger::instance().qbsWarning(); } +inline qbs::Internal::LogWriter qbsInfo() { return ConsoleLogger::instance().qbsInfo(); } +inline qbs::Internal::LogWriter qbsDebug() { return ConsoleLogger::instance().qbsDebug(); } +inline qbs::Internal::LogWriter qbsTrace() { return ConsoleLogger::instance().qbsTrace(); } + +#endif // QBS_LOGSINK_H diff --git a/src/app/shared/logging/logging.pri b/src/app/shared/logging/logging.pri new file mode 100644 index 00000000..e24f33e1 --- /dev/null +++ b/src/app/shared/logging/logging.pri @@ -0,0 +1,2 @@ +HEADERS += $$PWD/consolelogger.h $$PWD/coloredoutput.h +SOURCES += $$PWD/consolelogger.cpp $$PWD/coloredoutput.cpp diff --git a/src/install_prefix.pri b/src/install_prefix.pri new file mode 100644 index 00000000..34aef1d5 --- /dev/null +++ b/src/install_prefix.pri @@ -0,0 +1 @@ +unix: isEmpty(QBS_INSTALL_PREFIX): QBS_INSTALL_PREFIX = /usr/local diff --git a/src/lib/bundledlibs.pri b/src/lib/bundledlibs.pri new file mode 100644 index 00000000..6ffedbbd --- /dev/null +++ b/src/lib/bundledlibs.pri @@ -0,0 +1,3 @@ +!qbs_use_bundled_qtscript:!qtHaveModule(script) { + CONFIG += qbs_use_bundled_qtscript +} diff --git a/src/lib/corelib/api/api.pri b/src/lib/corelib/api/api.pri new file mode 100644 index 00000000..ddb1171d --- /dev/null +++ b/src/lib/corelib/api/api.pri @@ -0,0 +1,52 @@ +include(../../../install_prefix.pri) + +HEADERS += \ + $$PWD/internaljobs.h \ + $$PWD/projectdata.h \ + $$PWD/runenvironment.h \ + $$PWD/jobs.h \ + $$PWD/languageinfo.h \ + $$PWD/project.h \ + $$PWD/project_p.h \ + $$PWD/propertymap_p.h \ + $$PWD/projectdata_p.h \ + $$PWD/rulecommand.h \ + $$PWD/rulecommand_p.h \ + $$PWD/transformerdata.h \ + $$PWD/transformerdata_p.h + +SOURCES += \ + $$PWD/internaljobs.cpp \ + $$PWD/runenvironment.cpp \ + $$PWD/projectdata.cpp \ + $$PWD/jobs.cpp \ + $$PWD/languageinfo.cpp \ + $$PWD/project.cpp \ + $$PWD/rulecommand.cpp \ + $$PWD/transformerdata.cpp + +!qbs_no_dev_install { + api_headers.files = \ + $$PWD/jobs.h \ + $$PWD/languageinfo.h \ + $$PWD/project.h \ + $$PWD/projectdata.h \ + $$PWD/rulecommand.h \ + $$PWD/runenvironment.h \ + $$PWD/transformerdata.h + api_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/api + INSTALLS += api_headers +} + +qbs_enable_project_file_updates { + HEADERS += \ + $$PWD/changeset.h \ + $$PWD/projectfileupdater.h \ + $$PWD/qmljsrewriter.h + + SOURCES += \ + $$PWD/changeset.cpp \ + $$PWD/projectfileupdater.cpp \ + $$PWD/qmljsrewriter.cpp + DEFINES += QBS_ENABLE_PROJECT_FILE_UPDATES +} diff --git a/src/lib/corelib/api/changeset.cpp b/src/lib/corelib/api/changeset.cpp new file mode 100644 index 00000000..773af328 --- /dev/null +++ b/src/lib/corelib/api/changeset.cpp @@ -0,0 +1,397 @@ +/**************************************************************************** +** +** 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 "changeset.h" + +#include + +namespace QbsQmlJS { + +ChangeSet::ChangeSet() + : m_string(nullptr), m_cursor(nullptr), m_error(false) +{ +} + +ChangeSet::ChangeSet(QList operations) + : m_string(nullptr), m_cursor(nullptr), m_operationList(std::move(operations)), m_error(false) +{ +} + +static bool overlaps(int posA, int lengthA, int posB, int lengthB) { + if (lengthB > 0) { + return + // right edge of B contained in A + (posA < posB + lengthB && posA + lengthA >= posB + lengthB) + // left edge of B contained in A + || (posA <= posB && posA + lengthA > posB) + // A contained in B + || (posB < posA && posB + lengthB > posA + lengthA); + } else { + return (posB > posA && posB < posA + lengthA); + } +} + +bool ChangeSet::hasOverlap(int pos, int length) +{ + QListIterator i(m_operationList); + while (i.hasNext()) { + const EditOp &cmd = i.next(); + + switch (cmd.type) { + case EditOp::Replace: + if (overlaps(pos, length, cmd.pos1, cmd.length1)) + return true; + break; + + case EditOp::Move: + if (overlaps(pos, length, cmd.pos1, cmd.length1)) + return true; + if (cmd.pos2 > pos && cmd.pos2 < pos + length) + return true; + break; + + case EditOp::Insert: + if (cmd.pos1 > pos && cmd.pos1 < pos + length) + return true; + break; + + case EditOp::Remove: + if (overlaps(pos, length, cmd.pos1, cmd.length1)) + return true; + break; + + case EditOp::Flip: + if (overlaps(pos, length, cmd.pos1, cmd.length1)) + return true; + if (overlaps(pos, length, cmd.pos2, cmd.length2)) + return true; + break; + + case EditOp::Copy: + if (overlaps(pos, length, cmd.pos1, cmd.length1)) + return true; + if (cmd.pos2 > pos && cmd.pos2 < pos + length) + return true; + break; + + case EditOp::Unset: + break; + } + } + + return false; +} + +bool ChangeSet::empty() const +{ + return m_operationList.empty(); +} + +QList ChangeSet::operationList() const +{ + return m_operationList; +} + +void ChangeSet::clear() +{ + m_string = nullptr; + m_cursor = nullptr; + m_operationList.clear(); + m_error = false; +} + +bool ChangeSet::replace_helper(int pos, int length, const QString &replacement) +{ + if (hasOverlap(pos, length)) + m_error = true; + + EditOp cmd(EditOp::Replace); + cmd.pos1 = pos; + cmd.length1 = length; + cmd.text = replacement; + m_operationList.push_back(cmd); + + return !m_error; +} + +bool ChangeSet::move_helper(int pos, int length, int to) +{ + if (hasOverlap(pos, length) + || hasOverlap(to, 0) + || overlaps(pos, length, to, 0)) + m_error = true; + + EditOp cmd(EditOp::Move); + cmd.pos1 = pos; + cmd.length1 = length; + cmd.pos2 = to; + m_operationList.push_back(cmd); + + return !m_error; +} + +bool ChangeSet::insert(int pos, const QString &text) +{ + Q_ASSERT(pos >= 0); + + if (hasOverlap(pos, 0)) + m_error = true; + + EditOp cmd(EditOp::Insert); + cmd.pos1 = pos; + cmd.text = text; + m_operationList.push_back(cmd); + + return !m_error; +} + +bool ChangeSet::replace(const Range &range, const QString &replacement) +{ return replace(range.start, range.end, replacement); } + +bool ChangeSet::remove(const Range &range) +{ return remove(range.start, range.end); } + +bool ChangeSet::move(const Range &range, int to) +{ return move(range.start, range.end, to); } + +bool ChangeSet::flip(const Range &range1, const Range &range2) +{ return flip(range1.start, range1.end, range2.start, range2.end); } + +bool ChangeSet::copy(const Range &range, int to) +{ return copy(range.start, range.end, to); } + +bool ChangeSet::replace(int start, int end, const QString &replacement) +{ return replace_helper(start, end - start, replacement); } + +bool ChangeSet::remove(int start, int end) +{ return remove_helper(start, end - start); } + +bool ChangeSet::move(int start, int end, int to) +{ return move_helper(start, end - start, to); } + +bool ChangeSet::flip(int start1, int end1, int start2, int end2) +{ return flip_helper(start1, end1 - start1, start2, end2 - start2); } + +bool ChangeSet::copy(int start, int end, int to) +{ return copy_helper(start, end - start, to); } + +bool ChangeSet::remove_helper(int pos, int length) +{ + if (hasOverlap(pos, length)) + m_error = true; + + EditOp cmd(EditOp::Remove); + cmd.pos1 = pos; + cmd.length1 = length; + m_operationList.push_back(cmd); + + return !m_error; +} + +bool ChangeSet::flip_helper(int pos1, int length1, int pos2, int length2) +{ + if (hasOverlap(pos1, length1) + || hasOverlap(pos2, length2) + || overlaps(pos1, length1, pos2, length2)) + m_error = true; + + EditOp cmd(EditOp::Flip); + cmd.pos1 = pos1; + cmd.length1 = length1; + cmd.pos2 = pos2; + cmd.length2 = length2; + m_operationList.push_back(cmd); + + return !m_error; +} + +bool ChangeSet::copy_helper(int pos, int length, int to) +{ + if (hasOverlap(pos, length) + || hasOverlap(to, 0) + || overlaps(pos, length, to, 0)) + m_error = true; + + EditOp cmd(EditOp::Copy); + cmd.pos1 = pos; + cmd.length1 = length; + cmd.pos2 = to; + m_operationList.push_back(cmd); + + return !m_error; +} + +void ChangeSet::doReplace(const EditOp &replace_helper, QList *replaceList) +{ + Q_ASSERT(replace_helper.type == EditOp::Replace); + + { + QMutableListIterator i(*replaceList); + while (i.hasNext()) { + EditOp &c = i.next(); + if (replace_helper.pos1 <= c.pos1) + c.pos1 += replace_helper.text.size(); + if (replace_helper.pos1 < c.pos1) + c.pos1 -= replace_helper.length1; + } + } + + if (m_string) { + m_string->replace(replace_helper.pos1, replace_helper.length1, replace_helper.text); + } else if (m_cursor) { + m_cursor->setPosition(replace_helper.pos1); + m_cursor->setPosition(replace_helper.pos1 + replace_helper.length1, QTextCursor::KeepAnchor); + m_cursor->insertText(replace_helper.text); + } +} + +void ChangeSet::convertToReplace(const EditOp &op, QList *replaceList) +{ + EditOp replace1(EditOp::Replace); + EditOp replace2(EditOp::Replace); + + switch (op.type) { + case EditOp::Replace: + replaceList->push_back(op); + break; + + case EditOp::Move: + replace1.pos1 = op.pos1; + replace1.length1 = op.length1; + replaceList->push_back(replace1); + + replace2.pos1 = op.pos2; + replace2.text = textAt(op.pos1, op.length1); + replaceList->push_back(replace2); + break; + + case EditOp::Insert: + replace1.pos1 = op.pos1; + replace1.text = op.text; + replaceList->push_back(replace1); + break; + + case EditOp::Remove: + replace1.pos1 = op.pos1; + replace1.length1 = op.length1; + replaceList->push_back(replace1); + break; + + case EditOp::Flip: + replace1.pos1 = op.pos1; + replace1.length1 = op.length1; + replace1.text = textAt(op.pos2, op.length2); + replaceList->push_back(replace1); + + replace2.pos1 = op.pos2; + replace2.length1 = op.length2; + replace2.text = textAt(op.pos1, op.length1); + replaceList->push_back(replace2); + break; + + case EditOp::Copy: + replace1.pos1 = op.pos2; + replace1.text = textAt(op.pos1, op.length1); + replaceList->push_back(replace1); + break; + + case EditOp::Unset: + break; + } +} + +bool ChangeSet::hadErrors() +{ + return m_error; +} + +void ChangeSet::apply(QString *s) +{ + m_string = s; + apply_helper(); + m_string = nullptr; +} + +void ChangeSet::apply(QTextCursor *textCursor) +{ + m_cursor = textCursor; + apply_helper(); + m_cursor = nullptr; +} + +QString ChangeSet::textAt(int pos, int length) +{ + if (m_string) { + return m_string->mid(pos, length); + } else if (m_cursor) { + m_cursor->setPosition(pos); + m_cursor->setPosition(pos + length, QTextCursor::KeepAnchor); + return m_cursor->selectedText(); + } + return {}; +} + +void ChangeSet::apply_helper() +{ + // convert all ops to replace + QList replaceList; + { + while (!m_operationList.empty()) { + const EditOp cmd(m_operationList.front()); + m_operationList.removeFirst(); + convertToReplace(cmd, &replaceList); + } + } + + // execute replaces + if (m_cursor) + m_cursor->beginEditBlock(); + + while (!replaceList.empty()) { + const EditOp cmd(replaceList.front()); + replaceList.removeFirst(); + doReplace(cmd, &replaceList); + } + + if (m_cursor) + m_cursor->endEditBlock(); +} + +} // namespace QbsQmlJS + diff --git a/src/lib/corelib/api/changeset.h b/src/lib/corelib/api/changeset.h new file mode 100644 index 00000000..23248c48 --- /dev/null +++ b/src/lib/corelib/api/changeset.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** 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 QBS_CHANGESET_H +#define QBS_CHANGESET_H + +#include +#include + +QT_FORWARD_DECLARE_CLASS(QTextCursor) + +namespace QbsQmlJS { + +class ChangeSet +{ +public: + struct EditOp { + enum Type + { + Unset, + Replace, + Move, + Insert, + Remove, + Flip, + Copy + }; + + EditOp(): type(Unset), pos1(0), pos2(0), length1(0), length2(0) {} + EditOp(Type t): type(t), pos1(0), pos2(0), length1(0), length2(0) {} + + Type type; + int pos1; + int pos2; + int length1; + int length2; + QString text; + }; + + struct Range { + Range() + : start(0), end(0) {} + + Range(int start, int end) + : start(start), end(end) {} + + int start; + int end; + }; + +public: + ChangeSet(); + ChangeSet(QList operations); + + bool empty() const; + + QList operationList() const; + + void clear(); + + bool replace(const Range &range, const QString &replacement); + bool remove(const Range &range); + bool move(const Range &range, int to); + bool flip(const Range &range1, const Range &range2); + bool copy(const Range &range, int to); + bool replace(int start, int end, const QString &replacement); + bool remove(int start, int end); + bool move(int start, int end, int to); + bool flip(int start1, int end1, int start2, int end2); + bool copy(int start, int end, int to); + bool insert(int pos, const QString &text); + + bool hadErrors(); + + void apply(QString *s); + void apply(QTextCursor *textCursor); + +private: + // length-based API. + bool replace_helper(int pos, int length, const QString &replacement); + bool move_helper(int pos, int length, int to); + bool remove_helper(int pos, int length); + bool flip_helper(int pos1, int length1, int pos2, int length2); + bool copy_helper(int pos, int length, int to); + + bool hasOverlap(int pos, int length); + QString textAt(int pos, int length); + + void doReplace(const EditOp &replace, QList *replaceList); + void convertToReplace(const EditOp &op, QList *replaceList); + + void apply_helper(); + +private: + QString *m_string; + QTextCursor *m_cursor; + + QList m_operationList; + bool m_error; +}; + +} // namespace QbsQmlJS + +#endif // Include guard. diff --git a/src/lib/corelib/api/internaljobs.cpp b/src/lib/corelib/api/internaljobs.cpp new file mode 100644 index 00000000..0fabc627 --- /dev/null +++ b/src/lib/corelib/api/internaljobs.cpp @@ -0,0 +1,464 @@ +/**************************************************************************** +** +** 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 "internaljobs.h" + +#include "jobs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace qbs { +namespace Internal { + +class JobObserver : public ProgressObserver +{ +public: + JobObserver(InternalJob *job) : m_job(job) { } + ~JobObserver() override { delete m_timedLogger; } + + void cancel() + { + std::lock_guard lock(m_cancelMutex); + m_canceled = true; + } + +private: + void initialize(const QString &task, int maximum) override + { + QBS_ASSERT(!m_timedLogger, delete m_timedLogger); + if (m_job->timed()) + m_timedLogger = new TimedActivityLogger(m_job->logger(), task, true); + m_value = 0; + m_maximum = maximum; + emit m_job->newTaskStarted(task, maximum, m_job); + } + + void setMaximum(int maximum) override + { + m_maximum = maximum; + emit m_job->totalEffortChanged(maximum, m_job); + } + + 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)); + m_value = value; + if (value == m_maximum) { + delete m_timedLogger; + m_timedLogger = nullptr; + } + emit m_job->taskProgress(value, m_job); + } + + int progressValue() override { return m_value; } + int maximum() const override { return m_maximum; } + bool canceled() const override + { + std::lock_guard lock(m_cancelMutex); + return m_canceled; + } + + int m_value = 0; + int m_maximum = 0; + mutable std::mutex m_cancelMutex; + bool m_canceled = false; + InternalJob * const m_job; + TimedActivityLogger *m_timedLogger = nullptr; +}; + + +InternalJob::InternalJob(Logger logger, QObject *parent) + : QObject(parent) + , m_observer(new JobObserver(this)) + , m_ownsObserver(true) + , m_logger(std::move(logger)) + , m_timed(false) +{ +} + +InternalJob::~InternalJob() +{ + if (m_ownsObserver) + delete m_observer; +} + +void InternalJob::cancel() +{ + m_observer->cancel(); +} + +void InternalJob::shareObserverWith(InternalJob *otherJob) +{ + if (m_ownsObserver) { + delete m_observer; + m_ownsObserver = false; + } + m_observer = otherJob->m_observer; +} + +void InternalJob::storeBuildGraph(const TopLevelProjectPtr &project) +{ + try { + doSanityChecks(project, logger()); + TimedActivityLogger storeTimer(m_logger, Tr::tr("Storing build graph"), timed()); + project->store(logger()); + } catch (const ErrorInfo &error) { + ErrorInfo fullError = this->error(); + const auto items = error.items(); + for (const ErrorItem &item : items) + fullError.append(item); + setError(fullError); + } +} + + +/** + * Construct a new thread wrapper for a synchronous job. + * This object takes over ownership of the synchronous job. + */ +InternalJobThreadWrapper::InternalJobThreadWrapper(InternalJob *synchronousJob, QObject *parent) + : InternalJob(synchronousJob->logger(), parent) + , m_job(synchronousJob) + , m_running(false) +{ + synchronousJob->shareObserverWith(this); + m_job->moveToThread(&m_thread); + connect(m_job, &InternalJob::finished, this, &InternalJobThreadWrapper::handleFinished); + connect(m_job, &InternalJob::newTaskStarted, + this, &InternalJob::newTaskStarted); + connect(m_job, &InternalJob::taskProgress, + this, &InternalJob::taskProgress); + connect(m_job, &InternalJob::totalEffortChanged, + this, &InternalJob::totalEffortChanged); + connect(this, &InternalJobThreadWrapper::startRequested, m_job, &InternalJob::start); +} + +InternalJobThreadWrapper::~InternalJobThreadWrapper() +{ + if (m_running) { + cancel(); + while (m_running) + QCoreApplication::processEvents(); + } + m_thread.quit(); + m_thread.wait(); + delete m_job; + m_job = nullptr; +} + +void InternalJobThreadWrapper::start() +{ + setTimed(m_job->timed()); + m_thread.start(); + m_running = true; + QTimer::singleShot(0, this, &InternalJobThreadWrapper::startRequested); +} + +void InternalJobThreadWrapper::handleFinished() +{ + m_running = false; + setError(m_job->error()); + emit finished(this); +} + + +InternalSetupProjectJob::InternalSetupProjectJob(const Logger &logger) + : InternalJob(logger) +{ +} + +InternalSetupProjectJob::~InternalSetupProjectJob() = default; + +void InternalSetupProjectJob::init(const TopLevelProjectPtr &existingProject, + const SetupProjectParameters ¶meters) +{ + m_existingProject = existingProject; + m_parameters = parameters; + setTimed(parameters.logElapsedTime()); +} + +void InternalSetupProjectJob::reportError(const ErrorInfo &error) +{ + setError(error); + emit finished(this); +} + +TopLevelProjectPtr InternalSetupProjectJob::project() const +{ + return m_newProject; +} + +void InternalSetupProjectJob::start() +{ + BuildGraphLocker *bgLocker = m_existingProject ? m_existingProject->bgLocker : nullptr; + bool deleteLocker = false; + try { + const ErrorInfo err = m_parameters.expandBuildConfiguration(); + if (err.hasError()) + throw err; + const QString projectId = TopLevelProject::deriveId( + m_parameters.finalBuildConfigurationTree()); + const QString buildDir + = TopLevelProject::deriveBuildDirectory(m_parameters.buildRoot(), projectId); + if (m_existingProject && m_existingProject->buildDirectory != buildDir) + m_existingProject.reset(); + if (!m_existingProject) { + bgLocker = new BuildGraphLocker(ProjectBuildData::deriveBuildGraphFilePath(buildDir, + projectId), + logger(), m_parameters.waitLockBuildGraph(), observer()); + deleteLocker = true; + } + execute(); + if (m_existingProject) { + if (m_existingProject != m_newProject) + m_existingProject->makeModuleProvidersNonTransient(); + m_existingProject->bgLocker = nullptr; + } + m_newProject->bgLocker = bgLocker; + deleteLocker = false; + } catch (const ErrorInfo &error) { + m_newProject.reset(); + setError(error); + + // Delete the build graph locker if and only if we allocated it here. + if (deleteLocker) + delete bgLocker; + } + emit finished(this); +} + +void InternalSetupProjectJob::execute() +{ + RulesEvaluationContextPtr evalContext(new RulesEvaluationContext(logger())); + evalContext->setObserver(observer()); + + switch (m_parameters.restoreBehavior()) { + case SetupProjectParameters::ResolveOnly: + resolveProjectFromScratch(evalContext->engine()); + resolveBuildDataFromScratch(evalContext); + break; + case SetupProjectParameters::RestoreOnly: + m_newProject = restoreProject(evalContext).loadedProject; + break; + case SetupProjectParameters::RestoreAndTrackChanges: { + const BuildGraphLoadResult loadResult = restoreProject(evalContext); + m_newProject = loadResult.newlyResolvedProject; + if (!m_newProject) + m_newProject = loadResult.loadedProject; + if (!m_newProject) { + resolveProjectFromScratch(evalContext->engine()); + resolveBuildDataFromScratch(evalContext); + } else { + QBS_CHECK(m_newProject->buildData); + } + break; + } + } + + if (!m_parameters.dryRun()) + storeBuildGraph(m_newProject); + + // The evalutation context cannot be re-used for building, which runs in a different thread. + m_newProject->buildData->evaluationContext.reset(); +} + +void InternalSetupProjectJob::resolveProjectFromScratch(ScriptEngine *engine) +{ + Loader loader(engine, logger()); + loader.setSearchPaths(m_parameters.searchPaths()); + loader.setProgressObserver(observer()); + m_newProject = loader.loadProject(m_parameters); + QBS_CHECK(m_newProject); +} + +void InternalSetupProjectJob::resolveBuildDataFromScratch(const RulesEvaluationContextPtr &evalContext) +{ + TimedActivityLogger resolveLogger(logger(), QStringLiteral("Resolving build project"), timed()); + BuildDataResolver(logger()).resolveBuildData(m_newProject, evalContext); +} + +BuildGraphLoadResult InternalSetupProjectJob::restoreProject(const RulesEvaluationContextPtr &evalContext) +{ + BuildGraphLoader bgLoader(logger()); + const BuildGraphLoadResult loadResult + = bgLoader.load(m_existingProject, m_parameters, evalContext); + return loadResult; +} + +BuildGraphTouchingJob::BuildGraphTouchingJob(const Logger &logger, QObject *parent) + : InternalJob(logger, parent), m_dryRun(false) +{ +} + +BuildGraphTouchingJob::~BuildGraphTouchingJob() = default; + +void BuildGraphTouchingJob::setup(const TopLevelProjectPtr &project, + const QVector &products, bool dryRun) +{ + m_project = project; + m_products = products; + m_dryRun = dryRun; +} + +void BuildGraphTouchingJob::storeBuildGraph() +{ + if (!m_dryRun && !error().isInternalError()) + InternalJob::storeBuildGraph(m_project); +} + +InternalBuildJob::InternalBuildJob(const Logger &logger, QObject *parent) + : BuildGraphTouchingJob(logger, parent), m_executor(nullptr) +{ +} + +void InternalBuildJob::build(const TopLevelProjectPtr &project, + const QVector &products, const BuildOptions &buildOptions) +{ + setup(project, products, buildOptions.dryRun()); + setTimed(buildOptions.logElapsedTime()); + + m_executor = new Executor(logger()); + m_executor->setProject(project); + m_executor->setProducts(products); + m_executor->setBuildOptions(buildOptions); + m_executor->setProgressObserver(observer()); + + const auto executorThread = new QThread(this); + m_executor->moveToThread(executorThread); + connect(m_executor, &Executor::reportCommandDescription, + this, &BuildGraphTouchingJob::reportCommandDescription); + connect(m_executor, &Executor::reportProcessResult, + this, &BuildGraphTouchingJob::reportProcessResult); + + connect(executorThread, &QThread::started, m_executor, &Executor::build); + connect(m_executor, &Executor::finished, this, &InternalBuildJob::handleFinished); + connect(m_executor, &QObject::destroyed, executorThread, &QThread::quit); + connect(executorThread, &QThread::finished, this, &InternalBuildJob::emitFinished); + executorThread->start(); +} + +void InternalBuildJob::handleFinished() +{ + setError(m_executor->error()); + project()->buildData->evaluationContext.reset(); + storeBuildGraph(); + m_executor->deleteLater(); +} + +void InternalBuildJob::emitFinished() +{ + emit finished(this); +} + +InternalCleanJob::InternalCleanJob(const Logger &logger, QObject *parent) + : BuildGraphTouchingJob(logger, parent) +{ +} + +void InternalCleanJob::init(const TopLevelProjectPtr &project, + const QVector &products, + const CleanOptions &options) +{ + setup(project, products, options.dryRun()); + setTimed(options.logElapsedTime()); + m_options = options; +} + +void InternalCleanJob::start() +{ + try { + ArtifactCleaner cleaner(logger(), observer()); + cleaner.cleanup(project(), products(), m_options); + } catch (const ErrorInfo &error) { + setError(error); + } + storeBuildGraph(); + emit finished(this); +} + + +InternalInstallJob::InternalInstallJob(const Logger &logger) + : InternalJob(logger) +{ +} + +InternalInstallJob::~InternalInstallJob() = default; + +void InternalInstallJob::init(const TopLevelProjectPtr &project, + const QVector &products, const InstallOptions &options) +{ + m_project = project; + m_products = products; + m_options = options; + setTimed(options.logElapsedTime()); +} + +void InternalInstallJob::start() +{ + try { + ProductInstaller(m_project, m_products, m_options, observer(), logger()).install(); + } catch (const ErrorInfo &error) { + setError(error); + } + emit finished(this); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/api/internaljobs.h b/src/lib/corelib/api/internaljobs.h new file mode 100644 index 00000000..d6683594 --- /dev/null +++ b/src/lib/corelib/api/internaljobs.h @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** 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 QBS_INTERNALJOBS_H +#define QBS_INTERNALJOBS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace qbs { +class ProcessResult; +class Settings; + +namespace Internal { +class BuildGraphLoadResult; +class BuildGraphLocker; +class Executor; +class JobObserver; +class ScriptEngine; + +class InternalJob : public QObject +{ + Q_OBJECT + friend class JobObserver; +public: + ~InternalJob() override; + + void cancel(); + virtual void start() {} + ErrorInfo error() const { return m_error; } + void setError(const ErrorInfo &error) { m_error = error; } + + Logger logger() const { return m_logger; } + bool timed() const { return m_timed; } + void shareObserverWith(InternalJob *otherJob); + +protected: + explicit InternalJob(Logger logger, QObject *parent = nullptr); + + JobObserver *observer() const { return m_observer; } + void setTimed(bool timed) { m_timed = timed; } + void storeBuildGraph(const TopLevelProjectPtr &project); + +signals: + void finished(Internal::InternalJob *job); + void newTaskStarted(const QString &description, int totalEffort, Internal::InternalJob *job); + void totalEffortChanged(int totalEffort, Internal::InternalJob *job); + void taskProgress(int value, Internal::InternalJob *job); + +private: + ErrorInfo m_error; + JobObserver *m_observer; + bool m_ownsObserver; + Logger m_logger; + bool m_timed; +}; + + +class InternalJobThreadWrapper : public InternalJob +{ + Q_OBJECT +public: + InternalJobThreadWrapper(InternalJob *synchronousJob, QObject *parent = nullptr); + ~InternalJobThreadWrapper() override; + + void start() override; + InternalJob *synchronousJob() const { return m_job; } + +signals: + void startRequested(); + +private: + void handleFinished(); + + QThread m_thread; + InternalJob *m_job; + bool m_running; +}; + +class InternalSetupProjectJob : public InternalJob +{ + Q_OBJECT +public: + InternalSetupProjectJob(const Logger &logger); + ~InternalSetupProjectJob() override; + + void init(const TopLevelProjectPtr &existingProject, const SetupProjectParameters ¶meters); + void reportError(const ErrorInfo &error); + + TopLevelProjectPtr project() const; + +private: + void start() override; + void resolveProjectFromScratch(Internal::ScriptEngine *engine); + void resolveBuildDataFromScratch(const RulesEvaluationContextPtr &evalContext); + BuildGraphLoadResult restoreProject(const RulesEvaluationContextPtr &evalContext); + void execute(); + + TopLevelProjectPtr m_existingProject; + TopLevelProjectPtr m_newProject; + SetupProjectParameters m_parameters; +}; + + +class BuildGraphTouchingJob : public InternalJob +{ + Q_OBJECT +public: + const QVector &products() const { return m_products; } + const TopLevelProjectPtr &project() const { return m_project; } + +signals: + void reportCommandDescription(const QString &highlight, const QString &message); + void reportProcessResult(const qbs::ProcessResult &result); + +protected: + BuildGraphTouchingJob(const Logger &logger, QObject *parent = nullptr); + ~BuildGraphTouchingJob() override; + + void setup(const TopLevelProjectPtr &project, const QVector &products, + bool dryRun); + void storeBuildGraph(); + +private: + TopLevelProjectPtr m_project; + QVector m_products; + bool m_dryRun; +}; + + +class InternalBuildJob : public BuildGraphTouchingJob +{ + Q_OBJECT +public: + InternalBuildJob(const Logger &logger, QObject *parent = nullptr); + + void build(const TopLevelProjectPtr &project, const QVector &products, + const BuildOptions &buildOptions); + +private: + void handleFinished(); + void emitFinished(); + + Executor *m_executor; +}; + + +class InternalCleanJob : public BuildGraphTouchingJob +{ + Q_OBJECT +public: + InternalCleanJob(const Logger &logger, QObject *parent = nullptr); + + void init(const TopLevelProjectPtr &project, const QVector &products, + const CleanOptions &options); + +private: + void start() override; + + CleanOptions m_options; +}; + + +class InternalInstallJob : public InternalJob +{ + Q_OBJECT +public: + InternalInstallJob(const Logger &logger); + ~InternalInstallJob() override; + + void init(const TopLevelProjectPtr &project, const QVector &products, + const InstallOptions &options); + +private: + void start() override; + + TopLevelProjectPtr m_project; + QVector m_products; + InstallOptions m_options; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_INTERNALJOBS_H diff --git a/src/lib/corelib/api/jobs.cpp b/src/lib/corelib/api/jobs.cpp new file mode 100644 index 00000000..7a845b0a --- /dev/null +++ b/src/lib/corelib/api/jobs.cpp @@ -0,0 +1,375 @@ +/**************************************************************************** +** +** 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 "jobs.h" + +#include "internaljobs.h" +#include "project_p.h" +#include +#include +#include + +#include +#include + +namespace qbs { +using namespace Internal; + +/*! + * \class AbstractJob + * \brief The \c AbstractJob class represents an operation relating to a \c Project. + * Concrete child classes of \c AbstractJob are created by factory functions in the \c Project + * class. The respective objects represent an operation that is started automatically + * and is considered "running" until the \c finished() signal has been emitted. Afterwards, + * callers can find out whether the operation was successful by calling \c hasError(). While + * the operation is going on, progress information is being provided via \c taskStarted() and + * \c taskProgress. + * Note that though a job is being started automatically by its factory function, you are guaranteed + * to recevieve all signals it emits if you connect to it right after getting the object from the + * creating function. + * \sa Project + */ + +/*! + * \enum AbstractJob::State + * This enum type specifies which states a job can be in. + * \value StateRunning The respective operation is ongoing. + * \value StateCanceling The job has been requested to cancel via \c AbstractJob::cancel(), + * but the \c AbstractJob::finished() signal has not been emitted yet. + * \value StateFinished The operation has finished and the \c AbstractJob::finished() signal + * has been emitted. + */ + + /*! + * \fn AbstractJob::State AbstractJob::state() const + * \brief Returns the current state of the operation. + */ + + /*! + * \fn bool AbstractJob::hasError() const + * \brief Returns true if the operation has finished with an error, otherwise returns false. + * This function should not be called before the \c finished() signal has been emitted. + */ + +/*! + * \fn void AbstractJob::taskStarted(const QString &description, int maximumProgressValue, qbs::AbstractJob *job) + * \brief Indicates that a new task has been started. + * The \a description parameter is a string intended for presentation to a user. + * The \a maximumProgressValue parameter indicates the maximum value to which subsequent values of + * \c taskProgress() will go. + * This signal is typically emitted exactly once for a job that finishes successfully. However, + * operations might emit it several times if they are made up of subtasks whose overall effort + * cannot be determined in advance. + * \sa AbstractJob::taskProgress() + */ + +/*! + * \fn void taskProgress(int newProgressValue, qbs::AbstractJob *job) + * \brief Indicates progress in executing the operation. + * The \a newProgressValue parameter represents the current progress. It is always greater than + * zero, strictly increasing and goes up to the \c maximumProgressValue argument of the last + * call to \c taskStarted(). + * \sa AbstractJob::taskStarted() + */ + + /*! + * \fn void finished(bool success, qbs::AbstractJob *job) + * \brief Indicates that the operation has finished. + * Check the \a success parameter to find out whether everything went fine or an error occurred. + */ + +AbstractJob::AbstractJob(InternalJob *internalJob, QObject *parent) + : QObject(parent), m_internalJob(internalJob) +{ + m_internalJob->setParent(this); + connect(m_internalJob, &InternalJob::newTaskStarted, + this, &AbstractJob::handleTaskStarted, Qt::QueuedConnection); + connect(m_internalJob, &InternalJob::totalEffortChanged, + this, &AbstractJob::handleTotalEffortChanged); + connect(m_internalJob, &InternalJob::taskProgress, + this, &AbstractJob::handleTaskProgress, Qt::QueuedConnection); + connect(m_internalJob, &InternalJob::finished, this, &AbstractJob::handleFinished); + m_state = StateRunning; +} + +bool AbstractJob::lockProject(const TopLevelProjectPtr &project) +{ + // The API is not thread-safe, so we don't need a mutex here, as the API requests come in + // synchronously. + if (project->locked) { + internalJob()->setError(tr("Cannot start a job while another one is in progress.")); + QTimer::singleShot(0, this, [this] { emit finished(false, this); }); + return false; + } + project->locked = true; + m_project = project; + return true; +} + +void AbstractJob::unlockProject() +{ + if (!m_project) + return; + QBS_ASSERT(m_project->locked, return); + m_project->locked = false; +} + +/*! + * \brief Destroys the object, canceling the operation if necessary. + */ +AbstractJob::~AbstractJob() +{ + m_internalJob->disconnect(this); + cancel(); +} + +/*! + * \brief Returns the error which caused this operation to fail, if it did fail. + */ +ErrorInfo AbstractJob::error() const +{ + if (m_error.hasError()) + return m_error; + return internalJob()->error(); +} + +/*! + * \brief Cancels this job. + * Note that the job might not finish immediately. If you need to make sure it has actually + * finished, wait for the \c finished() signal. + * \sa AbstractJob::finished(AbstractJob *); + */ +void AbstractJob::cancel() +{ + if (m_state != StateRunning) + return; + m_state = StateCanceling; + internalJob()->cancel(); +} + +void AbstractJob::handleTaskStarted(const QString &description, int maximumProgressValue) +{ + emit taskStarted(description, maximumProgressValue, this); +} + +void AbstractJob::handleTotalEffortChanged(int totalEffort) +{ + emit totalEffortChanged(totalEffort, this); +} + +void AbstractJob::handleTaskProgress(int newProgressValue) +{ + emit taskProgress(newProgressValue, this); +} + +void AbstractJob::handleFinished() +{ + QBS_ASSERT(m_state != StateFinished, return); + finish(); + m_state = StateFinished; + unlockProject(); + emit finished(!error().hasError(), this); +} + + +/*! + * \class SetupProjectJob + * \brief The \c SetupProjectJob class represents an operation that reads a qbs project file and + * creates a \c Project object from it. + * Note that this job can emit the \c taskStarted() signal more than once. + * \sa AbstractJob::taskStarted() + */ + +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")); +} + +/*! + * \brief Returns the project resulting from this operation. + * Note that the result is undefined if the job did not finish successfully. + * \sa AbstractJob::hasError() + */ +Project SetupProjectJob::project() const +{ + auto const wrapper = qobject_cast(internalJob()); + auto const job = qobject_cast(wrapper->synchronousJob()); + return Project(job->project(), job->logger()); +} + +void SetupProjectJob::resolve(const Project &existingProject, + const SetupProjectParameters ¶meters) +{ + m_existingProject = existingProject; + const TopLevelProjectPtr &existingInternalProject + = existingProject.d ? existingProject.d->internalProject : TopLevelProjectPtr(); + if (existingInternalProject && !lockProject(existingInternalProject)) + return; + auto const wrapper = qobject_cast(internalJob()); + auto const job = qobject_cast(wrapper->synchronousJob()); + job->init(existingInternalProject, parameters); + wrapper->start(); +} + +void SetupProjectJob::reportError(const ErrorInfo &error) +{ + auto const wrapper = qobject_cast(internalJob()); + auto const job = qobject_cast(wrapper->synchronousJob()); + job->reportError(error); +} + +void SetupProjectJob::finish() +{ + // If the new project was successfully created, invalidate the existing one. + // The invariant is that there must always be at most one valid Project object + // for the same build directory, so that exclusive ownership of the build graph lock + // is ensured. + // We also need to invalidate the project if an error has occurred after the build data was + // already transferred. + if (m_existingProject.isValid() + && (!error().hasError() || !m_existingProject.d->internalProject->buildData)) { + m_existingProject.d->internalProject.reset(); + } +} + +/*! + * \class ProcessResult + * \brief The \c ProcessResult class represents the result of one external program run by Qbs. + * + * The \c ProcessResult class represents all the information on one external program that was + * run by Qbs. It includes the command line used to start the program, the working directory + * as well as output and exit codes. + */ + +/*! + * \class BuildJob + * \brief The \c BuildJob class represents a build operation. + */ + +/*! + * \fn void BuildJob::reportCommandDescription(const QString &highlight, const QString &message) + * \brief Signals that a new command is being worked on. + * The \a highlight parameter is used to decide on the colors and font styles to be used to + * print the message. + * The \a message parameter is the localized message to print. + */ + +/*! + * \fn void BuildJob::reportProcessResult(const qbs::ProcessResult &result) + * \brief Signals that an external command has finished. + * The \a result parameter contains all details on the process that was run by Qbs. + */ + +BuildJob::BuildJob(const Logger &logger, QObject *parent) + : AbstractJob(new InternalBuildJob(logger), parent) +{ + connect(&LauncherInterface::instance(), &LauncherInterface::errorOccurred, + this, &BuildJob::handleLauncherError); + auto job = static_cast(internalJob()); + connect(job, &BuildGraphTouchingJob::reportCommandDescription, + this, &BuildJob::reportCommandDescription); + connect(job, &BuildGraphTouchingJob::reportProcessResult, + this, &BuildJob::reportProcessResult); +} + +void BuildJob::build(const TopLevelProjectPtr &project, const QVector &products, + const BuildOptions &options) +{ + if (!lockProject(project)) + return; + LauncherInterface::startLauncher(); + qobject_cast(internalJob())->build(project, products, options); +} + +void BuildJob::handleLauncherError(const ErrorInfo &error) +{ + setError(error); + cancel(); +} + +void BuildJob::finish() +{ + LauncherInterface::stopLauncher(); +} + + +/*! + * \class CleanJob + * \brief The \c CleanJob class represents an operation removing build artifacts. + */ + +CleanJob::CleanJob(const Logger &logger, QObject *parent) + : AbstractJob(new InternalJobThreadWrapper(new InternalCleanJob(logger)), parent) +{ +} + +void CleanJob::clean(const TopLevelProjectPtr &project, const QVector &products, + const qbs::CleanOptions &options) +{ + if (!lockProject(project)) + return; + auto wrapper = qobject_cast(internalJob()); + qobject_cast(wrapper->synchronousJob())->init(project, products, options); + wrapper->start(); +} + +/*! + * \class InstallJob + * \brief The \c InstallJob class represents an operation installing files. + */ + +InstallJob::InstallJob(const Logger &logger, QObject *parent) + : AbstractJob(new InternalJobThreadWrapper(new InternalInstallJob(logger)), parent) +{ +} + +void InstallJob::install(const TopLevelProjectPtr &project, + const QVector &products, + const InstallOptions &options) +{ + if (!lockProject(project)) + return; + auto wrapper = qobject_cast(internalJob()); + auto installJob = qobject_cast(wrapper->synchronousJob()); + installJob->init(project, products, options); + wrapper->start(); +} + +} // namespace qbs diff --git a/src/lib/corelib/api/jobs.h b/src/lib/corelib/api/jobs.h new file mode 100644 index 00000000..64929489 --- /dev/null +++ b/src/lib/corelib/api/jobs.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** 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 QBS_JOBS_H +#define QBS_JOBS_H + +#include "project.h" +#include "../language/forward_decls.h" +#include "../tools/error.h" +#include "../tools/qbs_export.h" + +#include +#include +#include + +namespace qbs { +class BuildOptions; +class CleanOptions; +class InstallOptions; +class ProcessResult; +class SetupProjectParameters; +namespace Internal { +class InternalJob; +class Logger; +class ProjectPrivate; +} // namespace Internal + +class Project; + +class QBS_EXPORT AbstractJob : public QObject +{ + Q_OBJECT +public: + ~AbstractJob() override; + + enum State { StateRunning, StateCanceling, StateFinished }; + State state() const { return m_state; } + + ErrorInfo error() const; + +public slots: + void cancel(); + +protected: + AbstractJob(Internal::InternalJob *internalJob, QObject *parent); + Internal::InternalJob *internalJob() const { return m_internalJob; } + + bool lockProject(const Internal::TopLevelProjectPtr &project); + void setError(const ErrorInfo &error) { m_error = error; } + +signals: + void taskStarted(const QString &description, int maximumProgressValue, qbs::AbstractJob *job); + void totalEffortChanged(int totalEffort, qbs::AbstractJob *job); + void taskProgress(int newProgressValue, qbs::AbstractJob *job); + void finished(bool success, qbs::AbstractJob *job); + +private: + void handleTaskStarted(const QString &description, int maximumProgressValue); + void handleTotalEffortChanged(int totalEffort); + void handleTaskProgress(int newProgressValue); + void handleFinished(); + + void unlockProject(); + virtual void finish() { } + + Internal::InternalJob * const m_internalJob; + Internal::TopLevelProjectPtr m_project; + ErrorInfo m_error; + State m_state; +}; + + +class QBS_EXPORT SetupProjectJob : public AbstractJob +{ + Q_OBJECT + friend class Project; +public: + Project project() const; + +private: + SetupProjectJob(const Internal::Logger &logger, QObject *parent); + + void resolve(const Project &existingProject, const SetupProjectParameters ¶meters); + void reportError(const ErrorInfo &error); + + void finish() override; + + Project m_existingProject; +}; + +class QBS_EXPORT BuildJob : public AbstractJob +{ + Q_OBJECT + friend class Internal::ProjectPrivate; + +signals: + void reportCommandDescription(const QString &highlight, const QString &message); + void reportProcessResult(const qbs::ProcessResult &result); + +private: + BuildJob(const Internal::Logger &logger, QObject *parent); + + void build(const Internal::TopLevelProjectPtr &project, + const QVector &products, + const BuildOptions &options); + void handleLauncherError(const ErrorInfo &error); + + void finish() override; +}; + + +class QBS_EXPORT CleanJob : public AbstractJob +{ + Q_OBJECT + friend class Internal::ProjectPrivate; + +private: + CleanJob(const Internal::Logger &logger, QObject *parent); + + void clean(const Internal::TopLevelProjectPtr &project, + const QVector &products, const CleanOptions &options); +}; + +class QBS_EXPORT InstallJob : public AbstractJob +{ + Q_OBJECT + friend class Internal::ProjectPrivate; +private: + InstallJob(const Internal::Logger &logger, QObject *parent); + + void install(const Internal::TopLevelProjectPtr &project, + const QVector &products, + const InstallOptions &options); +}; + +} // namespace qbs + +#endif // QBS_JOBS_H diff --git a/src/lib/corelib/api/languageinfo.cpp b/src/lib/corelib/api/languageinfo.cpp new file mode 100644 index 00000000..505012e8 --- /dev/null +++ b/src/lib/corelib/api/languageinfo.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** 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 "languageinfo.h" + +#include +#include +#include + +#include + +namespace qbs { + +std::string LanguageInfo::qmlTypeInfo() +{ + const Internal::BuiltinDeclarations &builtins = Internal::BuiltinDeclarations::instance(); + + // Header: + std::string result; + result.append("import QtQuick.tooling 1.0\n\n"); + result.append("// This file describes the plugin-supplied types contained in the library.\n"); + result.append("// It is used for QML tooling purposes only.\n\n"); + result.append("Module {\n"); + + // Individual Components: + auto typeNames = builtins.allTypeNames(); + typeNames.sort(); + for (const QString &typeName : qAsConst(typeNames)) { + const auto typeNameString = typeName.toStdString(); + result.append(" Component {\n"); + result.append(" name: \"" + typeNameString + "\"\n"); + result.append(" exports: [ \"qbs/"); + result.append(typeNameString); + result.append(" "); + const auto v = builtins.languageVersion(); + result.append(QStringLiteral("%1.%2") + .arg(v.majorVersion()).arg(v.minorVersion()).toUtf8().data()); + result.append("\" ]\n"); + result.append(" prototype: \"QQuickItem\"\n"); + + Internal::ItemDeclaration itemDecl + = builtins.declarationsForType(builtins.typeForName(typeName)); + auto properties = itemDecl.properties(); + std::sort(std::begin(properties), std::end(properties), [] + (const Internal::PropertyDeclaration &a, const Internal::PropertyDeclaration &b) { + return a.name() < b.name(); + }); + for (const Internal::PropertyDeclaration &property : qAsConst(properties)) { + result.append(" Property { name: \""); + result.append(property.name().toUtf8().data()); + result.append("\"; "); + switch (property.type()) { + case qbs::Internal::PropertyDeclaration::UnknownType: + result.append("type: \"unknown\""); + break; + case qbs::Internal::PropertyDeclaration::Boolean: + result.append("type: \"bool\""); + break; + case qbs::Internal::PropertyDeclaration::Integer: + result.append("type: \"int\""); + break; + case qbs::Internal::PropertyDeclaration::Path: + result.append("type: \"string\""); + break; + case qbs::Internal::PropertyDeclaration::PathList: + result.append("type: \"string\"; isList: true"); + break; + case qbs::Internal::PropertyDeclaration::String: + result.append("type: \"string\""); + break; + case qbs::Internal::PropertyDeclaration::StringList: + result.append("type: \"string\"; isList: true"); + break; + case qbs::Internal::PropertyDeclaration::Variant: + result.append("type: \"QVariant\""); + break; + case qbs::Internal::PropertyDeclaration::VariantList: + result.append("type: \"QVariantList\""); + break; + } + result.append(" }\n"); // Property + } + + result.append(" }\n"); // Component + } + + // Footer: + result.append("}\n"); // Module + return result; +} + +Version LanguageInfo::qbsVersion() +{ + static const auto v = Version::fromString(QLatin1String(QBS_VERSION)); + return v; +} + +} // namespace qbs diff --git a/src/lib/corelib/api/languageinfo.h b/src/lib/corelib/api/languageinfo.h new file mode 100644 index 00000000..d0db7d5e --- /dev/null +++ b/src/lib/corelib/api/languageinfo.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** 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 QBS_LANGUAGEINFO_H +#define QBS_LANGUAGEINFO_H + +#include "../tools/qbs_export.h" + +#include + +namespace qbs { + +class Version; + +class QBS_EXPORT LanguageInfo +{ + LanguageInfo() = delete; + +public: + static std::string qmlTypeInfo(); + static Version qbsVersion(); +}; + +} // namespace qbs + +#endif // QBS_LANGUAGEINFO_H diff --git a/src/lib/corelib/api/project.cpp b/src/lib/corelib/api/project.cpp new file mode 100644 index 00000000..f7a9eca0 --- /dev/null +++ b/src/lib/corelib/api/project.cpp @@ -0,0 +1,1160 @@ +/**************************************************************************** +** +** 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 "project.h" +#include "project_p.h" + +#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES +#include "projectfileupdater.h" +#endif + +#include "internaljobs.h" +#include "jobs.h" +#include "projectdata_p.h" +#include "propertymap_p.h" +#include "rulecommand_p.h" +#include "runenvironment.h" +#include "transformerdata_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +static bool pluginsLoaded = false; +static std::mutex pluginsLoadedMutex; + +static void loadPlugins(const QStringList &_pluginPaths, const Logger &logger) +{ + std::lock_guard locker(pluginsLoadedMutex); + if (pluginsLoaded) + return; + + std::vector pluginPaths; + for (const QString &pluginPath : _pluginPaths) { + if (!FileInfo::exists(pluginPath)) { +#ifndef QBS_STATIC_LIB + logger.qbsWarning() << Tr::tr("Plugin path '%1' does not exist.") + .arg(QDir::toNativeSeparators(pluginPath)); +#endif + } else { + pluginPaths.push_back(pluginPath.toStdString()); + } + } + auto pluginManager = QbsPluginManager::instance(); + pluginManager->loadStaticPlugins(); + pluginManager->loadPlugins(pluginPaths, logger); + + qRegisterMetaType("qbs::ErrorInfo"); + qRegisterMetaType("qbs::ProcessResult"); + qRegisterMetaType("Internal::InternalJob *"); + qRegisterMetaType("qbs::AbstractJob *"); + pluginsLoaded = true; +} + +ProjectData ProjectPrivate::projectData() +{ + m_projectData = ProjectData(); + retrieveProjectData(m_projectData, internalProject); + m_projectData.d->buildDir = internalProject->buildDirectory; + return m_projectData; +} + +static void addDependencies(QVector &products) +{ + for (int i = 0; i < products.size(); ++i) { + const ResolvedProductPtr &product = products.at(i); + for (const ResolvedProductPtr &dependency : qAsConst(product->dependencies)) { + if (!products.contains(dependency)) + products.push_back(dependency); + } + } +} + +BuildJob *ProjectPrivate::buildProducts(const QVector &products, + const BuildOptions &options, bool needsDepencencyResolving, + QObject *jobOwner) +{ + QVector productsToBuild = products; + if (needsDepencencyResolving) + addDependencies(productsToBuild); + + const auto job = new BuildJob(logger, jobOwner); + job->build(internalProject, productsToBuild, options); + QBS_ASSERT(job->state() == AbstractJob::StateRunning,); + return job; +} + +CleanJob *ProjectPrivate::cleanProducts(const QVector &products, + const CleanOptions &options, QObject *jobOwner) +{ + const auto job = new CleanJob(logger, jobOwner); + job->clean(internalProject, products, options); + QBS_ASSERT(job->state() == AbstractJob::StateRunning,); + return job; +} + +InstallJob *ProjectPrivate::installProducts(const QVector &products, + const InstallOptions &options, bool needsDepencencyResolving, QObject *jobOwner) +{ + QVector productsToInstall = products; + if (needsDepencencyResolving) + addDependencies(productsToInstall); + const auto job = new InstallJob(logger, jobOwner); + job->install(internalProject, productsToInstall, options); + QBS_ASSERT(job->state() == AbstractJob::StateRunning,); + return job; +} + +QVector ProjectPrivate::internalProducts(const QList &products) const +{ + QVector internalProducts; + for (const ProductData &product : products) { + if (product.isEnabled()) + internalProducts.push_back(internalProduct(product)); + } + return internalProducts; +} + +static QVector enabledInternalProducts(const ResolvedProjectConstPtr &project, + bool includingNonDefault) +{ + QVector products; + for (const ResolvedProductPtr &p : project->products) { + if (p->enabled && (includingNonDefault || p->builtByDefault())) + products.push_back(p); + } + for (const auto &subProject : qAsConst(project->subProjects)) + products << enabledInternalProducts(subProject, includingNonDefault); + return products; +} + +QVector ProjectPrivate::allEnabledInternalProducts( + bool includingNonDefault) const +{ + return enabledInternalProducts(internalProject, includingNonDefault); +} + +static bool matches(const ProductData &product, const ResolvedProductConstPtr &rproduct) +{ + return product.name() == rproduct->name + && product.multiplexConfigurationId() == rproduct->multiplexConfigurationId; +} + +static ResolvedProductPtr internalProductForProject(const ResolvedProjectConstPtr &project, + const ProductData &product) +{ + for (const ResolvedProductPtr &resolvedProduct : project->products) { + if (matches(product, resolvedProduct)) + return resolvedProduct; + } + for (const auto &subProject : qAsConst(project->subProjects)) { + const ResolvedProductPtr &p = internalProductForProject(subProject, product); + if (p) + return p; + } + return {}; +} + +ResolvedProductPtr ProjectPrivate::internalProduct(const ProductData &product) const +{ + return internalProductForProject(internalProject, product); +} + +ProductData ProjectPrivate::findProductData(const ProductData &product) const +{ + for (const ProductData &p : m_projectData.allProducts()) { + if (p.name() == product.name() + && p.profile() == product.profile() + && p.multiplexConfigurationId() == product.multiplexConfigurationId()) { + return p; + } + } + return {}; +} + +QList ProjectPrivate::findProductsByName(const QString &name) const +{ + QList list; + for (const ProductData &p : m_projectData.allProducts()) { + if (p.name() == name) + list.push_back(p); + } + return list; +} + +GroupData ProjectPrivate::findGroupData(const ProductData &product, const QString &groupName) const +{ + for (const GroupData &g : product.groups()) { + if (g.name() == groupName) + return g; + } + return {}; +} + +GroupData ProjectPrivate::createGroupDataFromGroup(const GroupPtr &resolvedGroup, + const ResolvedProductConstPtr &product) +{ + GroupData group; + group.d->name = resolvedGroup->name; + group.d->prefix = resolvedGroup->prefix; + group.d->location = resolvedGroup->location; + for (const auto &sa : resolvedGroup->files) { + ArtifactData artifact = createApiSourceArtifact(sa); + setupInstallData(artifact, product); + group.d->sourceArtifacts.push_back(artifact); + } + if (resolvedGroup->wildcards) { + for (const auto &sa : resolvedGroup->wildcards->files) { + ArtifactData artifact = createApiSourceArtifact(sa); + setupInstallData(artifact, product); + group.d->sourceArtifactsFromWildcards.push_back(artifact); + } + } + std::sort(group.d->sourceArtifacts.begin(), + group.d->sourceArtifacts.end()); + std::sort(group.d->sourceArtifactsFromWildcards.begin(), + group.d->sourceArtifactsFromWildcards.end()); + group.d->properties.d->m_map = resolvedGroup->properties; + group.d->isEnabled = resolvedGroup->enabled; + group.d->isValid = true; + return group; +} + +ArtifactData ProjectPrivate::createApiSourceArtifact(const SourceArtifactConstPtr &sa) +{ + ArtifactData saApi; + saApi.d->isValid = true; + saApi.d->filePath = sa->absoluteFilePath; + saApi.d->fileTags = sa->fileTags.toStringList(); + saApi.d->isGenerated = false; + saApi.d->isTargetArtifact = false; + saApi.d->properties.d->m_map = sa->properties; + return saApi; +} + +ArtifactData ProjectPrivate::createArtifactData(const Artifact *artifact, + const ResolvedProductConstPtr &product, const ArtifactSet &targetArtifacts) +{ + ArtifactData ta; + ta.d->filePath = artifact->filePath(); + ta.d->fileTags = artifact->fileTags().toStringList(); + ta.d->properties.d->m_map = artifact->properties; + ta.d->isGenerated = artifact->artifactType == Artifact::Generated; + ta.d->isTargetArtifact = targetArtifacts.contains(const_cast(artifact)); + ta.d->isValid = true; + setupInstallData(ta, product); + return ta; +} + +void ProjectPrivate::setupInstallData(ArtifactData &artifact, + const ResolvedProductConstPtr &product) +{ + artifact.d->installData.d->isValid = true; + artifact.d->installData.d->isInstallable = artifact.properties().getModuleProperty( + StringConstants::qbsModule(), StringConstants::installProperty()).toBool(); + if (!artifact.d->installData.d->isInstallable) + return; + const QString installRoot = artifact.properties().getModuleProperty( + StringConstants::qbsModule(), StringConstants::installRootProperty()).toString(); + InstallOptions options; + options.setInstallRoot(installRoot); + artifact.d->installData.d->installRoot = installRoot; + try { + QString installFilePath = ProductInstaller::targetFilePath(product->topLevelProject(), + product->sourceDirectory, artifact.filePath(), artifact.properties().d->m_map, + options); + if (!installRoot.isEmpty()) + installFilePath.remove(0, installRoot.size()); + artifact.d->installData.d->installFilePath = installFilePath; + } catch (const ErrorInfo &e) { + logger.printWarning(e); + } +} + +#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES +void ProjectPrivate::addGroup(const ProductData &product, const QString &groupName) +{ + if (groupName.isEmpty()) + throw ErrorInfo(Tr::tr("Group has an empty name.")); + if (!product.isValid()) + throw ErrorInfo(Tr::tr("Product is invalid.")); + QList products = findProductsByName(product.name()); + if (products.empty()) + throw ErrorInfo(Tr::tr("Product '%1' does not exist.").arg(product.name())); + const auto resolvedProducts = internalProducts(products); + QBS_CHECK(products.size() == resolvedProducts.size()); + + for (const GroupPtr &resolvedGroup : resolvedProducts.front()->groups) { + if (resolvedGroup->name == groupName) { + throw ErrorInfo(Tr::tr("Group '%1' already exists in product '%2'.") + .arg(groupName, product.name()), resolvedGroup->location); + } + } + + ProjectFileGroupInserter groupInserter(products.front(), groupName); + groupInserter.apply(); +} + +ProjectPrivate::GroupUpdateContext ProjectPrivate::getGroupContext(const ProductData &product, + const GroupData &group) +{ + GroupUpdateContext context; + if (!product.isValid()) + throw ErrorInfo(Tr::tr("Product is invalid.")); + context.products = findProductsByName(product.name()); + if (context.products.empty()) + throw ErrorInfo(Tr::tr("Product '%1' does not exist.").arg(product.name())); + context.resolvedProducts = internalProducts(context.products); + + const QString groupName = group.isValid() ? group.name() : product.name(); + for (const ResolvedProductPtr &p : qAsConst(context.resolvedProducts)) { + for (const GroupPtr &g : p->groups) { + if (g->name == groupName) { + context.resolvedGroups << g; + break; + } + } + } + if (context.resolvedGroups.empty()) + throw ErrorInfo(Tr::tr("Group '%1' does not exist.").arg(groupName)); + for (const ProductData &p : qAsConst(context.products)) { + const GroupData &g = findGroupData(p, groupName); + QBS_CHECK(p.isValid()); + context.groups << g; + } + QBS_CHECK(context.resolvedProducts.size() == context.products.size()); + QBS_CHECK(context.resolvedProducts.size() == context.resolvedGroups.size()); + QBS_CHECK(context.products.size() == context.groups.size()); + return context; +} + +static bool matchesWildcard(const QString &filePath, const GroupConstPtr &group) +{ + if (!group->wildcards) + return false; + for (const QString &pattern : qAsConst(group->wildcards->patterns)) { + QString fullPattern; + if (QFileInfo(group->prefix).isAbsolute()) { + fullPattern = group->prefix; + } else { + fullPattern = QFileInfo(group->location.filePath()).absolutePath() + + QLatin1Char('/') + group->prefix; + } + fullPattern.append(QLatin1Char('/')).append(pattern); + fullPattern = QDir::cleanPath(fullPattern); + if (QRegExp(fullPattern, Qt::CaseSensitive, QRegExp::Wildcard).exactMatch(filePath)) + return true; + } + return false; +} + +ProjectPrivate::FileListUpdateContext ProjectPrivate::getFileListContext(const ProductData &product, + const GroupData &group, const QStringList &filePaths, bool forAdding) +{ + FileListUpdateContext filesContext; + GroupUpdateContext &groupContext = filesContext.groupContext; + groupContext = getGroupContext(product, group); + + if (filePaths.empty()) + throw ErrorInfo(Tr::tr("No files supplied.")); + + QString prefix; + for (int i = 0; i < groupContext.resolvedGroups.size(); ++i) { + const GroupPtr &g = groupContext.resolvedGroups.at(i); + if (!g->prefix.isEmpty() && !g->prefix.endsWith(QLatin1Char('/'))) + throw ErrorInfo(Tr::tr("Group has non-directory prefix.")); + if (i == 0) + prefix = g->prefix; + else if (prefix != g->prefix) + throw ErrorInfo(Tr::tr("Cannot update: Group prefix depends on properties.")); + } + QString baseDirPath = QFileInfo(product.location().filePath()).dir().absolutePath() + + QLatin1Char('/') + prefix; + QDir baseDir(baseDirPath); + for (const QString &filePath : filePaths) { + const QString absPath = QDir::cleanPath(FileInfo::resolvePath(baseDirPath, filePath)); + if (filesContext.absoluteFilePaths.contains(absPath)) + throw ErrorInfo(Tr::tr("File '%1' appears more than once.").arg(absPath)); + if (forAdding && !FileInfo(absPath).exists()) + throw ErrorInfo(Tr::tr("File '%1' does not exist.").arg(absPath)); + if (matchesWildcard(absPath, groupContext.resolvedGroups.front())) { + filesContext.absoluteFilePathsFromWildcards << absPath; + } else { + filesContext.absoluteFilePaths << absPath; + filesContext.relativeFilePaths << baseDir.relativeFilePath(absPath); + } + } + + return filesContext; +} + +void ProjectPrivate::addFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths) +{ + FileListUpdateContext filesContext = getFileListContext(product, group, filePaths, true); + GroupUpdateContext &groupContext = filesContext.groupContext; + + // We do not check for entries in other groups, because such doublettes might be legitimate + // due to conditions. + for (const GroupPtr &group : qAsConst(groupContext.resolvedGroups)) { + for (const QString &filePath : qAsConst(filesContext.absoluteFilePaths)) { + for (const auto &sa : group->files) { + if (sa->absoluteFilePath == filePath) { + throw ErrorInfo(Tr::tr("File '%1' already exists in group '%2'.") + .arg(filePath, group->name)); + } + } + } + } + + ProjectFileFilesAdder adder(groupContext.products.front(), + group.isValid() ? groupContext.groups.front() : GroupData(), + filesContext.relativeFilePaths); + adder.apply(); +} + +void ProjectPrivate::removeFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths) +{ + FileListUpdateContext filesContext = getFileListContext(product, group, filePaths, false); + GroupUpdateContext &groupContext = filesContext.groupContext; + + if (!filesContext.absoluteFilePathsFromWildcards.empty()) { + throw ErrorInfo(Tr::tr("The following files cannot be removed from the project file, " + "because they match wildcard patterns: %1") + .arg(filesContext.absoluteFilePathsFromWildcards.join(QLatin1String(", ")))); + } + QStringList filesNotFound = filesContext.absoluteFilePaths; + std::vector sourceArtifacts; + for (const SourceArtifactPtr &sa : groupContext.resolvedGroups.front()->files) { + if (filesNotFound.removeOne(sa->absoluteFilePath)) + sourceArtifacts << sa; + } + if (!filesNotFound.empty()) { + throw ErrorInfo(Tr::tr("The following files are not known to qbs: %1") + .arg(filesNotFound.join(QLatin1String(", ")))); + } + + ProjectFileFilesRemover remover(groupContext.products.front(), + group.isValid() ? groupContext.groups.front() : GroupData(), + filesContext.relativeFilePaths); + remover.apply(); +} + +void ProjectPrivate::removeGroup(const ProductData &product, const GroupData &group) +{ + GroupUpdateContext context = getGroupContext(product, group); + ProjectFileGroupRemover remover(context.products.front(), context.groups.front()); + remover.apply(); + +} +#endif // QBS_ENABLE_PROJECT_FILE_UPDATES + +void ProjectPrivate::prepareChangeToProject() +{ + if (internalProject->locked) + throw ErrorInfo(Tr::tr("A job is currently in progress.")); + if (!m_projectData.isValid()) + retrieveProjectData(m_projectData, internalProject); +} + +RuleCommandList ProjectPrivate::ruleCommandListForTransformer(const Transformer *transformer) +{ + RuleCommandList list; + for (const AbstractCommandPtr &internalCommand : qAsConst(transformer->commands.commands())) { + RuleCommand externalCommand; + externalCommand.d->description = internalCommand->description(); + externalCommand.d->extendedDescription = internalCommand->extendedDescription(); + switch (internalCommand->type()) { + case AbstractCommand::JavaScriptCommandType: { + externalCommand.d->type = RuleCommand::JavaScriptCommandType; + const JavaScriptCommandPtr &jsCmd + = std::static_pointer_cast(internalCommand); + externalCommand.d->sourceCode = jsCmd->sourceCode(); + break; + } + case AbstractCommand::ProcessCommandType: { + externalCommand.d->type = RuleCommand::ProcessCommandType; + const ProcessCommandPtr &procCmd + = std::static_pointer_cast(internalCommand); + externalCommand.d->executable = procCmd->program(); + externalCommand.d->arguments = procCmd->arguments(); + externalCommand.d->workingDir = procCmd->workingDir(); + externalCommand.d->environment = procCmd->environment(); + break; + } + } + list << externalCommand; + } + return list; +} + +RuleCommandList ProjectPrivate::ruleCommands(const ProductData &product, + const QString &inputFilePath, const QString &outputFileTag) +{ + if (internalProject->locked) + throw ErrorInfo(Tr::tr("A job is currently in progress.")); + const ResolvedProductConstPtr resolvedProduct = internalProduct(product); + if (!resolvedProduct) + throw ErrorInfo(Tr::tr("No such product '%1'.").arg(product.name())); + if (!resolvedProduct->enabled) + throw ErrorInfo(Tr::tr("Product '%1' is disabled.").arg(product.name())); + QBS_CHECK(resolvedProduct->buildData); + const ArtifactSet &outputArtifacts = resolvedProduct->buildData->artifactsByFileTag() + .value(FileTag(outputFileTag.toLocal8Bit())); + for (const Artifact * const outputArtifact : qAsConst(outputArtifacts)) { + const TransformerConstPtr transformer = outputArtifact->transformer; + if (!transformer) + continue; + for (const Artifact * const inputArtifact : qAsConst(transformer->inputs)) { + if (inputArtifact->filePath() == inputFilePath) + return ruleCommandListForTransformer(transformer.get()); + } + } + + throw ErrorInfo(Tr::tr("No rule was found that produces an artifact tagged '%1' " + "from input file '%2'.").arg(outputFileTag, inputFilePath)); +} + +ProjectTransformerData ProjectPrivate::transformerData() +{ + if (!m_projectData.isValid()) + retrieveProjectData(m_projectData, internalProject); + ProjectTransformerData projectTransformerData; + for (const ProductData &productData : m_projectData.allProducts()) { + if (!productData.isEnabled()) + continue; + const ResolvedProductConstPtr product = internalProduct(productData); + QBS_ASSERT(!!product, continue); + QBS_ASSERT(!!product->buildData, continue); + const ArtifactSet targetArtifacts = product->targetArtifacts(); + Set allTransformers; + for (const Artifact * const a : TypeFilter(product->buildData->allNodes())) { + if (a->artifactType == Artifact::Generated) + allTransformers.insert(a->transformer.get()); + } + if (allTransformers.empty()) + continue; + ProductTransformerData productTransformerData; + for (const Transformer * const t : allTransformers) { + TransformerData tData; + Set allInputs; + for (Artifact * const a : t->outputs) { + tData.d->outputs << createArtifactData(a, product, targetArtifacts); + for (const Artifact * const child : filterByType(a->children)) + allInputs << child; + for (Artifact * const a + : RulesApplicator::collectAuxiliaryInputs(t->rule.get(), product.get())) { + if (a->artifactType == Artifact::Generated) + tData.d->inputs << createArtifactData(a, product, targetArtifacts); + } + } + for (const Artifact * const input : allInputs) + tData.d->inputs << createArtifactData(input, product, targetArtifacts); + tData.d->commands = ruleCommandListForTransformer(t); + productTransformerData << tData; + } + projectTransformerData << qMakePair(productData, productTransformerData); + } + return projectTransformerData; +} + +static bool productIsRunnable(const ResolvedProductConstPtr &product) +{ + const bool isBundle = product->moduleProperties->moduleProperty( + QStringLiteral("bundle"), QStringLiteral("isBundle")).toBool(); + const QString androidSdkPackageType = product->moduleProperties->moduleProperty( + QStringLiteral("Android.sdk"), QStringLiteral("packageType")).toString(); + const bool isAndroidApk = androidSdkPackageType == QStringLiteral("apk"); + return isRunnableArtifact(product->fileTags, isBundle, isAndroidApk); +} + +static bool productIsMultiplexed(const ResolvedProductConstPtr &product) +{ + return product->productProperties.value(StringConstants::multiplexedProperty()).toBool(); +} + +void ProjectPrivate::retrieveProjectData(ProjectData &projectData, + const ResolvedProjectConstPtr &internalProject) +{ + projectData.d->name = internalProject->name; + projectData.d->location = internalProject->location; + projectData.d->enabled = internalProject->enabled; + for (const auto &resolvedProduct : internalProject->products) { + ProductData product; + product.d->type = resolvedProduct->fileTags.toStringList(); + product.d->name = resolvedProduct->name; + product.d->targetName = resolvedProduct->targetName; + product.d->version = resolvedProduct + ->productProperties.value(StringConstants::versionProperty()).toString(); + product.d->multiplexConfigurationId = resolvedProduct->multiplexConfigurationId; + product.d->location = resolvedProduct->location; + product.d->buildDirectory = resolvedProduct->buildDirectory(); + product.d->isEnabled = resolvedProduct->enabled; + product.d->isRunnable = productIsRunnable(resolvedProduct); + product.d->isMultiplexed = productIsMultiplexed(resolvedProduct); + product.d->properties = resolvedProduct->productProperties; + product.d->moduleProperties.d->m_map = resolvedProduct->moduleProperties; + for (const GroupPtr &resolvedGroup : resolvedProduct->groups) { + if (resolvedGroup->targetOfModule.isEmpty()) + product.d->groups << createGroupDataFromGroup(resolvedGroup, resolvedProduct); + } + if (resolvedProduct->enabled) { + QBS_CHECK(resolvedProduct->buildData); + const ArtifactSet targetArtifacts = resolvedProduct->targetArtifacts(); + for (Artifact * const a + : filterByType(resolvedProduct->buildData->allNodes())) { + if (a->artifactType != Artifact::Generated) + continue; + product.d->generatedArtifacts << createArtifactData(a, resolvedProduct, + targetArtifacts); + } + const AllRescuableArtifactData &rad + = resolvedProduct->buildData->rescuableArtifactData(); + for (auto it = rad.begin(); it != rad.end(); ++it) { + ArtifactData ta; + ta.d->filePath = it.key(); + ta.d->fileTags = it.value().fileTags.toStringList(); + ta.d->properties.d->m_map = it.value().properties; + ta.d->isGenerated = true; + ta.d->isTargetArtifact = resolvedProduct->fileTags.intersects(it.value().fileTags); + ta.d->isValid = true; + setupInstallData(ta, resolvedProduct); + product.d->generatedArtifacts << ta; + } + } + for (const ResolvedProductPtr &resolvedDependentProduct + : qAsConst(resolvedProduct->dependencies)) { + product.d->dependencies << resolvedDependentProduct->name; // FIXME: Shouldn't this be a unique name? + } + std::sort(product.d->type.begin(), product.d->type.end()); + std::sort(product.d->groups.begin(), product.d->groups.end()); + std::sort(product.d->generatedArtifacts.begin(), product.d->generatedArtifacts.end()); + product.d->isValid = true; + projectData.d->products << product; + } + for (const auto &internalSubProject : qAsConst(internalProject->subProjects)) { + if (!internalSubProject->enabled) + continue; + ProjectData subProject; + retrieveProjectData(subProject, internalSubProject); + projectData.d->subProjects << subProject; + } + projectData.d->isValid = true; + std::sort(projectData.d->products.begin(), projectData.d->products.end()); + std::sort(projectData.d->subProjects.begin(), projectData.d->subProjects.end()); +} + +} // namespace Internal + +using namespace Internal; + + /*! + * \class Project + * \brief The \c Project class provides services related to a qbs project. + */ + +Project::Project(const TopLevelProjectPtr &internalProject, const Logger &logger) + : d(new ProjectPrivate(internalProject, logger)) +{ +} + +Project::Project(const Project &other) = default; + +Project::~Project() = default; + +/*! + * \brief Returns true if and only if this object was retrieved from a successful \c SetupProjectJob. + * \sa SetupProjectJob + */ +bool Project::isValid() const +{ + return d && d->internalProject; +} + +/*! + * \brief The top-level profile for building this project. + */ +QString Project::profile() const +{ + QBS_ASSERT(isValid(), return {}); + return d->internalProject->profile(); +} + +Project &Project::operator=(const Project &other) = default; + +/*! + * \brief Sets up a \c Project from a source file, possibly re-using previously stored information. + * The function will finish immediately, returning a \c SetupProjectJob which can be used to + * track the results of the operation. + * If the function is called on a valid \c Project object, the build graph will not be loaded + * from a file, but will be taken from the existing project. In that case, if resolving + * finishes successfully, the existing project will be invalidated. If resolving fails, qbs will + * try to keep the existing project valid. However, under certain circumstances, resolving the new + * project will fail at a time where existing project data has already been touched, in which case + * the existing project has to be invalidated (this could be avoided, but it would hurt performance). + * So after an unsuccessful re-resolve job, the existing project may or may not be valid anymore. + * \note The qbs plugins will only be loaded once. As a result, the value of + * \c parameters.pluginPaths will only have an effect the first time this function is called. + * Similarly, the value of \c parameters.searchPaths will not have an effect if + * a stored build graph is available. + */ +SetupProjectJob *Project::setupProject(const SetupProjectParameters ¶meters, + ILogSink *logSink, QObject *jobOwner) +{ + Logger logger(logSink); + const auto job = new SetupProjectJob(logger, jobOwner); + try { + loadPlugins(parameters.pluginPaths(), logger); + job->resolve(*this, parameters); + QBS_ASSERT(job->state() == AbstractJob::StateRunning,); + } catch (const ErrorInfo &error) { + // Throwing from here would complicate the API, so let's report the error the same way + // as all others, via AbstractJob::error(). + job->reportError(error); + } + return job; +} + +Project::Project() = default; + +/*! + * \brief Retrieves information for this project. + * Call this function if you need insight into the project structure, e.g. because you want to know + * which products or files are in it. + */ +ProjectData Project::projectData() const +{ + QBS_ASSERT(isValid(), return {}); + return d->projectData(); +} + +RunEnvironment Project::getRunEnvironment(const ProductData &product, + const InstallOptions &installOptions, + const QProcessEnvironment &environment, + const QStringList &setupRunEnvConfig, Settings *settings) const +{ + const ResolvedProductPtr resolvedProduct = d->internalProduct(product); + return RunEnvironment(resolvedProduct, d->internalProject, installOptions, environment, + setupRunEnvConfig, settings, d->logger); +} + +/*! + * \enum Project::ProductSelection + * This enum type specifies which products to include if "all" products are to be built. + * \value Project::ProdProductSelectionDefaultOnly Indicates that only those products should be + * built whose \c builtByDefault property + * is \c true. + * \value Project::ProdProductSelectionWithNonDefault Indicates that products whose + * \c builtByDefault property is \c false should + * also be built. + */ + +/*! + * \brief Causes all products of this project to be built, if necessary. + * If and only if \c producSelection is \c Project::ProductSelectionWithNonDefault, products with + * the \c builtByDefault property set to \c false will be built too. + * The function will finish immediately, returning a \c BuildJob identifiying the operation. + */ +BuildJob *Project::buildAllProducts(const BuildOptions &options, ProductSelection productSelection, + QObject *jobOwner) const +{ + QBS_ASSERT(isValid(), return nullptr); + const bool includingNonDefault = productSelection == ProductSelectionWithNonDefault; + return d->buildProducts(d->allEnabledInternalProducts(includingNonDefault), options, + !includingNonDefault, jobOwner); +} + +/*! + * \brief Causes the specified list of products to be built. + * Use this function if you only want to build some products, not the whole project. If any of + * the products in \a products depend on other products, those will also be built. + * The function will finish immediately, returning a \c BuildJob identifiying the operation. + */ +BuildJob *Project::buildSomeProducts(const QList &products, + const BuildOptions &options, QObject *jobOwner) const +{ + QBS_ASSERT(isValid(), return nullptr); + return d->buildProducts(d->internalProducts(products), options, true, jobOwner); +} + +/*! + * \brief Convenience function for \c buildSomeProducts(). + * \sa Project::buildSomeProducts(). + */ +BuildJob *Project::buildOneProduct(const ProductData &product, const BuildOptions &options, + QObject *jobOwner) const +{ + return buildSomeProducts(QList() << product, options, jobOwner); +} + +/*! + * \brief Removes the build artifacts of all products in the project. + * The function will finish immediately, returning a \c CleanJob identifiying this operation. + * \sa Project::cleanSomeProducts() + */ +CleanJob *Project::cleanAllProducts(const CleanOptions &options, QObject *jobOwner) const +{ + QBS_ASSERT(isValid(), return nullptr); + return d->cleanProducts(d->allEnabledInternalProducts(true), options, jobOwner); +} + +/*! + * \brief Removes the build artifacts of the given products. + * The function will finish immediately, returning a \c CleanJob identifiying this operation. + */ +CleanJob *Project::cleanSomeProducts(const QList &products, + const CleanOptions &options, QObject *jobOwner) const +{ + QBS_ASSERT(isValid(), return nullptr); + return d->cleanProducts(d->internalProducts(products), options, jobOwner); +} + +/*! + * \brief Convenience function for \c cleanSomeProducts(). + * \sa Project::cleanSomeProducts(). + */ +CleanJob *Project::cleanOneProduct(const ProductData &product, const CleanOptions &options, + QObject *jobOwner) const +{ + return cleanSomeProducts(QList() << product, options, jobOwner); +} + +/*! + * \brief Installs the installable files of all products in the project. + * If and only if \c producSelection is \c Project::ProductSelectionWithNonDefault, products with + * the \c builtByDefault property set to \c false will be installed too. + * The function will finish immediately, returning an \c InstallJob identifiying this operation. + */ +InstallJob *Project::installAllProducts(const InstallOptions &options, + ProductSelection productSelection, QObject *jobOwner) const +{ + QBS_ASSERT(isValid(), return nullptr); + const bool includingNonDefault = productSelection == ProductSelectionWithNonDefault; + return d->installProducts(d->allEnabledInternalProducts(includingNonDefault), options, + !includingNonDefault, jobOwner); +} + +/*! + * \brief Installs the installable files of the given products. + * The function will finish immediately, returning an \c InstallJob identifiying this operation. + */ +InstallJob *Project::installSomeProducts(const QList &products, + const InstallOptions &options, QObject *jobOwner) const +{ + QBS_ASSERT(isValid(), return nullptr); + return d->installProducts(d->internalProducts(products), options, true, jobOwner); +} + +/*! + * \brief Convenience function for \c installSomeProducts(). + * \sa Project::installSomeProducts(). + */ +InstallJob *Project::installOneProduct(const ProductData &product, const InstallOptions &options, + QObject *jobOwner) const +{ + return installSomeProducts(QList() << product, options, jobOwner); +} + +/*! + * \brief Updates the timestamps of all build artifacts in the given products. + * Afterwards, the build graph will have the same state as if a successful build had been done. + */ +void Project::updateTimestamps(const QList &products) +{ + QBS_ASSERT(isValid(), return); + TimestampsUpdater().updateTimestamps(d->internalProject, d->internalProducts(products), + d->logger); +} + +/*! + * \brief Finds files generated from the given file in the given product. + * If \a recursive is \c false, only files generated directly from \a file will be considered, + * otherwise the generated files are collected recursively. + * If \a tags is not empty, only generated files matching at least one of these tags will + * be considered. + */ +QStringList Project::generatedFiles(const ProductData &product, const QString &file, + bool recursive, const QStringList &tags) const +{ + QBS_ASSERT(isValid(), return {}); + const ResolvedProductConstPtr internalProduct = d->internalProduct(product); + return internalProduct->generatedFiles(file, recursive, FileTags::fromStringList(tags)); +} + +QVariantMap Project::projectConfiguration() const +{ + QBS_ASSERT(isValid(), return {}); + return d->internalProject->buildConfiguration(); +} + +std::set Project::buildSystemFiles() const +{ + QBS_ASSERT(isValid(), return {}); + return d->internalProject->buildSystemFiles.toStdSet(); +} + +RuleCommandList Project::ruleCommands(const ProductData &product, + const QString &inputFilePath, const QString &outputFileTag, ErrorInfo *error) const +{ + QBS_ASSERT(isValid(), return {}); + QBS_ASSERT(product.isValid(), return {}); + + try { + return d->ruleCommands(product, inputFilePath, outputFileTag); + } catch (const ErrorInfo &e) { + if (error) + *error = e; + return {}; + } +} + +ProjectTransformerData Project::transformerData(ErrorInfo *error) const +{ + QBS_ASSERT(isValid(), return {}); + try { + return d->transformerData(); + } catch (const ErrorInfo &e) { + if (error) + *error = e; + return {}; + } +} + +ErrorInfo Project::dumpNodesTree(QIODevice &outDevice, const QList &products) +{ + try { + NodeTreeDumper(outDevice).start(d->internalProducts(products)); + } catch (const ErrorInfo &e) { + return e; + } + return {}; +} + +Project::BuildGraphInfo Project::getBuildGraphInfo(const QString &bgFilePath, + const QStringList &requestedProperties) +{ + BuildGraphInfo info; + try { + const Internal::TopLevelProjectConstPtr project = BuildGraphLoader::loadProject(bgFilePath); + info.bgFilePath = bgFilePath; + info.overriddenProperties = project->overriddenValues; + info.profileData = project->profileConfigs; + std::vector> props; + for (const QString &prop : requestedProperties) { + QStringList components = prop.split(QLatin1Char('.')); + const QString propName = components.takeLast(); + props.emplace_back(components.join(QLatin1Char('.')), propName); + } + for (const auto &product : project->allProducts()) { + if (props.empty()) + break; + if (product->profile() != project->profile()) + continue; + for (auto it = props.begin(); it != props.end();) { + const QVariant value + = product->moduleProperties->moduleProperty(it->first, it->second); + if (value.isValid()) { + info.requestedProperties.insert(it->first + QLatin1Char('.') + it->second, + value); + it = props.erase(it); + } else { + ++it; + } + } + } + } catch (const ErrorInfo &e) { + info.error = e; + } + return info; +} + +Project::BuildGraphInfo Project::getBuildGraphInfo() const +{ + QBS_ASSERT(isValid(), return {}); + BuildGraphInfo info; + try { + if (d->internalProject->locked) + throw ErrorInfo(Tr::tr("A job is currently in progress.")); + info.bgFilePath = d->internalProject->buildGraphFilePath(); + info.overriddenProperties = d->internalProject->overriddenValues; + info.profileData = d->internalProject->profileConfigs; + } catch (const ErrorInfo &e) { + info.error = e; + } + return info; +} + +#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES +/*! + * \brief Adds a new empty group to the given product. + * Returns an \c ErrorInfo object for which \c hasError() is false in case of a success + * and true otherwise. In the latter case, the object will have a sensible description. + * After calling this function, it is recommended to re-fetch the project data, as other + * items can be affected. + * \sa qbs::Project::projectData() + */ +ErrorInfo Project::addGroup(const ProductData &product, const QString &groupName) +{ + try { + QBS_CHECK(isValid()); + d->prepareChangeToProject(); + d->addGroup(product, groupName); + d->internalProject->store(d->logger); + return {}; + } catch (const ErrorInfo &exception) { + auto errorInfo = exception; + errorInfo.prepend(Tr::tr("Failure adding group '%1' to product '%2'.") + .arg(groupName, product.name())); + return errorInfo; + } +} + +/*! + * \brief Adds the given files to the given product. + * If \c group is a default-constructed object, the files will be added to the product's + * "files" property, otherwise to the one of \c group. + * The file paths can be absolute or relative to the location of \c product (including a possible + * prefix in the group). The project file will always contain relative paths. + * After calling this function, it is recommended to re-fetch the project data, as other + * items can be affected. + * \sa qbs::Project::projectData() + */ +ErrorInfo Project::addFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths) +{ + try { + QBS_CHECK(isValid()); + d->prepareChangeToProject(); + d->addFiles(product, group, filePaths); + d->internalProject->store(d->logger); + return {}; + } catch (const ErrorInfo &exception) { + auto errorInfo = exception; + errorInfo.prepend(Tr::tr("Failure adding files to product.")); + return errorInfo; + } +} + +/*! + * \brief Removes the given files from the given product. + * If \c group is a default-constructed object, the files will be removed from the product's + * "files" property, otherwise from the one of \c group. + * The file paths can be absolute or relative to the location of \c product (including a possible + * prefix in the group). + * After calling this function, it is recommended to re-fetch the project data, as other + * items can be affected. + * \sa qbs::Project::projectData() + */ +ErrorInfo Project::removeFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths) +{ + try { + QBS_CHECK(isValid()); + d->prepareChangeToProject(); + d->removeFiles(product, group, filePaths); + d->internalProject->store(d->logger); + return {}; + } catch (const ErrorInfo &exception) { + auto errorInfo = exception; + errorInfo.prepend(Tr::tr("Failure removing files from product '%1'.").arg(product.name())); + return errorInfo; + } +} + +/*! + * \brief Removes the given group from the given product. + * After calling this function, it is recommended to re-fetch the project data, as other + * items can be affected. + * \sa qbs::Project::projectData() + */ +ErrorInfo Project::removeGroup(const ProductData &product, const GroupData &group) +{ + try { + QBS_CHECK(isValid()); + d->prepareChangeToProject(); + d->removeGroup(product, group); + d->internalProject->store(d->logger); + return {}; + } catch (const ErrorInfo &exception) { + auto errorInfo = exception; + errorInfo.prepend(Tr::tr("Failure removing group '%1' from product '%2'.") + .arg(group.name(), product.name())); + return errorInfo; + } +} +#endif // QBS_ENABLE_PROJECT_FILE_UPDATES + +} // namespace qbs diff --git a/src/lib/corelib/api/project.h b/src/lib/corelib/api/project.h new file mode 100644 index 00000000..9000d654 --- /dev/null +++ b/src/lib/corelib/api/project.h @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** 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 QBS_PROJECT_H +#define QBS_PROJECT_H + +#include "rulecommand.h" +#include "transformerdata.h" +#include "../language/forward_decls.h" +#include "../tools/error.h" +#include "../tools/qbs_export.h" + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE +class QIODevice; +class QObject; +class QProcessEnvironment; +QT_END_NAMESPACE + +namespace qbs { +class BuildJob; +class BuildOptions; +class CleanJob; +class CleanOptions; +class GroupData; +class ILogSink; +class InstallJob; +class InstallOptions; +class ProductData; +class ProjectData; +class RunEnvironment; +class Settings; +class SetupProjectJob; +class SetupProjectParameters; + +namespace Internal { +class Logger; +class ProjectPrivate; +} // namespace Internal; + +class QBS_EXPORT Project +{ + friend class SetupProjectJob; + friend uint qHash(const Project &p); +public: + SetupProjectJob *setupProject(const SetupProjectParameters ¶meters, + ILogSink *logSink, QObject *jobOwner); + + Project(); + Project(const Project &other); + Project &operator=(const Project &other); + ~Project(); + + bool isValid() const; + QString profile() const; + ProjectData projectData() const; + RunEnvironment getRunEnvironment(const ProductData &product, + const InstallOptions &installOptions, + const QProcessEnvironment &environment, + const QStringList &setupRunEnvConfig, Settings *settings) const; + + enum ProductSelection { ProductSelectionDefaultOnly, ProductSelectionWithNonDefault }; + BuildJob *buildAllProducts(const BuildOptions &options, + ProductSelection productSelection = ProductSelectionDefaultOnly, + QObject *jobOwner = nullptr) const; + BuildJob *buildSomeProducts(const QList &products, const BuildOptions &options, + QObject *jobOwner = nullptr) const; + BuildJob *buildOneProduct(const ProductData &product, const BuildOptions &options, + QObject *jobOwner = nullptr) const; + + CleanJob *cleanAllProducts(const CleanOptions &options, QObject *jobOwner = nullptr) const; + CleanJob *cleanSomeProducts(const QList &products, const CleanOptions &options, + QObject *jobOwner = nullptr) const; + CleanJob *cleanOneProduct(const ProductData &product, const CleanOptions &options, + QObject *jobOwner = nullptr) const; + + InstallJob *installAllProducts(const InstallOptions &options, + ProductSelection productSelection = ProductSelectionDefaultOnly, + QObject *jobOwner = nullptr) const; + InstallJob *installSomeProducts(const QList &products, + const InstallOptions &options, + QObject *jobOwner = nullptr) const; + InstallJob *installOneProduct(const ProductData &product, const InstallOptions &options, + QObject *jobOwner = nullptr) const; + + void updateTimestamps(const QList &products); + + bool operator==(const Project &other) const { return d.data() == other.d.data(); } + + QStringList generatedFiles(const ProductData &product, const QString &file, + bool recursive, const QStringList &tags = QStringList()) const; + + QVariantMap projectConfiguration() const; + + std::set buildSystemFiles() const; + + RuleCommandList ruleCommands(const ProductData &product, const QString &inputFilePath, + const QString &outputFileTag, ErrorInfo *error = nullptr) const; + ProjectTransformerData transformerData(ErrorInfo *error = nullptr) const; + + ErrorInfo dumpNodesTree(QIODevice &outDevice, const QList &products); + + + class BuildGraphInfo + { + public: + QString bgFilePath; + QVariantMap overriddenProperties; + QVariantMap profileData; + QVariantMap requestedProperties; + ErrorInfo error; + }; + static BuildGraphInfo getBuildGraphInfo(const QString &bgFilePath, + const QStringList &requestedProperties); + + // Use with loaded project. Does not set requestedProperties. + BuildGraphInfo getBuildGraphInfo() const; + + +#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES + ErrorInfo addGroup(const ProductData &product, const QString &groupName); + ErrorInfo addFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths); + ErrorInfo removeFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths); + ErrorInfo removeGroup(const ProductData &product, const GroupData &group); +#endif // QBS_ENABLE_PROJECT_FILE_UPDATES + +private: + Project(const Internal::TopLevelProjectPtr &internalProject, const Internal::Logger &logger); + + QExplicitlySharedDataPointer d; +}; + +inline bool operator!=(const Project &p1, const Project &p2) { return !(p1 == p2); } +inline uint qHash(const Project &p) { return QT_PREPEND_NAMESPACE(qHash)(p.d.data()); } + +} // namespace qbs + +#endif // QBS_PROJECT_H diff --git a/src/lib/corelib/api/project_p.h b/src/lib/corelib/api/project_p.h new file mode 100644 index 00000000..60c8311f --- /dev/null +++ b/src/lib/corelib/api/project_p.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** 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 QBS_PROJECT_P_H +#define QBS_PROJECT_P_H + +#include "projectdata.h" +#include "rulecommand.h" +#include "transformerdata.h" + +#include +#include + +#include +#include + +namespace qbs { +class BuildJob; +class BuildOptions; +class CleanJob; +class CleanOptions; +class InstallJob; +class InstallOptions; + +namespace Internal { + +class ProjectPrivate : public QSharedData +{ +public: + ProjectPrivate(TopLevelProjectPtr internalProject, Logger logger) + : internalProject(std::move(internalProject)), logger(std::move(logger)) + { + } + + ProjectData projectData(); + BuildJob *buildProducts(const QVector &products, const BuildOptions &options, + bool needsDepencencyResolving, + QObject *jobOwner); + CleanJob *cleanProducts(const QVector &products, const CleanOptions &options, + QObject *jobOwner); + InstallJob *installProducts(const QVector &products, + const InstallOptions &options, bool needsDepencencyResolving, + QObject *jobOwner); + QVector internalProducts(const QList &products) const; + QVector allEnabledInternalProducts(bool includingNonDefault) const; + ResolvedProductPtr internalProduct(const ProductData &product) const; + ProductData findProductData(const ProductData &product) const; + QList findProductsByName(const QString &name) const; + GroupData findGroupData(const ProductData &product, const QString &groupName) const; + + GroupData createGroupDataFromGroup(const GroupPtr &resolvedGroup, + const ResolvedProductConstPtr &product); + ArtifactData createApiSourceArtifact(const SourceArtifactConstPtr &sa); + ArtifactData createArtifactData(const Artifact *artifact, + const ResolvedProductConstPtr &product, + const ArtifactSet &targetArtifacts); + void setupInstallData(ArtifactData &artifact, const ResolvedProductConstPtr &product); + + struct GroupUpdateContext { + QVector resolvedProducts; + QList resolvedGroups; + QList products; + QList groups; + }; + + struct FileListUpdateContext { + GroupUpdateContext groupContext; + QStringList absoluteFilePaths; + QStringList relativeFilePaths; + QStringList absoluteFilePathsFromWildcards; // Not included in the other two lists. + }; + + GroupUpdateContext getGroupContext(const ProductData &product, const GroupData &group); + FileListUpdateContext getFileListContext(const ProductData &product, const GroupData &group, + const QStringList &filePaths, bool forAdding); + + void addGroup(const ProductData &product, const QString &groupName); + void addFiles(const ProductData &product, const GroupData &group, const QStringList &filePaths); + void removeFiles(const ProductData &product, const GroupData &group, + const QStringList &filePaths); + void removeGroup(const ProductData &product, const GroupData &group); + + void prepareChangeToProject(); + + RuleCommandList ruleCommandListForTransformer(const Transformer *transformer); + RuleCommandList ruleCommands(const ProductData &product, + const QString &inputFilePath, const QString &outputFileTag); + ProjectTransformerData transformerData(); + + TopLevelProjectPtr internalProject; + Logger logger; + +private: + void retrieveProjectData(ProjectData &projectData, + const ResolvedProjectConstPtr &internalProject); + + ProjectData m_projectData; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/api/projectdata.cpp b/src/lib/corelib/api/projectdata.cpp new file mode 100644 index 00000000..c378fbea --- /dev/null +++ b/src/lib/corelib/api/projectdata.cpp @@ -0,0 +1,1022 @@ +/**************************************************************************** +** +** 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 "projectdata.h" + +#include "projectdata_p.h" +#include "propertymap_p.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace qbs { + +using namespace Internal; + +template static QJsonArray toJsonArray(const QList &list, + const QStringList &moduleProperties) +{ + QJsonArray jsonArray; + std::transform(list.begin(), list.end(), std::back_inserter(jsonArray), + [&moduleProperties](const T &v) { return v.toJson(moduleProperties);}); + return jsonArray; +} + +static QVariant getModuleProperty(const PropertyMap &properties, const QString &fullPropertyName) +{ + const int lastDotIndex = fullPropertyName.lastIndexOf(QLatin1Char('.')); + if (lastDotIndex == -1) + return QVariant(); + return properties.getModuleProperty(fullPropertyName.left(lastDotIndex), + fullPropertyName.mid(lastDotIndex + 1)); +} + +static void addModuleProperties(QJsonObject &obj, const PropertyMap &properties, + const QStringList &propertyNames) +{ + QJsonObject propertyValues; + for (const QString &prop : propertyNames) { + const QVariant v = getModuleProperty(properties, prop); + if (v.isValid()) + propertyValues.insert(prop, QJsonValue::fromVariant(v)); + } + if (!propertyValues.isEmpty()) + obj.insert(StringConstants::modulePropertiesKey(), propertyValues); +} + +/*! + * \class GroupData + * \brief The \c GroupData class corresponds to the Group item in a qbs source file. + */ + +GroupData::GroupData() : d(new GroupDataPrivate) +{ +} + +GroupData::GroupData(const GroupData &other) = default; + +GroupData::GroupData(GroupData &&) Q_DECL_NOEXCEPT = default; + +GroupData &GroupData::operator=(const GroupData &other) = default; + +GroupData &GroupData::operator=(GroupData &&) Q_DECL_NOEXCEPT = default; + +GroupData::~GroupData() = default; + +/*! + * \brief Returns true if and only if the Group holds data that was initialized by Qbs. + */ +bool GroupData::isValid() const +{ + return d->isValid; +} + +QJsonObject GroupData::toJson(const QStringList &moduleProperties) const +{ + QJsonObject obj; + if (isValid()) { + obj.insert(StringConstants::locationKey(), location().toJson()); + obj.insert(StringConstants::nameProperty(), name()); + obj.insert(StringConstants::prefixProperty(), prefix()); + obj.insert(StringConstants::isEnabledKey(), isEnabled()); + obj.insert(QStringLiteral("source-artifacts"), toJsonArray(sourceArtifacts(), {})); + obj.insert(QStringLiteral("source-artifacts-from-wildcards"), + toJsonArray(sourceArtifactsFromWildcards(), {})); + addModuleProperties(obj, properties(), moduleProperties); + } + return obj; +} + +/*! + * \brief The location at which the group is defined in the respective source file. + */ +CodeLocation GroupData::location() const +{ + return d->location; +} + +/*! + * \brief The name of the group. + */ +QString GroupData::name() const +{ + return d->name; +} + +/*! + * \brief The prefix of the group. + */ +QString GroupData::prefix() const +{ + return d->prefix; +} + +/*! + * \brief The files listed in the group item's "files" binding. + * \note These do not include expanded wildcards. + * \sa GroupData::sourceArtifactsFromWildcards + */ +QList GroupData::sourceArtifacts() const +{ + return d->sourceArtifacts; +} + +/*! + * \brief The list of files resulting from expanding all wildcard patterns in the group. + */ +QList GroupData::sourceArtifactsFromWildcards() const +{ + return d->sourceArtifactsFromWildcards; +} + +/*! + * \brief All files in this group, regardless of how whether they were given explicitly + * or via wildcards. + * \sa GroupData::sourceArtifacts + * \sa GroupData::sourceArtifactsFromWildcards + */ +QList GroupData::allSourceArtifacts() const +{ + return sourceArtifacts() + sourceArtifactsFromWildcards(); +} + +/*! + * \brief The set of properties valid in this group. + * Typically, most of them are inherited from the respective \c Product. + */ +PropertyMap GroupData::properties() const +{ + return d->properties; +} + +/*! + * \brief Returns true if this group is enabled in Qbs + * This method returns the "condition" property of the \c Group definition. If the group is enabled + * then the files in this group will be processed, provided the product it belongs to is also + * enabled. + * + * Note that a group can be enabled, even if the product it belongs to is not. In this case + * the files in the group will not be processed. + * \sa ProductData::isEnabled() + */ +bool GroupData::isEnabled() const +{ + QBS_ASSERT(isValid(), return false); + return d->isEnabled; +} + +/*! + * \brief The paths of all files in this group. + * \sa GroupData::allSourceArtifacts + */ +QStringList GroupData::allFilePaths() const +{ + const QList &artifacts = allSourceArtifacts(); + QStringList paths; + paths.reserve(artifacts.size()); + std::transform(artifacts.cbegin(), artifacts.cend(), std::back_inserter(paths), + [](const ArtifactData &sa) { return sa.filePath(); }); + return paths; +} + +bool operator!=(const GroupData &lhs, const GroupData &rhs) +{ + return !(lhs == rhs); +} + +bool operator==(const GroupData &lhs, const GroupData &rhs) +{ + if (!lhs.isValid() && !rhs.isValid()) + return true; + + return lhs.isValid() == rhs.isValid() + && lhs.name() == rhs.name() + && lhs.location() == rhs.location() + && lhs.sourceArtifactsFromWildcards() == rhs.sourceArtifactsFromWildcards() + && lhs.sourceArtifacts() == rhs.sourceArtifacts() + && lhs.properties() == rhs.properties() + && lhs.isEnabled() == rhs.isEnabled(); +} + +bool operator<(const GroupData &lhs, const GroupData &rhs) +{ + return lhs.name() < rhs.name(); +} + + +/*! + * \class ArtifactData + * The \c ArtifactData class describes a file in a product. It is either a source file + * or it gets generated during the build process. + */ + +ArtifactData::ArtifactData() : d(new ArtifactDataPrivate) +{ +} + +ArtifactData::ArtifactData(const ArtifactData &other) = default; + +ArtifactData::ArtifactData(ArtifactData &&) Q_DECL_NOEXCEPT = default; + +ArtifactData &ArtifactData::operator=(const ArtifactData &other) = default; + +ArtifactData &ArtifactData::operator=(ArtifactData &&) Q_DECL_NOEXCEPT = default; + +ArtifactData::~ArtifactData() = default; + +/*! + * \brief Returns true if and only if this object holds data that was initialized by Qbs. + */ +bool ArtifactData::isValid() const +{ + return d->isValid; +} + +QJsonObject ArtifactData::toJson(const QStringList &moduleProperties) const +{ + QJsonObject obj; + if (isValid()) { + obj.insert(StringConstants::filePathKey(), filePath()); + obj.insert(QStringLiteral("file-tags"), QJsonArray::fromStringList(fileTags())); + obj.insert(QStringLiteral("is-generated"), isGenerated()); + obj.insert(QStringLiteral("is-executable"), isExecutable()); + obj.insert(QStringLiteral("is-target"), isTargetArtifact()); + obj.insert(QStringLiteral("install-data"), installData().toJson()); + addModuleProperties(obj, properties(), moduleProperties); + } + return obj; +} + +/*! + * \brief The full path of this file. + */ +QString ArtifactData::filePath() const +{ + return d->filePath; +} + +/*! + * \brief The tags of this file. + * Typically, this list will contain just one element. + */ +QStringList ArtifactData::fileTags() const +{ + return d->fileTags; +} + +bool ArtifactData::isGenerated() const +{ + return d->isGenerated; +} + +/*! + * \brief True if and only if this file is executable, + * either natively or through an interpreter or shell. + */ +bool ArtifactData::isExecutable() const +{ + const bool isBundle = d->properties.getModuleProperty( + QStringLiteral("bundle"), QStringLiteral("isBundle")).toBool(); + const QString androidSdkPackageType = d->properties.getModuleProperty( + QStringLiteral("Android.sdk"), QStringLiteral("packageType")).toString(); + const bool isAndroidApk = androidSdkPackageType == QStringLiteral("apk"); + return isRunnableArtifact(FileTags::fromStringList(d->fileTags), isBundle, isAndroidApk); +} + +/*! + * \brief True if and only if this artifact is a target artifact of its product. + */ +bool ArtifactData::isTargetArtifact() const +{ + QBS_ASSERT(isValid(), return false); + return d->isTargetArtifact; +} + +/*! + * \brief The properties of this file. + */ +PropertyMap ArtifactData::properties() const +{ + return d->properties; +} + +/*! + \brief The installation-related data of this artifact. + */ +InstallData ArtifactData::installData() const +{ + return d->installData; +} + +bool operator==(const ArtifactData &ad1, const ArtifactData &ad2) +{ + return ad1.filePath() == ad2.filePath() + && ad1.fileTags() == ad2.fileTags() + && ad1.isGenerated() == ad2.isGenerated() + && ad1.properties() == ad2.properties(); +} + +bool operator!=(const ArtifactData &ta1, const ArtifactData &ta2) +{ + return !(ta1 == ta2); +} + +bool operator<(const ArtifactData &ta1, const ArtifactData &ta2) +{ + return ta1.filePath() < ta2.filePath(); +} + + +/*! + * \class InstallData + * \brief The \c InstallData class provides the installation-related data of an artifact. + */ + +InstallData::InstallData() : d(new InstallDataPrivate) +{ +} + +InstallData::InstallData(const InstallData &other) = default; + +InstallData::InstallData(InstallData &&) Q_DECL_NOEXCEPT = default; + +InstallData &InstallData::operator=(const InstallData &other) = default; + +InstallData &InstallData::operator=(InstallData &&) Q_DECL_NOEXCEPT = default; + +InstallData::~InstallData() = default; + +/*! + * \brief Returns true if and only if this object holds data that was initialized by Qbs. + */ +bool InstallData::isValid() const +{ + return d->isValid; +} + +QJsonObject InstallData::toJson() const +{ + QJsonObject obj; + if (isValid()) { + obj.insert(QStringLiteral("is-installable"), isInstallable()); + if (isInstallable()) { + obj.insert(QStringLiteral("install-file-path"), installFilePath()); + obj.insert(QStringLiteral("install-root"), installRoot()); + } + } + return obj; +} + +/*! + \brief Returns true if and only if \c{qbs.install} is \c true for the artifact. + */ +bool InstallData::isInstallable() const +{ + QBS_ASSERT(isValid(), return false); + return d->isInstallable; +} + +/*! + \brief Returns the directory into which the artifact will be installed. + \note This is not necessarily the same as \c{qbs.installDir}, because \c{qbs.installSourceBase} + might have been used. + */ +QString InstallData::installDir() const +{ + QBS_ASSERT(isValid(), return {}); + return FileInfo::path(installFilePath()); +} + +/*! + \brief Returns the installed file path of the artifact. + */ +QString InstallData::installFilePath() const +{ + QBS_ASSERT(isValid(), return {}); + return d->installFilePath; +} + +/*! + \brief Returns the value of \c{qbs.installRoot} for the artifact. + */ +QString InstallData::installRoot() const +{ + QBS_ASSERT(isValid(), return {}); + return d->installRoot; +} + +/*! + \brief Returns the local installation directory of the artifact, that is \c installDir() + prepended by \c installRoot(). + */ +QString InstallData::localInstallDir() const +{ + return QDir::cleanPath(installRoot() + QLatin1Char('/') + installDir()); +} + +/*! + \brief Returns the local installed file path of the artifact, that is \c installFilePath() + prepended by \c installRoot(). + */ +QString InstallData::localInstallFilePath() const +{ + return QDir::cleanPath(installRoot() + QLatin1Char('/') + installFilePath()); +} + +/*! + * \class ProductData + * \brief The \c ProductData class corresponds to the Product item in a qbs source file. + */ + +ProductData::ProductData() : d(new ProductDataPrivate) +{ +} + +ProductData::ProductData(const ProductData &other) = default; + +ProductData::ProductData(ProductData &&) Q_DECL_NOEXCEPT = default; + +ProductData &ProductData::operator=(const ProductData &other) = default; + +ProductData &ProductData::operator=(ProductData &&) Q_DECL_NOEXCEPT = default; + +ProductData::~ProductData() = default; + +/*! + * \brief Returns true if and only if the Product holds data that was initialized by Qbs. + */ +bool ProductData::isValid() const +{ + return d->isValid; +} + +QJsonObject ProductData::toJson(const QStringList &propertyNames) const +{ + QJsonObject obj; + if (!isValid()) + return obj; + obj.insert(StringConstants::typeProperty(), QJsonArray::fromStringList(type())); + obj.insert(StringConstants::dependenciesProperty(), + QJsonArray::fromStringList(dependencies())); + obj.insert(StringConstants::nameProperty(), name()); + obj.insert(StringConstants::fullDisplayNameKey(), fullDisplayName()); + obj.insert(QStringLiteral("target-name"), targetName()); + obj.insert(StringConstants::versionProperty(), version()); + obj.insert(QStringLiteral("multiplex-configuration-id"), multiplexConfigurationId()); + obj.insert(StringConstants::locationKey(), location().toJson()); + obj.insert(StringConstants::buildDirectoryKey(), buildDirectory()); + obj.insert(QStringLiteral("generated-artifacts"), toJsonArray(generatedArtifacts(), + propertyNames)); + obj.insert(QStringLiteral("target-executable"), targetExecutable()); + QJsonArray groupArray; + for (const GroupData &g : groups()) { + const QStringList groupPropNames = g.properties() == moduleProperties() + ? QStringList() : propertyNames; + groupArray << g.toJson(groupPropNames); + } + obj.insert(QStringLiteral("groups"), groupArray); + obj.insert(QStringLiteral("properties"), QJsonObject::fromVariantMap(properties())); + obj.insert(StringConstants::isEnabledKey(), isEnabled()); + obj.insert(QStringLiteral("is-runnable"), isRunnable()); + obj.insert(QStringLiteral("is-multiplexed"), isMultiplexed()); + addModuleProperties(obj, moduleProperties(), propertyNames); + return obj; +} + +/*! + * \brief The product type, which is the list of file tags matching the product's target artifacts. + */ +const QStringList &ProductData::type() const +{ + return d->type; +} + +/*! + * \brief The names of dependent products. + */ +const QStringList &ProductData::dependencies() const +{ + return d->dependencies; +} + +/*! + * \brief The name of the product as given in the qbs source file. + */ +const QString &ProductData::name() const +{ + return d->name; +} + +/*! + 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 ResolvedProduct::fullDisplayName(name(), multiplexConfigurationId()); +} + +/*! + * \brief The base name of the product's target file as given in the qbs source file. + */ +const QString &ProductData::targetName() const +{ + return d->targetName; +} + +/*! + * \brief The version number of the product. + */ +const QString &ProductData::version() const +{ + return d->version; +} + +/*! + * \brief The profile this product will be built for. + */ +QString ProductData::profile() const +{ + return d->moduleProperties.getModuleProperty( + StringConstants::qbsModule(), + StringConstants::profileProperty()).toString(); +} + +const QString &ProductData::multiplexConfigurationId() const +{ + return d->multiplexConfigurationId; +} + +/*! + * \brief The location at which the product is defined in the source file. + */ +const CodeLocation &ProductData::location() const +{ + return d->location; +} + +/*! + * \brief The directory under which the product's generated artifacts are located. + */ +const QString &ProductData::buildDirectory() const +{ + return d->buildDirectory; +} + +/*! + * \brief All artifacts that are generated when building this product. + */ +const QList &ProductData::generatedArtifacts() const +{ + return d->generatedArtifacts; +} + +/*! + \brief This product's target artifacts. + This is a subset of \c generatedArtifacts() + */ +const QList ProductData::targetArtifacts() const +{ + QList list; + std::copy_if(d->generatedArtifacts.cbegin(), d->generatedArtifacts.cend(), + std::back_inserter(list), + [](const ArtifactData &a) { return a.isTargetArtifact(); }); + return list; +} + +/*! + * \brief The list of artifacts in this product that are to be installed. + */ +const QList ProductData::installableArtifacts() const +{ + QList artifacts; + for (const GroupData &g : qAsConst(d->groups)) { + const auto sourceArtifacts = g.allSourceArtifacts(); + for (const ArtifactData &a : sourceArtifacts) { + if (a.installData().isInstallable()) + artifacts << a; + } + } + for (const ArtifactData &a : qAsConst(d->generatedArtifacts)) { + if (a.installData().isInstallable()) + artifacts << a; + } + return artifacts; +} + +/*! + * \brief Returns the file path of the executable associated with this product. + * If the product is not an application, an empty string is returned. + */ +QString ProductData::targetExecutable() const +{ + QBS_ASSERT(isValid(), return {}); + if (d->moduleProperties.getModuleProperty(QStringLiteral("bundle"), + QStringLiteral("isBundle")).toBool()) { + const auto artifacts = targetArtifacts(); + for (const ArtifactData &ta : artifacts) { + if (ta.fileTags().contains(QLatin1String("bundle.application-executable"))) { + if (ta.installData().isInstallable()) + return ta.installData().localInstallFilePath(); + return ta.filePath(); + } + } + } + const auto artifacts = targetArtifacts(); + for (const ArtifactData &ta : artifacts) { + if (ta.isExecutable()) { + if (ta.installData().isInstallable()) + return ta.installData().localInstallFilePath(); + return ta.filePath(); + } + } + return {}; +} + +/*! + * \brief The list of \c GroupData in this product. + */ +const QList &ProductData::groups() const +{ + return d->groups; +} + +/*! + * \brief The product properties. + */ +const QVariantMap &ProductData::properties() const +{ + return d->properties; +} + +/*! + * \brief The set of properties inherited from dependent products and modules. + */ +const PropertyMap &ProductData::moduleProperties() const +{ + return d->moduleProperties; +} + +/*! + * \brief Returns true if this Product is enabled in Qbs. + * This method returns the \c condition property of the \c Product definition. If a product is + * enabled, then it will be built in the current configuration. + * \sa GroupData::isEnabled() + */ +bool ProductData::isEnabled() const +{ + QBS_ASSERT(isValid(), return false); + return d->isEnabled; +} + +bool ProductData::isRunnable() const +{ + QBS_ASSERT(isValid(), return false); + return d->isRunnable; +} + +bool ProductData::isMultiplexed() const +{ + QBS_ASSERT(isValid(), return false); + return d->isMultiplexed; +} + +bool operator==(const ProductData &lhs, const ProductData &rhs) +{ + if (!lhs.isValid() && !rhs.isValid()) + return true; + + return lhs.isValid() == rhs.isValid() + && lhs.name() == rhs.name() + && lhs.targetName() == rhs.targetName() + && lhs.type() == rhs.type() + && lhs.version() == rhs.version() + && lhs.dependencies() == rhs.dependencies() + && lhs.profile() == rhs.profile() + && lhs.multiplexConfigurationId() == rhs.multiplexConfigurationId() + && lhs.location() == rhs.location() + && lhs.groups() == rhs.groups() + && lhs.generatedArtifacts() == rhs.generatedArtifacts() + && lhs.properties() == rhs.properties() + && lhs.moduleProperties() == rhs.moduleProperties() + && lhs.isEnabled() == rhs.isEnabled() + && lhs.isMultiplexed() == rhs.isMultiplexed(); +} + +bool operator!=(const ProductData &lhs, const ProductData &rhs) +{ + return !(lhs == rhs); +} + +bool operator<(const ProductData &lhs, const ProductData &rhs) +{ + const int nameCmp = lhs.name().compare(rhs.name()); + if (nameCmp < 0) + return true; + if (nameCmp > 0) + return false; + return lhs.profile() < rhs.profile() + && lhs.multiplexConfigurationId() < rhs.multiplexConfigurationId(); +} + +/*! + * \class ProjectData + * \brief The \c ProjectData class corresponds to the \c Project item in a qbs source file. + */ + +/*! + * \fn QList ProjectData::products() const + * \brief The products in this project. + */ + +ProjectData::ProjectData() : d(new ProjectDataPrivate) +{ +} + +ProjectData::ProjectData(const ProjectData &other) = default; + +ProjectData::ProjectData(ProjectData &&) Q_DECL_NOEXCEPT = default; + +ProjectData &ProjectData::operator =(const ProjectData &other) = default; + +ProjectData &ProjectData::operator=(ProjectData &&) Q_DECL_NOEXCEPT = default; + +ProjectData::~ProjectData() = default; + +/*! + * \brief Returns true if and only if the Project holds data that was initialized by Qbs. + */ +bool ProjectData::isValid() const +{ + return d->isValid; +} + +QJsonObject ProjectData::toJson(const QStringList &moduleProperties) const +{ + QJsonObject obj; + if (!isValid()) + return obj; + obj.insert(StringConstants::nameProperty(), name()); + obj.insert(StringConstants::locationKey(), location().toJson()); + obj.insert(StringConstants::isEnabledKey(), isEnabled()); + obj.insert(StringConstants::productsKey(), toJsonArray(products(), moduleProperties)); + obj.insert(QStringLiteral("sub-projects"), toJsonArray(subProjects(), moduleProperties)); + return obj; +} + +/*! + * \brief The name of this project. + */ +const QString &ProjectData::name() const +{ + return d->name; +} + +/*! + * \brief The location at which the project is defined in a qbs source file. + */ +const CodeLocation &ProjectData::location() const +{ + return d->location; +} + +/*! + * \brief Whether the project is enabled. + * \note Disabled projects never have any products or sub-projects. + */ +bool ProjectData::isEnabled() const +{ + QBS_ASSERT(isValid(), return false); + return d->enabled; +} + +/*! + * \brief The base directory under which the build artifacts of this project will be created. + * This is only valid for the top-level project. + */ +const QString &ProjectData::buildDirectory() const +{ + return d->buildDir; +} + +/*! + * The products in this project. + * \note This also includes disabled products. + */ +const QList &ProjectData::products() const +{ + return d->products; +} + +/*! + * The sub-projects of this project. + */ +const QList &ProjectData::subProjects() const +{ + return d->subProjects; +} + +/*! + * All products in this projects and its direct and indirect sub-projects. + */ +const QList ProjectData::allProducts() const +{ + QList productList = products(); + for (const ProjectData &pd : qAsConst(d->subProjects)) + productList << pd.allProducts(); + return productList; +} + +/*! + * The artifacts of all products in this project that are to be installed. + */ +const QList ProjectData::installableArtifacts() const +{ + QList artifacts; + const auto products = allProducts(); + for (const ProductData &p : products) + artifacts << p.installableArtifacts(); + return artifacts; +} + +bool operator==(const ProjectData &lhs, const ProjectData &rhs) +{ + if (!lhs.isValid() && !rhs.isValid()) + return true; + + return lhs.isValid() == rhs.isValid() + && lhs.isEnabled() == rhs.isEnabled() + && lhs.name() == rhs.name() + && lhs.buildDirectory() == rhs.buildDirectory() + && lhs.location() == rhs.location() + && lhs.subProjects() == rhs.subProjects() + && lhs.products() == rhs.products(); +} + +bool operator!=(const ProjectData &lhs, const ProjectData &rhs) +{ + return !(lhs == rhs); +} + +bool operator<(const ProjectData &lhs, const ProjectData &rhs) +{ + return lhs.name() < rhs.name(); +} + +/*! + * \class PropertyMap + * \brief The \c PropertyMap class represents the properties of a group or a product. + */ + +PropertyMap::PropertyMap() + : d(std::make_unique()) +{ + static PropertyMapPtr defaultInternalMap = PropertyMapInternal::create(); + d->m_map = defaultInternalMap; +} + +PropertyMap::PropertyMap(const PropertyMap &other) + : d(std::make_unique(*other.d)) +{ +} + +PropertyMap::PropertyMap(PropertyMap &&other) Q_DECL_NOEXCEPT = default; + +PropertyMap::~PropertyMap() = default; + +PropertyMap &PropertyMap::operator =(const PropertyMap &other) +{ + if (this != &other) + d = std::make_unique(*other.d); + return *this; +} + +PropertyMap &PropertyMap::operator =(PropertyMap &&other) Q_DECL_NOEXCEPT = default; + +/*! + * \brief Returns the names of all properties. + */ +QStringList PropertyMap::allProperties() const +{ + QStringList properties; + for (QVariantMap::ConstIterator it = d->m_map->value().constBegin(); + it != d->m_map->value().constEnd(); ++it) { + if (!it.value().canConvert()) + properties << it.key(); + } + return properties; +} + +/*! + * \brief Returns the value of the given property of a product or group. + */ +QVariant PropertyMap::getProperty(const QString &name) const +{ + return d->m_map->value().value(name); +} + +/*! + * \brief Convenience wrapper around \c PropertyMap::getModuleProperty for properties of list type. + * + */ +QStringList PropertyMap::getModulePropertiesAsStringList(const QString &moduleName, + const QString &propertyName) const +{ + const QVariantList &vl = d->m_map->moduleProperty(moduleName, propertyName).toList(); + QStringList sl; + for (const QVariant &v : vl) { + QBS_ASSERT(v.canConvert(), continue); + sl << v.toString(); + } + return sl; +} + +/*! + * \brief Returns the value of the given module property. + */ +QVariant PropertyMap::getModuleProperty(const QString &moduleName, + const QString &propertyName) const +{ + return d->m_map->moduleProperty(moduleName, propertyName); +} + +static QString mapToString(const QVariantMap &map, const QString &prefix) +{ + QStringList keys(map.keys()); + std::sort(keys.begin(), keys.end()); + QString stringRep; + for (const QString &key : qAsConst(keys)) { + const QVariant &val = map.value(key); + if (val.type() == QVariant::Map) { + stringRep += mapToString(val.value(), prefix + key + QLatin1Char('.')); + } else { + stringRep += QStringLiteral("%1%2: %3\n") + .arg(prefix, key, toJSLiteral(val)); + } + } + return stringRep; +} + +QString PropertyMap::toString() const +{ + return mapToString(d->m_map->value(), QString()); +} + +bool operator==(const PropertyMap &pm1, const PropertyMap &pm2) +{ + return *pm1.d->m_map == *pm2.d->m_map; +} + +bool operator!=(const PropertyMap &pm1, const PropertyMap &pm2) +{ + return !(*pm1.d->m_map == *pm2.d->m_map); +} + +} // namespace qbs diff --git a/src/lib/corelib/api/projectdata.h b/src/lib/corelib/api/projectdata.h new file mode 100644 index 00000000..9fe6445c --- /dev/null +++ b/src/lib/corelib/api/projectdata.h @@ -0,0 +1,263 @@ +/**************************************************************************** +** +** 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 QBS_PROJECTDATA_H +#define QBS_PROJECTDATA_H + +#include "../tools/codelocation.h" +#include "../tools/qbs_export.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { +class ArtifactDataPrivate; +class GroupDataPrivate; +class InstallDataPrivate; +class ProductDataPrivate; +class ProjectPrivate; +class ProjectDataPrivate; +class PropertyMapPrivate; +} // namespace Internal + +class PropertyMap; + +QBS_EXPORT bool operator==(const PropertyMap &pm1, const PropertyMap &pm2); +QBS_EXPORT bool operator!=(const PropertyMap &pm1, const PropertyMap &pm2); + +class QBS_EXPORT PropertyMap +{ + friend class Internal::ProjectPrivate; + friend QBS_EXPORT bool operator==(const PropertyMap &, const PropertyMap &); + friend QBS_EXPORT bool operator!=(const PropertyMap &, const PropertyMap &); + +public: + PropertyMap(); + PropertyMap(const PropertyMap &other); + PropertyMap(PropertyMap &&other) Q_DECL_NOEXCEPT; + ~PropertyMap(); + + PropertyMap &operator =(const PropertyMap &other); + PropertyMap &operator =(PropertyMap &&other) Q_DECL_NOEXCEPT; + + QStringList allProperties() const; + QVariant getProperty(const QString &name) const; + + QStringList getModulePropertiesAsStringList(const QString &moduleName, + const QString &propertyName) const; + QVariant getModuleProperty(const QString &moduleName, const QString &propertyName) const; + + // For debugging. + QString toString() const; + +private: + std::unique_ptr d; +}; + +class InstallData; + +class QBS_EXPORT ArtifactData +{ + friend class Internal::ProjectPrivate; +public: + ArtifactData(); + ArtifactData(const ArtifactData &other); + ArtifactData(ArtifactData &&) Q_DECL_NOEXCEPT; + ArtifactData &operator=(const ArtifactData &other); + ArtifactData &operator=(ArtifactData &&) Q_DECL_NOEXCEPT; + ~ArtifactData(); + + bool isValid() const; + QJsonObject toJson(const QStringList &moduleProperties = {}) const; + + QString filePath() const; + QStringList fileTags() const; + bool isGenerated() const; + bool isExecutable() const; + bool isTargetArtifact() const; + PropertyMap properties() const; + InstallData installData() const; + +private: + QExplicitlySharedDataPointer d; +}; + +class QBS_EXPORT InstallData +{ + friend class Internal::ProjectPrivate; +public: + InstallData(); + InstallData(const InstallData &other); + InstallData(InstallData &&) Q_DECL_NOEXCEPT; + InstallData &operator=(const InstallData &other); + InstallData &operator=(InstallData &&) Q_DECL_NOEXCEPT; + ~InstallData(); + + bool isValid() const; + QJsonObject toJson() const; + + bool isInstallable() const; + QString installDir() const; + QString installFilePath() const; + QString installRoot() const; + QString localInstallDir() const; + QString localInstallFilePath() const; +private: + QExplicitlySharedDataPointer d; +}; + +QBS_EXPORT bool operator==(const ArtifactData &ta1, const ArtifactData &ta2); +QBS_EXPORT bool operator!=(const ArtifactData &ta1, const ArtifactData &ta2); +QBS_EXPORT bool operator<(const ArtifactData &ta1, const ArtifactData &ta2); + +class QBS_EXPORT GroupData +{ + friend class Internal::ProjectPrivate; +public: + GroupData(); + GroupData(const GroupData &other); + GroupData(GroupData &&) Q_DECL_NOEXCEPT; + GroupData &operator=(const GroupData &other); + GroupData &operator=(GroupData &&) Q_DECL_NOEXCEPT; + ~GroupData(); + + bool isValid() const; + QJsonObject toJson(const QStringList &moduleProperties = {}) const; + + CodeLocation location() const; + QString name() const; + QString prefix() const; + QList sourceArtifacts() const; + QList sourceArtifactsFromWildcards() const; + QList allSourceArtifacts() const; + PropertyMap properties() const; + bool isEnabled() const; + QStringList allFilePaths() const; + +private: + QExplicitlySharedDataPointer d; +}; + +QBS_EXPORT bool operator==(const GroupData &lhs, const GroupData &rhs); +QBS_EXPORT bool operator!=(const GroupData &lhs, const GroupData &rhs); +QBS_EXPORT bool operator<(const GroupData &lhs, const GroupData &rhs); + +class QBS_EXPORT ProductData +{ + friend class Internal::ProjectPrivate; +public: + ProductData(); + ProductData(const ProductData &other); + ProductData(ProductData &&) Q_DECL_NOEXCEPT; + ProductData &operator=(const ProductData &other); + ProductData &operator=(ProductData &&) Q_DECL_NOEXCEPT; + ~ProductData(); + + bool isValid() const; + QJsonObject toJson(const QStringList &propertyNames = {}) const; + + const QStringList &type() const; + const QStringList &dependencies() const; + const QString &name() const; + QString fullDisplayName() const; + const QString &targetName() const; + const QString &version() const; + QString profile() const; + const QString &multiplexConfigurationId() const; + const CodeLocation &location() const; + const QString &buildDirectory() const; + const QList &generatedArtifacts() const; + const QList targetArtifacts() const; + const QList installableArtifacts() const; + QString targetExecutable() const; + const QList &groups() const; + const QVariantMap &properties() const; + const PropertyMap &moduleProperties() const; + bool isEnabled() const; + bool isRunnable() const; + bool isMultiplexed() const; + +private: + QExplicitlySharedDataPointer d; +}; + +QBS_EXPORT bool operator==(const ProductData &lhs, const ProductData &rhs); +QBS_EXPORT bool operator!=(const ProductData &lhs, const ProductData &rhs); +QBS_EXPORT bool operator<(const ProductData &lhs, const ProductData &rhs); + +class QBS_EXPORT ProjectData +{ + friend class Internal::ProjectPrivate; +public: + ProjectData(); + ProjectData(const ProjectData &other); + ProjectData(ProjectData &&) Q_DECL_NOEXCEPT; + ProjectData &operator=(const ProjectData &other); + ProjectData &operator=(ProjectData &&) Q_DECL_NOEXCEPT; + ~ProjectData(); + + bool isValid() const; + QJsonObject toJson(const QStringList &moduleProperties = {}) const; + + const QString &name() const; + const CodeLocation &location() const; + bool isEnabled() const; + const QString &buildDirectory() const; + const QList &products() const; + const QList &subProjects() const; + const QList allProducts() const; + const QList installableArtifacts() const; + +private: + QExplicitlySharedDataPointer d; +}; + +QBS_EXPORT bool operator==(const ProjectData &lhs, const ProjectData &rhs); +QBS_EXPORT bool operator!=(const ProjectData &lhs, const ProjectData &rhs); +QBS_EXPORT bool operator<(const ProjectData &lhs, const ProjectData &rhs); + +} // namespace qbs + +#endif // QBS_PROJECTDATA_H diff --git a/src/lib/corelib/api/projectdata_p.h b/src/lib/corelib/api/projectdata_p.h new file mode 100644 index 00000000..e241ea92 --- /dev/null +++ b/src/lib/corelib/api/projectdata_p.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** 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 QBS_PROJECTDATA_P_H +#define QBS_PROJECTDATA_P_H + +#include "projectdata.h" +#include + +#include + +namespace qbs { +namespace Internal { + +class InstallDataPrivate : public QSharedData +{ +public: + QString installFilePath; + QString installRoot; + bool isValid = false; + bool isInstallable = false; +}; + +class ArtifactDataPrivate : public QSharedData +{ +public: + QString filePath; + QStringList fileTags; + PropertyMap properties; + InstallData installData; + bool isValid = false; + bool isGenerated = false; + bool isTargetArtifact = false; +}; + +class GroupDataPrivate : public QSharedData +{ +public: + QString name; + QString prefix; + CodeLocation location; + QList sourceArtifacts; + QList sourceArtifactsFromWildcards; + PropertyMap properties; + bool isEnabled = false; + bool isValid = false; +}; + +class InstallableFilePrivate: public QSharedData +{ +public: + QString sourceFilePath; + QString targetFilePath; + QStringList fileTags; + bool isValid = false; +}; + +class ProductDataPrivate : public QSharedData +{ +public: + QStringList type; + QStringList dependencies; + QString name; + QString targetName; + QString version; + QString multiplexConfigurationId; + CodeLocation location; + QString buildDirectory; + QList groups; + QVariantMap properties; + PropertyMap moduleProperties; + QList generatedArtifacts; + bool isEnabled = false; + bool isRunnable = false; + bool isMultiplexed = false; + bool isValid = false; +}; + +class ProjectDataPrivate : public QSharedData +{ +public: + QString name; + CodeLocation location; + bool enabled = false; + bool isValid = false; + QList products; + QList subProjects; + QString buildDir; +}; + +inline bool isRunnableArtifact(const FileTags &fileTags, bool isBundle, bool isAndroidApk) +{ + return (fileTags.contains("application") && (!isBundle || fileTags.contains("bundle.content"))) + || fileTags.contains("bundle.application-executable") + || (fileTags.contains("android.package") && isAndroidApk) + || fileTags.contains("msi"); +} + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/api/projectfileupdater.cpp b/src/lib/corelib/api/projectfileupdater.cpp new file mode 100644 index 00000000..0bc5bc7c --- /dev/null +++ b/src/lib/corelib/api/projectfileupdater.cpp @@ -0,0 +1,552 @@ +/**************************************************************************** +** +** 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 "projectfileupdater.h" + +#include "projectdata.h" +#include "qmljsrewriter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace QbsQmlJS; +using namespace AST; + +namespace qbs { +namespace Internal { + +class ItemFinder : public Visitor +{ +public: + ItemFinder(const CodeLocation &cl) : m_cl(cl), m_item(nullptr) { } + + UiObjectDefinition *item() const { return m_item; } + +private: + bool visit(UiObjectDefinition *ast) override + { + if (toCodeLocation(m_cl.filePath(), ast->firstSourceLocation()) == m_cl) { + m_item = ast; + return false; + } + return true; + } + + const CodeLocation m_cl; + UiObjectDefinition *m_item; +}; + +class FilesBindingFinder : public Visitor +{ +public: + FilesBindingFinder(const UiObjectDefinition *startItem) + : m_startItem(startItem), m_binding(nullptr) + { + } + + UiScriptBinding *binding() const { return m_binding; } + +private: + bool visit(UiObjectDefinition *ast) override + { + // We start with the direct parent of the binding, so do not descend into any + // other item. + return ast == m_startItem; + } + + bool visit(UiScriptBinding *ast) override + { + if (ast->qualifiedId->name.toString() != StringConstants::filesProperty()) + return true; + m_binding = ast; + return false; + } + + const UiObjectDefinition * const m_startItem; + UiScriptBinding *m_binding; +}; + + +ProjectFileUpdater::ProjectFileUpdater(QString projectFile) : m_projectFile(std::move(projectFile)) +{ +} + +ProjectFileUpdater::~ProjectFileUpdater() = default; + +ProjectFileUpdater::LineEndingType ProjectFileUpdater::guessLineEndingType(const QByteArray &text) +{ + char before = 0; + int lfCount = 0; + int crlfCount = 0; + int i = text.indexOf('\n'); + while (i != -1) { + if (i > 0) + before = text.at(i - 1); + if (before == '\r') + ++crlfCount; + else + ++lfCount; + i = text.indexOf('\n', i + 1); + } + if (lfCount == 0 && crlfCount == 0) + return UnknownLineEndings; + if (crlfCount == 0) + return UnixLineEndings; + if (lfCount == 0) + return WindowsLineEndings; + return MixedLineEndings; +} + +void ProjectFileUpdater::convertToUnixLineEndings(QByteArray *text, LineEndingType oldLineEndings) +{ + if (oldLineEndings == UnixLineEndings) + return; + text->replace("\r\n", "\n"); +} + +void ProjectFileUpdater::convertFromUnixLineEndings(QByteArray *text, LineEndingType newLineEndings) +{ + if (newLineEndings == WindowsLineEndings + || (newLineEndings != UnixLineEndings && HostOsInfo::isWindowsHost())) { + text->replace('\n', "\r\n"); + } +} + +void ProjectFileUpdater::apply() +{ + QFile file(m_projectFile); + if (!file.open(QFile::ReadOnly)) { + throw ErrorInfo(Tr::tr("File '%1' cannot be opened for reading: %2") + .arg(m_projectFile, file.errorString())); + } + QByteArray rawContent = file.readAll(); + const LineEndingType origLineEndingType = guessLineEndingType(rawContent); + convertToUnixLineEndings(&rawContent, origLineEndingType); + QString content = QString::fromUtf8(rawContent); + + file.close(); + Engine engine; + Lexer lexer(&engine); + lexer.setCode(content, 1); + Parser parser(&engine); + if (!parser.parse()) { + QList parserMessages = parser.diagnosticMessages(); + if (!parserMessages.empty()) { + ErrorInfo errorInfo; + errorInfo.append(Tr::tr("Failure parsing project file.")); + for (const DiagnosticMessage &msg : qAsConst(parserMessages)) + errorInfo.append(msg.message, toCodeLocation(file.fileName(), msg.loc)); + throw errorInfo; + } + } + + doApply(content, parser.ast()); + + if (!file.open(QFile::WriteOnly)) { + throw ErrorInfo(Tr::tr("File '%1' cannot be opened for writing: %2") + .arg(m_projectFile, file.errorString())); + } + file.resize(0); + rawContent = content.toUtf8(); + convertFromUnixLineEndings(&rawContent, origLineEndingType); + file.write(rawContent); +} + + +ProjectFileGroupInserter::ProjectFileGroupInserter(ProductData product, QString groupName) + : ProjectFileUpdater(product.location().filePath()) + , m_product(std::move(product)) + , m_groupName(std::move(groupName)) +{ +} + +void ProjectFileGroupInserter::doApply(QString &fileContent, UiProgram *ast) +{ + ItemFinder itemFinder(m_product.location()); + ast->accept(&itemFinder); + if (!itemFinder.item()) { + throw ErrorInfo(Tr::tr("The project file parser failed to find the product item."), + CodeLocation(projectFile())); + } + + ChangeSet changeSet; + Rewriter rewriter(fileContent, &changeSet, QStringList()); + QString groupItemString; + const int productItemIndentation + = int(itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1); + const int groupItemIndentation = productItemIndentation + 4; + const QString groupItemIndentationString = QString(groupItemIndentation, QLatin1Char(' ')); + groupItemString += groupItemIndentationString + QLatin1String("Group {\n"); + groupItemString += groupItemIndentationString + groupItemIndentationString + + QLatin1String("name: \"") + m_groupName + QLatin1String("\"\n"); + groupItemString += groupItemIndentationString + groupItemIndentationString + + QLatin1String("files: []\n"); + groupItemString += groupItemIndentationString + QLatin1Char('}'); + rewriter.addObject(itemFinder.item()->initializer, groupItemString); + + int lineOffset = 3 + 1; // Our text + a leading newline that is always added by the rewriter. + const QList &editOps = changeSet.operationList(); + QBS_CHECK(editOps.size() == 1); + const ChangeSet::EditOp &insertOp = editOps.front(); + setLineOffset(lineOffset); + + int insertionLine = fileContent.left(insertOp.pos1).count(QLatin1Char('\n')); + for (int i = 0; i < insertOp.text.size() && insertOp.text.at(i) == QLatin1Char('\n'); ++i) + ++insertionLine; // To account for newlines prepended by the rewriter. + ++insertionLine; // To account for zero-based indexing. + setItemPosition(CodeLocation(projectFile(), insertionLine, + groupItemIndentation + 1)); + changeSet.apply(&fileContent); +} + +static QString getNodeRepresentation(const QString &fileContent, const QbsQmlJS::AST::Node *node) +{ + const quint32 start = node->firstSourceLocation().offset; + const quint32 end = node->lastSourceLocation().end(); + return fileContent.mid(start, int(end - start)); +} + +static const ChangeSet::EditOp &getEditOp(const ChangeSet &changeSet) +{ + const QList &editOps = changeSet.operationList(); + QBS_CHECK(editOps.size() == 1); + return editOps.front(); +} + +static int getLineOffsetForChangedBinding(const ChangeSet &changeSet, const QString &oldRhs) +{ + return getEditOp(changeSet).text.count(QLatin1Char('\n')) - oldRhs.count(QLatin1Char('\n')); +} + +static int getBindingLine(const ChangeSet &changeSet, const QString &fileContent) +{ + return fileContent.left(getEditOp(changeSet).pos1 + 1).count(QLatin1Char('\n')) + 1; +} + + +ProjectFileFilesAdder::ProjectFileFilesAdder(ProductData product, GroupData group, + QStringList files) + : ProjectFileUpdater(product.location().filePath()) + , m_product(std::move(product)) + , m_group(std::move(group)) + , m_files(std::move(files)) +{ +} + +static QString &addToFilesRepr(QString &filesRepr, const QString &fileRepr, int indentation) +{ + filesRepr += QString(indentation, QLatin1Char(' ')); + filesRepr += fileRepr; + filesRepr += QLatin1String(",\n"); + return filesRepr; +} + +static QString &addToFilesRepr(QString &filesRepr, const QStringList &filePaths, int indentation) +{ + for (const QString &f : filePaths) + addToFilesRepr(filesRepr, toJSLiteral(f), indentation); + return filesRepr; +} + +static QString &completeFilesRepr(QString &filesRepr, int indentation) +{ + return filesRepr.prepend(QLatin1String("[\n")).append(QString(indentation, QLatin1Char(' '))) + .append(QLatin1Char(']')); +} + +void ProjectFileFilesAdder::doApply(QString &fileContent, UiProgram *ast) +{ + if (m_files.empty()) + return; + QStringList sortedFiles = m_files; + sortedFiles.sort(); + + // Find the item containing the "files" binding. + ItemFinder itemFinder(m_group.isValid() ? m_group.location() : m_product.location()); + ast->accept(&itemFinder); + if (!itemFinder.item()) { + throw ErrorInfo(Tr::tr("The project file parser failed to find the item."), + CodeLocation(projectFile())); + } + + const int itemIndentation + = int(itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1); + const int bindingIndentation = itemIndentation + 4; + const int arrayElemIndentation = bindingIndentation + 4; + + // Now get the binding itself. + FilesBindingFinder bindingFinder(itemFinder.item()); + itemFinder.item()->accept(&bindingFinder); + + ChangeSet changeSet; + Rewriter rewriter(fileContent, &changeSet, QStringList()); + + UiScriptBinding * const filesBinding = bindingFinder.binding(); + if (filesBinding) { + QString filesRepresentation; + if (filesBinding->statement->kind != QbsQmlJS::AST::Node::Kind_ExpressionStatement) + throw ErrorInfo(Tr::tr("JavaScript construct in source file is too complex.")); // TODO: rename, add new and concat. + const auto exprStatement + = static_cast(filesBinding->statement); + switch (exprStatement->expression->kind) { + case QbsQmlJS::AST::Node::Kind_ArrayLiteral: { + auto elem = static_cast(exprStatement->expression)->elements; + QStringList oldFileReprs; + while (elem) { + oldFileReprs << getNodeRepresentation(fileContent, elem->expression); + elem = elem->next; + } + + // Insert new files "sorted", but do not change the order of existing files. + const QString firstNewFileRepr = toJSLiteral(sortedFiles.front()); + while (!oldFileReprs.empty()) { + if (oldFileReprs.front() > firstNewFileRepr) + break; + addToFilesRepr(filesRepresentation, oldFileReprs.takeFirst(), arrayElemIndentation); + } + addToFilesRepr(filesRepresentation, sortedFiles, arrayElemIndentation); + while (!oldFileReprs.empty()) + addToFilesRepr(filesRepresentation, oldFileReprs.takeFirst(), arrayElemIndentation); + completeFilesRepr(filesRepresentation, bindingIndentation); + break; + } + case QbsQmlJS::AST::Node::Kind_StringLiteral: { + const auto existingElement + = static_cast(exprStatement->expression)->value.toString(); + sortedFiles << existingElement; + sortedFiles.sort(); + addToFilesRepr(filesRepresentation, sortedFiles, arrayElemIndentation); + completeFilesRepr(filesRepresentation, bindingIndentation); + break; + } + default: { + // Note that we can often do better than simply concatenating: For instance, + // in the case where the existing list is of the form ["a", "b"].concat(myProperty), + // we could keep on parsing until we find the array literal and then merge it with + // the new files, preventing cascading concat() calls. + // But this is not essential and can be implemented when we have some downtime. + const QString rhsRepr = getNodeRepresentation(fileContent, exprStatement->expression); + addToFilesRepr(filesRepresentation, sortedFiles, arrayElemIndentation); + completeFilesRepr(filesRepresentation, bindingIndentation); + + // It cannot be the other way around, since the existing right-hand side could + // have string type. + filesRepresentation += QStringLiteral(".concat(%1)").arg(rhsRepr); + + } + } + rewriter.changeBinding(itemFinder.item()->initializer, StringConstants::filesProperty(), + filesRepresentation, Rewriter::ScriptBinding); + } else { // Can happen for the product itself, for which the "files" binding is not mandatory. + QString filesRepresentation; + addToFilesRepr(filesRepresentation, sortedFiles, arrayElemIndentation); + completeFilesRepr(filesRepresentation, bindingIndentation); + const QString bindingString = QString(bindingIndentation, QLatin1Char(' ')) + + StringConstants::filesProperty(); + rewriter.addBinding(itemFinder.item()->initializer, bindingString, filesRepresentation, + Rewriter::ScriptBinding); + } + + setLineOffset(getLineOffsetForChangedBinding(changeSet, + filesBinding ? getNodeRepresentation(fileContent, filesBinding->statement) + : QString())); + const int insertionLine = getBindingLine(changeSet, fileContent); + const int insertionColumn = (filesBinding ? arrayElemIndentation : bindingIndentation) + 1; + setItemPosition(CodeLocation(projectFile(), insertionLine, insertionColumn)); + changeSet.apply(&fileContent); +} + +ProjectFileFilesRemover::ProjectFileFilesRemover(ProductData product, GroupData group, + QStringList files) + : ProjectFileUpdater(product.location().filePath()) + , m_product(std::move(product)) + , m_group(std::move(group)) + , m_files(std::move(files)) +{ +} + +void ProjectFileFilesRemover::doApply(QString &fileContent, UiProgram *ast) +{ + if (m_files.empty()) + return; + + // Find the item containing the "files" binding. + ItemFinder itemFinder(m_group.isValid() ? m_group.location() : m_product.location()); + ast->accept(&itemFinder); + if (!itemFinder.item()) { + throw ErrorInfo(Tr::tr("The project file parser failed to find the item."), + CodeLocation(projectFile())); + } + + // Now get the binding itself. + FilesBindingFinder bindingFinder(itemFinder.item()); + itemFinder.item()->accept(&bindingFinder); + if (!bindingFinder.binding()) { + throw ErrorInfo(Tr::tr("Could not find the 'files' binding in the project file."), + m_product.location()); + } + + if (bindingFinder.binding()->statement->kind != QbsQmlJS::AST::Node::Kind_ExpressionStatement) + throw ErrorInfo(Tr::tr("JavaScript construct in source file is too complex.")); + const CodeLocation bindingLocation + = toCodeLocation(projectFile(), bindingFinder.binding()->firstSourceLocation()); + + ChangeSet changeSet; + Rewriter rewriter(fileContent, &changeSet, QStringList()); + + const int itemIndentation + = int(itemFinder.item()->qualifiedTypeNameId->firstSourceLocation().startColumn - 1); + const int bindingIndentation = itemIndentation + 4; + const int arrayElemIndentation = bindingIndentation + 4; + + const auto exprStatement + = static_cast(bindingFinder.binding()->statement); + switch (exprStatement->expression->kind) { + case QbsQmlJS::AST::Node::Kind_ArrayLiteral: { + QStringList filesToRemove = m_files; + QStringList newFilesList; + auto elem = static_cast(exprStatement->expression)->elements; + while (elem) { + if (elem->expression->kind != QbsQmlJS::AST::Node::Kind_StringLiteral) { + throw ErrorInfo(Tr::tr("JavaScript construct in source file is too complex."), + bindingLocation); + } + const auto existingFile + = static_cast(elem->expression)->value.toString(); + if (!filesToRemove.removeOne(existingFile)) + newFilesList << existingFile; + elem = elem->next; + } + if (!filesToRemove.empty()) { + throw ErrorInfo(Tr::tr("The following files were not found in the 'files' list: %1") + .arg(filesToRemove.join(QLatin1String(", "))), bindingLocation); + } + QString filesString; + filesString += QLatin1String("[\n"); + for (const QString &file : qAsConst(newFilesList)) { + filesString += QString(arrayElemIndentation, QLatin1Char(' ')); + filesString += QStringLiteral("\"%1\",\n").arg(file); + } + filesString += QString(bindingIndentation, QLatin1Char(' ')); + filesString += QLatin1Char(']'); + rewriter.changeBinding(itemFinder.item()->initializer, StringConstants::filesProperty(), + filesString, Rewriter::ScriptBinding); + break; + } + case QbsQmlJS::AST::Node::Kind_StringLiteral: { + if (m_files.size() != 1) { + throw ErrorInfo(Tr::tr("Was requested to remove %1 files, but there is only " + "one in the list.").arg(m_files.size()), bindingLocation); + } + const auto existingFile + = static_cast(exprStatement->expression)->value.toString(); + if (existingFile != m_files.front()) { + throw ErrorInfo(Tr::tr("File '%1' could not be found in the 'files' list.") + .arg(m_files.front()), bindingLocation); + } + rewriter.changeBinding(itemFinder.item()->initializer, StringConstants::filesProperty(), + StringConstants::emptyArrayValue(), Rewriter::ScriptBinding); + break; + } + default: + throw ErrorInfo(Tr::tr("JavaScript construct in source file is too complex."), + bindingLocation); + } + + setLineOffset(getLineOffsetForChangedBinding(changeSet, + getNodeRepresentation(fileContent, exprStatement->expression))); + const int bindingLine = getBindingLine(changeSet, fileContent); + const int bindingColumn = (bindingFinder.binding() + ? arrayElemIndentation : bindingIndentation) + 1; + setItemPosition(CodeLocation(projectFile(), bindingLine, bindingColumn)); + changeSet.apply(&fileContent); +} + + +ProjectFileGroupRemover::ProjectFileGroupRemover(ProductData product, GroupData group) + : ProjectFileUpdater(product.location().filePath()) + , m_product(std::move(product)) + , m_group(std::move(group)) +{ +} + +void ProjectFileGroupRemover::doApply(QString &fileContent, UiProgram *ast) +{ + ItemFinder productFinder(m_product.location()); + ast->accept(&productFinder); + if (!productFinder.item()) { + throw ErrorInfo(Tr::tr("The project file parser failed to find the product item."), + CodeLocation(projectFile())); + } + + ItemFinder groupFinder(m_group.location()); + productFinder.item()->accept(&groupFinder); + if (!groupFinder.item()) { + throw ErrorInfo(Tr::tr("The project file parser failed to find the group item."), + m_product.location()); + } + + ChangeSet changeSet; + Rewriter rewriter(fileContent, &changeSet, QStringList()); + rewriter.removeObjectMember(groupFinder.item(), productFinder.item()); + + setItemPosition(m_group.location()); + const QList &editOps = changeSet.operationList(); + QBS_CHECK(editOps.size() == 1); + const ChangeSet::EditOp &op = editOps.front(); + const QString removedText = fileContent.mid(op.pos1, op.length1); + setLineOffset(-removedText.count(QLatin1Char('\n'))); + + changeSet.apply(&fileContent); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/api/projectfileupdater.h b/src/lib/corelib/api/projectfileupdater.h new file mode 100644 index 00000000..ded4e08a --- /dev/null +++ b/src/lib/corelib/api/projectfileupdater.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** 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 QBS_PROJECTFILEUPDATER_H +#define QBS_PROJECTFILEUPDATER_H + +#include "projectdata.h" + +#include +#include + +#include + +namespace QbsQmlJS { namespace AST { class UiProgram; } } + +namespace qbs { +namespace Internal { + +class ProjectFileUpdater +{ +public: + virtual ~ProjectFileUpdater(); + void apply(); + + CodeLocation itemPosition() const { return m_itemPosition; } + int lineOffset() const { return m_lineOffset; } + +protected: + ProjectFileUpdater(QString projectFile); + + QString projectFile() const { return m_projectFile; } + + void setLineOffset(int offset) { m_lineOffset = offset; } + void setItemPosition(const CodeLocation &cl) { m_itemPosition = cl; } + +private: + virtual void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast) = 0; + + enum LineEndingType + { + UnknownLineEndings, + UnixLineEndings, + WindowsLineEndings, + MixedLineEndings + }; + + static LineEndingType guessLineEndingType(const QByteArray &text); + static void convertToUnixLineEndings(QByteArray *text, LineEndingType oldLineEndings); + static void convertFromUnixLineEndings(QByteArray *text, LineEndingType newLineEndings); + + const QString m_projectFile; + CodeLocation m_itemPosition; + int m_lineOffset = 0; +}; + + +class ProjectFileGroupInserter : public ProjectFileUpdater +{ +public: + ProjectFileGroupInserter(ProductData product, QString groupName); + +private: + void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast) override; + + const ProductData m_product; + const QString m_groupName; +}; + + +class ProjectFileFilesAdder : public ProjectFileUpdater +{ +public: + ProjectFileFilesAdder(ProductData product, GroupData group, QStringList files); + +private: + void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast) override; + + const ProductData m_product; + const GroupData m_group; + const QStringList m_files; +}; + +class ProjectFileFilesRemover : public ProjectFileUpdater +{ +public: + ProjectFileFilesRemover(ProductData product, GroupData group, + QStringList files); + +private: + void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast) override; + + const ProductData m_product; + const GroupData m_group; + const QStringList m_files; +}; + +class ProjectFileGroupRemover : public ProjectFileUpdater +{ +public: + ProjectFileGroupRemover(ProductData product, GroupData group); + +private: + void doApply(QString &fileContent, QbsQmlJS::AST::UiProgram *ast) override; + + const ProductData m_product; + const GroupData m_group; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/api/propertymap_p.h b/src/lib/corelib/api/propertymap_p.h new file mode 100644 index 00000000..5f592484 --- /dev/null +++ b/src/lib/corelib/api/propertymap_p.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** 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 QBS_PROPERTYMAP_P_H +#define QBS_PROPERTYMAP_P_H + +#include + +namespace qbs { +namespace Internal { + +class PropertyMapPrivate +{ +public: + PropertyMapPtr m_map; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROPERTYMAP_P_H diff --git a/src/lib/corelib/api/qmljsrewriter.cpp b/src/lib/corelib/api/qmljsrewriter.cpp new file mode 100644 index 00000000..5551e565 --- /dev/null +++ b/src/lib/corelib/api/qmljsrewriter.cpp @@ -0,0 +1,728 @@ +/**************************************************************************** +** +** 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 "qmljsrewriter.h" + +#include + +#include +#include +#include + +namespace QbsQmlJS { +using namespace AST; + +static QString toString(UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.')) +{ + QString result; + + for (UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) { + if (iter != qualifiedId) + result += delimiter; + + result += iter->name; + } + + return result; +} + + +Rewriter::Rewriter(QString originalText, + ChangeSet *changeSet, + QStringList propertyOrder) + : m_originalText(std::move(originalText)) + , m_changeSet(changeSet) + , m_propertyOrder(std::move(propertyOrder)) +{ + Q_ASSERT(changeSet); +} + +Rewriter::Range Rewriter::addBinding(AST::UiObjectInitializer *ast, + const QString &propertyName, + const QString &propertyValue, + BindingType bindingType) +{ + UiObjectMemberList *insertAfter = searchMemberToInsertAfter(ast->members, + propertyName, + m_propertyOrder); + return addBinding(ast, propertyName, propertyValue, bindingType, insertAfter); +} + +Rewriter::Range Rewriter::addBinding(AST::UiObjectInitializer *ast, + const QString &propertyName, + const QString &propertyValue, + BindingType bindingType, + UiObjectMemberList *insertAfter) +{ + SourceLocation endOfPreviousMember; + SourceLocation startOfNextMember; + + if (insertAfter == nullptr || insertAfter->member == nullptr) { + // insert as first member + endOfPreviousMember = ast->lbraceToken; + + if (ast->members && ast->members->member) + startOfNextMember = ast->members->member->firstSourceLocation(); + else + startOfNextMember = ast->rbraceToken; + } else { + endOfPreviousMember = insertAfter->member->lastSourceLocation(); + + if (insertAfter->next && insertAfter->next->member) + startOfNextMember = insertAfter->next->member->firstSourceLocation(); + else + startOfNextMember = ast->rbraceToken; + } + const bool isOneLiner = endOfPreviousMember.startLine == startOfNextMember.startLine; + bool needsPreceedingSemicolon = false; + bool needsTrailingSemicolon = false; + + if (isOneLiner) { + if (insertAfter == nullptr) { // we're inserting after an lbrace + if (ast->members) { // we're inserting before a member (and not the rbrace) + needsTrailingSemicolon = bindingType == ScriptBinding; + } + } else { // we're inserting after a member, not after the lbrace + if (endOfPreviousMember.isValid()) { // there already is a semicolon after the previous member + if (insertAfter->next && insertAfter->next->member) { // and the after us there is a member, not an rbrace, so: + needsTrailingSemicolon = bindingType == ScriptBinding; + } + } else { // there is no semicolon after the previous member (probably because there is an rbrace after us/it, so: + needsPreceedingSemicolon = true; + } + } + } + + QString newPropertyTemplate; + switch (bindingType) { + case ArrayBinding: + newPropertyTemplate = QStringLiteral("%1: [\n%2\n]"); + break; + + case ObjectBinding: + newPropertyTemplate = QStringLiteral("%1: %2"); + break; + + case ScriptBinding: + newPropertyTemplate = QStringLiteral("%1: %2"); + break; + + default: + Q_ASSERT(!"unknown property type"); + } + + if (isOneLiner) { + if (needsPreceedingSemicolon) + newPropertyTemplate.prepend(QLatin1Char(';')); + newPropertyTemplate.prepend(QLatin1Char(' ')); + if (needsTrailingSemicolon) + newPropertyTemplate.append(QLatin1Char(';')); + } else { + newPropertyTemplate.prepend(QLatin1Char('\n')); + } + + m_changeSet->insert(endOfPreviousMember.end(), + newPropertyTemplate.arg(propertyName, propertyValue)); + + return {int(endOfPreviousMember.end()), int(endOfPreviousMember.end())}; +} + +UiObjectMemberList *Rewriter::searchMemberToInsertAfter(UiObjectMemberList *members, + const QStringList &propertyOrder) +{ + const int objectDefinitionInsertionPoint = propertyOrder.indexOf(QString()); + + UiObjectMemberList *lastObjectDef = nullptr; + UiObjectMemberList *lastNonObjectDef = nullptr; + + for (UiObjectMemberList *iter = members; iter; iter = iter->next) { + UiObjectMember *member = iter->member; + int idx = -1; + + if (cast(member)) + lastObjectDef = iter; + else if (auto arrayBinding = cast(member)) + idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId)); + else if (auto objectBinding = cast(member)) + idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId)); + else if (auto scriptBinding = cast(member)) + idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId)); + else if (cast(member)) + idx = propertyOrder.indexOf(QLatin1String("property")); + + if (idx < objectDefinitionInsertionPoint) + lastNonObjectDef = iter; + } + + if (lastObjectDef) + return lastObjectDef; + else + return lastNonObjectDef; +} + +UiArrayMemberList *Rewriter::searchMemberToInsertAfter(UiArrayMemberList *members, + const QStringList &propertyOrder) +{ + const int objectDefinitionInsertionPoint = propertyOrder.indexOf(QString()); + + UiArrayMemberList *lastObjectDef = nullptr; + UiArrayMemberList *lastNonObjectDef = nullptr; + + for (UiArrayMemberList *iter = members; iter; iter = iter->next) { + UiObjectMember *member = iter->member; + int idx = -1; + + if (cast(member)) + lastObjectDef = iter; + else if (auto arrayBinding = cast(member)) + idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId)); + else if (auto objectBinding = cast(member)) + idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId)); + else if (auto scriptBinding = cast(member)) + idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId)); + else if (cast(member)) + idx = propertyOrder.indexOf(QLatin1String("property")); + + if (idx < objectDefinitionInsertionPoint) + lastNonObjectDef = iter; + } + + if (lastObjectDef) + return lastObjectDef; + else + return lastNonObjectDef; +} + +UiObjectMemberList *Rewriter::searchMemberToInsertAfter(UiObjectMemberList *members, + const QString &propertyName, + const QStringList &propertyOrder) +{ + if (!members) + return nullptr; // empty members + + QHash orderedMembers; + + for (UiObjectMemberList *iter = members; iter; iter = iter->next) { + UiObjectMember *member = iter->member; + + if (auto arrayBinding = cast(member)) + orderedMembers[toString(arrayBinding->qualifiedId)] = iter; + else if (auto objectBinding = cast(member)) + orderedMembers[toString(objectBinding->qualifiedId)] = iter; + else if (cast(member)) + orderedMembers[QString()] = iter; + else if (auto scriptBinding = cast(member)) + orderedMembers[toString(scriptBinding->qualifiedId)] = iter; + else if (cast(member)) + orderedMembers[QStringLiteral("property")] = iter; + } + + int idx = propertyOrder.indexOf(propertyName); + if (idx == -1) + idx = propertyOrder.indexOf(QString()); + if (idx == -1) + idx = propertyOrder.size() - 1; + + for (; idx > 0; --idx) { + const QString &prop = propertyOrder.at(idx - 1); + UiObjectMemberList *candidate = orderedMembers.value(prop, 0); + if (candidate != nullptr) + return candidate; + } + + return nullptr; +} + +void Rewriter::changeBinding(UiObjectInitializer *ast, + const QString &propertyName, + const QString &newValue, + BindingType binding) +{ + QString prefix, suffix; + int dotIdx = propertyName.indexOf(QLatin1Char('.')); + if (dotIdx != -1) { + prefix = propertyName.left(dotIdx); + suffix = propertyName.mid(dotIdx + 1); + } + + for (UiObjectMemberList *members = ast->members; members; members = members->next) { + UiObjectMember *member = members->member; + + // for non-grouped properties: + if (isMatchingPropertyMember(propertyName, member)) { + switch (binding) { + case ArrayBinding: + insertIntoArray(cast(member), newValue); + break; + + case ObjectBinding: + replaceMemberValue(member, newValue, false); + break; + + case ScriptBinding: + replaceMemberValue(member, newValue, nextMemberOnSameLine(members)); + break; + + default: + Q_ASSERT(!"Unhandled QmlRefactoring::PropertyType"); + } + + break; + // for grouped properties: + } else if (!prefix.isEmpty()) { + if (auto def = cast(member)) { + if (toString(def->qualifiedTypeNameId) == prefix) + changeBinding(def->initializer, suffix, newValue, binding); + } + } + } +} + +void Rewriter::replaceMemberValue(UiObjectMember *propertyMember, + const QString &newValue, + bool needsSemicolon) +{ + QString replacement = newValue; + int startOffset = -1; + int endOffset = -1; + if (auto objectBinding = AST::cast(propertyMember)) { + startOffset = objectBinding->qualifiedTypeNameId->identifierToken.offset; + endOffset = objectBinding->initializer->rbraceToken.end(); + } else if (auto scriptBinding = AST::cast(propertyMember)) { + startOffset = scriptBinding->statement->firstSourceLocation().offset; + endOffset = scriptBinding->statement->lastSourceLocation().end(); + } else if (auto arrayBinding = AST::cast(propertyMember)) { + startOffset = arrayBinding->lbracketToken.offset; + endOffset = arrayBinding->rbracketToken.end(); + } else if (auto publicMember = AST::cast(propertyMember)) { + if (publicMember->statement) { + startOffset = publicMember->statement->firstSourceLocation().offset; + if (publicMember->semicolonToken.isValid()) + endOffset = publicMember->semicolonToken.end(); + else + endOffset = publicMember->statement->lastSourceLocation().offset; + } else { + startOffset = publicMember->lastSourceLocation().end(); + endOffset = startOffset; + if (publicMember->semicolonToken.isValid()) + startOffset = publicMember->semicolonToken.offset; + replacement.prepend(QStringLiteral(": ")); + } + } else { + return; + } + + if (needsSemicolon) + replacement += QLatin1Char(';'); + + m_changeSet->replace(startOffset, endOffset, replacement); +} + +bool Rewriter::isMatchingPropertyMember(const QString &propertyName, + UiObjectMember *member) +{ + if (auto publicMember = cast(member)) + return publicMember->name == propertyName; + else if (auto objectBinding = cast(member)) + return toString(objectBinding->qualifiedId) == propertyName; + else if (auto scriptBinding = cast(member)) + return toString(scriptBinding->qualifiedId) == propertyName; + else if (auto arrayBinding = cast(member)) + return toString(arrayBinding->qualifiedId) == propertyName; + else + return false; +} + +bool Rewriter::nextMemberOnSameLine(UiObjectMemberList *members) +{ + if (members && members->next && members->next->member) + return members->next->member->firstSourceLocation().startLine == members->member->lastSourceLocation().startLine; + else + return false; +} + +void Rewriter::insertIntoArray(UiArrayBinding *ast, const QString &newValue) +{ + if (!ast) + return; + + UiObjectMember *lastMember = nullptr; + for (UiArrayMemberList *iter = ast->members; iter; iter = iter->next) { + lastMember = iter->member; + } + + if (!lastMember) + return; + + const int insertionPoint = lastMember->lastSourceLocation().end(); + m_changeSet->insert(insertionPoint, QLatin1String(",\n") + newValue); +} + +void Rewriter::removeBindingByName(UiObjectInitializer *ast, const QString &propertyName) +{ + QString prefix; + int dotIdx = propertyName.indexOf(QLatin1Char('.')); + if (dotIdx != -1) + prefix = propertyName.left(dotIdx); + + for (UiObjectMemberList *it = ast->members; it; it = it->next) { + UiObjectMember *member = it->member; + + // run full name match (for ungrouped properties): + if (isMatchingPropertyMember(propertyName, member)) { + removeMember(member); + // check for grouped properties: + } else if (!prefix.isEmpty()) { + if (auto def = cast(member)) { + if (toString(def->qualifiedTypeNameId) == prefix) + removeGroupedProperty(def, propertyName); + } + } + } +} + +void Rewriter::removeGroupedProperty(UiObjectDefinition *ast, + const QString &propertyName) +{ + int dotIdx = propertyName.indexOf(QLatin1Char('.')); + if (dotIdx == -1) + return; + + const QString propName = propertyName.mid(dotIdx + 1); + + UiObjectMember *wanted = nullptr; + unsigned memberCount = 0; + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + ++memberCount; + UiObjectMember *member = it->member; + + if (!wanted && isMatchingPropertyMember(propName, member)) + wanted = member; + } + + if (!wanted) + return; + if (memberCount == 1) + removeMember(ast); + else + removeMember(wanted); +} + +void Rewriter::removeMember(UiObjectMember *member) +{ + int start = member->firstSourceLocation().offset; + int end = member->lastSourceLocation().end(); + + includeSurroundingWhitespace(m_originalText, start, end); + + m_changeSet->remove(start, end); +} + +bool Rewriter::includeSurroundingWhitespace(const QString &source, int &start, int &end) +{ + bool includeStartingWhitespace = true; + bool paragraphFound = false; + bool paragraphSkipped = false; + + if (end >= 0) { + QChar c = source.at(end); + + while (c.isSpace()) { + ++end; + if (c.unicode() == 10) { + paragraphFound = true; + paragraphSkipped = true; + break; + } else if (end == source.length()) { + break; + } + + c = source.at(end); + } + + includeStartingWhitespace = paragraphFound; + } + + paragraphFound = false; + if (includeStartingWhitespace) { + while (start > 0) { + const QChar c = source.at(start - 1); + + if (c.unicode() == 10) { + paragraphFound = true; + break; + } + if (!c.isSpace()) + break; + + --start; + } + } + if (!paragraphFound && paragraphSkipped) //keep the line ending + --end; + + return paragraphFound; +} + +void Rewriter::includeLeadingEmptyLine(const QString &source, int &start) +{ + QTextDocument doc(source); + + if (start == 0) + return; + + if (doc.characterAt(start - 1) != QChar::ParagraphSeparator) + return; + + QTextCursor tc(&doc); + tc.setPosition(start); + const int blockNr = tc.blockNumber(); + if (blockNr == 0) + return; + + const QTextBlock prevBlock = tc.block().previous(); + const QString trimmedPrevBlockText = prevBlock.text().trimmed(); + if (!trimmedPrevBlockText.isEmpty()) + return; + + start = prevBlock.position(); +} + +void Rewriter::includeEmptyGroupedProperty(UiObjectDefinition *groupedProperty, UiObjectMember *memberToBeRemoved, int &start, int &end) +{ + if (groupedProperty->qualifiedTypeNameId && !groupedProperty->qualifiedTypeNameId->name.isEmpty() + && groupedProperty->qualifiedTypeNameId->name.at(0).isLower()) { + // grouped property + UiObjectMemberList *memberIter = groupedProperty->initializer->members; + while (memberIter) { + if (memberIter->member != memberToBeRemoved) + return; + memberIter = memberIter->next; + } + start = groupedProperty->firstSourceLocation().begin(); + end = groupedProperty->lastSourceLocation().end(); + } +} + +#if 0 +UiObjectMemberList *QMLRewriter::searchMemberToInsertAfter(UiObjectMemberList *members, const QStringList &propertyOrder) +{ + const int objectDefinitionInsertionPoint = propertyOrder.indexOf(QString()); + + UiObjectMemberList *lastObjectDef = nullptr; + UiObjectMemberList *lastNonObjectDef = nullptr; + + for (UiObjectMemberList *iter = members; iter; iter = iter->next) { + UiObjectMember *member = iter->member; + int idx = -1; + + if (cast(member)) + lastObjectDef = iter; + else if (UiArrayBinding *arrayBinding = cast(member)) + idx = propertyOrder.indexOf(toString(arrayBinding->qualifiedId)); + else if (UiObjectBinding *objectBinding = cast(member)) + idx = propertyOrder.indexOf(toString(objectBinding->qualifiedId)); + else if (UiScriptBinding *scriptBinding = cast(member)) + idx = propertyOrder.indexOf(toString(scriptBinding->qualifiedId)); + else if (cast(member)) + idx = propertyOrder.indexOf(QLatin1String("property")); + + if (idx < objectDefinitionInsertionPoint) + lastNonObjectDef = iter; + } + + if (lastObjectDef) + return lastObjectDef; + else + return lastNonObjectDef; +} + +UiObjectMemberList *QMLRewriter::searchMemberToInsertAfter(UiObjectMemberList *members, const QString &propertyName, const QStringList &propertyOrder) +{ + if (!members) + return nullptr; // empty members + + QHash orderedMembers; + + for (UiObjectMemberList *iter = members; iter; iter = iter->next) { + UiObjectMember *member = iter->member; + + if (UiArrayBinding *arrayBinding = cast(member)) + orderedMembers[toString(arrayBinding->qualifiedId)] = iter; + else if (UiObjectBinding *objectBinding = cast(member)) + orderedMembers[toString(objectBinding->qualifiedId)] = iter; + else if (cast(member)) + orderedMembers[QString()] = iter; + else if (UiScriptBinding *scriptBinding = cast(member)) + orderedMembers[toString(scriptBinding->qualifiedId)] = iter; + else if (cast(member)) + orderedMembers[QStringLiteral("property")] = iter; + } + + int idx = propertyOrder.indexOf(propertyName); + if (idx == -1) + idx = propertyOrder.indexOf(QString()); + if (idx == -1) + idx = propertyOrder.size() - 1; + + for (; idx > 0; --idx) { + const QString prop = propertyOrder.at(idx - 1); + UiObjectMemberList *candidate = orderedMembers.value(prop, 0); + if (candidate != 0) + return candidate; + } + + return nullptr; +} + +#endif + +void Rewriter::appendToArrayBinding(UiArrayBinding *arrayBinding, + const QString &content) +{ + UiObjectMember *lastMember = nullptr; + for (UiArrayMemberList *iter = arrayBinding->members; iter; iter = iter->next) + if (iter->member) + lastMember = iter->member; + + if (!lastMember) + return; // an array binding cannot be empty, so there will (or should) always be a last member. + + const int insertionPoint = lastMember->lastSourceLocation().end(); + + m_changeSet->insert(insertionPoint, QLatin1String(",\n") + content); +} + +Rewriter::Range Rewriter::addObject(UiObjectInitializer *ast, const QString &content) +{ + UiObjectMemberList *insertAfter = searchMemberToInsertAfter(ast->members, m_propertyOrder); + return addObject(ast, content, insertAfter); +} + +Rewriter::Range Rewriter::addObject(UiObjectInitializer *ast, const QString &content, UiObjectMemberList *insertAfter) +{ + int insertionPoint; + QString textToInsert; + if (insertAfter && insertAfter->member) { + insertionPoint = insertAfter->member->lastSourceLocation().end(); + textToInsert += QLatin1String("\n"); + } else { + insertionPoint = ast->lbraceToken.end(); + } + + textToInsert += content; + m_changeSet->insert(insertionPoint, QLatin1String("\n") + textToInsert); + + return {insertionPoint, insertionPoint}; +} + +Rewriter::Range Rewriter::addObject(UiArrayBinding *ast, const QString &content) +{ + UiArrayMemberList *insertAfter = searchMemberToInsertAfter(ast->members, m_propertyOrder); + return addObject(ast, content, insertAfter); +} + +Rewriter::Range Rewriter::addObject(UiArrayBinding *ast, const QString &content, UiArrayMemberList *insertAfter) +{ + int insertionPoint; + QString textToInsert; + if (insertAfter && insertAfter->member) { + insertionPoint = insertAfter->member->lastSourceLocation().end(); + textToInsert = QLatin1String(",\n") + content; + } else { + insertionPoint = ast->lbracketToken.end(); + textToInsert += QLatin1String("\n") + content + QLatin1Char(','); + } + + m_changeSet->insert(insertionPoint, textToInsert); + + return {insertionPoint, insertionPoint}; +} + +void Rewriter::removeObjectMember(UiObjectMember *member, UiObjectMember *parent) +{ + int start = member->firstSourceLocation().offset; + int end = member->lastSourceLocation().end(); + + if (auto parentArray = cast(parent)) { + extendToLeadingOrTrailingComma(parentArray, member, start, end); + } else { + if (auto parentObjectDefinition = cast(parent)) + includeEmptyGroupedProperty(parentObjectDefinition, member, start, end); + includeSurroundingWhitespace(m_originalText, start, end); + } + + includeLeadingEmptyLine(m_originalText, start); + m_changeSet->remove(start, end); +} + +void Rewriter::extendToLeadingOrTrailingComma(UiArrayBinding *parentArray, + UiObjectMember *member, + int &start, + int &end) const +{ + UiArrayMemberList *currentMember = nullptr; + for (UiArrayMemberList *it = parentArray->members; it; it = it->next) { + if (it->member == member) { + currentMember = it; + break; + } + } + + if (!currentMember) + return; + + if (currentMember->commaToken.isValid()) { + // leading comma + start = currentMember->commaToken.offset; + if (includeSurroundingWhitespace(m_originalText, start, end)) + --end; + } else if (currentMember->next && currentMember->next->commaToken.isValid()) { + // trailing comma + end = currentMember->next->commaToken.end(); + includeSurroundingWhitespace(m_originalText, start, end); + } else { + // array with 1 element, so remove the complete binding + start = parentArray->firstSourceLocation().offset; + end = parentArray->lastSourceLocation().end(); + includeSurroundingWhitespace(m_originalText, start, end); + } +} + +} // namespace QbsQmlJS diff --git a/src/lib/corelib/api/qmljsrewriter.h b/src/lib/corelib/api/qmljsrewriter.h new file mode 100644 index 00000000..3788035f --- /dev/null +++ b/src/lib/corelib/api/qmljsrewriter.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** 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 QMLJSREWRITER_H +#define QMLJSREWRITER_H + +#include "changeset.h" + +#include + +#include + +namespace QbsQmlJS { + +class Rewriter +{ +public: + enum BindingType { + ScriptBinding, + ObjectBinding, + ArrayBinding + }; + + using Range = ChangeSet::Range; + +public: + Rewriter(QString originalText, + ChangeSet *changeSet, + QStringList propertyOrder); + + Range addBinding(AST::UiObjectInitializer *ast, + const QString &propertyName, + const QString &propertyValue, + BindingType bindingType); + + Range addBinding(AST::UiObjectInitializer *ast, + const QString &propertyName, + const QString &propertyValue, + BindingType bindingType, + AST::UiObjectMemberList *insertAfter); + + void changeBinding(AST::UiObjectInitializer *ast, + const QString &propertyName, + const QString &newValue, + BindingType binding); + + void removeBindingByName(AST::UiObjectInitializer *ast, const QString &propertyName); + + void appendToArrayBinding(AST::UiArrayBinding *arrayBinding, + const QString &content); + + Range addObject(AST::UiObjectInitializer *ast, const QString &content); + Range addObject(AST::UiObjectInitializer *ast, const QString &content, AST::UiObjectMemberList *insertAfter); + Range addObject(AST::UiArrayBinding *ast, const QString &content); + Range addObject(AST::UiArrayBinding *ast, const QString &content, AST::UiArrayMemberList *insertAfter); + + void removeObjectMember(AST::UiObjectMember *member, AST::UiObjectMember *parent); + + static AST::UiObjectMemberList *searchMemberToInsertAfter(AST::UiObjectMemberList *members, const QStringList &propertyOrder); + static AST::UiArrayMemberList *searchMemberToInsertAfter(AST::UiArrayMemberList *members, const QStringList &propertyOrder); + static AST::UiObjectMemberList *searchMemberToInsertAfter(AST::UiObjectMemberList *members, const QString &propertyName, const QStringList &propertyOrder); + + static bool includeSurroundingWhitespace(const QString &source, int &start, int &end); + static void includeLeadingEmptyLine(const QString &source, int &start); + static void includeEmptyGroupedProperty(AST::UiObjectDefinition *groupedProperty, AST::UiObjectMember *memberToBeRemoved, int &start, int &end); + +private: + void replaceMemberValue(AST::UiObjectMember *propertyMember, + const QString &newValue, + bool needsSemicolon); + static bool isMatchingPropertyMember(const QString &propertyName, + AST::UiObjectMember *member); + static bool nextMemberOnSameLine(AST::UiObjectMemberList *members); + + void insertIntoArray(AST::UiArrayBinding* ast, const QString &newValue); + + void removeMember(AST::UiObjectMember *member); + void removeGroupedProperty(AST::UiObjectDefinition *ast, + const QString &propertyName); + + void extendToLeadingOrTrailingComma(AST::UiArrayBinding *parentArray, + AST::UiObjectMember *member, + int &start, + int &end) const; + +private: + QString m_originalText; + ChangeSet *m_changeSet; + const QStringList m_propertyOrder; +}; + +} // namespace QbsQmlJS + +#endif // QMLJSREWRITER_H diff --git a/src/lib/corelib/api/rulecommand.cpp b/src/lib/corelib/api/rulecommand.cpp new file mode 100644 index 00000000..e01cde7c --- /dev/null +++ b/src/lib/corelib/api/rulecommand.cpp @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** 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 "rulecommand.h" +#include "rulecommand_p.h" + +#include + +namespace qbs { + +/*! + * \class RuleCommand + * \brief The \c RuleCommand class corresponds to a \c ProcessCommand or \c JavaScriptCommand + * in \QBS. + */ + +/*! + * \enum RuleCommand::Type + * This enum type represents the different kinds of commands. + * \value ProcessCommandType For the \QBS type \c ProcessCommand, which represents a command + * whose execution involves calling an executable. + * \value JavaScriptCommandType For the \QBS type \c JavaScriptCommand, which represents a command + * whose execution involves running a piece of JavaScript code inside \QBS. + * \value InvalidType Used to mark \c RuleCommand objects as invalid. + */ + + +RuleCommand::RuleCommand() : d(new Internal::RuleCommandPrivate) +{ +} + +RuleCommand::RuleCommand(const RuleCommand &other) = default; + +RuleCommand::~RuleCommand() = default; + +RuleCommand& RuleCommand::operator=(const RuleCommand &other) = default; + +/*! + * Returns the type of this object. If the value is \c InvalidType, the object is invalid. + */ +RuleCommand::Type RuleCommand::type() const +{ + return d->type; +} + +/*! + * Returns the human-readable description of this command that \QBS will print when + * the command executed. + */ +QString RuleCommand::description() const +{ + return d->description; +} + +/*! + * Returns the detailed description of this command that \QBS will print when + * the command is executed. + */ +QString RuleCommand::extendedDescription() const +{ + return d->extendedDescription; +} + +/*! + * Returns the source of the command if \c type() is \c JavaScriptCommandType. + * If \c type() is anything else, the behavior of this function is undefined. + */ +QString RuleCommand::sourceCode() const +{ + QBS_ASSERT(type() == JavaScriptCommandType, return {}); + return d->sourceCode; +} + +/*! + * Returns the executable that will be called when the corresponding \c ProcessCommand + * is executed. + * If \c type() is not \c ProcessCommandType, the behavior of this function is undefined. + */ +QString RuleCommand::executable() const +{ + QBS_ASSERT(type() == ProcessCommandType, return {}); + return d->executable; +} + +/*! + * Returns the command-line arguments of the executable that will be called when the + * corresponding \c ProcessCommand is executed. + * If \c type() is not \c ProcessCommandType, the behavior of this function is undefined. + */ +QStringList RuleCommand::arguments() const +{ + QBS_ASSERT(type() == ProcessCommandType, return {}); + return d->arguments; +} + +/*! + * Returns the working directory of the executable that will be called when the + * corresponding \c ProcessCommand is executed. + * If \c type() is not \c ProcessCommandType, the behavior of this function is undefined. + */ +QString RuleCommand::workingDirectory() const +{ + QBS_ASSERT(type() == ProcessCommandType, return {}); + return d->workingDir; +} + +/*! + * Returns the environment of the executable that will be called when the + * corresponding \c ProcessCommand is executed. + * If \c type() is not \c ProcessCommandType, the behavior of this function is undefined. + */ +QProcessEnvironment RuleCommand::environment() const +{ + QBS_ASSERT(type() == ProcessCommandType, return {}); + return d->environment; +} + +} // namespace qbs diff --git a/src/lib/corelib/api/rulecommand.h b/src/lib/corelib/api/rulecommand.h new file mode 100644 index 00000000..43884960 --- /dev/null +++ b/src/lib/corelib/api/rulecommand.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** 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 QBS_RULECOMMAND_H +#define QBS_RULECOMMAND_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE +class QProcessEnvironment; +class QString; +class QStringList; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class ProjectPrivate; +class RuleCommandPrivate; +} + +class QBS_EXPORT RuleCommand +{ + friend class Internal::ProjectPrivate; +public: + RuleCommand(); + RuleCommand(const RuleCommand &other); + ~RuleCommand(); + RuleCommand &operator=(const RuleCommand &other); + + enum Type { ProcessCommandType, JavaScriptCommandType, InvalidType }; + + + Type type() const; + QString description() const; + QString extendedDescription() const; + QString sourceCode() const; + QString executable() const; + QStringList arguments() const; + QString workingDirectory() const; + QProcessEnvironment environment() const; + +private: + QExplicitlySharedDataPointer d; +}; + + +using RuleCommandList = QList; + +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/api/rulecommand_p.h b/src/lib/corelib/api/rulecommand_p.h new file mode 100644 index 00000000..253ec405 --- /dev/null +++ b/src/lib/corelib/api/rulecommand_p.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 QBS_RULECOMMAND_P_H +#define QBS_RULECOMMAND_P_H + +#include "rulecommand.h" + +#include +#include + +namespace qbs { +namespace Internal { + +class RuleCommandPrivate : public QSharedData +{ +public: + RuleCommandPrivate(): type(RuleCommand::InvalidType) {} + + RuleCommand::Type type; + QString description; + QString extendedDescription; + QString sourceCode; + QString executable; + QStringList arguments; + QString workingDir; + QProcessEnvironment environment; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/api/runenvironment.cpp b/src/lib/corelib/api/runenvironment.cpp new file mode 100644 index 00000000..8cea759a --- /dev/null +++ b/src/lib/corelib/api/runenvironment.cpp @@ -0,0 +1,493 @@ +/**************************************************************************** +** +** 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 "runenvironment.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace qbs { +using namespace Internal; + +class RunEnvironment::RunEnvironmentPrivate +{ +public: + RunEnvironmentPrivate(ResolvedProductPtr product, TopLevelProjectConstPtr project, + InstallOptions installOptions, const QProcessEnvironment &environment, + QStringList setupRunEnvConfig, Settings *settings, Logger logger) + : resolvedProduct(std::move(product)) + , project(std::move(project)) + , installOptions(std::move(installOptions)) + , environment(environment) + , setupRunEnvConfig(std::move(setupRunEnvConfig)) + , settings(settings) + , logger(std::move(logger)) + , evalContext(this->logger) + { + } + + void checkProduct() + { + if (!resolvedProduct) + throw ErrorInfo(Tr::tr("Cannot run: No such product.")); + if (!resolvedProduct->enabled) { + throw ErrorInfo(Tr::tr("Cannot run disabled product '%1'.") + .arg(resolvedProduct->fullDisplayName())); + } + } + + const ResolvedProductPtr resolvedProduct; + const TopLevelProjectConstPtr project; + InstallOptions installOptions; + const QProcessEnvironment environment; + const QStringList setupRunEnvConfig; + Settings * const settings; + Logger logger; + RulesEvaluationContext evalContext; +}; + +RunEnvironment::RunEnvironment(const ResolvedProductPtr &product, + const TopLevelProjectConstPtr &project, const InstallOptions &installOptions, + const QProcessEnvironment &environment, const QStringList &setupRunEnvConfig, + Settings *settings, const Logger &logger) + : d(new RunEnvironmentPrivate(product, project, installOptions, environment, setupRunEnvConfig, + settings, logger)) +{ +} + +RunEnvironment::~RunEnvironment() +{ + delete d; +} + +int RunEnvironment::runShell(ErrorInfo *error) +{ + try { + return doRunShell(); + } catch (const ErrorInfo &e) { + if (error) + *error = e; + return -1; + } +} + +int RunEnvironment::runTarget(const QString &targetBin, const QStringList &arguments, bool dryRun, + ErrorInfo *error) +{ + try { + return doRunTarget(targetBin, arguments, dryRun); + } catch (const ErrorInfo &e) { + if (error) + *error = e; + return -1; + } +} + +const QProcessEnvironment RunEnvironment::runEnvironment(ErrorInfo *error) const +{ + try { + return getRunEnvironment(); + } catch (const ErrorInfo &e) { + if (error) + *error = e; + return {}; + } +} + +const QProcessEnvironment RunEnvironment::buildEnvironment(ErrorInfo *error) const +{ + try { + return getBuildEnvironment(); + } catch (const ErrorInfo &e) { + if (error) + *error = e; + return {}; + } +} + +int RunEnvironment::doRunShell() +{ + if (d->resolvedProduct) { + EnvironmentScriptRunner(d->resolvedProduct.get(), &d->evalContext, + d->project->environment).setupForBuild(); + } + + const QString productId = d->resolvedProduct ? d->resolvedProduct->name : QString(); + const QString configName = d->project->id(); + if (productId.isEmpty()) { + d->logger.qbsInfo() << Tr::tr("Starting shell for configuration '%1'.").arg(configName); + } else { + d->logger.qbsInfo() << Tr::tr("Starting shell for product '%1' in configuration '%2'.") + .arg(productId, configName); + } + const QProcessEnvironment environment = d->resolvedProduct + ? d->resolvedProduct->buildEnvironment : d->project->environment; +#if defined(Q_OS_LINUX) + clearenv(); +#endif + const auto keys = environment.keys(); + for (const QString &key : keys) + qputenv(key.toLocal8Bit().constData(), environment.value(key).toLocal8Bit()); + QString command; + if (HostOsInfo::isWindowsHost()) { + command = environment.value(QStringLiteral("COMSPEC")); + if (command.isEmpty()) + command = QStringLiteral("cmd"); + const QString prompt = environment.value(QStringLiteral("PROMPT")); + command += QLatin1String(" /k prompt [qbs] ") + prompt; + } else { + const QVariantMap qbsProps = + (d->resolvedProduct ? d->resolvedProduct->moduleProperties->value() + : d->project->buildConfiguration()) + .value(StringConstants::qbsModule()).toMap(); + const QString profileName = qbsProps.value(StringConstants::profileProperty()).toString(); + command = Preferences(d->settings, profileName).shell(); + if (command.isEmpty()) + command = environment.value(QStringLiteral("SHELL"), QStringLiteral("/bin/sh")); + + // Yes, we have to use this procedure. PS1 is not inherited from the environment. + const QString prompt = QLatin1String("qbs ") + configName + + (!productId.isEmpty() ? QLatin1Char(' ') + productId : QString()) + + QLatin1String(" $ "); + QTemporaryFile envFile; + if (envFile.open()) { + if (command.endsWith(QLatin1String("bash"))) + command += QLatin1String(" --posix"); // Teach bash some manners. + const QString promptLine = QLatin1String("PS1='") + prompt + QLatin1String("'\n"); + envFile.write(promptLine.toLocal8Bit()); + envFile.close(); + qputenv("ENV", envFile.fileName().toLocal8Bit()); + } else { + d->logger.qbsWarning() << Tr::tr("Setting custom shell prompt failed."); + } + } + + // We cannot use QProcess, since it does not do stdin forwarding. + return system(command.toLocal8Bit().constData()); +} + +static QString findExecutable(const QStringList &fileNames) +{ + const QStringList path = QString::fromLocal8Bit(qgetenv("PATH")) + .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS); + + for (const QString &fileName : fileNames) { + const QString exeFileName = HostOsInfo::appendExecutableSuffix(fileName); + for (const QString &ppath : path) { + const QString fullPath = ppath + QLatin1Char('/') + exeFileName; + QFileInfo fi(fullPath); + if (fi.exists() && fi.isFile() && fi.isExecutable()) + return QDir::cleanPath(fullPath); + } + } + return {}; +} + +static std::string readAaptBadgingAttribute(const std::string &line) +{ + std::regex re("^[A-Za-z\\-]+:\\s+name='(.+?)'.*$"); + std::smatch match; + if (std::regex_match(line, match, re)) + return match[1]; + return {}; +} + +static QString findMainIntent(const QString &aapt, const QString &apkFilePath) +{ + QString packageId; + QString activity; + QProcess aaptProcess; + aaptProcess.start(aapt, QStringList() + << QStringLiteral("dump") + << QStringLiteral("badging") + << apkFilePath); + if (aaptProcess.waitForFinished(-1)) { + const auto lines = aaptProcess.readAllStandardOutput().split('\n'); + for (const auto &line : lines) { + if (line.startsWith(QByteArrayLiteral("package:"))) + packageId = QString::fromStdString(readAaptBadgingAttribute(line.toStdString())); + else if (line.startsWith(QByteArrayLiteral("launchable-activity:"))) + activity = QString::fromStdString(readAaptBadgingAttribute(line.toStdString())); + } + } + + if (!packageId.isEmpty() && !activity.isEmpty()) + return packageId + QStringLiteral("/") + activity; + return {}; +} + +void RunEnvironment::printStartInfo(const QProcess &proc, bool dryRun) +{ + QString message = dryRun ? Tr::tr("Would start target.") : Tr::tr("Starting target."); + message.append(QLatin1Char(' ')).append(Tr::tr("Full command line: %1") + .arg(shellQuote(QStringList(QDir::toNativeSeparators(proc.program())) + << proc.arguments()))); + d->logger.qbsInfo() << message; +} + +int RunEnvironment::doRunTarget(const QString &targetBin, const QStringList &arguments, bool dryRun) +{ + d->checkProduct(); + const QStringList targetOS = d->resolvedProduct->moduleProperties->qbsPropertyValue( + QStringLiteral("targetOS")).toStringList(); + const QStringList toolchain = d->resolvedProduct->moduleProperties->qbsPropertyValue( + QStringLiteral("toolchain")).toStringList(); + + QString targetExecutable = targetBin; + QStringList targetArguments = arguments; + const QString completeSuffix = QFileInfo(targetBin).completeSuffix(); + + if (targetOS.contains(QLatin1String("android"))) { + const auto aapt = d->resolvedProduct->moduleProperties->moduleProperty( + QStringLiteral("Android.sdk"), QStringLiteral("aaptFilePath")).toString(); + const auto intent = findMainIntent(aapt, targetBin); + const auto sdkDir = d->resolvedProduct->moduleProperties->moduleProperty( + QStringLiteral("Android.sdk"), QStringLiteral("sdkDir")).toString(); + targetExecutable = sdkDir + QStringLiteral("/platform-tools/adb"); + + if (!dryRun) { + QProcess process; + process.setProcessChannelMode(QProcess::ForwardedChannels); + process.start(targetExecutable, QStringList() + << StringConstants::androidInstallCommand() + << QStringLiteral("-r") // replace existing application + << QStringLiteral("-t") // allow test packages + << QStringLiteral("-d") // allow version code downgrade + << targetBin); + if (!process.waitForFinished(-1)) { + if (process.error() == QProcess::FailedToStart) { + throw ErrorInfo(Tr::tr("The process '%1' could not be started: %2") + .arg(targetExecutable) + .arg(process.errorString())); + } else { + d->logger.qbsWarning() + << "QProcess error: " << process.errorString(); + } + + return EXIT_FAILURE; + } + + targetArguments << QStringList() + << QStringLiteral("shell") + << QStringLiteral("am") + << QStringLiteral("start") + << QStringLiteral("-W") // wait for launch to complete + << QStringLiteral("-n") + << intent; + } + } else if (targetOS.contains(QLatin1String("ios")) || targetOS.contains(QLatin1String("tvos"))) { + const QString bundlePath = targetBin + StringConstants::slashDotDot(); + + if (targetOS.contains(QStringLiteral("ios-simulator")) + || targetOS.contains(QStringLiteral("tvos-simulator"))) { + const auto developerDir = d->resolvedProduct->moduleProperties->moduleProperty( + StringConstants::xcode(), QStringLiteral("developerPath")).toString(); + targetExecutable = developerDir + QStringLiteral("/usr/bin/simctl"); + const auto simulatorId = QStringLiteral("booted"); // TODO: parameterize + const auto bundleId = d->resolvedProduct->moduleProperties->moduleProperty( + QStringLiteral("bundle"), QStringLiteral("identifier")).toString(); + + if (!dryRun) { + QProcess process; + process.setProcessChannelMode(QProcess::ForwardedChannels); + process.start(targetExecutable, QStringList() + << StringConstants::simctlInstallCommand() + << simulatorId + << QDir::cleanPath(bundlePath)); + if (!process.waitForFinished(-1)) { + if (process.error() == QProcess::FailedToStart) { + throw ErrorInfo(Tr::tr("The process '%1' could not be started: %2") + .arg(targetExecutable) + .arg(process.errorString())); + } + + return EXIT_FAILURE; + } + + targetArguments << QStringList() + << QStringLiteral("launch") + << QStringLiteral("--console") + << simulatorId + << bundleId + << arguments; + } + } else { + if (QFileInfo(targetExecutable = findExecutable(QStringList() + << QStringLiteral("iostool"))).isExecutable()) { + targetArguments = QStringList() + << QStringLiteral("-run") + << QStringLiteral("-bundle") + << QDir::cleanPath(bundlePath); + if (!arguments.empty()) + targetArguments << QStringLiteral("-extra-args") << arguments; + } else if (QFileInfo(targetExecutable = findExecutable(QStringList() + << QStringLiteral("ios-deploy"))).isExecutable()) { + targetArguments = QStringList() + << QStringLiteral("--no-wifi") + << QStringLiteral("--noninteractive") + << QStringLiteral("--bundle") + << QDir::cleanPath(bundlePath); + if (!arguments.empty()) + targetArguments << QStringLiteral("--args") << arguments; + } else { + d->logger.qbsLog(LoggerError) + << Tr::tr("No suitable deployment tools were found in the environment. " + "Consider installing ios-deploy."); + return EXIT_FAILURE; + } + } + } else if (targetOS.contains(QLatin1String("windows"))) { + if (completeSuffix == QLatin1String("msi")) { + targetExecutable = QLatin1String("msiexec"); + targetArguments.prepend(QDir::toNativeSeparators(targetBin)); + targetArguments.prepend(QLatin1String("/package")); + } + + // Run Windows executables through Wine when not on Windows + if (!HostOsInfo::isWindowsHost()) { + targetArguments.prepend(targetExecutable); + targetExecutable = QStringLiteral("wine"); + } + } + + if (toolchain.contains(QLatin1String("mono"))) { + targetArguments.prepend(targetExecutable); + targetExecutable = QStringLiteral("mono"); + } + + if (completeSuffix == QLatin1String("js")) { + targetExecutable = d->resolvedProduct->moduleProperties->moduleProperty( + QStringLiteral("nodejs"), QStringLiteral("interpreterFilePath")).toString(); + if (targetExecutable.isEmpty()) + // The Node.js binary is called nodejs on Debian/Ubuntu-family operating systems due to + // conflict with another package containing a binary named node + targetExecutable = findExecutable(QStringList() + << QStringLiteral("nodejs") + << QStringLiteral("node")); + targetArguments.prepend(targetBin); + } + + // Only check if the target is executable if we're not running it through another + // known application such as msiexec or wine, as we can't check in this case anyways + QFileInfo fi(targetExecutable); + if (!dryRun && targetBin == targetExecutable && (!fi.isFile() || !fi.isExecutable())) { + d->logger.qbsLog(LoggerError) << Tr::tr("File '%1' is not an executable.") + .arg(QDir::toNativeSeparators(targetExecutable)); + return EXIT_FAILURE; + } + + QProcessEnvironment env = d->environment; + env.insert(QStringLiteral("QBS_RUN_FILE_PATH"), targetBin); + EnvironmentScriptRunner(d->resolvedProduct.get(), &d->evalContext, env) + .setupForRun(d->setupRunEnvConfig); + + QProcess process; + process.setProcessEnvironment(d->resolvedProduct->runEnvironment); + process.setProcessChannelMode(QProcess::ForwardedChannels); + process.setProgram(targetExecutable); + process.setArguments(targetArguments); + printStartInfo(process, dryRun); + if (dryRun) + return EXIT_SUCCESS; + process.start(); + if (!process.waitForFinished(-1)) { + if (process.error() == QProcess::FailedToStart) { + QString errorPrefixString; +#ifdef Q_OS_UNIX + if (QFileInfo(targetExecutable).isExecutable()) { + const QString interpreter(shellInterpreter(targetExecutable)); + if (!interpreter.isEmpty()) { + errorPrefixString = Tr::tr("%1: bad interpreter: ").arg(interpreter); + } + } +#endif + throw ErrorInfo(Tr::tr("The process '%1' could not be started: %2") + .arg(targetExecutable) + .arg(errorPrefixString + process.errorString())); + } else { + d->logger.qbsWarning() + << "QProcess error: " << process.errorString(); + } + + return EXIT_FAILURE; + } + return process.exitCode(); +} + +const QProcessEnvironment RunEnvironment::getRunEnvironment() const +{ + d->checkProduct(); + EnvironmentScriptRunner(d->resolvedProduct.get(), &d->evalContext, d->environment) + .setupForRun(d->setupRunEnvConfig); + return d->resolvedProduct->runEnvironment; +} + +const QProcessEnvironment RunEnvironment::getBuildEnvironment() const +{ + if (!d->resolvedProduct) + return d->environment; + EnvironmentScriptRunner(d->resolvedProduct.get(), &d->evalContext, d->environment) + .setupForBuild(); + return d->resolvedProduct->buildEnvironment; +} + +} // namespace qbs diff --git a/src/lib/corelib/api/runenvironment.h b/src/lib/corelib/api/runenvironment.h new file mode 100644 index 00000000..c5123ca9 --- /dev/null +++ b/src/lib/corelib/api/runenvironment.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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 QBS_RUNENVIRONMENT_H +#define QBS_RUNENVIRONMENT_H + +#include +#include + +#include + +QT_BEGIN_NAMESPACE +class QProcess; +class QProcessEnvironment; +class QString; +class QStringList; +QT_END_NAMESPACE + +class TestApi; + +namespace qbs { +class ErrorInfo; +class InstallOptions; +class Settings; + +namespace Internal { +class Logger; +class ResolvedProduct; +} // namespace Internal + +class QBS_EXPORT RunEnvironment +{ + friend class CommandLineFrontend; + friend class Project; + friend class ::TestApi; +public: + ~RunEnvironment(); + + int runShell(ErrorInfo *error = nullptr); + int runTarget(const QString &targetBin, const QStringList &arguments, bool dryRun, + ErrorInfo *error = nullptr); + + const QProcessEnvironment runEnvironment(ErrorInfo *error = nullptr) const; + const QProcessEnvironment buildEnvironment(ErrorInfo *error = nullptr) const; + +private: + RunEnvironment(const Internal::ResolvedProductPtr &product, + const Internal::TopLevelProjectConstPtr &project, + const InstallOptions &installOptions, + const QProcessEnvironment &environment, + const QStringList &setupRunEnvConfig, + Settings *settings, + const Internal::Logger &logger); + + int doRunShell(); + int doRunTarget(const QString &targetBin, const QStringList &arguments, bool dryRun); + void printStartInfo(const QProcess &proc, bool dryRun); + + const QProcessEnvironment getRunEnvironment() const; + const QProcessEnvironment getBuildEnvironment() const; + + class RunEnvironmentPrivate; + RunEnvironmentPrivate * const d; +}; + +} // namespace qbs + +#endif // QBS_RUNENVIRONMENT_H diff --git a/src/lib/corelib/api/transformerdata.cpp b/src/lib/corelib/api/transformerdata.cpp new file mode 100644 index 00000000..9724e641 --- /dev/null +++ b/src/lib/corelib/api/transformerdata.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "transformerdata_p.h" + +#include + +namespace qbs { + +TransformerData::TransformerData() : d(new Internal::TransformerDataPrivate) { } +TransformerData::TransformerData(const TransformerData &other) = default; +TransformerData::~TransformerData() = default; + +TransformerData& TransformerData::operator=(const TransformerData &other) = default; + +QList TransformerData::inputs() const { return d->inputs; } +QList TransformerData::outputs() const { return d->outputs; } +RuleCommandList TransformerData::commands() const { return d->commands; } + +} // namespace qbs diff --git a/src/lib/corelib/api/transformerdata.h b/src/lib/corelib/api/transformerdata.h new file mode 100644 index 00000000..bdd93332 --- /dev/null +++ b/src/lib/corelib/api/transformerdata.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_TRANSFORMERDATA_H +#define QBS_TRANSFORMERDATA_H + +#include "projectdata.h" +#include "rulecommand.h" + +#include +#include +#include + +namespace qbs { +namespace Internal { +class ProjectPrivate; +class TransformerDataPrivate; +} + +class QBS_EXPORT TransformerData +{ + friend class Internal::ProjectPrivate; +public: + TransformerData(); + TransformerData(const TransformerData &other); + ~TransformerData(); + TransformerData &operator=(const TransformerData &other); + + QList inputs() const; + QList outputs() const; + RuleCommandList commands() const; + +private: + QExplicitlySharedDataPointer d; +}; + +using ProductTransformerData = QList; +using ProjectTransformerData = QList>; + +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/api/transformerdata_p.h b/src/lib/corelib/api/transformerdata_p.h new file mode 100644 index 00000000..a7ed4ee2 --- /dev/null +++ b/src/lib/corelib/api/transformerdata_p.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_TRANSFORMERDATA_P_H +#define QBS_TRANSFORMERDATA_P_H + +#include "transformerdata.h" + +namespace qbs { +namespace Internal { + +class TransformerDataPrivate : public QSharedData +{ +public: + QList inputs; + QList outputs; + RuleCommandList commands; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp b/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp new file mode 100644 index 00000000..16c3621b --- /dev/null +++ b/src/lib/corelib/buildgraph/abstractcommandexecutor.cpp @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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 "abstractcommandexecutor.h" + +#include "rulecommands.h" +#include "transformer.h" + +#include +#include +#include + +namespace qbs { +namespace Internal { + +AbstractCommandExecutor::AbstractCommandExecutor(Logger logger, QObject *parent) + : QObject(parent) + , m_echoMode(defaultCommandEchoMode()) + , m_command(nullptr) + , m_transformer(nullptr) + , m_mainThreadScriptEngine(nullptr) + , m_dryRun(false) + , m_logger(std::move(logger)) +{ + m_watchdog.setSingleShot(true); + connect(&m_watchdog, &QTimer::timeout, + this, [this]() { + cancel(ErrorInfo{Tr::tr("Command cancelled because it exceeded the timeout.")}); + }); + connect(this, &AbstractCommandExecutor::finished, + &m_watchdog, &QTimer::stop); + +} + +void AbstractCommandExecutor::start(Transformer *transformer, AbstractCommand *cmd) +{ + m_transformer = transformer; + m_command = cmd; + doSetup(); + doReportCommandDescription(transformer->product()->fullDisplayName()); + if (doStart()) + startTimeout(); +} + +void AbstractCommandExecutor::doReportCommandDescription(const QString &productName) +{ + if (m_command->isSilent() || m_echoMode == CommandEchoModeSilent) + return; + + if (m_command->description().isEmpty()) { + m_logger.printWarning( + ErrorInfo(Tr::tr("Command is not marked silent, but has no description."), + m_command->codeLocation())); + } else { + emit reportCommandDescription(m_command->highlight(), + m_command->fullDescription(productName)); + } +} + +void AbstractCommandExecutor::startTimeout() +{ + if (!m_dryRun || m_command->ignoreDryRun()) { + const auto timeout = m_command->timeout(); + if (timeout > 0) + m_watchdog.start(timeout * 1000); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/abstractcommandexecutor.h b/src/lib/corelib/buildgraph/abstractcommandexecutor.h new file mode 100644 index 00000000..c0f14962 --- /dev/null +++ b/src/lib/corelib/buildgraph/abstractcommandexecutor.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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_ABSTRACTCOMMANDEXECUTOR_H +#define QBS_ABSTRACTCOMMANDEXECUTOR_H + +#include +#include +#include + +#include +#include + +namespace qbs { +class ErrorInfo; + +namespace Internal { +class AbstractCommand; +class ScriptEngine; +class Transformer; + +class AbstractCommandExecutor : public QObject +{ + Q_OBJECT +public: + explicit AbstractCommandExecutor(Internal::Logger logger, QObject *parent = nullptr); + + void setMainThreadScriptEngine(ScriptEngine *engine) { m_mainThreadScriptEngine = engine; } + void setDryRunEnabled(bool enabled) { m_dryRun = enabled; } + void setEchoMode(CommandEchoMode echoMode) { m_echoMode = echoMode; } + + virtual void cancel(const qbs::ErrorInfo &reason = {}) = 0; + + void start(Transformer *transformer, AbstractCommand *cmd); + +signals: + void reportCommandDescription(const QString &highlight, const QString &message); + void finished(const qbs::ErrorInfo &err = ErrorInfo()); // !hasError() <=> command successful + +protected: + virtual void doReportCommandDescription(const QString &productName); + AbstractCommand *command() const { return m_command; } + Transformer *transformer() const { return m_transformer; } + ScriptEngine *scriptEngine() const { return m_mainThreadScriptEngine; } + bool dryRun() const { return m_dryRun; } + Internal::Logger logger() const { return m_logger; } + CommandEchoMode m_echoMode; + +private: + virtual void doSetup() { }; + virtual bool doStart() = 0; + + void startTimeout(); + +private: + AbstractCommand *m_command; + Transformer *m_transformer; + ScriptEngine *m_mainThreadScriptEngine; + bool m_dryRun; + Internal::Logger m_logger; + QTimer m_watchdog; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ABSTRACTCOMMANDEXECUTOR_H diff --git a/src/lib/corelib/buildgraph/artifact.cpp b/src/lib/corelib/buildgraph/artifact.cpp new file mode 100644 index 00000000..8d3a8bd3 --- /dev/null +++ b/src/lib/corelib/buildgraph/artifact.cpp @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** 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 "artifact.h" + +#include "transformer.h" +#include "buildgraphvisitor.h" +#include "productbuilddata.h" +#include "rulenode.h" +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +Artifact::Artifact() : + artifactType(ArtifactType::Unknown), + inputsScanned(false), + timestampRetrieved(false), + alwaysUpdated(false), + oldDataPossiblyPresent(true) +{ +} + +Artifact::~Artifact() +{ + for (Artifact *p : parentArtifacts()) + p->childrenAddedByScanner.remove(this); +} + +void Artifact::accept(BuildGraphVisitor *visitor) +{ + if (visitor->visit(this)) + acceptChildren(visitor); + visitor->endVisit(this); +} + +QString Artifact::toString() const +{ + return QLatin1String("ARTIFACT ") + filePath() + QLatin1String(" [") + + (!product.expired() ? product->name : QLatin1String("")) + QLatin1Char(']'); +} + +void Artifact::addFileTag(const FileTag &t) +{ + m_fileTags += t; + if (!product.expired() && product->buildData) { + product->buildData->addFileTagToArtifact(this, t); + if (product->fileTags.contains(t)) + product->buildData->addRootNode(this); + } +} + +void Artifact::removeFileTag(const FileTag &t) +{ + m_fileTags -= t; + if (!product.expired() && product->buildData) { + product->buildData->removeArtifactFromSetByFileTag(this, t); + if (product->fileTags.contains(t) && !product->fileTags.intersects(m_fileTags)) + product->buildData->removeFromRootNodes(this); + } +} + +void Artifact::setFileTags(const FileTags &newFileTags) +{ + if (product.expired() || !product->buildData) { + m_fileTags = newFileTags; + return; + } + if (m_fileTags == newFileTags) + return; + const Set addedTags = newFileTags - m_fileTags; + for (const FileTag &t : addedTags) + addFileTag(t); + const Set removedTags = m_fileTags - newFileTags; + for (const FileTag &t : removedTags) + removeFileTag(t); +} + +RuleNode *Artifact::producer() const +{ + if (artifactType == SourceFile) + return nullptr; + const auto ruleNodes = filterByType(children); + QBS_CHECK(ruleNodes.begin() != ruleNodes.end()); + return *ruleNodes.begin(); +} + +const TypeFilter Artifact::parentArtifacts() const +{ + return TypeFilter(parents); +} + +const TypeFilter Artifact::childArtifacts() const +{ + return TypeFilter(children); +} + +void Artifact::onChildDisconnected(BuildGraphNode *child) +{ + if (child->type() != BuildGraphNode::ArtifactNodeType) + return; + childrenAddedByScanner.remove(static_cast(child)); +} + +void Artifact::load(PersistentPool &pool) +{ + FileResourceBase::load(pool); + BuildGraphNode::load(pool); + children.load(pool); + + // restore parents of the loaded children + for (auto it = children.constBegin(); it != children.constEnd(); ++it) + (*it)->parents.insert(this); + + pool.load(childrenAddedByScanner); + pool.load(fileDependencies); + pool.load(properties); + pool.load(targetOfModule); + pool.load(transformer); + pool.load(m_fileTags); + pool.load(pureFileTags); + pool.load(pureProperties); + artifactType = static_cast(pool.load()); + alwaysUpdated = pool.load(); + oldDataPossiblyPresent = pool.load(); +} + +void Artifact::store(PersistentPool &pool) +{ + FileResourceBase::store(pool); + BuildGraphNode::store(pool); + // Do not store parents to avoid recursion. + children.store(pool); + pool.store(childrenAddedByScanner); + pool.store(fileDependencies); + pool.store(properties); + pool.store(targetOfModule); + pool.store(transformer); + pool.store(m_fileTags); + pool.store(pureFileTags); + pool.store(pureProperties); + pool.store(static_cast(artifactType)); + pool.store(alwaysUpdated); + pool.store(oldDataPossiblyPresent); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/artifact.h b/src/lib/corelib/buildgraph/artifact.h new file mode 100644 index 00000000..2572a3b5 --- /dev/null +++ b/src/lib/corelib/buildgraph/artifact.h @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** 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 QBS_ARTIFACT_H +#define QBS_ARTIFACT_H + +#include "filedependency.h" +#include "buildgraphnode.h" +#include "forward_decls.h" +#include +#include +#include +#include + +#include + +#include +#include + +namespace qbs { +namespace Internal { +class Logger; + +class Artifact; +using ArtifactSet = Set; + +/** + * The Artifact class + * + * Let artifact P be the parent of artifact C. Thus C is child of P. + * C produces P using the transformer P.transformer. + * + * + */ +class QBS_AUTOTEST_EXPORT Artifact : public FileResourceBase, public BuildGraphNode +{ +public: + Artifact(); + ~Artifact() override; + + Type type() const override { return ArtifactNodeType; } + FileType fileType() const override { return FileTypeArtifact; } + void accept(BuildGraphVisitor *visitor) override; + QString toString() const override; + + void addFileTag(const FileTag &t); + void removeFileTag(const FileTag &t); + void setFileTags(const FileTags &newFileTags); + const FileTags &fileTags() const { return m_fileTags; } + + RuleNode *producer() const; + + ArtifactSet childrenAddedByScanner; + Set fileDependencies; + TransformerPtr transformer; + PropertyMapPtr properties; + QString targetOfModule; + + // The tags set directly via an Artifact item or an outputArtifacts script, + // not the result of file taggers or fileTagsFilter groups, nor the ones inherited from + // the product. + FileTags pureFileTags; + + // The properties attached directly to an artifact in an Artifact item or outputArtifacts + // script. + std::vector> pureProperties; + + enum ArtifactType + { + Unknown = 1, + SourceFile = 2, + Generated = 4 + }; + + ArtifactType artifactType; + bool inputsScanned : 1; // Do not serialize. Will be refreshed for every build. + bool timestampRetrieved : 1; // Do not serialize. Will be refreshed for every build. + bool alwaysUpdated : 1; + bool oldDataPossiblyPresent : 1; + + const TypeFilter parentArtifacts() const; + const TypeFilter childArtifacts() const; + void onChildDisconnected(BuildGraphNode *child) override; + + bool isTargetOfModule() const { return !targetOfModule.isEmpty(); } + + void load(PersistentPool &pool) override; + void store(PersistentPool &pool) override; + +private: + FileTags m_fileTags; +}; + +template<> inline QString Set::toString(Artifact * const &artifact) const +{ + return artifact ? artifact->filePath() : QStringLiteral(""); +} +template<> inline const void *uniqueAddress(const Artifact *a) +{ + return static_cast(a); +} + +template<> inline bool hasDynamicType(const BuildGraphNode *n) +{ + return n->type() == BuildGraphNode::ArtifactNodeType; +} + +// debugging helper +inline QString toString(Artifact::ArtifactType t) +{ + switch (t) { + case Artifact::SourceFile: + return QStringLiteral("SourceFile"); + case Artifact::Generated: + return QStringLiteral("Generated"); + case Artifact::Unknown: + default: + return QStringLiteral("Unknown"); + } +} + +// debugging helper +inline QString toString(BuildGraphNode::BuildState s) +{ + switch (s) { + case BuildGraphNode::Untouched: + return QStringLiteral("Untouched"); + case BuildGraphNode::Buildable: + return QStringLiteral("Buildable"); + case BuildGraphNode::Building: + return QStringLiteral("Building"); + case BuildGraphNode::Built: + return QStringLiteral("Built"); + default: + return QStringLiteral("Unknown"); + } +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ARTIFACT_H diff --git a/src/lib/corelib/buildgraph/artifactcleaner.cpp b/src/lib/corelib/buildgraph/artifactcleaner.cpp new file mode 100644 index 00000000..03b32723 --- /dev/null +++ b/src/lib/corelib/buildgraph/artifactcleaner.cpp @@ -0,0 +1,232 @@ +#include + +/**************************************************************************** +** +** 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 "artifactcleaner.h" + +#include "artifact.h" +#include "artifactvisitor.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "transformer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +static void printRemovalMessage(const QString &path, bool dryRun, const Logger &logger) +{ + if (dryRun) + logger.qbsInfo() << Tr::tr("Would remove '%1'.").arg(path); + else + logger.qbsDebug() << "Removing '" << path << "'."; +} + +static void invalidateArtifactTimestamp(Artifact *artifact) +{ + if (artifact->timestamp().isValid()) { + artifact->clearTimestamp(); + artifact->product->topLevelProject()->buildData->setDirty(); + } +} + +static void removeArtifactFromDisk(Artifact *artifact, bool dryRun, const Logger &logger) +{ + QFileInfo fileInfo(artifact->filePath()); + if (!FileInfo::fileExists(fileInfo)) { + if (!dryRun) + invalidateArtifactTimestamp(artifact); + return; + } + printRemovalMessage(fileInfo.filePath(), dryRun, logger); + if (dryRun) + return; + invalidateArtifactTimestamp(artifact); + QString errorMessage; + if (!removeFileRecursion(fileInfo, &errorMessage)) + throw ErrorInfo(errorMessage); +} + +class CleanupVisitor : public ArtifactVisitor +{ +public: + CleanupVisitor(CleanOptions options, const ProgressObserver *observer, + Logger logger) + : ArtifactVisitor(Artifact::Generated) + , m_options(std::move(options)) + , m_observer(observer) + , m_logger(std::move(logger)) + , m_hasError(false) + { + } + + void visitProduct(const ResolvedProductPtr &product) + { + m_product = product; + ArtifactVisitor::visitProduct(product); + const AllRescuableArtifactData rescuableArtifactData + = product->buildData->rescuableArtifactData(); + for (auto it = rescuableArtifactData.begin(); it != rescuableArtifactData.end(); ++it) { + Artifact tmp; + tmp.product = product; + tmp.setFilePath(it.key()); + tmp.setTimestamp(it.value().timeStamp); + removeArtifactFromDisk(&tmp, m_options.dryRun(), m_logger); + product->buildData->removeFromRescuableArtifactData(it.key()); + } + } + + const Set &directories() const { return m_directories; } + bool hasError() const { return m_hasError; } + +private: + void doVisit(Artifact *artifact) override + { + if (m_observer->canceled()) + throw ErrorInfo(Tr::tr("Cleaning up was canceled.")); + + if (artifact->product != m_product) + return; + try { + removeArtifactFromDisk(artifact, m_options.dryRun(), m_logger); + } catch (const ErrorInfo &error) { + if (!m_options.keepGoing()) + throw; + m_logger.printWarning(error); + m_hasError = true; + } + m_directories << artifact->dirPath(); + } + + const CleanOptions m_options; + const ProgressObserver * const m_observer; + Logger m_logger; + bool m_hasError; + ResolvedProductConstPtr m_product; + Set m_directories; +}; + +ArtifactCleaner::ArtifactCleaner(Logger logger, ProgressObserver *observer) + : m_logger(std::move(logger)), m_observer(observer) +{ +} + +void ArtifactCleaner::cleanup(const TopLevelProjectPtr &project, + const QVector &products, const CleanOptions &options) +{ + m_hasError = false; + + const QString configString = Tr::tr(" for configuration %1").arg(project->id()); + m_observer->initialize(Tr::tr("Cleaning up%1").arg(configString), products.size() + 1); + + Set directories; + for (const ResolvedProductPtr &product : products) { + CleanupVisitor visitor(options, m_observer, m_logger); + visitor.visitProduct(product); + directories.unite(visitor.directories()); + if (visitor.hasError()) + m_hasError = true; + m_observer->incrementProgressValue(); + } + + // Directories created during the build are not artifacts (TODO: should they be?), + // so we have to clean them up manually. + QList dirList = directories.toList(); + for (int i = 0; i < dirList.size(); ++i) { + const QString &dir = dirList.at(i); + if (!dir.startsWith(project->buildDirectory)) + continue; + if (FileInfo(dir).exists()) + removeEmptyDirectories(dir, options); + if (dir != project->buildDirectory) { + const QString parentDir = QDir::cleanPath(dir + StringConstants::slashDotDot()); + if (parentDir != project->buildDirectory && !dirList.contains(parentDir)) + dirList.push_back(parentDir); + } + } + m_observer->incrementProgressValue(); + + if (m_hasError) + throw ErrorInfo(Tr::tr("Failed to remove some files.")); + m_observer->setFinished(); +} + +void ArtifactCleaner::removeEmptyDirectories(const QString &rootDir, const CleanOptions &options, + bool *isEmpty) +{ + bool subTreeIsEmpty = true; + QDirIterator it(rootDir, QDir::Files | QDir::Dirs | QDir::Hidden | QDir::NoDotAndDotDot); + while (it.hasNext()) { + it.next(); + if (!it.fileInfo().isSymLink() && it.fileInfo().isDir()) + removeEmptyDirectories(it.filePath(), options, &subTreeIsEmpty); + else + subTreeIsEmpty = false; + } + if (subTreeIsEmpty) { + printRemovalMessage(rootDir, options.dryRun(), m_logger); + if (!QDir::root().rmdir(rootDir)) { + ErrorInfo error(Tr::tr("Failure to remove empty directory '%1'.").arg(rootDir)); + if (!options.keepGoing()) + throw error; + m_logger.printWarning(error); + m_hasError = true; + subTreeIsEmpty = false; + } + } + if (!subTreeIsEmpty && isEmpty) + *isEmpty = false; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/artifactcleaner.h b/src/lib/corelib/buildgraph/artifactcleaner.h new file mode 100644 index 00000000..8d0bef27 --- /dev/null +++ b/src/lib/corelib/buildgraph/artifactcleaner.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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 QBS_ARTIFACTCLEANER_H +#define QBS_ARTIFACTCLEANER_H + +#include + +#include +#include + +namespace qbs { +class CleanOptions; + +namespace Internal { +class ProgressObserver; + +class ArtifactCleaner +{ +public: + ArtifactCleaner(Logger logger, ProgressObserver *observer); + void cleanup(const TopLevelProjectPtr &project, const QVector &products, + const CleanOptions &options); + +private: + void removeEmptyDirectories(const QString &rootDir, const CleanOptions &options, + bool *isEmpty = nullptr); + + Logger m_logger; + bool m_hasError = false; + ProgressObserver *m_observer = nullptr; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ARTIFACTCLEANER_H diff --git a/src/lib/corelib/buildgraph/artifactsscriptvalue.cpp b/src/lib/corelib/buildgraph/artifactsscriptvalue.cpp new file mode 100644 index 00000000..2adb77d4 --- /dev/null +++ b/src/lib/corelib/buildgraph/artifactsscriptvalue.cpp @@ -0,0 +1,216 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "artifactsscriptvalue.h" + +#include "artifact.h" +#include "productbuilddata.h" +#include "transformer.h" + +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { + +enum BuildGraphScriptValueCommonPropertyKeys : quint32 { + CachedValueKey, + FileTagKey, + ProductPtrKey, +}; + +class ArtifactsScriptClass : public QScriptClass +{ +public: + ArtifactsScriptClass(QScriptEngine *engine) : QScriptClass(engine) { } + +private: + QueryFlags queryProperty(const QScriptValue &object, const QScriptString &name, + QueryFlags flags, uint *id) override + { + getProduct(object); + qbsEngine()->setNonExistingArtifactSetRequested(m_product, name.toString()); + return QScriptClass::queryProperty(object, name, flags, id); + } + + QScriptClassPropertyIterator *newIterator(const QScriptValue &object) override + { + getProduct(object); + qbsEngine()->setArtifactsEnumerated(m_product); + return QScriptClass::newIterator(object); + } + + void getProduct(const QScriptValue &object) + { + if (m_lastObjectId != object.objectId()) { + m_lastObjectId = object.objectId(); + m_product = reinterpret_cast( + object.data().property(ProductPtrKey).toVariant().value()); + } + } + + ScriptEngine *qbsEngine() const { return static_cast(engine()); } + + qint64 m_lastObjectId = 0; + const ResolvedProduct *m_product = nullptr; +}; + +static bool isRelevantArtifact(const ResolvedProduct *, const Artifact *artifact) +{ + return !artifact->isTargetOfModule(); +} +static bool isRelevantArtifact(const ResolvedModule *module, const Artifact *artifact) +{ + return artifact->targetOfModule == module->name; +} + +static ArtifactSetByFileTag artifactsMap(const ResolvedProduct *product) +{ + return product->buildData->artifactsByFileTag(); +} + +static ArtifactSetByFileTag artifactsMap(const ResolvedModule *module) +{ + return artifactsMap(module->product); +} + +static QScriptValue createArtifactsObject(const ResolvedProduct *product, ScriptEngine *engine) +{ + QScriptClass *scriptClass = engine->artifactsScriptClass(); + if (!scriptClass) { + scriptClass = new ArtifactsScriptClass(engine); + engine->setArtifactsScriptClass(scriptClass); + } + QScriptValue artifactsObj = engine->newObject(scriptClass); + QScriptValue data = engine->newObject(); + QVariant v; + v.setValue(reinterpret_cast(product)); + data.setProperty(ProductPtrKey, engine->newVariant(v)); + artifactsObj.setData(data); + return artifactsObj; +} + +static QScriptValue createArtifactsObject(const ResolvedModule *, ScriptEngine *engine) +{ + return engine->newObject(); +} + +static bool checkAndSetArtifactsMapUpToDateFlag(const ResolvedProduct *p) +{ + return p->buildData->checkAndSetJsArtifactsMapUpToDateFlag(); +} +static bool checkAndSetArtifactsMapUpToDateFlag(const ResolvedModule *) { return true; } + +static void registerArtifactsMapAccess(const ResolvedProduct *p, ScriptEngine *e, bool forceUpdate) +{ + e->setArtifactsMapRequested(p, forceUpdate); +} +static void registerArtifactsMapAccess(const ResolvedModule *, ScriptEngine *, bool) {} +static void registerArtifactsSetAccess(const ResolvedProduct *p, const FileTag &t, ScriptEngine *e) +{ + e->setArtifactSetRequestedForTag(p, t); +} +static void registerArtifactsSetAccess(const ResolvedModule *, const FileTag &, ScriptEngine *) {} + +template static QScriptValue js_artifactsForFileTag( + QScriptContext *ctx, ScriptEngine *engine, const ProductOrModule *productOrModule) +{ + const FileTag fileTag = FileTag(ctx->callee().property(FileTagKey).toString().toUtf8()); + registerArtifactsSetAccess(productOrModule, fileTag, engine); + QScriptValue result = ctx->callee().property(CachedValueKey); + if (result.isArray()) + return result; + auto artifacts = artifactsMap(productOrModule).value(fileTag); + const auto filter = [productOrModule](const Artifact *a) { + return !isRelevantArtifact(productOrModule, a); + }; + artifacts.erase(std::remove_if(artifacts.begin(), artifacts.end(), filter), artifacts.end()); + result = engine->newArray(uint(artifacts.size())); + ctx->callee().setProperty(CachedValueKey, result); + int k = 0; + for (const Artifact * const artifact : artifacts) + result.setProperty(k++, Transformer::translateFileConfig(engine, artifact, QString())); + return result; +} + +template static QScriptValue js_artifacts( + QScriptContext *ctx, ScriptEngine *engine, const ProductOrModule *productOrModule) +{ + QScriptValue artifactsObj = ctx->callee().property(CachedValueKey); + if (artifactsObj.isObject() && checkAndSetArtifactsMapUpToDateFlag(productOrModule)) { + registerArtifactsMapAccess(productOrModule, engine, false); + return artifactsObj; + } + registerArtifactsMapAccess(productOrModule, engine, true); + artifactsObj = createArtifactsObject(productOrModule, engine); + ctx->callee().setProperty(CachedValueKey, artifactsObj); + const auto &map = artifactsMap(productOrModule); + for (auto it = map.cbegin(); it != map.cend(); ++it) { + const auto filter = [productOrModule](const Artifact *a) { + return isRelevantArtifact(productOrModule, a); + }; + if (std::none_of(it.value().cbegin(), it.value().cend(), filter)) + continue; + QScriptValue fileTagFunc = engine->newFunction(&js_artifactsForFileTag, + productOrModule); + const QString fileTag = it.key().toString(); + fileTagFunc.setProperty(FileTagKey, fileTag); + artifactsObj.setProperty(fileTag, fileTagFunc, + QScriptValue::ReadOnly | QScriptValue::Undeletable + | QScriptValue::PropertyGetter); + } + return artifactsObj; +} + +QScriptValue artifactsScriptValueForProduct(QScriptContext *ctx, ScriptEngine *engine, + const ResolvedProduct *product) +{ + return js_artifacts(ctx, engine, product); +} + +QScriptValue artifactsScriptValueForModule(QScriptContext *ctx, ScriptEngine *engine, + const ResolvedModule *module) +{ + return js_artifacts(ctx, engine, module); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/artifactsscriptvalue.h b/src/lib/corelib/buildgraph/artifactsscriptvalue.h new file mode 100644 index 00000000..c0da8a05 --- /dev/null +++ b/src/lib/corelib/buildgraph/artifactsscriptvalue.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_ARTIFACTSSCRIPTVALUE_H +#define QBS_ARTIFACTSSCRIPTVALUE_H + +#include + +#include + +QT_BEGIN_NAMESPACE +class QScriptContext; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class ScriptEngine; + +QScriptValue artifactsScriptValueForProduct(QScriptContext *ctx, ScriptEngine *engine, + const ResolvedProduct *product); + +QScriptValue artifactsScriptValueForModule(QScriptContext *ctx, ScriptEngine *engine, + const ResolvedModule *module); + +} // namespace Internal +} // namespace qbs + +#endif // include guard diff --git a/src/lib/corelib/buildgraph/artifactvisitor.cpp b/src/lib/corelib/buildgraph/artifactvisitor.cpp new file mode 100644 index 00000000..22c98757 --- /dev/null +++ b/src/lib/corelib/buildgraph/artifactvisitor.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 "artifactvisitor.h" + +#include "artifact.h" +#include "productbuilddata.h" +#include +#include +#include + +namespace qbs { +namespace Internal { + +ArtifactVisitor::ArtifactVisitor(int artifactType) : m_artifactType(artifactType) +{ +} + +void ArtifactVisitor::visitProduct(const ResolvedProductConstPtr &product) +{ + if (!product->buildData) + return; + for (BuildGraphNode *node : qAsConst(product->buildData->allNodes())) + node->accept(this); +} + +void ArtifactVisitor::visitProject(const ResolvedProjectConstPtr &project) +{ + for (const auto &product : project->allProducts()) + visitProduct(product); +} + +bool ArtifactVisitor::visit(RuleNode *ruleNode) +{ + Q_UNUSED(ruleNode); + return false; +} + +bool ArtifactVisitor::visit(Artifact *artifact) +{ + QBS_CHECK(artifact); + if (m_artifactType & artifact->artifactType) + doVisit(artifact); + return false; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/artifactvisitor.h b/src/lib/corelib/buildgraph/artifactvisitor.h new file mode 100644 index 00000000..89bd4429 --- /dev/null +++ b/src/lib/corelib/buildgraph/artifactvisitor.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 QBS_ARTIFACTVISITOR_H +#define QBS_ARTIFACTVISITOR_H + +#include "forward_decls.h" + +#include "buildgraphvisitor.h" +#include + +#include + +namespace qbs { +namespace Internal { + +class ArtifactVisitor : public BuildGraphVisitor +{ +public: + ArtifactVisitor(int artifactType); + + void visitProduct(const ResolvedProductConstPtr &product); + void visitProject(const ResolvedProjectConstPtr &project); + bool visit(RuleNode *ruleNode) override; + bool visit(Artifact *artifact) override; + +private: + virtual void doVisit(Artifact *artifact) = 0; + + const int m_artifactType; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ARTIFACTVISITOR_H diff --git a/src/lib/corelib/buildgraph/buildgraph.cpp b/src/lib/corelib/buildgraph/buildgraph.cpp new file mode 100644 index 00000000..0d5e8a1f --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraph.cpp @@ -0,0 +1,859 @@ +/**************************************************************************** +** +** 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 "buildgraph.h" + +#include "artifact.h" +#include "artifactsscriptvalue.h" +#include "cycledetector.h" +#include "dependencyparametersscriptvalue.h" +#include "projectbuilddata.h" +#include "productbuilddata.h" +#include "rulenode.h" +#include "scriptclasspropertyiterator.h" +#include "transformer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +static QString childItemsProperty() { return QStringLiteral("childItems"); } +static QString exportsProperty() { return QStringLiteral("exports"); } + +// TODO: Introduce productscriptvalue.{h,cpp}. +static QScriptValue getDataForProductScriptValue(QScriptEngine *engine, + const ResolvedProduct *product) +{ + QScriptValue data = engine->newObject(); + QVariant v; + v.setValue(reinterpret_cast(product)); + data.setProperty(ProductPtrKey, engine->newVariant(v)); + return data; +} + +class ProductPropertyScriptClass : public QScriptClass +{ +public: + ProductPropertyScriptClass(QScriptEngine *engine) : QScriptClass(engine) { } + +private: + QueryFlags queryProperty(const QScriptValue &object, const QScriptString &name, QueryFlags, + uint *) override + { + if (name == StringConstants::parametersProperty()) { + m_result = object.data().property(DependencyParametersKey); + return HandlesReadAccess; + } + if (name == StringConstants::moduleNameProperty()) { + m_result = object.data().property(ModuleNameKey); + return HandlesReadAccess; + } + if (name == StringConstants::dependenciesProperty() + || name == StringConstants::artifactsProperty() + || name == exportsProperty()) { + // The prototype is not backed by a QScriptClass. + m_result = object.prototype().property(name); + return HandlesReadAccess; + } + + getProduct(object); + QBS_ASSERT(m_product, return {}); + + const auto it = m_product->productProperties.find(name); + + // It is important that we reject unknown property names. Otherwise QtScript will forward + // *everything* to us, including built-in stuff like the hasOwnProperty function. + if (it == m_product->productProperties.cend()) + return {}; + + qbsEngine()->addPropertyRequestedInScript(Property(m_product->uniqueName(), QString(), name, + it.value(), Property::PropertyInProduct)); + m_result = qbsEngine()->toScriptValue(it.value()); + return HandlesReadAccess; + } + + QScriptValue property(const QScriptValue &, const QScriptString &, uint) override + { + return m_result; + } + + QScriptClassPropertyIterator *newIterator(const QScriptValue &object) override + { + getProduct(object); + QBS_ASSERT(m_product, return nullptr); + + // These two are in the prototype and are thus common to all product script values. + std::vector additionalProperties({StringConstants::artifactsProperty(), + StringConstants::dependenciesProperty(), + exportsProperty()}); + + // The "moduleName" convenience property is only available for the "main product" in a rule, + // and the "parameters" property exists only for elements of the "dependencies" array for + // which dependency parameters are present. + if (object.data().property(ModuleNameKey).isValid()) + additionalProperties.push_back(StringConstants::moduleNameProperty()); + else if (object.data().property(DependencyParametersKey).isValid()) + additionalProperties.push_back(StringConstants::parametersProperty()); + return new ScriptClassPropertyIterator(object, m_product->productProperties, + additionalProperties); + } + + void getProduct(const QScriptValue &object) + { + if (m_lastObjectId != object.objectId()) { + m_lastObjectId = object.objectId(); + m_product = reinterpret_cast( + object.data().property(ProductPtrKey).toVariant().value()); + } + } + + ScriptEngine *qbsEngine() const { return static_cast(engine()); } + + qint64 m_lastObjectId = 0; + const ResolvedProduct *m_product = nullptr; + QScriptValue m_result; +}; + +static QScriptValue setupProjectScriptValue(ScriptEngine *engine, + const ResolvedProjectConstPtr &project) +{ + QScriptValue &obj = engine->projectScriptValue(project.get()); + if (obj.isValid()) + return obj; + obj = engine->newObject(); + obj.setProperty(StringConstants::filePathProperty(), project->location.filePath()); + obj.setProperty(StringConstants::pathProperty(), FileInfo::path(project->location.filePath())); + const QVariantMap &projectProperties = project->projectProperties(); + for (QVariantMap::const_iterator it = projectProperties.begin(); + it != projectProperties.end(); ++it) { + engine->setObservedProperty(obj, it.key(), engine->toScriptValue(it.value())); + } + engine->observer()->addProjectObjectId(obj.objectId(), project->name); + return obj; +} + +static QScriptValue setupProductScriptValue(ScriptEngine *engine, const ResolvedProduct *product); + +class DependenciesFunction +{ +public: + DependenciesFunction(ScriptEngine *engine) + : m_engine(engine) + { + } + + void init(QScriptValue &productScriptValue, QScriptValue &exportsScriptValue, + const ResolvedProduct *product) + { + QScriptValue depfunc = m_engine->newFunction(&js_internalProductDependencies, product); + productScriptValue.setProperty(StringConstants::dependenciesProperty(), depfunc, + QScriptValue::ReadOnly | QScriptValue::Undeletable + | QScriptValue::PropertyGetter); + depfunc = m_engine->newFunction(&js_exportedProductDependencies, product); + exportsScriptValue.setProperty(StringConstants::dependenciesProperty(), depfunc, + QScriptValue::ReadOnly | QScriptValue::Undeletable + | QScriptValue::PropertyGetter); + } + +private: + enum class DependencyType { Internal, Exported }; + static QScriptValue js_productDependencies(QScriptContext *, ScriptEngine *engine, + const ResolvedProduct *product, DependencyType depType) + { + QScriptValue result = engine->newArray(); + quint32 idx = 0; + const bool exportCase = depType == DependencyType::Exported; + std::vector productDeps; + if (exportCase) { + if (!product->exportedModule.productDependencies.empty()) { + const auto allProducts = product->topLevelProject()->allProducts(); + const auto getProductForName = [&allProducts](const QString &name) { + const auto cmp = [name](const ResolvedProductConstPtr &p) { + return p->uniqueName() == name; + }; + const auto it = std::find_if(allProducts.cbegin(), allProducts.cend(), cmp); + QBS_ASSERT(it != allProducts.cend(), return ResolvedProductPtr()); + return *it; + }; + std::transform(product->exportedModule.productDependencies.cbegin(), + product->exportedModule.productDependencies.cend(), + std::back_inserter(productDeps), getProductForName); + } + } else { + productDeps = product->dependencies; + } + for (const ResolvedProductPtr &dependency : qAsConst(productDeps)) { + QScriptValue obj = engine->newObject(engine->productPropertyScriptClass()); + obj.setPrototype(setupProductScriptValue(static_cast(engine), + dependency.get())); + const QVariantMap ¶ms + = (exportCase ? product->exportedModule.dependencyParameters.value(dependency) + : product->dependencyParameters.value(dependency)); + QScriptValue data = getDataForProductScriptValue(engine, dependency.get()); + data.setProperty(DependencyParametersKey, dependencyParametersValue( + product->uniqueName(), dependency->name, params, engine)); + obj.setData(data); + result.setProperty(idx++, obj); + } + if (exportCase) { + for (const ExportedModuleDependency &m : product->exportedModule.moduleDependencies) { + QScriptValue obj = engine->newObject(); + obj.setProperty(StringConstants::nameProperty(), m.name); + QScriptValue exportsValue = engine->newObject(); + obj.setProperty(exportsProperty(), exportsValue); + exportsValue.setProperty(StringConstants::dependenciesProperty(), + engine->newArray()); + for (auto modIt = m.moduleProperties.begin(); modIt != m.moduleProperties.end(); + ++modIt) { + const QVariantMap entries = modIt.value().toMap(); + if (entries.empty()) + continue; + QScriptValue moduleObj = engine->newObject(); + ModuleProperties::setModuleScriptValue(exportsValue, moduleObj, modIt.key()); + for (auto valIt = entries.begin(); valIt != entries.end(); ++valIt) + moduleObj.setProperty(valIt.key(), engine->toScriptValue(valIt.value())); + } + result.setProperty(idx++, obj); + } + return result; + } + for (const auto &dependency : product->modules) { + if (dependency->isProduct) + continue; + QScriptValue obj = engine->newObject(engine->modulePropertyScriptClass()); + obj.setPrototype(engine->moduleScriptValuePrototype(dependency.get())); + + // The prototype must exist already, because we set it up for all modules + // of the product in ModuleProperties::init(). + QBS_ASSERT(obj.prototype().isValid(), ;); + + const QVariantMap ¶ms = product->moduleParameters.value(dependency); + QScriptValue data = getDataForModuleScriptValue(engine, product, nullptr, + dependency.get()); + data.setProperty(DependencyParametersKey, dependencyParametersValue( + product->uniqueName(), dependency->name, params, engine)); + obj.setData(data); + result.setProperty(idx++, obj); + } + return result; + } + + static QScriptValue js_internalProductDependencies(QScriptContext *ctx, ScriptEngine *engine, + const ResolvedProduct * const product) + { + engine->addDependenciesArrayRequested(product); + return js_productDependencies(ctx, engine, product, DependencyType::Internal); + } + + static QScriptValue js_exportedProductDependencies(QScriptContext *ctx, ScriptEngine *engine, + const ResolvedProduct * const product) + { + return js_productDependencies(ctx, engine, product, DependencyType::Exported); + } + + ScriptEngine *m_engine; +}; + +static QScriptValue setupExportedPropertyScriptValue(const ExportedProperty &property, + ScriptEngine *engine) +{ + QScriptValue propertyScriptValue = engine->newObject(); + propertyScriptValue.setProperty(StringConstants::nameProperty(), property.fullName); + propertyScriptValue.setProperty(StringConstants::typeProperty(), + PropertyDeclaration::typeString(property.type)); + propertyScriptValue.setProperty(StringConstants::sourceCodeProperty(), property.sourceCode); + propertyScriptValue.setProperty(QStringLiteral("isBuiltin"), property.isBuiltin); + return propertyScriptValue; +} + +static void setupExportedPropertiesScriptValue(QScriptValue &parentObject, + const std::vector &properties, + ScriptEngine *engine) +{ + QScriptValue propertiesScriptValue = engine->newArray(static_cast(properties.size())); + parentObject.setProperty(QStringLiteral("properties"), propertiesScriptValue); + quint32 arrayIndex = 0; + for (const ExportedProperty &p : properties) { + propertiesScriptValue.setProperty(arrayIndex++, + setupExportedPropertyScriptValue(p, engine)); + } +} + +static QScriptValue setupExportedItemScriptValue(const ExportedItem *item, ScriptEngine *engine) +{ + QScriptValue itemScriptValue = engine->newObject(); + itemScriptValue.setProperty(StringConstants::nameProperty(), item->name); + setupExportedPropertiesScriptValue(itemScriptValue, item->properties, engine); + QScriptValue childrenScriptValue = engine->newArray(static_cast(item->children.size())); + itemScriptValue.setProperty(childItemsProperty(), childrenScriptValue); + quint32 arrayIndex = 0; + for (const auto &childItem : item->children) { + childrenScriptValue.setProperty(arrayIndex++, + setupExportedItemScriptValue(childItem.get(), engine)); + } + return itemScriptValue; +} + +static QScriptValue setupExportsScriptValue(const ExportedModule &module, ScriptEngine *engine) +{ + QScriptValue exportsScriptValue = engine->newObject(); + for (auto it = module.propertyValues.cbegin(); it != module.propertyValues.cend(); ++it) + exportsScriptValue.setProperty(it.key(), engine->toScriptValue(it.value())); + setupExportedPropertiesScriptValue(exportsScriptValue, module.m_properties, engine); + QScriptValue childrenScriptValue = engine->newArray(static_cast(module.children.size())); + exportsScriptValue.setProperty(childItemsProperty(), childrenScriptValue); + quint32 arrayIndex = 0; + for (const auto &exportedItem : module.children) { + childrenScriptValue.setProperty(arrayIndex++, + setupExportedItemScriptValue(exportedItem.get(), engine)); + } + QScriptValue importsScriptValue = engine->newArray(module.importStatements.size()); + exportsScriptValue.setProperty(StringConstants::importsProperty(), importsScriptValue); + arrayIndex = 0; + for (const QString &importStatement : module.importStatements) + importsScriptValue.setProperty(arrayIndex++, importStatement); + for (auto it = module.modulePropertyValues.cbegin(); it != module.modulePropertyValues.cend(); + ++it) { + const QVariantMap entries = it.value().toMap(); + if (entries.empty()) + continue; + QScriptValue moduleObject = engine->newObject(); + ModuleProperties::setModuleScriptValue(exportsScriptValue, moduleObject, it.key()); + for (auto valIt = entries.begin(); valIt != entries.end(); ++valIt) + moduleObject.setProperty(valIt.key(), engine->toScriptValue(valIt.value())); + } + return exportsScriptValue; +} + +static QScriptValue setupProductScriptValue(ScriptEngine *engine, const ResolvedProduct *product) +{ + QScriptValue &productScriptValue = engine->productScriptValuePrototype(product); + if (productScriptValue.isValid()) + return productScriptValue; + productScriptValue = engine->newObject(); + ModuleProperties::init(productScriptValue, product); + + QScriptValue artifactsFunc = engine->newFunction(&artifactsScriptValueForProduct, product); + productScriptValue.setProperty(StringConstants::artifactsProperty(), artifactsFunc, + QScriptValue::ReadOnly | QScriptValue::Undeletable + | QScriptValue::PropertyGetter); + + QScriptValue exportsScriptValue = setupExportsScriptValue(product->exportedModule, engine); + DependenciesFunction(engine).init(productScriptValue, exportsScriptValue, product); + engine->setObservedProperty(productScriptValue, exportsProperty(), exportsScriptValue); + engine->observer()->addExportsObjectId(exportsScriptValue.objectId(), product); + return productScriptValue; +} + +void setupScriptEngineForFile(ScriptEngine *engine, const FileContextBaseConstPtr &fileContext, + QScriptValue targetObject, const ObserveMode &observeMode) +{ + engine->import(fileContext, targetObject, observeMode); + JsExtensions::setupExtensions(fileContext->jsExtensions(), targetObject); +} + +void setupScriptEngineForProduct(ScriptEngine *engine, ResolvedProduct *product, + const ResolvedModule *module, QScriptValue targetObject, + bool setBuildEnvironment) +{ + QScriptValue projectScriptValue = setupProjectScriptValue(engine, product->project.lock()); + targetObject.setProperty(StringConstants::projectVar(), projectScriptValue); + + if (setBuildEnvironment) { + QVariant v; + v.setValue(&product->buildEnvironment); + engine->setProperty(StringConstants::qbsProcEnvVarInternal(), v); + } + QScriptClass *scriptClass = engine->productPropertyScriptClass(); + if (!scriptClass) { + scriptClass = new ProductPropertyScriptClass(engine); + engine->setProductPropertyScriptClass(scriptClass); + } + QScriptValue productScriptValue = engine->newObject(scriptClass); + productScriptValue.setPrototype(setupProductScriptValue(engine, product)); + targetObject.setProperty(StringConstants::productVar(), productScriptValue); + + QScriptValue data = getDataForProductScriptValue(engine, product); + // If the Rule is in a Module, set up the 'moduleName' property + if (!module->name.isEmpty()) + data.setProperty(ModuleNameKey, module->name); + productScriptValue.setData(data); +} + +bool findPath(BuildGraphNode *u, BuildGraphNode *v, QList &path) +{ + if (u == v) { + path.push_back(v); + return true; + } + + for (BuildGraphNode * const childNode : qAsConst(u->children)) { + if (findPath(childNode, v, path)) { + path.prepend(u); + return true; + } + } + + return false; +} + +/* + * 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).noquote() << "connect" << p->toString() << "->" << c->toString(); + if (c->type() == BuildGraphNode::ArtifactNodeType) { + auto const ac = static_cast(c); + for (const Artifact *child : filterByType(p->children)) { + if (child == ac) + return; + const bool filePathsMustBeDifferent = child->artifactType == Artifact::Generated + || child->product == ac->product || child->artifactType != ac->artifactType; + if (filePathsMustBeDifferent && child->filePath() == ac->filePath()) { + throw ErrorInfo(QStringLiteral("%1 already has a child artifact %2 as " + "different object.").arg(p->toString(), + ac->filePath()), + CodeLocation(), true); + } + } + } + p->children.insert(c); + c->parents.insert(p); + p->product->topLevelProject()->buildData->setDirty(); +} + +static bool existsPath_impl(BuildGraphNode *u, BuildGraphNode *v, NodeSet *seen) +{ + if (u == v) + return true; + + if (!seen->insert(u).second) + return false; + + for (BuildGraphNode * const childNode : qAsConst(u->children)) { + if (existsPath_impl(childNode, v, seen)) + return true; + } + + return false; +} + +static bool existsPath(BuildGraphNode *u, BuildGraphNode *v) +{ + NodeSet seen; + return existsPath_impl(u, v, &seen); +} + +static QStringList toStringList(const QList &path) +{ + QStringList lst; + for (BuildGraphNode *node : path) + lst << node->toString(); + return lst; +} + +bool safeConnect(Artifact *u, Artifact *v) +{ + QBS_CHECK(u != v); + qCDebug(lcBuildGraph) << "safeConnect:" << relativeArtifactFileName(u) + << "->" << relativeArtifactFileName(v); + + if (existsPath(v, u)) { + QList circle; + findPath(v, u, circle); + qCDebug(lcBuildGraph) << "safeConnect: circle detected " << toStringList(circle); + return false; + } + + connect(u, v); + return true; +} + +void disconnect(BuildGraphNode *u, BuildGraphNode *v) +{ + qCDebug(lcBuildGraph).noquote() << "disconnect:" << u->toString() << v->toString(); + u->children.remove(v); + v->parents.remove(u); + u->onChildDisconnected(v); +} + +void removeGeneratedArtifactFromDisk(Artifact *artifact, const Logger &logger) +{ + if (artifact->artifactType != Artifact::Generated) + return; + removeGeneratedArtifactFromDisk(artifact->filePath(), logger); +} + +void removeGeneratedArtifactFromDisk(const QString &filePath, const Logger &logger) +{ + QFile file(filePath); + if (!file.exists()) + return; + logger.qbsDebug() << "removing " << filePath; + if (!file.remove()) + logger.qbsWarning() << QStringLiteral("Cannot remove '%1'.").arg(filePath); +} + +QString relativeArtifactFileName(const Artifact *artifact) +{ + const QString &buildDir = artifact->product->topLevelProject()->buildDirectory; + QString str = artifact->filePath(); + if (str.startsWith(buildDir)) + str.remove(0, buildDir.size()); + if (str.startsWith(QLatin1Char('/'))) + str.remove(0, 1); + return str; +} + +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, + const ProjectBuildData *projectBuildData, const QString &dirPath, const QString &fileName, + bool compareByName) +{ + for (const auto &fileResource : projectBuildData->lookupFiles(dirPath, fileName)) { + if (fileResource->fileType() != FileResourceBase::FileTypeArtifact) + continue; + const auto artifact = static_cast(fileResource); + if (compareByName + ? artifact->product->uniqueName() == product->uniqueName() + : artifact->product == product) { + return artifact; + } + } + return nullptr; +} + +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const QString &dirPath, + const QString &fileName, bool compareByName) +{ + return lookupArtifact(product, product->topLevelProject()->buildData.get(), dirPath, fileName, + compareByName); +} + +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const QString &filePath, + bool compareByName) +{ + QString dirPath, fileName; + FileInfo::splitIntoDirectoryAndFileName(filePath, &dirPath, &fileName); + return lookupArtifact(product, dirPath, fileName, compareByName); +} + +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const ProjectBuildData *buildData, + const QString &filePath, bool compareByName) +{ + QString dirPath, fileName; + FileInfo::splitIntoDirectoryAndFileName(filePath, &dirPath, &fileName); + return lookupArtifact(product, buildData, dirPath, fileName, compareByName); +} + +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const Artifact *artifact, + bool compareByName) +{ + return lookupArtifact(product, artifact->dirPath(), artifact->fileName(), compareByName); +} + +Artifact *createArtifact(const ResolvedProductPtr &product, + const SourceArtifactConstPtr &sourceArtifact) +{ + const auto artifact = new Artifact; + artifact->artifactType = Artifact::SourceFile; + setArtifactData(artifact, sourceArtifact); + insertArtifact(product, artifact); + return artifact; +} + +void setArtifactData(Artifact *artifact, const SourceArtifactConstPtr &sourceArtifact) +{ + artifact->targetOfModule = sourceArtifact->targetOfModule; + artifact->setFilePath(sourceArtifact->absoluteFilePath); + artifact->setFileTags(sourceArtifact->fileTags); + artifact->properties = sourceArtifact->properties; +} + +void updateArtifactFromSourceArtifact(const ResolvedProductPtr &product, + const SourceArtifactConstPtr &sourceArtifact) +{ + Artifact * const artifact = lookupArtifact(product, sourceArtifact->absoluteFilePath, false); + QBS_CHECK(artifact); + const FileTags oldFileTags = artifact->fileTags(); + const QVariantMap oldModuleProperties = artifact->properties->value(); + setArtifactData(artifact, sourceArtifact); + if (oldFileTags != artifact->fileTags() + || oldModuleProperties != artifact->properties->value()) { + invalidateArtifactAsRuleInputIfNecessary(artifact); + } +} + +void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact) +{ + qCDebug(lcBuildGraph) << "insert artifact" << artifact->filePath(); + QBS_CHECK(!artifact->product); + QBS_CHECK(!artifact->filePath().isEmpty()); + artifact->product = product; + product->topLevelProject()->buildData->insertIntoLookupTable(artifact); + product->buildData->addArtifact(artifact); +} + +void provideFullFileTagsAndProperties(Artifact *artifact) +{ + artifact->properties = artifact->product->moduleProperties; + FileTags allTags = artifact->pureFileTags.empty() + ? artifact->product->fileTagsForFileName(artifact->fileName()) : artifact->pureFileTags; + for (const auto &props : artifact->product->artifactProperties) { + if (allTags.intersects(props->fileTagsFilter())) { + artifact->properties = props->propertyMap(); + allTags += props->extraFileTags(); + break; + } + } + artifact->setFileTags(allTags); + + // Let a positive value of qbs.install imply the file tag "installable". + if (artifact->properties->qbsPropertyValue(StringConstants::installProperty()).toBool()) + artifact->addFileTag("installable"); +} + +void applyPerArtifactProperties(Artifact *artifact) +{ + if (artifact->pureProperties.empty()) + return; + QVariantMap props = artifact->properties->value(); + for (const auto &property : artifact->pureProperties) + setConfigProperty(props, property.first, property.second); + artifact->properties = artifact->properties->clone(); + artifact->properties->setValue(props); +} + +void updateGeneratedArtifacts(ResolvedProduct *product) +{ + if (!product->buildData) + return; + for (Artifact * const artifact : filterByType(product->buildData->allNodes())) { + if (artifact->artifactType == Artifact::Generated) { + const FileTags oldFileTags = artifact->fileTags(); + const QVariantMap oldModuleProperties = artifact->properties->value(); + provideFullFileTagsAndProperties(artifact); + applyPerArtifactProperties(artifact); + if (oldFileTags != artifact->fileTags() + || oldModuleProperties != artifact->properties->value()) { + invalidateArtifactAsRuleInputIfNecessary(artifact); + } + } + } +} + +// This is needed for artifacts which are inputs to rules whose outputArtifacts script +// returned an empty array for this input. Since there is no transformer, our usual change +// tracking procedure will not notice if the artifact's file tags or module properties have +// changed, so we need to force a re-run of the outputArtifacts script. +void invalidateArtifactAsRuleInputIfNecessary(Artifact *artifact) +{ + for (RuleNode * const parentRuleNode : filterByType(artifact->parents)) { + if (!parentRuleNode->rule()->isDynamic()) + continue; + bool artifactNeedsExplicitInvalidation = true; + for (Artifact * const output : filterByType(parentRuleNode->parents)) { + if (output->children.contains(artifact) + && !output->childrenAddedByScanner.contains(artifact)) { + artifactNeedsExplicitInvalidation = false; + break; + } + } + if (artifactNeedsExplicitInvalidation) + parentRuleNode->removeOldInputArtifact(artifact); + } +} + +static void doSanityChecksForProduct(const ResolvedProductConstPtr &product, + const Set &allProducts, const Logger &logger) +{ + qCDebug(lcBuildGraph) << "Sanity checking product" << product->uniqueName(); + CycleDetector cycleDetector(logger); + cycleDetector.visitProduct(product); + const ProductBuildData * const buildData = product->buildData.get(); + for (const auto &m : product->modules) + QBS_CHECK(m->product == product.get()); + qCDebug(lcBuildGraph) << "enabled:" << product->enabled << "build data:" << buildData; + if (product->enabled) + QBS_CHECK(buildData); + if (!product->buildData) + return; + for (BuildGraphNode * const node : qAsConst(buildData->rootNodes())) { + qCDebug(lcBuildGraph).noquote() << "Checking root node" << node->toString(); + QBS_CHECK(buildData->allNodes().contains(node)); + } + Set filePaths; + for (BuildGraphNode * const node : qAsConst(buildData->allNodes())) { + qCDebug(lcBuildGraph).noquote() << "Sanity checking node" << node->toString(); + QBS_CHECK(node->product == product); + for (const BuildGraphNode * const parent : qAsConst(node->parents)) + QBS_CHECK(parent->children.contains(node)); + for (BuildGraphNode * const child : qAsConst(node->children)) { + QBS_CHECK(child->parents.contains(node)); + QBS_CHECK(!child->product.expired()); + QBS_CHECK(child->product->buildData); + QBS_CHECK(child->product->buildData->allNodes().contains(child)); + QBS_CHECK(allProducts.contains(child->product.lock())); + } + + Artifact * const artifact = node->type() == BuildGraphNode::ArtifactNodeType + ? static_cast(node) : nullptr; + if (!artifact) { + QBS_CHECK(node->type() == BuildGraphNode::RuleNodeType); + auto const ruleNode = static_cast(node); + 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(contains(product->rules, std::const_pointer_cast(ruleNode->rule()))); + continue; + } + + QBS_CHECK(product->topLevelProject()->buildData->fileDependencies.contains( + artifact->fileDependencies)); + QBS_CHECK(artifact->artifactType == Artifact::SourceFile || + !filePaths.contains(artifact->filePath())); + filePaths << artifact->filePath(); + + for (Artifact * const child : qAsConst(artifact->childrenAddedByScanner)) + QBS_CHECK(artifact->children.contains(child)); + const TransformerConstPtr transformer = artifact->transformer; + if (artifact->artifactType == Artifact::SourceFile) + continue; + + const auto parentRuleNodes = filterByType(artifact->children); + QBS_CHECK(std::distance(parentRuleNodes.begin(), parentRuleNodes.end()) == 1); + + 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)); + QBS_CHECK(contains(product->rules, std::const_pointer_cast(transformer->rule))); + qCDebug(lcBuildGraph) + << "The transformer has" << transformer->outputs.size() << "outputs."; + ArtifactSet transformerOutputChildren; + for (const Artifact * const output : qAsConst(transformer->outputs)) { + QBS_CHECK(output->transformer == transformer); + transformerOutputChildren.unite(ArtifactSet::filtered(output->children)); + for (const Artifact *a : filterByType(output->children)) { + for (const Artifact *other : filterByType(output->children)) { + if (other != a && other->filePath() == a->filePath() + && (other->artifactType != Artifact::SourceFile + || a->artifactType != Artifact::SourceFile + || other->product == a->product)) { + throw ErrorInfo(QStringLiteral("There is more than one artifact for " + "file '%1' in the child list for output '%2'.") + .arg(a->filePath(), output->filePath()), CodeLocation(), true); + } + } + } + } + if (lcBuildGraph().isDebugEnabled()) { + qCDebug(lcBuildGraph) << "The transformer output children are:"; + for (const Artifact * const a : qAsConst(transformerOutputChildren)) + qCDebug(lcBuildGraph) << "\t" << a->fileName(); + qCDebug(lcBuildGraph) << "The transformer inputs are:"; + for (const Artifact * const a : qAsConst(transformer->inputs)) + qCDebug(lcBuildGraph) << "\t" << a->fileName(); + } + QBS_CHECK(transformer->inputs.size() <= transformerOutputChildren.size()); + for (Artifact * const transformerInput : qAsConst(transformer->inputs)) + QBS_CHECK(transformerOutputChildren.contains(transformerInput)); + transformer->artifactsMapRequestedInPrepareScript.doSanityChecks(); + transformer->artifactsMapRequestedInCommands.doSanityChecks(); + } +} + +static void doSanityChecks(const ResolvedProjectPtr &project, + const Set &allProducts, Set &productNames, + const Logger &logger) +{ + logger.qbsDebug() << "Sanity checking project '" << project->name << "'"; + for (const ResolvedProjectPtr &subProject : qAsConst(project->subProjects)) + doSanityChecks(subProject, allProducts, productNames, logger); + + for (const auto &product : project->products) { + QBS_CHECK(product->project == project); + QBS_CHECK(product->topLevelProject() == project->topLevelProject()); + doSanityChecksForProduct(product, allProducts, logger); + QBS_CHECK(!productNames.contains(product->uniqueName())); + productNames << product->uniqueName(); + } +} + +void doSanityChecks(const ResolvedProjectPtr &project, const Logger &logger) +{ + if (qEnvironmentVariableIsEmpty("QBS_SANITY_CHECKS")) + return; + Set productNames; + const Set allProducts + = Set::fromStdVector(project->allProducts()); + doSanityChecks(project, allProducts, productNames, logger); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/buildgraph.h b/src/lib/corelib/buildgraph/buildgraph.h new file mode 100644 index 00000000..2909b06b --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraph.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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 QBS_BUILDGRAPH_H +#define QBS_BUILDGRAPH_H + +#include "forward_decls.h" +#include +#include + +#include + +#include + +namespace qbs { +namespace Internal { +class BuildGraphNode; +class Logger; +class ScriptEngine; +class PrepareScriptObserver; + +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, + const ProjectBuildData *projectBuildData, + const QString &dirPath, const QString &fileName, + bool compareByName = false); +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const QString &dirPath, + const QString &fileName, bool compareByName = false); +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const ProjectBuildData *buildData, + const QString &filePath, bool compareByName = false); +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const QString &filePath, + bool compareByName = false); +Artifact *lookupArtifact(const ResolvedProductConstPtr &product, const Artifact *artifact, + bool compareByName); + +Artifact *createArtifact(const ResolvedProductPtr &product, + const SourceArtifactConstPtr &sourceArtifact); +void setArtifactData(Artifact *artifact, const SourceArtifactConstPtr &sourceArtifact); +void updateArtifactFromSourceArtifact(const ResolvedProductPtr &product, + const SourceArtifactConstPtr &sourceArtifact); +void insertArtifact(const ResolvedProductPtr &product, Artifact *artifact); +void dumpProductBuildData(const ResolvedProductConstPtr &product); +void provideFullFileTagsAndProperties(Artifact *artifact); +void applyPerArtifactProperties(Artifact *artifact); +void updateGeneratedArtifacts(ResolvedProduct *product); +void invalidateArtifactAsRuleInputIfNecessary(Artifact *artifact); + +bool findPath(BuildGraphNode *u, BuildGraphNode *v, QList &path); +void QBS_AUTOTEST_EXPORT connect(BuildGraphNode *p, BuildGraphNode *c); +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); + +void setupScriptEngineForFile(ScriptEngine *engine, const FileContextBaseConstPtr &fileContext, + QScriptValue targetObject, const ObserveMode &observeMode); +void setupScriptEngineForProduct(ScriptEngine *engine, ResolvedProduct *product, + const ResolvedModule *module, QScriptValue targetObject, + bool setBuildEnvironment); +QString relativeArtifactFileName(const Artifact *artifact); // Debugging helpers + +void doSanityChecks(const ResolvedProjectPtr &project, const Logger &logger); + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILDGRAPH_H diff --git a/src/lib/corelib/buildgraph/buildgraph.pri b/src/lib/corelib/buildgraph/buildgraph.pri new file mode 100644 index 00000000..2ed6be4f --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraph.pri @@ -0,0 +1,89 @@ +include(../../../install_prefix.pri) + +SOURCES += \ + $$PWD/abstractcommandexecutor.cpp \ + $$PWD/artifact.cpp \ + $$PWD/artifactcleaner.cpp \ + $$PWD/artifactsscriptvalue.cpp \ + $$PWD/artifactvisitor.cpp \ + $$PWD/buildgraph.cpp \ + $$PWD/buildgraphloader.cpp \ + $$PWD/buildgraphnode.cpp \ + $$PWD/cycledetector.cpp \ + $$PWD/dependencyparametersscriptvalue.cpp \ + $$PWD/depscanner.cpp \ + $$PWD/emptydirectoriesremover.cpp \ + $$PWD/environmentscriptrunner.cpp \ + $$PWD/executor.cpp \ + $$PWD/executorjob.cpp \ + $$PWD/filedependency.cpp \ + $$PWD/inputartifactscanner.cpp \ + $$PWD/jscommandexecutor.cpp \ + $$PWD/nodeset.cpp \ + $$PWD/nodetreedumper.cpp \ + $$PWD/processcommandexecutor.cpp \ + $$PWD/productbuilddata.cpp \ + $$PWD/productinstaller.cpp \ + $$PWD/projectbuilddata.cpp \ + $$PWD/qtmocscanner.cpp \ + $$PWD/rawscanneddependency.cpp \ + $$PWD/rawscanresults.cpp \ + $$PWD/requestedartifacts.cpp \ + $$PWD/requesteddependencies.cpp \ + $$PWD/rulecommands.cpp \ + $$PWD/rulegraph.cpp \ + $$PWD/rulenode.cpp \ + $$PWD/rulesapplicator.cpp \ + $$PWD/rulesevaluationcontext.cpp \ + $$PWD/timestampsupdater.cpp \ + $$PWD/transformerchangetracking.cpp \ + $$PWD/transformer.cpp + +HEADERS += \ + $$PWD/abstractcommandexecutor.h \ + $$PWD/artifact.h \ + $$PWD/artifactcleaner.h \ + $$PWD/artifactsscriptvalue.h \ + $$PWD/artifactvisitor.h \ + $$PWD/buildgraph.h \ + $$PWD/buildgraphloader.h \ + $$PWD/buildgraphnode.h \ + $$PWD/buildgraphvisitor.h \ + $$PWD/cycledetector.h \ + $$PWD/dependencyparametersscriptvalue.h \ + $$PWD/depscanner.h \ + $$PWD/emptydirectoriesremover.h \ + $$PWD/environmentscriptrunner.h \ + $$PWD/executor.h \ + $$PWD/executorjob.h \ + $$PWD/filedependency.h \ + $$PWD/forward_decls.h \ + $$PWD/inputartifactscanner.h \ + $$PWD/jscommandexecutor.h \ + $$PWD/nodeset.h \ + $$PWD/nodetreedumper.h \ + $$PWD/processcommandexecutor.h \ + $$PWD/productbuilddata.h \ + $$PWD/productinstaller.h \ + $$PWD/projectbuilddata.h \ + $$PWD/qtmocscanner.h \ + $$PWD/rawscanneddependency.h \ + $$PWD/rawscanresults.h \ + $$PWD/requestedartifacts.h \ + $$PWD/requesteddependencies.h \ + $$PWD/rescuableartifactdata.h \ + $$PWD/rulecommands.h \ + $$PWD/rulegraph.h \ + $$PWD/rulenode.h \ + $$PWD/rulesapplicator.h \ + $$PWD/rulesevaluationcontext.h \ + $$PWD/scriptclasspropertyiterator.h \ + $$PWD/timestampsupdater.h \ + $$PWD/transformerchangetracking.h \ + $$PWD/transformer.h + +!qbs_no_dev_install { + buildgraph_headers.files = $$PWD/forward_decls.h + buildgraph_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/buildgraph + INSTALLS += buildgraph_headers +} diff --git a/src/lib/corelib/buildgraph/buildgraphloader.cpp b/src/lib/corelib/buildgraph/buildgraphloader.cpp new file mode 100644 index 00000000..58a34b61 --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraphloader.cpp @@ -0,0 +1,974 @@ +/**************************************************************************** +** +** 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 "buildgraphloader.h" + +#include "buildgraph.h" +#include "cycledetector.h" +#include "emptydirectoriesremover.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "rulenode.h" +#include "rulecommands.h" +#include "rulesevaluationcontext.h" +#include "transformer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +BuildGraphLoader::BuildGraphLoader(Logger logger) : + m_logger(std::move(logger)) +{ +} + +BuildGraphLoader::~BuildGraphLoader() +{ + qDeleteAll(m_objectsToDelete); +} + +static void restoreBackPointers(const ResolvedProjectPtr &project) +{ + for (const ResolvedProductPtr &product : project->products) { + product->project = project; + if (!product->buildData) + continue; + for (BuildGraphNode * const n : qAsConst(product->buildData->allNodes())) { + if (n->type() == BuildGraphNode::ArtifactNodeType) { + project->topLevelProject()->buildData + ->insertIntoLookupTable(static_cast(n)); + } + } + } + + for (const ResolvedProjectPtr &subProject : qAsConst(project->subProjects)) { + subProject->parentProject = project; + restoreBackPointers(subProject); + } +} + +BuildGraphLoadResult BuildGraphLoader::load(const TopLevelProjectPtr &existingProject, + const SetupProjectParameters ¶meters, + const RulesEvaluationContextPtr &evalContext) +{ + m_parameters = parameters; + m_result = BuildGraphLoadResult(); + m_evalContext = evalContext; + + if (existingProject) { + QBS_CHECK(existingProject->buildData); + existingProject->buildData->evaluationContext = evalContext; + if (!checkBuildGraphCompatibility(existingProject)) + return m_result; + m_result.loadedProject = existingProject; + } else { + loadBuildGraphFromDisk(); + } + if (!m_result.loadedProject) + return m_result; + if (parameters.restoreBehavior() == SetupProjectParameters::RestoreOnly) { + for (const ErrorInfo &e : qAsConst(m_result.loadedProject->warningsEncountered)) + m_logger.printWarning(e); + return m_result; + } + QBS_CHECK(parameters.restoreBehavior() == SetupProjectParameters::RestoreAndTrackChanges); + + if (m_parameters.logElapsedTime()) { + m_wildcardExpansionEffort = 0; + m_propertyComparisonEffort = 0; + } + trackProjectChanges(); + if (m_parameters.logElapsedTime()) { + m_logger.qbsLog(LoggerInfo, true) << "\t" + << Tr::tr("Wildcard expansion took %1.") + .arg(elapsedTimeString(m_wildcardExpansionEffort)); + m_logger.qbsLog(LoggerInfo, true) << "\t" + << Tr::tr("Comparing property values took %1.") + .arg(elapsedTimeString(m_propertyComparisonEffort)); + } + return m_result; +} + +TopLevelProjectConstPtr BuildGraphLoader::loadProject(const QString &bgFilePath) +{ + class LogSink : public ILogSink { + void doPrintMessage(LoggerLevel, const QString &, const QString &) override { } + } dummySink; + Logger dummyLogger(&dummySink); + BuildGraphLocker bgLocker(bgFilePath, dummyLogger, false, nullptr); + PersistentPool pool(dummyLogger); + pool.load(bgFilePath); + const TopLevelProjectPtr project = TopLevelProject::create(); + project->load(pool); + project->setBuildConfiguration(pool.headData().projectConfig); + return project; +} + +void BuildGraphLoader::loadBuildGraphFromDisk() +{ + const QString projectId = TopLevelProject::deriveId(m_parameters.finalBuildConfigurationTree()); + const QString buildDir + = TopLevelProject::deriveBuildDirectory(m_parameters.buildRoot(), projectId); + const QString buildGraphFilePath + = ProjectBuildData::deriveBuildGraphFilePath(buildDir, projectId); + + PersistentPool pool(m_logger); + qCDebug(lcBuildGraph) << "trying to load:" << buildGraphFilePath; + try { + pool.load(buildGraphFilePath); + } catch (const NoBuildGraphError &) { + if (m_parameters.restoreBehavior() == SetupProjectParameters::RestoreOnly) + throw; + m_logger.qbsInfo() + << Tr::tr("Build graph does not yet exist for configuration '%1'. " + "Starting from scratch.").arg(m_parameters.configurationName()); + return; + } catch (const ErrorInfo &loadError) { + if (!m_parameters.overrideBuildGraphData()) { + ErrorInfo fullError = loadError; + fullError.append(Tr::tr("Use the 'resolve' command to set up a new build graph.")); + throw fullError; + } + m_logger.qbsWarning() << loadError.toString(); + return; + } + + const TopLevelProjectPtr project = TopLevelProject::create(); + + // TODO: Store some meta data that will enable us to show actual progress (e.g. number of products). + m_evalContext->initializeObserver(Tr::tr("Restoring build graph from disk"), 1); + + project->load(pool); + project->buildData->evaluationContext = m_evalContext; + project->setBuildConfiguration(pool.headData().projectConfig); + project->buildDirectory = buildDir; + if (!checkBuildGraphCompatibility(project)) + return; + restoreBackPointers(project); + project->buildData->setClean(); + project->location = CodeLocation(m_parameters.projectFilePath(), project->location.line(), + project->location.column()); + m_result.loadedProject = project; + m_evalContext->incrementProgressValue(); + doSanityChecks(project, m_logger); +} + +bool BuildGraphLoader::checkBuildGraphCompatibility(const TopLevelProjectConstPtr &project) +{ + if (m_parameters.projectFilePath().isEmpty()) + m_parameters.setProjectFilePath(project->location.filePath()); + else + Loader::setupProjectFilePath(m_parameters); + if (QFileInfo(project->location.filePath()) == QFileInfo(m_parameters.projectFilePath())) + return true; + QString message = Tr::tr("Stored build graph at '%1' is for project file '%2', but " + "input file is '%3'.") + .arg(QDir::toNativeSeparators(project->buildGraphFilePath()), + QDir::toNativeSeparators(project->location.filePath()), + QDir::toNativeSeparators(m_parameters.projectFilePath())); + if (m_parameters.overrideBuildGraphData()) { + m_logger.qbsInfo() << message; + return false; + } + message.append(QLatin1Char('\n')).append(Tr::tr("Use the 'resolve' command to enforce " + "using a different project file.")); + throw ErrorInfo(message); +} + +static bool checkProductForChangedDependency(std::vector &changedProducts, + Set &seenProducts, const ResolvedProductPtr &product) +{ + if (seenProducts.contains(product)) + return false; + if (contains(changedProducts, product)) + return true; + for (const ResolvedProductPtr &dep : qAsConst(product->dependencies)) { + if (checkProductForChangedDependency(changedProducts, seenProducts, dep)) { + changedProducts << product; + return true; + } + } + seenProducts << product; + return false; +} + +// All products depending on changed products also become changed. Otherwise the output +// artifacts of the rules taking the artifacts from the dependency as inputs will be +// rebuilt due to their rule getting re-applied (as the rescued input artifacts will show +// up as newly added) and no rescue data being available. +static void makeChangedProductsListComplete(std::vector &changedProducts, + const std::vector &allRestoredProducts) +{ + Set seenProducts; + for (const ResolvedProductPtr &p : allRestoredProducts) + checkProductForChangedDependency(changedProducts, seenProducts, p); +} + +static void updateProductAndRulePointers(const ResolvedProductPtr &newProduct) +{ + std::unordered_map ruleMap; + for (BuildGraphNode *node : qAsConst(newProduct->buildData->allNodes())) { + 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 auto &r : qAsConst(newProduct->rules)) { + if (*r == *oldRule) { + ruleMap.insert(std::make_pair(oldRule, r)); + return r; + } + } + QBS_CHECK(false); + return {}; + }; + if (node->type() == BuildGraphNode::RuleNodeType) { + const auto ruleNode = static_cast(node); + ruleNode->setRule(findNewRule(ruleNode->rule())); + } else { + QBS_CHECK(node->type() == BuildGraphNode::ArtifactNodeType); + const auto artifact = static_cast(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"), + m_parameters.logElapsedTime()); + const TopLevelProjectPtr &restoredProject = m_result.loadedProject; + Set buildSystemFiles = restoredProject->buildSystemFiles; + std::vector allRestoredProducts = restoredProject->allProducts(); + std::vector changedProducts; + bool reResolvingNecessary = false; + if (!checkConfigCompatibility()) + reResolvingNecessary = true; + if (hasProductFileChanged(allRestoredProducts, restoredProject->lastStartResolveTime, + buildSystemFiles, changedProducts)) { + reResolvingNecessary = true; + } + + // "External" changes, e.g. in the environment or in a JavaScript file, + // can make the list of source files in a product change without the respective file + // having been touched. In such a case, the build data for that product will have to be set up + // anew. + if (probeExecutionForced(restoredProject, allRestoredProducts) + || hasBuildSystemFileChanged(buildSystemFiles, restoredProject.get()) + || hasEnvironmentChanged(restoredProject) + || hasCanonicalFilePathResultChanged(restoredProject) + || hasFileExistsResultChanged(restoredProject) + || hasDirectoryEntriesResultChanged(restoredProject) + || hasFileLastModifiedResultChanged(restoredProject)) { + reResolvingNecessary = true; + } + + if (!reResolvingNecessary) { + for (const ErrorInfo &e : qAsConst(restoredProject->warningsEncountered)) + m_logger.printWarning(e); + return; + } + + restoredProject->buildData->setDirty(); + markTransformersForChangeTracking(allRestoredProducts); + if (!m_parameters.overrideBuildGraphData()) + m_parameters.setEnvironment(restoredProject->environment); + Loader ldr(m_evalContext->engine(), m_logger); + ldr.setSearchPaths(m_parameters.searchPaths()); + ldr.setProgressObserver(m_evalContext->observer()); + ldr.setOldProjectProbes(restoredProject->probes); + if (!m_parameters.forceProbeExecution()) + ldr.setStoredModuleProviderInfo(restoredProject->moduleProviderInfo); + ldr.setLastResolveTime(restoredProject->lastStartResolveTime); + QHash> restoredProbes; + for (const auto &restoredProduct : qAsConst(allRestoredProducts)) + restoredProbes.insert(restoredProduct->uniqueName(), restoredProduct->probes); + ldr.setOldProductProbes(restoredProbes); + if (!m_parameters.overrideBuildGraphData()) + ldr.setStoredProfiles(restoredProject->profileConfigs); + m_result.newlyResolvedProject = ldr.loadProject(m_parameters); + + std::vector allNewlyResolvedProducts + = m_result.newlyResolvedProject->allProducts(); + for (const ResolvedProductPtr &cp : qAsConst(allNewlyResolvedProducts)) + m_freshProductsByName.insert(cp->uniqueName(), cp); + + checkAllProductsForChanges(allRestoredProducts, changedProducts); + + std::shared_ptr oldBuildData; + ChildListHash childLists; + if (!changedProducts.empty()) { + oldBuildData = std::make_shared(restoredProject->buildData.get()); + for (const auto &product : qAsConst(allRestoredProducts)) { + if (!product->buildData) + continue; + + // If the product gets temporarily removed, its artifacts will get disconnected + // and this structural information will no longer be directly available from them. + for (const Artifact *a : filterByType(product->buildData->allNodes())) { + childLists.insert(a, ChildrenInfo(ArtifactSet::filtered(a->children), + a->childrenAddedByScanner)); + } + } + } + + makeChangedProductsListComplete(changedProducts, allRestoredProducts); + + // Set up build data from scratch for all changed products. This does not necessarily + // mean that artifacts will have to get rebuilt; whether this is necesessary will be decided + // an a per-artifact basis by the Executor on the next build. + QHash rescuableArtifactData; + for (const ResolvedProductPtr &product : qAsConst(changedProducts)) { + const QString name = product->uniqueName(); + m_changedSourcesByProduct.erase(name); + m_productsWhoseArtifactsNeedUpdate.remove(name); + ResolvedProductPtr freshProduct = m_freshProductsByName.value(name); + if (!freshProduct) + continue; + onProductRemoved(product, product->topLevelProject()->buildData.get(), false); + if (product->buildData) { + rescuableArtifactData.insert(product->uniqueName(), + product->buildData->rescuableArtifactData()); + } + removeOne(allRestoredProducts, product); + } + + // Move over restored build data to newly resolved project. + m_result.newlyResolvedProject->buildData.swap(restoredProject->buildData); + QBS_CHECK(m_result.newlyResolvedProject->buildData); + m_result.newlyResolvedProject->buildData->setDirty(); + + for (auto it = allNewlyResolvedProducts.begin(); it != allNewlyResolvedProducts.end();) { + const ResolvedProductPtr &newlyResolvedProduct = *it; + auto k = std::find_if(allRestoredProducts.begin(), allRestoredProducts.end(), + [&newlyResolvedProduct](const ResolvedProductPtr &restoredProduct) { + return newlyResolvedProduct->uniqueName() == restoredProduct->uniqueName(); + }); + if (k == allRestoredProducts.end()) { + ++it; + } else { + const ResolvedProductPtr &restoredProduct = *k; + if (newlyResolvedProduct->enabled) + newlyResolvedProduct->buildData.swap(restoredProduct->buildData); + if (newlyResolvedProduct->buildData) + updateProductAndRulePointers(newlyResolvedProduct); + + // Keep in list if build data still needs to be resolved. + if (!newlyResolvedProduct->enabled || newlyResolvedProduct->buildData) + it = allNewlyResolvedProducts.erase(it); + + allRestoredProducts.erase(k); + } + } + + // Products still left in the list do not exist anymore. + for (const ResolvedProductPtr &removedProduct : qAsConst(allRestoredProducts)) { + removeOne(changedProducts, removedProduct); + onProductRemoved(removedProduct, m_result.newlyResolvedProject->buildData.get()); + } + + // Products still left in the list need resolving, either because they are new + // or because they are newly enabled. + if (!allNewlyResolvedProducts.empty()) { + BuildDataResolver bpr(m_logger); + bpr.resolveProductBuildDataForExistingProject(m_result.newlyResolvedProject, + allNewlyResolvedProducts); + } + + for (const auto &kv : m_changedSourcesByProduct) { + const ResolvedProductPtr product = m_freshProductsByName.value(kv.first); + QBS_CHECK(!!product); + for (const SourceArtifactConstPtr &sa : kv.second) + updateArtifactFromSourceArtifact(product, sa); + } + + for (const QString &productName : m_productsWhoseArtifactsNeedUpdate) { + const ResolvedProductPtr product = m_freshProductsByName.value(productName); + QBS_CHECK(!!product); + updateGeneratedArtifacts(product.get()); + } + + for (const auto &changedProduct : qAsConst(changedProducts)) { + rescueOldBuildData(changedProduct, + m_freshProductsByName.value(changedProduct->uniqueName()), + childLists, rescuableArtifactData.value(changedProduct->uniqueName())); + } + + EmptyDirectoriesRemover(m_result.newlyResolvedProject.get(), m_logger) + .removeEmptyParentDirectories(m_artifactsRemovedFromDisk); + + for (FileResourceBase * const f : qAsConst(m_objectsToDelete)) { + if (f->fileType() == FileResourceBase::FileTypeArtifact) + static_cast(f)->product.reset(); // To help with the sanity checks. + } + doSanityChecks(m_result.newlyResolvedProject, m_logger); +} + +bool BuildGraphLoader::probeExecutionForced( + const TopLevelProjectConstPtr &restoredProject, + const std::vector &restoredProducts) const +{ + if (!m_parameters.forceProbeExecution()) + return false; + + if (!restoredProject->probes.empty()) + return true; + + for (const auto &p : qAsConst(restoredProducts)) { + if (!p->probes.empty()) + return true; + } + + return false; +} + +bool BuildGraphLoader::hasEnvironmentChanged(const TopLevelProjectConstPtr &restoredProject) const +{ + if (!m_parameters.overrideBuildGraphData()) + return false; + QProcessEnvironment oldEnv = restoredProject->environment; + QProcessEnvironment newEnv = m_parameters.adjustedEnvironment(); + + static const QString ldPreloadEnvVar = QStringLiteral("LD_PRELOAD"); + // HACK. Valgrind screws up our null-build benchmarker otherwise. + // TODO: Think about a (module-provided) whitelist. + oldEnv.remove(ldPreloadEnvVar); + newEnv.remove(ldPreloadEnvVar); + + if (oldEnv != newEnv) { + qCDebug(lcBuildGraph) << "Set of environment variables changed. Must re-resolve project." + << "\nold:" << restoredProject->environment.toStringList() + << "\nnew:" << m_parameters.adjustedEnvironment().toStringList(); + return true; + } + return false; +} + +bool BuildGraphLoader::hasCanonicalFilePathResultChanged(const TopLevelProjectConstPtr &restoredProject) const +{ + for (auto it = restoredProject->canonicalFilePathResults.constBegin(); + it != restoredProject->canonicalFilePathResults.constEnd(); ++it) { + if (QFileInfo(it.key()).canonicalFilePath() != it.value()) { + qCDebug(lcBuildGraph) << "Canonical file path for file" << it.key() + << "changed, must re-resolve project."; + return true; + } + } + + return false; +} + +bool BuildGraphLoader::hasFileExistsResultChanged(const TopLevelProjectConstPtr &restoredProject) const +{ + for (QHash::ConstIterator it = restoredProject->fileExistsResults.constBegin(); + it != restoredProject->fileExistsResults.constEnd(); ++it) { + if (FileInfo(it.key()).exists() != it.value()) { + qCDebug(lcBuildGraph) << "Existence check for file" << it.key() + << "changed, must re-resolve project."; + return true; + } + } + + return false; +} + +bool BuildGraphLoader::hasDirectoryEntriesResultChanged(const TopLevelProjectConstPtr &restoredProject) const +{ + for (auto it = restoredProject->directoryEntriesResults.constBegin(); + it != restoredProject->directoryEntriesResults.constEnd(); ++it) { + if (QDir(it.key().first).entryList(static_cast(it.key().second), QDir::Name) + != it.value()) { + qCDebug(lcBuildGraph) << "Entry list for directory" << it.key().first + << static_cast(it.key().second) + << "changed, must re-resolve project."; + return true; + } + } + + return false; +} + +bool BuildGraphLoader::hasFileLastModifiedResultChanged(const TopLevelProjectConstPtr &restoredProject) const +{ + for (QHash::ConstIterator it + = restoredProject->fileLastModifiedResults.constBegin(); + it != restoredProject->fileLastModifiedResults.constEnd(); ++it) { + if (FileInfo(it.key()).lastModified() != it.value()) { + qCDebug(lcBuildGraph) << "Timestamp for file" << it.key() + << "changed, must re-resolve project."; + return true; + } + } + + return false; +} + +bool BuildGraphLoader::hasProductFileChanged(const std::vector &restoredProducts, + const FileTime &referenceTime, Set &remainingBuildSystemFiles, + std::vector &changedProducts) +{ + bool hasChanged = false; + for (const ResolvedProductPtr &product : restoredProducts) { + const QString filePath = product->location.filePath(); + const FileInfo pfi(filePath); + remainingBuildSystemFiles.remove(filePath); + if (!pfi.exists()) { + qCDebug(lcBuildGraph) << "A product was removed, must re-resolve project"; + hasChanged = true; + } else if (referenceTime < pfi.lastModified()) { + qCDebug(lcBuildGraph) << "A product was changed, must re-resolve project"; + hasChanged = true; + } else if (!contains(changedProducts, product)) { + bool foundMissingSourceFile = false; + for (const QString &file : qAsConst(product->missingSourceFiles)) { + if (FileInfo(file).exists()) { + qCDebug(lcBuildGraph) << "Formerly missing file" << file << "in product" + << product->name << "exists now, must re-resolve project"; + foundMissingSourceFile = true; + break; + } + } + if (foundMissingSourceFile) { + hasChanged = true; + changedProducts.push_back(product); + continue; + } + + AccumulatingTimer wildcardTimer(m_parameters.logElapsedTime() + ? &m_wildcardExpansionEffort : nullptr); + for (const GroupPtr &group : product->groups) { + if (!group->wildcards) + continue; + const bool reExpansionRequired = std::any_of( + group->wildcards->dirTimeStamps.cbegin(), + group->wildcards->dirTimeStamps.cend(), + [](const std::pair &pair) { + return FileInfo(pair.first).lastModified() > pair.second; + }); + if (!reExpansionRequired) + continue; + const Set files = group->wildcards->expandPatterns(group, + FileInfo::path(group->location.filePath()), + product->topLevelProject()->buildDirectory); + Set wcFiles; + for (const auto &sourceArtifact : group->wildcards->files) + wcFiles += sourceArtifact->absoluteFilePath; + if (files == wcFiles) + continue; + hasChanged = true; + changedProducts.push_back(product); + break; + } + } + } + + return hasChanged; +} + +bool BuildGraphLoader::hasBuildSystemFileChanged(const Set &buildSystemFiles, + const TopLevelProject *restoredProject) +{ + for (const QString &file : buildSystemFiles) { + const FileInfo fi(file); + if (!fi.exists()) { + qCDebug(lcBuildGraph) << "Project file" << file + << "no longer exists, must re-resolve project."; + return true; + } + const auto generatedChecker = [&file, restoredProject](const ModuleProviderInfo &mpi) { + return file.startsWith(mpi.outputDirPath(restoredProject->buildDirectory)); + }; + const bool fileWasCreatedByModuleProvider = any_of(restoredProject->moduleProviderInfo, + generatedChecker); + const FileTime referenceTime = fileWasCreatedByModuleProvider + ? restoredProject->lastEndResolveTime : restoredProject->lastStartResolveTime; + if (referenceTime < fi.lastModified()) { + qCDebug(lcBuildGraph) << "Project file" << file << "changed, must re-resolve project."; + return true; + } + } + return false; +} + +void BuildGraphLoader::markTransformersForChangeTracking( + const std::vector &restoredProducts) +{ + for (const ResolvedProductPtr &product : restoredProducts) { + if (!product->buildData) + continue; + for (Artifact * const artifact : filterByType(product->buildData->allNodes())) { + if (artifact->transformer) { + artifact->transformer->prepareScriptNeedsChangeTracking = true; + artifact->transformer->commandsNeedChangeTracking = true; + } + } + } +} + +void BuildGraphLoader::checkAllProductsForChanges( + const std::vector &restoredProducts, + std::vector &changedProducts) +{ + for (const ResolvedProductPtr &restoredProduct : restoredProducts) { + const ResolvedProductPtr newlyResolvedProduct + = m_freshProductsByName.value(restoredProduct->uniqueName()); + if (!newlyResolvedProduct) + continue; + if (newlyResolvedProduct->enabled != restoredProduct->enabled) { + qCDebug(lcBuildGraph) << "Condition of product" << restoredProduct->uniqueName() + << "was changed, must set up build data from scratch"; + if (!contains(changedProducts, restoredProduct)) + changedProducts << restoredProduct; + continue; + } + + if (checkProductForChanges(restoredProduct, newlyResolvedProduct)) { + qCDebug(lcBuildGraph) << "Product" << restoredProduct->uniqueName() + << "was changed, must set up build data from scratch"; + if (!contains(changedProducts, restoredProduct)) + changedProducts << restoredProduct; + continue; + } + + if (checkProductForChangesInSourceFiles(restoredProduct, newlyResolvedProduct)) { + qCDebug(lcBuildGraph) << "File list of product" << restoredProduct->uniqueName() + << "was changed."; + if (!contains(changedProducts, restoredProduct)) + changedProducts << restoredProduct; + } + } +} + +bool BuildGraphLoader::checkProductForChangesInSourceFiles( + const ResolvedProductPtr &restoredProduct, const ResolvedProductPtr &newlyResolvedProduct) +{ + std::vector oldFiles = restoredProduct->allEnabledFiles(); + std::vector newFiles = newlyResolvedProduct->allEnabledFiles(); + // TODO: Also handle added and removed files in a fine-grained manner. + if (oldFiles.size() != newFiles.size()) + return true; + static const auto cmp = [](const SourceArtifactConstPtr &a1, + const SourceArtifactConstPtr &a2) { + return a1->absoluteFilePath < a2->absoluteFilePath; + }; + std::sort(oldFiles.begin(), oldFiles.end(), cmp); + std::sort(newFiles.begin(), newFiles.end(), cmp); + std::vector changedFiles; + for (int i = 0; i < int(oldFiles.size()); ++i) { + const SourceArtifactConstPtr &oldFile = oldFiles.at(i); + const SourceArtifactConstPtr &newFile = newFiles.at(i); + if (oldFile->absoluteFilePath != newFile->absoluteFilePath) + return true; + if (*oldFile != *newFile) { + qCDebug(lcBuildGraph) << "source artifact" << oldFile->absoluteFilePath << "changed"; + changedFiles.push_back(newFile); + } + } + if (!changedFiles.empty()) { + m_changedSourcesByProduct.insert(std::make_pair(restoredProduct->uniqueName(), + changedFiles)); + } + return false; +} + +static bool dependenciesAreEqual(const ResolvedProductConstPtr &p1, + const ResolvedProductConstPtr &p2) +{ + if (p1->dependencies.size() != p2->dependencies.size()) + return false; + Set names1; + Set names2; + for (const auto &dep : qAsConst(p1->dependencies)) + names1 << dep->uniqueName(); + for (const auto &dep : qAsConst(p2->dependencies)) + names2 << dep->uniqueName(); + return names1 == names2; +} + +bool BuildGraphLoader::checkProductForChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct) +{ + // This check must come first, as it can prevent build data rescuing as a side effect. + // TODO: Similar special checks must be done for Environment.getEnv() and File.exists() in + // commands (or possibly it could be reasonable to just forbid such "dynamic" constructs + // within commands). + if (checkForPropertyChanges(restoredProduct, newlyResolvedProduct)) + return true; + if (!ruleListsAreEqual(restoredProduct->rules, newlyResolvedProduct->rules)) + return true; + if (!dependenciesAreEqual(restoredProduct, newlyResolvedProduct)) + return true; + return false; +} + +bool BuildGraphLoader::checkProductForInstallInfoChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct) +{ + // These are not requested from rules at build time, but we still need to take + // them into account. + const QStringList specialProperties = QStringList() << StringConstants::installProperty() + << StringConstants::installDirProperty() << StringConstants::installPrefixProperty() + << StringConstants::installRootProperty(); + for (const QString &key : specialProperties) { + if (restoredProduct->moduleProperties->qbsPropertyValue(key) + != newlyResolvedProduct->moduleProperties->qbsPropertyValue(key)) { + qCDebug(lcBuildGraph).noquote().nospace() + << "Product property 'qbs." << key << "' changed."; + return true; + } + } + return false; +} + +bool BuildGraphLoader::checkForPropertyChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct) +{ + AccumulatingTimer propertyComparisonTimer(m_parameters.logElapsedTime() + ? &m_propertyComparisonEffort: nullptr); + qCDebug(lcBuildGraph) << "Checking for changes in properties requested in prepare scripts for " + "product" << restoredProduct->uniqueName(); + if (!restoredProduct->buildData) + return false; + + if (restoredProduct->fileTags != newlyResolvedProduct->fileTags) { + qCDebug(lcBuildGraph) << "Product type changed from" << restoredProduct->fileTags + << "to" << newlyResolvedProduct->fileTags; + return true; + } + + if (checkProductForInstallInfoChanges(restoredProduct, newlyResolvedProduct)) + return true; + if (!artifactPropertyListsAreEqual(restoredProduct->artifactProperties, + newlyResolvedProduct->artifactProperties)) { + qCDebug(lcBuildGraph) << "a fileTagFilter group changed for product" + << restoredProduct->uniqueName(); + m_productsWhoseArtifactsNeedUpdate << restoredProduct->uniqueName(); + } + if (restoredProduct->moduleProperties != newlyResolvedProduct->moduleProperties) { + qCDebug(lcBuildGraph) << "module properties changed for product" + << restoredProduct->uniqueName(); + m_productsWhoseArtifactsNeedUpdate << restoredProduct->uniqueName(); + } + return false; +} + +void BuildGraphLoader::onProductRemoved(const ResolvedProductPtr &product, + ProjectBuildData *projectBuildData, bool removeArtifactsFromDisk) +{ + qCDebug(lcBuildGraph) << "product" << product->uniqueName() << "removed."; + + removeOne(product->project->products, product); + if (product->buildData) { + for (BuildGraphNode * const node : qAsConst(product->buildData->allNodes())) { + if (node->type() == BuildGraphNode::ArtifactNodeType) { + const auto artifact = static_cast(node); + projectBuildData->removeArtifact(artifact, m_logger, removeArtifactsFromDisk, + false); + if (removeArtifactsFromDisk && artifact->artifactType == Artifact::Generated) + m_artifactsRemovedFromDisk << artifact->filePath(); + } else { + for (BuildGraphNode * const parent : qAsConst(node->parents)) + parent->children.remove(node); + node->parents.clear(); + for (BuildGraphNode * const child : qAsConst(node->children)) + child->parents.remove(node); + node->children.clear(); + } + } + } +} + +void BuildGraphLoader::replaceFileDependencyWithArtifact(const ResolvedProductPtr &fileDepProduct, + FileDependency *filedep, Artifact *artifact) +{ + 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(product->buildData->allNodes())) { + if (artifactInProduct->fileDependencies.remove(filedep)) + connect(artifactInProduct, artifact); + } + } + fileDepProduct->topLevelProject()->buildData->fileDependencies.remove(filedep); + fileDepProduct->topLevelProject()->buildData->removeFromLookupTable(filedep); + m_objectsToDelete << filedep; +} + +bool BuildGraphLoader::checkConfigCompatibility() +{ + const TopLevelProjectConstPtr restoredProject = m_result.loadedProject; + if (m_parameters.topLevelProfile().isEmpty()) + m_parameters.setTopLevelProfile(restoredProject->profile()); + if (!m_parameters.overrideBuildGraphData()) { + if (!m_parameters.overriddenValues().empty() + && m_parameters.overriddenValues() != restoredProject->overriddenValues) { + throw ErrorInfo(Tr::tr("Property values set on the command line differ from the " + "ones used for the previous build. Use the 'resolve' command if " + "you really want to rebuild with the new properties.")); + } + m_parameters.setOverriddenValues(restoredProject->overriddenValues); + if (m_parameters.topLevelProfile() != restoredProject->profile()) { + throw ErrorInfo(Tr::tr("The current profile is '%1', but profile '%2' was used " + "when last building for configuration '%3'. Use the " + "'resolve' command if you really want to rebuild with a " + "different profile.") + .arg(m_parameters.topLevelProfile(), restoredProject->profile(), + m_parameters.configurationName())); + } + m_parameters.setTopLevelProfile(restoredProject->profile()); + m_parameters.expandBuildConfiguration(); + } + if (!m_parameters.overrideBuildGraphData()) + 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( + profile, m_parameters.configurationName()); + const QVariantMap newConfig = SetupProjectParameters::finalBuildConfigurationTree( + buildConfig, m_parameters.overriddenValues()); + if (newConfig != it.value()) + return false; + } + return true; +} + +void BuildGraphLoader::rescueOldBuildData(const ResolvedProductConstPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct, const ChildListHash &childLists, + const AllRescuableArtifactData &existingRad) +{ + QBS_CHECK(newlyResolvedProduct); + if (!restoredProduct->buildData) + return; + if (!newlyResolvedProduct->buildData) + newlyResolvedProduct->buildData = std::make_unique(); + + qCDebug(lcBuildGraph) << "rescue data of product" << restoredProduct->uniqueName(); + QBS_CHECK(newlyResolvedProduct->buildData); + QBS_CHECK(newlyResolvedProduct->buildData->rescuableArtifactData().empty()); + newlyResolvedProduct->buildData->setRescuableArtifactData(existingRad); + + // This is needed for artifacts created by rules, which happens later in the executor. + for (Artifact * const oldArtifact + : filterByType(restoredProduct->buildData->allNodes())) { + if (!oldArtifact->transformer) + continue; + Artifact * const newArtifact = lookupArtifact(newlyResolvedProduct, oldArtifact, false); + if (!newArtifact) { + RescuableArtifactData rad; + rad.timeStamp = oldArtifact->timestamp(); + rad.knownOutOfDate = oldArtifact->transformer->markedForRerun; + rad.fileTags = oldArtifact->fileTags(); + rad.properties = oldArtifact->properties; + rad.commands = oldArtifact->transformer->commands; + rad.propertiesRequestedInPrepareScript + = oldArtifact->transformer->propertiesRequestedInPrepareScript; + rad.propertiesRequestedInCommands + = oldArtifact->transformer->propertiesRequestedInCommands; + rad.propertiesRequestedFromArtifactInPrepareScript + = oldArtifact->transformer->propertiesRequestedFromArtifactInPrepareScript; + rad.propertiesRequestedFromArtifactInCommands + = oldArtifact->transformer->propertiesRequestedFromArtifactInCommands; + rad.importedFilesUsedInPrepareScript + = oldArtifact->transformer->importedFilesUsedInPrepareScript; + rad.importedFilesUsedInCommands = oldArtifact->transformer->importedFilesUsedInCommands; + rad.depsRequestedInPrepareScript + = oldArtifact->transformer->depsRequestedInPrepareScript; + rad.depsRequestedInCommands = oldArtifact->transformer->depsRequestedInCommands; + rad.artifactsMapRequestedInPrepareScript + = oldArtifact->transformer->artifactsMapRequestedInPrepareScript; + rad.artifactsMapRequestedInCommands + = oldArtifact->transformer->artifactsMapRequestedInCommands; + rad.exportedModulesAccessedInPrepareScript + = oldArtifact->transformer->exportedModulesAccessedInPrepareScript; + rad.exportedModulesAccessedInCommands + = oldArtifact->transformer->exportedModulesAccessedInCommands; + rad.lastCommandExecutionTime = oldArtifact->transformer->lastCommandExecutionTime; + rad.lastPrepareScriptExecutionTime + = oldArtifact->transformer->lastPrepareScriptExecutionTime; + const ChildrenInfo &childrenInfo = childLists.value(oldArtifact); + for (Artifact * const child : qAsConst(childrenInfo.children)) { + rad.children.emplace_back(child->product->name, + child->product->multiplexConfigurationId, child->filePath(), + childrenInfo.childrenAddedByScanner.contains(child)); + std::transform(oldArtifact->fileDependencies.cbegin(), + oldArtifact->fileDependencies.cend(), + std::back_inserter(rad.fileDependencies), + std::mem_fn(&FileDependency::filePath)); + } + newlyResolvedProduct->buildData->addRescuableArtifactData(oldArtifact->filePath(), rad); + } + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/buildgraphloader.h b/src/lib/corelib/buildgraph/buildgraphloader.h new file mode 100644 index 00000000..c62ba7fa --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraphloader.h @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** 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 QBS_BUILDGRAPHLOADER_H +#define QBS_BUILDGRAPHLOADER_H + +#include "forward_decls.h" + +#include "artifact.h" +#include "rescuableartifactdata.h" + +#include +#include +#include + +#include +#include + +#include + +namespace qbs { + +namespace Internal { +class FileDependency; +class FileResourceBase; +class FileTime; +class Property; + +class BuildGraphLoadResult +{ +public: + TopLevelProjectPtr newlyResolvedProject; + TopLevelProjectPtr loadedProject; +}; + + +class BuildGraphLoader +{ +public: + BuildGraphLoader(Logger logger); + ~BuildGraphLoader(); + + BuildGraphLoadResult load(const TopLevelProjectPtr &existingProject, + const SetupProjectParameters ¶meters, + const RulesEvaluationContextPtr &evalContext); + + static TopLevelProjectConstPtr loadProject(const QString &bgFilePath); + +private: + void loadBuildGraphFromDisk(); + bool checkBuildGraphCompatibility(const TopLevelProjectConstPtr &project); + void trackProjectChanges(); + bool probeExecutionForced(const TopLevelProjectConstPtr &restoredProject, + const std::vector &restoredProducts) const; + bool hasEnvironmentChanged(const TopLevelProjectConstPtr &restoredProject) const; + bool hasCanonicalFilePathResultChanged(const TopLevelProjectConstPtr &restoredProject) const; + bool hasFileExistsResultChanged(const TopLevelProjectConstPtr &restoredProject) const; + bool hasDirectoryEntriesResultChanged(const TopLevelProjectConstPtr &restoredProject) const; + bool hasFileLastModifiedResultChanged(const TopLevelProjectConstPtr &restoredProject) const; + bool hasProductFileChanged(const std::vector &restoredProducts, + const FileTime &referenceTime, + Set &remainingBuildSystemFiles, + std::vector &productsWithChangedFiles); + bool hasBuildSystemFileChanged(const Set &buildSystemFiles, + const TopLevelProject *restoredProject); + void markTransformersForChangeTracking(const std::vector &restoredProducts); + void checkAllProductsForChanges(const std::vector &restoredProducts, + std::vector &changedProducts); + bool checkProductForChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct); + bool checkProductForChangesInSourceFiles(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct); + bool checkProductForInstallInfoChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct); + bool checkForPropertyChanges(const ResolvedProductPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct); + QVariantMap propertyMapByKind(const ResolvedProductConstPtr &product, const Property &property); + void onProductRemoved(const ResolvedProductPtr &product, ProjectBuildData *projectBuildData, + bool removeArtifactsFromDisk = true); + bool checkForPropertyChanges(const TransformerPtr &restoredTrafo, + const ResolvedProductPtr &freshProduct); + bool checkForPropertyChange(const Property &restoredProperty, + const QVariantMap &newProperties); + void replaceFileDependencyWithArtifact(const ResolvedProductPtr &fileDepProduct, + FileDependency *filedep, Artifact *artifact); + bool checkConfigCompatibility(); + + struct ChildrenInfo { + ChildrenInfo() = default; + ChildrenInfo(ArtifactSet c1, ArtifactSet c2) + : children(std::move(c1)), childrenAddedByScanner(std::move(c2)) {} + ArtifactSet children; + ArtifactSet childrenAddedByScanner; + }; + using ChildListHash = QHash; + void rescueOldBuildData(const ResolvedProductConstPtr &restoredProduct, + const ResolvedProductPtr &newlyResolvedProduct, + const ChildListHash &childLists, + const AllRescuableArtifactData &existingRad); + + QMap m_freshProductsByName; + RulesEvaluationContextPtr m_evalContext; + SetupProjectParameters m_parameters; + BuildGraphLoadResult m_result; + Logger m_logger; + QStringList m_artifactsRemovedFromDisk; + std::unordered_map> m_changedSourcesByProduct; + Set m_productsWhoseArtifactsNeedUpdate; + qint64 m_wildcardExpansionEffort = 0; + qint64 m_propertyComparisonEffort = 0; + + // These must only be deleted at the end so we can still peek into the old look-up table. + QList m_objectsToDelete; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/buildgraph/buildgraphnode.cpp b/src/lib/corelib/buildgraph/buildgraphnode.cpp new file mode 100644 index 00000000..7d011d50 --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraphnode.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** 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 "buildgraphnode.h" + +#include "buildgraphvisitor.h" +#include "projectbuilddata.h" +#include +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +BuildGraphNode::BuildGraphNode() : buildState(Untouched) +{ +} + +BuildGraphNode::~BuildGraphNode() +{ + for (BuildGraphNode *p : qAsConst(parents)) + p->children.remove(this); + for (BuildGraphNode *c : qAsConst(children)) + c->parents.remove(this); +} + +void BuildGraphNode::onChildDisconnected(BuildGraphNode *child) +{ + Q_UNUSED(child); +} + +void BuildGraphNode::acceptChildren(BuildGraphVisitor *visitor) +{ + for (BuildGraphNode *child : qAsConst(children)) + child->accept(visitor); +} + +void BuildGraphNode::load(PersistentPool &pool) +{ + serializationOp(pool); +} + +void BuildGraphNode::store(PersistentPool &pool) +{ + serializationOp(pool); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/buildgraphnode.h b/src/lib/corelib/buildgraph/buildgraphnode.h new file mode 100644 index 00000000..44a0a5b1 --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraphnode.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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 QBS_BUILDGRAPHNODE_H +#define QBS_BUILDGRAPHNODE_H + +#include "nodeset.h" +#include +#include +#include + +namespace qbs { +namespace Internal { + +class BuildGraphVisitor; + +class BuildGraphNode +{ + friend NodeSet; +public: + virtual ~BuildGraphNode(); + + NodeSet parents; + NodeSet children; + WeakPointer product; + + enum BuildState + { + Untouched = 0, + Buildable, + Building, + Built + }; + + BuildState buildState; // Do not serialize. Will be refreshed for every build. + + enum Type + { + ArtifactNodeType, + RuleNodeType + }; + + virtual Type type() const = 0; + virtual void accept(BuildGraphVisitor *visitor) = 0; + virtual QString toString() const = 0; + virtual void onChildDisconnected(BuildGraphNode *child); + + bool isBuilt() const { return buildState == Built; } + + virtual void load(PersistentPool &pool); + virtual void store(PersistentPool &pool); + +protected: + explicit BuildGraphNode(); + void acceptChildren(BuildGraphVisitor *visitor); + + // Do not store parents to avoid recursion. + // Parents must be updated after loading all nodes. + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(children); + } +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILDGRAPHNODE_H diff --git a/src/lib/corelib/buildgraph/buildgraphvisitor.h b/src/lib/corelib/buildgraph/buildgraphvisitor.h new file mode 100644 index 00000000..cc2dde79 --- /dev/null +++ b/src/lib/corelib/buildgraph/buildgraphvisitor.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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 QBS_BUILDGRAPHVISITOR_H +#define QBS_BUILDGRAPHVISITOR_H + +namespace qbs { +namespace Internal { + +class Artifact; +class RuleNode; + +/*! + * \brief The BuildGraphVisitor class + * + * The return value of a visit method indicates whether the children of the current node + * are to be visited next. + */ +class BuildGraphVisitor +{ +public: + virtual ~BuildGraphVisitor() = default; + virtual bool visit(Artifact *) { return true; } + virtual void endVisit(Artifact *) { } + virtual bool visit(RuleNode *) { return true; } + virtual void endVisit(RuleNode *) { } +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILDGRAPHVISITOR_H diff --git a/src/lib/corelib/buildgraph/cycledetector.cpp b/src/lib/corelib/buildgraph/cycledetector.cpp new file mode 100644 index 00000000..5daed55f --- /dev/null +++ b/src/lib/corelib/buildgraph/cycledetector.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** 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 "cycledetector.h" + +#include "artifact.h" +#include "buildgraph.h" +#include "projectbuilddata.h" +#include "rulenode.h" + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +CycleDetector::CycleDetector(Logger logger) + : m_parent(nullptr), m_logger(std::move(logger)) +{ +} + +void CycleDetector::visitProject(const TopLevelProjectConstPtr &project) +{ + project->accept(this); +} + +void CycleDetector::visitProduct(const ResolvedProductConstPtr &product) +{ + product->accept(this); +} + +bool CycleDetector::visit(Artifact *artifact) +{ + return visitNode(artifact); +} + +bool CycleDetector::visit(RuleNode *ruleNode) +{ + return visitNode(ruleNode); +} + +bool CycleDetector::visitNode(BuildGraphNode *node) +{ + if (Q_UNLIKELY(m_nodesInCurrentPath.contains(node))) { + ErrorInfo error(Tr::tr("Cycle in build graph detected.")); + const auto nodes = cycle(node); + for (const BuildGraphNode * const n : nodes) + error.append(n->toString()); + throw error; + } + + if (m_allNodes.contains(node)) + return false; + + m_nodesInCurrentPath += node; + m_parent = node; + for (BuildGraphNode * const child : qAsConst(node->children)) + child->accept(this); + m_nodesInCurrentPath -= node; + m_allNodes += node; + return false; +} + +QList CycleDetector::cycle(BuildGraphNode *doubleEntry) +{ + QList path; + findPath(doubleEntry, m_parent, path); + path.push_back(doubleEntry); + return path; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/cycledetector.h b/src/lib/corelib/buildgraph/cycledetector.h new file mode 100644 index 00000000..5bfb44ef --- /dev/null +++ b/src/lib/corelib/buildgraph/cycledetector.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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 QBS_CYCLEDETECTOR_H +#define QBS_CYCLEDETECTOR_H + +#include "buildgraphvisitor.h" +#include "nodeset.h" +#include +#include + +namespace qbs { +namespace Internal { + +class BuildGraphNode; + +class QBS_AUTOTEST_EXPORT CycleDetector : private BuildGraphVisitor +{ +public: + CycleDetector(Logger logger); + + void visitProject(const TopLevelProjectConstPtr &project); + void visitProduct(const ResolvedProductConstPtr &product); + +private: + bool visit(Artifact *artifact) override; + bool visit(RuleNode *ruleNode) override; + + bool visitNode(BuildGraphNode *node); + + QList cycle(BuildGraphNode *doubleEntry); + + NodeSet m_allNodes; + NodeSet m_nodesInCurrentPath; + BuildGraphNode *m_parent; + Logger m_logger; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_CYCLEDETECTOR_H diff --git a/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.cpp b/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.cpp new file mode 100644 index 00000000..f1bf8db1 --- /dev/null +++ b/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.cpp @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "dependencyparametersscriptvalue.h" + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +static QScriptValue toScriptValue(ScriptEngine *engine, const QString &productName, + const QVariantMap &v, const QString &depName, + const QualifiedId &moduleName) +{ + QScriptValue obj = engine->newObject(); + bool objIdAddedToObserver = false; + for (auto it = v.begin(); it != v.end(); ++it) { + if (it.value().type() == QVariant::Map) { + obj.setProperty(it.key(), toScriptValue(engine, productName, it.value().toMap(), + depName, QualifiedId(moduleName) << it.key())); + } else { + if (!objIdAddedToObserver) { + objIdAddedToObserver = true; + engine->observer()->addParameterObjectId(obj.objectId(), productName, depName, + moduleName); + } + engine->setObservedProperty(obj, it.key(), engine->toScriptValue(it.value())); + } + } + return obj; +} + + +static QScriptValue toScriptValue(ScriptEngine *scriptEngine, const QString &productName, + const QVariantMap &v, const QString &depName) +{ + return toScriptValue(scriptEngine, productName, v, depName, {}); +} + +QScriptValue dependencyParametersValue(const QString &productName, const QString &dependencyName, + const QVariantMap ¶metersMap, ScriptEngine *engine) +{ + return toScriptValue(engine, productName, parametersMap, dependencyName); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.h b/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.h new file mode 100644 index 00000000..7e4287be --- /dev/null +++ b/src/lib/corelib/buildgraph/dependencyparametersscriptvalue.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_DEPENDENCYPARAMETERSSCRIPTVALUE_H +#define QBS_DEPENDENCYPARAMETERSSCRIPTVALUE_H + +#include +#include + +namespace qbs { +namespace Internal { +class ScriptEngine; + +QScriptValue dependencyParametersValue(const QString &productName, const QString &dependencyName, + const QVariantMap ¶metersMap, ScriptEngine *engine); + +} // namespace Internal +} // namespace qbs + +#endif // include guard diff --git a/src/lib/corelib/buildgraph/depscanner.cpp b/src/lib/corelib/buildgraph/depscanner.cpp new file mode 100644 index 00000000..0bf64428 --- /dev/null +++ b/src/lib/corelib/buildgraph/depscanner.cpp @@ -0,0 +1,270 @@ +/**************************************************************************** +** +** 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 "depscanner.h" +#include "artifact.h" +#include "projectbuilddata.h" +#include "buildgraph.h" +#include "transformer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace qbs { +namespace Internal { + +QString DependencyScanner::id() const +{ + if (m_id.isEmpty()) + m_id = createId(); + return m_id; +} + +static QStringList collectCppIncludePaths(const QVariantMap &modules) +{ + QStringList result; + const QVariantMap cpp = modules.value(StringConstants::cppModule()).toMap(); + if (cpp.empty()) + return result; + + result << cpp.value(QStringLiteral("includePaths")).toStringList(); + const bool useSystemHeaders + = cpp.value(QStringLiteral("treatSystemHeadersAsDependencies")).toBool(); + if (useSystemHeaders) { + result + << cpp.value(QStringLiteral("systemIncludePaths")).toStringList() + << cpp.value(QStringLiteral("distributionIncludePaths")).toStringList() + << cpp.value(QStringLiteral("compilerIncludePaths")).toStringList(); + } + result.removeDuplicates(); + return result; +} + +PluginDependencyScanner::PluginDependencyScanner(ScannerPlugin *plugin) + : m_plugin(plugin) +{ +} + +QStringList PluginDependencyScanner::collectSearchPaths(Artifact *artifact) +{ + if (m_plugin->flags & ScannerUsesCppIncludePaths) + return collectCppIncludePaths(artifact->properties->value()); + return {}; +} + +QStringList PluginDependencyScanner::collectDependencies(Artifact *artifact, FileResourceBase *file, + const char *fileTags) +{ + Q_UNUSED(artifact); + Set result; + QString baseDirOfInFilePath = file->dirPath(); + const QString &filepath = file->filePath(); + void *scannerHandle = m_plugin->open(filepath.utf16(), fileTags, ScanForDependenciesFlag); + if (!scannerHandle) + return {}; + forever { + int flags = 0; + int length = 0; + const char *szOutFilePath = m_plugin->next(scannerHandle, &length, &flags); + if (szOutFilePath == nullptr) + break; + QString outFilePath = QString::fromLocal8Bit(szOutFilePath, length); + if (outFilePath.isEmpty()) + continue; + if (flags & SC_LOCAL_INCLUDE_FLAG) { + QString localFilePath = FileInfo::resolvePath(baseDirOfInFilePath, outFilePath); + if (FileInfo::exists(localFilePath)) + outFilePath = localFilePath; + } + result += outFilePath; + } + m_plugin->close(scannerHandle); + return result.toList(); +} + +bool PluginDependencyScanner::recursive() const +{ + return m_plugin->flags & ScannerRecursiveDependencies; +} + +const void *PluginDependencyScanner::key() const +{ + return m_plugin; +} + +QString PluginDependencyScanner::createId() const +{ + return QString::fromLatin1(m_plugin->name); +} + +bool PluginDependencyScanner::areModulePropertiesCompatible(const PropertyMapConstPtr &m1, + const PropertyMapConstPtr &m2) const +{ + // This changes when our C++ scanner starts taking defines into account. + Q_UNUSED(m1); + Q_UNUSED(m2); + return true; +} + +UserDependencyScanner::UserDependencyScanner(ResolvedScannerConstPtr scanner, + ScriptEngine *engine) + : m_scanner(std::move(scanner)), + m_engine(engine), + m_product(nullptr) +{ + m_global = m_engine->newObject(); + m_global.setPrototype(m_engine->globalObject()); + setupScriptEngineForFile(m_engine, m_scanner->scanScript.fileContext(), m_global, + ObserveMode::Disabled); // TODO: QBS-1092 +} + +QStringList UserDependencyScanner::collectSearchPaths(Artifact *artifact) +{ + return evaluate(artifact, nullptr, m_scanner->searchPathsScript); +} + +QStringList UserDependencyScanner::collectDependencies(Artifact *artifact, FileResourceBase *file, + const char *fileTags) +{ + Q_UNUSED(fileTags); + return evaluate(artifact, file, m_scanner->scanScript); +} + +bool UserDependencyScanner::recursive() const +{ + return m_scanner->recursive; +} + +const void *UserDependencyScanner::key() const +{ + return m_scanner.get(); +} + +QString UserDependencyScanner::createId() const +{ + return m_scanner->scanScript.sourceCode(); +} + +bool UserDependencyScanner::areModulePropertiesCompatible(const PropertyMapConstPtr &m1, + const PropertyMapConstPtr &m2) const +{ + // TODO: This should probably be made more fine-grained. Perhaps the Scanner item + // could declare the relevant properties, or we could figure them out automatically + // somehow. + return m1 == m2 || *m1 == *m2; +} + +class ScriptEngineActiveFlagGuard +{ + ScriptEngine *m_engine; +public: + ScriptEngineActiveFlagGuard(ScriptEngine *engine) + : m_engine(engine) + { + m_engine->setActive(true); + } + + ~ScriptEngineActiveFlagGuard() + { + m_engine->setActive(false); + } +}; + +QStringList UserDependencyScanner::evaluate(const Artifact *artifact, + const FileResourceBase *fileToScan, const PrivateScriptFunction &script) +{ + ScriptEngineActiveFlagGuard guard(m_engine); + + if (artifact->product.get() != m_product) { + m_product = artifact->product.get(); + setupScriptEngineForProduct(m_engine, artifact->product.get(), + m_scanner->module.get(), m_global, true); + } + + QScriptValueList args; + args.reserve(fileToScan ? 4 : 3); + args.push_back(m_global.property(StringConstants::projectVar())); + args.push_back(m_global.property(StringConstants::productVar())); + args.push_back(Transformer::translateFileConfig(m_engine, artifact, m_scanner->module->name)); + if (fileToScan) + args.push_back(fileToScan->filePath()); + + m_engine->setGlobalObject(m_global); + QScriptValue &function = script.scriptFunction; + if (!function.isValid() || function.engine() != m_engine) { + function = m_engine->evaluate(script.sourceCode()); + if (Q_UNLIKELY(!function.isFunction())) + throw ErrorInfo(Tr::tr("Invalid scan script."), script.location()); + } + QScriptValue result = function.call(QScriptValue(), args); + m_engine->setGlobalObject(m_global.prototype()); + m_engine->clearRequestedProperties(); + if (Q_UNLIKELY(m_engine->hasErrorOrException(result))) { + QString msg = Tr::tr("evaluating scan script: ") + m_engine->lastErrorString(result); + const CodeLocation loc = m_engine->lastErrorLocation(result, script.location()); + m_engine->clearExceptions(); + throw ErrorInfo(msg, loc); + } + QStringList list; + if (result.isArray()) { + const int count = result.property(StringConstants::lengthProperty()).toInt32(); + list.reserve(count); + for (qint32 i = 0; i < count; ++i) { + QScriptValue item = result.property(i); + if (item.isValid() && !item.isUndefined()) + list.push_back(item.toString()); + } + } + return list; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/depscanner.h b/src/lib/corelib/buildgraph/depscanner.h new file mode 100644 index 00000000..6b18004f --- /dev/null +++ b/src/lib/corelib/buildgraph/depscanner.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** 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 QBS_DEPENDENCY_SCANNER_H +#define QBS_DEPENDENCY_SCANNER_H + +#include +#include +#include + +#include + +#include + +class ScannerPlugin; + +namespace qbs { +namespace Internal { + +class Artifact; +class FileResourceBase; +class Logger; +class ScriptEngine; + +class DependencyScanner +{ +public: + virtual ~DependencyScanner() = default; + + QString id() const; + + virtual QStringList collectSearchPaths(Artifact *artifact) = 0; + virtual QStringList collectDependencies(Artifact *artifact, FileResourceBase *file, + const char *fileTags) = 0; + virtual bool recursive() const = 0; + virtual const void *key() const = 0; + virtual bool areModulePropertiesCompatible(const PropertyMapConstPtr &m1, + const PropertyMapConstPtr &m2) const = 0; + virtual bool cacheIsPerFile() const = 0; + +private: + virtual QString createId() const = 0; + + mutable QString m_id; +}; + +class PluginDependencyScanner : public DependencyScanner +{ +public: + PluginDependencyScanner(ScannerPlugin *plugin); + +private: + QStringList collectSearchPaths(Artifact *artifact) override; + QStringList collectDependencies(Artifact *artifact, FileResourceBase *file, + const char *fileTags) override; + bool recursive() const override; + const void *key() const override; + QString createId() const override; + bool areModulePropertiesCompatible(const PropertyMapConstPtr &m1, + const PropertyMapConstPtr &m2) const override; + bool cacheIsPerFile() const override { return false; } + + ScannerPlugin* m_plugin; +}; + +class UserDependencyScanner : public DependencyScanner +{ +public: + UserDependencyScanner(ResolvedScannerConstPtr scanner, ScriptEngine *engine); + +private: + QStringList collectSearchPaths(Artifact *artifact) override; + QStringList collectDependencies(Artifact *artifact, FileResourceBase *file, + const char *fileTags) override; + bool recursive() const override; + const void *key() const override; + QString createId() const override; + bool areModulePropertiesCompatible(const PropertyMapConstPtr &m1, + const PropertyMapConstPtr &m2) const override; + bool cacheIsPerFile() const override { return true; } + + QStringList evaluate(const Artifact *artifact, const FileResourceBase *fileToScan, const PrivateScriptFunction &script); + + ResolvedScannerConstPtr m_scanner; + ScriptEngine *m_engine; + QScriptValue m_global; + ResolvedProduct *m_product; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_DEPENDENCY_SCANNER_H diff --git a/src/lib/corelib/buildgraph/emptydirectoriesremover.cpp b/src/lib/corelib/buildgraph/emptydirectoriesremover.cpp new file mode 100644 index 00000000..ebbcf67a --- /dev/null +++ b/src/lib/corelib/buildgraph/emptydirectoriesremover.cpp @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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 "emptydirectoriesremover.h" + +#include "artifact.h" + +#include + +#include +#include + +namespace qbs { +namespace Internal { + +EmptyDirectoriesRemover::EmptyDirectoriesRemover(const TopLevelProject *project, + Logger logger) + : m_project(project), m_logger(std::move(logger)) +{ +} + +void EmptyDirectoriesRemover::removeEmptyParentDirectories(const QStringList &artifactFilePaths) +{ + m_dirsToRemove.clear(); + m_handledDirs.clear(); + for (const QString &filePath : artifactFilePaths) + insertSorted(QFileInfo(filePath).absolutePath()); + while (!m_dirsToRemove.empty()) + removeDirIfEmpty(); +} + +void EmptyDirectoriesRemover::removeEmptyParentDirectories(const ArtifactSet &artifacts) +{ + QStringList filePaths; + for (const Artifact * const a : artifacts) { + if (a->artifactType == Artifact::Generated) + filePaths << a->filePath(); + } + removeEmptyParentDirectories(filePaths); +} + +// List is sorted so that "deeper" directories come first. +void EmptyDirectoriesRemover::insertSorted(const QString &dirPath) +{ + int i; + for (i = 0; i < m_dirsToRemove.size(); ++i) { + const QString &cur = m_dirsToRemove.at(i); + if (dirPath == cur) + return; + if (dirPath.count(QLatin1Char('/')) > cur.count(QLatin1Char('/'))) + break; + } + m_dirsToRemove.insert(i, dirPath); +} + +void EmptyDirectoriesRemover::removeDirIfEmpty() +{ + const QString dirPath = m_dirsToRemove.takeFirst(); + m_handledDirs.insert(dirPath); + QFileInfo fi(dirPath); + if (fi.isSymLink() || !fi.exists() || !dirPath.startsWith(m_project->buildDirectory) + || fi.filePath() == m_project->buildDirectory) { + return; + } + QDir dir(dirPath); + dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); + if (dir.count() != 0) + return; + dir.cdUp(); + if (!dir.rmdir(fi.fileName())) { + m_logger.qbsWarning() << QStringLiteral("Cannot remove empty directory '%1'.") + .arg(dirPath); + return; + } + const QString parentDir = dir.path(); + if (!m_handledDirs.contains(parentDir)) + insertSorted(parentDir); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/emptydirectoriesremover.h b/src/lib/corelib/buildgraph/emptydirectoriesremover.h new file mode 100644 index 00000000..7d73fb81 --- /dev/null +++ b/src/lib/corelib/buildgraph/emptydirectoriesremover.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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 QBS_EMPTYDIRECTORIESREMOVER_H +#define QBS_EMPTYDIRECTORIESREMOVER_H + +#include "forward_decls.h" + +#include +#include + +#include + +namespace qbs { +namespace Internal { +class TopLevelProject; + +class EmptyDirectoriesRemover +{ +public: + EmptyDirectoriesRemover(const TopLevelProject *project, Logger logger); + void removeEmptyParentDirectories(const QStringList &artifactFilePaths); + void removeEmptyParentDirectories(const ArtifactSet &artifacts); + +private: + void insertSorted(const QString &dirPath); + void removeDirIfEmpty(); + + const TopLevelProject * const m_project; + Logger m_logger; + QStringList m_dirsToRemove; + Set m_handledDirs; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/buildgraph/environmentscriptrunner.cpp b/src/lib/corelib/buildgraph/environmentscriptrunner.cpp new file mode 100644 index 00000000..9dafbf29 --- /dev/null +++ b/src/lib/corelib/buildgraph/environmentscriptrunner.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** 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 "environmentscriptrunner.h" + +#include "buildgraph.h" +#include "rulesevaluationcontext.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class EnvProvider +{ +public: + EnvProvider(ScriptEngine *engine, const QProcessEnvironment &originalEnv) + : m_engine(engine), m_env(originalEnv) + { + QVariant v; + v.setValue(&m_env); + m_engine->setProperty(StringConstants::qbsProcEnvVarInternal(), v); + } + + ~EnvProvider() { m_engine->setProperty(StringConstants::qbsProcEnvVarInternal(), QVariant()); } + + QProcessEnvironment alteredEnvironment() const { return m_env; } + +private: + ScriptEngine * const m_engine; + QProcessEnvironment m_env; +}; + +static QList topSortModules(const QHash > &moduleChildren, + const QList &modules, + Set &seenModuleNames) +{ + QList result; + for (const ResolvedModule * const m : modules) { + if (m->name.isNull()) + continue; + result << topSortModules(moduleChildren, moduleChildren.value(m), seenModuleNames); + if (seenModuleNames.insert(m->name).second) + result.push_back(m); + } + return result; +} + +EnvironmentScriptRunner::EnvironmentScriptRunner(ResolvedProduct *product, + RulesEvaluationContext *evalContext, + const QProcessEnvironment &env) + : m_product(product), m_evalContext(evalContext), m_env(env) +{ +} + +void EnvironmentScriptRunner::setupForBuild() +{ + // TODO: Won't this fail to take changed properties into account? We probably need + // change tracking here as well. + if (!m_product->buildEnvironment.isEmpty()) + return; + m_envType = BuildEnv; + setupEnvironment(); +} + +void EnvironmentScriptRunner::setupForRun(const QStringList &config) +{ + m_envType = RunEnv; + m_runEnvConfig = config; + setupEnvironment(); +} + +void EnvironmentScriptRunner::setupEnvironment() +{ + const auto hasScript = [this](const ResolvedModuleConstPtr &m) { + return !getScript(m.get()).sourceCode().isEmpty(); + }; + const bool hasAnyScripts = std::any_of(m_product->modules.cbegin(), m_product->modules.cend(), + hasScript); + if (!hasAnyScripts) + return; + + QMap moduleMap; + for (const auto &module : m_product->modules) + moduleMap.insert(module->name, module.get()); + + QHash > moduleParents; + QHash > moduleChildren; + for (const auto &module : m_product->modules) { + for (const QString &moduleName : qAsConst(module->moduleDependencies)) { + const ResolvedModule * const depmod = moduleMap.value(moduleName); + QBS_ASSERT(depmod, return); + moduleParents[depmod].push_back(module.get()); + moduleChildren[module.get()].push_back(depmod); + } + } + + QList rootModules; + for (const auto &module : m_product->modules) { + if (moduleParents.value(module.get()).isEmpty()) { + QBS_ASSERT(module, return); + rootModules.push_back(module.get()); + } + } + + EnvProvider envProvider(engine(), m_env); + + Set seenModuleNames; + const QList &topSortedModules + = topSortModules(moduleChildren, rootModules, seenModuleNames); + const QStringList scriptFunctionArgs = m_envType == BuildEnv + ? ResolvedModule::argumentNamesForSetupBuildEnv() + : ResolvedModule::argumentNamesForSetupRunEnv(); + for (const ResolvedModule * const module : topSortedModules) { + const PrivateScriptFunction &setupScript = getScript(module); + if (setupScript.sourceCode().isEmpty()) + continue; + + RulesEvaluationContext::Scope s(m_evalContext); + QScriptValue envScriptContext = engine()->newObject(); + envScriptContext.setPrototype(engine()->globalObject()); + setupScriptEngineForProduct(engine(), m_product, module, envScriptContext, false); + const QString &productKey = StringConstants::productVar(); + const QString &projectKey = StringConstants::projectVar(); + m_evalContext->scope().setProperty(productKey, envScriptContext.property(productKey)); + m_evalContext->scope().setProperty(projectKey, envScriptContext.property(projectKey)); + if (m_envType == RunEnv) { + QScriptValue configArray = engine()->newArray(m_runEnvConfig.size()); + for (int i = 0; i < m_runEnvConfig.size(); ++i) + configArray.setProperty(i, QScriptValue(m_runEnvConfig.at(i))); + m_evalContext->scope().setProperty(QStringLiteral("config"), configArray); + } + setupScriptEngineForFile(engine(), setupScript.fileContext(), m_evalContext->scope(), + ObserveMode::Disabled); + // TODO: Cache evaluate result + QScriptValue fun = engine()->evaluate(setupScript.sourceCode(), + setupScript.location().filePath(), + setupScript.location().line()); + QBS_CHECK(fun.isFunction()); + const QScriptValueList svArgs = ScriptEngine::argumentList(scriptFunctionArgs, + m_evalContext->scope()); + const QScriptValue res = fun.call(QScriptValue(), svArgs); + engine()->releaseResourcesOfScriptObjects(); + if (Q_UNLIKELY(engine()->hasErrorOrException(res))) { + const QString scriptName = m_envType == BuildEnv + ? StringConstants::setupBuildEnvironmentProperty() + : StringConstants::setupRunEnvironmentProperty(); + throw ErrorInfo(Tr::tr("Error running %1 script for product '%2': %3") + .arg(scriptName, m_product->fullDisplayName(), + engine()->lastErrorString(res)), + engine()->lastErrorLocation(res, setupScript.location())); + } + } + + const QProcessEnvironment &newEnv = envProvider.alteredEnvironment(); + if (m_envType == BuildEnv) + m_product->buildEnvironment = newEnv; + else + m_product->runEnvironment = newEnv; +} + +ScriptEngine *EnvironmentScriptRunner::engine() const +{ + return m_evalContext->engine(); +} + +const PrivateScriptFunction &EnvironmentScriptRunner::getScript(const ResolvedModule *module) const +{ + return m_envType == BuildEnv + ? module->setupBuildEnvironmentScript + : module->setupRunEnvironmentScript; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/environmentscriptrunner.h b/src/lib/corelib/buildgraph/environmentscriptrunner.h new file mode 100644 index 00000000..22131479 --- /dev/null +++ b/src/lib/corelib/buildgraph/environmentscriptrunner.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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_ENVIRONMENTSCRIPTRUNNER_H +#define QBS_ENVIRONMENTSCRIPTRUNNER_H + +#include + +#include +#include + +namespace qbs { +namespace Internal { +class RulesEvaluationContext; +class ScriptEngine; + +class EnvironmentScriptRunner +{ +public: + EnvironmentScriptRunner(ResolvedProduct *product, RulesEvaluationContext *evalContext, + const QProcessEnvironment &env); + + void setupForBuild(); + void setupForRun(const QStringList &config); + +private: + void setupEnvironment(); + ScriptEngine *engine() const; + const PrivateScriptFunction &getScript(const ResolvedModule *module) const; + + ResolvedProduct * const m_product; + RulesEvaluationContext * const m_evalContext; + const QProcessEnvironment m_env; + + QStringList m_runEnvConfig; + enum EnvType { BuildEnv, RunEnv } m_envType = EnvType::BuildEnv; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/buildgraph/executor.cpp b/src/lib/corelib/buildgraph/executor.cpp new file mode 100644 index 00000000..377222d2 --- /dev/null +++ b/src/lib/corelib/buildgraph/executor.cpp @@ -0,0 +1,1332 @@ +/**************************************************************************** +** +** 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 "executor.h" + +#include "buildgraph.h" +#include "emptydirectoriesremover.h" +#include "environmentscriptrunner.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "cycledetector.h" +#include "executorjob.h" +#include "inputartifactscanner.h" +#include "productinstaller.h" +#include "rescuableartifactdata.h" +#include "rulecommands.h" +#include "rulenode.h" +#include "rulesevaluationcontext.h" +#include "transformerchangetracking.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +bool Executor::ComparePriority::operator() (const BuildGraphNode *x, const BuildGraphNode *y) const +{ + return x->product->buildData->buildPriority() < y->product->buildData->buildPriority(); +} + + +Executor::Executor(Logger logger, QObject *parent) + : QObject(parent) + , m_productInstaller(nullptr) + , m_logger(std::move(logger)) + , m_progressObserver(nullptr) + , m_state(ExecutorIdle) + , m_cancelationTimer(new QTimer(this)) +{ + m_inputArtifactScanContext = new InputArtifactScannerContext; + m_cancelationTimer->setSingleShot(false); + m_cancelationTimer->setInterval(1000); + connect(m_cancelationTimer, &QTimer::timeout, this, &Executor::checkForCancellation); +} + +Executor::~Executor() +{ + // jobs must be destroyed before deleting the m_inputArtifactScanContext + m_allJobs.clear(); + delete m_inputArtifactScanContext; + delete m_productInstaller; +} + +FileTime Executor::recursiveFileTime(const QString &filePath) const +{ + FileTime newest; + FileInfo fileInfo(filePath); + if (!fileInfo.exists()) { + const QString nativeFilePath = QDir::toNativeSeparators(filePath); + m_logger.qbsWarning() << Tr::tr("File '%1' not found.").arg(nativeFilePath); + return newest; + } + newest = std::max(fileInfo.lastModified(), fileInfo.lastStatusChange()); + if (!fileInfo.isDir()) + return newest; + const QStringList dirContents = QDir(filePath) + .entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &curFileName : dirContents) { + const FileTime ft = recursiveFileTime(filePath + QLatin1Char('/') + curFileName); + if (ft > newest) + newest = ft; + } + return newest; +} + +void Executor::retrieveSourceFileTimestamp(Artifact *artifact) const +{ + QBS_CHECK(artifact->artifactType == Artifact::SourceFile); + + if (m_buildOptions.changedFiles().empty()) + artifact->setTimestamp(recursiveFileTime(artifact->filePath())); + else if (m_buildOptions.changedFiles().contains(artifact->filePath())) + artifact->setTimestamp(FileTime::currentTime()); + else if (!artifact->timestamp().isValid()) + artifact->setTimestamp(recursiveFileTime(artifact->filePath())); + + artifact->timestampRetrieved = true; + if (!artifact->timestamp().isValid()) + throw ErrorInfo(Tr::tr("Source file '%1' has disappeared.").arg(artifact->filePath())); +} + +void Executor::build() +{ + try { + m_partialBuild = size_t(m_productsToBuild.size()) != m_allProducts.size(); + doBuild(); + } catch (const ErrorInfo &e) { + handleError(e); + } +} + +void Executor::setProject(const TopLevelProjectPtr &project) +{ + m_project = project; + m_allProducts = project->allProducts(); + m_projectsByName.clear(); + m_projectsByName.insert(std::make_pair(project->name, project.get())); + for (const ResolvedProjectPtr &p : project->allSubProjects()) + m_projectsByName.insert(std::make_pair(p->name, p.get())); +} + +void Executor::setProducts(const QVector &productsToBuild) +{ + m_productsToBuild = productsToBuild; + m_productsByName.clear(); + for (const ResolvedProductPtr &p : productsToBuild) + m_productsByName.insert(std::make_pair(p->uniqueName(), p.get())); +} + +class ProductPrioritySetter +{ + const std::vector &m_allProducts; + unsigned int m_priority = 0; + Set m_seenProducts; +public: + ProductPrioritySetter(const std::vector &allProducts) // TODO: Use only products to build? + : m_allProducts(allProducts) + { + } + + void apply() + { + Set allDependencies; + for (const ResolvedProductPtr &product : m_allProducts) { + for (const ResolvedProductPtr &dep : product->dependencies) + allDependencies += dep; + } + const Set rootProducts + = Set::fromStdVector(m_allProducts) - allDependencies; + m_priority = UINT_MAX; + m_seenProducts.clear(); + for (const ResolvedProductPtr &rootProduct : rootProducts) + traverse(rootProduct); + } + +private: + void traverse(const ResolvedProductPtr &product) + { + if (!m_seenProducts.insert(product).second) + return; + for (const ResolvedProductPtr &dependency : qAsConst(product->dependencies)) + traverse(dependency); + if (!product->buildData) + return; + product->buildData->setBuildPriority(m_priority--); + } +}; + +void Executor::doBuild() +{ + if (m_buildOptions.maxJobCount() <= 0) { + m_buildOptions.setMaxJobCount(BuildOptions::defaultMaxJobCount()); + qCDebug(lcExec) << "max job count not explicitly set, using value of" + << m_buildOptions.maxJobCount(); + } + QBS_CHECK(m_state == ExecutorIdle); + m_leaves = Leaves(); + m_error.clear(); + m_explicitlyCanceled = false; + m_activeFileTags = FileTags::fromStringList(m_buildOptions.activeFileTags()); + m_tagsOfFilesToConsider.clear(); + m_tagsNeededForFilesToConsider.clear(); + m_productsOfFilesToConsider.clear(); + m_artifactsRemovedFromDisk.clear(); + m_jobCountPerPool.clear(); + + setupJobLimits(); + + // TODO: The "filesToConsider" thing is badly designed; we should know exactly which artifact + // it is. Remove this from the BuildOptions class and introduce Project::buildSomeFiles() + // instead. + const QStringList &filesToConsider = m_buildOptions.filesToConsider(); + if (!filesToConsider.empty()) { + for (const QString &fileToConsider : filesToConsider) { + const auto &files = m_project->buildData->lookupFiles(fileToConsider); + for (const FileResourceBase * const file : files) { + if (file->fileType() != FileResourceBase::FileTypeArtifact) + continue; + auto const artifact = static_cast(file); + if (contains(m_productsToBuild, artifact->product.lock())) { + m_tagsOfFilesToConsider.unite(artifact->fileTags()); + m_productsOfFilesToConsider << artifact->product.lock(); + } + } + } + } + + setState(ExecutorRunning); + + if (m_productsToBuild.empty()) { + qCDebug(lcExec) << "No products to build, finishing."; + QTimer::singleShot(0, this, &Executor::finish); // Don't call back on the caller. + return; + } + + doSanityChecks(); + QBS_CHECK(!m_project->buildData->evaluationContext); + m_project->buildData->evaluationContext = std::make_shared(m_logger); + m_evalContext = m_project->buildData->evaluationContext; + + m_elapsedTimeRules = m_elapsedTimeScanners = m_elapsedTimeInstalling = 0; + m_evalContext->engine()->enableProfiling(m_buildOptions.logElapsedTime()); + + InstallOptions installOptions; + installOptions.setDryRun(m_buildOptions.dryRun()); + installOptions.setInstallRoot(m_productsToBuild.front()->moduleProperties + ->qbsPropertyValue(StringConstants::installRootProperty()).toString()); + installOptions.setKeepGoing(m_buildOptions.keepGoing()); + m_productInstaller = new ProductInstaller(m_project, m_productsToBuild, installOptions, + m_progressObserver, m_logger); + if (m_buildOptions.removeExistingInstallation()) + m_productInstaller->removeInstallRoot(); + + addExecutorJobs(); + syncFileDependencies(); + prepareAllNodes(); + prepareProducts(); + setupRootNodes(); + prepareReachableNodes(); + setupProgressObserver(); + initLeaves(); + if (!scheduleJobs()) { + qCDebug(lcExec) << "Nothing to do at all, finishing."; + QTimer::singleShot(0, this, &Executor::finish); // Don't call back on the caller. + } + if (m_progressObserver) + m_cancelationTimer->start(); +} + +void Executor::setBuildOptions(const BuildOptions &buildOptions) +{ + m_buildOptions = buildOptions; +} + + +void Executor::initLeaves() +{ + updateLeaves(m_roots); +} + +void Executor::updateLeaves(const NodeSet &nodes) +{ + NodeSet seenNodes; + for (BuildGraphNode * const node : nodes) + updateLeaves(node, seenNodes); +} + +void Executor::updateLeaves(BuildGraphNode *node, NodeSet &seenNodes) +{ + if (!seenNodes.insert(node).second) + return; + + // Artifacts that appear in the build graph after + // prepareBuildGraph() has been called, must be initialized. + if (node->buildState == BuildGraphNode::Untouched) { + node->buildState = BuildGraphNode::Buildable; + if (node->type() == BuildGraphNode::ArtifactNodeType) { + auto const artifact = static_cast(node); + if (artifact->artifactType == Artifact::SourceFile) + retrieveSourceFileTimestamp(artifact); + } + } + + bool isLeaf = true; + for (BuildGraphNode *child : qAsConst(node->children)) { + if (child->buildState != BuildGraphNode::Built) { + isLeaf = false; + updateLeaves(child, seenNodes); + } + } + + if (isLeaf) { + qCDebug(lcExec).noquote() << "adding leaf" << node->toString(); + m_leaves.push(node); + } +} + +// Returns true if some artifacts are still waiting to be built or currently building. +bool Executor::scheduleJobs() +{ + QBS_CHECK(m_state == ExecutorRunning); + std::vector delayedLeaves; + while (!m_leaves.empty() && !m_availableJobs.empty()) { + BuildGraphNode * const nodeToBuild = m_leaves.top(); + m_leaves.pop(); + + switch (nodeToBuild->buildState) { + case BuildGraphNode::Untouched: + QBS_ASSERT(!"untouched node in leaves list", + qDebug("%s", qPrintable(nodeToBuild->toString()))); + break; + case BuildGraphNode::Buildable: // This is the only state in which we want to build a node. + // TODO: It's a bit annoying that we have to check this here already, when we + // don't know whether the transformer needs to run at all. Investigate + // moving the whole job allocation logic to runTransformer(). + if (schedulingBlockedByJobLimit(nodeToBuild)) { + qCDebug(lcExec).noquote() << "node delayed due to occupied job pool:" + << nodeToBuild->toString(); + delayedLeaves.push_back(nodeToBuild); + } else { + nodeToBuild->accept(this); + } + break; + case BuildGraphNode::Building: + qCDebug(lcExec).noquote() << nodeToBuild->toString(); + qCDebug(lcExec) << "node is currently being built. Skipping."; + break; + case BuildGraphNode::Built: + qCDebug(lcExec).noquote() << nodeToBuild->toString(); + qCDebug(lcExec) << "node already built. Skipping."; + break; + } + } + for (BuildGraphNode * const delayedLeaf : delayedLeaves) + m_leaves.push(delayedLeaf); + return !m_leaves.empty() || !m_processingJobs.empty(); +} + +bool Executor::schedulingBlockedByJobLimit(const BuildGraphNode *node) +{ + if (node->type() != BuildGraphNode::ArtifactNodeType) + return false; + const auto artifact = static_cast(node); + if (artifact->artifactType == Artifact::SourceFile) + return false; + + const Transformer * const transformer = artifact->transformer.get(); + for (const QString &jobPool : transformer->jobPools()) { + const int currentJobCount = m_jobCountPerPool[jobPool]; + if (currentJobCount == 0) + continue; + const auto jobLimitIsExceeded = [currentJobCount, jobPool, this](const Transformer *t) { + const int maxJobCount = m_jobLimitsPerProduct.at(t->product().get()) + .getLimit(jobPool); + return maxJobCount > 0 && currentJobCount >= maxJobCount; + }; + + // Different products can set different limits. The effective limit is the minimum of what + // is set in this transformer's product and in the products of all currently + // running transformers. + if (jobLimitIsExceeded(transformer)) + return true; + const auto runningJobs = m_processingJobs.keys(); + for (const ExecutorJob * const runningJob : runningJobs) { + if (!runningJob->jobPools().contains(jobPool)) + continue; + const Transformer * const runningTransformer = runningJob->transformer(); + if (!runningTransformer) + continue; // This can happen if the ExecutorJob has already finished. + if (runningTransformer->product() == transformer->product()) + continue; // We have already checked this product's job limit. + if (jobLimitIsExceeded(runningTransformer)) + return true; + } + } + return false; +} + +bool Executor::isUpToDate(Artifact *artifact) const +{ + QBS_CHECK(artifact->artifactType == Artifact::Generated); + + qCDebug(lcUpToDateCheck) << "check" << artifact->filePath() + << artifact->timestamp().toString(); + + if (m_buildOptions.forceTimestampCheck()) { + artifact->setTimestamp(FileInfo(artifact->filePath()).lastModified()); + qCDebug(lcUpToDateCheck) << "timestamp retrieved from filesystem:" + << artifact->timestamp().toString(); + } + + if (!artifact->timestamp().isValid()) { + qCDebug(lcUpToDateCheck) << "invalid timestamp. Out of date."; + return false; + } + + for (Artifact *childArtifact : filterByType(artifact->children)) { + QBS_CHECK(!childArtifact->alwaysUpdated || childArtifact->timestamp().isValid()); + qCDebug(lcUpToDateCheck) << "child timestamp" + << childArtifact->timestamp().toString() + << childArtifact->filePath(); + if (artifact->timestamp() < childArtifact->timestamp()) + return false; + } + + for (FileDependency *fileDependency : qAsConst(artifact->fileDependencies)) { + if (!fileDependency->timestamp().isValid()) { + qCDebug(lcUpToDateCheck) << "file dependency doesn't exist" + << fileDependency->filePath(); + return false; + } + qCDebug(lcUpToDateCheck) << "file dependency timestamp" + << fileDependency->timestamp().toString() + << fileDependency->filePath(); + if (artifact->timestamp() < fileDependency->timestamp()) + return false; + } + + return true; +} + +bool Executor::mustExecuteTransformer(const TransformerPtr &transformer) const +{ + if (transformer->alwaysRun) + return true; + if (transformer->markedForRerun) { + qCDebug(lcUpToDateCheck) << "explicitly marked for re-run."; + return true; + } + + bool hasAlwaysUpdatedArtifacts = false; + bool hasUpToDateNotAlwaysUpdatedArtifacts = false; + for (Artifact *artifact : qAsConst(transformer->outputs)) { + if (isUpToDate(artifact)) { + if (artifact->alwaysUpdated) + hasAlwaysUpdatedArtifacts = true; + else + hasUpToDateNotAlwaysUpdatedArtifacts = true; + } else if (artifact->alwaysUpdated || m_buildOptions.forceTimestampCheck()) { + return true; + } + } + + if (commandsNeedRerun(transformer.get(), transformer->product().get(), m_productsByName, + m_projectsByName)) { + return true; + } + + // If all artifacts in a transformer have "alwaysUpdated" set to false, that transformer is + // run if and only if *all* of them are out of date. + return !hasAlwaysUpdatedArtifacts && !hasUpToDateNotAlwaysUpdatedArtifacts; +} + +void Executor::buildArtifact(Artifact *artifact) +{ + qCDebug(lcExec) << relativeArtifactFileName(artifact); + + QBS_CHECK(artifact->buildState == BuildGraphNode::Buildable); + + if (artifact->artifactType != Artifact::SourceFile && !checkNodeProduct(artifact)) + return; + + // skip artifacts without transformer + if (artifact->artifactType != Artifact::Generated) { + // For source artifacts, that were not reachable when initializing the build, we must + // retrieve timestamps. This can happen, if a dependency that's added during the build + // makes the source artifact reachable. + if (artifact->artifactType == Artifact::SourceFile && !artifact->timestampRetrieved) + retrieveSourceFileTimestamp(artifact); + + qCDebug(lcExec) << "artifact type" << toString(artifact->artifactType) << "Skipping."; + finishArtifact(artifact); + return; + } + + // Every generated artifact must have a transformer. + QBS_CHECK(artifact->transformer); + potentiallyRunTransformer(artifact->transformer); +} + +void Executor::executeRuleNode(RuleNode *ruleNode) +{ + AccumulatingTimer rulesTimer(m_buildOptions.logElapsedTime() ? &m_elapsedTimeRules : nullptr); + + if (!checkNodeProduct(ruleNode)) + return; + + QBS_CHECK(!m_evalContext->engine()->isActive()); + + RuleNode::ApplicationResult result; + ruleNode->apply(m_logger, m_productsByName, m_projectsByName, &result); + updateLeaves(result.createdArtifacts); + updateLeaves(result.invalidatedArtifacts); + m_artifactsRemovedFromDisk << result.removedArtifacts; + + if (m_progressObserver) { + const int transformerCount = ruleNode->transformerCount(); + if (transformerCount == 0) { + m_progressObserver->incrementProgressValue(); + } else { + m_pendingTransformersPerRule.insert(std::make_pair(ruleNode->rule().get(), + transformerCount)); + } + } + + finishNode(ruleNode); +} + +void Executor::finishJob(ExecutorJob *job, bool success) +{ + QBS_CHECK(job); + QBS_CHECK(m_state != ExecutorIdle); + + const JobMap::Iterator it = m_processingJobs.find(job); + QBS_CHECK(it != m_processingJobs.end()); + const TransformerPtr transformer = it.value(); + m_processingJobs.erase(it); + m_availableJobs.push_back(job); + updateJobCounts(transformer.get(), -1); + if (success) { + m_project->buildData->setDirty(); + for (Artifact * const artifact : qAsConst(transformer->outputs)) { + if (artifact->alwaysUpdated) { + artifact->setTimestamp(FileTime::currentTime()); + for (Artifact * const parent : artifact->parentArtifacts()) + parent->transformer->markedForRerun = true; + if (m_buildOptions.forceOutputCheck() + && !m_buildOptions.dryRun() && !FileInfo(artifact->filePath()).exists()) { + if (transformer->rule) { + if (!transformer->rule->name.isEmpty()) { + throw ErrorInfo(tr("Rule '%1' declares artifact '%2', " + "but the artifact was not produced.") + .arg(transformer->rule->name, artifact->filePath())); + } + throw ErrorInfo(tr("Rule declares artifact '%1', " + "but the artifact was not produced.") + .arg(artifact->filePath())); + } + throw ErrorInfo(tr("Transformer declares artifact '%1', " + "but the artifact was not produced.") + .arg(artifact->filePath())); + } + } else { + artifact->setTimestamp(FileInfo(artifact->filePath()).lastModified()); + } + } + finishTransformer(transformer); + } + + if (!success && !m_buildOptions.keepGoing()) + cancelJobs(); + + if (m_state == ExecutorRunning && m_progressObserver && m_progressObserver->canceled()) { + qCDebug(lcExec) << "Received cancel request; canceling build."; + m_explicitlyCanceled = true; + cancelJobs(); + } + + if (m_state == ExecutorCanceling) { + if (m_processingJobs.empty()) { + qCDebug(lcExec) << "All pending jobs are done, finishing."; + finish(); + } + return; + } + + if (!scheduleJobs()) { + qCDebug(lcExec) << "Nothing left to build; finishing."; + finish(); + } +} + +static bool allChildrenBuilt(BuildGraphNode *node) +{ + return std::all_of(node->children.cbegin(), node->children.cend(), + std::mem_fn(&BuildGraphNode::isBuilt)); +} + +void Executor::finishNode(BuildGraphNode *leaf) +{ + leaf->buildState = BuildGraphNode::Built; + for (BuildGraphNode * const parent : qAsConst(leaf->parents)) { + if (parent->buildState != BuildGraphNode::Buildable) { + qCDebug(lcExec).noquote() << "parent" << parent->toString() + << "build state:" << toString(parent->buildState); + continue; + } + + if (allChildrenBuilt(parent)) { + m_leaves.push(parent); + qCDebug(lcExec).noquote() << "finishNode adds leaf" + << parent->toString() << toString(parent->buildState); + } else { + qCDebug(lcExec).noquote() << "parent" << parent->toString() + << "build state:" << toString(parent->buildState); + } + } +} + +void Executor::finishArtifact(Artifact *leaf) +{ + QBS_CHECK(leaf); + qCDebug(lcExec) << "finishArtifact" << relativeArtifactFileName(leaf); + finishNode(leaf); +} + +QString Executor::configString() const +{ + return tr(" for configuration %1").arg(m_project->id()); +} + +bool Executor::transformerHasMatchingOutputTags(const TransformerConstPtr &transformer) const +{ + if (m_activeFileTags.empty()) + return true; // No filtering requested. + + return std::any_of(transformer->outputs.cbegin(), transformer->outputs.cend(), + [this](const Artifact *a) { return artifactHasMatchingOutputTags(a); }); +} + +bool Executor::artifactHasMatchingOutputTags(const Artifact *artifact) const +{ + return m_activeFileTags.intersects(artifact->fileTags()) + || m_tagsNeededForFilesToConsider.intersects(artifact->fileTags()); +} + +bool Executor::transformerHasMatchingInputFiles(const TransformerConstPtr &transformer) const +{ + if (m_buildOptions.filesToConsider().empty()) + return true; // No filtering requested. + if (!m_productsOfFilesToConsider.contains(transformer->product())) + return false; + if (transformer->inputs.empty()) + return true; + for (const Artifact * const input : qAsConst(transformer->inputs)) { + const auto files = m_buildOptions.filesToConsider(); + for (const QString &filePath : files) { + if (input->filePath() == filePath + || input->fileTags().intersects(m_tagsNeededForFilesToConsider)) { + return true; + } + } + } + + return false; +} + +void Executor::setupJobLimits() +{ + Settings settings(m_buildOptions.settingsDirectory()); + for (const auto &p : qAsConst(m_productsToBuild)) { + const Preferences prefs(&settings, p->profile()); + const JobLimits &jobLimitsFromSettings = prefs.jobLimits(); + JobLimits effectiveJobLimits; + if (m_buildOptions.projectJobLimitsTakePrecedence()) { + effectiveJobLimits.update(jobLimitsFromSettings).update(m_buildOptions.jobLimits()) + .update(p->jobLimits); + } else { + effectiveJobLimits.update(p->jobLimits).update(jobLimitsFromSettings) + .update(m_buildOptions.jobLimits()); + } + m_jobLimitsPerProduct.insert(std::make_pair(p.get(), effectiveJobLimits)); + } +} + +void Executor::updateJobCounts(const Transformer *transformer, int diff) +{ + for (const QString &jobPool : transformer->jobPools()) + m_jobCountPerPool[jobPool] += diff; +} + +void Executor::cancelJobs() +{ + if (m_state == ExecutorCanceling) + return; + qCDebug(lcExec) << "Canceling all jobs."; + setState(ExecutorCanceling); + const auto jobs = m_processingJobs.keys(); + for (ExecutorJob *job : jobs) + job->cancel(); +} + +void Executor::setupProgressObserver() +{ + if (!m_progressObserver) + return; + int totalEffort = 1; // For the effort after the last rule application; + for (const auto &product : qAsConst(m_productsToBuild)) { + QBS_CHECK(product->buildData); + const auto filtered = filterByType(product->buildData->allNodes()); + totalEffort += std::distance(filtered.begin(), filtered.end()); + } + m_progressObserver->initialize(tr("Building%1").arg(configString()), totalEffort); +} + +void Executor::doSanityChecks() +{ + QBS_CHECK(m_project); + QBS_CHECK(!m_productsToBuild.empty()); + for (const auto &product : qAsConst(m_productsToBuild)) { + QBS_CHECK(product->buildData); + QBS_CHECK(product->topLevelProject() == m_project.get()); + } +} + +void Executor::handleError(const ErrorInfo &error) +{ + const auto items = error.items(); + for (const ErrorItem &ei : items) + m_error.append(ei); + if (m_processingJobs.empty()) + finish(); + else + cancelJobs(); +} + +void Executor::addExecutorJobs() +{ + const int count = m_buildOptions.maxJobCount(); + qCDebug(lcExec) << "preparing executor for" << count << "jobs in parallel"; + m_allJobs.reserve(count); + m_availableJobs.reserve(count); + for (int i = 1; i <= count; i++) { + m_allJobs.push_back(std::make_unique(m_logger)); + const auto job = m_allJobs.back().get(); + job->setMainThreadScriptEngine(m_evalContext->engine()); + job->setObjectName(QStringLiteral("J%1").arg(i)); + job->setDryRun(m_buildOptions.dryRun()); + job->setEchoMode(m_buildOptions.echoMode()); + m_availableJobs.push_back(job); + connect(job, &ExecutorJob::reportCommandDescription, + this, &Executor::reportCommandDescription); + connect(job, &ExecutorJob::reportProcessResult, this, &Executor::reportProcessResult); + connect(job, &ExecutorJob::finished, + this, &Executor::onJobFinished, Qt::QueuedConnection); + } +} + +void Executor::rescueOldBuildData(Artifact *artifact, bool *childrenAdded = nullptr) +{ + if (childrenAdded) + *childrenAdded = false; + if (!artifact->oldDataPossiblyPresent) + return; + artifact->oldDataPossiblyPresent = false; + if (artifact->artifactType != Artifact::Generated) + return; + + ResolvedProduct * const product = artifact->product.get(); + const RescuableArtifactData rad + = product->buildData->removeFromRescuableArtifactData(artifact->filePath()); + if (!rad.isValid()) + return; + qCDebug(lcBuildGraph) << "Attempting to rescue data of artifact" << artifact->fileName(); + + std::vector childrenToConnect; + bool canRescue = artifact->transformer->commands == rad.commands; + if (canRescue) { + ResolvedProductPtr pseudoProduct = ResolvedProduct::create(); + for (const RescuableArtifactData::ChildData &cd : rad.children) { + pseudoProduct->name = cd.productName; + pseudoProduct->multiplexConfigurationId = cd.productMultiplexId; + Artifact * const child = lookupArtifact(pseudoProduct, m_project->buildData.get(), + cd.childFilePath, true); + if (artifact->children.contains(child)) + continue; + if (!child) { + // 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; + qCDebug(lcBuildGraph) << "Former child artifact" << cd.childFilePath + << "does not exist anymore."; + const RescuableArtifactData childRad + = product->buildData->removeFromRescuableArtifactData(cd.childFilePath); + if (childRad.isValid()) { + m_artifactsRemovedFromDisk << artifact->filePath(); + removeGeneratedArtifactFromDisk(cd.childFilePath, m_logger); + } + } + if (!cd.addedByScanner) { + // If an artifact has disappeared from the list of children, the commands + // might need to run again. + canRescue = false; + qCDebug(lcBuildGraph) << "Former child artifact" << cd.childFilePath << + "is no longer in the list of children"; + } + if (canRescue) + childrenToConnect.push_back(child); + } + for (const QString &depPath : rad.fileDependencies) { + const auto &depList = m_project->buildData->lookupFiles(depPath); + if (depList.empty()) { + canRescue = false; + qCDebug(lcBuildGraph) << "File dependency" << depPath + << "not in the project's list of dependencies anymore."; + break; + } + const auto depFinder = [](const FileResourceBase *f) { + return f->fileType() == FileResourceBase::FileTypeDependency; + }; + const auto depIt = std::find_if(depList.cbegin(), depList.cend(), depFinder); + if (depIt == depList.cend()) { + canRescue = false; + qCDebug(lcBuildGraph) << "File dependency" << depPath + << "not in the project's list of dependencies anymore."; + break; + } + artifact->fileDependencies.insert(static_cast(*depIt)); + } + + if (canRescue) { + const TypeFilter childArtifacts(artifact->children); + const size_t newChildCount = childrenToConnect.size() + + std::distance(childArtifacts.begin(), childArtifacts.end()); + QBS_CHECK(newChildCount >= rad.children.size()); + if (newChildCount > rad.children.size()) { + canRescue = false; + qCDebug(lcBuildGraph) << "Artifact has children not present in rescue data."; + } + } + } else { + qCDebug(lcBuildGraph) << "Transformer commands changed."; + } + + if (canRescue) { + artifact->transformer->propertiesRequestedInPrepareScript + = rad.propertiesRequestedInPrepareScript; + artifact->transformer->propertiesRequestedInCommands + = rad.propertiesRequestedInCommands; + artifact->transformer->propertiesRequestedFromArtifactInPrepareScript + = rad.propertiesRequestedFromArtifactInPrepareScript; + artifact->transformer->propertiesRequestedFromArtifactInCommands + = rad.propertiesRequestedFromArtifactInCommands; + artifact->transformer->importedFilesUsedInPrepareScript + = rad.importedFilesUsedInPrepareScript; + artifact->transformer->importedFilesUsedInCommands = rad.importedFilesUsedInCommands; + artifact->transformer->depsRequestedInPrepareScript = rad.depsRequestedInPrepareScript; + artifact->transformer->depsRequestedInCommands = rad.depsRequestedInCommands; + artifact->transformer->artifactsMapRequestedInPrepareScript + = rad.artifactsMapRequestedInPrepareScript; + artifact->transformer->artifactsMapRequestedInCommands + = rad.artifactsMapRequestedInCommands; + artifact->transformer->exportedModulesAccessedInPrepareScript + = rad.exportedModulesAccessedInPrepareScript; + artifact->transformer->exportedModulesAccessedInCommands + = rad.exportedModulesAccessedInCommands; + artifact->transformer->lastCommandExecutionTime = rad.lastCommandExecutionTime; + artifact->transformer->lastPrepareScriptExecutionTime = rad.lastPrepareScriptExecutionTime; + artifact->transformer->commandsNeedChangeTracking = true; + artifact->setTimestamp(rad.timeStamp); + artifact->transformer->markedForRerun + = artifact->transformer->markedForRerun || rad.knownOutOfDate; + if (childrenAdded && !childrenToConnect.empty()) + *childrenAdded = true; + for (Artifact * const child : childrenToConnect) { + if (safeConnect(artifact, child)) + artifact->childrenAddedByScanner << child; + } + qCDebug(lcBuildGraph) << "Data was rescued."; + } else { + removeGeneratedArtifactFromDisk(artifact, m_logger); + m_artifactsRemovedFromDisk << artifact->filePath(); + qCDebug(lcBuildGraph) << "Data not rescued."; + } +} + +bool Executor::checkForUnbuiltDependencies(Artifact *artifact) +{ + bool buildingDependenciesFound = false; + NodeSet unbuiltDependencies; + for (BuildGraphNode * const dependency : qAsConst(artifact->children)) { + switch (dependency->buildState) { + case BuildGraphNode::Untouched: + case BuildGraphNode::Buildable: + qCDebug(lcExec).noquote() << "unbuilt dependency:" << dependency->toString(); + unbuiltDependencies += dependency; + break; + case BuildGraphNode::Building: { + qCDebug(lcExec).noquote() << "dependency in state 'Building':" << dependency->toString(); + buildingDependenciesFound = true; + break; + } + case BuildGraphNode::Built: + // do nothing + break; + } + } + if (!unbuiltDependencies.empty()) { + artifact->inputsScanned = false; + updateLeaves(unbuiltDependencies); + return true; + } + if (buildingDependenciesFound) { + artifact->inputsScanned = false; + return true; + } + return false; +} + +void Executor::potentiallyRunTransformer(const TransformerPtr &transformer) +{ + for (Artifact * const output : qAsConst(transformer->outputs)) { + // Rescuing build data can introduce new dependencies, potentially delaying execution of + // this transformer. + bool childrenAddedDueToRescue; + rescueOldBuildData(output, &childrenAddedDueToRescue); + if (childrenAddedDueToRescue && checkForUnbuiltDependencies(output)) + return; + } + + if (!transformerHasMatchingOutputTags(transformer)) { + qCDebug(lcExec) << "file tags do not match. Skipping."; + finishTransformer(transformer); + return; + } + + if (!transformerHasMatchingInputFiles(transformer)) { + qCDebug(lcExec) << "input files do not match. Skipping."; + finishTransformer(transformer); + return; + } + + const bool mustExecute = mustExecuteTransformer(transformer); + 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) { + qCDebug(lcExec) << "Up to date. Skipping."; + finishTransformer(transformer); + return; + } + + if (m_buildOptions.executeRulesOnly()) + finishTransformer(transformer); + else + runTransformer(transformer); +} + +void Executor::runTransformer(const TransformerPtr &transformer) +{ + QBS_CHECK(transformer); + + // create the output directories + if (!m_buildOptions.dryRun()) { + for (Artifact * const output : qAsConst(transformer->outputs)) { + QDir outDir = QFileInfo(output->filePath()).absoluteDir(); + if (!outDir.exists() && !outDir.mkpath(StringConstants::dot())) { + throw ErrorInfo(tr("Failed to create directory '%1'.") + .arg(QDir::toNativeSeparators(outDir.absolutePath()))); + } + } + } + + QBS_CHECK(!m_availableJobs.empty()); + ExecutorJob *job = m_availableJobs.takeFirst(); + for (Artifact * const artifact : qAsConst(transformer->outputs)) + artifact->buildState = BuildGraphNode::Building; + m_processingJobs.insert(job, transformer); + updateJobCounts(transformer.get(), 1); + job->run(transformer.get()); +} + +void Executor::finishTransformer(const TransformerPtr &transformer) +{ + transformer->markedForRerun = false; + for (Artifact * const artifact : qAsConst(transformer->outputs)) { + possiblyInstallArtifact(artifact); + finishArtifact(artifact); + } + if (m_progressObserver) { + const auto it = m_pendingTransformersPerRule.find(transformer->rule.get()); + QBS_CHECK(it != m_pendingTransformersPerRule.cend()); + if (--it->second == 0) { + m_progressObserver->incrementProgressValue(); + m_pendingTransformersPerRule.erase(it); + } + } +} + +void Executor::possiblyInstallArtifact(const Artifact *artifact) +{ + AccumulatingTimer installTimer(m_buildOptions.logElapsedTime() + ? &m_elapsedTimeInstalling : nullptr); + + if (m_buildOptions.install() && !m_buildOptions.executeRulesOnly() + && (m_activeFileTags.empty() || artifactHasMatchingOutputTags(artifact)) + && artifact->properties->qbsPropertyValue(StringConstants::installProperty()) + .toBool()) { + m_productInstaller->copyFile(artifact); + } +} + +void Executor::onJobFinished(const qbs::ErrorInfo &err) +{ + try { + auto const job = qobject_cast(sender()); + QBS_CHECK(job); + if (m_evalContext->engine()->isActive()) { + qCDebug(lcExec) << "Executor job finished while rule execution is pausing. " + "Delaying slot execution."; + QTimer::singleShot(0, job, [job, err] { job->finished(err); }); + return; + } + + if (err.hasError()) { + if (m_buildOptions.keepGoing()) { + ErrorInfo fullWarning(err); + fullWarning.prepend(Tr::tr("Ignoring the following errors on user request:")); + m_logger.printWarning(fullWarning); + } else { + if (!m_error.hasError()) + m_error = err; // All but the first one could be due to canceling. + } + } + + finishJob(job, !err.hasError()); + } catch (const ErrorInfo &error) { + handleError(error); + } +} + +void Executor::checkForUnbuiltProducts() +{ + if (m_buildOptions.executeRulesOnly()) + return; + std::vector unbuiltProducts; + for (const ResolvedProductPtr &product : qAsConst(m_productsToBuild)) { + bool productBuilt = true; + for (BuildGraphNode *rootNode : qAsConst(product->buildData->rootNodes())) { + if (rootNode->buildState != BuildGraphNode::Built) { + productBuilt = false; + unbuiltProducts.push_back(product); + break; + } + } + if (productBuilt) { + // Any element still left after a successful build has not been re-created + // by any rule and therefore does not exist anymore as an artifact. + const AllRescuableArtifactData rad = product->buildData->rescuableArtifactData(); + for (auto it = rad.cbegin(); it != rad.cend(); ++it) { + removeGeneratedArtifactFromDisk(it.key(), m_logger); + product->buildData->removeFromRescuableArtifactData(it.key()); + m_artifactsRemovedFromDisk << it.key(); + } + } + } + + if (unbuiltProducts.empty()) { + 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())); + QStringList productNames; + std::transform(unbuiltProducts.cbegin(), unbuiltProducts.cend(), + std::back_inserter(productNames), + [](const ResolvedProductConstPtr &p) { return p->fullDisplayName(); }); + std::sort(productNames.begin(), productNames.end()); + m_error.append(productNames.join(QLatin1String(", "))); + } +} + +bool Executor::checkNodeProduct(BuildGraphNode *node) +{ + if (!m_partialBuild || contains(m_productsToBuild, node->product.lock())) + return true; + + // TODO: Turn this into a warning once we have a reliable C++ scanner. + qCDebug(lcExec).noquote() + << "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; +} + +void Executor::finish() +{ + QBS_ASSERT(m_state != ExecutorIdle, /* ignore */); + QBS_ASSERT(!m_evalContext || !m_evalContext->engine()->isActive(), /* ignore */); + + checkForUnbuiltProducts(); + if (m_explicitlyCanceled) { + QString message = Tr::tr(m_buildOptions.executeRulesOnly() + ? "Rule execution canceled" : "Build canceled"); + m_error.append(Tr::tr("%1%2.").arg(message, configString())); + } + setState(ExecutorIdle); + if (m_progressObserver) { + m_progressObserver->setFinished(); + m_cancelationTimer->stop(); + } + + EmptyDirectoriesRemover(m_project.get(), m_logger) + .removeEmptyParentDirectories(m_artifactsRemovedFromDisk); + + if (m_buildOptions.logElapsedTime()) { + m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("Rule execution took %1.") + .arg(elapsedTimeString(m_elapsedTimeRules)); + m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("Artifact scanning took %1.") + .arg(elapsedTimeString(m_elapsedTimeScanners)); + m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("Installing artifacts took %1.") + .arg(elapsedTimeString(m_elapsedTimeInstalling)); + } + + emit finished(); +} + +void Executor::checkForCancellation() +{ + QBS_ASSERT(m_progressObserver, return); + if (m_state == ExecutorRunning && m_progressObserver->canceled()) { + cancelJobs(); + if (m_evalContext->engine()->isActive()) + m_evalContext->engine()->cancel(); + } +} + +bool Executor::visit(Artifact *artifact) +{ + QBS_CHECK(artifact->buildState != BuildGraphNode::Untouched); + buildArtifact(artifact); + return false; +} + +bool Executor::visit(RuleNode *ruleNode) +{ + QBS_CHECK(ruleNode->buildState != BuildGraphNode::Untouched); + executeRuleNode(ruleNode); + return false; +} + +/** + * Sets the state of all artifacts in the graph to "untouched". + * This must be done before doing a build. + * + * Retrieves the timestamps of source artifacts. + * + * This function also fills the list of changed source files. + */ +void Executor::prepareAllNodes() +{ + for (const ResolvedProductPtr &product : m_allProducts) { + if (product->enabled) { + QBS_CHECK(product->buildData); + for (BuildGraphNode * const node : qAsConst(product->buildData->allNodes())) + node->buildState = BuildGraphNode::Untouched; + } + } + for (const ResolvedProductPtr &product : qAsConst(m_productsToBuild)) { + QBS_CHECK(product->buildData); + for (Artifact * const artifact : filterByType(product->buildData->allNodes())) + prepareArtifact(artifact); + } +} + +void Executor::syncFileDependencies() +{ + Set &globalFileDepList = m_project->buildData->fileDependencies; + for (auto it = globalFileDepList.begin(); it != globalFileDepList.end(); ) { + FileDependency * const dep = *it; + FileInfo fi(dep->filePath()); + if (fi.exists()) { + dep->setTimestamp(fi.lastModified()); + ++it; + continue; + } + qCDebug(lcBuildGraph()) << "file dependency" << dep->filePath() << "no longer exists; " + "removing from lookup table"; + m_project->buildData->removeFromLookupTable(dep); + bool isReferencedByArtifact = false; + for (const auto &product : m_allProducts) { + if (!product->buildData) + continue; + const auto artifactList = filterByType(product->buildData->allNodes()); + isReferencedByArtifact = std::any_of(artifactList.begin(), artifactList.end(), + [dep](const Artifact *a) { return a->fileDependencies.contains(dep); }); + // TODO: Would it be safe to mark the artifact as "not up to date" here and clear + // its list of file dependencies, rather than doing the check again in + // isUpToDate()? + if (isReferencedByArtifact) + break; + } + if (!isReferencedByArtifact) { + qCDebug(lcBuildGraph()) << "dependency is not referenced by any artifact, deleting"; + it = globalFileDepList.erase(it); + delete dep; + } else { + dep->clearTimestamp(); + ++it; + } + } +} + +void Executor::prepareArtifact(Artifact *artifact) +{ + artifact->inputsScanned = false; + artifact->timestampRetrieved = false; + + if (artifact->artifactType == Artifact::SourceFile) { + retrieveSourceFileTimestamp(artifact); + possiblyInstallArtifact(artifact); + } +} + +void Executor::setupForBuildingSelectedFiles(const BuildGraphNode *node) +{ + if (node->type() != BuildGraphNode::RuleNodeType) + return; + if (m_buildOptions.filesToConsider().empty()) + return; + if (!m_productsOfFilesToConsider.contains(node->product.lock())) + return; + const auto ruleNode = static_cast(node); + const Rule * const rule = ruleNode->rule().get(); + if (rule->inputs.intersects(m_tagsOfFilesToConsider)) { + FileTags otherInputs = rule->auxiliaryInputs; + otherInputs.unite(rule->explicitlyDependsOn).subtract(rule->excludedInputs); + m_tagsNeededForFilesToConsider.unite(otherInputs); + } else if (rule->collectedOutputFileTags().intersects(m_tagsNeededForFilesToConsider)) { + FileTags allInputs = rule->inputs; + allInputs.unite(rule->auxiliaryInputs).unite(rule->explicitlyDependsOn) + .subtract(rule->excludedInputs); + m_tagsNeededForFilesToConsider.unite(allInputs); + } +} + +/** + * Walk the build graph top-down from the roots and for each reachable node N + * - mark N as buildable. + */ +void Executor::prepareReachableNodes() +{ + for (BuildGraphNode * const root : qAsConst(m_roots)) + prepareReachableNodes_impl(root); +} + +void Executor::prepareReachableNodes_impl(BuildGraphNode *node) +{ + setupForBuildingSelectedFiles(node); + + if (node->buildState != BuildGraphNode::Untouched) + return; + + node->buildState = BuildGraphNode::Buildable; + for (BuildGraphNode *child : qAsConst(node->children)) + prepareReachableNodes_impl(child); +} + +void Executor::prepareProducts() +{ + ProductPrioritySetter prioritySetter(m_allProducts); + prioritySetter.apply(); + for (const ResolvedProductPtr &product : qAsConst(m_productsToBuild)) { + EnvironmentScriptRunner(product.get(), m_evalContext.get(), m_project->environment) + .setupForBuild(); + } +} + +void Executor::setupRootNodes() +{ + m_roots.clear(); + for (const ResolvedProductPtr &product : qAsConst(m_productsToBuild)) + m_roots += product->buildData->rootNodes(); +} + +void Executor::setState(ExecutorState s) +{ + if (m_state == s) + return; + m_state = s; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/executor.h b/src/lib/corelib/buildgraph/executor.h new file mode 100644 index 00000000..cc879e12 --- /dev/null +++ b/src/lib/corelib/buildgraph/executor.h @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** 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 QBS_BUILDGRAPHEXECUTOR_H +#define QBS_BUILDGRAPHEXECUTOR_H + +#include "forward_decls.h" +#include "buildgraphvisitor.h" +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE +class QTimer; +QT_END_NAMESPACE + +namespace qbs { +class ProcessResult; + +namespace Internal { +class ExecutorJob; +class FileTime; +class InputArtifactScannerContext; +class ProductInstaller; +class ProgressObserver; +class RuleNode; + +class Executor : public QObject, private BuildGraphVisitor +{ + Q_OBJECT + +public: + void build(); + + Executor(Logger logger, QObject *parent = nullptr); + ~Executor() override; + + void setProject(const TopLevelProjectPtr &project); + void setProducts(const QVector &productsToBuild); + void setBuildOptions(const BuildOptions &buildOptions); + void setProgressObserver(ProgressObserver *observer) { m_progressObserver = observer; } + + ErrorInfo error() const { return m_error; } + +signals: + void reportCommandDescription(const QString &highlight, const QString &message); + void reportProcessResult(const qbs::ProcessResult &result); + + void finished(); + +private: + void onJobFinished(const qbs::ErrorInfo &err); + void finish(); + void checkForCancellation(); + + // BuildGraphVisitor implementation + bool visit(Artifact *artifact) override; + bool visit(RuleNode *ruleNode) override; + + enum ExecutorState { ExecutorIdle, ExecutorRunning, ExecutorCanceling }; + + struct ComparePriority + { + bool operator() (const BuildGraphNode *x, const BuildGraphNode *y) const; + }; + + using Leaves = std::priority_queue, + ComparePriority>; + + void doBuild(); + void prepareAllNodes(); + void syncFileDependencies(); + void prepareArtifact(Artifact *artifact); + void setupForBuildingSelectedFiles(const BuildGraphNode *node); + void prepareReachableNodes(); + void prepareReachableNodes_impl(BuildGraphNode *node); + void prepareProducts(); + void setupRootNodes(); + void initLeaves(); + void updateLeaves(const NodeSet &nodes); + void updateLeaves(BuildGraphNode *node, NodeSet &seenNodes); + bool scheduleJobs(); + void buildArtifact(Artifact *artifact); + void executeRuleNode(RuleNode *ruleNode); + void finishJob(ExecutorJob *job, bool success); + void finishNode(BuildGraphNode *leaf); + void finishArtifact(Artifact *artifact); + void setState(ExecutorState); + void addExecutorJobs(); + void cancelJobs(); + void setupProgressObserver(); + void doSanityChecks(); + void handleError(const ErrorInfo &error); + void rescueOldBuildData(Artifact *artifact, bool *childrenAdded); + bool checkForUnbuiltDependencies(Artifact *artifact); + void potentiallyRunTransformer(const TransformerPtr &transformer); + void runTransformer(const TransformerPtr &transformer); + void finishTransformer(const TransformerPtr &transformer); + void possiblyInstallArtifact(const Artifact *artifact); + void checkForUnbuiltProducts(); + bool checkNodeProduct(BuildGraphNode *node); + + bool mustExecuteTransformer(const TransformerPtr &transformer) const; + bool isUpToDate(Artifact *artifact) const; + void retrieveSourceFileTimestamp(Artifact *artifact) const; + FileTime recursiveFileTime(const QString &filePath) const; + QString configString() const; + bool transformerHasMatchingOutputTags(const TransformerConstPtr &transformer) const; + bool artifactHasMatchingOutputTags(const Artifact *artifact) const; + bool transformerHasMatchingInputFiles(const TransformerConstPtr &transformer) const; + + void setupJobLimits(); + void updateJobCounts(const Transformer *transformer, int diff); + bool schedulingBlockedByJobLimit(const BuildGraphNode *node); + + using JobMap = QHash; + JobMap m_processingJobs; + + ProductInstaller *m_productInstaller; + RulesEvaluationContextPtr m_evalContext; + BuildOptions m_buildOptions; + Logger m_logger; + ProgressObserver *m_progressObserver; + std::vector> m_allJobs; + QList m_availableJobs; + ExecutorState m_state; + TopLevelProjectPtr m_project; + QVector m_productsToBuild; + std::vector m_allProducts; + std::unordered_map m_productsByName; + std::unordered_map m_projectsByName; + std::unordered_map m_jobCountPerPool; + std::unordered_map m_jobLimitsPerProduct; + std::unordered_map m_pendingTransformersPerRule; + NodeSet m_roots; + Leaves m_leaves; + InputArtifactScannerContext *m_inputArtifactScanContext; + ErrorInfo m_error; + bool m_explicitlyCanceled = false; + FileTags m_activeFileTags; + FileTags m_tagsOfFilesToConsider; + FileTags m_tagsNeededForFilesToConsider; + QList m_productsOfFilesToConsider; + QTimer * const m_cancelationTimer; + QStringList m_artifactsRemovedFromDisk; + bool m_partialBuild = false; + qint64 m_elapsedTimeRules = 0; + qint64 m_elapsedTimeScanners = 0; + qint64 m_elapsedTimeInstalling = 0; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILDGRAPHEXECUTOR_H diff --git a/src/lib/corelib/buildgraph/executorjob.cpp b/src/lib/corelib/buildgraph/executorjob.cpp new file mode 100644 index 00000000..bc96cbb6 --- /dev/null +++ b/src/lib/corelib/buildgraph/executorjob.cpp @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** 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 "executorjob.h" + +#include "artifact.h" +#include "jscommandexecutor.h" +#include "processcommandexecutor.h" +#include "rulecommands.h" +#include "transformer.h" +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +ExecutorJob::ExecutorJob(const Logger &logger, QObject *parent) + : QObject(parent) + , m_processCommandExecutor(new ProcessCommandExecutor(logger, this)) + , m_jsCommandExecutor(new JsCommandExecutor(logger, this)) +{ + connect(m_processCommandExecutor, &AbstractCommandExecutor::reportCommandDescription, + this, &ExecutorJob::reportCommandDescription); + connect(m_processCommandExecutor, &ProcessCommandExecutor::reportProcessResult, + this, &ExecutorJob::reportProcessResult); + connect(m_processCommandExecutor, &AbstractCommandExecutor::finished, + this, &ExecutorJob::onCommandFinished); + connect(m_jsCommandExecutor, &AbstractCommandExecutor::reportCommandDescription, + this, &ExecutorJob::reportCommandDescription); + connect(m_jsCommandExecutor, &AbstractCommandExecutor::finished, + this, &ExecutorJob::onCommandFinished); + reset(); +} + +ExecutorJob::~ExecutorJob() = default; + +void ExecutorJob::setMainThreadScriptEngine(ScriptEngine *engine) +{ + m_processCommandExecutor->setMainThreadScriptEngine(engine); + m_jsCommandExecutor->setMainThreadScriptEngine(engine); +} + +void ExecutorJob::setDryRun(bool enabled) +{ + m_processCommandExecutor->setDryRunEnabled(enabled); + m_jsCommandExecutor->setDryRunEnabled(enabled); +} + +void ExecutorJob::setEchoMode(CommandEchoMode echoMode) +{ + m_processCommandExecutor->setEchoMode(echoMode); + m_jsCommandExecutor->setEchoMode(echoMode); +} + +void ExecutorJob::run(Transformer *t) +{ + QBS_ASSERT(m_currentCommandIdx == -1, return); + + if (t->commands.empty()) { + setFinished(); + return; + } + + t->propertiesRequestedInCommands.clear(); + t->propertiesRequestedFromArtifactInCommands.clear(); + t->importedFilesUsedInCommands.clear(); + t->depsRequestedInCommands.clear(); + t->artifactsMapRequestedInCommands.clear(); + t->exportedModulesAccessedInCommands.clear(); + t->lastCommandExecutionTime = FileTime::currentTime(); + QBS_CHECK(!t->outputs.empty()); + m_processCommandExecutor->setProcessEnvironment( + (*t->outputs.cbegin())->product->buildEnvironment); + m_transformer = t; + m_jobPools = t->jobPools(); + runNextCommand(); +} + +void ExecutorJob::cancel() +{ + if (!m_currentCommandExecutor) + return; + m_error = ErrorInfo(tr("Transformer execution canceled.")); + m_currentCommandExecutor->cancel(); +} + +void ExecutorJob::runNextCommand() +{ + QBS_ASSERT(m_currentCommandIdx <= m_transformer->commands.size(), return); + ++m_currentCommandIdx; + if (m_currentCommandIdx >= m_transformer->commands.size()) { + setFinished(); + return; + } + + const AbstractCommandPtr &command = m_transformer->commands.commandAt(m_currentCommandIdx); + switch (command->type()) { + case AbstractCommand::ProcessCommandType: + m_currentCommandExecutor = m_processCommandExecutor; + break; + case AbstractCommand::JavaScriptCommandType: + m_currentCommandExecutor = m_jsCommandExecutor; + break; + default: + qFatal("Missing implementation for command type %d", command->type()); + } + + m_currentCommandExecutor->start(m_transformer, command.get()); +} + +void ExecutorJob::onCommandFinished(const ErrorInfo &err) +{ + QBS_ASSERT(m_transformer, return); + if (m_error.hasError()) { // Canceled? + setFinished(); + } else if (err.hasError()) { + m_error = err; + setFinished(); + } else { + runNextCommand(); + } +} + +void ExecutorJob::setFinished() +{ + const ErrorInfo err = m_error; + reset(); + emit finished(err); +} + +void ExecutorJob::reset() +{ + m_transformer = nullptr; + m_jobPools.clear(); + m_currentCommandExecutor = nullptr; + m_currentCommandIdx = -1; + m_error.clear(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/executorjob.h b/src/lib/corelib/buildgraph/executorjob.h new file mode 100644 index 00000000..1f8f0cd7 --- /dev/null +++ b/src/lib/corelib/buildgraph/executorjob.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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 QBS_EXECUTORJOB_H +#define QBS_EXECUTORJOB_H + +#include +#include +#include +#include + +#include +#include + +namespace qbs { +class CodeLocation; +class ProcessResult; + +namespace Internal { +class AbstractCommandExecutor; +class ProductBuildData; +class JsCommandExecutor; +class Logger; +class ProcessCommandExecutor; +class ScriptEngine; +class Transformer; + +class ExecutorJob : public QObject +{ + Q_OBJECT +public: + explicit ExecutorJob(const Logger &logger, QObject *parent = nullptr); + ~ExecutorJob() override; + + void setMainThreadScriptEngine(ScriptEngine *engine); + void setDryRun(bool enabled); + void setEchoMode(CommandEchoMode echoMode); + void run(Transformer *t); + void cancel(); + const Transformer *transformer() const { return m_transformer; } + Set jobPools() const { return m_jobPools; } + +signals: + void reportCommandDescription(const QString &highlight, const QString &message); + void reportProcessResult(const qbs::ProcessResult &result); + void finished(const qbs::ErrorInfo &error = ErrorInfo()); // !hasError() <=> command successful + +private: + void runNextCommand(); + void onCommandFinished(const qbs::ErrorInfo &err); + + void setFinished(); + void reset(); + + AbstractCommandExecutor *m_currentCommandExecutor = nullptr; + ProcessCommandExecutor *m_processCommandExecutor = nullptr; + JsCommandExecutor *m_jsCommandExecutor = nullptr; + Transformer *m_transformer = nullptr; + Set m_jobPools; + int m_currentCommandIdx = 0; + ErrorInfo m_error; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_EXECUTORJOB_H diff --git a/src/lib/corelib/buildgraph/filedependency.cpp b/src/lib/corelib/buildgraph/filedependency.cpp new file mode 100644 index 00000000..cbee758c --- /dev/null +++ b/src/lib/corelib/buildgraph/filedependency.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** 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 "filedependency.h" + +#include + +namespace qbs { +namespace Internal { + +FileResourceBase::FileResourceBase() = default; + +FileResourceBase::~FileResourceBase() = default; + +void FileResourceBase::setTimestamp(const FileTime &t) + +{ + m_timestamp = t; +} + +const FileTime &FileResourceBase::timestamp() const +{ + return m_timestamp; +} + +void FileResourceBase::setFilePath(const QString &filePath) +{ + m_filePath = filePath; + FileInfo::splitIntoDirectoryAndFileName(m_filePath, &m_dirPath, &m_fileName); +} + +const QString &FileResourceBase::filePath() const +{ + return m_filePath; +} + +void FileResourceBase::load(PersistentPool &pool) +{ + serializationOp(pool); + FileInfo::splitIntoDirectoryAndFileName(m_filePath, &m_dirPath, &m_fileName); +} + +void FileResourceBase::store(PersistentPool &pool) +{ + serializationOp(pool); +} + + +FileDependency::FileDependency() = default; + +FileDependency::~FileDependency() = default; + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/filedependency.h b/src/lib/corelib/buildgraph/filedependency.h new file mode 100644 index 00000000..802654e9 --- /dev/null +++ b/src/lib/corelib/buildgraph/filedependency.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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 QBS_FILEDEPENDENCY_H +#define QBS_FILEDEPENDENCY_H + +#include +#include + +namespace qbs { +namespace Internal { + +class FileResourceBase +{ +protected: + FileResourceBase(); + +public: + virtual ~FileResourceBase(); + + enum FileType { FileTypeDependency, FileTypeArtifact }; + virtual FileType fileType() const = 0; + + void setTimestamp(const FileTime &t); + const FileTime ×tamp() const; + void clearTimestamp() { m_timestamp.clear(); } + + void setFilePath(const QString &filePath); + const QString &filePath() const; + QString dirPath() const { return m_dirPath.toString(); } + QString fileName() const { return m_fileName.toString(); } + + virtual void load(PersistentPool &pool); + virtual void store(PersistentPool &pool); + +private: + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(m_filePath, m_timestamp); + } + + FileTime m_timestamp; + QString m_filePath; + QStringRef m_dirPath; + QStringRef m_fileName; +}; + +class FileDependency : public FileResourceBase +{ +public: + FileDependency(); + ~FileDependency() override; + + FileType fileType() const override { return FileTypeDependency; } +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_FILEDEPENDENCY_H diff --git a/src/lib/corelib/buildgraph/forward_decls.h b/src/lib/corelib/buildgraph/forward_decls.h new file mode 100644 index 00000000..a7627228 --- /dev/null +++ b/src/lib/corelib/buildgraph/forward_decls.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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 QBS_BG_FORWARD_DECLS_H +#define QBS_BG_FORWARD_DECLS_H + +#include + +namespace qbs { +namespace Internal { + +class Artifact; +class BuildGraphNode; +class ProjectBuildData; +class ProductBuildData; +class RuleNode; + +class Transformer; +using TransformerPtr = std::shared_ptr; +using TransformerConstPtr = std::shared_ptr; + +class RulesEvaluationContext; +using RulesEvaluationContextPtr = std::shared_ptr; + +class AbstractCommand; +using AbstractCommandPtr = std::shared_ptr; + +class ProcessCommand; +using ProcessCommandPtr = std::shared_ptr; + +class JavaScriptCommand; +using JavaScriptCommandPtr = std::shared_ptr; + +template class Set; +using ArtifactSet = Set; +using NodeSet = Set; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BG_FORWARD_DECLS_H diff --git a/src/lib/corelib/buildgraph/inputartifactscanner.cpp b/src/lib/corelib/buildgraph/inputartifactscanner.cpp new file mode 100644 index 00000000..1d00e29e --- /dev/null +++ b/src/lib/corelib/buildgraph/inputartifactscanner.cpp @@ -0,0 +1,399 @@ +/**************************************************************************** +** +** 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 "inputartifactscanner.h" + +#include "artifact.h" +#include "buildgraph.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "transformer.h" +#include "depscanner.h" +#include "rulesevaluationcontext.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +static void resolveDepencency(const RawScannedDependency &dependency, + const ResolvedProduct *product, ResolvedDependency *result, + const QString &baseDir = QString()) +{ + QString absDirPath = baseDir.isEmpty() + ? dependency.dirPath() + : dependency.dirPath().isEmpty() + ? baseDir : FileInfo::resolvePath(baseDir, dependency.dirPath()); + if (!dependency.isClean()) + absDirPath = QDir::cleanPath(absDirPath); + + ResolvedProject *project = product->project.get(); + FileDependency *fileDependencyArtifact = nullptr; + Artifact *dependencyInProduct = nullptr; + Artifact *dependencyInOtherProduct = nullptr; + bool productOfDependencyIsDependency = false; + const auto files = project->topLevelProject() + ->buildData->lookupFiles(absDirPath, dependency.fileName()); + for (FileResourceBase *lookupResult : files) { + switch (lookupResult->fileType()) { + case FileResourceBase::FileTypeDependency: + fileDependencyArtifact = static_cast(lookupResult); + break; + case FileResourceBase::FileTypeArtifact: { + auto const foundArtifact = static_cast(lookupResult); + if (foundArtifact->product == product) { + dependencyInProduct = foundArtifact; + } else if (!productOfDependencyIsDependency) { + dependencyInOtherProduct = foundArtifact; + productOfDependencyIsDependency + = contains(product->dependencies, dependencyInOtherProduct->product.lock()); + } + break; + } + } + if (dependencyInProduct) + break; + } + + // prioritize found artifacts + if ((result->file = dependencyInProduct) + || (result->file = dependencyInOtherProduct) + || (result->file = fileDependencyArtifact)) { + result->filePath = result->file->filePath(); + + if (result->file == dependencyInOtherProduct && !productOfDependencyIsDependency) { + qCDebug(lcDepScan) << "product" << dependencyInOtherProduct->product->fullDisplayName() + << "of scanned dependency" << result->filePath + << "is not a dependency of product" << product->fullDisplayName() + << ". The file dependency might get lost during change tracking."; + } + + return; + } + + const QString &absFilePath = baseDir.isEmpty() + ? dependency.filePath() + : absDirPath + QLatin1Char('/') + dependency.fileName(); + + // TODO: We probably need a flag that tells us whether directories are allowed. + const FileInfo fi(absFilePath); + if (fi.exists(absFilePath) && !fi.isDir()) + result->filePath = absFilePath; +} + +InputArtifactScanner::InputArtifactScanner(Artifact *artifact, InputArtifactScannerContext *ctx, + Logger logger) + : m_artifact(artifact), + m_rawScanResults(artifact->product->topLevelProject()->buildData->rawScanResults), + m_context(ctx), + m_newDependencyAdded(false), + m_logger(std::move(logger)) +{ +} + +void InputArtifactScanner::scan() +{ + if (m_artifact->inputsScanned) + return; + + qCDebug(lcDepScan) << "scan inputs for" << m_artifact->filePath() << m_artifact->fileTags() + << "in product" << m_artifact->product->name; + + m_artifact->inputsScanned = true; + + // clear file dependencies; they will be regenerated + m_artifact->fileDependencies.clear(); + + // Remove all connections to children that were added by the dependency scanner. + // They will be regenerated. + const Set childrenAddedByScanner = m_artifact->childrenAddedByScanner; + m_artifact->childrenAddedByScanner.clear(); + for (Artifact * const dependency : childrenAddedByScanner) + disconnect(m_artifact, dependency); + + for (Artifact * const inputArtifact : qAsConst(m_artifact->transformer->inputs)) + scanForFileDependencies(inputArtifact); +} + +void InputArtifactScanner::scanForFileDependencies(Artifact *inputArtifact) +{ + qCDebug(lcDepScan) << "input artifact" << inputArtifact->filePath() + << inputArtifact->fileTags(); + + Set visitedFilePaths; + QList filesToScan; + filesToScan.push_back(inputArtifact); + const Set scanners = scannersForArtifact(inputArtifact); + if (scanners.empty()) + return; + m_fileTagsForScanner + = inputArtifact->fileTags().toStringList().join(QLatin1Char(',')).toLatin1(); + InputArtifactScannerContext::CacheItem *lastPerFileCacheItem = nullptr; + InputArtifactScannerContext::CacheItem *lastPerPropsCacheItem = nullptr; + while (!filesToScan.empty()) { + FileResourceBase *fileToBeScanned = filesToScan.takeFirst(); + const QString &filePathToBeScanned = fileToBeScanned->filePath(); + if (!visitedFilePaths.insert(filePathToBeScanned).second) + continue; + + for (DependencyScanner * const scanner : scanners) { + InputArtifactScannerContext::CacheItem *cacheItem; + if (scanner->cacheIsPerFile()) { + if (!lastPerFileCacheItem) + lastPerFileCacheItem = &m_context->cachePerFile[inputArtifact]; + cacheItem = lastPerFileCacheItem; + } else { + if (!lastPerPropsCacheItem) { + lastPerPropsCacheItem = &m_context->cachePerProperties + [inputArtifact->properties]; + } + cacheItem = lastPerPropsCacheItem; + } + scanForScannerFileDependencies(scanner, inputArtifact, fileToBeScanned, + scanner->recursive() ? &filesToScan : nullptr, (*cacheItem)[scanner->key()]); + } + } +} + +Set InputArtifactScanner::scannersForArtifact(const Artifact *artifact) const +{ + Set scanners; + ResolvedProduct *product = artifact->product.get(); + ScriptEngine *engine = product->topLevelProject()->buildData->evaluationContext->engine(); + QHash &scannerCache + = m_context->scannersCache[product]; + for (const FileTag &fileTag : artifact->fileTags()) { + InputArtifactScannerContext::DependencyScannerCacheItem &cache = scannerCache[fileTag]; + if (!cache.valid) { + cache.valid = true; + for (ScannerPlugin *scanner : ScannerPluginManager::scannersForFileTag(fileTag)) { + const auto pluginScanner = new PluginDependencyScanner(scanner); + cache.scanners.push_back(DependencyScannerPtr(pluginScanner)); + } + for (const ResolvedScannerConstPtr &scanner : product->scanners) { + if (scanner->inputs.contains(fileTag)) { + cache.scanners.push_back(DependencyScannerPtr( + new UserDependencyScanner(scanner, engine))); + break; + } + } + } + for (const DependencyScannerPtr &scanner : qAsConst(cache.scanners)) + scanners += scanner.get(); + } + return scanners; +} + +void InputArtifactScanner::scanForScannerFileDependencies(DependencyScanner *scanner, + Artifact *inputArtifact, FileResourceBase *fileToBeScanned, + QList *filesToScan, + InputArtifactScannerContext::ScannerResolvedDependenciesCache &cache) +{ + qCDebug(lcDepScan) << "file" << fileToBeScanned->filePath(); + + const bool cacheHit = cache.valid; + if (!cacheHit) { + cache.valid = true; + cache.searchPaths = scanner->collectSearchPaths(inputArtifact); + } + 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 { + qCDebug(lcDepScan) << "scanning" << FileInfo::fileName(filePathToBeScanned); + scanWithScannerPlugin(scanner, inputArtifact, fileToBeScanned, &scanData.rawScanResult); + scanData.lastScanTime = FileTime::currentTime(); + } catch (const ErrorInfo &error) { + m_logger.printWarning(error); + return; + } + } + + resolveScanResultDependencies(inputArtifact, scanData.rawScanResult, filesToScan, cache); +} + +void InputArtifactScanner::resolveScanResultDependencies(const Artifact *inputArtifact, + const RawScanResult &scanResult, QList *artifactsToScan, + InputArtifactScannerContext::ScannerResolvedDependenciesCache &cache) +{ + for (const RawScannedDependency &dependency : scanResult.deps) { + const QString &dependencyFilePath = dependency.filePath(); + InputArtifactScannerContext::ResolvedDependencyCacheItem &cachedResolvedDependencyItem + = cache.resolvedDependenciesCache[dependency.dirPath()][dependency.fileName()]; + ResolvedDependency &resolvedDependency = cachedResolvedDependencyItem.resolvedDependency; + if (cachedResolvedDependencyItem.valid) { + if (resolvedDependency.filePath.isEmpty()) + goto unresolved; + goto resolved; + } + cachedResolvedDependencyItem.valid = true; + + if (FileInfo::isAbsolute(dependencyFilePath)) { + resolveDepencency(dependency, inputArtifact->product.get(), &resolvedDependency); + if (resolvedDependency.filePath.isEmpty()) + goto unresolved; + goto resolved; + } + + // try include paths + for (const QString &includePath : qAsConst(cache.searchPaths)) { + resolveDepencency(dependency, inputArtifact->product.get(), + &resolvedDependency, includePath); + if (resolvedDependency.isValid()) + goto resolved; + } + +unresolved: + qCWarning(lcDepScan) << "unresolved dependency " << dependencyFilePath; + continue; + +resolved: + handleDependency(resolvedDependency); + if (artifactsToScan && resolvedDependency.file) { + if (resolvedDependency.file->fileType() == FileResourceBase::FileTypeArtifact) { + // Do not scan an artifact that is not built yet: Its contents might still change. + auto const artifactDependency = static_cast(resolvedDependency.file); + if (artifactDependency->artifactType == Artifact::SourceFile + || artifactDependency->buildState == BuildGraphNode::Built) { + artifactsToScan->push_back(artifactDependency); + } + } else { + // Add file dependency to the next round of scanning. + artifactsToScan->push_back(resolvedDependency.file); + } + } + } +} + +void InputArtifactScanner::handleDependency(ResolvedDependency &dependency) +{ + const ResolvedProductPtr product = m_artifact->product.lock(); + QBS_CHECK(m_artifact->artifactType == Artifact::Generated); + QBS_CHECK(product); + + Artifact *artifactDependency = nullptr; + FileDependency *fileDependency = nullptr; + if (dependency.file) { + switch (dependency.file->fileType()) { + case FileResourceBase::FileTypeArtifact: + artifactDependency = static_cast(dependency.file); + break; + case FileResourceBase::FileTypeDependency: + fileDependency = static_cast(dependency.file); + break; + } + } + QBS_CHECK(!dependency.file || artifactDependency || fileDependency); + + if (!dependency.file) { + // The dependency is an existing file but does not exist in the build graph. + qCDebug(lcDepScan) << "add new file dependency" << dependency.filePath; + + fileDependency = new FileDependency(); + dependency.file = fileDependency; + fileDependency->setFilePath(dependency.filePath); + product->topLevelProject()->buildData->insertFileDependency(fileDependency); + } else if (fileDependency) { + // The dependency exists in the project's list of file dependencies. + qCDebug(lcDepScan) << "add existing file dependency" << dependency.filePath; + } else if (artifactDependency->product == product) { + // The dependency is in our product. + qCDebug(lcDepScan) << "add artifact dependency" << dependency.filePath << + "(from this product)"; + } else { + // The dependency is in some other product. + ResolvedProduct * const otherProduct = artifactDependency->product; + qCDebug(lcDepScan) + << "add artifact dependency" << dependency.filePath + << " (from product" << otherProduct->uniqueName() << ')'; + } + + if (m_artifact == dependency.file) + return; + if (artifactDependency && artifactDependency->transformer == m_artifact->transformer) + return; + + if (fileDependency) { + m_artifact->fileDependencies << fileDependency; + if (!fileDependency->timestamp().isValid()) + fileDependency->setTimestamp(FileInfo(fileDependency->filePath()).lastModified()); + } else { + if (m_artifact->children.contains(artifactDependency)) + return; + if (safeConnect(m_artifact, artifactDependency)) + m_artifact->childrenAddedByScanner += artifactDependency; + m_newDependencyAdded = true; + } +} + +void InputArtifactScanner::scanWithScannerPlugin(DependencyScanner *scanner, + Artifact *inputArtifact, + FileResourceBase *fileToBeScanned, + RawScanResult *scanResult) +{ + scanResult->deps.clear(); + const QStringList &dependencies = scanner->collectDependencies( + inputArtifact, fileToBeScanned, m_fileTagsForScanner.constData()); + for (const QString &s : dependencies) + scanResult->deps.emplace_back(s); +} + +InputArtifactScannerContext::DependencyScannerCacheItem::DependencyScannerCacheItem() : valid(false) +{ +} + +InputArtifactScannerContext::DependencyScannerCacheItem::~DependencyScannerCacheItem() = default; + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/inputartifactscanner.h b/src/lib/corelib/buildgraph/inputartifactscanner.h new file mode 100644 index 00000000..7b7630b6 --- /dev/null +++ b/src/lib/corelib/buildgraph/inputartifactscanner.h @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** 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 QBS_INPUTARTIFACTSCANNER_H +#define QBS_INPUTARTIFACTSCANNER_H + +#include +#include +#include +#include + +#include +#include + +class ScannerPlugin; + +namespace qbs { +namespace Internal { + +class Artifact; +class FileResourceBase; +class RawScanResult; +class RawScanResults; +class PropertyMapInternal; + +class DependencyScanner; +using DependencyScannerPtr = std::shared_ptr; + +class ResolvedDependency +{ +public: + bool isValid() const { return !filePath.isNull(); } + + QString filePath; + FileResourceBase *file = nullptr; +}; + +class InputArtifactScannerContext +{ + struct ResolvedDependencyCacheItem + { + ResolvedDependencyCacheItem() + : valid(false) + {} + + bool valid; + ResolvedDependency resolvedDependency; + }; + + using ResolvedDependenciesCache = QHash>; + + struct ScannerResolvedDependenciesCache + { + ScannerResolvedDependenciesCache() : + valid(false) + {} + + bool valid; + QStringList searchPaths; + ResolvedDependenciesCache resolvedDependenciesCache; + }; + + struct DependencyScannerCacheItem + { + DependencyScannerCacheItem(); + ~DependencyScannerCacheItem(); + + bool valid; + QList scanners; + }; + + using CacheItem = QHash; + + QHash cachePerProperties; + QHash cachePerFile; + QHash> scannersCache; + + friend class InputArtifactScanner; +}; + +class InputArtifactScanner +{ +public: + InputArtifactScanner(Artifact *artifact, InputArtifactScannerContext *ctx, + Logger logger); + void scan(); + bool newDependencyAdded() const { return m_newDependencyAdded; } + +private: + void scanForFileDependencies(Artifact *inputArtifact); + Set scannersForArtifact(const Artifact *artifact) const; + void scanForScannerFileDependencies(DependencyScanner *scanner, + Artifact *inputArtifact, FileResourceBase *fileToBeScanned, + QList *filesToScan, + InputArtifactScannerContext::ScannerResolvedDependenciesCache &cache); + void resolveScanResultDependencies(const Artifact *inputArtifact, + const RawScanResult &scanResult, QList *artifactsToScan, + InputArtifactScannerContext::ScannerResolvedDependenciesCache &cache); + void handleDependency(ResolvedDependency &dependency); + void scanWithScannerPlugin(DependencyScanner *scanner, Artifact *inputArtifact, + FileResourceBase *fileToBeScanned, RawScanResult *scanResult); + + Artifact * const m_artifact; + RawScanResults &m_rawScanResults; + InputArtifactScannerContext *const m_context; + QByteArray m_fileTagsForScanner; + bool m_newDependencyAdded; + Logger m_logger; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_INPUTARTIFACTSCANNER_H diff --git a/src/lib/corelib/buildgraph/jscommandexecutor.cpp b/src/lib/corelib/buildgraph/jscommandexecutor.cpp new file mode 100644 index 00000000..b7270c9a --- /dev/null +++ b/src/lib/corelib/buildgraph/jscommandexecutor.cpp @@ -0,0 +1,289 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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 "jscommandexecutor.h" + +#include "artifact.h" +#include "buildgraph.h" +#include "rulecommands.h" +#include "transformer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +struct JavaScriptCommandResult +{ + bool success = false; + QString errorMessage; + CodeLocation errorLocation; +}; + +class JsCommandExecutorThreadObject : public QObject +{ + Q_OBJECT +public: + JsCommandExecutorThreadObject(Logger logger) + : m_logger(std::move(logger)) + , m_scriptEngine(nullptr) + { + } + + const JavaScriptCommandResult &result() const + { + return m_result; + } + + void cancel(const qbs::ErrorInfo &reason) + { + m_result.success = !reason.hasError(); + m_result.errorMessage = reason.toString(); + if (m_scriptEngine) + m_scriptEngine->abortEvaluation(); + m_cancelled = true; + } + +signals: + void finished(); + +public: + void start(const JavaScriptCommand *cmd, Transformer *transformer) + { + if (m_cancelled) { + emit finished(); + return; + } + + m_running = true; + try { + doStart(cmd, transformer); + } catch (const qbs::ErrorInfo &error) { + setError(error.toString(), cmd->codeLocation()); + } + + m_running = false; + emit finished(); + } + +private: + void doStart(const JavaScriptCommand *cmd, Transformer *transformer) + { + m_result.success = true; + m_result.errorMessage.clear(); + ScriptEngine * const scriptEngine = provideScriptEngine(); + QScriptValue scope = scriptEngine->newObject(); + scope.setPrototype(scriptEngine->globalObject()); + m_scriptEngine->clearRequestedProperties(); + setupScriptEngineForFile(scriptEngine, + transformer->rule->prepareScript.fileContext(), scope, + ObserveMode::Enabled); + + QScriptValue importScopeForSourceCode; + if (!cmd->scopeName().isEmpty()) + importScopeForSourceCode = scope.property(cmd->scopeName()); + + setupScriptEngineForProduct(scriptEngine, transformer->product().get(), + transformer->rule->module.get(), scope, true); + transformer->setupInputs(scope); + transformer->setupOutputs(scope); + transformer->setupExplicitlyDependsOn(scope); + + for (QVariantMap::const_iterator it = cmd->properties().constBegin(); + it != cmd->properties().constEnd(); ++it) { + scope.setProperty(it.key(), scriptEngine->toScriptValue(it.value())); + } + + scriptEngine->setGlobalObject(scope); + if (importScopeForSourceCode.isObject()) + scriptEngine->currentContext()->pushScope(importScopeForSourceCode); + scriptEngine->evaluate(cmd->sourceCode()); + scriptEngine->releaseResourcesOfScriptObjects(); + if (importScopeForSourceCode.isObject()) + scriptEngine->currentContext()->popScope(); + scriptEngine->setGlobalObject(scope.prototype()); + transformer->propertiesRequestedInCommands + += scriptEngine->propertiesRequestedInScript(); + unite(transformer->propertiesRequestedFromArtifactInCommands, + scriptEngine->propertiesRequestedFromArtifact()); + const std::vector &importFilesUsedInCommand + = scriptEngine->importedFilesUsedInScript(); + transformer->importedFilesUsedInCommands.insert( + transformer->importedFilesUsedInCommands.cend(), + importFilesUsedInCommand.cbegin(), importFilesUsedInCommand.cend()); + transformer->depsRequestedInCommands.add(scriptEngine->productsWithRequestedDependencies()); + transformer->artifactsMapRequestedInCommands.unite(scriptEngine->requestedArtifacts()); + for (const ResolvedProduct * const p : scriptEngine->requestedExports()) { + transformer->exportedModulesAccessedInCommands.insert( + std::make_pair(p->uniqueName(), p->exportedModule)); + } + scriptEngine->clearRequestedProperties(); + if (scriptEngine->hasUncaughtException()) { + // ### We don't know the line number of the command's sourceCode property assignment. + setError(scriptEngine->uncaughtException().toString(), cmd->codeLocation()); + } + } + + void setError(const QString &errorMessage, const CodeLocation &codeLocation) + { + m_result.success = false; + m_result.errorMessage = errorMessage; + m_result.errorLocation = codeLocation; + } + + ScriptEngine *provideScriptEngine() + { + if (!m_scriptEngine) + m_scriptEngine = ScriptEngine::create(m_logger, EvalContext::JsCommand, this); + return m_scriptEngine; + } + + Logger m_logger; + ScriptEngine *m_scriptEngine; + JavaScriptCommandResult m_result; + bool m_running = false; + bool m_cancelled = false; +}; + + +JsCommandExecutor::JsCommandExecutor(const Logger &logger, QObject *parent) + : AbstractCommandExecutor(logger, parent) + , m_thread(new QThread(this)) + , m_objectInThread(new JsCommandExecutorThreadObject(logger)) + , m_running(false) +{ + m_objectInThread->moveToThread(m_thread); + connect(m_objectInThread, &JsCommandExecutorThreadObject::finished, + this, &JsCommandExecutor::onJavaScriptCommandFinished); + connect(this, &JsCommandExecutor::startRequested, + m_objectInThread, &JsCommandExecutorThreadObject::start); +} + +JsCommandExecutor::~JsCommandExecutor() +{ + waitForFinished(); + m_thread->quit(); + m_thread->wait(); + delete m_objectInThread; +} + +void JsCommandExecutor::doReportCommandDescription(const QString &productName) +{ + if ((m_echoMode == CommandEchoModeCommandLine + || m_echoMode == CommandEchoModeCommandLineWithEnvironment) + && !command()->extendedDescription().isEmpty()) { + emit reportCommandDescription(command()->highlight(), command()->extendedDescription()); + return; + } + + AbstractCommandExecutor::doReportCommandDescription(productName); +} + +void JsCommandExecutor::waitForFinished() +{ + if (!m_running) + return; + QEventLoop loop; + connect(this, &AbstractCommandExecutor::finished, &loop, &QEventLoop::quit); + loop.exec(); +} + +bool JsCommandExecutor::doStart() +{ + QBS_ASSERT(!m_running, return false); + + if (dryRun() && !command()->ignoreDryRun()) { + QTimer::singleShot(0, this, [this] { emit finished(); }); // Don't call back on the caller. + return false; + } + + m_thread->start(); + m_running = true; + emit startRequested(jsCommand(), transformer()); + return true; +} + +void JsCommandExecutor::cancel(const qbs::ErrorInfo &reason) +{ + if (m_running && (!dryRun() || command()->ignoreDryRun())) + QTimer::singleShot(0, m_objectInThread, [objectInThread = QPointer{m_objectInThread}, reason] { + if (objectInThread) + objectInThread->cancel(reason); + }); +} + +void JsCommandExecutor::onJavaScriptCommandFinished() +{ + m_running = false; + const JavaScriptCommandResult &result = m_objectInThread->result(); + ErrorInfo err; + if (!result.success) { + logger().qbsDebug() << "JS context:\n" << jsCommand()->properties(); + logger().qbsDebug() << "JS code:\n" << jsCommand()->sourceCode(); + err.append(result.errorMessage); + // ### We don't know the line number of the command's sourceCode property assignment. + err.appendBacktrace(QStringLiteral("JavaScriptCommand.sourceCode")); + err.appendBacktrace(QStringLiteral("Rule.prepare"), result.errorLocation); + } + emit finished(err); +} + +const JavaScriptCommand *JsCommandExecutor::jsCommand() const +{ + return static_cast(command()); +} + +} // namespace Internal +} // namespace qbs + +#include "jscommandexecutor.moc" diff --git a/src/lib/corelib/buildgraph/jscommandexecutor.h b/src/lib/corelib/buildgraph/jscommandexecutor.h new file mode 100644 index 00000000..0725f0d2 --- /dev/null +++ b/src/lib/corelib/buildgraph/jscommandexecutor.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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_JSCOMMANDEXECUTOR_H +#define QBS_JSCOMMANDEXECUTOR_H + +#include "abstractcommandexecutor.h" + +#include + +namespace qbs { +class CodeLocation; + +namespace Internal { +class JavaScriptCommand; +class JsCommandExecutorThreadObject; + +class JsCommandExecutor : public AbstractCommandExecutor +{ + Q_OBJECT +public: + explicit JsCommandExecutor(const Logger &logger, QObject *parent = nullptr); + ~JsCommandExecutor() override; + +signals: + void startRequested(const JavaScriptCommand *cmd, Transformer *transformer); + +private: + void onJavaScriptCommandFinished(); + + void doReportCommandDescription(const QString &productName) override; + bool doStart() override; + void cancel(const qbs::ErrorInfo &reason) override; + + void waitForFinished(); + + const JavaScriptCommand *jsCommand() const; + + QThread *m_thread; + JsCommandExecutorThreadObject *m_objectInThread; + bool m_running; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_JSCOMMANDEXECUTOR_H diff --git a/src/lib/corelib/buildgraph/nodeset.cpp b/src/lib/corelib/buildgraph/nodeset.cpp new file mode 100644 index 00000000..b9ef222e --- /dev/null +++ b/src/lib/corelib/buildgraph/nodeset.cpp @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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 "nodeset.h" + +#include "artifact.h" +#include "rulenode.h" +#include +#include + +namespace qbs { +namespace Internal { + +BuildGraphNode *loadBuildGraphNode(PersistentPool &pool) +{ + const auto t = pool.load(); + BuildGraphNode *node = nullptr; + switch (static_cast(t)) { + case BuildGraphNode::ArtifactNodeType: + node = pool.load(); + break; + case BuildGraphNode::RuleNodeType: + node = pool.load(); + break; + } + QBS_CHECK(node); + return node; +} + +void storeBuildGraphNode(PersistentPool &pool, const BuildGraphNode *node) +{ + pool.store(static_cast(node->type())); + pool.store(node); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/nodeset.h b/src/lib/corelib/buildgraph/nodeset.h new file mode 100644 index 00000000..961a4bcc --- /dev/null +++ b/src/lib/corelib/buildgraph/nodeset.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** 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 QBS_NODESET_H +#define QBS_NODESET_H + +#include + +#include + +namespace qbs { +namespace Internal { +class BuildGraphNode; + +BuildGraphNode *loadBuildGraphNode(PersistentPool &pool); +void storeBuildGraphNode(PersistentPool &pool, const BuildGraphNode *node); + +using NodeSet = Set; +template<> inline BuildGraphNode *NodeSet::loadElem(PersistentPool &pool) +{ + return loadBuildGraphNode(pool); +} +template<> inline void NodeSet::storeElem(PersistentPool &pool, BuildGraphNode * const &node) const +{ + storeBuildGraphNode(pool, node); +} + +template +class TypeFilter +{ + const NodeSet &m_nodes; +public: + TypeFilter(const NodeSet &nodes) + : m_nodes(nodes) + { + } + + class const_iterator : public std::iterator + { + const NodeSet &m_nodes; + NodeSet::const_iterator m_it; + public: + const_iterator(const NodeSet &nodes, const NodeSet::const_iterator &it) + : m_nodes(nodes), m_it(it) + { + while (m_it != m_nodes.constEnd() && !hasDynamicType(*m_it)) + ++m_it; + } + + bool operator==(const const_iterator &rhs) + { + return m_it == rhs.m_it; + } + + bool operator!=(const const_iterator &rhs) + { + return !(*this == rhs); + } + + const_iterator &operator++() + { + for (;;) { + ++m_it; + if (m_it == m_nodes.constEnd() || hasDynamicType(*m_it)) + return *this; + } + } + + T *operator*() const + { + return static_cast(*m_it); + } + }; + + const_iterator begin() const + { + return const_iterator(m_nodes, m_nodes.constBegin()); + } + + const_iterator end() const + { + return const_iterator(m_nodes, m_nodes.constEnd()); + } +}; + +template +const TypeFilter filterByType(const NodeSet &nodes) +{ + return TypeFilter(nodes); +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_NODESET_H diff --git a/src/lib/corelib/buildgraph/nodetreedumper.cpp b/src/lib/corelib/buildgraph/nodetreedumper.cpp new file mode 100644 index 00000000..8475a46c --- /dev/null +++ b/src/lib/corelib/buildgraph/nodetreedumper.cpp @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** 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 "nodetreedumper.h" + +#include "artifact.h" +#include "productbuilddata.h" +#include "rulenode.h" + +#include +#include + +#include + +namespace qbs { +namespace Internal { + +static int indentWidth() { return 4; } + +NodeTreeDumper::NodeTreeDumper(QIODevice &outDevice) : m_outDevice(outDevice) +{ +} + +void NodeTreeDumper::start(const QVector &products) +{ + m_indentation = 0; + for (const ResolvedProductPtr &p : products) { + if (!p->buildData) + continue; + m_currentProduct = p; + for (Artifact * const root : p->buildData->rootArtifacts()) + root->accept(this); + m_visited.clear(); + QBS_CHECK(m_indentation == 0); + } +} + +bool NodeTreeDumper::visit(Artifact *artifact) +{ + return doVisit(artifact, artifact->fileName()); +} + +void NodeTreeDumper::endVisit(Artifact *artifact) +{ + Q_UNUSED(artifact); + doEndVisit(); +} + +bool NodeTreeDumper::visit(RuleNode *rule) +{ + return doVisit(rule, rule->toString()); +} + +void NodeTreeDumper::endVisit(RuleNode *rule) +{ + Q_UNUSED(rule); + doEndVisit(); +} + +void NodeTreeDumper::doEndVisit() +{ + unindent(); +} + +void NodeTreeDumper::indent() +{ + m_outDevice.write("\n"); + m_indentation += indentWidth(); +} + +void NodeTreeDumper::unindent() +{ + m_indentation -= indentWidth(); +} + +bool NodeTreeDumper::doVisit(BuildGraphNode *node, const QString &nodeRepr) +{ + m_outDevice.write(indentation()); + m_outDevice.write(nodeRepr.toLocal8Bit()); + indent(); + const bool wasVisited = !m_visited.insert(node).second; + return !wasVisited && node->product == m_currentProduct; +} + +QByteArray NodeTreeDumper::indentation() const +{ + return QByteArray(m_indentation, ' '); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/nodetreedumper.h b/src/lib/corelib/buildgraph/nodetreedumper.h new file mode 100644 index 00000000..38ccd6da --- /dev/null +++ b/src/lib/corelib/buildgraph/nodetreedumper.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 QBS_NODETREEDUMPER_H +#define QBS_NODETREEDUMPER_H + +#include "artifact.h" +#include "buildgraphvisitor.h" +#include + +#include + +QT_BEGIN_NAMESPACE +class QIODevice; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +class NodeTreeDumper : public BuildGraphVisitor +{ +public: + NodeTreeDumper(QIODevice &outDevice); + + void start(const QVector &products); + +private: + bool visit(Artifact *artifact) override; + void endVisit(Artifact *artifact) override; + bool visit(RuleNode *rule) override; + void endVisit(RuleNode *rule) override; + + void doEndVisit(); + void indent(); + void unindent(); + bool doVisit(BuildGraphNode *node, const QString &nodeRepr); + QByteArray indentation() const; + + QIODevice &m_outDevice; + ResolvedProductPtr m_currentProduct; + NodeSet m_visited; + int m_indentation = 0; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/buildgraph/processcommandexecutor.cpp b/src/lib/corelib/buildgraph/processcommandexecutor.cpp new file mode 100644 index 00000000..89350272 --- /dev/null +++ b/src/lib/corelib/buildgraph/processcommandexecutor.cpp @@ -0,0 +1,424 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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 "processcommandexecutor.h" + +#include "artifact.h" +#include "rulecommands.h" +#include "transformer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +ProcessCommandExecutor::ProcessCommandExecutor(const Logger &logger, QObject *parent) + : AbstractCommandExecutor(logger, parent) +{ + connect(&m_process, static_cast(&QbsProcess::error), + this, &ProcessCommandExecutor::onProcessError); + connect(&m_process, static_cast(&QbsProcess::finished), + this, &ProcessCommandExecutor::onProcessFinished); +} + +static QProcessEnvironment mergeEnvironments(const QProcessEnvironment &baseEnv, + const QProcessEnvironment &additionalEnv) +{ + QProcessEnvironment env = baseEnv; + + static const QStringList pathListVariables{ + StringConstants::pathEnvVar(), + QStringLiteral("LD_LIBRARY_PATH"), + QStringLiteral("DYLD_LIBRARY_PATH"), + QStringLiteral("DYLD_FRAMEWORK_PATH"), + }; + const auto keys = additionalEnv.keys(); + for (const QString &key : keys) { + QString newValue = additionalEnv.value(key); + if (pathListVariables.contains(key, HostOsInfo::fileNameCaseSensitivity())) { + const QString &oldValue = baseEnv.value(key); + if (!oldValue.isEmpty()) + newValue.append(HostOsInfo::pathListSeparator()).append(oldValue); + } + env.insert(key, newValue); + } + return env; +} + +void ProcessCommandExecutor::doSetup() +{ + ProcessCommand * const cmd = processCommand(); + const QString program = ExecutableFinder(transformer()->product(), + transformer()->product()->buildEnvironment) + .findExecutable(cmd->program(), cmd->workingDir()); + cmd->clearRelevantEnvValues(); + const auto keys = cmd->relevantEnvVars(); + for (const QString &key : keys) + cmd->addRelevantEnvValue(key, transformer()->product()->buildEnvironment.value(key)); + + m_commandEnvironment = mergeEnvironments(m_buildEnvironment, cmd->environment()); + m_program = program; + m_arguments = cmd->arguments(); + m_shellInvocation = shellQuote(QDir::toNativeSeparators(m_program), m_arguments); +} + +bool ProcessCommandExecutor::doStart() +{ + QBS_ASSERT(m_process.state() == QProcess::NotRunning, return false); + + const ProcessCommand * const cmd = processCommand(); + + m_process.setProcessEnvironment(m_commandEnvironment); + + QStringList arguments = m_arguments; + + if (dryRun() && !cmd->ignoreDryRun()) { + QTimer::singleShot(0, this, [this] { emit finished(); }); // Don't call back on the caller. + return false; + } + + const QString workingDir = QDir::fromNativeSeparators(cmd->workingDir()); + if (!workingDir.isEmpty()) { + FileInfo fi(workingDir); + if (!fi.exists() || !fi.isDir()) { + emit finished(ErrorInfo(Tr::tr("The working directory '%1' for process '%2' " + "is invalid.").arg(QDir::toNativeSeparators(workingDir), + QDir::toNativeSeparators(m_program)), + cmd->codeLocation())); + return false; + } + } + + // Automatically use response files, if the command line gets to long. + if (!cmd->responseFileUsagePrefix().isEmpty()) { + const int commandLineLength = m_shellInvocation.length(); + if (cmd->responseFileThreshold() >= 0 && commandLineLength > cmd->responseFileThreshold()) { + 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. + // We need to delete the file manually, later. + QTemporaryFile responseFile; + responseFile.setAutoRemove(false); + responseFile.setFileTemplate(QDir::tempPath() + QLatin1String("/qbsresp")); + if (!responseFile.open()) { + emit finished(ErrorInfo(Tr::tr("Cannot create response file '%1'.") + .arg(responseFile.fileName()))); + return false; + } + const auto separator = cmd->responseFileSeparator().toUtf8(); + for (int i = cmd->responseFileArgumentIndex(); i < cmd->arguments().size(); ++i) { + const QString arg = cmd->arguments().at(i); + if (arg.startsWith(cmd->responseFileUsagePrefix())) { + QFile f(arg.mid(cmd->responseFileUsagePrefix().size())); + if (!f.open(QIODevice::ReadOnly)) { + emit finished(ErrorInfo(Tr::tr("Cannot open command file '%1'.") + .arg(QDir::toNativeSeparators(f.fileName())))); + return false; + } + responseFile.write(f.readAll()); + } else { + responseFile.write(qbs::Internal::shellQuote(arg).toLocal8Bit()); + } + responseFile.write(separator); + } + responseFile.close(); + m_responseFileName = responseFile.fileName(); + arguments = arguments.mid(0, + std::min(cmd->responseFileArgumentIndex(), arguments.size())); + arguments += QDir::toNativeSeparators(cmd->responseFileUsagePrefix() + + responseFile.fileName()); + } + } + + qCDebug(lcExec) << "Running external process; full command line is:" << m_shellInvocation; + const QProcessEnvironment &additionalVariables = cmd->environment(); + qCDebug(lcExec) << "Additional environment:" << additionalVariables.toStringList(); + m_process.setWorkingDirectory(workingDir); + m_process.start(m_program, arguments); + return true; +} + +void ProcessCommandExecutor::cancel(const qbs::ErrorInfo &reason) +{ + // We don't want this command to be reported as failing, since we explicitly terminated it. + disconnect(this, &ProcessCommandExecutor::reportProcessResult, nullptr, nullptr); + + m_cancelReason = reason; + m_process.cancel(); +} + +QString ProcessCommandExecutor::filterProcessOutput(const QByteArray &_output, + const QString &filterFunctionSource) +{ + const QString output = QString::fromLocal8Bit(_output); + if (filterFunctionSource.isEmpty()) + return output; + + QScriptValue scope = scriptEngine()->newObject(); + scope.setPrototype(scriptEngine()->globalObject()); + for (QVariantMap::const_iterator it = command()->properties().constBegin(); + it != command()->properties().constEnd(); ++it) { + scope.setProperty(it.key(), scriptEngine()->toScriptValue(it.value())); + } + + TemporaryGlobalObjectSetter tgos(scope); + QScriptValue filterFunction = scriptEngine()->evaluate(QLatin1String("var f = ") + + filterFunctionSource + + QLatin1String("; f")); + if (!filterFunction.isFunction()) { + logger().printWarning(ErrorInfo(Tr::tr("Error in filter function: %1.\n%2") + .arg(filterFunctionSource, filterFunction.toString()))); + return output; + } + + QScriptValue outputArg = scriptEngine()->newArray(1); + outputArg.setProperty(0, scriptEngine()->toScriptValue(output)); + QScriptValue filteredOutput = filterFunction.call(scriptEngine()->undefinedValue(), outputArg); + if (scriptEngine()->hasErrorOrException(filteredOutput)) { + logger().printWarning(ErrorInfo(Tr::tr("Error when calling output filter function: %1") + .arg(scriptEngine()->lastErrorString(filteredOutput)), + scriptEngine()->lastErrorLocation(filteredOutput))); + return output; + } + + return filteredOutput.toString(); +} + +static QProcess::ProcessError saveToFile(const QString &filePath, const QByteArray &content) +{ + QBS_ASSERT(!filePath.isEmpty(), return QProcess::WriteError); + + QFile f(filePath); + if (!f.open(QIODevice::WriteOnly)) + return QProcess::WriteError; + + if (f.write(content) != content.size()) + return QProcess::WriteError; + f.close(); + return f.error() == QFileDevice::NoError ? QProcess::UnknownError : QProcess::WriteError; +} + +void ProcessCommandExecutor::getProcessOutput(bool stdOut, ProcessResult &result) +{ + QByteArray content; + QString filterFunction; + QString redirectPath; + QStringList *target; + if (stdOut) { + content = m_process.readAllStandardOutput(); + filterFunction = processCommand()->stdoutFilterFunction(); + redirectPath = processCommand()->stdoutFilePath(); + target = &result.d->stdOut; + } else { + content = m_process.readAllStandardError(); + filterFunction = processCommand()->stderrFilterFunction(); + redirectPath = processCommand()->stderrFilePath(); + target = &result.d->stdErr; + } + QString contentString = filterProcessOutput(content, filterFunction); + if (!redirectPath.isEmpty()) { + const QByteArray dataToWrite = filterFunction.isEmpty() ? content + : contentString.toLocal8Bit(); + const QProcess::ProcessError error = saveToFile(redirectPath, dataToWrite); + if (result.error() == QProcess::UnknownError && error != QProcess::UnknownError) + result.d->error = error; + } else { + if (!contentString.isEmpty() && contentString.endsWith(QLatin1Char('\n'))) + contentString.chop(1); + *target = contentString.split(QLatin1Char('\n'), QBS_SKIP_EMPTY_PARTS); + } +} + +void ProcessCommandExecutor::sendProcessOutput() +{ + ProcessResult result; + result.d->executableFilePath = m_program; + result.d->arguments = m_arguments; + result.d->workingDirectory = m_process.workingDirectory(); + if (result.workingDirectory().isEmpty()) + result.d->workingDirectory = QDir::currentPath(); + result.d->exitCode = m_process.exitCode(); + result.d->error = m_process.error(); + QString errorString = m_process.errorString(); + + getProcessOutput(true, result); + getProcessOutput(false, result); + + const bool processError = result.error() != QProcess::UnknownError; + const bool failureExit = quint32(m_process.exitCode()) + > quint32(processCommand()->maxExitCode()); + const bool cancelledWithError = m_cancelReason.hasError(); + result.d->success = !processError && !failureExit && !cancelledWithError; + emit reportProcessResult(result); + + if (Q_UNLIKELY(cancelledWithError)) { + emit finished(m_cancelReason); + } else if (Q_UNLIKELY(processError)) { + emit finished(ErrorInfo(errorString)); + } else if (Q_UNLIKELY(failureExit)) { + emit finished(ErrorInfo(Tr::tr("Process failed with exit code %1.") + .arg(m_process.exitCode()))); + } else { + emit finished(); + } +} + +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; + } + if (m_cancelReason.hasError()) + return; // Ignore. Cancel reasons will be handled by on ProcessFinished(). + switch (m_process.error()) { + case QProcess::FailedToStart: { + removeResponseFile(); + const QString binary = QDir::toNativeSeparators(processCommand()->program()); + QString errorPrefixString; +#ifdef Q_OS_UNIX + if (QFileInfo(binary).isExecutable()) { + const QString interpreter(shellInterpreter(binary)); + if (!interpreter.isEmpty()) { + errorPrefixString = Tr::tr("%1: bad interpreter: ").arg(interpreter); + } + } +#endif + emit finished(ErrorInfo(Tr::tr("The process '%1' could not be started: %2. " + "The full command line invocation was: %3") + .arg(binary, errorPrefixString + m_process.errorString(), + m_shellInvocation))); + break; + } + case QProcess::Crashed: + break; // Ignore. Will be handled by onProcessFinished(). + default: + logger().qbsWarning() << "QProcess error: " << m_process.errorString(); + } +} + +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(); +} + +static QString environmentVariableString(const QString &key, const QString &value) +{ + QString str; + if (HostOsInfo::isWindowsHost()) + str += QStringLiteral("set "); + str += shellQuote(key + QLatin1Char('=') + value); + if (HostOsInfo::isWindowsHost()) + str += QLatin1Char('\n'); + else + str += QLatin1Char(' '); + return str; +} + +void ProcessCommandExecutor::doReportCommandDescription(const QString &productName) +{ + if (m_echoMode == CommandEchoModeCommandLine || + m_echoMode == CommandEchoModeCommandLineWithEnvironment) { + QString fullInvocation; + if (m_echoMode == CommandEchoModeCommandLineWithEnvironment) { + QStringList keys = m_commandEnvironment.keys(); + keys.sort(); + for (const QString &key : qAsConst(keys)) + fullInvocation += environmentVariableString(key, m_commandEnvironment.value(key)); + } + fullInvocation += m_shellInvocation; + emit reportCommandDescription(command()->highlight(), + !command()->extendedDescription().isEmpty() + ? command()->extendedDescription() + : fullInvocation); + return; + } + + AbstractCommandExecutor::doReportCommandDescription(productName); +} + +void ProcessCommandExecutor::removeResponseFile() +{ + if (m_responseFileName.isEmpty()) + return; + QFile::remove(m_responseFileName); + m_responseFileName.clear(); +} + +ProcessCommand *ProcessCommandExecutor::processCommand() const +{ + return static_cast(command()); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/processcommandexecutor.h b/src/lib/corelib/buildgraph/processcommandexecutor.h new file mode 100644 index 00000000..b0f95588 --- /dev/null +++ b/src/lib/corelib/buildgraph/processcommandexecutor.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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_PROCESSCOMMANDEXECUTOR_H +#define QBS_PROCESSCOMMANDEXECUTOR_H + +#include "abstractcommandexecutor.h" + +#include + +#include + +namespace qbs { +class ProcessResult; + +namespace Internal { +class ProcessCommand; + +class ProcessCommandExecutor : public AbstractCommandExecutor +{ + Q_OBJECT +public: + explicit ProcessCommandExecutor(const Internal::Logger &logger, QObject *parent = nullptr); + + void setProcessEnvironment(const QProcessEnvironment &processEnvironment) { + m_buildEnvironment = processEnvironment; + } + +signals: + void reportProcessResult(const qbs::ProcessResult &result); + +private: + void onProcessError(); + void onProcessFinished(); + + void doSetup() override; + void doReportCommandDescription(const QString &productName) override; + bool doStart() override; + void cancel(const qbs::ErrorInfo &reason) override; + + void startProcessCommand(); + QString filterProcessOutput(const QByteArray &output, const QString &filterFunctionSource); + void getProcessOutput(bool stdOut, ProcessResult &result); + + void sendProcessOutput(); + void removeResponseFile(); + ProcessCommand *processCommand() const; + +private: + QString m_program; + QStringList m_arguments; + QString m_shellInvocation; + + QbsProcess m_process; + QProcessEnvironment m_buildEnvironment; + QProcessEnvironment m_commandEnvironment; + QString m_responseFileName; + qbs::ErrorInfo m_cancelReason; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROCESSCOMMANDEXECUTOR_H diff --git a/src/lib/corelib/buildgraph/productbuilddata.cpp b/src/lib/corelib/buildgraph/productbuilddata.cpp new file mode 100644 index 00000000..db51b2b9 --- /dev/null +++ b/src/lib/corelib/buildgraph/productbuilddata.cpp @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** 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 "productbuilddata.h" + +#include "artifact.h" +#include "projectbuilddata.h" +#include "rulecommands.h" +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +ProductBuildData::~ProductBuildData() +{ + qDeleteAll(m_nodes); +} + +const TypeFilter ProductBuildData::rootArtifacts() const +{ + return TypeFilter(m_roots); +} + +void ProductBuildData::addArtifact(Artifact *artifact) +{ + QBS_CHECK(m_nodes.insert(artifact).second); + addArtifactToSet(artifact); +} + +void ProductBuildData::addArtifactToSet(Artifact *artifact) +{ + std::lock_guard l(m_artifactsMapMutex); + for (const FileTag &tag : artifact->fileTags()) { + m_artifactsByFileTag[tag] += artifact; + m_jsArtifactsMapUpToDate = false; + } +} + +void ProductBuildData::removeArtifact(Artifact *artifact) +{ + m_roots.remove(artifact); + m_nodes.remove(artifact); + removeArtifactFromSet(artifact); +} + +void ProductBuildData::removeArtifactFromSetByFileTag(Artifact *artifact, const FileTag &fileTag) +{ + std::lock_guard l(m_artifactsMapMutex); + const auto it = m_artifactsByFileTag.find(fileTag); + if (it == m_artifactsByFileTag.end()) + return; + it->remove(artifact); + if (it->empty()) + m_artifactsByFileTag.erase(it); + m_jsArtifactsMapUpToDate = false; +} + +void ProductBuildData::addFileTagToArtifact(Artifact *artifact, const FileTag &tag) +{ + std::lock_guard l(m_artifactsMapMutex); + m_artifactsByFileTag[tag] += artifact; + m_jsArtifactsMapUpToDate = false; +} + +ArtifactSetByFileTag ProductBuildData::artifactsByFileTag() const +{ + std::lock_guard l(m_artifactsMapMutex); + return m_artifactsByFileTag; +} + +void ProductBuildData::setRescuableArtifactData(const AllRescuableArtifactData &rad) +{ + m_rescuableArtifactData = rad; +} + +RescuableArtifactData ProductBuildData::removeFromRescuableArtifactData(const QString &filePath) +{ + return m_rescuableArtifactData.take(filePath); +} + +void ProductBuildData::addRescuableArtifactData(const QString &filePath, + const RescuableArtifactData &rad) +{ + m_rescuableArtifactData.insert(filePath, rad); +} + +bool ProductBuildData::checkAndSetJsArtifactsMapUpToDateFlag() +{ + std::lock_guard l(m_artifactsMapMutex); + if (!m_jsArtifactsMapUpToDate) { + m_jsArtifactsMapUpToDate = true; + return false; + } + return true; +} + +void ProductBuildData::removeArtifactFromSet(Artifact *artifact) +{ + for (const FileTag &t : artifact->fileTags()) + removeArtifactFromSetByFileTag(artifact, t); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/productbuilddata.h b/src/lib/corelib/buildgraph/productbuilddata.h new file mode 100644 index 00000000..a7660af2 --- /dev/null +++ b/src/lib/corelib/buildgraph/productbuilddata.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** 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 QBS_PRODUCTBUILDDATA_H +#define QBS_PRODUCTBUILDDATA_H + +#include "artifact.h" +#include "nodeset.h" +#include "rescuableartifactdata.h" +#include +#include +#include + +#include + +#include + +namespace qbs { +namespace Internal { + +class Logger; + +using ArtifactSetByFileTag = QHash; + +class QBS_AUTOTEST_EXPORT ProductBuildData +{ +public: + ~ProductBuildData(); + + const TypeFilter rootArtifacts() const; + const NodeSet &allNodes() const { return m_nodes; } + const NodeSet &rootNodes() const { return m_roots; } + + void addNode(BuildGraphNode *node) { m_nodes.insert(node); } + void addRootNode(BuildGraphNode *node) { m_roots.insert(node); } + void removeFromRootNodes(BuildGraphNode *node) { m_roots.remove(node); } + void addArtifact(Artifact *artifact); + void addArtifactToSet(Artifact *artifact); + void removeArtifact(Artifact *artifact); + void removeArtifactFromSetByFileTag(Artifact *artifact, const FileTag &fileTag); + void addFileTagToArtifact(Artifact *artifact, const FileTag &tag); + + ArtifactSetByFileTag artifactsByFileTag() const; + + AllRescuableArtifactData rescuableArtifactData() const { return m_rescuableArtifactData; } + void setRescuableArtifactData(const AllRescuableArtifactData &rad); + RescuableArtifactData removeFromRescuableArtifactData(const QString &filePath); + void addRescuableArtifactData(const QString &filePath, const RescuableArtifactData &rad); + + unsigned int buildPriority() const { return m_buildPriority; } + void setBuildPriority(unsigned int prio) { m_buildPriority = prio; } + + bool checkAndSetJsArtifactsMapUpToDateFlag(); + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(m_nodes, m_roots, m_rescuableArtifactData, + m_artifactsByFileTag); + } + +private: + void removeArtifactFromSet(Artifact *artifact); + + NodeSet m_nodes; + NodeSet m_roots; + + // After change tracking, this is the relevant data of artifacts that were in the build data + // of the restored product, and will potentially be re-created by our rules. + // If and when that happens, the relevant data will be copied over to the newly created + // artifact. + AllRescuableArtifactData m_rescuableArtifactData; + + // Do not store, initialized in executor. Higher prioritized artifacts are built first. + unsigned int m_buildPriority = 0; + + ArtifactSetByFileTag m_artifactsByFileTag; + mutable std::mutex m_artifactsMapMutex; + + bool m_jsArtifactsMapUpToDate = true; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PRODUCTBUILDDATA_H diff --git a/src/lib/corelib/buildgraph/productinstaller.cpp b/src/lib/corelib/buildgraph/productinstaller.cpp new file mode 100644 index 00000000..80a76d7f --- /dev/null +++ b/src/lib/corelib/buildgraph/productinstaller.cpp @@ -0,0 +1,254 @@ +/**************************************************************************** +** +** 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 "productinstaller.h" + +#include "artifact.h" +#include "productbuilddata.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { + +ProductInstaller::ProductInstaller(TopLevelProjectPtr project, + QVector products, InstallOptions options, + ProgressObserver *observer, Logger logger) + : m_project(std::move(project)), + m_products(std::move(products)), + m_options(std::move(options)), + m_observer(observer), + m_logger(std::move(logger)) +{ + if (!m_options.installRoot().isEmpty()) { + QFileInfo installRootFileInfo(m_options.installRoot()); + QBS_ASSERT(installRootFileInfo.isAbsolute(), /* just complain */); + if (m_options.removeExistingInstallation()) { + const QString cfp = installRootFileInfo.canonicalFilePath(); + if (cfp == QFileInfo(QDir::rootPath()).canonicalFilePath()) + throw ErrorInfo(Tr::tr("Refusing to remove root directory.")); + if (cfp == QFileInfo(QDir::homePath()).canonicalFilePath()) + throw ErrorInfo(Tr::tr("Refusing to remove home directory.")); + } + return; + } + + if (m_options.installIntoSysroot()) { + if (m_options.removeExistingInstallation()) + throw ErrorInfo(Tr::tr("Refusing to remove sysroot.")); + } + initInstallRoot(m_project.get(), m_options); +} + +void ProductInstaller::install() +{ + m_targetFilePathsMap.clear(); + + if (m_options.removeExistingInstallation()) + removeInstallRoot(); + + QList artifactsToInstall; + for (const auto &product : qAsConst(m_products)) { + QBS_CHECK(product->buildData); + for (const Artifact *artifact : filterByType(product->buildData->allNodes())) { + if (artifact->properties->qbsPropertyValue(StringConstants::installProperty()).toBool()) + artifactsToInstall.push_back(artifact); + } + } + m_observer->initialize(Tr::tr("Installing"), artifactsToInstall.size()); + + for (const Artifact * const a : qAsConst(artifactsToInstall)) { + copyFile(a); + m_observer->incrementProgressValue(); + } +} + +QString ProductInstaller::targetFilePath(const TopLevelProject *project, + const QString &productSourceDir, + const QString &sourceFilePath, const PropertyMapConstPtr &properties, + InstallOptions &options) +{ + if (!properties->qbsPropertyValue(StringConstants::installProperty()).toBool()) + return {}; + const QString relativeInstallDir + = properties->qbsPropertyValue(StringConstants::installDirProperty()).toString(); + const QString installPrefix + = properties->qbsPropertyValue(StringConstants::installPrefixProperty()).toString(); + const QString installSourceBase + = properties->qbsPropertyValue(StringConstants::installSourceBaseProperty()).toString(); + initInstallRoot(project, options); + QString targetDir = options.installRoot(); + if (targetDir.isEmpty()) + targetDir = properties->qbsPropertyValue(StringConstants::installRootProperty()).toString(); + targetDir.append(QLatin1Char('/')).append(installPrefix) + .append(QLatin1Char('/')).append(relativeInstallDir); + targetDir = QDir::cleanPath(targetDir); + + QString targetFilePath; + if (installSourceBase.isEmpty()) { + if (!targetDir.startsWith(options.installRoot())) { + throw ErrorInfo(Tr::tr("Cannot install '%1', because target directory '%2' " + "is outside of install root '%3'") + .arg(sourceFilePath, targetDir, options.installRoot())); + } + + // This has the same effect as if installSourceBase would equal the directory of the file. + targetFilePath = FileInfo::fileName(sourceFilePath); + } else { + const QString localAbsBasePath = FileInfo::resolvePath(QDir::cleanPath(productSourceDir), + QDir::cleanPath(installSourceBase)); + targetFilePath = sourceFilePath; + if (!targetFilePath.startsWith(localAbsBasePath)) { + throw ErrorInfo(Tr::tr("Cannot install '%1', because it doesn't start with the" + " value of qbs.installSourceBase '%2'.").arg(sourceFilePath, + localAbsBasePath)); + } + + // Since there is a difference between X: and X:\\ on Windows, absolute paths can sometimes + // end with a slash, so only remove an extra character if there is no ending slash + targetFilePath.remove(0, localAbsBasePath.length() + + (localAbsBasePath.endsWith(QLatin1Char('/')) ? 0 : 1)); + } + + targetFilePath.prepend(targetDir + QLatin1Char('/')); + return QDir::cleanPath(targetFilePath); +} + +void ProductInstaller::initInstallRoot(const TopLevelProject *project, + InstallOptions &options) +{ + if (!options.installRoot().isEmpty()) + return; + + options.setInstallRoot(effectiveInstallRoot(options, project)); +} + +void ProductInstaller::removeInstallRoot() +{ + const QString nativeInstallRoot = QDir::toNativeSeparators(m_options.installRoot()); + if (m_options.dryRun()) { + m_logger.qbsInfo() << Tr::tr("Would remove install root '%1'.").arg(nativeInstallRoot); + return; + } + m_logger.qbsDebug() << QStringLiteral("Removing install root '%1'.") + .arg(nativeInstallRoot); + + QString errorMessage; + if (!removeDirectoryWithContents(m_options.installRoot(), &errorMessage)) { + const QString fullErrorMessage = Tr::tr("Cannot remove install root '%1': %2") + .arg(QDir::toNativeSeparators(m_options.installRoot()), errorMessage); + handleError(fullErrorMessage); + } +} + +void ProductInstaller::copyFile(const Artifact *artifact) +{ + if (m_observer->canceled()) { + throw ErrorInfo(Tr::tr("Installation canceled for configuration '%1'.") + .arg(m_products.front()->project->topLevelProject()->id())); + } + + const QString targetFilePath = this->targetFilePath(m_project.get(), + artifact->product->sourceDirectory, artifact->filePath(), + artifact->properties, m_options); + const QString targetDir = FileInfo::path(targetFilePath); + const QString nativeFilePath = QDir::toNativeSeparators(artifact->filePath()); + const QString nativeTargetDir = QDir::toNativeSeparators(targetDir); + if (m_options.dryRun()) { + m_logger.qbsDebug() << Tr::tr("Would copy file '%1' into target directory '%2'.") + .arg(nativeFilePath, nativeTargetDir); + return; + } + m_logger.qbsDebug() << QStringLiteral("Copying file '%1' into target directory '%2'.") + .arg(nativeFilePath, nativeTargetDir); + + if (!QDir::root().mkpath(targetDir)) { + handleError(Tr::tr("Directory '%1' could not be created.").arg(nativeTargetDir)); + return; + } + QFileInfo fi(artifact->filePath()); + if (fi.isDir() && !(HostOsInfo::isAnyUnixHost() && fi.isSymLink())) { + m_logger.qbsWarning() << Tr::tr("Not recursively copying directory '%1' into target " + "directory '%2'. Install the individual file artifacts " + "instead.") + .arg(nativeFilePath, nativeTargetDir); + } + + if (m_targetFilePathsMap.contains(targetFilePath)) { + // We only want this error message when installing artifacts pointing to different file + // paths, to the same location. We do NOT want it when installing different artifacts + // pointing to the same file, to the same location. This reduces unnecessary noise: for + // example, when installing headers from a multiplexed product, the user does not need to + // do extra work to ensure the files are installed by only one of the instances. + if (artifact->filePath() != m_targetFilePathsMap[targetFilePath]) { + handleError(Tr::tr("Cannot install files '%1' and '%2' to the same location '%3'. " + "If you are attempting to install a directory hierarchy, consider " + "using the qbs.installSourceBase property.") + .arg(artifact->filePath(), m_targetFilePathsMap[targetFilePath], + targetFilePath)); + } + } + m_targetFilePathsMap.insert(targetFilePath, artifact->filePath()); + + QString errorMessage; + if (!copyFileRecursion(artifact->filePath(), targetFilePath, true, false, &errorMessage)) + handleError(Tr::tr("Installation error: %1").arg(errorMessage)); +} + +void ProductInstaller::handleError(const QString &message) +{ + if (!m_options.keepGoing()) + throw ErrorInfo(message); + m_logger.qbsWarning() << message; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/productinstaller.h b/src/lib/corelib/buildgraph/productinstaller.h new file mode 100644 index 00000000..c07e0b7c --- /dev/null +++ b/src/lib/corelib/buildgraph/productinstaller.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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 QBS_PRODUCT_INSTALLER_H +#define QBS_PRODUCT_INSTALLER_H + +#include "forward_decls.h" + +#include +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { +class ProgressObserver; + +class ProductInstaller +{ +public: + ProductInstaller(TopLevelProjectPtr project, + QVector products, + InstallOptions options, ProgressObserver *observer, Logger logger); + void install(); + + static QString targetFilePath(const TopLevelProject *project, const QString &productSourceDir, + const QString &sourceFilePath, const PropertyMapConstPtr &properties, + InstallOptions &options); + static void initInstallRoot(const TopLevelProject *project, InstallOptions &options); + + void removeInstallRoot(); + void copyFile(const Artifact *artifact); + +private: + void handleError(const QString &message); + + const TopLevelProjectConstPtr m_project; + const QVector m_products; + InstallOptions m_options; + ProgressObserver * const m_observer; + Logger m_logger; + QHash m_targetFilePathsMap; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Header guard diff --git a/src/lib/corelib/buildgraph/projectbuilddata.cpp b/src/lib/corelib/buildgraph/projectbuilddata.cpp new file mode 100644 index 00000000..0ac6b153 --- /dev/null +++ b/src/lib/corelib/buildgraph/projectbuilddata.cpp @@ -0,0 +1,483 @@ +/**************************************************************************** +** +** 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 "projectbuilddata.h" + +#include "artifact.h" +#include "buildgraph.h" +#include "buildgraphvisitor.h" +#include "productbuilddata.h" +#include "rulecommands.h" +#include "rulegraph.h" +#include "rulenode.h" +#include "rulesevaluationcontext.h" +#include "transformer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +static Set findDependentProducts(const ResolvedProductPtr &product) +{ + Set result; + for (const ResolvedProductPtr &parent : product->topLevelProject()->allProducts()) { + if (contains(parent->dependencies, product)) + result += parent; + } + return result; +} + +ProjectBuildData::ProjectBuildData(const ProjectBuildData *other) +{ + // This is needed for temporary duplication of build data when doing change tracking. + if (other) { + *this = *other; + m_doCleanupInDestructor = false; + } +} + +ProjectBuildData::~ProjectBuildData() +{ + if (!m_doCleanupInDestructor) + return; + qDeleteAll(fileDependencies); +} + +QString ProjectBuildData::deriveBuildGraphFilePath(const QString &buildDir, const QString &projectId) +{ + return buildDir + QLatin1Char('/') + projectId + QStringLiteral(".bg"); +} + +void ProjectBuildData::insertIntoLookupTable(FileResourceBase *fileres) +{ + auto &lst = m_artifactLookupTable[{fileres->fileName(), fileres->dirPath()}]; + const auto * const artifact = fileres->fileType() == FileResourceBase::FileTypeArtifact + ? static_cast(fileres) : nullptr; + if (artifact && artifact->artifactType == Artifact::Generated) { + for (const auto *file : lst) { + if (file->fileType() != FileResourceBase::FileTypeArtifact) + continue; + const auto * const otherArtifact = static_cast(file); + ErrorInfo error; + 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(otherArtifact->product->fullDisplayName()), + otherArtifact->product->location); + error.append(Tr::tr("The second artifact comes from product '%1'.") + .arg(artifact->product->fullDisplayName()), + artifact->product->location); + throw error; + } + } + QBS_CHECK(!contains(lst, fileres)); + lst.push_back(fileres); + m_isDirty = true; +} + +void ProjectBuildData::removeFromLookupTable(FileResourceBase *fileres) +{ + removeOne(m_artifactLookupTable[{fileres->fileName(), fileres->dirPath()}], fileres); +} + +const std::vector &ProjectBuildData::lookupFiles(const QString &filePath) const +{ + QString dirPath, fileName; + FileInfo::splitIntoDirectoryAndFileName(filePath, &dirPath, &fileName); + return lookupFiles(dirPath, fileName); +} + +const std::vector &ProjectBuildData::lookupFiles(const QString &dirPath, + const QString &fileName) const +{ + static const std::vector emptyResult; + const auto it = m_artifactLookupTable.find({fileName, dirPath}); + return it != m_artifactLookupTable.end() ? it->second : emptyResult; +} + +const std::vector &ProjectBuildData::lookupFiles(const Artifact *artifact) const +{ + return lookupFiles(artifact->dirPath(), artifact->fileName()); +} + +void ProjectBuildData::insertFileDependency(FileDependency *dependency) +{ + fileDependencies += dependency; + insertIntoLookupTable(dependency); +} + +static void disconnectArtifactChildren(Artifact *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) +{ + qCDebug(lcBuildGraph) << "disconnect parents of" << relativeArtifactFileName(artifact); + for (BuildGraphNode * const parent : qAsConst(artifact->parents)) { + parent->children.remove(artifact); + if (parent->type() != BuildGraphNode::ArtifactNodeType) + continue; + auto const parentArtifact = static_cast(parent); + QBS_CHECK(parentArtifact->transformer); + parentArtifact->childrenAddedByScanner.remove(artifact); + parentArtifact->transformer->inputs.remove(artifact); + parentArtifact->transformer->explicitlyDependsOn.remove(artifact); + } + + artifact->parents.clear(); +} + +static void disconnectArtifact(Artifact *artifact) +{ + disconnectArtifactChildren(artifact); + disconnectArtifactParents(artifact); +} + +/*! + * Removes the artifact and all the artifacts that depend exclusively on it. + * Example: if you remove a cpp artifact then the obj artifact is removed but + * not the resulting application (if there's more then one cpp artifact). + */ +void ProjectBuildData::removeArtifactAndExclusiveDependents(Artifact *artifact, + const Logger &logger, bool removeFromProduct, + ArtifactSet *removedArtifacts) +{ + if (removedArtifacts) + removedArtifacts->insert(artifact); + + // Iterate over a copy of the artifact's parents, because we'll change + // artifact->parents with the disconnect call. + const NodeSet parentsCopy = artifact->parents; + for (Artifact *parent : filterByType(parentsCopy)) { + bool removeParent = false; + disconnect(parent, artifact); + if (parent->children.empty()) { + removeParent = true; + } else if (parent->transformer) { + parent->transformer->inputs.remove(artifact); + removeParent = parent->transformer->inputs.empty(); + } + if (removeParent) { + removeArtifactAndExclusiveDependents(parent, logger, removeFromProduct, + removedArtifacts); + } else { + parent->clearTimestamp(); + } + } + const bool removeFromDisk = artifact->artifactType == Artifact::Generated; + removeArtifact(artifact, logger, removeFromDisk, removeFromProduct); +} + +static void removeFromRuleNodes(Artifact *artifact) +{ + for (RuleNode * const ruleNode : filterByType(artifact->parents)) + ruleNode->removeOldInputArtifact(artifact); +} + +void ProjectBuildData::removeArtifact(Artifact *artifact, + const Logger &logger, bool removeFromDisk, bool removeFromProduct) +{ + qCDebug(lcBuildGraph) << "remove artifact" << relativeArtifactFileName(artifact); + if (removeFromDisk) + removeGeneratedArtifactFromDisk(artifact, logger); + removeFromLookupTable(artifact); + removeFromRuleNodes(artifact); + disconnectArtifact(artifact); + if (artifact->transformer) + artifact->transformer->outputs.remove(artifact); + if (removeFromProduct) + artifact->product->buildData->removeArtifact(artifact); + m_isDirty = false; +} + +void ProjectBuildData::setDirty() +{ + qCDebug(lcBuildGraph) << "Marking build graph as dirty"; + m_isDirty = true; +} + +void ProjectBuildData::setClean() +{ + qCDebug(lcBuildGraph) << "Marking build graph as clean"; + m_isDirty = false; +} + +void ProjectBuildData::load(PersistentPool &pool) +{ + serializationOp(pool); + for (FileDependency * const dep : qAsConst(fileDependencies)) + insertIntoLookupTable(dep); + m_isDirty = false; +} + +void ProjectBuildData::store(PersistentPool &pool) +{ + serializationOp(pool); +} + + +BuildDataResolver::BuildDataResolver(Logger logger) : m_logger(std::move(logger)) +{ +} + +void BuildDataResolver::resolveBuildData(const TopLevelProjectPtr &resolvedProject, + const RulesEvaluationContextPtr &evalContext) +{ + QBS_CHECK(!resolvedProject->buildData); + m_project = resolvedProject; + resolvedProject->buildData = std::make_unique(); + resolvedProject->buildData->evaluationContext = evalContext; + const std::vector &allProducts = resolvedProject->allProducts(); + evalContext->initializeObserver(Tr::tr("Setting up build graph for configuration %1") + .arg(resolvedProject->id()), int(allProducts.size()) + 1); + for (const auto &rProduct : allProducts) { + if (rProduct->enabled) + resolveProductBuildData(rProduct); + evalContext->incrementProgressValue(); + } + evalContext->incrementProgressValue(); + doSanityChecks(resolvedProject, m_logger); +} + +void BuildDataResolver::resolveProductBuildDataForExistingProject(const TopLevelProjectPtr &project, + const std::vector &freshProducts) +{ + m_project = project; + for (const ResolvedProductPtr &product : freshProducts) { + if (product->enabled) + resolveProductBuildData(product); + } + + QHash> dependencyMap; + for (const ResolvedProductPtr &product : freshProducts) { + if (!product->enabled) + continue; + QBS_CHECK(product->buildData); + const Set dependents = findDependentProducts(product); + for (const ResolvedProductPtr &dependentProduct : dependents) { + if (!dependentProduct->enabled) + continue; + if (!contains(dependencyMap[dependentProduct], product)) + dependencyMap[dependentProduct].push_back(product); + } + } + for (auto it = dependencyMap.cbegin(); it != dependencyMap.cend(); ++it) { + if (!contains(freshProducts, it.key())) + connectRulesToDependencies(it.key(), it.value()); + } +} + +class CreateRuleNodes : public RuleGraphVisitor +{ +public: + CreateRuleNodes(const ResolvedProductPtr &product) + : m_product(product) + { + } + +private: + const ResolvedProductPtr &m_product; + QHash m_nodePerRule; + Set m_rulesOnPath; + QList m_rulePath; + + void visit(const RuleConstPtr &parentRule, const RuleConstPtr &rule) override + { + if (!m_rulesOnPath.insert(rule.get()).second) { + QString pathstr; + for (const Rule *r : qAsConst(m_rulePath)) { + pathstr += QLatin1Char('\n') + r->toString() + QLatin1Char('\t') + + r->prepareScript.location().toString(); + } + throw ErrorInfo(Tr::tr("Cycle detected in rule dependencies: %1").arg(pathstr)); + } + m_rulePath.push_back(rule.get()); + RuleNode *node = m_nodePerRule.value(rule); + if (!node) { + node = new RuleNode; + m_nodePerRule.insert(rule, node); + node->product = m_product; + node->setRule(rule); + m_product->buildData->addNode(node); + qCDebug(lcBuildGraph).noquote() << "create" << node->toString() + << "for product" << m_product->uniqueName(); + } + if (parentRule) { + RuleNode *parent = m_nodePerRule.value(parentRule); + QBS_CHECK(parent); + connect(parent, node); + } else { + m_product->buildData->addRootNode(node); + } + } + + void endVisit(const RuleConstPtr &rule) override + { + m_rulesOnPath.remove(rule.get()); + m_rulePath.removeLast(); + } +}; + +static bool areRulesCompatible(const RuleNode *ruleNode, const RuleNode *dependencyRule) +{ + const FileTags outTags = dependencyRule->rule()->collectedOutputFileTags(); + if (ruleNode->rule()->excludedInputs.intersects(outTags)) + return false; + if (ruleNode->rule()->inputsFromDependencies.intersects(outTags)) + return true; + if (!dependencyRule->product->fileTags.intersects(outTags)) + return false; + if (ruleNode->rule()->explicitlyDependsOnFromDependencies.intersects(outTags)) + return true; + return ruleNode->rule()->auxiliaryInputs.intersects(outTags); +} + +void BuildDataResolver::resolveProductBuildData(const ResolvedProductPtr &product) +{ + if (product->buildData) + return; + + evalContext()->checkForCancelation(); + + product->buildData = std::make_unique(); + ArtifactSetByFileTag artifactsPerFileTag; + + for (const auto &dependency : qAsConst(product->dependencies)) { + QBS_CHECK(dependency->enabled); + resolveProductBuildData(dependency); + } + + //add qbsFile artifact + Artifact *qbsFileArtifact = lookupArtifact(product, product->location.filePath()); + if (!qbsFileArtifact) { + qbsFileArtifact = new Artifact; + qbsFileArtifact->artifactType = Artifact::SourceFile; + qbsFileArtifact->setFilePath(product->location.filePath()); + qbsFileArtifact->properties = product->moduleProperties; + insertArtifact(product, qbsFileArtifact); + } + qbsFileArtifact->addFileTag("qbs"); + artifactsPerFileTag["qbs"].insert(qbsFileArtifact); + + // read sources + for (const auto &sourceArtifact : product->allEnabledFiles()) { + QString filePath = sourceArtifact->absoluteFilePath; + if (lookupArtifact(product, filePath)) + continue; // ignore duplicate artifacts + + 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); + ruleGraph.accept(&crn); + + connectRulesToDependencies(product, product->dependencies); +} + +static bool isRootRuleNode(RuleNode *ruleNode) +{ + return ruleNode->product->buildData->rootNodes().contains(ruleNode); +} + +void BuildDataResolver::connectRulesToDependencies(const ResolvedProductPtr &product, + const std::vector &dependencies) +{ + // Connect the rules of this product to the compatible rules of all product dependencies. + // Rules that take "installable" artifacts are connected to all root rules of product + // dependencies. + std::vector ruleNodes; + for (RuleNode *ruleNode : filterByType(product->buildData->allNodes())) + ruleNodes.push_back(ruleNode); + for (const auto &dep : dependencies) { + if (!dep->buildData) + continue; + for (RuleNode *depRuleNode : filterByType(dep->buildData->allNodes())) { + for (RuleNode *ruleNode : ruleNodes) { + static const FileTag installableTag("installable"); + if (areRulesCompatible(ruleNode, depRuleNode) + || ((ruleNode->rule()->inputsFromDependencies.contains(installableTag) + || ruleNode->rule()->auxiliaryInputs.contains(installableTag) + || ruleNode->rule()->explicitlyDependsOnFromDependencies.contains( + installableTag)) + && isRootRuleNode(depRuleNode))) { + connect(ruleNode, depRuleNode); + } + } + } + } +} + +RulesEvaluationContextPtr BuildDataResolver::evalContext() const +{ + return m_project->buildData->evaluationContext; +} + +ScriptEngine *BuildDataResolver::engine() const +{ + return evalContext()->engine(); +} + +QScriptValue BuildDataResolver::scope() const +{ + return evalContext()->scope(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/projectbuilddata.h b/src/lib/corelib/buildgraph/projectbuilddata.h new file mode 100644 index 00000000..93034443 --- /dev/null +++ b/src/lib/corelib/buildgraph/projectbuilddata.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** 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 QBS_PROJECTBUILDDATA_H +#define QBS_PROJECTBUILDDATA_H + +#include "forward_decls.h" +#include "rawscanresults.h" +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +namespace qbs { +namespace Internal { +class BuildGraphNode; +class FileDependency; +class FileResourceBase; +class ScriptEngine; + +class QBS_AUTOTEST_EXPORT ProjectBuildData +{ +public: + ProjectBuildData(const ProjectBuildData *other = nullptr); + ~ProjectBuildData(); + + static QString deriveBuildGraphFilePath(const QString &buildDir, const QString &projectId); + + void insertIntoLookupTable(FileResourceBase *fileres); + void removeFromLookupTable(FileResourceBase *fileres); + + const std::vector &lookupFiles(const QString &filePath) const; + const std::vector &lookupFiles(const QString &dirPath, const QString &fileName) const; + const std::vector &lookupFiles(const Artifact *artifact) const; + void insertFileDependency(FileDependency *dependency); + void removeArtifactAndExclusiveDependents(Artifact *artifact, const Logger &logger, + bool removeFromProduct = true, ArtifactSet *removedArtifacts = nullptr); + void removeArtifact(Artifact *artifact, const Logger &logger, bool removeFromDisk = true, + bool removeFromProduct = true); + + void setDirty(); + void setClean(); + bool isDirty() const { return m_isDirty; } + + + Set fileDependencies; + RawScanResults rawScanResults; + + // do not serialize: + RulesEvaluationContextPtr evaluationContext; + + void load(PersistentPool &pool); + void store(PersistentPool &pool); + +private: + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(fileDependencies, rawScanResults); + } + + using ArtifactKey = std::pair; + using ArtifactLookupTable = std::unordered_map>; + ArtifactLookupTable m_artifactLookupTable; + + bool m_doCleanupInDestructor = true; + bool m_isDirty = true; +}; + + +class BuildDataResolver +{ +public: + BuildDataResolver(Logger logger); + void resolveBuildData(const TopLevelProjectPtr &resolvedProject, + const RulesEvaluationContextPtr &evalContext); + void resolveProductBuildDataForExistingProject(const TopLevelProjectPtr &project, + const std::vector &freshProducts); + +private: + void resolveProductBuildData(const ResolvedProductPtr &product); + void connectRulesToDependencies(const ResolvedProductPtr &product, + const std::vector &dependencies); + + RulesEvaluationContextPtr evalContext() const; + ScriptEngine *engine() const; + QScriptValue scope() const; + + TopLevelProjectPtr m_project; + Logger m_logger; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROJECTBUILDDATA_H diff --git a/src/lib/corelib/buildgraph/qtmocscanner.cpp b/src/lib/corelib/buildgraph/qtmocscanner.cpp new file mode 100644 index 00000000..4e054a63 --- /dev/null +++ b/src/lib/corelib/buildgraph/qtmocscanner.cpp @@ -0,0 +1,278 @@ +/**************************************************************************** +** +** 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 "qtmocscanner.h" + +#include "artifact.h" +#include "depscanner.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "rawscanresults.h" +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace qbs { +namespace Internal { + +struct CommonFileTags +{ + const FileTag cpp = "cpp"; + const FileTag cppCombine = "cpp.combine"; + const FileTag hpp = "hpp"; + const FileTag moc_cpp = "moc_cpp"; + const FileTag moc_cpp_plugin = "moc_cpp_plugin"; + const FileTag moc_hpp_plugin = "moc_hpp_plugin"; + const FileTag moc_hpp = "moc_hpp"; + const FileTag objcpp = "objcpp"; + const FileTag objcppCombine = "objcpp.combine"; +}; + +Q_GLOBAL_STATIC(CommonFileTags, commonFileTags) + +class QtScanner : public DependencyScanner +{ +public: + QtScanner(const DependencyScanner &actualScanner) + : m_id(QStringLiteral("qt") + actualScanner.id()) {} + +private: + QStringList collectSearchPaths(Artifact *) override { return {}; } + QStringList collectDependencies(Artifact *, FileResourceBase *, const char *) override + { + return {}; + } + 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 override + { + return true; + } + bool cacheIsPerFile() const override { return false; } + + const QString m_id; +}; + +static QString qtMocScannerJsName() { return QStringLiteral("QtMocScanner"); } + +QtMocScanner::QtMocScanner(const ResolvedProductPtr &product, QScriptValue targetScriptValue) + : m_tags(*commonFileTags()) + , m_product(product) + , m_targetScriptValue(targetScriptValue) +{ + const auto engine = static_cast(targetScriptValue.engine()); + QScriptValue scannerObj = engine->newObject(); + targetScriptValue.setProperty(qtMocScannerJsName(), scannerObj); + QScriptValue applyFunction = engine->newFunction(&js_apply, this); + scannerObj.setProperty(QStringLiteral("apply"), applyFunction); +} + +QtMocScanner::~QtMocScanner() +{ + m_targetScriptValue.setProperty(qtMocScannerJsName(), QScriptValue()); +} + +static RawScanResult runScanner(ScannerPlugin *scanner, const Artifact *artifact) +{ + const QString &filepath = artifact->filePath(); + QtScanner depScanner((PluginDependencyScanner(scanner))); + RawScanResults &rawScanResults + = artifact->product->topLevelProject()->buildData->rawScanResults; + RawScanResults::ScanData &scanData = rawScanResults.findScanData(artifact, &depScanner, + artifact->properties); + if (scanData.lastScanTime < artifact->timestamp()) { + FileTags tags = artifact->fileTags(); + if (tags.contains(commonFileTags->cppCombine)) { + tags.remove(commonFileTags->cppCombine); + tags.insert(commonFileTags->cpp); + } + if (tags.contains(commonFileTags->objcppCombine)) { + tags.remove(commonFileTags->objcppCombine); + tags.insert(commonFileTags->objcpp); + } + const QByteArray tagsForScanner = tags.toStringList().join(QLatin1Char(',')).toLatin1(); + void *opaq = scanner->open(filepath.utf16(), tagsForScanner.constData(), + ScanForDependenciesFlag | ScanForFileTagsFlag); + if (!opaq || !scanner->additionalFileTags) + return scanData.rawScanResult; + + scanData.rawScanResult.additionalFileTags.clear(); + scanData.rawScanResult.deps.clear(); + int length = 0; + const char **szFileTagsFromScanner = scanner->additionalFileTags(opaq, &length); + if (szFileTagsFromScanner) { + for (int i = length; --i >= 0;) + scanData.rawScanResult.additionalFileTags += szFileTagsFromScanner[i]; + } + + QString baseDirOfInFilePath = artifact->dirPath(); + forever { + int flags = 0; + const char *szOutFilePath = scanner->next(opaq, &length, &flags); + if (szOutFilePath == nullptr) + break; + QString includedFilePath = QString::fromLocal8Bit(szOutFilePath, length); + if (includedFilePath.isEmpty()) + continue; + bool isLocalInclude = (flags & SC_LOCAL_INCLUDE_FLAG); + if (isLocalInclude) { + QString localFilePath = FileInfo::resolvePath(baseDirOfInFilePath, includedFilePath); + if (FileInfo::exists(localFilePath)) + includedFilePath = localFilePath; + } + scanData.rawScanResult.deps.emplace_back(includedFilePath); + } + + scanner->close(opaq); + scanData.lastScanTime = FileTime::currentTime(); + } + return scanData.rawScanResult; +} + +void QtMocScanner::findIncludedMocCppFiles() +{ + if (!m_includedMocCppFiles.empty()) + return; + + 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)) { + const RawScanResult scanResult = runScanner(m_cppScanner, artifact); + for (const RawScannedDependency &dependency : scanResult.deps) { + QString includedFileName = dependency.fileName(); + if (includedFileName.startsWith(QLatin1String("moc_")) + && includedFileName.endsWith(QLatin1String(".cpp"))) { + qCDebug(lcMocScan) << artifact->fileName() << "includes" << includedFileName; + includedFileName.remove(0, 4); + includedFileName.chop(4); + m_includedMocCppFiles.insert(includedFileName, artifact->fileName()); + } + } + } +} + +QScriptValue QtMocScanner::js_apply(QScriptContext *ctx, QScriptEngine *engine, + QtMocScanner *that) +{ + QScriptValue input = ctx->argument(0); + return that->apply(engine, attachedPointer(input)); +} + +static QScriptValue scannerCountError(QScriptEngine *engine, size_t scannerCount, + const QString &fileTag) +{ + return engine->currentContext()->throwError( + Tr::tr("There are %1 scanners for the file tag %2. " + "Expected is exactly one.").arg(scannerCount).arg(fileTag)); +} + +QScriptValue QtMocScanner::apply(QScriptEngine *engine, const Artifact *artifact) +{ + if (!m_cppScanner) { + auto scanners = ScannerPluginManager::scannersForFileTag(m_tags.cpp); + if (scanners.size() != 1) + return scannerCountError(engine, scanners.size(), m_tags.cpp.toString()); + m_cppScanner = scanners.front(); + } + + qCDebug(lcMocScan).noquote() << "scanning" << artifact->toString(); + + bool hasQObjectMacro = false; + bool mustCompile = false; + bool hasPluginMetaDataMacro = false; + const bool isHeaderFile = artifact->fileTags().contains(m_tags.hpp); + + RawScanResult scanResult = runScanner(m_cppScanner, artifact); + if (scanResult.additionalFileTags.empty() && artifact->fileTags().contains("mocable")) { + if (isHeaderFile) { + scanResult.additionalFileTags.insert(m_tags.moc_hpp); + } else if (artifact->fileTags().contains(m_tags.cpp) + || artifact->fileTags().contains(m_tags.cppCombine) + || artifact->fileTags().contains(m_tags.objcpp) + || artifact->fileTags().contains(m_tags.objcppCombine)) { + scanResult.additionalFileTags.insert(m_tags.moc_cpp); + } + } + if (!scanResult.additionalFileTags.empty()) { + if (isHeaderFile) { + if (scanResult.additionalFileTags.contains(m_tags.moc_hpp)) + hasQObjectMacro = true; + if (scanResult.additionalFileTags.contains(m_tags.moc_hpp_plugin)) { + hasQObjectMacro = true; + hasPluginMetaDataMacro = true; + } + findIncludedMocCppFiles(); + if (!m_includedMocCppFiles.contains(FileInfo::completeBaseName(artifact->fileName()))) + mustCompile = true; + } else { + if (scanResult.additionalFileTags.contains(m_tags.moc_cpp)) + hasQObjectMacro = true; + if (scanResult.additionalFileTags.contains(m_tags.moc_cpp_plugin)) { + hasQObjectMacro = true; + hasPluginMetaDataMacro = true; + } + } + } + + qCDebug(lcMocScan) << "hasQObjectMacro:" << hasQObjectMacro + << "mustCompile:" << mustCompile + << "hasPluginMetaDataMacro:" << hasPluginMetaDataMacro; + + QScriptValue obj = engine->newObject(); + obj.setProperty(QStringLiteral("hasQObjectMacro"), hasQObjectMacro); + obj.setProperty(QStringLiteral("mustCompile"), mustCompile); + obj.setProperty(QStringLiteral("hasPluginMetaDataMacro"), hasPluginMetaDataMacro); + static_cast(engine)->setUsesIo(); + return obj; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/qtmocscanner.h b/src/lib/corelib/buildgraph/qtmocscanner.h new file mode 100644 index 00000000..6455383f --- /dev/null +++ b/src/lib/corelib/buildgraph/qtmocscanner.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 QBS_QTMOCSCANNER_H +#define QBS_QTMOCSCANNER_H + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE +class QScriptContext; +QT_END_NAMESPACE + +class ScannerPlugin; + +namespace qbs { +namespace Internal { + +class Artifact; +struct CommonFileTags; + +class QtMocScanner +{ +public: + explicit QtMocScanner(const ResolvedProductPtr &product, QScriptValue targetScriptValue); + ~QtMocScanner(); + +private: + void findIncludedMocCppFiles(); + static QScriptValue js_apply(QScriptContext *ctx, QScriptEngine *engine, QtMocScanner *that); + QScriptValue apply(QScriptEngine *engine, const Artifact *artifact); + + const CommonFileTags &m_tags; + const ResolvedProductPtr &m_product; + QScriptValue m_targetScriptValue; + QHash m_includedMocCppFiles; + ScannerPlugin *m_cppScanner = nullptr; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_QTMOCSCANNER_H diff --git a/src/lib/corelib/buildgraph/rawscanneddependency.cpp b/src/lib/corelib/buildgraph/rawscanneddependency.cpp new file mode 100644 index 00000000..bea9655e --- /dev/null +++ b/src/lib/corelib/buildgraph/rawscanneddependency.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 "rawscanneddependency.h" + +#include + +namespace qbs { +namespace Internal { + +RawScannedDependency::RawScannedDependency() : m_isClean(true) {} + +RawScannedDependency::RawScannedDependency(const QString &filePath) +{ + FileInfo::splitIntoDirectoryAndFileName(filePath, &m_dirPath, &m_fileName); + setClean(); +} + +QString RawScannedDependency::filePath() const +{ + return m_dirPath.isEmpty() ? m_fileName : m_dirPath + QLatin1Char('/') + m_fileName; +} + +void RawScannedDependency::setClean() +{ + m_isClean = !m_dirPath.contains(QLatin1Char('.')) && !m_dirPath.contains(QStringLiteral("//")); +} + +void RawScannedDependency::load(PersistentPool &pool) +{ + serializationOp(pool); + setClean(); +} + +void RawScannedDependency::store(PersistentPool &pool) +{ + serializationOp(pool); +} + +bool operator==(const RawScannedDependency &d1, const RawScannedDependency &d2) +{ + return d1.dirPath() == d2.dirPath() && d1.fileName() == d2.fileName(); +} + + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/rawscanneddependency.h b/src/lib/corelib/buildgraph/rawscanneddependency.h new file mode 100644 index 00000000..4871bea6 --- /dev/null +++ b/src/lib/corelib/buildgraph/rawscanneddependency.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** 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 QBS_RAWSCANNEDDEPENDENCY_H +#define QBS_RAWSCANNEDDEPENDENCY_H + +#include + +#include + +namespace qbs { +namespace Internal { +class PersistentPool; + +class RawScannedDependency +{ +public: + RawScannedDependency(); + RawScannedDependency(const QString &filePath); + + QString filePath() const; + const QString &dirPath() const { return m_dirPath; } + const QString &fileName() const { return m_fileName; } + bool isClean() const { return m_isClean; } + bool isValid() const { return !m_fileName.isEmpty(); } + + void load(PersistentPool &pool); + void store(PersistentPool &pool); + +private: + void setClean(); + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(m_dirPath, m_fileName); + } + + QString m_dirPath; + QString m_fileName; + bool m_isClean = 0; +}; + +bool operator==(const RawScannedDependency &d1, const RawScannedDependency &d2); +inline bool operator!=(const RawScannedDependency &d1, const RawScannedDependency &d2) +{ + return !(d1 == d2); +} + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/buildgraph/rawscanresults.cpp b/src/lib/corelib/buildgraph/rawscanresults.cpp new file mode 100644 index 00000000..a519f685 --- /dev/null +++ b/src/lib/corelib/buildgraph/rawscanresults.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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 "rawscanresults.h" + +#include "filedependency.h" +#include "depscanner.h" + +#include + +namespace qbs { +namespace Internal { + +RawScanResults::ScanData &RawScanResults::findScanData( + const FileResourceBase *file, + const DependencyScanner *scanner, + const PropertyMapConstPtr &moduleProperties) +{ + std::vector &scanDataForFile = m_rawScanData[file->filePath()]; + const QString &scannerId = scanner->id(); + for (auto &scanData : scanDataForFile) { + if (scannerId != scanData.scannerId) + continue; + if (!scanner->areModulePropertiesCompatible(moduleProperties, scanData.moduleProperties)) + continue; + return scanData; + } + ScanData newScanData; + newScanData.scannerId = scannerId; + newScanData.moduleProperties = moduleProperties; + scanDataForFile.push_back(std::move(newScanData)); + return scanDataForFile.back(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/rawscanresults.h b/src/lib/corelib/buildgraph/rawscanresults.h new file mode 100644 index 00000000..b1089708 --- /dev/null +++ b/src/lib/corelib/buildgraph/rawscanresults.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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 QBS_RAWSCANRESULTS_H +#define QBS_RAWSCANRESULTS_H + +#include "rawscanneddependency.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace qbs { +namespace Internal { +class DependencyScanner; +class FileResourceBase; + +class RawScanResult +{ +public: + std::vector deps; + FileTags additionalFileTags; + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(deps, additionalFileTags); + } +}; + +class RawScanResults +{ +public: + struct ScanData + { + QString scannerId; + PropertyMapConstPtr moduleProperties; + FileTime lastScanTime; + RawScanResult rawScanResult; + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(scannerId, moduleProperties, lastScanTime, rawScanResult); + } + }; + + ScanData &findScanData( + const FileResourceBase *file, + const DependencyScanner *scanner, + const PropertyMapConstPtr &moduleProperties); + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(m_rawScanData); + } + +private: + QHash> m_rawScanData; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/buildgraph/requestedartifacts.cpp b/src/lib/corelib/buildgraph/requestedartifacts.cpp new file mode 100644 index 00000000..e4730d2f --- /dev/null +++ b/src/lib/corelib/buildgraph/requestedartifacts.cpp @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "requestedartifacts.h" + +#include "productbuilddata.h" +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +bool RequestedArtifacts::isUpToDate(const TopLevelProject *project) const +{ + if (m_requestedArtifactsPerProduct.empty()) + return true; + const std::vector &allProducts = project->allProducts(); + for (const auto &kv : m_requestedArtifactsPerProduct) { + const QString &productName = kv.first; + const auto findProduct = [productName](const ResolvedProductConstPtr &p) { + return p->uniqueName() == productName; + }; + const auto productIt = std::find_if(allProducts.begin(), allProducts.end(), findProduct); + if (productIt == allProducts.end()) { + qCDebug(lcBuildGraph) << "artifacts map not up to date: product" << productName + << "does not exist anymore"; + return false; + } + if (!kv.second.isUpToDate(productIt->get())) + return false; + } + return true; +} + +void RequestedArtifacts::setAllArtifactTags(const ResolvedProduct *product, bool forceUpdate) +{ + RequestedArtifactsPerProduct &ra = m_requestedArtifactsPerProduct[product->uniqueName()]; + if (!ra.allTags.empty() && !forceUpdate) + return; + ra.allTags.clear(); + const ArtifactSetByFileTag artifactsMap = product->buildData->artifactsByFileTag(); + for (auto it = artifactsMap.begin(); it != artifactsMap.end(); ++it) + ra.allTags.insert(it.key().toString()); +} + +void RequestedArtifacts::setArtifactsForTag(const ResolvedProduct *product, + const FileTag &tag) +{ + RequestedArtifactsPerProduct &ra = m_requestedArtifactsPerProduct[product->uniqueName()]; + QBS_ASSERT(!ra.allTags.empty(), ;); + Set &filePaths = ra.requestedTags[tag.toString()]; + for (const Artifact * const a : product->buildData->artifactsByFileTag().value(tag)) + filePaths.insert(a->filePath()); +} + +void RequestedArtifacts::setNonExistingTagRequested(const ResolvedProduct *product, + const QString &tag) +{ + RequestedArtifactsPerProduct &ra = m_requestedArtifactsPerProduct[product->uniqueName()]; + QBS_ASSERT(!ra.allTags.empty(), ;); + Set &filePaths = ra.requestedTags[tag]; + QBS_CHECK(filePaths.empty()); +} + +void RequestedArtifacts::setArtifactsEnumerated(const ResolvedProduct *product) +{ + m_requestedArtifactsPerProduct[product->uniqueName()].artifactsEnumerated = true; +} + +void RequestedArtifacts::unite(const RequestedArtifacts &other) +{ + for (const auto &kv : other.m_requestedArtifactsPerProduct) + m_requestedArtifactsPerProduct[kv.first].unite(kv.second); +} + +void RequestedArtifacts::doSanityChecks() const +{ + for (const auto &kv : m_requestedArtifactsPerProduct) + kv.second.doSanityChecks(); +} + +void RequestedArtifacts::load(PersistentPool &pool) +{ + serializationOp(pool); +} + +void RequestedArtifacts::store(PersistentPool &pool) +{ + serializationOp(pool); +} + +bool RequestedArtifacts::RequestedArtifactsPerProduct::isUpToDate( + const ResolvedProduct *product) const +{ + if (!product->buildData) { + qCDebug(lcBuildGraph) << "artifacts map not up to date: product" << product->uniqueName() + << "is now disabled"; + return false; + } + + if (!artifactsEnumerated && requestedTags.empty()) + return true; + + const ArtifactSetByFileTag currentArtifacts = product->buildData->artifactsByFileTag(); + for (const auto &kv : requestedTags) { + const FileTag tag = FileTag(kv.first.toUtf8()); + const auto currentIt = currentArtifacts.constFind(tag); + Set currentFilePathsForTag; + if (currentIt != currentArtifacts.constEnd()) { + for (const Artifact * const a : currentIt.value()) + currentFilePathsForTag.insert(a->filePath()); + } + if (currentFilePathsForTag != kv.second) { + qCDebug(lcBuildGraph) << "artifacts map not up to date: requested artifact set for " + "file tag" << kv.first << "in product" + << product->uniqueName() << "differs from the current one"; + return false; + } + } + + if (!artifactsEnumerated) + return true; + + Set currentTags; + for (auto it = currentArtifacts.begin(); it != currentArtifacts.end(); ++it) + currentTags.insert(it.key().toString()); + if (currentTags != allTags) { + qCDebug(lcBuildGraph) << "artifacts map not up to date: overall file tags differ for " + << "product" << product->uniqueName(); + return false; + } + return true; +} + +void RequestedArtifacts::RequestedArtifactsPerProduct::unite( + const RequestedArtifactsPerProduct &other) +{ + if (allTags.empty()) { + *this = other; + return; + } + allTags = other.allTags; + for (const auto &kv : other.requestedTags) + requestedTags[kv.first] = kv.second; +} + +void RequestedArtifacts::RequestedArtifactsPerProduct::doSanityChecks() const +{ + for (const auto &kv : requestedTags) + QBS_CHECK(allTags.contains(kv.first) || kv.second.empty()); +} + +void RequestedArtifacts::RequestedArtifactsPerProduct::load(PersistentPool &pool) +{ + serializationOp(pool); +} + +void RequestedArtifacts::RequestedArtifactsPerProduct::store(PersistentPool &pool) +{ + serializationOp(pool); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/requestedartifacts.h b/src/lib/corelib/buildgraph/requestedartifacts.h new file mode 100644 index 00000000..d3804ca1 --- /dev/null +++ b/src/lib/corelib/buildgraph/requestedartifacts.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_REQUESTEDARTIFACTS_H +#define QBS_REQUESTEDARTIFACTS_H + +#include +#include +#include + +#include + +#include + +namespace qbs { +namespace Internal { +class FileTag; +class PersistentPool; + +class RequestedArtifacts +{ +public: + bool isUpToDate(const TopLevelProject *project) const; + + void clear() { m_requestedArtifactsPerProduct.clear(); } + void setAllArtifactTags(const ResolvedProduct *product, bool forceUpdate); + void setArtifactsForTag(const ResolvedProduct *product, const FileTag &tag); + void setNonExistingTagRequested(const ResolvedProduct *product, const QString &tag); + void setArtifactsEnumerated(const ResolvedProduct *product); + void unite(const RequestedArtifacts &other); + + void doSanityChecks() const; + + void load(PersistentPool &pool); + void store(PersistentPool &pool); + + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(m_requestedArtifactsPerProduct); + } + +private: + struct RequestedArtifactsPerProduct + { + Set allTags; + std::unordered_map> requestedTags; + bool artifactsEnumerated = false; + + bool isUpToDate(const ResolvedProduct *product) const; + + void unite(const RequestedArtifactsPerProduct &other); + + void doSanityChecks() const; + + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(allTags, requestedTags, artifactsEnumerated); + } + + void load(PersistentPool &pool); + void store(PersistentPool &pool); + }; + + std::unordered_map m_requestedArtifactsPerProduct; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/buildgraph/requesteddependencies.cpp b/src/lib/corelib/buildgraph/requesteddependencies.cpp new file mode 100644 index 00000000..b95c8db9 --- /dev/null +++ b/src/lib/corelib/buildgraph/requesteddependencies.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "requesteddependencies.h" + +#include +#include +#include + +namespace qbs { +namespace Internal { + +static Set depNamesForProduct(const ResolvedProduct *p) +{ + Set names; + for (const auto &dep : p->dependencies) + names.insert(dep->uniqueName()); + for (const auto &m : p->modules) { + if (!m->isProduct) + names.insert(m->name); + } + return names; +} + +void RequestedDependencies::set(const Set &products) +{ + m_depsPerProduct.clear(); + add(products); +} + +void RequestedDependencies::add(const Set &products) +{ + for (const ResolvedProduct * const p : products) + m_depsPerProduct[p->uniqueName()] = depNamesForProduct(p); +} + +bool RequestedDependencies::isUpToDate(const TopLevelProject *project) const +{ + if (m_depsPerProduct.empty()) + return true; + for (const auto &product : project->allProducts()) { + const auto it = m_depsPerProduct.find(product->uniqueName()); + if (it == m_depsPerProduct.cend()) + continue; + const Set newDepNames = depNamesForProduct(product.get()); + if (newDepNames != it->second) { + qCDebug(lcBuildGraph) << "dependencies list was accessed for product" + << product->fullDisplayName() << "and dependencies have changed."; + return false; + } + } + return true; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/requesteddependencies.h b/src/lib/corelib/buildgraph/requesteddependencies.h new file mode 100644 index 00000000..e9e341d4 --- /dev/null +++ b/src/lib/corelib/buildgraph/requesteddependencies.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_REQUESTEDDEPENDENCIES_H +#define QBS_REQUESTEDDEPENDENCIES_H + +#include +#include +#include + +#include + +#include + +namespace qbs { +namespace Internal { + +class RequestedDependencies +{ +public: + RequestedDependencies() = default; + RequestedDependencies(const Set &products) { set(products); } + void set(const Set &products); + void add(const Set &products); + void clear() { m_depsPerProduct.clear(); } + bool isUpToDate(const TopLevelProject *project) const; + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(m_depsPerProduct); + } + +private: + struct QStringHash { std::size_t operator()(const QString &s) const { return qHash(s); } }; + std::unordered_map, QStringHash> m_depsPerProduct; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/buildgraph/rescuableartifactdata.h b/src/lib/corelib/buildgraph/rescuableartifactdata.h new file mode 100644 index 00000000..6dd10f76 --- /dev/null +++ b/src/lib/corelib/buildgraph/rescuableartifactdata.h @@ -0,0 +1,139 @@ +/**************************************************************************** +** +** 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 QBS_RESCUABLEARTIFACTDATA_H +#define QBS_RESCUABLEARTIFACTDATA_H + +#include "forward_decls.h" +#include "requestedartifacts.h" +#include "requesteddependencies.h" +#include "rulecommands.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace qbs { +namespace Internal { + +class QBS_AUTOTEST_EXPORT RescuableArtifactData +{ +public: + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(timeStamp, children, fileDependencies, knownOutOfDate, + propertiesRequestedInPrepareScript, + propertiesRequestedInCommands, + propertiesRequestedFromArtifactInPrepareScript, + propertiesRequestedFromArtifactInCommands, + importedFilesUsedInPrepareScript, importedFilesUsedInCommands, + depsRequestedInPrepareScript, depsRequestedInCommands, + commands, artifactsMapRequestedInPrepareScript, + artifactsMapRequestedInCommands, + exportedModulesAccessedInPrepareScript, + exportedModulesAccessedInCommands, + lastPrepareScriptExecutionTime, + lastCommandExecutionTime, fileTags, properties); + } + + bool isValid() const { return !!properties; } + + struct ChildData + { + ChildData(QString n = QString(), QString m = QString(), + QString c = QString(), bool byScanner = false) + : productName(std::move(n)) + , productMultiplexId(std::move(m)) + , childFilePath(std::move(c)) + , addedByScanner(byScanner) + {} + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(productName, productMultiplexId, childFilePath, + addedByScanner); + } + + QString productName; + QString productMultiplexId; + QString childFilePath; + bool addedByScanner; + }; + + FileTime timeStamp; + std::vector children; + std::vector fileDependencies; + + // Per-Transformer data + CommandList commands; + PropertySet propertiesRequestedInPrepareScript; + PropertySet propertiesRequestedInCommands; + PropertyHash propertiesRequestedFromArtifactInPrepareScript; + PropertyHash propertiesRequestedFromArtifactInCommands; + std::vector importedFilesUsedInPrepareScript; + std::vector importedFilesUsedInCommands; + RequestedDependencies depsRequestedInPrepareScript; + RequestedDependencies depsRequestedInCommands; + RequestedArtifacts artifactsMapRequestedInPrepareScript; + RequestedArtifacts artifactsMapRequestedInCommands; + FileTime lastPrepareScriptExecutionTime; + FileTime lastCommandExecutionTime; + std::unordered_map exportedModulesAccessedInPrepareScript; + std::unordered_map exportedModulesAccessedInCommands; + bool knownOutOfDate = false; + + // Only needed for API purposes + FileTags fileTags; + PropertyMapPtr properties; + +}; +using AllRescuableArtifactData = QHash; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/buildgraph/rulecommands.cpp b/src/lib/corelib/buildgraph/rulecommands.cpp new file mode 100644 index 00000000..8fa3255f --- /dev/null +++ b/src/lib/corelib/buildgraph/rulecommands.cpp @@ -0,0 +1,489 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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 "rulecommands.h" +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace qbs { +namespace Internal { + +static QString argumentsProperty() { return QStringLiteral("arguments"); } +static QString environmentProperty() { return QStringLiteral("environment"); } +static QString extendedDescriptionProperty() { return QStringLiteral("extendedDescription"); } +static QString highlightProperty() { return QStringLiteral("highlight"); } +static QString ignoreDryRunProperty() { return QStringLiteral("ignoreDryRun"); } +static QString maxExitCodeProperty() { return QStringLiteral("maxExitCode"); } +static QString programProperty() { return QStringLiteral("program"); } +static QString responseFileArgumentIndexProperty() +{ + return QStringLiteral("responseFileArgumentIndex"); +} +static QString responseFileThresholdProperty() { return QStringLiteral("responseFileThreshold"); } +static QString responseFileUsagePrefixProperty() +{ + return QStringLiteral("responseFileUsagePrefix"); +} +static QString responseFileSeparatorProperty() { return QStringLiteral("responseFileSeparator"); } +static QString silentProperty() { return QStringLiteral("silent"); } +static QString stderrFilePathProperty() { return QStringLiteral("stderrFilePath"); } +static QString stderrFilterFunctionProperty() { return QStringLiteral("stderrFilterFunction"); } +static QString stdoutFilePathProperty() { return QStringLiteral("stdoutFilePath"); } +static QString stdoutFilterFunctionProperty() { return QStringLiteral("stdoutFilterFunction"); } +static QString timeoutProperty() { return QStringLiteral("timeout"); } +static QString workingDirProperty() { return QStringLiteral("workingDirectory"); } + +static QString invokedSourceCode(const QScriptValue &codeOrFunction) +{ + const QString &code = codeOrFunction.toString(); + return codeOrFunction.isFunction() ? QStringLiteral("(") + code + QStringLiteral(")()") : code; +} + +AbstractCommand::AbstractCommand() + : m_description(defaultDescription()), + m_extendedDescription(defaultExtendedDescription()), + m_highlight(defaultHighLight()), + m_ignoreDryRun(defaultIgnoreDryRun()), + m_silent(defaultIsSilent()), + m_timeout(defaultTimeout()) +{ +} + +AbstractCommand::~AbstractCommand() = default; + +bool AbstractCommand::equals(const AbstractCommand *other) const +{ + return type() == other->type() + && m_description == other->m_description + && m_extendedDescription == other->m_extendedDescription + && m_highlight == other->m_highlight + && m_ignoreDryRun == other->m_ignoreDryRun + && m_silent == other->m_silent + && m_jobPool == other->m_jobPool + && m_timeout == other->m_timeout + && m_properties == other->m_properties; +} + +void AbstractCommand::fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation) +{ + m_description = scriptValue->property(StringConstants::descriptionProperty()).toString(); + m_extendedDescription = scriptValue->property(extendedDescriptionProperty()).toString(); + m_highlight = scriptValue->property(highlightProperty()).toString(); + m_ignoreDryRun = scriptValue->property(ignoreDryRunProperty()).toBool(); + m_silent = scriptValue->property(silentProperty()).toBool(); + m_jobPool = scriptValue->property(StringConstants::jobPoolProperty()).toString(); + const auto timeoutScriptValue = scriptValue->property(timeoutProperty()); + if (!timeoutScriptValue.isUndefined() && !timeoutScriptValue.isNull()) + m_timeout = timeoutScriptValue.toInt32(); + m_codeLocation = codeLocation; + + m_predefinedProperties + << StringConstants::descriptionProperty() + << extendedDescriptionProperty() + << highlightProperty() + << ignoreDryRunProperty() + << StringConstants::jobPoolProperty() + << silentProperty() + << timeoutProperty(); +} + +QString AbstractCommand::fullDescription(const QString &productName) const +{ + return description() + QLatin1String(" [") + productName + QLatin1Char(']'); +} + +void AbstractCommand::load(PersistentPool &pool) +{ + serializationOp(pool); +} + +void AbstractCommand::store(PersistentPool &pool) +{ + serializationOp(pool); +} + +void AbstractCommand::applyCommandProperties(const QScriptValue *scriptValue) +{ + QScriptValueIterator it(*scriptValue); + while (it.hasNext()) { + it.next(); + if (m_predefinedProperties.contains(it.name())) + continue; + const QVariant value = it.value().toVariant(); + if (QMetaType::Type(value.type()) == QMetaType::QObjectStar + || it.value().scriptClass() + || it.value().data().isValid()) { + throw ErrorInfo(Tr::tr("Property '%1' has a type unsuitable for storing in a command " + "object.").arg(it.name()), m_codeLocation); + } + m_properties.insert(it.name(), value); + } +} + +static QScriptValue js_CommandBase(QScriptContext *context, QScriptEngine *engine) +{ + QScriptValue cmd = context->thisObject(); + QBS_ASSERT(context->isCalledAsConstructor(), cmd = engine->newObject()); + cmd.setProperty(StringConstants::descriptionProperty(), + engine->toScriptValue(AbstractCommand::defaultDescription())); + cmd.setProperty(extendedDescriptionProperty(), + engine->toScriptValue(AbstractCommand::defaultExtendedDescription())); + cmd.setProperty(highlightProperty(), + engine->toScriptValue(AbstractCommand::defaultHighLight())); + cmd.setProperty(ignoreDryRunProperty(), + engine->toScriptValue(AbstractCommand::defaultIgnoreDryRun())); + cmd.setProperty(silentProperty(), + engine->toScriptValue(AbstractCommand::defaultIsSilent())); + cmd.setProperty(timeoutProperty(), + engine->toScriptValue(AbstractCommand::defaultTimeout())); + return cmd; +} + +static QScriptValue js_Command(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(!context->isCalledAsConstructor())) + return context->throwError(Tr::tr("Command constructor called without new.")); + + static ProcessCommandPtr commandPrototype = ProcessCommand::create(); + + QScriptValue program = context->argument(0); + if (program.isUndefined()) + program = engine->toScriptValue(commandPrototype->program()); + QScriptValue arguments = context->argument(1); + if (arguments.isUndefined()) + arguments = engine->toScriptValue(commandPrototype->arguments()); + QScriptValue cmd = js_CommandBase(context, engine); + cmd.setProperty(StringConstants::classNameProperty(), + engine->toScriptValue(StringConstants::commandType())); + cmd.setProperty(programProperty(), program); + cmd.setProperty(argumentsProperty(), arguments); + cmd.setProperty(workingDirProperty(), + engine->toScriptValue(commandPrototype->workingDir())); + cmd.setProperty(maxExitCodeProperty(), + engine->toScriptValue(commandPrototype->maxExitCode())); + cmd.setProperty(stdoutFilterFunctionProperty(), + engine->toScriptValue(commandPrototype->stdoutFilterFunction())); + cmd.setProperty(stderrFilterFunctionProperty(), + engine->toScriptValue(commandPrototype->stderrFilterFunction())); + cmd.setProperty(responseFileThresholdProperty(), + engine->toScriptValue(commandPrototype->responseFileThreshold())); + cmd.setProperty(responseFileArgumentIndexProperty(), + engine->toScriptValue(commandPrototype->responseFileArgumentIndex())); + cmd.setProperty(responseFileUsagePrefixProperty(), + engine->toScriptValue(commandPrototype->responseFileUsagePrefix())); + cmd.setProperty(responseFileSeparatorProperty(), + engine->toScriptValue(commandPrototype->responseFileSeparator())); + cmd.setProperty(stdoutFilePathProperty(), + engine->toScriptValue(commandPrototype->stdoutFilePath())); + cmd.setProperty(stderrFilePathProperty(), + engine->toScriptValue(commandPrototype->stderrFilePath())); + cmd.setProperty(environmentProperty(), + engine->toScriptValue(commandPrototype->environment().toStringList())); + cmd.setProperty(ignoreDryRunProperty(), + engine->toScriptValue(commandPrototype->ignoreDryRun())); + return cmd; +} + + +void ProcessCommand::setupForJavaScript(QScriptValue targetObject) +{ + QBS_CHECK(targetObject.isObject()); + QScriptValue ctor = targetObject.engine()->newFunction(js_Command, 2); + targetObject.setProperty(StringConstants::commandType(), ctor); +} + +ProcessCommand::ProcessCommand() + : m_maxExitCode(0) + , m_responseFileThreshold(defaultResponseFileThreshold()) + , m_responseFileArgumentIndex(0) + , m_responseFileSeparator(QStringLiteral("\n")) +{ +} + +int ProcessCommand::defaultResponseFileThreshold() const +{ + // TODO: Non-Windows platforms likely have their own limits. Investigate. + return HostOsInfo::isWindowsHost() + ? 31000 // 32000 minus "safety offset" + : -1; +} + +void ProcessCommand::getEnvironmentFromList(const QStringList &envList) +{ + m_environment.clear(); + for (const QString &env : envList) { + const int equalsIndex = env.indexOf(QLatin1Char('=')); + if (equalsIndex <= 0 || equalsIndex == env.size() - 1) + continue; + const QString &var = env.left(equalsIndex); + const QString &value = env.mid(equalsIndex + 1); + m_environment.insert(var, value); + } +} + +bool ProcessCommand::equals(const AbstractCommand *otherAbstractCommand) const +{ + if (!AbstractCommand::equals(otherAbstractCommand)) + return false; + const auto other = static_cast(otherAbstractCommand); + return m_program == other->m_program + && m_arguments == other->m_arguments + && m_workingDir == other->m_workingDir + && m_maxExitCode == other->m_maxExitCode + && m_stdoutFilterFunction == other->m_stdoutFilterFunction + && m_stderrFilterFunction == other->m_stderrFilterFunction + && m_responseFileThreshold == other->m_responseFileThreshold + && m_responseFileArgumentIndex == other->m_responseFileArgumentIndex + && m_responseFileUsagePrefix == other->m_responseFileUsagePrefix + && m_responseFileSeparator == other->m_responseFileSeparator + && m_stdoutFilePath == other->m_stdoutFilePath + && m_stderrFilePath == other->m_stderrFilePath + && m_relevantEnvVars == other->m_relevantEnvVars + && m_relevantEnvValues == other->m_relevantEnvValues + && m_environment == other->m_environment; +} + +void ProcessCommand::fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation) +{ + AbstractCommand::fillFromScriptValue(scriptValue, codeLocation); + m_program = scriptValue->property(programProperty()).toString(); + m_arguments = scriptValue->property(argumentsProperty()).toVariant().toStringList(); + m_workingDir = scriptValue->property(workingDirProperty()).toString(); + m_maxExitCode = scriptValue->property(maxExitCodeProperty()).toInt32(); + + // toString() is required, presumably due to QtScript bug that manifests itself on Windows + const QScriptValue stdoutFilterFunction + = scriptValue->property(stdoutFilterFunctionProperty()).toString(); + + m_stdoutFilterFunction = invokedSourceCode(stdoutFilterFunction); + + // toString() is required, presumably due to QtScript bug that manifests itself on Windows + const QScriptValue stderrFilterFunction + = scriptValue->property(stderrFilterFunctionProperty()).toString(); + + m_stderrFilterFunction = invokedSourceCode(stderrFilterFunction); + m_relevantEnvVars = scriptValue->property(QStringLiteral("relevantEnvironmentVariables")) + .toVariant().toStringList(); + m_responseFileThreshold = scriptValue->property(responseFileThresholdProperty()) + .toInt32(); + m_responseFileArgumentIndex = scriptValue->property(responseFileArgumentIndexProperty()) + .toInt32(); + m_responseFileUsagePrefix = scriptValue->property(responseFileUsagePrefixProperty()) + .toString(); + m_responseFileSeparator = scriptValue->property(responseFileSeparatorProperty()) + .toString(); + QStringList envList = scriptValue->property(environmentProperty()).toVariant() + .toStringList(); + getEnvironmentFromList(envList); + m_stdoutFilePath = scriptValue->property(stdoutFilePathProperty()).toString(); + m_stderrFilePath = scriptValue->property(stderrFilePathProperty()).toString(); + + m_predefinedProperties + << programProperty() + << argumentsProperty() + << workingDirProperty() + << maxExitCodeProperty() + << stdoutFilterFunctionProperty() + << stderrFilterFunctionProperty() + << responseFileThresholdProperty() + << responseFileArgumentIndexProperty() + << responseFileUsagePrefixProperty() + << environmentProperty() + << stdoutFilePathProperty() + << stderrFilePathProperty(); + applyCommandProperties(scriptValue); +} + +QStringList ProcessCommand::relevantEnvVars() const +{ + QStringList vars = m_relevantEnvVars; + if (!FileInfo::isAbsolute(program())) + vars << StringConstants::pathEnvVar(); + return vars; +} + +void ProcessCommand::addRelevantEnvValue(const QString &key, const QString &value) +{ + m_relevantEnvValues.insert(key, value); +} + +void ProcessCommand::load(PersistentPool &pool) +{ + AbstractCommand::load(pool); + serializationOp(pool); +} + +void ProcessCommand::store(PersistentPool &pool) +{ + AbstractCommand::store(pool); + serializationOp(pool); +} + +static QString currentImportScopeName(QScriptContext *context) +{ + for (; context; context = context->parentContext()) { + QScriptValue v = context->thisObject() + .property(StringConstants::importScopeNamePropertyInternal()); + if (v.isString()) + return v.toString(); + } + return {}; +} + +static QScriptValue js_JavaScriptCommand(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(!context->isCalledAsConstructor())) + return context->throwError(Tr::tr("JavaScriptCommand constructor called without new.")); + if (Q_UNLIKELY(context->argumentCount() != 0)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("JavaScriptCommand c'tor doesn't take arguments.")); + } + + static JavaScriptCommandPtr commandPrototype = JavaScriptCommand::create(); + QScriptValue cmd = js_CommandBase(context, engine); + cmd.setProperty(StringConstants::classNameProperty(), + engine->toScriptValue(StringConstants::javaScriptCommandType())); + cmd.setProperty(StringConstants::sourceCodeProperty(), + engine->toScriptValue(commandPrototype->sourceCode())); + cmd.setProperty(StringConstants::importScopeNamePropertyInternal(), + engine->toScriptValue(currentImportScopeName(context))); + + return cmd; +} + +void JavaScriptCommand::setupForJavaScript(QScriptValue targetObject) +{ + QBS_CHECK(targetObject.isObject()); + QScriptValue ctor = targetObject.engine()->newFunction(js_JavaScriptCommand, 0); + targetObject.setProperty(StringConstants::javaScriptCommandType(), ctor); +} + +JavaScriptCommand::JavaScriptCommand() = default; + +bool JavaScriptCommand::equals(const AbstractCommand *otherAbstractCommand) const +{ + if (!AbstractCommand::equals(otherAbstractCommand)) + return false; + auto const other = static_cast(otherAbstractCommand); + return m_sourceCode == other->m_sourceCode; +} + +void JavaScriptCommand::fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation) +{ + AbstractCommand::fillFromScriptValue(scriptValue, codeLocation); + + const QScriptValue importScope = scriptValue->property( + StringConstants::importScopeNamePropertyInternal()); + if (importScope.isString()) + m_scopeName = importScope.toString(); + + const QScriptValue sourceCode = scriptValue->property(StringConstants::sourceCodeProperty()); + m_sourceCode = invokedSourceCode(sourceCode); + + m_predefinedProperties << StringConstants::classNameProperty() + << StringConstants::sourceCodeProperty() + << StringConstants::importScopeNamePropertyInternal(); + applyCommandProperties(scriptValue); +} + +void JavaScriptCommand::load(PersistentPool &pool) +{ + AbstractCommand::load(pool); + serializationOp(pool); +} + +void JavaScriptCommand::store(PersistentPool &pool) +{ + AbstractCommand::store(pool); + serializationOp(pool); +} + +void CommandList::load(PersistentPool &pool) +{ + m_commands.clear(); + int count = pool.load(); + m_commands.reserve(count); + while (--count >= 0) { + const auto cmdType = pool.load(); + AbstractCommandPtr cmd; + switch (cmdType) { + case AbstractCommand::JavaScriptCommandType: + cmd = pool.load(); + break; + case AbstractCommand::ProcessCommandType: + cmd = pool.load(); + break; + default: + QBS_CHECK(false); + } + addCommand(cmd); + } +} + +void CommandList::store(PersistentPool &pool) const +{ + pool.store(m_commands.size()); + for (const AbstractCommandPtr &cmd : m_commands) { + pool.store(static_cast(cmd->type())); + pool.store(cmd); + } +} + +bool operator==(const CommandList &l1, const CommandList &l2) +{ + if (l1.size() != l2.size()) + return false; + for (int i = 0; i < l1.size(); ++i) + if (!l1.commandAt(i)->equals(l2.commandAt(i).get())) + return false; + return true; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/rulecommands.h b/src/lib/corelib/buildgraph/rulecommands.h new file mode 100644 index 00000000..725cd6d8 --- /dev/null +++ b/src/lib/corelib/buildgraph/rulecommands.h @@ -0,0 +1,237 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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_BUILDGRAPH_COMMAND_H +#define QBS_BUILDGRAPH_COMMAND_H + +#include "forward_decls.h" + +#include +#include +#include + +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class AbstractCommand +{ +public: + virtual ~AbstractCommand(); + + enum CommandType { + ProcessCommandType, + JavaScriptCommandType + }; + + static QString defaultDescription() { return {}; } + static QString defaultExtendedDescription() { return {}; } + static QString defaultHighLight() { return {}; } + static bool defaultIgnoreDryRun() { return false; } + static bool defaultIsSilent() { return false; } + static int defaultTimeout() { return -1; } + + virtual CommandType type() const = 0; + virtual bool equals(const AbstractCommand *other) const; + virtual void fillFromScriptValue(const QScriptValue *scriptValue, const CodeLocation &codeLocation); + + QString fullDescription(const QString &productName) const; + const QString description() const { return m_description; } + const QString extendedDescription() const { return m_extendedDescription; } + const QString highlight() const { return m_highlight; } + bool ignoreDryRun() const { return m_ignoreDryRun; } + bool isSilent() const { return m_silent; } + QString jobPool() const { return m_jobPool; } + CodeLocation codeLocation() const { return m_codeLocation; } + int timeout() const { return m_timeout; } + + const QVariantMap &properties() const { return m_properties; } + + virtual void load(PersistentPool &pool); + virtual void store(PersistentPool &pool); + +protected: + AbstractCommand(); + void applyCommandProperties(const QScriptValue *scriptValue); + + Set m_predefinedProperties; + +private: + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(m_description, m_extendedDescription, m_highlight, + m_ignoreDryRun, m_silent, m_codeLocation, m_jobPool, + m_timeout, m_properties); + } + + QString m_description; + QString m_extendedDescription; + QString m_highlight; + bool m_ignoreDryRun; + bool m_silent; + CodeLocation m_codeLocation; + QString m_jobPool; + int m_timeout; + QVariantMap m_properties; +}; + +class ProcessCommand : public AbstractCommand +{ +public: + static ProcessCommandPtr create() { return ProcessCommandPtr(new ProcessCommand); } + static void setupForJavaScript(QScriptValue targetObject); + + CommandType type() const override { return ProcessCommandType; } + bool equals(const AbstractCommand *otherAbstractCommand) const override; + void fillFromScriptValue(const QScriptValue *scriptValue, + const CodeLocation &codeLocation) override; + const QString program() const { return m_program; } + const QStringList arguments() const { return m_arguments; } + const QString workingDir() const { return m_workingDir; } + int maxExitCode() const { return m_maxExitCode; } + QString stdoutFilterFunction() const { return m_stdoutFilterFunction; } + QString stderrFilterFunction() const { return m_stderrFilterFunction; } + int responseFileThreshold() const { return m_responseFileThreshold; } + int responseFileArgumentIndex() const { return m_responseFileArgumentIndex; } + QString responseFileUsagePrefix() const { return m_responseFileUsagePrefix; } + QString responseFileSeparator() const { return m_responseFileSeparator; } + QProcessEnvironment environment() const { return m_environment; } + QStringList relevantEnvVars() const; + void clearRelevantEnvValues() { m_relevantEnvValues.clear(); } + void addRelevantEnvValue(const QString &key, const QString &value); + QString relevantEnvValue(const QString &key) const { return m_relevantEnvValues.value(key); } + QString stdoutFilePath() const { return m_stdoutFilePath; } + QString stderrFilePath() const { return m_stderrFilePath; } + + void load(PersistentPool &pool) override; + void store(PersistentPool &pool) override; + +private: + ProcessCommand(); + + int defaultResponseFileThreshold() const; + + void getEnvironmentFromList(const QStringList &envList); + + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(m_program, m_arguments, m_environment, m_workingDir, + m_stdoutFilterFunction, m_stderrFilterFunction, + m_responseFileUsagePrefix, m_responseFileSeparator, + m_maxExitCode, m_responseFileThreshold, + m_responseFileArgumentIndex, m_relevantEnvVars, + m_relevantEnvValues, m_stdoutFilePath, m_stderrFilePath); + } + + QString m_program; + QStringList m_arguments; + QString m_workingDir; + int m_maxExitCode; + QString m_stdoutFilterFunction; + QString m_stderrFilterFunction; + int m_responseFileThreshold; // When to use response files? In bytes of (program name + arguments). + int m_responseFileArgumentIndex; + QString m_responseFileUsagePrefix; + QString m_responseFileSeparator; + QProcessEnvironment m_environment; + QStringList m_relevantEnvVars; + QProcessEnvironment m_relevantEnvValues; + QString m_stdoutFilePath; + QString m_stderrFilePath; +}; + +class JavaScriptCommand : public AbstractCommand +{ +public: + static JavaScriptCommandPtr create() { return JavaScriptCommandPtr(new JavaScriptCommand); } + static void setupForJavaScript(QScriptValue targetObject); + + CommandType type() const override { return JavaScriptCommandType; } + bool equals(const AbstractCommand *otherAbstractCommand) const override; + void fillFromScriptValue(const QScriptValue *scriptValue, + const CodeLocation &codeLocation) override; + + const QString &scopeName() const { return m_scopeName; } + const QString &sourceCode() const { return m_sourceCode; } + void setSourceCode(const QString &str) { m_sourceCode = str; } + + void load(PersistentPool &pool) override; + void store(PersistentPool &pool) override; + +private: + JavaScriptCommand(); + + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(m_scopeName, m_sourceCode); + } + + QString m_scopeName; + QString m_sourceCode; +}; + +class CommandList +{ +public: + bool empty() const { return m_commands.empty(); } + int size() const { return m_commands.size(); } + AbstractCommandPtr commandAt(int i) const { return m_commands.at(i); } + const QList &commands() const { return m_commands; } + + void clear() { m_commands.clear(); } + void addCommand(const AbstractCommandPtr &cmd) { m_commands.push_back(cmd); } + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; +private: + QList m_commands; +}; +bool operator==(const CommandList &cl1, const CommandList &cl2); +inline bool operator!=(const CommandList &cl1, const CommandList &cl2) { return !(cl1 == cl2); } + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILDGRAPH_COMMAND_H diff --git a/src/lib/corelib/buildgraph/rulegraph.cpp b/src/lib/corelib/buildgraph/rulegraph.cpp new file mode 100644 index 00000000..23f22b7f --- /dev/null +++ b/src/lib/corelib/buildgraph/rulegraph.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** 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 "rulegraph.h" +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +RuleGraph::RuleGraph() = default; + +void RuleGraph::build(const std::vector &rules, const FileTags &productFileTags) +{ + QMap > inputFileTagToRule; + m_rules.reserve(rules.size()); + for (const RulePtr &rule : rules) { + for (const FileTag &fileTag : rule->collectedOutputFileTags()) + m_outputFileTagToRule[fileTag].push_back(rule.get()); + insert(rule); + } + + m_parents.resize(rules.size()); + m_children.resize(rules.size()); + + for (const auto &rule : qAsConst(m_rules)) { + FileTags inFileTags = rule->inputs; + inFileTags += rule->auxiliaryInputs; + inFileTags += rule->explicitlyDependsOn; + for (const FileTag &fileTag : qAsConst(inFileTags)) { + inputFileTagToRule[fileTag].push_back(rule.get()); + for (const Rule * const producingRule : m_outputFileTagToRule.value(fileTag)) { + if (!producingRule->collectedOutputFileTags().intersects( + rule->excludedInputs)) { + connect(rule.get(), producingRule); + } + } + } + } + + QList productRules; + for (const FileTag &productFileTag : productFileTags) { + QList rules = m_outputFileTagToRule.value(productFileTag); + productRules << rules; + //### check: the rule graph must be a in valid shape! + } + for (const Rule *r : qAsConst(productRules)) + m_rootRules += r->ruleGraphId; +} + +void RuleGraph::accept(RuleGraphVisitor *visitor) const +{ + const RuleConstPtr nullParent; + for (int rootIndex : qAsConst(m_rootRules)) + traverse(visitor, nullParent, m_rules.at(rootIndex)); +} + +void RuleGraph::dump() const +{ + QByteArray indent; + printf("---rule graph dump:\n"); + Set rootRules; + for (const auto &rule : qAsConst(m_rules)) + if (m_parents[rule->ruleGraphId].empty()) + rootRules += rule->ruleGraphId; + for (int idx : qAsConst(rootRules)) + dump_impl(indent, idx); +} + +void RuleGraph::dump_impl(QByteArray &indent, int rootIndex) const +{ + const RuleConstPtr r = m_rules[rootIndex]; + printf("%s", indent.constData()); + printf("%s", qPrintable(r->toString())); + printf("\n"); + + indent.append(" "); + for (int childIndex : qAsConst(m_children[rootIndex])) + dump_impl(indent, childIndex); + indent.chop(2); +} + +int RuleGraph::insert(const RulePtr &rule) +{ + rule->ruleGraphId = int(m_rules.size()); + m_rules.push_back(rule); + return rule->ruleGraphId; +} + +void RuleGraph::connect(const Rule *creatingRule, const Rule *consumingRule) +{ + int maxIndex = std::max(creatingRule->ruleGraphId, consumingRule->ruleGraphId); + if (static_cast(m_parents.size()) <= maxIndex) { + const int c = maxIndex + 1; + m_parents.resize(c); + m_children.resize(c); + } + m_parents[consumingRule->ruleGraphId].push_back(creatingRule->ruleGraphId); + m_children[creatingRule->ruleGraphId].push_back(consumingRule->ruleGraphId); +} + +void RuleGraph::traverse(RuleGraphVisitor *visitor, const RuleConstPtr &parentRule, + const RuleConstPtr &rule) const +{ + visitor->visit(parentRule, rule); + for (int childIndex : m_children.at(rule->ruleGraphId)) + traverse(visitor, rule, m_rules.at(childIndex)); + visitor->endVisit(rule); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/rulegraph.h b/src/lib/corelib/buildgraph/rulegraph.h new file mode 100644 index 00000000..4ce5ecea --- /dev/null +++ b/src/lib/corelib/buildgraph/rulegraph.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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 QBS_RULEGRAPH_H +#define QBS_RULEGRAPH_H + +#include +#include +#include + +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class RuleGraphVisitor +{ +public: + virtual ~RuleGraphVisitor() = default; + virtual void visit(const RuleConstPtr &parentRule, const RuleConstPtr &rule) = 0; + virtual void endVisit(const RuleConstPtr &rule) { Q_UNUSED(rule); } +}; + +class RuleGraph +{ +public: + RuleGraph(); + + void build(const std::vector &rules, const FileTags &productFileTag); + void accept(RuleGraphVisitor *visitor) const; + + void dump() const; + +private: + void dump_impl(QByteArray &indent, int rootIndex) const; + int insert(const RulePtr &rule); + void connect(const Rule *creatingRule, const Rule *consumingRule); + void traverse(RuleGraphVisitor *visitor, const RuleConstPtr &parentRule, + const RuleConstPtr &rule) const; + +private: + QMap > m_outputFileTagToRule; + std::vector m_rules; + std::vector< std::vector > m_parents; + std::vector< std::vector > m_children; + Set m_rootRules; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_RULEGRAPH_H diff --git a/src/lib/corelib/buildgraph/rulenode.cpp b/src/lib/corelib/buildgraph/rulenode.cpp new file mode 100644 index 00000000..0558ba14 --- /dev/null +++ b/src/lib/corelib/buildgraph/rulenode.cpp @@ -0,0 +1,321 @@ +/**************************************************************************** +** +** 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 "rulenode.h" + +#include "buildgraph.h" +#include "buildgraphvisitor.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "rulesapplicator.h" +#include "transformer.h" +#include "transformerchangetracking.h" + +#include +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +RuleNode::RuleNode() = default; + +RuleNode::~RuleNode() = default; + +void RuleNode::accept(BuildGraphVisitor *visitor) +{ + if (visitor->visit(this)) + acceptChildren(visitor); + visitor->endVisit(this); +} + +QString RuleNode::toString() const +{ + return QLatin1String("RULE ") + m_rule->toString() + QLatin1String(" [") + + (!product.expired() ? product->name : QLatin1String("")) + QLatin1Char(']') + + QLatin1String(" located at ") + m_rule->prepareScript.location().toString(); +} + +void RuleNode::apply(const Logger &logger, + const std::unordered_map &productsByName, + const std::unordered_map &projectsByName, + ApplicationResult *result) +{ + ArtifactSet allCompatibleInputs = currentInputArtifacts(); + const ArtifactSet explicitlyDependsOn + = RulesApplicator::collectExplicitlyDependsOn(m_rule.get(), product.get()); + const ArtifactSet auxiliaryInputs + = RulesApplicator::collectAuxiliaryInputs(m_rule.get(), product.get()); + const ArtifactSet addedInputs = allCompatibleInputs - m_oldInputArtifacts; + const ArtifactSet removedInputs = m_oldInputArtifacts - allCompatibleInputs; + const ArtifactSet changedInputs = changedInputArtifacts(allCompatibleInputs, + explicitlyDependsOn, + auxiliaryInputs); + bool upToDate = changedInputs.empty() && addedInputs.empty() && removedInputs.empty(); + + 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 (m_rule->multiplex) + inputs = allCompatibleInputs; + else + inputs += addedInputs; + + for (Artifact * const input : allCompatibleInputs) { + for (const Artifact * const output : input->parentArtifacts()) { + if (output->transformer->rule != m_rule) + continue; + if (prepareScriptNeedsRerun(output->transformer.get(), + output->transformer->product().get(), + productsByName, projectsByName)) { + upToDate = false; + inputs += input; + } + break; + } + if (m_rule->multiplex) + break; + } + + // Handle rules without inputs: We want to run such a rule if and only if it has not run yet + // or its transformer is not up to date regarding the prepare script. + if (upToDate && (!m_rule->declaresInputs() || !m_rule->requiresInputs) && inputs.empty()) { + bool hasOutputs = false; + for (const Artifact * const output : filterByType(parents)) { + if (output->transformer->rule != m_rule) + continue; + hasOutputs = true; + if (prepareScriptNeedsRerun(output->transformer.get(), + output->transformer->product().get(), + productsByName, projectsByName)) { + upToDate = false; + break; + } + if (m_rule->multiplex) + break; + } + if (!hasOutputs) + upToDate = false; + } + + if (upToDate) { + qCDebug(lcExec) << "rule is up to date. Skipping."; + return; + } + + const bool mustApplyRule = !inputs.empty() || !m_rule->declaresInputs() + || !m_rule->requiresInputs; + + // For a non-multiplex rule, the removal of an input always implies that the + // corresponding outputs disappear. + // For a multiplex rule, the outputs disappear only if *all* inputs are gone *and* + // the rule requires inputs. This is exactly the opposite condition of whether to + // re-apply the rule. + const bool removedInputForcesOutputRemoval = !m_rule->multiplex || !mustApplyRule; + ArtifactSet outputArtifactsToRemove; + std::vector> connectionsToBreak; + for (Artifact * const artifact : removedInputs) { + if (!artifact) // dummy artifact + continue; + for (Artifact *parent : filterByType(artifact->parents)) { + if (parent->transformer->rule != m_rule) { + // parent was not created by our rule. + continue; + } + + // parent must always have a transformer, because it's generated. + QBS_CHECK(parent->transformer); + + // artifact is a former input of m_rule and parent was created by m_rule + // the inputs of the transformer must contain artifact + QBS_CHECK(parent->transformer->inputs.contains(artifact)); + + if (removedInputForcesOutputRemoval) + outputArtifactsToRemove += parent; + else + connectionsToBreak.emplace_back(parent, artifact); + } + disconnect(this, artifact); + } + for (const auto &connection : connectionsToBreak) + disconnect(connection.first, connection.second); + if (!outputArtifactsToRemove.empty()) { + RulesApplicator::handleRemovedRuleOutputs(inputs, outputArtifactsToRemove, + result->removedArtifacts, logger); + } + + if (mustApplyRule) { + RulesApplicator applicator(product.lock(), productsByName, projectsByName, logger); + applicator.applyRule(this, inputs, explicitlyDependsOn); + result->createdArtifacts = applicator.createdArtifacts(); + result->invalidatedArtifacts = applicator.invalidatedArtifacts(); + m_lastApplicationTime = FileTime::currentTime(); + if (applicator.ruleUsesIo()) + m_needsToConsiderChangedInputs = true; + } else { + qCDebug(lcExec).noquote() << "prepare script does not need to run"; + } + m_oldInputArtifacts = allCompatibleInputs; + m_oldExplicitlyDependsOn = explicitlyDependsOn; + m_oldAuxiliaryInputs = auxiliaryInputs; + product->topLevelProject()->buildData->setDirty(); +} + +void RuleNode::load(PersistentPool &pool) +{ + BuildGraphNode::load(pool); + serializationOp(pool); +} + +void RuleNode::store(PersistentPool &pool) +{ + BuildGraphNode::store(pool); + serializationOp(pool); +} + +int RuleNode::transformerCount() const +{ + Set transformers; + for (const Artifact * const output : filterByType(parents)) + transformers.insert(output->transformer.get()); + return int(transformers.size()); +} + +ArtifactSet RuleNode::currentInputArtifacts() const +{ + ArtifactSet s; + for (const FileTag &t : qAsConst(m_rule->inputs)) { + for (Artifact *artifact : product->lookupArtifactsByFileTag(t)) { + if (artifact->isTargetOfModule()) + continue; + if (artifact->transformer && artifact->transformer->rule == m_rule) { + // Do not add compatible artifacts as inputs that were created by this rule. + // This can e.g. happen for the ["cpp", "hpp"] -> ["hpp", "cpp", "unmocable"] rule. + continue; + } + if (artifact->fileTags().intersects(m_rule->excludedInputs)) + continue; + s += artifact; + } + } + + if (m_rule->inputsFromDependencies.empty()) + return s; + for (const FileTag &t : qAsConst(m_rule->inputsFromDependencies)) { + for (Artifact *artifact : product->lookupArtifactsByFileTag(t)) { + if (!artifact->isTargetOfModule()) + continue; + if (artifact->transformer && artifact->transformer->rule == m_rule) + continue; + if (artifact->fileTags().intersects(m_rule->excludedInputs)) + continue; + s += artifact; + } + } + + for (const auto &dep : qAsConst(product->dependencies)) { + if (!dep->buildData) + continue; + for (Artifact * const a : filterByType(dep->buildData->allNodes())) { + if (a->fileTags().intersects(m_rule->inputsFromDependencies) + && !a->fileTags().intersects(m_rule->excludedInputs)) + s += a; + } + } + + return s; +} + +ArtifactSet RuleNode::changedInputArtifacts(const ArtifactSet &allCompatibleInputs, + const ArtifactSet &explicitlyDependsOn, + const ArtifactSet &auxiliaryInputs) const +{ + ArtifactSet changedInputArtifacts; + if (explicitlyDependsOn != m_oldExplicitlyDependsOn) + return allCompatibleInputs; + if (!m_needsToConsiderChangedInputs) + return changedInputArtifacts; + + for (Artifact * const artifact : explicitlyDependsOn) { + if (artifact->timestamp() > m_lastApplicationTime) + return allCompatibleInputs; + } + if (auxiliaryInputs != m_oldAuxiliaryInputs) + return allCompatibleInputs; + for (Artifact * const artifact : auxiliaryInputs) { + if (artifact->timestamp() > m_lastApplicationTime) + return allCompatibleInputs; + } + for (Artifact * const artifact : allCompatibleInputs) { + if (artifact->timestamp() > m_lastApplicationTime) + changedInputArtifacts.insert(artifact); + } + return changedInputArtifacts; +} + +void RuleNode::removeOldInputArtifact(Artifact *artifact) +{ + if (m_oldInputArtifacts.remove(artifact)) { + qCDebug(lcBuildGraph) << "remove old input" << artifact->filePath() + << "from rule" << rule()->toString(); + m_oldInputArtifacts.insert(nullptr); + } + if (m_oldExplicitlyDependsOn.remove(artifact)) { + qCDebug(lcBuildGraph) << "remove old explicitlyDependsOn" << artifact->filePath() + << "from rule" << rule()->toString(); + m_oldExplicitlyDependsOn.insert(nullptr); + } + if (m_oldAuxiliaryInputs.remove(artifact)) { + qCDebug(lcBuildGraph) << "remove old auxiliaryInput" << artifact->filePath() + << "from rule" << rule()->toString(); + m_oldAuxiliaryInputs.insert(nullptr); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/rulenode.h b/src/lib/corelib/buildgraph/rulenode.h new file mode 100644 index 00000000..cfb2039d --- /dev/null +++ b/src/lib/corelib/buildgraph/rulenode.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** 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 QBS_RULENODE_H +#define QBS_RULENODE_H + +#include "artifact.h" +#include "buildgraphnode.h" +#include "forward_decls.h" +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class Logger; + +class RuleNode : public BuildGraphNode +{ +public: + RuleNode(); + ~RuleNode() override; + + void setRule(const RuleConstPtr &rule) { m_rule = rule; } + const RuleConstPtr &rule() const { return m_rule; } + + Type type() const override { return RuleNodeType; } + void accept(BuildGraphVisitor *visitor) override; + QString toString() const override; + + struct ApplicationResult + { + NodeSet createdArtifacts; + NodeSet invalidatedArtifacts; + QStringList removedArtifacts; + }; + + void apply(const Logger &logger, + const std::unordered_map &productsByName, + const std::unordered_map &projectsByName, + ApplicationResult *result); + void removeOldInputArtifact(Artifact *artifact); + + void load(PersistentPool &pool) override; + void store(PersistentPool &pool) override; + + int transformerCount() const; + +private: + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(m_rule, m_oldInputArtifacts, m_oldExplicitlyDependsOn, + m_oldAuxiliaryInputs, m_lastApplicationTime, + m_needsToConsiderChangedInputs); + } + + ArtifactSet currentInputArtifacts() const; + ArtifactSet changedInputArtifacts(const ArtifactSet &allCompatibleInputs, + const ArtifactSet &explicitlyDependsOn, const ArtifactSet &auxiliaryInputs) const; + + RuleConstPtr m_rule; + + // These three can contain null pointers, which represent a "dummy artifact" encoding + // the information that an artifact that used to be in here has ceased to exist. + // This is okay, because no code outside this class has access to these sets, so + // we cannot break any assumptions about non-nullness. + ArtifactSet m_oldInputArtifacts; + ArtifactSet m_oldExplicitlyDependsOn; + ArtifactSet m_oldAuxiliaryInputs; + + FileTime m_lastApplicationTime; + bool m_needsToConsiderChangedInputs = false; +}; + +template<> inline bool hasDynamicType(const BuildGraphNode *n) +{ + return n->type() == BuildGraphNode::RuleNodeType; +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_RULENODE_H diff --git a/src/lib/corelib/buildgraph/rulesapplicator.cpp b/src/lib/corelib/buildgraph/rulesapplicator.cpp new file mode 100644 index 00000000..0d36e1e2 --- /dev/null +++ b/src/lib/corelib/buildgraph/rulesapplicator.cpp @@ -0,0 +1,684 @@ +#include + +/**************************************************************************** +** +** 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 "rulesapplicator.h" + +#include "buildgraph.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" +#include "qtmocscanner.h" +#include "rulecommands.h" +#include "rulenode.h" +#include "rulesevaluationcontext.h" +#include "transformer.h" +#include "transformerchangetracking.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { + +RulesApplicator::RulesApplicator( + ResolvedProductPtr product, + const std::unordered_map &productsByName, + const std::unordered_map &projectsByName, + Logger logger) + : m_product(std::move(product)) + // m_productsByName and m_projectsByName are references, cannot move-construct + , m_productsByName(productsByName) + , m_projectsByName(projectsByName) + , m_mocScanner(nullptr) + , m_logger(std::move(logger)) +{ +} + +RulesApplicator::~RulesApplicator() +{ + delete m_mocScanner; +} + +void RulesApplicator::applyRule(RuleNode *ruleNode, const ArtifactSet &inputArtifacts, + const ArtifactSet &explicitlyDependsOn) +{ + m_ruleNode = ruleNode; + m_rule = ruleNode->rule(); + QBS_CHECK(!inputArtifacts.empty() || !m_rule->declaresInputs() || !m_rule->requiresInputs); + + m_product->topLevelProject()->buildData->setDirty(); + m_createdArtifacts.clear(); + m_invalidatedArtifacts.clear(); + m_removedArtifacts.clear(); + m_explicitlyDependsOn = explicitlyDependsOn; + RulesEvaluationContext::Scope s(evalContext().get()); + + m_completeInputSet = inputArtifacts; + if (m_rule->name.startsWith(QLatin1String("QtCoreMocRule"))) { + delete m_mocScanner; + m_mocScanner = new QtMocScanner(m_product, scope()); + } + QScriptValue prepareScriptContext = engine()->newObject(); + prepareScriptContext.setPrototype(engine()->globalObject()); + setupScriptEngineForFile(engine(), m_rule->prepareScript.fileContext(), scope(), + ObserveMode::Enabled); + setupScriptEngineForProduct(engine(), m_product.get(), m_rule->module.get(), + prepareScriptContext, true); + + engine()->clearUsesIo(); + if (m_rule->multiplex) { // apply the rule once for a set of inputs + doApply(inputArtifacts, prepareScriptContext); + } else { // apply the rule once for each input + for (Artifact * const inputArtifact : inputArtifacts) { + ArtifactSet lst; + lst += inputArtifact; + doApply(lst, prepareScriptContext); + } + } + if (engine()->usesIo()) + m_ruleUsesIo = true; +} + +void RulesApplicator::handleRemovedRuleOutputs(const ArtifactSet &inputArtifacts, + const ArtifactSet &outputArtifactsToRemove, QStringList &removedArtifacts, + const Logger &logger) +{ + ArtifactSet artifactsToRemove; + const TopLevelProject *project = nullptr; + for (Artifact * const removedArtifact : outputArtifactsToRemove) { + qCDebug(lcBuildGraph).noquote() << "dynamic rule removed output artifact" + << removedArtifact->toString(); + if (!project) + project = removedArtifact->product->topLevelProject(); + project->buildData->removeArtifactAndExclusiveDependents(removedArtifact, logger, true, + &artifactsToRemove); + } + for (Artifact * const artifact : qAsConst(artifactsToRemove)) { + QBS_CHECK(!inputArtifacts.contains(artifact)); + removedArtifacts << artifact->filePath(); + delete artifact; + } +} + +ArtifactSet RulesApplicator::collectAuxiliaryInputs(const Rule *rule, + const ResolvedProduct *product) +{ + return collectAdditionalInputs(rule->auxiliaryInputs, rule, product, + CurrentProduct | Dependencies); +} + +static void copyProperty(const QString &name, const QScriptValue &src, QScriptValue dst) +{ + dst.setProperty(name, src.property(name)); +} + +static QStringList toStringList(const ArtifactSet &artifacts) +{ + QStringList lst; + for (const Artifact * const artifact : artifacts) { + const QString str = artifact->filePath() + QLatin1String(" [") + + artifact->fileTags().toStringList().join(QLatin1String(", ")) + QLatin1Char(']'); + lst << str; + } + return lst; +} + +void RulesApplicator::doApply(const ArtifactSet &inputArtifacts, QScriptValue &prepareScriptContext) +{ + evalContext()->checkForCancelation(); + for (const Artifact *inputArtifact : inputArtifacts) + QBS_CHECK(!inputArtifact->fileTags().intersects(m_rule->excludedInputs)); + + qCDebug(lcBuildGraph) << "apply rule" << m_rule->toString() + << toStringList(inputArtifacts).join(QLatin1String(",\n ")); + + std::vector> ruleArtifactArtifactMap; + QList outputArtifacts; + + m_transformer = Transformer::create(); + m_transformer->rule = m_rule; + m_transformer->inputs = inputArtifacts; + m_transformer->explicitlyDependsOn = m_explicitlyDependsOn; + m_transformer->alwaysRun = m_rule->alwaysRun; + m_oldTransformer.reset(); + + engine()->clearRequestedProperties(); + + // create the output artifacts from the set of input artifacts + m_transformer->setupInputs(prepareScriptContext); + m_transformer->setupExplicitlyDependsOn(prepareScriptContext); + copyProperty(StringConstants::inputsVar(), prepareScriptContext, scope()); + copyProperty(StringConstants::inputVar(), prepareScriptContext, scope()); + copyProperty(StringConstants::explicitlyDependsOnVar(), prepareScriptContext, scope()); + copyProperty(StringConstants::productVar(), prepareScriptContext, scope()); + copyProperty(StringConstants::projectVar(), prepareScriptContext, scope()); + if (m_rule->isDynamic()) { + outputArtifacts = runOutputArtifactsScript(inputArtifacts, + ScriptEngine::argumentList(Rule::argumentNamesForOutputArtifacts(), scope())); + } else { + Set outputFilePaths; + for (const auto &ruleArtifact : m_rule->artifacts) { + const OutputArtifactInfo outputInfo = createOutputArtifactFromRuleArtifact( + ruleArtifact, inputArtifacts, &outputFilePaths); + if (!outputInfo.artifact) + continue; + outputArtifacts.push_back(outputInfo.artifact); + ruleArtifactArtifactMap.emplace_back(ruleArtifact.get(), outputInfo); + } + if (m_rule->artifacts.empty()) { + outputArtifacts.push_back(createOutputArtifactFromRuleArtifact( + nullptr, inputArtifacts, &outputFilePaths).artifact); + } + } + + ArtifactSet newOutputs = ArtifactSet::fromList(outputArtifacts); + const ArtifactSet oldOutputs = collectOldOutputArtifacts(inputArtifacts); + handleRemovedRuleOutputs(m_completeInputSet, oldOutputs - newOutputs, m_removedArtifacts, + m_logger); + + // The inputs become children of the rule node. Generated artifacts in the same product + // already are children, because output artifacts become children of the producing + // rule node's parent rule node. + for (Artifact * const input : inputArtifacts) { + if (input->artifactType == Artifact::SourceFile || input->product != m_ruleNode->product + || input->producer()->rule()->collectedOutputFileTags().intersects( + m_ruleNode->rule()->excludedInputs)) { + connect(m_ruleNode, input); + } else { + QBS_CHECK(m_ruleNode->children.contains(input)); + } + } + + if (outputArtifacts.empty()) + return; + + for (Artifact * const outputArtifact : qAsConst(outputArtifacts)) { + for (Artifact * const dependency : qAsConst(m_transformer->explicitlyDependsOn)) + connect(outputArtifact, dependency); + } + + if (inputArtifacts != m_transformer->inputs) + m_transformer->setupInputs(prepareScriptContext); + + // change the transformer outputs according to the bindings in Artifact + QScriptValue scriptValue; + if (!ruleArtifactArtifactMap.empty()) + engine()->setGlobalObject(prepareScriptContext); + for (auto it = ruleArtifactArtifactMap.crbegin(), end = ruleArtifactArtifactMap.crend(); + it != end; ++it) { + const RuleArtifact *ra = it->first; + if (ra->bindings.empty()) + continue; + + // expose attributes of this artifact + const OutputArtifactInfo outputInfo = it->second; + Artifact *outputArtifact = outputInfo.artifact; + outputArtifact->properties = outputArtifact->properties->clone(); + + scope().setProperty(StringConstants::fileNameProperty(), + engine()->toScriptValue(outputArtifact->filePath())); + scope().setProperty(StringConstants::fileTagsProperty(), + toScriptValue(engine(), outputArtifact->fileTags().toStringList())); + + QVariantMap artifactModulesCfg = outputArtifact->properties->value(); + for (const auto &binding : ra->bindings) { + scriptValue = engine()->evaluate(binding.code); + if (Q_UNLIKELY(engine()->hasErrorOrException(scriptValue))) { + QString msg = QStringLiteral("evaluating rule binding '%1': %2"); + throw ErrorInfo(msg.arg(binding.name.join(QLatin1Char('.')), + engine()->lastErrorString(scriptValue)), + engine()->lastErrorLocation(scriptValue, binding.location)); + } + const QVariant value = scriptValue.toVariant(); + setConfigProperty(artifactModulesCfg, binding.name, value); + outputArtifact->pureProperties.emplace_back(binding.name, value); + } + outputArtifact->properties->setValue(artifactModulesCfg); + if (!outputInfo.newlyCreated && (outputArtifact->fileTags() != outputInfo.oldFileTags + || outputArtifact->properties->value() != outputInfo.oldProperties)) { + invalidateArtifactAsRuleInputIfNecessary(outputArtifact); + } + } + if (!ruleArtifactArtifactMap.empty()) + engine()->setGlobalObject(prepareScriptContext.prototype()); + + m_transformer->setupOutputs(prepareScriptContext); + m_transformer->createCommands(engine(), m_rule->prepareScript, + ScriptEngine::argumentList(Rule::argumentNamesForPrepare(), prepareScriptContext)); + if (Q_UNLIKELY(m_transformer->commands.empty())) + throw ErrorInfo(Tr::tr("There is a rule without commands: %1.") + .arg(m_rule->toString()), m_rule->prepareScript.location()); + if (!m_oldTransformer || m_oldTransformer->outputs != m_transformer->outputs + || m_oldTransformer->inputs != m_transformer->inputs + || m_oldTransformer->explicitlyDependsOn != m_transformer->explicitlyDependsOn + || m_oldTransformer->commands != m_transformer->commands + || commandsNeedRerun(m_transformer.get(), m_product.get(), m_productsByName, + m_projectsByName)) { + for (Artifact * const output : qAsConst(outputArtifacts)) { + output->clearTimestamp(); + m_invalidatedArtifacts += output; + } + } + m_transformer->commandsNeedChangeTracking = false; +} + +ArtifactSet RulesApplicator::collectOldOutputArtifacts(const ArtifactSet &inputArtifacts) const +{ + ArtifactSet result; + for (Artifact * const a : inputArtifacts) { + for (Artifact *p : a->parentArtifacts()) { + QBS_CHECK(p->transformer); + if (p->transformer->rule == m_rule && p->transformer->inputs.contains(a)) + result += p; + } + } + return result; +} + +ArtifactSet RulesApplicator::collectAdditionalInputs(const FileTags &tags, const Rule *rule, + const ResolvedProduct *product, + InputsSources inputsSources) +{ + ArtifactSet artifacts; + for (const FileTag &fileTag : tags) { + for (Artifact *dependency : product->lookupArtifactsByFileTag(fileTag)) { + // Skip excluded inputs. + if (dependency->fileTags().intersects(rule->excludedInputs)) + continue; + + // Two cases are considered: + // 1) An artifact is considered a dependency when it's part of the current product. + // 2) An artifact marked with filesAreTargets: true inside a Group inside of a + // Module also ends up in the results returned by product->lookupArtifactsByFileTag, + // so it should be considered conceptually as a "dependent product artifact". + if ((inputsSources.testFlag(CurrentProduct) && !dependency->isTargetOfModule()) + || (inputsSources.testFlag(Dependencies) && dependency->isTargetOfModule())) { + artifacts << dependency; + } + } + + if (inputsSources.testFlag(Dependencies)) { + for (const auto &depProduct : product->dependencies) { + for (Artifact * const ta : depProduct->targetArtifacts()) { + if (ta->fileTags().contains(fileTag) + && !ta->fileTags().intersects(rule->excludedInputs)) { + artifacts << ta; + } + } + } + } + } + return artifacts; +} + +ArtifactSet RulesApplicator::collectExplicitlyDependsOn(const Rule *rule, + const ResolvedProduct *product) +{ + ArtifactSet first = collectAdditionalInputs( + rule->explicitlyDependsOn, rule, product, CurrentProduct); + ArtifactSet second = collectAdditionalInputs( + rule->explicitlyDependsOnFromDependencies, rule, product, Dependencies); + return first.unite(second); +} + +RulesApplicator::OutputArtifactInfo RulesApplicator::createOutputArtifactFromRuleArtifact( + const RuleArtifactConstPtr &ruleArtifact, const ArtifactSet &inputArtifacts, + Set *outputFilePaths) +{ + QString outputPath; + FileTags fileTags; + bool alwaysUpdated; + if (ruleArtifact) { + QScriptValue scriptValue = engine()->evaluate(ruleArtifact->filePath, + ruleArtifact->filePathLocation.filePath(), + ruleArtifact->filePathLocation.line()); + if (Q_UNLIKELY(engine()->hasErrorOrException(scriptValue))) + throw engine()->lastError(scriptValue, ruleArtifact->filePathLocation); + outputPath = scriptValue.toString(); + fileTags = ruleArtifact->fileTags; + alwaysUpdated = ruleArtifact->alwaysUpdated; + } else { + outputPath = QStringLiteral("__dummyoutput__"); + QByteArray hashInput = m_rule->toString().toLatin1(); + for (const Artifact * const input : inputArtifacts) + hashInput += input->filePath().toLatin1(); + outputPath += QLatin1String(QCryptographicHash::hash(hashInput, QCryptographicHash::Sha1) + .toHex().left(16)); + fileTags = m_rule->outputFileTags; + alwaysUpdated = false; + } + outputPath = FileInfo::resolvePath(m_product->buildDirectory(), outputPath); + if (Q_UNLIKELY(!outputFilePaths->insert(outputPath).second)) { + throw ErrorInfo(Tr::tr("Rule %1 already created '%2'.") + .arg(m_rule->toString(), outputPath)); + } + return createOutputArtifact(outputPath, fileTags, alwaysUpdated, inputArtifacts); +} + +RulesApplicator::OutputArtifactInfo RulesApplicator::createOutputArtifact(const QString &filePath, + const FileTags &fileTags, bool alwaysUpdated, const ArtifactSet &inputArtifacts) +{ + QString outputPath = filePath; + // don't let the output artifact "escape" its build dir + outputPath.replace(StringConstants::dotDot(), QStringLiteral("dotdot")); + outputPath = resolveOutPath(outputPath); + + if (m_rule->isDynamic()) { + const Set undeclaredTags = fileTags - m_rule->collectedOutputFileTags(); + if (!undeclaredTags.empty()) { + throw ErrorInfo(Tr::tr("Artifact '%1' has undeclared file tags [\"%2\"].") + .arg(outputPath, undeclaredTags.toStringList() + .join(QLatin1String("\",\""))), + m_rule->prepareScript.location()); + } + } + + OutputArtifactInfo outputInfo; + Artifact *& outputArtifact = outputInfo.artifact; + outputArtifact = lookupArtifact(m_product, outputPath); + outputInfo.newlyCreated = !outputArtifact; + if (outputArtifact) { + const Transformer * const transformer = outputArtifact->transformer.get(); + if (transformer && transformer->rule != m_rule) { + QString e = Tr::tr("Conflicting rules for producing %1 %2 \n") + .arg(outputArtifact->filePath(), + QLatin1Char('[') + + outputArtifact->fileTags().toStringList().join(QLatin1String(", ")) + + QLatin1Char(']')); + QString str = QLatin1Char('[') + m_rule->inputs.toStringList().join(QLatin1String(", ")) + + QLatin1String("] -> [") + outputArtifact->fileTags().toStringList() + .join(QLatin1String(", ")) + QLatin1Char(']'); + + e += QStringLiteral(" while trying to apply: %1:%2:%3 %4\n") + .arg(m_rule->prepareScript.location().filePath()) + .arg(m_rule->prepareScript.location().line()) + .arg(m_rule->prepareScript.location().column()) + .arg(str); + + e += QStringLiteral(" was already defined in: %1:%2:%3 %4\n") + .arg(transformer->rule->prepareScript.location().filePath()) + .arg(transformer->rule->prepareScript.location().line()) + .arg(transformer->rule->prepareScript.location().column()) + .arg(str); + + throw ErrorInfo(e); + } + if (transformer && !m_rule->multiplex && transformer->inputs != inputArtifacts) { + QBS_CHECK(inputArtifacts.size() == 1); + QBS_CHECK(transformer->inputs.size() == 1); + ErrorInfo error(Tr::tr("Conflicting instances of rule '%1':").arg(m_rule->toString()), + m_rule->prepareScript.location()); + error.append(Tr::tr("Output artifact '%1' is to be produced from input " + "artifacts '%2' and '%3', but the rule is not a multiplex rule.") + .arg(outputArtifact->filePath(), + (*transformer->inputs.cbegin())->filePath(), + (*inputArtifacts.cbegin())->filePath())); + throw error; + } + m_transformer->rescueChangeTrackingData(outputArtifact->transformer); + m_oldTransformer = outputArtifact->transformer; + outputInfo.oldFileTags = outputArtifact->fileTags(); + outputInfo.oldProperties = outputArtifact->properties->value(); + } else { + std::unique_ptr newArtifact(new Artifact); + newArtifact->artifactType = Artifact::Generated; + newArtifact->setFilePath(outputPath); + insertArtifact(m_product, newArtifact.get()); + m_createdArtifacts += newArtifact.get(); + outputArtifact = newArtifact.release(); + qCDebug(lcExec).noquote() << "rule created" << outputArtifact->toString(); + connect(outputArtifact, m_ruleNode); + } + + outputArtifact->alwaysUpdated = alwaysUpdated; + outputArtifact->pureFileTags = fileTags; + provideFullFileTagsAndProperties(outputArtifact); + if (outputInfo.newlyCreated || outputInfo.oldFileTags != outputArtifact->fileTags()) { + for (RuleNode * const parentRule : filterByType(m_ruleNode->parents)) + connect(parentRule, outputArtifact); + } + + for (Artifact * const inputArtifact : inputArtifacts) { + QBS_CHECK(outputArtifact != inputArtifact); + connect(outputArtifact, inputArtifact); + } + + outputArtifact->transformer = m_transformer; + m_transformer->outputs.insert(outputArtifact); + QBS_CHECK(m_rule->multiplex || m_transformer->inputs.size() == 1); + + return outputInfo; +} + +class RuleOutputArtifactsException : public ErrorInfo +{ +public: + using ErrorInfo::ErrorInfo; +}; + +QList RulesApplicator::runOutputArtifactsScript(const ArtifactSet &inputArtifacts, + const QScriptValueList &args) +{ + QList lst; + QScriptValue fun = engine()->evaluate(m_rule->outputArtifactsScript.sourceCode(), + m_rule->outputArtifactsScript.location().filePath(), + m_rule->outputArtifactsScript.location().line()); + if (!fun.isFunction()) + throw ErrorInfo(QStringLiteral("Function expected."), + m_rule->outputArtifactsScript.location()); + QScriptValue res = fun.call(QScriptValue(), args); + engine()->releaseResourcesOfScriptObjects(); + if (engine()->hasErrorOrException(res)) + throw engine()->lastError(res, m_rule->outputArtifactsScript.location()); + if (!res.isArray()) + throw ErrorInfo(Tr::tr("Rule.outputArtifacts must return an array of objects."), + m_rule->outputArtifactsScript.location()); + const quint32 c = res.property(StringConstants::lengthProperty()).toUInt32(); + for (quint32 i = 0; i < c; ++i) { + try { + lst.push_back(createOutputArtifactFromScriptValue(res.property(i), inputArtifacts)); + } catch (const RuleOutputArtifactsException &roae) { + ErrorInfo ei = roae; + ei.prepend(Tr::tr("Error in Rule.outputArtifacts[%1]").arg(i), + m_rule->outputArtifactsScript.location()); + throw ei; + } + } + return lst; +} + +class ArtifactBindingsExtractor +{ + struct Entry + { + Entry(QString module, QString name, QVariant value) + : module(std::move(module)), name(std::move(name)), value(std::move(value)) + {} + + QString module; + QString name; + QVariant value; + }; + std::vector m_propertyValues; + + static Set getArtifactItemPropertyNames() + { + Set s; + const auto properties = BuiltinDeclarations::instance().declarationsForType( + ItemType::Artifact).properties(); + for (const PropertyDeclaration &pd : properties) { + s.insert(pd.name()); + } + s.insert(StringConstants::explicitlyDependsOnProperty()); + return s; + } + + void extractPropertyValues(const QScriptValue &obj, const QString &moduleName = QString()) + { + QScriptValueIterator svit(obj); + while (svit.hasNext()) { + svit.next(); + const QString name = svit.name(); + if (moduleName.isEmpty()) { + // Ignore property names that are part of the Artifact item. + static const Set artifactItemPropertyNames + = getArtifactItemPropertyNames(); + if (artifactItemPropertyNames.contains(name)) + continue; + } + + const QScriptValue value = svit.value(); + if (value.isObject() && !value.isArray() && !value.isError() && !value.isRegExp()) { + QString newModuleName; + if (!moduleName.isEmpty()) + newModuleName.append(moduleName + QLatin1Char('.')); + newModuleName.append(name); + extractPropertyValues(value, newModuleName); + } else { + m_propertyValues.emplace_back(moduleName, name, value.toVariant()); + } + } + } +public: + void apply(Artifact *outputArtifact, const QScriptValue &obj) + { + extractPropertyValues(obj); + if (m_propertyValues.empty()) + return; + + outputArtifact->properties = outputArtifact->properties->clone(); + QVariantMap artifactCfg = outputArtifact->properties->value(); + for (const auto &e : m_propertyValues) { + const QStringList key{e.module, e.name}; + setConfigProperty(artifactCfg, key, e.value); + outputArtifact->pureProperties.emplace_back(key, e.value); + } + outputArtifact->properties->setValue(artifactCfg); + } +}; + +Artifact *RulesApplicator::createOutputArtifactFromScriptValue(const QScriptValue &obj, + const ArtifactSet &inputArtifacts) +{ + if (!obj.isObject()) { + throw ErrorInfo(Tr::tr("Elements of the Rule.outputArtifacts array must be " + "of Object type."), m_rule->outputArtifactsScript.location()); + } + const QString unresolvedFilePath + = obj.property(StringConstants::filePathProperty()).toVariant().toString(); + if (unresolvedFilePath.isEmpty()) { + throw RuleOutputArtifactsException( + Tr::tr("Property filePath must be a non-empty string.")); + } + const QString filePath = FileInfo::resolvePath(m_product->buildDirectory(), unresolvedFilePath); + const FileTags fileTags = FileTags::fromStringList( + obj.property(StringConstants::fileTagsProperty()).toVariant().toStringList()); + const QVariant alwaysUpdatedVar + = obj.property(StringConstants::alwaysUpdatedProperty()).toVariant(); + const bool alwaysUpdated = alwaysUpdatedVar.isValid() ? alwaysUpdatedVar.toBool() : true; + OutputArtifactInfo outputInfo = createOutputArtifact(filePath, fileTags, alwaysUpdated, + inputArtifacts); + if (outputInfo.artifact->fileTags().empty()) { + // Check the file tags after file taggers were run. + throw RuleOutputArtifactsException( + Tr::tr("Property fileTags for artifact '%1' must be a non-empty string list. " + "Alternatively, a FileTagger can be provided.") + .arg(unresolvedFilePath)); + } + const FileTags explicitlyDependsOn = FileTags::fromStringList( + obj.property(StringConstants::explicitlyDependsOnProperty()) + .toVariant().toStringList()); + for (const FileTag &tag : explicitlyDependsOn) { + for (Artifact * const dependency : m_product->lookupArtifactsByFileTag(tag)) + connect(outputInfo.artifact, dependency); + } + ArtifactBindingsExtractor().apply(outputInfo.artifact, obj); + if (!outputInfo.newlyCreated && (outputInfo.artifact->fileTags() != outputInfo.oldFileTags + || outputInfo.artifact->properties->value() != outputInfo.oldProperties)) { + invalidateArtifactAsRuleInputIfNecessary(outputInfo.artifact); + } + return outputInfo.artifact; +} + +QString RulesApplicator::resolveOutPath(const QString &path) const +{ + QString buildDir = m_product->topLevelProject()->buildDirectory; + QString result = FileInfo::resolvePath(buildDir, path); + result = QDir::cleanPath(result); + return result; +} + +const RulesEvaluationContextPtr &RulesApplicator::evalContext() const +{ + return m_product->topLevelProject()->buildData->evaluationContext; +} + +ScriptEngine *RulesApplicator::engine() const +{ + return evalContext()->engine(); +} + +QScriptValue RulesApplicator::scope() const +{ + return evalContext()->scope(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/rulesapplicator.h b/src/lib/corelib/buildgraph/rulesapplicator.h new file mode 100644 index 00000000..da781501 --- /dev/null +++ b/src/lib/corelib/buildgraph/rulesapplicator.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** 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 QBS_RULESAPPLICATOR_H +#define QBS_RULESAPPLICATOR_H + +#include "artifact.h" +#include "forward_decls.h" +#include "nodeset.h" +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { +class BuildGraphNode; +class QtMocScanner; +class ScriptEngine; + +class RulesApplicator +{ +public: + RulesApplicator(ResolvedProductPtr product, + const std::unordered_map &productsByName, + const std::unordered_map &projectsByName, + Logger logger); + ~RulesApplicator(); + + const NodeSet &createdArtifacts() const { return m_createdArtifacts; } + const NodeSet &invalidatedArtifacts() const { return m_invalidatedArtifacts; } + QStringList removedArtifacts() const { return m_removedArtifacts; } + bool ruleUsesIo() const { return m_ruleUsesIo; } + + void applyRule(RuleNode *ruleNode, const ArtifactSet &inputArtifacts, + const ArtifactSet &explicitlyDependsOn); + static void handleRemovedRuleOutputs(const ArtifactSet &inputArtifacts, + const ArtifactSet &artifactsToRemove, QStringList &removedArtifacts, + const Logger &logger); + static ArtifactSet collectAuxiliaryInputs(const Rule *rule, const ResolvedProduct *product); + static ArtifactSet collectExplicitlyDependsOn(const Rule *rule, const ResolvedProduct *product); + + enum InputsSourceFlag { CurrentProduct = 1, Dependencies = 2 }; + Q_DECLARE_FLAGS(InputsSources, InputsSourceFlag) + +private: + void doApply(const ArtifactSet &inputArtifacts, QScriptValue &prepareScriptContext); + ArtifactSet collectOldOutputArtifacts(const ArtifactSet &inputArtifacts) const; + + struct OutputArtifactInfo { + Artifact *artifact = nullptr; + bool newlyCreated = false; + FileTags oldFileTags; + QVariantMap oldProperties; + }; + OutputArtifactInfo createOutputArtifactFromRuleArtifact( + const RuleArtifactConstPtr &ruleArtifact, const ArtifactSet &inputArtifacts, + Set *outputFilePaths); + OutputArtifactInfo createOutputArtifact(const QString &filePath, const FileTags &fileTags, + bool alwaysUpdated, const ArtifactSet &inputArtifacts); + QList runOutputArtifactsScript(const ArtifactSet &inputArtifacts, + const QScriptValueList &args); + Artifact *createOutputArtifactFromScriptValue(const QScriptValue &obj, + const ArtifactSet &inputArtifacts); + QString resolveOutPath(const QString &path) const; + const RulesEvaluationContextPtr &evalContext() const; + ScriptEngine *engine() const; + QScriptValue scope() const; + + static ArtifactSet collectAdditionalInputs(const FileTags &tags, + const Rule *rule, const ResolvedProduct *product, + InputsSources inputsSources); + + const ResolvedProductPtr m_product; + const std::unordered_map &m_productsByName; + const std::unordered_map &m_projectsByName; + ArtifactSet m_explicitlyDependsOn; + NodeSet m_createdArtifacts; + NodeSet m_invalidatedArtifacts; + QStringList m_removedArtifacts; + RuleNode *m_ruleNode = nullptr; + RuleConstPtr m_rule; + ArtifactSet m_completeInputSet; + TransformerPtr m_transformer; + TransformerConstPtr m_oldTransformer; + QtMocScanner *m_mocScanner; + Logger m_logger; + bool m_ruleUsesIo = false; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(RulesApplicator::InputsSources) + +} // namespace Internal +} // namespace qbs + +#endif // QBS_RULESAPPLICATOR_H diff --git a/src/lib/corelib/buildgraph/rulesevaluationcontext.cpp b/src/lib/corelib/buildgraph/rulesevaluationcontext.cpp new file mode 100644 index 00000000..6ae23032 --- /dev/null +++ b/src/lib/corelib/buildgraph/rulesevaluationcontext.cpp @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** 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 "rulesevaluationcontext.h" + +#include "artifact.h" +#include "rulecommands.h" +#include "transformer.h" +#include +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +RulesEvaluationContext::RulesEvaluationContext(Logger logger) + : m_logger(std::move(logger)), + m_engine(ScriptEngine::create(m_logger, EvalContext::RuleExecution)), + m_observer(nullptr), + m_initScopeCalls(0) +{ + m_prepareScriptScope = m_engine->newObject(); + m_prepareScriptScope.setPrototype(m_engine->globalObject()); + ProcessCommand::setupForJavaScript(m_prepareScriptScope); + JavaScriptCommand::setupForJavaScript(m_prepareScriptScope); +} + +RulesEvaluationContext::~RulesEvaluationContext() +{ + delete m_engine; +} + +void RulesEvaluationContext::initializeObserver(const QString &description, int maximumProgress) +{ + if (m_observer) + m_observer->initialize(description, maximumProgress); +} + +void RulesEvaluationContext::incrementProgressValue() +{ + if (m_observer) + m_observer->incrementProgressValue(); +} + +void RulesEvaluationContext::checkForCancelation() +{ + if (Q_UNLIKELY(m_observer && m_observer->canceled())) + throw ErrorInfo(Tr::tr("Build canceled.")); +} + +void RulesEvaluationContext::initScope() +{ + if (m_initScopeCalls++ > 0) + return; + + m_engine->setActive(true); + m_scope = m_engine->newObject(); + m_scope.setPrototype(m_prepareScriptScope); + m_engine->setGlobalObject(m_scope); +} + +void RulesEvaluationContext::cleanupScope() +{ + QBS_CHECK(m_initScopeCalls > 0); + if (--m_initScopeCalls > 0) + return; + + m_scope = QScriptValue(); + m_engine->setGlobalObject(m_prepareScriptScope.prototype()); + m_engine->setActive(false); +} + +RulesEvaluationContext::Scope::Scope(RulesEvaluationContext *evalContext) + : m_evalContext(evalContext) +{ + evalContext->initScope(); +} + +RulesEvaluationContext::Scope::~Scope() +{ + m_evalContext->cleanupScope(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/rulesevaluationcontext.h b/src/lib/corelib/buildgraph/rulesevaluationcontext.h new file mode 100644 index 00000000..a5d81ce6 --- /dev/null +++ b/src/lib/corelib/buildgraph/rulesevaluationcontext.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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 QBS_RULESEVALUATIONCONTEXT_H +#define QBS_RULESEVALUATIONCONTEXT_H + +#include +#include + +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { +class ProgressObserver; +class ScriptEngine; + +class RulesEvaluationContext +{ +public: + RulesEvaluationContext(Logger logger); + ~RulesEvaluationContext(); + + class Scope + { + public: + Scope(RulesEvaluationContext *evalContext); + ~Scope(); + + private: + RulesEvaluationContext * const m_evalContext; + }; + + ScriptEngine *engine() const { return m_engine; } + QScriptValue scope() const { return m_scope; } + + void setObserver(ProgressObserver *observer) { m_observer = observer; } + ProgressObserver *observer() const { return m_observer; } + void initializeObserver(const QString &description, int maximumProgress); + void incrementProgressValue(); + void checkForCancelation(); + +private: + friend class Scope; + + void initScope(); + void cleanupScope(); + + Logger m_logger; + ScriptEngine * const m_engine; + ProgressObserver *m_observer; + unsigned int m_initScopeCalls; + QScriptValue m_scope; + QScriptValue m_prepareScriptScope; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_RULESEVALUATIONCONTEXT_H diff --git a/src/lib/corelib/buildgraph/scriptclasspropertyiterator.h b/src/lib/corelib/buildgraph/scriptclasspropertyiterator.h new file mode 100644 index 00000000..b5707223 --- /dev/null +++ b/src/lib/corelib/buildgraph/scriptclasspropertyiterator.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_SCRIPTCLASSPROPERTYITERATOR_H +#define QBS_SCRIPTCLASSPROPERTYITERATOR_H + +#include + +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class ScriptClassPropertyIterator : public QScriptClassPropertyIterator +{ +public: + ScriptClassPropertyIterator(const QScriptValue &object, const QVariantMap &properties, + std::vector additionalProperties) + : QScriptClassPropertyIterator(object), + m_it(properties), + m_additionalProperties(std::move(additionalProperties)) + { + } + +private: + bool hasNext() const override + { + return m_it.hasNext() || m_index < int(m_additionalProperties.size()) - 1; + } + bool hasPrevious() const override { return m_index > -1 || m_it.hasPrevious(); } + void toFront() override { m_it.toFront(); m_index = -1; } + void toBack() override { m_it.toBack(); m_index = int(m_additionalProperties.size()) - 1; } + + void next() override + { + QBS_ASSERT(hasNext(), return); + if (m_it.hasNext()) + m_it.next(); + else + ++m_index; + } + + void previous() override + { + QBS_ASSERT(hasPrevious(), return); + if (m_index >= 0) + --m_index; + if (m_index == -1) + m_it.previous(); + } + + QScriptString name() const override + { + const QString theName = m_index >= 0 && m_index < int(m_additionalProperties.size()) + ? m_additionalProperties.at(m_index) + : m_it.key(); + return object().engine()->toStringHandle(theName); + } + + QMapIterator m_it; + const std::vector m_additionalProperties; + int m_index = -1; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_SCRIPTCLASSPROPERTYITERATOR_H diff --git a/src/lib/corelib/buildgraph/timestampsupdater.cpp b/src/lib/corelib/buildgraph/timestampsupdater.cpp new file mode 100644 index 00000000..3f5279dd --- /dev/null +++ b/src/lib/corelib/buildgraph/timestampsupdater.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** 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 "timestampsupdater.h" + +#include "artifact.h" +#include "artifactvisitor.h" +#include "productbuilddata.h" +#include "projectbuilddata.h" + +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class TimestampsUpdateVisitor : public ArtifactVisitor +{ +public: + TimestampsUpdateVisitor() + : ArtifactVisitor(Artifact::Generated), m_now(FileTime::currentTime()) {} + + void visitProduct(const ResolvedProductConstPtr &product) + { + QBS_CHECK(product->buildData); + ArtifactVisitor::visitProduct(product); + + // For target artifacts, we have to update the on-disk timestamp, because + // the executor will look at it. + for (Artifact * const targetArtifact : product->targetArtifacts()) { + if (FileInfo(targetArtifact->filePath()).exists()) + QFile(targetArtifact->filePath()).open(QIODevice::WriteOnly | QIODevice::Append); + } + } + +private: + void doVisit(Artifact *artifact) override + { + if (FileInfo(artifact->filePath()).exists()) + artifact->setTimestamp(m_now); + } + + FileTime m_now; +}; + +void TimestampsUpdater::updateTimestamps(const TopLevelProjectPtr &project, + const QVector &products, const Logger &logger) +{ + TimestampsUpdateVisitor v; + for (const ResolvedProductPtr &product : products) + v.visitProduct(product); + if (!products.empty()) + project->buildData->setDirty(); + project->store(logger); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/timestampsupdater.h b/src/lib/corelib/buildgraph/timestampsupdater.h new file mode 100644 index 00000000..8184ca70 --- /dev/null +++ b/src/lib/corelib/buildgraph/timestampsupdater.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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 TIMESTAMPSUPDATER_H +#define TIMESTAMPSUPDATER_H + +#include + +#include + +namespace qbs { +namespace Internal { +class Logger; + +class TimestampsUpdater +{ +public: + void updateTimestamps(const TopLevelProjectPtr &project, + const QVector &products, const Logger &logger); +}; + +} // namespace Internal +} // namespace qbs + +#endif // TIMESTAMPSUPDATER_H diff --git a/src/lib/corelib/buildgraph/transformer.cpp b/src/lib/corelib/buildgraph/transformer.cpp new file mode 100644 index 00000000..29f2bcdf --- /dev/null +++ b/src/lib/corelib/buildgraph/transformer.cpp @@ -0,0 +1,331 @@ +/**************************************************************************** +** +** 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 "transformer.h" + +#include "artifact.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace qbs { +namespace Internal { + +Transformer::Transformer() : alwaysRun(false) +{ +} + +Transformer::~Transformer() = default; + +static QScriptValue js_baseName(QScriptContext *ctx, QScriptEngine *engine, + const Artifact *artifact) +{ + Q_UNUSED(ctx); + Q_UNUSED(engine); + return {FileInfo::baseName(artifact->filePath())}; +} + +static QScriptValue js_completeBaseName(QScriptContext *ctx, QScriptEngine *engine, + const Artifact *artifact) +{ + Q_UNUSED(ctx); + Q_UNUSED(engine); + return {FileInfo::completeBaseName(artifact->filePath())}; +} + +static QScriptValue js_baseDir(QScriptContext *ctx, QScriptEngine *engine, + const Artifact *artifact) +{ + Q_UNUSED(ctx); + Q_UNUSED(engine); + QString basedir; + if (artifact->artifactType == Artifact::SourceFile) { + QDir sourceDir(artifact->product->sourceDirectory); + basedir = FileInfo::path(sourceDir.relativeFilePath(artifact->filePath())); + } else { + QDir buildDir(artifact->product->buildDirectory()); + basedir = FileInfo::path(buildDir.relativeFilePath(artifact->filePath())); + } + return basedir; +} + +static QScriptValue js_children(QScriptContext *ctx, QScriptEngine *engine, const Artifact *artifact) +{ + Q_UNUSED(ctx); + QScriptValue sv = engine->newArray(); + uint idx = 0; + for (const Artifact *child : artifact->childArtifacts()) { + sv.setProperty(idx++, Transformer::translateFileConfig(static_cast(engine), + child, QString())); + } + return sv; +} + +static void setArtifactProperty(QScriptValue &obj, const QString &name, + QScriptValue (*func)(QScriptContext *, QScriptEngine *, const Artifact *), + const Artifact *artifact) +{ + obj.setProperty(name, static_cast(obj.engine())->newFunction(func, artifact), + QScriptValue::PropertyGetter); +} + +QScriptValue Transformer::translateFileConfig(ScriptEngine *scriptEngine, const Artifact *artifact, + const QString &defaultModuleName) +{ + QScriptValue obj = scriptEngine->newObject(); + attachPointerTo(obj, artifact); + ModuleProperties::init(obj, artifact); + obj.setProperty(StringConstants::fileNameProperty(), artifact->fileName()); + obj.setProperty(StringConstants::filePathProperty(), artifact->filePath()); + setArtifactProperty(obj, StringConstants::baseNameProperty(), js_baseName, artifact); + setArtifactProperty(obj, StringConstants::completeBaseNameProperty(), js_completeBaseName, + artifact); + setArtifactProperty(obj, QStringLiteral("baseDir"), js_baseDir, artifact); + setArtifactProperty(obj, QStringLiteral("children"), js_children, artifact); + const QStringList fileTags = sorted(artifact->fileTags().toStringList()); + scriptEngine->setObservedProperty(obj, StringConstants::fileTagsProperty(), + scriptEngine->toScriptValue(fileTags)); + scriptEngine->observer()->addArtifactId(obj.objectId()); + if (!defaultModuleName.isEmpty()) + obj.setProperty(StringConstants::moduleNameProperty(), defaultModuleName); + return obj; +} + +static bool compareByFilePath(const Artifact *a1, const Artifact *a2) +{ + return a1->filePath() < a2->filePath(); +} + +QScriptValue Transformer::translateInOutputs(ScriptEngine *scriptEngine, + const ArtifactSet &artifacts, + const QString &defaultModuleName) +{ + using TagArtifactsMap = QMap>; + TagArtifactsMap tagArtifactsMap; + for (Artifact *artifact : artifacts) + for (const FileTag &fileTag : artifact->fileTags()) + tagArtifactsMap[fileTag.toString()].push_back(artifact); + for (TagArtifactsMap::Iterator it = tagArtifactsMap.begin(); it != tagArtifactsMap.end(); ++it) + std::sort(it.value().begin(), it.value().end(), compareByFilePath); + + QScriptValue jsTagFiles = scriptEngine->newObject(); + for (TagArtifactsMap::const_iterator tag = tagArtifactsMap.constBegin(); tag != tagArtifactsMap.constEnd(); ++tag) { + const QList &artifacts = tag.value(); + QScriptValue jsFileConfig = scriptEngine->newArray(artifacts.size()); + int i = 0; + for (Artifact * const artifact : artifacts) { + jsFileConfig.setProperty(i++, translateFileConfig(scriptEngine, artifact, + defaultModuleName)); + } + jsTagFiles.setProperty(tag.key(), jsFileConfig); + } + + return jsTagFiles; +} + +ResolvedProductPtr Transformer::product() const +{ + if (outputs.empty()) + return {}; + return (*outputs.cbegin())->product.lock(); +} + +void Transformer::setupInputs(QScriptValue targetScriptValue, const ArtifactSet &inputs, + const QString &defaultModuleName) +{ + const auto scriptEngine = static_cast(targetScriptValue.engine()); + QScriptValue scriptValue = translateInOutputs(scriptEngine, inputs, defaultModuleName); + targetScriptValue.setProperty(StringConstants::inputsVar(), scriptValue); + QScriptValue inputScriptValue; + if (inputs.size() == 1) { + Artifact *input = *inputs.cbegin(); + const FileTags &fileTags = input->fileTags(); + QBS_ASSERT(!fileTags.empty(), return); + QScriptValue inputsForFileTag = scriptValue.property(fileTags.cbegin()->toString()); + inputScriptValue = inputsForFileTag.property(0); + } + targetScriptValue.setProperty(StringConstants::inputVar(), inputScriptValue); +} + +void Transformer::setupInputs(const QScriptValue &targetScriptValue) +{ + setupInputs(targetScriptValue, inputs, rule->module->name); +} + +void Transformer::setupOutputs(QScriptValue targetScriptValue) +{ + const auto scriptEngine = static_cast(targetScriptValue.engine()); + const QString &defaultModuleName = rule->module->name; + QScriptValue scriptValue = translateInOutputs(scriptEngine, outputs, defaultModuleName); + targetScriptValue.setProperty(StringConstants::outputsVar(), scriptValue); + QScriptValue outputScriptValue; + if (outputs.size() == 1) { + Artifact *output = *outputs.cbegin(); + const FileTags &fileTags = output->fileTags(); + QBS_ASSERT(!fileTags.empty(), return); + QScriptValue outputsForFileTag = scriptValue.property(fileTags.cbegin()->toString()); + outputScriptValue = outputsForFileTag.property(0); + } + targetScriptValue.setProperty(StringConstants::outputVar(), outputScriptValue); +} + +void Transformer::setupExplicitlyDependsOn(QScriptValue targetScriptValue) +{ + const auto scriptEngine = static_cast(targetScriptValue.engine()); + QScriptValue scriptValue = translateInOutputs(scriptEngine, explicitlyDependsOn, + rule->module->name); + targetScriptValue.setProperty(StringConstants::explicitlyDependsOnVar(), scriptValue); +} + +AbstractCommandPtr Transformer::createCommandFromScriptValue(const QScriptValue &scriptValue, + const CodeLocation &codeLocation) +{ + AbstractCommandPtr cmdBase; + if (scriptValue.isUndefined() || !scriptValue.isValid()) + return cmdBase; + QString className = scriptValue.property(StringConstants::classNameProperty()).toString(); + if (className == StringConstants::commandType()) + cmdBase = ProcessCommand::create(); + else if (className == StringConstants::javaScriptCommandType()) + cmdBase = JavaScriptCommand::create(); + if (cmdBase) + cmdBase->fillFromScriptValue(&scriptValue, codeLocation); + if (className == StringConstants::commandType()) { + auto procCmd = static_cast(cmdBase.get()); + procCmd->clearRelevantEnvValues(); + const auto envVars = procCmd->relevantEnvVars(); + for (const QString &key : envVars) + procCmd->addRelevantEnvValue(key, product()->buildEnvironment.value(key)); + } + return cmdBase; +} + +void Transformer::createCommands(ScriptEngine *engine, const PrivateScriptFunction &script, + const QScriptValueList &args) +{ + if (!script.scriptFunction.isValid() || script.scriptFunction.engine() != engine) { + script.scriptFunction = engine->evaluate(script.sourceCode(), + script.location().filePath(), + script.location().line()); + if (Q_UNLIKELY(!script.scriptFunction.isFunction())) + throw ErrorInfo(Tr::tr("Invalid prepare script."), script.location()); + } + + QScriptValue scriptValue = script.scriptFunction.call(QScriptValue(), args); + engine->releaseResourcesOfScriptObjects(); + propertiesRequestedInPrepareScript = engine->propertiesRequestedInScript(); + propertiesRequestedFromArtifactInPrepareScript = engine->propertiesRequestedFromArtifact(); + importedFilesUsedInPrepareScript = engine->importedFilesUsedInScript(); + depsRequestedInPrepareScript = engine->requestedDependencies(); + artifactsMapRequestedInPrepareScript = engine->requestedArtifacts(); + lastPrepareScriptExecutionTime = FileTime::currentTime(); + for (const ResolvedProduct * const p : engine->requestedExports()) { + exportedModulesAccessedInPrepareScript.insert(std::make_pair(p->uniqueName(), + p->exportedModule)); + } + engine->clearRequestedProperties(); + if (Q_UNLIKELY(engine->hasErrorOrException(scriptValue))) + throw engine->lastError(scriptValue, script.location()); + commands.clear(); + if (scriptValue.isArray()) { + const int count = scriptValue.property(StringConstants::lengthProperty()).toInt32(); + for (qint32 i = 0; i < count; ++i) { + QScriptValue item = scriptValue.property(i); + if (item.isValid() && !item.isUndefined()) { + const AbstractCommandPtr cmd + = createCommandFromScriptValue(item, script.location()); + if (cmd) + commands.addCommand(cmd); + } + } + } else { + const AbstractCommandPtr cmd = createCommandFromScriptValue(scriptValue, + script.location()); + if (cmd) + commands.addCommand(cmd); + } +} + +void Transformer::rescueChangeTrackingData(const TransformerConstPtr &other) +{ + if (!other) + return; + propertiesRequestedInPrepareScript = other->propertiesRequestedInPrepareScript; + propertiesRequestedInCommands = other->propertiesRequestedInCommands; + propertiesRequestedFromArtifactInPrepareScript + = other->propertiesRequestedFromArtifactInPrepareScript; + propertiesRequestedFromArtifactInCommands = other->propertiesRequestedFromArtifactInCommands; + importedFilesUsedInPrepareScript = other->importedFilesUsedInPrepareScript; + importedFilesUsedInCommands = other->importedFilesUsedInCommands; + depsRequestedInPrepareScript = other->depsRequestedInPrepareScript; + depsRequestedInCommands = other->depsRequestedInCommands; + artifactsMapRequestedInPrepareScript = other->artifactsMapRequestedInPrepareScript; + artifactsMapRequestedInCommands = other->artifactsMapRequestedInCommands; + lastCommandExecutionTime = other->lastCommandExecutionTime; + lastPrepareScriptExecutionTime = other->lastPrepareScriptExecutionTime; + prepareScriptNeedsChangeTracking = other->prepareScriptNeedsChangeTracking; + commandsNeedChangeTracking = other->commandsNeedChangeTracking; + markedForRerun = other->markedForRerun; + exportedModulesAccessedInPrepareScript = other->exportedModulesAccessedInPrepareScript; + exportedModulesAccessedInCommands = other->exportedModulesAccessedInCommands; +} + +Set Transformer::jobPools() const +{ + Set pools; + for (const AbstractCommandPtr &c : commands.commands()) { + if (!c->jobPool().isEmpty()) + pools.insert(c->jobPool()); + } + return pools; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/transformer.h b/src/lib/corelib/buildgraph/transformer.h new file mode 100644 index 00000000..8772ed86 --- /dev/null +++ b/src/lib/corelib/buildgraph/transformer.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** 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 QBS_TRANSFORMER_H +#define QBS_TRANSFORMER_H + +#include "artifact.h" +#include "forward_decls.h" +#include "requestedartifacts.h" +#include "requesteddependencies.h" +#include "rulecommands.h" +#include +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { +class Artifact; +class AbstractCommand; +class Rule; + +class Transformer +{ +public: + static TransformerPtr create() { return TransformerPtr(new Transformer); } + + ~Transformer(); + + ArtifactSet inputs; // Subset of "children of all outputs". + ArtifactSet outputs; + ArtifactSet explicitlyDependsOn; + RuleConstPtr rule; + CommandList commands; + PropertySet propertiesRequestedInPrepareScript; + PropertySet propertiesRequestedInCommands; + QHash propertiesRequestedFromArtifactInPrepareScript; + QHash propertiesRequestedFromArtifactInCommands; + std::vector importedFilesUsedInPrepareScript; + std::vector importedFilesUsedInCommands; + RequestedDependencies depsRequestedInPrepareScript; + RequestedDependencies depsRequestedInCommands; + RequestedArtifacts artifactsMapRequestedInPrepareScript; + RequestedArtifacts artifactsMapRequestedInCommands; + FileTime lastPrepareScriptExecutionTime; + FileTime lastCommandExecutionTime; + std::unordered_map exportedModulesAccessedInPrepareScript; + std::unordered_map exportedModulesAccessedInCommands; + bool alwaysRun; + bool prepareScriptNeedsChangeTracking = false; + bool commandsNeedChangeTracking = false; + bool markedForRerun = false; + + static QScriptValue translateFileConfig(ScriptEngine *scriptEngine, + const Artifact *artifact, + const QString &defaultModuleName); + ResolvedProductPtr product() const; + void setupInputs(const QScriptValue &targetScriptValue); + void setupOutputs(QScriptValue targetScriptValue); + void setupExplicitlyDependsOn(QScriptValue targetScriptValue); + void createCommands(ScriptEngine *engine, const PrivateScriptFunction &script, + const QScriptValueList &args); + void rescueChangeTrackingData(const TransformerConstPtr &other); + + Set jobPools() const; + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(rule, inputs, outputs, explicitlyDependsOn, + propertiesRequestedInPrepareScript, + propertiesRequestedInCommands, + propertiesRequestedFromArtifactInPrepareScript, + propertiesRequestedFromArtifactInCommands, + importedFilesUsedInPrepareScript, importedFilesUsedInCommands, + depsRequestedInPrepareScript, depsRequestedInCommands, + commands, artifactsMapRequestedInPrepareScript, + artifactsMapRequestedInCommands, + lastPrepareScriptExecutionTime, lastCommandExecutionTime, + exportedModulesAccessedInPrepareScript, + exportedModulesAccessedInCommands, + alwaysRun, prepareScriptNeedsChangeTracking, + commandsNeedChangeTracking, markedForRerun); + } + +private: + Transformer(); + AbstractCommandPtr createCommandFromScriptValue(const QScriptValue &scriptValue, + const CodeLocation &codeLocation); + + static void setupInputs(QScriptValue targetScriptValue, const ArtifactSet &inputs, + const QString &defaultModuleName); + static QScriptValue translateInOutputs(ScriptEngine *scriptEngine, + const ArtifactSet &artifacts, + const QString &defaultModuleName); +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_TRANSFORMER_H diff --git a/src/lib/corelib/buildgraph/transformerchangetracking.cpp b/src/lib/corelib/buildgraph/transformerchangetracking.cpp new file mode 100644 index 00000000..e44c3163 --- /dev/null +++ b/src/lib/corelib/buildgraph/transformerchangetracking.cpp @@ -0,0 +1,381 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "transformerchangetracking.h" + +#include "projectbuilddata.h" +#include "requesteddependencies.h" +#include "rulecommands.h" +#include "transformer.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class TrafoChangeTracker +{ +public: + TrafoChangeTracker(const Transformer *transformer, + const ResolvedProduct *product, + const std::unordered_map &productsByName, + const std::unordered_map &projectsByName) + : m_transformer(transformer), + m_product(product), + m_productsByName(productsByName), + m_projectsByName(projectsByName) + { + } + + bool prepareScriptNeedsRerun() const; + bool commandsNeedRerun() const; + +private: + QVariantMap propertyMapByKind(const Property &property) const; + bool checkForPropertyChange(const Property &restoredProperty, + const QVariantMap &newProperties) const; + bool checkForImportFileChange(const std::vector &importedFiles, + const FileTime &referenceTime, + const char *context) const; + bool isExportedModuleUpToDate(const QString &productName, const ExportedModule &module) const; + bool areExportedModulesUpToDate( + const std::unordered_map &exportedModules) const; + const Artifact *getArtifact(const QString &filePath, const QString &productName) const; + const ResolvedProduct *getProduct(const QString &name) const; + + const Transformer * const m_transformer; + const ResolvedProduct * const m_product; + const std::unordered_map &m_productsByName; + const std::unordered_map &m_projectsByName; + mutable const ResolvedProduct * m_lastProduct = nullptr; + mutable const Artifact *m_lastArtifact = nullptr; +}; + +template static QVariantMap getParameterValue( + const QHash ¶meters, + const QString &depName) +{ + for (auto it = parameters.cbegin(); it != parameters.cend(); ++it) { + if (it.key()->name == depName) + return it.value(); + } + return {}; +} + +QVariantMap TrafoChangeTracker::propertyMapByKind(const Property &property) const +{ + switch (property.kind) { + case Property::PropertyInModule: { + const ResolvedProduct * const p = getProduct(property.productName); + return p ? p->moduleProperties->value() : QVariantMap(); + } + case Property::PropertyInProduct: { + const ResolvedProduct * const p = getProduct(property.productName); + return p ? p->productProperties : QVariantMap(); + } + case Property::PropertyInProject: { + if (property.productName == m_product->project->name) + return m_product->project->projectProperties(); + const auto it = m_projectsByName.find(property.productName); + if (it != m_projectsByName.cend()) + return it->second->projectProperties(); + return {}; + } + case Property::PropertyInParameters: { + const int sepIndex = property.moduleName.indexOf(QLatin1Char(':')); + const QString depName = property.moduleName.left(sepIndex); + const ResolvedProduct * const p = getProduct(property.productName); + if (!p) + return {}; + QVariantMap v = getParameterValue(p->dependencyParameters, depName); + if (!v.empty()) + return v; + return getParameterValue(p->moduleParameters, depName); + } + case Property::PropertyInArtifact: + default: + QBS_CHECK(false); + } + return {}; +} + +bool TrafoChangeTracker::checkForPropertyChange(const Property &restoredProperty, + const QVariantMap &newProperties) const +{ + QVariant v; + switch (restoredProperty.kind) { + case Property::PropertyInProduct: + case Property::PropertyInProject: + v = newProperties.value(restoredProperty.propertyName); + break; + case Property::PropertyInModule: + v = moduleProperty(newProperties, restoredProperty.moduleName, + restoredProperty.propertyName); + break; + case Property::PropertyInParameters: { + const int sepIndex = restoredProperty.moduleName.indexOf(QLatin1Char(':')); + QualifiedId moduleName + = QualifiedId::fromString(restoredProperty.moduleName.mid(sepIndex + 1)); + QVariantMap map = newProperties; + while (!moduleName.empty()) + map = map.value(moduleName.takeFirst()).toMap(); + v = map.value(restoredProperty.propertyName); + break; + } + case Property::PropertyInArtifact: + QBS_CHECK(false); + } + if (restoredProperty.value != 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; +} + +bool TrafoChangeTracker::checkForImportFileChange(const std::vector &importedFiles, + const FileTime &referenceTime, + const char *context) const +{ + for (const QString &importedFile : importedFiles) { + const FileInfo fi(importedFile); + if (!fi.exists()) { + qCDebug(lcBuildGraph) << context << "imported file" << importedFile + << "is gone, need to re-run"; + return true; + } + if (fi.lastModified() > referenceTime) { + qCDebug(lcBuildGraph) << context << "imported file" << importedFile + << "has been updated, need to re-run" + << fi.lastModified() << referenceTime; + return true; + } + } + return false; +} + +bool TrafoChangeTracker::isExportedModuleUpToDate(const QString &productName, + const ExportedModule &module) const +{ + const ResolvedProduct * const product = getProduct(productName); + if (!product) { + qCDebug(lcBuildGraph) << "product" << productName + << "does not exist anymore, need to re-run"; + return false; + } + if (product->exportedModule != module) { + qCDebug(lcBuildGraph) << "exported module has changed for product" << productName + << ", need to re-run"; + return false; + } + return true; +} + +bool TrafoChangeTracker::areExportedModulesUpToDate( + const std::unordered_map &exportedModules) const +{ + for (const auto &kv : exportedModules) { + if (!isExportedModuleUpToDate(kv.first, kv.second)) + return false; + } + return true; +} + +const Artifact *TrafoChangeTracker::getArtifact(const QString &filePath, + const QString &productName) const +{ + if (m_lastArtifact && m_lastArtifact->filePath() == filePath + && m_lastArtifact->product.get()->uniqueName() == productName) { + return m_lastArtifact; + } + const ResolvedProduct * const product = getProduct(productName); + if (!product) + return nullptr; + const auto &candidates = product->topLevelProject()->buildData->lookupFiles(filePath); + const Artifact *artifact = nullptr; + for (const FileResourceBase * const candidate : candidates) { + if (candidate->fileType() == FileResourceBase::FileTypeArtifact) { + auto const a = static_cast(candidate); + if (a->product.get() == product) { + m_lastArtifact = artifact = a; + break; + } + } + } + return artifact; +} + +const ResolvedProduct *TrafoChangeTracker::getProduct(const QString &name) const +{ + if (m_lastProduct && name == m_lastProduct->uniqueName()) + return m_lastProduct; + if (name == m_product->uniqueName()) { + m_lastProduct = m_product; + return m_product; + } + const auto it = m_productsByName.find(name); + if (it != m_productsByName.cend()) { + m_lastProduct = it->second; + return it->second; + } + return nullptr; +} + +bool TrafoChangeTracker::prepareScriptNeedsRerun() const +{ + for (const Property &property : qAsConst(m_transformer->propertiesRequestedInPrepareScript)) { + if (checkForPropertyChange(property, propertyMapByKind(property))) + return true; + } + + if (checkForImportFileChange(m_transformer->importedFilesUsedInPrepareScript, + m_transformer->lastPrepareScriptExecutionTime, "prepare script")) { + return true; + } + + for (auto it = m_transformer->propertiesRequestedFromArtifactInPrepareScript.constBegin(); + it != m_transformer->propertiesRequestedFromArtifactInPrepareScript.constEnd(); ++it) { + for (const Property &property : qAsConst(it.value())) { + const Artifact * const artifact = getArtifact(it.key(), property.productName); + if (!artifact) + return true; + if (property.kind == Property::PropertyInArtifact) { + if (sorted(artifact->fileTags().toStringList()) != property.value.toStringList()) + return true; + continue; + } + if (checkForPropertyChange(property, artifact->properties->value())) + return true; + } + } + + if (!m_transformer->depsRequestedInPrepareScript.isUpToDate(m_product->topLevelProject())) + return true; + if (!m_transformer->artifactsMapRequestedInPrepareScript.isUpToDate( + m_product->topLevelProject())) { + return true; + } + if (!areExportedModulesUpToDate(m_transformer->exportedModulesAccessedInPrepareScript)) + return true; + + return false; +} + +bool TrafoChangeTracker::commandsNeedRerun() const +{ + for (const Property &property : qAsConst(m_transformer->propertiesRequestedInCommands)) { + if (checkForPropertyChange(property, propertyMapByKind(property))) + return true; + } + + QMap artifactMap; + for (auto it = m_transformer->propertiesRequestedFromArtifactInCommands.cbegin(); + it != m_transformer->propertiesRequestedFromArtifactInCommands.cend(); ++it) { + for (const Property &property : qAsConst(it.value())) { + const Artifact * const artifact = getArtifact(it.key(), property.productName); + if (!artifact) + return true; + if (property.kind == Property::PropertyInArtifact) { + if (sorted(artifact->fileTags().toStringList()) != property.value.toStringList()) + return true; + continue; + } + if (checkForPropertyChange(property, artifact->properties->value())) + return true; + } + } + + if (checkForImportFileChange(m_transformer->importedFilesUsedInCommands, + m_transformer->lastCommandExecutionTime, "command")) { + return true; + } + + if (!m_transformer->depsRequestedInCommands.isUpToDate(m_product->topLevelProject())) + return true; + if (!m_transformer->artifactsMapRequestedInCommands.isUpToDate(m_product->topLevelProject())) + return true; + if (!areExportedModulesUpToDate(m_transformer->exportedModulesAccessedInCommands)) + return true; + + // TODO: Also track env access in JS commands and prepare scripts + for (const AbstractCommandPtr &c : qAsConst(m_transformer->commands.commands())) { + if (c->type() != AbstractCommand::ProcessCommandType) + continue; + const ProcessCommandPtr &processCmd = std::static_pointer_cast(c); + const auto envVars = processCmd->relevantEnvVars(); + for (const QString &var : envVars) { + const QString &oldValue = processCmd->relevantEnvValue(var); + const QString &newValue = m_product->buildEnvironment.value(var); + if (oldValue != newValue) { + qCDebug(lcBuildGraph) << "need to re-run command: value of environment variable" + << var << "changed from" << oldValue << "to" << newValue + << "for command" << processCmd->program() << "in rule" + << m_transformer->rule->toString(); + return true; + } + } + } + + return false; +} + +bool prepareScriptNeedsRerun( + Transformer *transformer, const ResolvedProduct *product, + const std::unordered_map &productsByName, + const std::unordered_map &projectsByName) +{ + if (!transformer->prepareScriptNeedsChangeTracking) + return false; + transformer->prepareScriptNeedsChangeTracking = false; + return TrafoChangeTracker(transformer, product, productsByName, projectsByName) + .prepareScriptNeedsRerun(); +} + +bool commandsNeedRerun(Transformer *transformer, const ResolvedProduct *product, + const std::unordered_map &productsByName, + const std::unordered_map &projectsByName) +{ + if (!transformer->commandsNeedChangeTracking) + return false; + transformer->commandsNeedChangeTracking = false; + return TrafoChangeTracker(transformer, product, productsByName, projectsByName) + .commandsNeedRerun(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/buildgraph/transformerchangetracking.h b/src/lib/corelib/buildgraph/transformerchangetracking.h new file mode 100644 index 00000000..56efa458 --- /dev/null +++ b/src/lib/corelib/buildgraph/transformerchangetracking.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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$ +** +****************************************************************************/ +#ifndef QBS_TRANSFORMERCHANGETRACKING_H +#define QBS_TRANSFORMERCHANGETRACKING_H + +#include "forward_decls.h" +#include + +#include + +namespace qbs { +namespace Internal { + +bool prepareScriptNeedsRerun( + Transformer *transformer, + const ResolvedProduct *product, + const std::unordered_map &productsByName, + const std::unordered_map &projectsByName); + +bool commandsNeedRerun(Transformer *transformer, + const ResolvedProduct *product, + const std::unordered_map &productsByName, + const std::unordered_map &projectsByName); + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/corelib.pro b/src/lib/corelib/corelib.pro new file mode 100644 index 00000000..002e3668 --- /dev/null +++ b/src/lib/corelib/corelib.pro @@ -0,0 +1,42 @@ +TARGET = qbscore +include(../library.pri) +include(../bundledlibs.pri) + +qbs_use_bundled_qtscript { + include(../scriptengine/use_scriptengine.pri) +} else { + QT += script +} + +isEmpty(QBS_RELATIVE_LIBEXEC_PATH) { + win32:QBS_RELATIVE_LIBEXEC_PATH=../bin + else:QBS_RELATIVE_LIBEXEC_PATH=../libexec/qbs +} +DEFINES += QBS_RELATIVE_LIBEXEC_PATH=\\\"$${QBS_RELATIVE_LIBEXEC_PATH}\\\" + +QT += core-private network +qbs_enable_project_file_updates: QT += gui + +INCLUDEPATH += $$PWD + +include(api/api.pri) +include(buildgraph/buildgraph.pri) +include(generators/generators.pri) +include(jsextensions/jsextensions.pri) +include(language/language.pri) +include(logging/logging.pri) +include(parser/parser.pri) +include(tools/tools.pri) + +win32:LIBS += -lpsapi -lshell32 + +HEADERS += \ + qbs.h + +!qbs_no_dev_install { + qbs_h.files = qbs.h + qbs_h.path = $${QBS_INSTALL_PREFIX}/include/qbs + use_pri.files = use_installed_corelib.pri ../../../qbs_version.pri + use_pri.path = $${qbs_h.path} + INSTALLS += qbs_h use_pri +} diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs new file mode 100644 index 00000000..619c0f0e --- /dev/null +++ b/src/lib/corelib/corelib.qbs @@ -0,0 +1,528 @@ +import qbs 1.0 +import qbs.Utilities + +QbsLibrary { + Depends { name: "cpp" } + Depends { name: "Qt"; submodules: ["core-private", "network", "xml"] } + Depends { + name: "Qt.script" + condition: !qbsbuildconfig.useBundledQtScript + required: false + } + Depends { + name: "qbsscriptengine" + condition: qbsbuildconfig.useBundledQtScript || !Qt.script.present + } + Depends { condition: qbsbuildconfig.enableProjectFileUpdates; name: "Qt.gui" } + name: "qbscore" + property stringList bundledQtScriptIncludes: qbsbuildconfig.useBundledQtScript + || !Qt.script.present ? qbsscriptengine.includePaths : [] + cpp.includePaths: base.concat(bundledQtScriptIncludes).concat([ + ".", + "../.." // for the plugin headers + ]) + property stringList projectFileUpdateDefines: + qbsbuildconfig.enableProjectFileUpdates ? ["QBS_ENABLE_PROJECT_FILE_UPDATES"] : [] + property stringList enableUnitTestsDefines: + qbsbuildconfig.enableUnitTests ? ["QBS_ENABLE_UNIT_TESTS"] : [] + property stringList systemSettingsDirDefines: qbsbuildconfig.systemSettingsDir + ? ['QBS_SYSTEM_SETTINGS_DIR="' + qbsbuildconfig.systemSettingsDir + '"'] : [] + cpp.defines: base.concat([ + "QBS_RELATIVE_LIBEXEC_PATH=" + Utilities.cStringQuote(qbsbuildconfig.relativeLibexecPath), + "QBS_VERSION=" + Utilities.cStringQuote(version), + ]).concat(projectFileUpdateDefines).concat(enableUnitTestsDefines) + .concat(systemSettingsDirDefines) + + Properties { + condition: qbs.targetOS.contains("windows") + cpp.dynamicLibraries: base.concat(["psapi", "shell32"]) + } + cpp.dynamicLibraries: base + + Properties { + condition: qbs.targetOS.contains("darwin") + cpp.frameworks: ["Foundation", "Security"] + } + + Group { + name: product.name + files: ["qbs.h"] + qbs.install: qbsbuildconfig.installApiHeaders + qbs.installDir: headerInstallPrefix + } + Group { + name: "project file updating" + condition: qbsbuildconfig.enableProjectFileUpdates + prefix: "api/" + files: [ + "changeset.cpp", + "changeset.h", + "projectfileupdater.cpp", + "projectfileupdater.h", + "qmljsrewriter.cpp", + "qmljsrewriter.h", + ] + } + + Group { + name: "api" + prefix: name + '/' + files: [ + "internaljobs.cpp", + "internaljobs.h", + "jobs.cpp", + "languageinfo.cpp", + "project.cpp", + "project_p.h", + "projectdata.cpp", + "projectdata_p.h", + "propertymap_p.h", + "rulecommand.cpp", + "rulecommand_p.h", + "runenvironment.cpp", + "transformerdata.cpp", + "transformerdata_p.h", + ] + } + Group { + name: "public api headers" + qbs.install: qbsbuildconfig.installApiHeaders + qbs.installDir: headerInstallPrefix + "/api" + prefix: "api/" + files: [ + "jobs.h", + "languageinfo.h", + "project.h", + "projectdata.h", + "rulecommand.h", + "runenvironment.h", + "transformerdata.h", + ] + } + Group { + name: "buildgraph" + prefix: name + '/' + files: [ + "abstractcommandexecutor.cpp", + "abstractcommandexecutor.h", + "artifact.cpp", + "artifact.h", + "artifactcleaner.cpp", + "artifactcleaner.h", + "artifactsscriptvalue.cpp", + "artifactsscriptvalue.h", + "artifactvisitor.cpp", + "artifactvisitor.h", + "buildgraph.cpp", + "buildgraph.h", + "buildgraphnode.cpp", + "buildgraphnode.h", + "buildgraphloader.cpp", + "buildgraphloader.h", + "buildgraphvisitor.h", + "cycledetector.cpp", + "cycledetector.h", + "dependencyparametersscriptvalue.cpp", + "dependencyparametersscriptvalue.h", + "depscanner.cpp", + "depscanner.h", + "emptydirectoriesremover.cpp", + "emptydirectoriesremover.h", + "environmentscriptrunner.cpp", + "environmentscriptrunner.h", + "executor.cpp", + "executor.h", + "executorjob.cpp", + "executorjob.h", + "filedependency.cpp", + "filedependency.h", + "inputartifactscanner.cpp", + "inputartifactscanner.h", + "jscommandexecutor.cpp", + "jscommandexecutor.h", + "nodeset.cpp", + "nodeset.h", + "nodetreedumper.cpp", + "nodetreedumper.h", + "processcommandexecutor.cpp", + "processcommandexecutor.h", + "productbuilddata.cpp", + "productbuilddata.h", + "productinstaller.cpp", + "productinstaller.h", + "projectbuilddata.cpp", + "projectbuilddata.h", + "qtmocscanner.cpp", + "qtmocscanner.h", + "rawscanneddependency.cpp", + "rawscanneddependency.h", + "rawscanresults.cpp", + "rawscanresults.h", + "requestedartifacts.cpp", + "requestedartifacts.h", + "requesteddependencies.cpp", + "requesteddependencies.h", + "rescuableartifactdata.h", + "rulecommands.cpp", + "rulecommands.h", + "rulegraph.cpp", + "rulegraph.h", + "rulenode.cpp", + "rulenode.h", + "rulesapplicator.cpp", + "rulesapplicator.h", + "rulesevaluationcontext.cpp", + "rulesevaluationcontext.h", + "scriptclasspropertyiterator.h", + "timestampsupdater.cpp", + "timestampsupdater.h", + "transformer.cpp", + "transformer.h", + "transformerchangetracking.cpp", + "transformerchangetracking.h", + ] + } + Group { + name: "public buildgraph headers" + qbs.install: qbsbuildconfig.installApiHeaders + qbs.installDir: headerInstallPrefix + "/buildgraph" + files: "buildgraph/forward_decls.h" + } + Group { + name: "generators" + prefix: "generators/" + files: [ + "generatableprojectiterator.cpp", + "generatableprojectiterator.h", + "generator.cpp", + "generatordata.cpp", + "generatorutils.cpp", + "generatorutils.h", + "generatorversioninfo.cpp", + "generatorversioninfo.h", + "igeneratableprojectvisitor.h", + "ixmlnodevisitor.h", + "xmlproject.cpp", + "xmlproject.h", + "xmlprojectwriter.cpp", + "xmlprojectwriter.h", + "xmlproperty.cpp", + "xmlproperty.h", + "xmlpropertygroup.cpp", + "xmlpropertygroup.h", + "xmlworkspace.cpp", + "xmlworkspace.h", + "xmlworkspacewriter.cpp", + "xmlworkspacewriter.h", + ] + } + Group { + name: "public generator headers" + prefix: "generators/" + qbs.install: qbsbuildconfig.installApiHeaders + qbs.installDir: headerInstallPrefix + "/generators" + files: [ + "generator.h", + "generatordata.h", + ] + } + Group { + name: "jsextensions" + prefix: name + '/' + files: [ + "environmentextension.cpp", + "file.cpp", + "fileinfoextension.cpp", + "jsextensions.cpp", + "jsextensions.h", + "moduleproperties.cpp", + "moduleproperties.h", + "process.cpp", + "temporarydir.cpp", + "textfile.cpp", + "binaryfile.cpp", + "utilitiesextension.cpp", + "domxml.cpp", + ] + } + Group { + name: "jsextensions (Non-Darwin-specific)" + prefix: "jsextensions/" + condition: !qbs.targetOS.contains("darwin") + files: [ + "propertylist.cpp", + ] + } + Group { + name: "jsextensions (Darwin-specific)" + prefix: "jsextensions/" + condition: qbs.targetOS.contains("darwin") + files: [ + "propertylist.mm", + "propertylistutils.h", + "propertylistutils.mm", + ] + } + Group { + name: "language" + prefix: name + '/' + files: [ + "artifactproperties.cpp", + "artifactproperties.h", + "astimportshandler.cpp", + "astimportshandler.h", + "astpropertiesitemhandler.cpp", + "astpropertiesitemhandler.h", + "asttools.cpp", + "asttools.h", + "builtindeclarations.cpp", + "builtindeclarations.h", + "deprecationinfo.h", + "evaluationdata.h", + "evaluator.cpp", + "evaluator.h", + "evaluatorscriptclass.cpp", + "evaluatorscriptclass.h", + "filecontext.cpp", + "filecontext.h", + "filecontextbase.cpp", + "filecontextbase.h", + "filetags.cpp", + "filetags.h", + "identifiersearch.cpp", + "identifiersearch.h", + "item.cpp", + "item.h", + "itemdeclaration.cpp", + "itemdeclaration.h", + "itemobserver.h", + "itempool.cpp", + "itempool.h", + "itemreader.cpp", + "itemreader.h", + "itemreaderastvisitor.cpp", + "itemreaderastvisitor.h", + "itemreadervisitorstate.cpp", + "itemreadervisitorstate.h", + "itemtype.h", + "jsimports.h", + "language.cpp", + "language.h", + "loader.cpp", + "loader.h", + "moduleloader.cpp", + "moduleloader.h", + "modulemerger.cpp", + "modulemerger.h", + "moduleproviderinfo.h", + "preparescriptobserver.cpp", + "preparescriptobserver.h", + "projectresolver.cpp", + "projectresolver.h", + "property.cpp", + "property.h", + "propertydeclaration.cpp", + "propertydeclaration.h", + "propertymapinternal.cpp", + "propertymapinternal.h", + "qualifiedid.cpp", + "qualifiedid.h", + "resolvedfilecontext.cpp", + "resolvedfilecontext.h", + "scriptengine.cpp", + "scriptengine.h", + "scriptimporter.cpp", + "scriptimporter.h", + "scriptpropertyobserver.cpp", + "scriptpropertyobserver.h", + "value.cpp", + "value.h", + ] + } + Group { + name: "public language headers" + qbs.install: qbsbuildconfig.installApiHeaders + qbs.installDir: headerInstallPrefix + "/language" + files: "language/forward_decls.h" + } + Group { + name: "logging" + prefix: name + '/' + files: [ + "categories.cpp", + "categories.h", + "ilogsink.cpp", + "logger.cpp", + "logger.h", + "translator.h" + ] + } + Group { + name: "public logging headers" + qbs.install: qbsbuildconfig.installApiHeaders + qbs.installDir: headerInstallPrefix + "/logging" + files: "logging/ilogsink.h" + } + Group { + name: "parser" + prefix: name + '/' + files: [ + "qmlerror.cpp", + "qmlerror.h", + "qmljsast.cpp", + "qmljsast_p.h", + "qmljsastfwd_p.h", + "qmljsastvisitor.cpp", + "qmljsastvisitor_p.h", + "qmljsengine_p.cpp", + "qmljsengine_p.h", + "qmljsglobal_p.h", + "qmljsgrammar.cpp", + "qmljsgrammar_p.h", + "qmljskeywords_p.h", + "qmljslexer.cpp", + "qmljslexer_p.h", + "qmljsmemorypool_p.h", + "qmljsparser.cpp", + "qmljsparser_p.h" + ] + } + Group { + name: "tools" + prefix: name + '/' + files: [ + "architectures.cpp", + "buildgraphlocker.cpp", + "buildgraphlocker.h", + "buildoptions.cpp", + "clangclinfo.cpp", + "clangclinfo.h", + "cleanoptions.cpp", + "codelocation.cpp", + "commandechomode.cpp", + "dynamictypecheck.h", + "error.cpp", + "executablefinder.cpp", + "executablefinder.h", + "fileinfo.cpp", + "fileinfo.h", + "filesaver.cpp", + "filesaver.h", + "filetime.cpp", + "filetime.h", + "generateoptions.cpp", + "hostosinfo.h", + "id.cpp", + "id.h", + "iosutils.h", + "joblimits.cpp", + "jsliterals.cpp", + "jsliterals.h", + "jsonhelper.h", + "installoptions.cpp", + "launcherinterface.cpp", + "launcherinterface.h", + "launcherpackets.cpp", + "launcherpackets.h", + "launchersocket.cpp", + "launchersocket.h", + "msvcinfo.cpp", + "msvcinfo.h", + "pathutils.h", + "persistence.cpp", + "persistence.h", + "preferences.cpp", + "processresult.cpp", + "processresult_p.h", + "processutils.cpp", + "processutils.h", + "profile.cpp", + "profiling.cpp", + "profiling.h", + "progressobserver.cpp", + "progressobserver.h", + "projectgeneratormanager.cpp", + "qbsassert.cpp", + "qbsassert.h", + "qbspluginmanager.cpp", + "qbspluginmanager.h", + "qbsprocess.cpp", + "qbsprocess.h", + "qttools.cpp", + "qttools.h", + "scannerpluginmanager.cpp", + "scannerpluginmanager.h", + "scripttools.cpp", + "scripttools.h", + "set.h", + "settings.cpp", + "settingscreator.cpp", + "settingscreator.h", + "settingsmodel.cpp", + "settingsrepresentation.cpp", + "setupprojectparameters.cpp", + "shellutils.cpp", + "shellutils.h", + "stlutils.h", + "stringconstants.h", + "stringutils.h", + "toolchains.cpp", + "version.cpp", + "visualstudioversioninfo.cpp", + "visualstudioversioninfo.h", + "vsenvironmentdetector.cpp", + "vsenvironmentdetector.h", + "weakpointer.h", + ] + } + Group { + name: "public tools headers" + prefix: "tools/" + files: [ + "architectures.h", + "buildoptions.h", + "cleanoptions.h", + "codelocation.h", + "commandechomode.h", + "error.h", + "generateoptions.h", + "installoptions.h", + "joblimits.h", + "preferences.h", + "processresult.h", + "profile.h", + "projectgeneratormanager.h", + "qbs_export.h", + "settings.h", + "settingsmodel.h", + "settingsrepresentation.h", + "setupprojectparameters.h", + "toolchains.h", + "version.h", + ] + qbs.install: qbsbuildconfig.installApiHeaders + qbs.installDir: headerInstallPrefix + "/tools" + } + Group { + condition: qbs.targetOS.contains("macos") + name: "tools (macOS)" + prefix: "tools/" + files: [ + "applecodesignutils.cpp", + "applecodesignutils.h" + ] + } + Group { + name: "use_installed.pri" + files: [ + "use_installed_corelib.pri", + "../../../qbs_version.pri" + ] + qbs.install: qbsbuildconfig.installApiHeaders + qbs.installDir: headerInstallPrefix + } + Export { + Depends { name: "cpp" } + cpp.defines: base.concat(product.projectFileUpdateDefines) + } +} diff --git a/src/lib/corelib/generators/generatableprojectiterator.cpp b/src/lib/corelib/generators/generatableprojectiterator.cpp new file mode 100644 index 00000000..7267662e --- /dev/null +++ b/src/lib/corelib/generators/generatableprojectiterator.cpp @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 "generatableprojectiterator.h" + +namespace qbs { + +GeneratableProjectIterator::GeneratableProjectIterator(GeneratableProject project) + : project(std::move(project)) +{ +} + +void GeneratableProjectIterator::accept(IGeneratableProjectVisitor *visitor) +{ + visitor->visitProject(project); + QMapIterator it(project.projects); + while (it.hasNext()) { + it.next(); + visitor->visitProject(it.value(), it.key()); + } + + accept(project, GeneratableProjectData(), project, visitor); +} + +void GeneratableProjectIterator::accept(const GeneratableProject &project, + const GeneratableProjectData &parentProjectData, + const GeneratableProjectData &projectData, + IGeneratableProjectVisitor *visitor) +{ + visitor->visitProjectData(project, parentProjectData, projectData); + visitor->visitProjectData(project, projectData); + QMapIterator it(projectData.data); + while (it.hasNext()) { + it.next(); + visitor->visitProjectData(parentProjectData.data.value(it.key()), it.value(), it.key()); + visitor->visitProjectData(it.value(), it.key()); + } + + for (const auto &subProject : projectData.subProjects) { + accept(project, projectData, subProject, visitor); + } + + for (const auto &productDataMap : projectData.products) { + visitor->visitProduct(project, projectData, productDataMap); + QMapIterator it(productDataMap.data); + while (it.hasNext()) { + it.next(); + visitor->visitProduct(it.value(), it.key()); + } + } +} + +} // namespace qbs diff --git a/src/lib/corelib/generators/generatableprojectiterator.h b/src/lib/corelib/generators/generatableprojectiterator.h new file mode 100644 index 00000000..993144ce --- /dev/null +++ b/src/lib/corelib/generators/generatableprojectiterator.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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 GENERATABLEPROJECTITERATOR_H +#define GENERATABLEPROJECTITERATOR_H + +#include "generatordata.h" +#include "igeneratableprojectvisitor.h" + +namespace qbs { + +class QBS_EXPORT GeneratableProjectIterator { + GeneratableProject project; + +public: + GeneratableProjectIterator(GeneratableProject project); + void accept(IGeneratableProjectVisitor *visitor); + +private: + void accept(const GeneratableProject &project, const GeneratableProjectData &parentProjectData, + const GeneratableProjectData &projectData, + IGeneratableProjectVisitor *visitor); +}; + +} // namespace qbs + +#endif // GENERATABLEPROJECTITERATOR_H diff --git a/src/lib/corelib/generators/generator.cpp b/src/lib/corelib/generators/generator.cpp new file mode 100644 index 00000000..90bebdca --- /dev/null +++ b/src/lib/corelib/generators/generator.cpp @@ -0,0 +1,251 @@ +/**************************************************************************** +** +** 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 "generator.h" +#include +#include +#include +#include +#include +#include +#include + +namespace qbs { + +class ProjectGeneratorPrivate { +public: + QList projects; + QList buildConfigurations; + InstallOptions installOptions; + QString qbsSettingsDir; + Internal::Logger logger = Internal::Logger(nullptr); +}; + +ProjectGenerator::ProjectGenerator() + : d(new ProjectGeneratorPrivate) +{ +} + +ProjectGenerator::~ProjectGenerator() +{ + delete d; +} + +static QString _configurationName(const Project &project) +{ + return project.projectConfiguration() + .value(Internal::StringConstants::qbsModule()).toMap() + .value(Internal::StringConstants::configurationNameProperty()).toString(); +} + +static QString _configurationName(const QVariantMap &buildConfiguration) +{ + return buildConfiguration.value(QStringLiteral("qbs.configurationName")).toString(); +} + +ErrorInfo ProjectGenerator::generate(const QList &projects, + const QList &buildConfigurations, + const InstallOptions &installOptions, + const QString &qbsSettingsDir, + const Internal::Logger &logger) +{ + d->projects = projects; + std::sort(d->projects.begin(), d->projects.end(), + [](const Project &a, const Project &b) { + return _configurationName(a) < _configurationName(b); }); + d->buildConfigurations = buildConfigurations; + std::sort(d->buildConfigurations.begin(), d->buildConfigurations.end(), + [](const QVariantMap &a, const QVariantMap &b) { + return _configurationName(a) < _configurationName(b); }); + d->installOptions = installOptions; + d->qbsSettingsDir = qbsSettingsDir; + d->logger = logger; + try { + generate(); + } catch (const ErrorInfo &e) { + return e; + } + return {}; +} + +QList ProjectGenerator::projects() const +{ + return d->projects; +} + +QList ProjectGenerator::buildConfigurations() const +{ + return d->buildConfigurations; +} + +QVariantMap ProjectGenerator::buildConfiguration(const Project &project) const +{ + int idx = d->projects.indexOf(project); + if (idx < 0) + return {}; + return d->buildConfigurations.at(idx); +} + +QStringList ProjectGenerator::buildConfigurationCommandLine(const Project &project) const +{ + QVariantMap config = buildConfiguration(project); + + const QString name = config.take(QStringLiteral("qbs.configurationName")).toString(); + if (name.isEmpty()) + throw ErrorInfo(QStringLiteral("Can't find configuration name for project")); + + QStringList commandLineParameters; + commandLineParameters += QStringLiteral("config:") + name; + + QMapIterator it(config); + while (it.hasNext()) { + it.next(); + commandLineParameters += it.key() + QStringLiteral(":") + it.value().toString(); + } + + return commandLineParameters; +} + +// Count the number of products in the project (singular) +// Precondition: each project data (i.e. per-configuration project data) +// has the same number of products. +static int _productCount(const QList &projects) +{ + int count = -1; + for (const auto &project : projects) { + int oldCount = count; + count = project.products().size(); + QBS_CHECK(oldCount == -1 || oldCount == count); + } + return count; +} + +static int _subprojectCount(const QList &projects) +{ + int count = -1; + for (const auto &project : projects) { + int oldCount = count; + count = project.subProjects().size(); + QBS_CHECK(oldCount == -1 || oldCount == count); + } + return count; +} + +static GeneratableProjectData _reduceProjectConfigurations( + const QMap &map) { + GeneratableProjectData gproject; + + // Add the project's project data for each configuration + QMapIterator it(map); + while (it.hasNext()) { + it.next(); + gproject.data.insert(it.key(), it.value()); + } + + // Add the project's products... + for (int i = 0; i < _productCount(map.values()); ++i) { + GeneratableProductData prod; + + // once for each configuration + QMapIterator it(map); + while (it.hasNext()) { + it.next(); + prod.data.insert(it.key(), it.value().products().at(i)); + } + + gproject.products.push_back(prod); + } + + // Add the project's subprojects... + for (int i = 0; i < _subprojectCount(map.values()); ++i) { + QMap subprojectMap; + + // once for each configuration + QMapIterator it(map); + while (it.hasNext()) { + it.next(); + subprojectMap.insert(it.key(), it.value().subProjects().at(i)); + } + + gproject.subProjects.push_back(_reduceProjectConfigurations(subprojectMap)); + } + + return gproject; +} + +const GeneratableProject ProjectGenerator::project() const +{ + QMap rootProjects; + GeneratableProject proj; + for (const auto &project : qAsConst(d->projects)) { + const QString configurationName = _configurationName(project); + rootProjects.insert(configurationName, project.projectData()); + proj.projects.insert(configurationName, project); + proj.buildConfigurations.insert(configurationName, buildConfiguration(project)); + proj.commandLines.insert(configurationName, buildConfigurationCommandLine(project)); + } + auto p = _reduceProjectConfigurations(rootProjects); + proj.data = p.data; + proj.products = p.products; + proj.subProjects = p.subProjects; + proj.installOptions = d->installOptions; + return proj; +} + +QFileInfo ProjectGenerator::qbsExecutableFilePath() const +{ + const QString qbsInstallDir = QString::fromLocal8Bit(qgetenv("QBS_INSTALL_DIR")); + auto file = QFileInfo(Internal::HostOsInfo::appendExecutableSuffix(!qbsInstallDir.isEmpty() + ? qbsInstallDir + QLatin1String("/bin/qbs") + : QCoreApplication::applicationDirPath() + QLatin1String("/qbs"))); + QBS_CHECK(file.isAbsolute() && file.exists()); + return file; +} + +QString ProjectGenerator::qbsSettingsDir() const +{ + return d->qbsSettingsDir; +} + +const Internal::Logger &ProjectGenerator::logger() const +{ + return d->logger; +} + +} // namespace qbs diff --git a/src/lib/corelib/generators/generator.h b/src/lib/corelib/generators/generator.h new file mode 100644 index 00000000..775469f1 --- /dev/null +++ b/src/lib/corelib/generators/generator.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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 GENERATORPLUGIN_H +#define GENERATORPLUGIN_H + +#include "generatordata.h" +#include +#include + +namespace qbs { + +class ProjectGeneratorPrivate; + +/*! + * \class ProjectGenerator + * \brief The \c ProjectGenerator class is an abstract base class for generators which generate + * arbitrary output given a resolved Qbs project. + */ +class QBS_EXPORT ProjectGenerator : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(ProjectGenerator) +public: + ~ProjectGenerator() override; + + /*! + * Returns the name of the generator used to create the external build system files. + */ + virtual QString generatorName() const = 0; + + ErrorInfo generate(const QList &projects, + const QList &buildConfigurations, + const InstallOptions &installOptions, + const QString &qbsSettingsDir, + const Internal::Logger &logger); + + const GeneratableProject project() const; + QFileInfo qbsExecutableFilePath() const; + QString qbsSettingsDir() const; + +protected: + ProjectGenerator(); + + const Internal::Logger &logger() const; + +private: + virtual void generate() = 0; + + QList projects() const; + QList buildConfigurations() const; + QVariantMap buildConfiguration(const Project &project) const; + QStringList buildConfigurationCommandLine(const Project &project) const; + + ProjectGeneratorPrivate *d; +}; + +} // namespace qbs + +#endif // GENERATORPLUGIN_H diff --git a/src/lib/corelib/generators/generatordata.cpp b/src/lib/corelib/generators/generatordata.cpp new file mode 100644 index 00000000..7c657348 --- /dev/null +++ b/src/lib/corelib/generators/generatordata.cpp @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** 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 "generatordata.h" +#include +#include + +#include + +namespace qbs { + +QString GeneratableProductData::name() const +{ + return uniqueValue(&ProductData::name, + QStringLiteral("Products with different names per configuration are not " + "compatible with this generator. " + "Consider using the targetName property instead.")); +} + +CodeLocation GeneratableProductData::location() const +{ + return uniqueValue(&ProductData::location, + QStringLiteral("GeneratableProductData::location: internal bug; this should not happen.")); +} + +QStringList GeneratableProductData::dependencies() const +{ + return uniqueValue(&ProductData::dependencies, + QStringLiteral("Products with different dependency lists per configuration are not " + "compatible with this generator.")); +} + +QStringList GeneratableProductData::type() const +{ + return uniqueValue(&ProductData::type, + QStringLiteral("Products with different types per configuration are not " + "compatible with this generator.")); +} + +QString GeneratableProductData::buildDirectory() const +{ + return uniqueValue(&ProductData::buildDirectory, + QStringLiteral("GeneratableProductData::buildDirectory: " + "internal bug; this should not happen.")); +} + +QString GeneratableProjectData::name() const +{ + return uniqueValue(&ProjectData::name, + QStringLiteral("Projects with different names per configuration are not " + "compatible with this generator.")); +} + +CodeLocation GeneratableProjectData::location() const +{ + CodeLocation location; + QMapIterator it(data); + while (it.hasNext()) { + it.next(); + CodeLocation oldLocation = location; + location = it.value().location(); + if (oldLocation.isValid() && oldLocation != location) + throw ErrorInfo(QStringLiteral("Projects with different code locations " + "per configuration are not compatible with this " + "generator.")); + } + return location; +} + +GeneratableProjectData::Id GeneratableProjectData::uniqueName() const +{ + GeneratableProjectData::Id id; + id.value = name() + QLatin1Char('-') + location().toString(); + return id; +} + +QDir GeneratableProject::baseBuildDirectory() const +{ + Internal::Set baseBuildDirectory; + QMapIterator it(data); + while (it.hasNext()) { + it.next(); + QDir dir(it.value().buildDirectory()); + dir.cdUp(); + baseBuildDirectory.insert(dir.absolutePath()); + } + Q_ASSERT(baseBuildDirectory.size() == 1); + return *baseBuildDirectory.begin(); +} + +QFileInfo GeneratableProject::filePath() const +{ + Internal::Set filePath; + QMapIterator it(data); + while (it.hasNext()) { + it.next(); + filePath.insert(it.value().location().filePath()); + } + Q_ASSERT(filePath.size() == 1); + return *filePath.begin(); +} + +bool GeneratableProject::hasMultipleConfigurations() const +{ + return projects.size() > 1; +} + +QStringList GeneratableProject::commandLine() const +{ + QStringList combinedCommandLine; + QMapIterator it(commandLines); + while (it.hasNext()) { + it.next(); + combinedCommandLine << it.value(); + } + return combinedCommandLine; +} + +} // namespace qbs diff --git a/src/lib/corelib/generators/generatordata.h b/src/lib/corelib/generators/generatordata.h new file mode 100644 index 00000000..fd9bdde3 --- /dev/null +++ b/src/lib/corelib/generators/generatordata.h @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** 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 GENERATORDATA_H +#define GENERATORDATA_H + +#include +#include +#include +#include +#include +#include +#include + +namespace qbs { + +using GeneratableProjectMap = QMap; + +template struct IMultiplexableContainer { + QMap data; + + template T uniqueValue(const std::function &func, + const QString &errorMessage) const + { + if (data.empty()) + return T(); + 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 &func) const + { + for (auto it = data.cbegin(), end = data.cend(); it != end; ++it) + func(it.key(), it.value()); + } + + void forEach(const std::function &func) const + { + for (auto it = data.cbegin(), end = data.cend(); it != end; ++it) + func(it.key().toStdString(), it.value()); + } + + const U operator[](const QString &configurationName) const + { + return data[configurationName]; + } + + const U operator[](const std::string &configurationName) const + { + return data[QString::fromStdString(configurationName)]; + } + + bool isValid() const + { + return !data.empty(); + } + +protected: + IMultiplexableContainer() = default; +}; + +struct QBS_EXPORT GeneratableProductData : public IMultiplexableContainer { + QString name() const; + CodeLocation location() const; + QStringList dependencies() const; + QStringList type() const; + QString buildDirectory() const; +}; + +struct QBS_EXPORT GeneratableProjectData : public IMultiplexableContainer { + struct Id { + private: + friend struct GeneratableProjectData; + Id() = default; + QString value; + + public: + bool operator<(const Id &id) const { return value < id.value; } + }; + + QList subProjects; + QList products; + QString name() const; + CodeLocation location() const; + Id uniqueName() const; +}; + +struct QBS_EXPORT GeneratableProject : public GeneratableProjectData { + GeneratableProjectMap projects; + QMap buildConfigurations; + QMap commandLines; + InstallOptions installOptions; + QDir baseBuildDirectory() const; + QFileInfo filePath() const; + bool hasMultipleConfigurations() const; + QStringList commandLine() const; + + void forEach(const std::function &func) const + { + for (auto it = projects.cbegin(), end = projects.cend(); it != end; ++it) + func(it.key(), it.value()); + } + + void forEach(const std::function &func) const + { + for (auto it = projects.cbegin(), end = projects.cend(); it != end; ++it) + 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.empty() && !projects.empty(); + } + + 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 + +#endif // GENERATORDATA_H diff --git a/src/lib/corelib/generators/generators.pri b/src/lib/corelib/generators/generators.pri new file mode 100644 index 00000000..e9730d89 --- /dev/null +++ b/src/lib/corelib/generators/generators.pri @@ -0,0 +1,38 @@ +include(../../../install_prefix.pri) + +SOURCES += \ + $$PWD/generatableprojectiterator.cpp \ + $$PWD/generator.cpp \ + $$PWD/generatordata.cpp \ + $$PWD/generatorutils.cpp \ + $$PWD/generatorversioninfo.cpp \ + $$PWD/xmlproject.cpp \ + $$PWD/xmlprojectwriter.cpp\ + $$PWD/xmlproperty.cpp \ + $$PWD/xmlpropertygroup.cpp \ + $$PWD/xmlworkspace.cpp \ + $$PWD/xmlworkspacewriter.cpp + +HEADERS += \ + $$PWD/generatableprojectiterator.h \ + $$PWD/generator.h \ + $$PWD/generatordata.h \ + $$PWD/generatorutils.h \ + $$PWD/generatorversioninfo.h \ + $$PWD/igeneratableprojectvisitor.h \ + $$PWD/ixmlnodevisitor.h \ + $$PWD/ixmlnodevisitor.h \ + $$PWD/xmlproject.h \ + $$PWD/xmlprojectwriter.h \ + $$PWD/xmlproperty.h \ + $$PWD/xmlpropertygroup.h \ + $$PWD/xmlworkspace.h \ + $$PWD/xmlworkspacewriter.h + +!qbs_no_dev_install { + generators_headers.files = \ + $$PWD/generator.h \ + $$PWD/generatordata.h + generators_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/generators + INSTALLS += generators_headers +} diff --git a/src/lib/corelib/generators/generatorutils.cpp b/src/lib/corelib/generators/generatorutils.cpp new file mode 100644 index 00000000..3767c946 --- /dev/null +++ b/src/lib/corelib/generators/generatorutils.cpp @@ -0,0 +1,261 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "generatorutils.h" + +namespace qbs { +namespace gen { +namespace utils { + +QString architectureName(Architecture arch) +{ + switch (arch) { + case Architecture::Arm: + return QStringLiteral("arm"); + case Architecture::Avr: + return QStringLiteral("avr"); + case Architecture::Mcs51: + return QStringLiteral("mcs51"); + default: + return QStringLiteral("unknown"); + } +} + +Architecture architecture(const Project &qbsProject) +{ + const auto qbsArch = qbsProject.projectConfiguration() + .value(Internal::StringConstants::qbsModule()).toMap() + .value(QStringLiteral("architecture")).toString(); + + if (qbsArch == QLatin1String("arm")) + return Architecture::Arm; + if (qbsArch == QLatin1String("avr")) + return Architecture::Avr; + if (qbsArch == QLatin1String("mcs51")) + return Architecture::Mcs51; + if (qbsArch == QLatin1String("stm8")) + return Architecture::Stm8; + if (qbsArch == QLatin1String("msp430")) + return Architecture::Msp430; + return Architecture::Unknown; +} + +QString buildConfigurationName(const Project &qbsProject) +{ + return qbsProject.projectConfiguration() + .value(Internal::StringConstants::qbsModule()).toMap() + .value(QStringLiteral("configurationName")).toString(); +} + +int debugInformation(const ProductData &qbsProduct) +{ + return qbsProduct.moduleProperties().getModuleProperty( + Internal::StringConstants::qbsModule(), + QStringLiteral("debugInformation")) + .toInt(); +} + +QString buildRootPath(const Project &qbsProject) +{ + QDir dir(qbsProject.projectData().buildDirectory()); + dir.cdUp(); + return dir.absolutePath(); +} + +QString relativeFilePath(const QString &baseDirectory, + const QString &fullFilePath) +{ + return QDir(baseDirectory).relativeFilePath(fullFilePath); +} + +QString binaryOutputDirectory(const QString &baseDirectory, + const ProductData &qbsProduct) +{ + return QDir(baseDirectory).relativeFilePath( + qbsProduct.buildDirectory()) + + QLatin1String("/bin"); +} + +QString objectsOutputDirectory(const QString &baseDirectory, + const ProductData &qbsProduct) +{ + return QDir(baseDirectory).relativeFilePath( + qbsProduct.buildDirectory()) + + QLatin1String("/obj"); +} + +QString listingOutputDirectory(const QString &baseDirectory, + const ProductData &qbsProduct) +{ + return QDir(baseDirectory).relativeFilePath( + qbsProduct.buildDirectory()) + + QLatin1String("/lst"); +} + +std::vector dependenciesOf(const ProductData &qbsProduct, + const GeneratableProject &genProject, + const QString &configurationName) +{ + std::vector result; + const auto &depsNames = qbsProduct.dependencies(); + for (const auto &product : qAsConst(genProject.products)) { + const auto pt = product.type(); + if (!pt.contains(QLatin1String("staticlibrary"))) + continue; + const auto pn = product.name(); + if (!depsNames.contains(pn)) + continue; + result.push_back(product.data.value(configurationName)); + } + return result; +} + +QString targetBinary(const ProductData &qbsProduct) +{ + const auto &type = qbsProduct.type(); + if (type.contains(QLatin1String("application"))) { + return QFileInfo(qbsProduct.targetExecutable()).fileName(); + } else if (type.contains(QLatin1String("staticlibrary"))) { + for (const auto &artifact : qbsProduct.targetArtifacts()) { + if (artifact.fileTags().contains(QLatin1String("staticlibrary"))) + return QFileInfo(artifact.filePath()).fileName(); + } + } + + return {}; +} + +QString targetBinaryPath(const QString &baseDirectory, + const ProductData &qbsProduct) +{ + return binaryOutputDirectory(baseDirectory, qbsProduct) + + QLatin1Char('/') + targetBinary(qbsProduct); +} + +QString cppStringModuleProperty(const PropertyMap &qbsProps, + const QString &propertyName) +{ + return qbsProps.getModuleProperty( + Internal::StringConstants::cppModule(), + propertyName).toString().trimmed(); +} + +bool cppBooleanModuleProperty(const PropertyMap &qbsProps, + const QString &propertyName) +{ + return qbsProps.getModuleProperty( + Internal::StringConstants::cppModule(), + propertyName).toBool(); +} + +int cppIntegerModuleProperty(const PropertyMap &qbsProps, + const QString &propertyName) +{ + return qbsProps.getModuleProperty( + Internal::StringConstants::cppModule(), + propertyName).toInt(); +} + +QStringList cppStringModuleProperties(const PropertyMap &qbsProps, + const QStringList &propertyNames) +{ + QStringList properties; + for (const auto &propertyName : propertyNames) { + const auto entries = qbsProps.getModuleProperty( + Internal::StringConstants::cppModule(), + propertyName).toStringList(); + for (const auto &entry : entries) + properties.push_back(entry.trimmed()); + } + return properties; +} + +QVariantList cppVariantModuleProperties(const PropertyMap &qbsProps, + const QStringList &propertyNames) +{ + QVariantList properties; + for (const auto &propertyName : propertyNames) { + properties << qbsProps.getModuleProperty( + Internal::StringConstants::cppModule(), + propertyName).toList(); + } + return properties; +} + +static QString parseFlagValue(const QString &flagKey, + QStringList::const_iterator &flagIt, + const QStringList::const_iterator &flagEnd) +{ + if (flagIt->contains(QLatin1Char('='))) { + // In this case an option is in form of 'flagKey='. + const auto parts = flagIt->split(QLatin1Char('=')); + if (parts.count() == 2) + return parts.at(1).trimmed(); + } else if (flagKey < *flagIt) { + // In this case an option is in form of 'flagKey'. + return flagIt->mid(flagKey.count()).trimmed(); + } else { + // In this case an option is in form of 'flagKey '. + ++flagIt; + if (flagIt < flagEnd && !flagIt->startsWith(QLatin1Char('-'))) + return (*flagIt).trimmed(); + } + return {}; +} + +QString firstFlagValue(const QStringList &flags, const QString &flagKey) +{ + const auto flagBegin = flags.cbegin(); + const auto flagEnd = flags.cend(); + auto flagIt = std::find_if(flagBegin, flagEnd, [flagKey](const QString &flag) { + return flag == flagKey || flag.startsWith(flagKey); + }); + if (flagIt == flagEnd) + return {}; + return parseFlagValue(flagKey, flagIt, flagEnd); +} + +QStringList allFlagValues(const QStringList &flags, const QString &flagKey) +{ + QStringList values; + const auto flagEnd = flags.cend(); + for (auto flagIt = flags.cbegin(); flagIt < flagEnd; ++flagIt) { + if (*flagIt == flagKey || flagIt->startsWith(flagKey)) { + const QString value = parseFlagValue(flagKey, flagIt, flagEnd); + if (!value.isEmpty()) + values.push_back(value); + } + } + return values; +} + +} // namespace utils +} // namespace gen +} // namespace qbs diff --git a/src/lib/corelib/generators/generatorutils.h b/src/lib/corelib/generators/generatorutils.h new file mode 100644 index 00000000..58e59cbf --- /dev/null +++ b/src/lib/corelib/generators/generatorutils.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef GENERATORS_UTILS_H +#define GENERATORS_UTILS_H + +#include + +#include +#include + +namespace qbs { +namespace gen { +namespace utils { + +enum class Architecture { + Unknown = 0, + Arm = 1 << 1, + Avr = 1 << 2, + Mcs51 = 1 << 3, + Stm8 = 1 << 4, + Msp430 = 1 << 5 +}; + +Q_DECLARE_FLAGS(ArchitectureFlags, Architecture) +Q_DECLARE_OPERATORS_FOR_FLAGS(ArchitectureFlags) + +QBS_EXPORT QString architectureName(Architecture arch); +QBS_EXPORT Architecture architecture(const Project &qbsProject); +QBS_EXPORT QString buildConfigurationName(const Project &qbsProject); +QBS_EXPORT int debugInformation(const ProductData &qbsProduct); +QBS_EXPORT QString buildRootPath(const Project &qbsProject); +QBS_EXPORT QString relativeFilePath(const QString &baseDirectory, + const QString &fullFilePath); +QBS_EXPORT QString binaryOutputDirectory(const QString &baseDirectory, + const ProductData &qbsProduct); +QBS_EXPORT QString objectsOutputDirectory(const QString &baseDirectory, + const ProductData &qbsProduct); +QBS_EXPORT QString listingOutputDirectory(const QString &baseDirectory, + const ProductData &qbsProduct); +QBS_EXPORT std::vector dependenciesOf(const ProductData &qbsProduct, + const GeneratableProject &genProject, + const QString &configurationName); +QBS_EXPORT QString targetBinary(const ProductData &qbsProduct); +QBS_EXPORT QString targetBinaryPath(const QString &baseDirectory, + const ProductData &qbsProduct); +QBS_EXPORT QString cppStringModuleProperty(const PropertyMap &qbsProps, + const QString &propertyName); +QBS_EXPORT bool cppBooleanModuleProperty(const PropertyMap &qbsProps, + const QString &propertyName); +QBS_EXPORT int cppIntegerModuleProperty(const PropertyMap &qbsProps, + const QString &propertyName); +QBS_EXPORT QStringList cppStringModuleProperties(const PropertyMap &qbsProps, + const QStringList &propertyNames); +QBS_EXPORT QVariantList cppVariantModuleProperties(const PropertyMap &qbsProps, + const QStringList &propertyNames); +QBS_EXPORT QString firstFlagValue(const QStringList &flags, + const QString &flagKey); +QBS_EXPORT QStringList allFlagValues(const QStringList &flags, + const QString &flagKey); + +template +bool inBounds(const T &value, const T &low, const T &high) +{ + return !(value < low) && !(high < value); +} + +} // namespace utils +} // namespace gen +} // namespace qbs + +#endif // GENERATORS_UTILS_H diff --git a/src/lib/corelib/generators/generatorversioninfo.cpp b/src/lib/corelib/generators/generatorversioninfo.cpp new file mode 100644 index 00000000..c5c8db03 --- /dev/null +++ b/src/lib/corelib/generators/generatorversioninfo.cpp @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "generatorversioninfo.h" + +namespace qbs { +namespace gen { + +int VersionInfo::marketingVersion() const +{ + return m_version.majorVersion(); +} + +} // namespace gen +} // namespace qbs diff --git a/src/lib/corelib/generators/generatorversioninfo.h b/src/lib/corelib/generators/generatorversioninfo.h new file mode 100644 index 00000000..38616eb9 --- /dev/null +++ b/src/lib/corelib/generators/generatorversioninfo.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 GENERATORS_VERSION_INFO_H +#define GENERATORS_VERSION_INFO_H + +#include "generatorutils.h" + +#include +#include + +#include + +namespace qbs { +namespace gen { + +class QBS_EXPORT VersionInfo +{ +public: + constexpr VersionInfo(const Version &version, utils::ArchitectureFlags archs) + : m_version(version), m_archs(archs) + { + } + + constexpr bool operator<(const VersionInfo &other) const { return m_version < other.m_version; } + constexpr bool operator==(const VersionInfo &other) const + { + return m_version == other.m_version && m_archs == other.m_archs; + } + + constexpr Version version() const { return m_version; } + constexpr bool containsArchitecture(utils::Architecture arch) const { return m_archs & arch; } + + int marketingVersion() const; + +private: + Version m_version; + utils::ArchitectureFlags m_archs; +}; + +inline quint32 qHash(const VersionInfo &info) +{ + return qHash(info.version().toString()); +} + +} // namespace gen +} // namespace qbs + +#endif // GENERATORS_VERSION_INFO_H diff --git a/src/lib/corelib/generators/igeneratableprojectvisitor.h b/src/lib/corelib/generators/igeneratableprojectvisitor.h new file mode 100644 index 00000000..dbf73b4e --- /dev/null +++ b/src/lib/corelib/generators/igeneratableprojectvisitor.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** 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 IGENERATABLEPROJECTVISITOR_H +#define IGENERATABLEPROJECTVISITOR_H + +#include "generatordata.h" + +namespace qbs { + +class IGeneratableProjectVisitor { +public: + virtual ~IGeneratableProjectVisitor() = default; + + // Collapsed configurations + virtual void visitProject(const GeneratableProject &project) { + Q_UNUSED(project); + } + + virtual void visitProjectData(const GeneratableProject &project, + const GeneratableProjectData &parentProjectData, + const GeneratableProjectData &projectData) { + Q_UNUSED(project); + Q_UNUSED(parentProjectData); + Q_UNUSED(projectData); + } + + virtual void visitProjectData(const GeneratableProject &project, + const GeneratableProjectData &projectData) { + Q_UNUSED(project); + Q_UNUSED(projectData); + } + + virtual void visitProduct(const GeneratableProject &project, + const GeneratableProjectData &projectData, + const GeneratableProductData &productData) { + Q_UNUSED(project); + Q_UNUSED(projectData); + Q_UNUSED(productData); + } + + // Expanded configurations + virtual void visitProject(const Project &project, const QString &configuration) { + Q_UNUSED(project); + Q_UNUSED(configuration); + } + + virtual void visitProjectData(const ProjectData &parentProjectData, + const ProjectData &projectData, + const QString &configuration) { + Q_UNUSED(parentProjectData); + Q_UNUSED(projectData); + Q_UNUSED(configuration); + } + + virtual void visitProjectData(const ProjectData &projectData, + const QString &configuration) { + Q_UNUSED(projectData); + Q_UNUSED(configuration); + } + + virtual void visitProduct(const ProductData &productData, + const QString &configuration) { + Q_UNUSED(productData); + Q_UNUSED(configuration); + } +}; + +} // namespace qbs + +#endif // IGENERATABLEPROJECTVISITOR_H diff --git a/src/lib/corelib/generators/ixmlnodevisitor.h b/src/lib/corelib/generators/ixmlnodevisitor.h new file mode 100644 index 00000000..278f4cf2 --- /dev/null +++ b/src/lib/corelib/generators/ixmlnodevisitor.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef GENERATORS_XML_INODE_VISITOR_H +#define GENERATORS_XML_INODE_VISITOR_H + +#include + +#include + +namespace qbs { +namespace gen { +namespace xml { + +class Project; +class Property; +class PropertyGroup; +class Workspace; + +class QBS_EXPORT INodeVisitor +{ +public: + virtual ~INodeVisitor() = default; + + virtual void visitWorkspaceStart(const Workspace *workspace) { Q_UNUSED(workspace) } + virtual void visitWorkspaceEnd(const Workspace *workspace) { Q_UNUSED(workspace) } + + virtual void visitProjectStart(const Project *project) { Q_UNUSED(project) } + virtual void visitProjectEnd(const Project *project) { Q_UNUSED(project) } + + virtual void visitPropertyStart(const Property *property) = 0; + virtual void visitPropertyEnd(const Property *property) = 0; + + virtual void visitPropertyGroupStart(const PropertyGroup *propertyGroup) = 0; + virtual void visitPropertyGroupEnd(const PropertyGroup *propertyGroup) = 0; +}; + +} // namespace xml +} // namespace gen +} // namespace qbs + +#endif // GENERATORS_XML_INODE_VISITOR_H diff --git a/src/lib/corelib/generators/xmlproject.cpp b/src/lib/corelib/generators/xmlproject.cpp new file mode 100644 index 00000000..e2ac951a --- /dev/null +++ b/src/lib/corelib/generators/xmlproject.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "xmlproject.h" +#include "ixmlnodevisitor.h" + +namespace qbs { +namespace gen { +namespace xml { + +void Project::accept(INodeVisitor *visitor) const +{ + visitor->visitProjectStart(this); + + for (const auto &child : children()) + child->accept(visitor); + + visitor->visitProjectEnd(this); +} + +} // namespace xml +} // namespace gen +} // namespace qbs diff --git a/src/lib/corelib/generators/xmlproject.h b/src/lib/corelib/generators/xmlproject.h new file mode 100644 index 00000000..a7f5b2b6 --- /dev/null +++ b/src/lib/corelib/generators/xmlproject.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef GENERATORS_XML_PROJECT_H +#define GENERATORS_XML_PROJECT_H + +#include "xmlproperty.h" + +#include + +#include + +namespace qbs { +namespace gen { +namespace xml { + +class QBS_EXPORT Project : public Property +{ +public: + void accept(INodeVisitor *visitor) const final; +}; + +} // namespace xml +} // namespace gen +} // namespace qbs + +#endif // GENERATORS_XML_PROJECT_H diff --git a/src/lib/corelib/generators/xmlprojectwriter.cpp b/src/lib/corelib/generators/xmlprojectwriter.cpp new file mode 100644 index 00000000..190e1304 --- /dev/null +++ b/src/lib/corelib/generators/xmlprojectwriter.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "xmlproject.h" +#include "xmlprojectwriter.h" +#include "xmlproperty.h" +#include "xmlpropertygroup.h" + +#include +#include + +namespace qbs { +namespace gen { +namespace xml { + +ProjectWriter::ProjectWriter(std::ostream *device) + : m_device(device) +{ + m_writer = std::make_unique(&m_buffer); + m_writer->setAutoFormatting(true); +} + +bool ProjectWriter::write(const Project *project) +{ + m_buffer.clear(); + m_writer->writeStartDocument(); + project->accept(this); + m_writer->writeEndDocument(); + if (m_writer->hasError()) + return false; + m_device->write(&*std::begin(m_buffer), m_buffer.size()); + return m_device->good(); +} + +void ProjectWriter::visitPropertyStart(const Property *property) +{ + const auto value = property->value().toString(); + const auto name = QString::fromUtf8(property->name()); + m_writer->writeTextElement(name, value); +} + +void ProjectWriter::visitPropertyEnd(const Property *property) +{ + Q_UNUSED(property) +} + +void ProjectWriter::visitPropertyGroupStart(const PropertyGroup *propertyGroup) +{ + const auto name = QString::fromUtf8(propertyGroup->name()); + m_writer->writeStartElement(name); +} + +void ProjectWriter::visitPropertyGroupEnd(const PropertyGroup *propertyGroup) +{ + Q_UNUSED(propertyGroup) + m_writer->writeEndElement(); +} + +QXmlStreamWriter *ProjectWriter::writer() const +{ + return m_writer.get(); +} + +} // namespace xml +} // namespace gen +} // namespace qbs diff --git a/src/lib/corelib/generators/xmlprojectwriter.h b/src/lib/corelib/generators/xmlprojectwriter.h new file mode 100644 index 00000000..8198de61 --- /dev/null +++ b/src/lib/corelib/generators/xmlprojectwriter.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef GENERATORS_XML_PROJECT_WRITER_H +#define GENERATORS_XML_PROJECT_WRITER_H + +#include "ixmlnodevisitor.h" + +#include + +#include + +namespace qbs { +namespace gen { +namespace xml { + +class QBS_EXPORT ProjectWriter : public INodeVisitor +{ + Q_DISABLE_COPY(ProjectWriter) +public: + explicit ProjectWriter(std::ostream *device); + bool write(const Project *project); + +protected: + QXmlStreamWriter *writer() const; + +private: + void visitPropertyStart(const Property *property) final; + void visitPropertyEnd(const Property *property) final; + + void visitPropertyGroupStart(const PropertyGroup *propertyGroup) final; + void visitPropertyGroupEnd(const PropertyGroup *propertyGroup) final; + + std::ostream *m_device = nullptr; + QByteArray m_buffer; + std::unique_ptr m_writer; +}; + +} // namespace xml +} // namespace gen +} // namespace qbs + +#endif // GENERATORS_XML_PROJECT_WRITER_H diff --git a/src/lib/corelib/generators/xmlproperty.cpp b/src/lib/corelib/generators/xmlproperty.cpp new file mode 100644 index 00000000..2fe5a014 --- /dev/null +++ b/src/lib/corelib/generators/xmlproperty.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "ixmlnodevisitor.h" +#include "xmlproperty.h" + +namespace qbs { +namespace gen { +namespace xml { + +Property::Property(QByteArray name, QVariant value) +{ + setName(std::move(name)); + setValue(std::move(value)); +} + +void Property::accept(INodeVisitor *visitor) const +{ + visitor->visitPropertyStart(this); + + for (const auto &child : children()) + child->accept(visitor); + + visitor->visitPropertyEnd(this); +} + +} // namespace xml +} // namespace gen +} // namespace qbs diff --git a/src/lib/corelib/generators/xmlproperty.h b/src/lib/corelib/generators/xmlproperty.h new file mode 100644 index 00000000..79573588 --- /dev/null +++ b/src/lib/corelib/generators/xmlproperty.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef GENERATORS_XML_PROPERTY_H +#define GENERATORS_XML_PROPERTY_H + +#include + +#include + +#include + +namespace qbs { +namespace gen { +namespace xml { + +class INodeVisitor; + +class QBS_EXPORT Property +{ + Q_DISABLE_COPY(Property) +public: + Property() = default; + explicit Property(QByteArray name, QVariant value); + virtual ~Property() = default; + + QByteArray name() const { return m_name; } + void setName(QByteArray name) { m_name = std::move(name); } + + QVariant value() const { return m_value; } + void setValue(QVariant value) { m_value = std::move(value); } + + template + T *appendChild(std::unique_ptr child) { + const auto p = child.get(); + m_children.push_back(std::move(child)); + return p; + } + + template + T *appendChild(Args&&... args) { + return appendChild(std::make_unique(std::forward(args)...)); + } + + virtual void accept(INodeVisitor *visitor) const; + +protected: + const std::vector> &children() const + { return m_children; } + +private: + QByteArray m_name; + QVariant m_value; + std::vector> m_children; +}; + +} // namespace xml +} // namespace gen +} // namespace qbs + +#endif // GENERATORS_XML_PROPERTY_H diff --git a/src/lib/corelib/generators/xmlpropertygroup.cpp b/src/lib/corelib/generators/xmlpropertygroup.cpp new file mode 100644 index 00000000..c9e6a97c --- /dev/null +++ b/src/lib/corelib/generators/xmlpropertygroup.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "ixmlnodevisitor.h" +#include "xmlpropertygroup.h" + +namespace qbs { +namespace gen { +namespace xml { + +PropertyGroup::PropertyGroup(QByteArray name) +{ + setName(std::move(name)); +} + +void PropertyGroup::appendProperty(QByteArray name, QVariant value) +{ + appendChild(std::move(name), std::move(value)); +} + +void PropertyGroup::appendMultiLineProperty(QByteArray key, const QStringList &values, QChar sep) +{ + const auto line = values.join(sep); + appendProperty(std::move(key), QVariant::fromValue(line)); +} + +void PropertyGroup::accept(INodeVisitor *visitor) const +{ + visitor->visitPropertyGroupStart(this); + + for (const auto &child : children()) + child->accept(visitor); + + visitor->visitPropertyGroupEnd(this); +} + +} // namespace xml +} // namespace gen +} // namespace qbs diff --git a/src/lib/corelib/generators/xmlpropertygroup.h b/src/lib/corelib/generators/xmlpropertygroup.h new file mode 100644 index 00000000..e7e051a8 --- /dev/null +++ b/src/lib/corelib/generators/xmlpropertygroup.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef GENERATORS_XML_PROPERTY_GROUP_H +#define GENERATORS_XML_PROPERTY_GROUP_H + +#include "generatorversioninfo.h" +#include "xmlproperty.h" + +#include + +#include + +namespace qbs { + +class ProductData; +class Project; + +namespace gen { +namespace xml { + +class QBS_EXPORT PropertyGroup : public Property +{ +public: + explicit PropertyGroup(QByteArray name); + + void appendProperty(QByteArray name, QVariant value); + void appendMultiLineProperty(QByteArray key, const QStringList &values, + QChar sep = QLatin1Char(',')); + + void accept(INodeVisitor *visitor) const final; +}; + +class PropertyGroupFactory +{ +public: + virtual ~PropertyGroupFactory() = default; + virtual bool canCreate(utils::Architecture arch, + const Version &version) const = 0; + + virtual std::unique_ptr create( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps) const = 0; +}; + +} // namespace xml +} // namespace gen +} // namespace qbs + +#endif // GENERATORS_XML_PROPERTY_GROUP_H diff --git a/src/lib/corelib/generators/xmlworkspace.cpp b/src/lib/corelib/generators/xmlworkspace.cpp new file mode 100644 index 00000000..7ce3f516 --- /dev/null +++ b/src/lib/corelib/generators/xmlworkspace.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "ixmlnodevisitor.h" +#include "xmlproperty.h" +#include "xmlpropertygroup.h" +#include "xmlworkspace.h" + +namespace qbs { +namespace gen { +namespace xml { + +Workspace::Workspace(const QString &workspacePath) + : m_baseDirectory(QFileInfo(workspacePath).absoluteDir()) +{ +} + +void Workspace::accept(INodeVisitor *visitor) const +{ + visitor->visitWorkspaceStart(this); + + for (const auto &child : children()) + child->accept(visitor); + + visitor->visitWorkspaceEnd(this); +} + +} // namespace xml +} // namespace gen +} // namespace qbs diff --git a/src/lib/corelib/generators/xmlworkspace.h b/src/lib/corelib/generators/xmlworkspace.h new file mode 100644 index 00000000..beab22c4 --- /dev/null +++ b/src/lib/corelib/generators/xmlworkspace.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 GENERATORS_XML_WWORKSPACE_H +#define GENERATORS_XML_WWORKSPACE_H + +#include "xmlproperty.h" + +#include + +#include + +namespace qbs { +namespace gen { +namespace xml { + +class QBS_EXPORT Workspace : public Property +{ +public: + explicit Workspace(const QString &workspacePath); + void accept(INodeVisitor *visitor) const final; + + virtual void addProject(const QString &projectPath) = 0; + +protected: + const QDir m_baseDirectory; +}; + +} // namespace xml +} // namespace gen +} // namespace qbs + +#endif // GENERATORS_XML_WWORKSPACE_H diff --git a/src/lib/corelib/generators/xmlworkspacewriter.cpp b/src/lib/corelib/generators/xmlworkspacewriter.cpp new file mode 100644 index 00000000..d21b63c4 --- /dev/null +++ b/src/lib/corelib/generators/xmlworkspacewriter.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "xmlproperty.h" +#include "xmlpropertygroup.h" +#include "xmlworkspace.h" +#include "xmlworkspacewriter.h" + +#include +#include + +namespace qbs { +namespace gen { +namespace xml { + +WorkspaceWriter::WorkspaceWriter(std::ostream *device) + : m_device(device) +{ + m_writer = std::make_unique(&m_buffer); + m_writer->setAutoFormatting(true); +} + +bool WorkspaceWriter::write(const Workspace *workspace) +{ + m_buffer.clear(); + m_writer->writeStartDocument(); + workspace->accept(this); + m_writer->writeEndDocument(); + if (m_writer->hasError()) + return false; + m_device->write(&*std::begin(m_buffer), m_buffer.size()); + return m_device->good(); +} + +void WorkspaceWriter::visitPropertyStart(const Property *property) +{ + const auto value = property->value().toString(); + const auto name = QString::fromUtf8(property->name()); + m_writer->writeTextElement(name, value); +} + +void WorkspaceWriter::visitPropertyEnd(const Property *property) +{ + Q_UNUSED(property) +} + +void WorkspaceWriter::visitPropertyGroupStart(const PropertyGroup *propertyGroup) +{ + const auto name = QString::fromUtf8(propertyGroup->name()); + m_writer->writeStartElement(name); +} + +void WorkspaceWriter::visitPropertyGroupEnd(const PropertyGroup *propertyGroup) +{ + Q_UNUSED(propertyGroup) + m_writer->writeEndElement(); +} + +QXmlStreamWriter *WorkspaceWriter::writer() const +{ + return m_writer.get(); +} + +} // namespace xml +} // namespace gen +} // namespace qbs diff --git a/src/lib/corelib/generators/xmlworkspacewriter.h b/src/lib/corelib/generators/xmlworkspacewriter.h new file mode 100644 index 00000000..343face5 --- /dev/null +++ b/src/lib/corelib/generators/xmlworkspacewriter.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef GENERATORS_XML_WORKSPACE_WRITER_H +#define GENERATORS_XML_WORKSPACE_WRITER_H + +#include "ixmlnodevisitor.h" + +#include + +#include + +namespace qbs { +namespace gen { +namespace xml { + +class QBS_EXPORT WorkspaceWriter : public INodeVisitor +{ + Q_DISABLE_COPY(WorkspaceWriter) +public: + explicit WorkspaceWriter(std::ostream *device); + bool write(const Workspace *workspace); + +protected: + QXmlStreamWriter *writer() const; + +private: + void visitPropertyStart(const Property *property) final; + void visitPropertyEnd(const Property *property) final; + + void visitPropertyGroupStart(const PropertyGroup *propertyGroup) final; + void visitPropertyGroupEnd(const PropertyGroup *propertyGroup) final; + + std::ostream *m_device = nullptr; + QByteArray m_buffer; + std::unique_ptr m_writer; +}; + +} // namespace xml +} // namespace gen +} // namespace qbs + +#endif // GENERATORS_XML_WORKSPACE_WRITER_H diff --git a/src/lib/corelib/jsextensions/binaryfile.cpp b/src/lib/corelib/jsextensions/binaryfile.cpp new file mode 100644 index 00000000..f02f0bff --- /dev/null +++ b/src/lib/corelib/jsextensions/binaryfile.cpp @@ -0,0 +1,276 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Sergey Belyashov +** Copyright (C) 2017 Denis Shienkov +** 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 +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +class BinaryFile : public QObject, public QScriptable, public ResourceAcquiringScriptObject +{ + Q_OBJECT + Q_ENUMS(OpenMode) +public: + enum OpenMode { + ReadOnly = 1, + WriteOnly = 2, + ReadWrite = ReadOnly | WriteOnly + }; + + static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); + ~BinaryFile() override; + + Q_INVOKABLE void close(); + Q_INVOKABLE QString filePath(); + Q_INVOKABLE bool atEof() const; + Q_INVOKABLE qint64 size() const; + Q_INVOKABLE void resize(qint64 size); + Q_INVOKABLE qint64 pos() const; + Q_INVOKABLE void seek(qint64 pos); + Q_INVOKABLE QVariantList read(qint64 size); + Q_INVOKABLE void write(const QVariantList &data); + +private: + explicit BinaryFile(QScriptContext *context, const QString &filePath, OpenMode mode = ReadOnly); + + bool checkForClosed() const; + + // ResourceAcquiringScriptObject implementation + void releaseResources() override; + + QFile *m_file = nullptr; +}; + +QScriptValue BinaryFile::ctor(QScriptContext *context, QScriptEngine *engine) +{ + BinaryFile *t = nullptr; + switch (context->argumentCount()) { + case 0: + return context->throwError(Tr::tr("BinaryFile constructor needs " + "path of file to be opened.")); + case 1: + t = new BinaryFile(context, context->argument(0).toString()); + break; + case 2: + t = new BinaryFile(context, + context->argument(0).toString(), + static_cast(context->argument(1).toInt32())); + break; + default: + return context->throwError(Tr::tr("BinaryFile constructor takes at most two parameters.")); + } + + const auto se = static_cast(engine); + se->addResourceAcquiringScriptObject(t); + const DubiousContextList dubiousContexts { + DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) + }; + se->checkContext(QStringLiteral("qbs.BinaryFile"), dubiousContexts); + se->setUsesIo(); + + return engine->newQObject(t, QScriptEngine::QtOwnership); +} + +BinaryFile::~BinaryFile() +{ + delete m_file; +} + +BinaryFile::BinaryFile(QScriptContext *context, const QString &filePath, OpenMode mode) +{ + Q_ASSERT(thisObject().engine() == engine()); + + QIODevice::OpenMode m = QIODevice::NotOpen; + switch (mode) { + case ReadWrite: + m = QIODevice::ReadWrite; + break; + case ReadOnly: + m = QIODevice::ReadOnly; + break; + case WriteOnly: + m = QIODevice::WriteOnly; + break; + default: + context->throwError(Tr::tr("Unable to open file '%1': Undefined mode '%2'") + .arg(filePath, mode)); + return; + } + + m_file = new QFile(filePath); + if (Q_UNLIKELY(!m_file->open(m))) { + context->throwError(Tr::tr("Unable to open file '%1': %2") + .arg(filePath, m_file->errorString())); + delete m_file; + m_file = nullptr; + } +} + +void BinaryFile::close() +{ + if (checkForClosed()) + return; + m_file->close(); + delete m_file; + m_file = nullptr; +} + +QString BinaryFile::filePath() +{ + if (checkForClosed()) + return {}; + return QFileInfo(*m_file).absoluteFilePath(); +} + +bool BinaryFile::atEof() const +{ + if (checkForClosed()) + return true; + return m_file->atEnd(); +} + +qint64 BinaryFile::size() const +{ + if (checkForClosed()) + return -1; + return m_file->size(); +} + +void BinaryFile::resize(qint64 size) +{ + if (checkForClosed()) + return; + if (Q_UNLIKELY(!m_file->resize(size))) { + context()->throwError(Tr::tr("Could not resize '%1': %2") + .arg(m_file->fileName(), m_file->errorString())); + } +} + +qint64 BinaryFile::pos() const +{ + if (checkForClosed()) + return -1; + return m_file->pos(); +} + +void BinaryFile::seek(qint64 pos) +{ + if (checkForClosed()) + return; + if (Q_UNLIKELY(!m_file->seek(pos))) { + context()->throwError(Tr::tr("Could not seek '%1': %2") + .arg(m_file->fileName(), m_file->errorString())); + } +} + +QVariantList BinaryFile::read(qint64 size) +{ + if (checkForClosed()) + return {}; + const QByteArray bytes = m_file->read(size); + if (Q_UNLIKELY(bytes.size() == 0 && m_file->error() != QFile::NoError)) { + context()->throwError(Tr::tr("Could not read from '%1': %2") + .arg(m_file->fileName(), m_file->errorString())); + } + + QVariantList data; + std::for_each(bytes.constBegin(), bytes.constEnd(), [&data](const char &c) { + data.append(c); }); + return data; +} + +void BinaryFile::write(const QVariantList &data) +{ + if (checkForClosed()) + return; + + QByteArray bytes; + std::for_each(data.constBegin(), data.constEnd(), [&bytes](const QVariant &v) { + bytes.append(char(v.toUInt() & 0xFF)); }); + + const qint64 size = m_file->write(bytes); + if (Q_UNLIKELY(size == -1)) { + context()->throwError(Tr::tr("Could not write to '%1': %2") + .arg(m_file->fileName(), m_file->errorString())); + } +} + +bool BinaryFile::checkForClosed() const +{ + if (m_file) + return false; + if (QScriptContext *ctx = context()) + ctx->throwError(Tr::tr("Access to BinaryFile object that was already closed.")); + return true; +} + +void BinaryFile::releaseResources() +{ + close(); + deleteLater(); +} + +} // namespace Internal +} // namespace qbs + +void initializeJsExtensionBinaryFile(QScriptValue extensionObject) +{ + using namespace qbs::Internal; + QScriptEngine *engine = extensionObject.engine(); + const QScriptValue obj = engine->newQMetaObject(&BinaryFile::staticMetaObject, + engine->newFunction(&BinaryFile::ctor)); + extensionObject.setProperty(QStringLiteral("BinaryFile"), obj); +} + +Q_DECLARE_METATYPE(qbs::Internal::BinaryFile *) + +#include "binaryfile.moc" diff --git a/src/lib/corelib/jsextensions/domxml.cpp b/src/lib/corelib/jsextensions/domxml.cpp new file mode 100644 index 00000000..86e1574c --- /dev/null +++ b/src/lib/corelib/jsextensions/domxml.cpp @@ -0,0 +1,465 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 BogDan Vatra +** 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 + +#include +#include +#include + +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class XmlDomDocument; + +class XmlDomNode: public QObject, public QScriptable +{ + Q_OBJECT +public: + static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); + + Q_INVOKABLE bool isElement() const; + Q_INVOKABLE bool isCDATASection() const; + Q_INVOKABLE bool isText() const; + + Q_INVOKABLE QString attribute(const QString & name, const QString & defValue = QString()); + Q_INVOKABLE void setAttribute(const QString & name, const QString & value); + Q_INVOKABLE bool hasAttribute(const QString & name) const; + Q_INVOKABLE QString tagName() const; + Q_INVOKABLE void setTagName(const QString & name); + + Q_INVOKABLE QString text() const; + + Q_INVOKABLE QString data() const; + Q_INVOKABLE void setData(const QString &v) const; + + Q_INVOKABLE void clear(); + Q_INVOKABLE bool hasAttributes() const; + Q_INVOKABLE bool hasChildNodes() const; + Q_INVOKABLE QScriptValue parentNode() const; + Q_INVOKABLE QScriptValue firstChild(const QString & tagName = QString()); + Q_INVOKABLE QScriptValue lastChild(const QString & tagName = QString()) const; + Q_INVOKABLE QScriptValue previousSibling(const QString & tagName = QString()) const; + Q_INVOKABLE QScriptValue nextSibling(const QString & tagName = QString()) const; + + Q_INVOKABLE QScriptValue appendChild(const QScriptValue &newChild); + Q_INVOKABLE QScriptValue insertBefore(const QScriptValue& newChild, const QScriptValue& refChild); + Q_INVOKABLE QScriptValue insertAfter(const QScriptValue& newChild, const QScriptValue& refChild); + Q_INVOKABLE QScriptValue replaceChild(const QScriptValue& newChild, const QScriptValue& oldChild); + Q_INVOKABLE QScriptValue removeChild(const QScriptValue& oldChild); + +protected: + friend class XmlDomDocument; + XmlDomNode(const QDomNode &other = QDomNode()); + QDomNode m_domNode; +}; + +class XmlDomDocument: public XmlDomNode +{ + Q_OBJECT +public: + static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); + Q_INVOKABLE QScriptValue documentElement(); + Q_INVOKABLE QScriptValue createElement(const QString & tagName); + Q_INVOKABLE QScriptValue createCDATASection(const QString & value); + Q_INVOKABLE QScriptValue createTextNode(const QString & value); + + Q_INVOKABLE bool setContent(const QString & content); + Q_INVOKABLE QString toString(int indent = 1); + + Q_INVOKABLE void save(const QString & filePath, int indent = 1); + Q_INVOKABLE void load(const QString & filePath); + +protected: + XmlDomDocument(QScriptContext *context, const QString &name = QString()); + +private: + QDomDocument m_domDocument; +}; + +QScriptValue XmlDomDocument::ctor(QScriptContext *context, QScriptEngine *engine) +{ + XmlDomDocument *xml = nullptr; + switch (context->argumentCount()) { + case 0: + xml = new XmlDomDocument(context); + break; + case 1: + xml = new XmlDomDocument(context, context->argument(0).toString()); + break; + default: + return context->throwError(QStringLiteral("DomXml(QString file = QLatin1String(\"\"))")); + } + QScriptValue obj = engine->newQObject(xml, QScriptEngine::ScriptOwnership); + static_cast(engine)->setUsesIo(); + return obj; +} + +QScriptValue XmlDomDocument::documentElement() +{ + return engine()->newQObject(new XmlDomNode(m_domDocument.documentElement()), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomDocument::createElement(const QString &tagName) +{ + return engine()->newQObject(new XmlDomNode(m_domDocument.createElement(tagName)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomDocument::createCDATASection(const QString &value) +{ + return engine()->newQObject(new XmlDomNode(m_domDocument.createCDATASection(value)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomDocument::createTextNode(const QString &value) +{ + return engine()->newQObject(new XmlDomNode(m_domDocument.createTextNode(value)), QScriptEngine::ScriptOwnership); +} + +bool XmlDomDocument::setContent(const QString &content) +{ + return m_domDocument.setContent(content); +} + +QString XmlDomDocument::toString(int indent) +{ + return m_domDocument.toString(indent); +} + +void XmlDomDocument::save(const QString &filePath, int indent) +{ + QFile f(filePath); + if (!f.open(QIODevice::WriteOnly)) { + context()->throwError(QStringLiteral("unable to open '%1'") + .arg(filePath)); + return; + } + + QByteArray buff(m_domDocument.toByteArray(indent)); + if (buff.size() != f.write(buff)) + { + context()->throwError(f.errorString()); + f.close(); + return; + } + + f.close(); + if (f.error() != QFile::NoError) + context()->throwError(f.errorString()); +} + +void XmlDomDocument::load(const QString &filePath) +{ + QFile f(filePath); + if (!f.open(QIODevice::ReadOnly)) { + context()->throwError(QStringLiteral("unable to open '%1'") + .arg(filePath)); + return; + } + + QString errorMsg; + if (!m_domDocument.setContent(&f, &errorMsg)) { + context()->throwError(errorMsg); + return; + } +} + +XmlDomDocument::XmlDomDocument(QScriptContext *context, const QString &name):m_domDocument(name) +{ + Q_UNUSED(context) + m_domNode = m_domDocument; +} + +QScriptValue XmlDomNode::ctor(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(context) + return engine->newQObject(new XmlDomNode(), QScriptEngine::ScriptOwnership); +} + +bool XmlDomNode::isElement() const +{ + return m_domNode.isElement(); +} + +bool XmlDomNode::isCDATASection() const +{ + return m_domNode.isCDATASection(); +} + +bool XmlDomNode::isText() const +{ + return m_domNode.isText(); +} + +QString XmlDomNode::attribute(const QString &name, const QString &defValue) +{ + QDomElement el = m_domNode.toElement(); + if (el.isNull()) { + context()->throwError(QStringLiteral("Node '%1' is not an element node").arg(m_domNode.nodeName())); + return defValue; + } + return el.attribute(name, defValue); +} + +void XmlDomNode::setAttribute(const QString &name, const QString &value) +{ + QDomElement el = m_domNode.toElement(); + if (el.isNull()) { + context()->throwError(QStringLiteral("Node '%1' is not an element node").arg(m_domNode.nodeName())); + return; + } + el.setAttribute(name, value); +} + +bool XmlDomNode::hasAttribute(const QString &name) const +{ + QDomElement el = m_domNode.toElement(); + if (el.isNull()) { + context()->throwError(QStringLiteral("Node '%1' is not an element node").arg(m_domNode.nodeName())); + return false; + } + return el.hasAttribute(name); +} + +QString XmlDomNode::tagName() const +{ + QDomElement el = m_domNode.toElement(); + if (el.isNull()) { + context()->throwError(QStringLiteral("Node '%1' is not an element node").arg(m_domNode.nodeName())); + return {}; + } + return el.tagName(); +} + +void XmlDomNode::setTagName(const QString &name) +{ + QDomElement el = m_domNode.toElement(); + if (el.isNull()) { + context()->throwError(QStringLiteral("Node '%1' is not an element node").arg(m_domNode.nodeName())); + return; + } + el.setTagName(name); +} + +QString XmlDomNode::text() const +{ + QDomElement el = m_domNode.toElement(); + if (el.isNull()) { + context()->throwError(QStringLiteral("Node '%1' is not an element node").arg(m_domNode.nodeName())); + return {}; + } + return el.text(); +} + +QString XmlDomNode::data() const +{ + if (m_domNode.isText()) + return m_domNode.toText().data(); + if (m_domNode.isCDATASection()) + return m_domNode.toCDATASection().data(); + if (m_domNode.isCharacterData()) + return m_domNode.toCharacterData().data(); + context()->throwError(QStringLiteral("Node '%1' is not a character data node").arg(m_domNode.nodeName())); + return {}; +} + +void XmlDomNode::setData(const QString &v) const +{ + if (m_domNode.isText()) + return m_domNode.toText().setData(v); + if (m_domNode.isCDATASection()) + return m_domNode.toCDATASection().setData(v); + if (m_domNode.isCharacterData()) + return m_domNode.toCharacterData().setData(v); + context()->throwError(QStringLiteral("Node '%1' is not a character data node").arg(m_domNode.nodeName())); +} + +void XmlDomNode::clear() +{ + m_domNode.clear(); +} + +bool XmlDomNode::hasAttributes() const +{ + return m_domNode.hasAttributes(); +} + +bool XmlDomNode::hasChildNodes() const +{ + return m_domNode.hasChildNodes(); +} + +QScriptValue XmlDomNode::parentNode() const +{ + return engine()->newQObject(new XmlDomNode(m_domNode.parentNode()), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::firstChild(const QString &tagName) +{ + if (tagName.isEmpty()) + return engine()->newQObject(new XmlDomNode(m_domNode.firstChild()), QScriptEngine::ScriptOwnership); + return engine()->newQObject(new XmlDomNode(m_domNode.firstChildElement(tagName)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::lastChild(const QString &tagName) const +{ + if (tagName.isEmpty()) + return engine()->newQObject(new XmlDomNode(m_domNode.lastChild()), QScriptEngine::ScriptOwnership); + return engine()->newQObject(new XmlDomNode(m_domNode.lastChildElement(tagName)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::previousSibling(const QString &tagName) const +{ + if (tagName.isEmpty()) + return engine()->newQObject(new XmlDomNode(m_domNode.previousSibling()), QScriptEngine::ScriptOwnership); + return engine()->newQObject(new XmlDomNode(m_domNode.previousSiblingElement(tagName)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::nextSibling(const QString &tagName) const +{ + if (tagName.isEmpty()) + return engine()->newQObject(new XmlDomNode(m_domNode.nextSibling()), QScriptEngine::ScriptOwnership); + return engine()->newQObject(new XmlDomNode(m_domNode.nextSiblingElement(tagName)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::appendChild(const QScriptValue &newChild) +{ + auto newNode = qobject_cast(newChild.toQObject()); + if (!newNode) { + context()->throwError(QStringLiteral("First argument is not a XmlDomNode object")); + return {}; + } + return engine()->newQObject(new XmlDomNode(m_domNode.appendChild(newNode->m_domNode)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::insertBefore(const QScriptValue &newChild, const QScriptValue &refChild) +{ + auto newNode = qobject_cast(newChild.toQObject()); + if (!newNode) { + context()->throwError(QStringLiteral("First argument is not a XmlDomNode object")); + return {}; + } + + auto refNode = qobject_cast(refChild.toQObject()); + if (!refNode) { + context()->throwError(QStringLiteral("Second argument is not a XmlDomNode object")); + return {}; + } + + return engine()->newQObject(new XmlDomNode(m_domNode.insertBefore(newNode->m_domNode, refNode->m_domNode)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::insertAfter(const QScriptValue &newChild, const QScriptValue &refChild) +{ + auto newNode = qobject_cast(newChild.toQObject()); + if (!newNode) { + context()->throwError(QStringLiteral("First argument is not a XmlDomNode object")); + return {}; + } + + auto refNode = qobject_cast(refChild.toQObject()); + if (!refNode) { + context()->throwError(QStringLiteral("Second argument is not a XmlDomNode object")); + return {}; + } + + return engine()->newQObject(new XmlDomNode(m_domNode.insertAfter(newNode->m_domNode, refNode->m_domNode)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::replaceChild(const QScriptValue &newChild, const QScriptValue &oldChild) +{ + auto newNode = qobject_cast(newChild.toQObject()); + if (!newNode) { + context()->throwError(QStringLiteral("First argument is not a XmlDomNode object")); + return {}; + } + + auto oldNode = qobject_cast(oldChild.toQObject()); + if (!oldNode) { + context()->throwError(QStringLiteral("Second argument is not a XmlDomNode object")); + return {}; + } + + return engine()->newQObject(new XmlDomNode(m_domNode.replaceChild(newNode->m_domNode, oldNode->m_domNode)), QScriptEngine::ScriptOwnership); +} + +QScriptValue XmlDomNode::removeChild(const QScriptValue &oldChild) +{ + auto oldNode = qobject_cast(oldChild.toQObject()); + if (!oldNode) { + context()->throwError(QStringLiteral("First argument is not a XmlDomNode object")); + return {}; + } + + return engine()->newQObject(new XmlDomNode(m_domNode.removeChild(oldNode->m_domNode)), QScriptEngine::ScriptOwnership); +} + +XmlDomNode::XmlDomNode(const QDomNode &other) +{ + m_domNode = other; +} + +} // namespace Internal +} // namespace qbs + +void initializeJsExtensionXml(QScriptValue extensionObject) +{ + using namespace qbs::Internal; + QScriptEngine *engine = extensionObject.engine(); + QScriptValue docObj = engine->newQMetaObject(&XmlDomDocument::staticMetaObject, + engine->newFunction(&XmlDomDocument::ctor)); + QScriptValue nodeObj = engine->newQMetaObject(&XmlDomNode::staticMetaObject, + engine->newFunction(&XmlDomNode::ctor)); + QScriptValue contextObject = engine->newObject(); + contextObject.setProperty(QStringLiteral("DomDocument"), docObj); + contextObject.setProperty(QStringLiteral("DomElement"), nodeObj); + + extensionObject.setProperty(QStringLiteral("Xml"), contextObject); +} + +Q_DECLARE_METATYPE(qbs::Internal::XmlDomDocument *) +Q_DECLARE_METATYPE(qbs::Internal::XmlDomNode *) + +#include "domxml.moc" diff --git a/src/lib/corelib/jsextensions/environmentextension.cpp b/src/lib/corelib/jsextensions/environmentextension.cpp new file mode 100644 index 00000000..cf17c938 --- /dev/null +++ b/src/lib/corelib/jsextensions/environmentextension.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include + +#include + +#include +#include + +namespace qbs { +namespace Internal { + +class EnvironmentExtension : public QObject, QScriptable +{ + Q_OBJECT +public: + void initializeJsExtensionEnvironment(QScriptValue extensionObject); + static QScriptValue js_ctor(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_getEnv(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_putEnv(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_unsetEnv(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_currentEnv(QScriptContext *context, QScriptEngine *engine); +}; + +QScriptValue EnvironmentExtension::js_ctor(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + return context->throwError(Tr::tr("'Environment' cannot be instantiated.")); +} + +static QProcessEnvironment *getProcessEnvironment(QScriptContext *context, QScriptEngine *engine, + const QString &func, bool doThrow = true) +{ + QVariant v = engine->property(StringConstants::qbsProcEnvVarInternal()); + auto procenv = reinterpret_cast(v.value()); + if (!procenv && doThrow) + throw context->throwError(QScriptContext::UnknownError, + QStringLiteral("%1 can only be called from ").arg(func) + + QStringLiteral("Module.setupBuildEnvironment and ") + + QStringLiteral("Module.setupRunEnvironment")); + return procenv; +} + +QScriptValue EnvironmentExtension::js_getEnv(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() != 1)) + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("getEnv expects 1 argument")); + const QProcessEnvironment env = static_cast(engine)->environment(); + const QProcessEnvironment *procenv = getProcessEnvironment(context, engine, + QStringLiteral("getEnv"), false); + if (!procenv) + procenv = &env; + + const QString name = context->argument(0).toString(); + const QString value = procenv->value(name); + return value.isNull() ? engine->undefinedValue() : value; +} + +QScriptValue EnvironmentExtension::js_putEnv(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() != 2)) + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("putEnv expects 2 arguments")); + getProcessEnvironment(context, engine, QStringLiteral("putEnv"))->insert( + context->argument(0).toString(), + context->argument(1).toString()); + return engine->undefinedValue(); +} + +QScriptValue EnvironmentExtension::js_unsetEnv(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() != 1)) + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("unsetEnv expects 1 argument")); + getProcessEnvironment(context, engine, QStringLiteral("unsetEnv"))->remove( + context->argument(0).toString()); + return engine->undefinedValue(); +} + +QScriptValue EnvironmentExtension::js_currentEnv(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(context); + const QProcessEnvironment env = static_cast(engine)->environment(); + const QProcessEnvironment *procenv = getProcessEnvironment(context, engine, + QStringLiteral("currentEnv"), false); + if (!procenv) + procenv = &env; + QScriptValue envObject = engine->newObject(); + const auto keys = procenv->keys(); + for (const QString &key : keys) { + const QString keyName = HostOsInfo::isWindowsHost() ? key.toUpper() : key; + envObject.setProperty(keyName, QScriptValue(procenv->value(key))); + } + return envObject; +} + +} // namespace Internal +} // namespace qbs + +void initializeJsExtensionEnvironment(QScriptValue extensionObject) +{ + using namespace qbs::Internal; + QScriptEngine *engine = extensionObject.engine(); + QScriptValue environmentObj = engine->newQMetaObject(&EnvironmentExtension::staticMetaObject, + engine->newFunction(&EnvironmentExtension::js_ctor)); + environmentObj.setProperty(QStringLiteral("getEnv"), + engine->newFunction(EnvironmentExtension::js_getEnv, 1)); + environmentObj.setProperty(QStringLiteral("putEnv"), + engine->newFunction(EnvironmentExtension::js_putEnv, 2)); + environmentObj.setProperty(QStringLiteral("unsetEnv"), + engine->newFunction(EnvironmentExtension::js_unsetEnv, 1)); + environmentObj.setProperty(QStringLiteral("currentEnv"), + engine->newFunction(EnvironmentExtension::js_currentEnv, 0)); + extensionObject.setProperty(QStringLiteral("Environment"), environmentObj); +} + +Q_DECLARE_METATYPE(qbs::Internal::EnvironmentExtension *) + +#include "environmentextension.moc" diff --git a/src/lib/corelib/jsextensions/file.cpp b/src/lib/corelib/jsextensions/file.cpp new file mode 100644 index 00000000..7cd2aadb --- /dev/null +++ b/src/lib/corelib/jsextensions/file.cpp @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** 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 +#include +#include + +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { + +class File : public QObject, QScriptable +{ + Q_OBJECT +public: + enum Filter { + Dirs = 0x001, + Files = 0x002, + Drives = 0x004, + NoSymLinks = 0x008, + AllEntries = Dirs | Files | Drives, + TypeMask = 0x00f, + + Readable = 0x010, + Writable = 0x020, + Executable = 0x040, + PermissionMask = 0x070, + + Modified = 0x080, + Hidden = 0x100, + System = 0x200, + + AccessMask = 0x3F0, + + AllDirs = 0x400, + CaseSensitive = 0x800, + NoDot = 0x2000, + NoDotDot = 0x4000, + NoDotAndDotDot = NoDot | NoDotDot, + + NoFilter = -1 + }; + Q_DECLARE_FLAGS(Filters, Filter) + Q_ENUMS(Filter) + + static QScriptValue js_ctor(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_copy(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_exists(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_directoryEntries(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_lastModified(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_makePath(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_move(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_remove(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_canonicalFilePath(QScriptContext *context, QScriptEngine *engine); +}; + +QScriptValue File::js_ctor(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + return context->throwError(Tr::tr("'File' cannot be instantiated.")); +} + +QScriptValue File::js_copy(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 2)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("copy expects 2 arguments")); + } + + const auto se = static_cast(engine); + const DubiousContextList dubiousContexts({ + DubiousContext(EvalContext::PropertyEvaluation), + DubiousContext(EvalContext::RuleExecution, DubiousContext::SuggestMoving) + }); + se->checkContext(QStringLiteral("File.copy()"), dubiousContexts); + + const QString sourceFile = context->argument(0).toString(); + const QString targetFile = context->argument(1).toString(); + QString errorMessage; + if (Q_UNLIKELY(!copyFileRecursion(sourceFile, targetFile, true, true, &errorMessage))) + return context->throwError(errorMessage); + return true; +} + +QScriptValue File::js_exists(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("exist expects 1 argument")); + } + const QString filePath = context->argument(0).toString(); + const bool exists = FileInfo::exists(filePath); + const auto se = static_cast(engine); + se->addFileExistsResult(filePath, exists); + return exists; +} + +QScriptValue File::js_directoryEntries(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 2)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("directoryEntries expects 2 arguments")); + } + + const auto se = static_cast(engine); + const DubiousContextList dubiousContexts({ + DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) + }); + se->checkContext(QStringLiteral("File.directoryEntries()"), dubiousContexts); + + const QString path = context->argument(0).toString(); + const auto filters = static_cast(context->argument(1).toUInt32()); + QDir dir(path); + const QStringList entries = dir.entryList(filters, QDir::Name); + se->addDirectoryEntriesResult(path, filters, entries); + return qScriptValueFromSequence(engine, entries); +} + +QScriptValue File::js_remove(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("remove expects 1 argument")); + } + + const auto se = static_cast(engine); + const DubiousContextList dubiousContexts({ DubiousContext(EvalContext::PropertyEvaluation) }); + se->checkContext(QStringLiteral("File.remove()"), dubiousContexts); + + QString fileName = context->argument(0).toString(); + + QString errorMessage; + if (Q_UNLIKELY(!removeFileRecursion(QFileInfo(fileName), &errorMessage))) + return context->throwError(errorMessage); + return true; +} + +QScriptValue File::js_lastModified(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("File.lastModified() expects an argument")); + } + const QString filePath = context->argument(0).toString(); + const FileTime timestamp = FileInfo(filePath).lastModified(); + const auto se = static_cast(engine); + se->addFileLastModifiedResult(filePath, timestamp); + return timestamp.asDouble(); +} + +QScriptValue File::js_makePath(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("makePath expects 1 argument")); + } + + const auto se = static_cast(engine); + const DubiousContextList dubiousContexts({ DubiousContext(EvalContext::PropertyEvaluation) }); + se->checkContext(QStringLiteral("File.makePath()"), dubiousContexts); + + return QDir::root().mkpath(context->argument(0).toString()); +} + +QScriptValue File::js_move(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 2)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("move expects 2 arguments")); + } + + const auto se = static_cast(engine); + const DubiousContextList dubiousContexts({ DubiousContext(EvalContext::PropertyEvaluation) }); + se->checkContext(QStringLiteral("File.move()"), dubiousContexts); + + const QString sourceFile = context->argument(0).toString(); + const QString targetFile = context->argument(1).toString(); + const bool overwrite = context->argumentCount() > 2 ? context->argument(2).toBool() : true; + + if (Q_UNLIKELY(QFileInfo(sourceFile).isDir())) + return context->throwError(QStringLiteral("Could not move '%1' to '%2': " + "Source file path is a directory.") + .arg(sourceFile, targetFile)); + + if (Q_UNLIKELY(QFileInfo(targetFile).isDir())) { + return context->throwError(QStringLiteral("Could not move '%1' to '%2': " + "Destination file path is a directory.") + .arg(sourceFile, targetFile)); + } + + QFile f(targetFile); + if (overwrite && f.exists() && !f.remove()) + return context->throwError(QStringLiteral("Could not move '%1' to '%2': %3") + .arg(sourceFile, targetFile, f.errorString())); + + if (QFile::exists(targetFile)) + return context->throwError(QStringLiteral("Could not move '%1' to '%2': " + "Destination file exists.") + .arg(sourceFile, targetFile)); + + QFile f2(sourceFile); + if (Q_UNLIKELY(!f2.rename(targetFile))) + return context->throwError(QStringLiteral("Could not move '%1' to '%2': %3") + .arg(sourceFile, targetFile, f2.errorString())); + return true; +} + +QScriptValue File::js_canonicalFilePath(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("canonicalFilePath expects 1 argument")); + } + return QFileInfo(context->argument(0).toString()).canonicalFilePath(); +} + +} // namespace Internal +} // namespace qbs + +void initializeJsExtensionFile(QScriptValue extensionObject) +{ + using namespace qbs::Internal; + QScriptEngine *engine = extensionObject.engine(); + QScriptValue fileObj = engine->newQMetaObject(&File::staticMetaObject, + engine->newFunction(&File::js_ctor)); + fileObj.setProperty(QStringLiteral("copy"), engine->newFunction(File::js_copy)); + fileObj.setProperty(QStringLiteral("exists"), engine->newFunction(File::js_exists)); + fileObj.setProperty(QStringLiteral("directoryEntries"), + engine->newFunction(File::js_directoryEntries)); + fileObj.setProperty(QStringLiteral("lastModified"), engine->newFunction(File::js_lastModified)); + fileObj.setProperty(QStringLiteral("makePath"), engine->newFunction(File::js_makePath)); + fileObj.setProperty(QStringLiteral("move"), engine->newFunction(File::js_move)); + fileObj.setProperty(QStringLiteral("remove"), engine->newFunction(File::js_remove)); + fileObj.setProperty(QStringLiteral("canonicalFilePath"), + engine->newFunction(File::js_canonicalFilePath)); + extensionObject.setProperty(QStringLiteral("File"), fileObj); +} + +Q_DECLARE_METATYPE(qbs::Internal::File *) + +#include "file.moc" diff --git a/src/lib/corelib/jsextensions/fileinfoextension.cpp b/src/lib/corelib/jsextensions/fileinfoextension.cpp new file mode 100644 index 00000000..038f3db4 --- /dev/null +++ b/src/lib/corelib/jsextensions/fileinfoextension.cpp @@ -0,0 +1,330 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include + +#include +#include + +#include +#include + +#include + +namespace qbs { +namespace Internal { + +// removes duplicate separators from the path +static QString uniqueSeparators(QString path) +{ + const auto it = std::unique(path.begin(), path.end(), [](QChar c1, QChar c2) { + return c1 == c2 && c1 == QLatin1Char('/'); + }); + path.resize(int(it - path.begin())); + return path; +} + +class FileInfoExtension : public QObject, QScriptable +{ + Q_OBJECT +public: + static QScriptValue js_ctor(QScriptContext *context, QScriptEngine *engine); + 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_suffix(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_completeSuffix(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_canonicalPath(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); + static QScriptValue js_isAbsolutePath(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_toWindowsSeparators(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_fromWindowsSeparators(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_toNativeSeparators(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_fromNativeSeparators(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_joinPaths(QScriptContext *context, QScriptEngine *engine); +}; + +QScriptValue FileInfoExtension::js_ctor(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + return context->throwError(Tr::tr("'FileInfo' cannot be instantiated.")); +} + +QScriptValue FileInfoExtension::js_path(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("path expects 1 argument")); + } + HostOsInfo::HostOs hostOs = HostOsInfo::hostOs(); + if (context->argumentCount() > 1) { + hostOs = context->argument(1).toVariant().toStringList().contains(QLatin1String("windows")) + ? HostOsInfo::HostOsWindows : HostOsInfo::HostOsOtherUnix; + } + return FileInfo::path(context->argument(0).toString(), hostOs); +} + +QScriptValue FileInfoExtension::js_fileName(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("fileName expects 1 argument")); + } + return FileInfo::fileName(context->argument(0).toString()); +} + +QScriptValue FileInfoExtension::js_baseName(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("baseName expects 1 argument")); + } + return FileInfo::baseName(context->argument(0).toString()); +} + +QScriptValue FileInfoExtension::js_suffix(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("suffix expects 1 argument")); + } + return FileInfo::suffix(context->argument(0).toString()); +} + +QScriptValue FileInfoExtension::js_completeSuffix(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("completeSuffix expects 1 argument")); + } + return FileInfo::completeSuffix(context->argument(0).toString()); +} + +QScriptValue FileInfoExtension::js_canonicalPath(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("canonicalPath expects 1 argument")); + } + return QFileInfo(context->argument(0).toString()).canonicalFilePath(); +} + +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); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("completeBaseName expects 1 argument")); + } + return FileInfo::completeBaseName(context->argument(0).toString()); +} + +QScriptValue FileInfoExtension::js_relativePath(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("relativePath expects 2 arguments")); + } + const QString baseDir = context->argument(0).toString(); + const QString filePath = context->argument(1).toString(); + if (!FileInfo::isAbsolute(baseDir)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("FileInfo.relativePath() expects an absolute path as " + "its first argument, but it is '%1'.").arg(baseDir)); + } + if (!FileInfo::isAbsolute(filePath)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("FileInfo.relativePath() expects an absolute path as " + "its second argument, but it is '%1'.").arg(filePath)); + } + return QDir(baseDir).relativeFilePath(filePath); +} + +QScriptValue FileInfoExtension::js_resolvePath(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("resolvePath expects 2 arguments")); + } + const QString base = context->argument(0).toString(); + const QString rel = context->argument(1).toString(); + return FileInfo::resolvePath(base, rel); +} + +QScriptValue FileInfoExtension::js_isAbsolutePath(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("isAbsolutePath expects 1 argument")); + } + HostOsInfo::HostOs hostOs = HostOsInfo::hostOs(); + if (context->argumentCount() > 1) { + hostOs = context->argument(1).toVariant().toStringList().contains(QLatin1String("windows")) + ? HostOsInfo::HostOsWindows : HostOsInfo::HostOsOtherUnix; + } + return FileInfo::isAbsolute(context->argument(0).toString(), hostOs); +} + +QScriptValue FileInfoExtension::js_toWindowsSeparators(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("toWindowsSeparators expects 1 argument")); + } + return context->argument(0).toString().replace(QLatin1Char('/'), QLatin1Char('\\')); +} + +QScriptValue FileInfoExtension::js_fromWindowsSeparators(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("fromWindowsSeparators expects 1 argument")); + } + return context->argument(0).toString().replace(QLatin1Char('\\'), QLatin1Char('/')); +} + +QScriptValue FileInfoExtension::js_toNativeSeparators(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("toNativeSeparators expects 1 argument")); + } + return QDir::toNativeSeparators(context->argument(0).toString()); +} + +QScriptValue FileInfoExtension::js_fromNativeSeparators(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("fromNativeSeparators expects 1 argument")); + } + return QDir::fromNativeSeparators(context->argument(0).toString()); +} + +QScriptValue FileInfoExtension::js_joinPaths(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + QStringList paths; + for (int i = 0; i < context->argumentCount(); ++i) { + const QScriptValue value = context->argument(i); + if (!value.isUndefined() && !value.isNull()) { + const QString arg = value.toString(); + if (!arg.isEmpty()) + paths.push_back(arg); + } + } + return engine->toScriptValue(uniqueSeparators(paths.join(QLatin1Char('/')))); +} + +} // namespace Internal +} // namespace qbs + +void initializeJsExtensionFileInfo(QScriptValue extensionObject) +{ + using namespace qbs::Internal; + QScriptEngine *engine = extensionObject.engine(); + QScriptValue fileInfoObj = engine->newQMetaObject(&FileInfoExtension::staticMetaObject, + engine->newFunction(&FileInfoExtension::js_ctor)); + fileInfoObj.setProperty(StringConstants::fileInfoPath(), + engine->newFunction(FileInfoExtension::js_path)); + fileInfoObj.setProperty(StringConstants::fileInfoFileName(), + engine->newFunction(FileInfoExtension::js_fileName)); + fileInfoObj.setProperty(StringConstants::baseNameProperty(), + engine->newFunction(FileInfoExtension::js_baseName)); + fileInfoObj.setProperty(QStringLiteral("suffix"), + engine->newFunction(FileInfoExtension::js_suffix)); + fileInfoObj.setProperty(QStringLiteral("completeSuffix"), + engine->newFunction(FileInfoExtension::js_completeSuffix)); + fileInfoObj.setProperty(QStringLiteral("canonicalPath"), + engine->newFunction(FileInfoExtension::js_canonicalPath)); + fileInfoObj.setProperty(QStringLiteral("cleanPath"), + engine->newFunction(FileInfoExtension::js_cleanPath)); + fileInfoObj.setProperty(StringConstants::completeBaseNameProperty(), + engine->newFunction(FileInfoExtension::js_completeBaseName)); + fileInfoObj.setProperty(QStringLiteral("relativePath"), + engine->newFunction(FileInfoExtension::js_relativePath)); + fileInfoObj.setProperty(QStringLiteral("resolvePath"), + engine->newFunction(FileInfoExtension::js_resolvePath)); + fileInfoObj.setProperty(QStringLiteral("isAbsolutePath"), + engine->newFunction(FileInfoExtension::js_isAbsolutePath)); + fileInfoObj.setProperty(QStringLiteral("toWindowsSeparators"), + engine->newFunction(FileInfoExtension::js_toWindowsSeparators)); + fileInfoObj.setProperty(QStringLiteral("fromWindowsSeparators"), + engine->newFunction(FileInfoExtension::js_fromWindowsSeparators)); + fileInfoObj.setProperty(QStringLiteral("toNativeSeparators"), + engine->newFunction(FileInfoExtension::js_toNativeSeparators)); + fileInfoObj.setProperty(QStringLiteral("fromNativeSeparators"), + engine->newFunction(FileInfoExtension::js_fromNativeSeparators)); + fileInfoObj.setProperty(QStringLiteral("joinPaths"), + engine->newFunction(FileInfoExtension::js_joinPaths)); + extensionObject.setProperty(QStringLiteral("FileInfo"), fileInfoObj); +} + +Q_DECLARE_METATYPE(qbs::Internal::FileInfoExtension *) + +#include "fileinfoextension.moc" diff --git a/src/lib/corelib/jsextensions/jsextensions.cpp b/src/lib/corelib/jsextensions/jsextensions.cpp new file mode 100644 index 00000000..052fb79e --- /dev/null +++ b/src/lib/corelib/jsextensions/jsextensions.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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 "jsextensions.h" + +#include +#include + +#include + +using InitializerMap = QMap; +static InitializerMap setupMap() +{ +#define INITIALIZER_NAME(name) initializeJsExtension##name +#define ADD_JS_EXTENSION(name) \ + void INITIALIZER_NAME(name)(QScriptValue); \ + map.insert(QStringLiteral(#name), &INITIALIZER_NAME(name)) + + InitializerMap map; + ADD_JS_EXTENSION(BinaryFile); + ADD_JS_EXTENSION(Environment); + ADD_JS_EXTENSION(File); + ADD_JS_EXTENSION(FileInfo); + ADD_JS_EXTENSION(Process); + ADD_JS_EXTENSION(PropertyList); + ADD_JS_EXTENSION(TemporaryDir); + ADD_JS_EXTENSION(TextFile); + ADD_JS_EXTENSION(Utilities); + ADD_JS_EXTENSION(Xml); + return map; +} + +namespace qbs { +namespace Internal { + +static InitializerMap &initializers() +{ + static InitializerMap theMap = setupMap(); + return theMap; +} + +void JsExtensions::setupExtensions(const QStringList &names, const QScriptValue &scope) +{ + for (const QString &name : names) + initializers().value(name)(scope); +} + +QScriptValue JsExtensions::loadExtension(QScriptEngine *engine, const QString &name) +{ + if (!hasExtension(name)) + return {}; + + QScriptValue extensionObj = engine->newObject(); + initializers().value(name)(extensionObj); + return extensionObj.property(name); +} + +bool JsExtensions::hasExtension(const QString &name) +{ + return initializers().contains(name); +} + +QStringList JsExtensions::extensionNames() +{ + return initializers().keys(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/jsextensions/jsextensions.h b/src/lib/corelib/jsextensions/jsextensions.h new file mode 100644 index 00000000..f1ebfbdc --- /dev/null +++ b/src/lib/corelib/jsextensions/jsextensions.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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 QBS_JSEXTENSIONS_H +#define QBS_JSEXTENSIONS_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QScriptEngine; +class QScriptValue; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +class JsExtensions +{ +public: + static void setupExtensions(const QStringList &names, const QScriptValue &scope); + static QScriptValue loadExtension(QScriptEngine *engine, const QString &name); + static bool hasExtension(const QString &name); + static QStringList extensionNames(); +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/jsextensions/jsextensions.pri b/src/lib/corelib/jsextensions/jsextensions.pri new file mode 100644 index 00000000..2bffc991 --- /dev/null +++ b/src/lib/corelib/jsextensions/jsextensions.pri @@ -0,0 +1,26 @@ +QT += xml + +HEADERS += \ + $$PWD/moduleproperties.h \ + $$PWD/jsextensions.h + +SOURCES += \ + $$PWD/environmentextension.cpp \ + $$PWD/file.cpp \ + $$PWD/fileinfoextension.cpp \ + $$PWD/temporarydir.cpp \ + $$PWD/textfile.cpp \ + $$PWD/binaryfile.cpp \ + $$PWD/process.cpp \ + $$PWD/moduleproperties.cpp \ + $$PWD/domxml.cpp \ + $$PWD/jsextensions.cpp \ + $$PWD/utilitiesextension.cpp + +darwin { + HEADERS += $$PWD/propertylistutils.h + SOURCES += $$PWD/propertylist.mm $$PWD/propertylistutils.mm + LIBS += -framework Foundation +} else { + SOURCES += $$PWD/propertylist.cpp +} diff --git a/src/lib/corelib/jsextensions/moduleproperties.cpp b/src/lib/corelib/jsextensions/moduleproperties.cpp new file mode 100644 index 00000000..f721e001 --- /dev/null +++ b/src/lib/corelib/jsextensions/moduleproperties.cpp @@ -0,0 +1,357 @@ +/**************************************************************************** +** +** 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 "moduleproperties.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +QScriptValue getDataForModuleScriptValue(QScriptEngine *engine, const ResolvedProduct *product, + const Artifact *artifact, const ResolvedModule *module) +{ + QScriptValue data = engine->newObject(); + data.setProperty(ModuleNameKey, module->name); + QVariant v; + v.setValue(reinterpret_cast(product)); + data.setProperty(ProductPtrKey, engine->newVariant(v)); + v.setValue(reinterpret_cast(artifact)); + data.setProperty(ArtifactPtrKey, engine->newVariant(v)); + return data; +} + +static QScriptValue getModuleProperty(const ResolvedProduct *product, const Artifact *artifact, + ScriptEngine *engine, const QString &moduleName, + const QString &propertyName, bool *isPresent = nullptr) +{ + const PropertyMapConstPtr &properties = artifact ? artifact->properties + : product->moduleProperties; + QVariant value; + if (engine->isPropertyCacheEnabled()) + value = engine->retrieveFromPropertyCache(moduleName, propertyName, properties); + if (!value.isValid()) { + value = properties->moduleProperty(moduleName, propertyName, isPresent); + + // Cache the variant value. We must not cache the QScriptValue here, because it's a + // reference and the user might change the actual object. + if (engine->isPropertyCacheEnabled()) + engine->addToPropertyCache(moduleName, propertyName, properties, value); + } else if (isPresent) { + *isPresent = true; + } + + const Property p(product->uniqueName(), moduleName, propertyName, value, + Property::PropertyInModule); + if (artifact) + engine->addPropertyRequestedFromArtifact(artifact, p); + else + engine->addPropertyRequestedInScript(p); + + return engine->toScriptValue(value); +} + +class ModulePropertyScriptClass : public QScriptClass +{ +public: + ModulePropertyScriptClass(QScriptEngine *engine) + : QScriptClass(engine) + { + } + +private: + QueryFlags queryProperty(const QScriptValue &object, const QScriptString &name, + QueryFlags flags, uint *id) override + { + Q_UNUSED(flags); + Q_UNUSED(id); + + if (name == StringConstants::dependenciesProperty() + || name == StringConstants::artifactsProperty()) { + // The prototype is not backed by a QScriptClass. + m_result = object.prototype().property(name); + return HandlesReadAccess; + } + + if (name == StringConstants::parametersProperty()) { + m_result = object.data().property(DependencyParametersKey); + return HandlesReadAccess; + } + + setup(object); + QBS_ASSERT(m_product, return {}); + bool isPresent; + m_result = getModuleProperty(m_product, m_artifact, static_cast(engine()), + m_moduleName, name, &isPresent); + + // It is important that we reject unknown property names. Otherwise QtScript will forward + // *everything* to us, including built-in stuff like the hasOwnProperty function. + return isPresent ? HandlesReadAccess : QueryFlags(); + } + + QScriptValue property(const QScriptValue &, const QScriptString &, uint) override + { + return m_result; + } + + QScriptClassPropertyIterator *newIterator(const QScriptValue &object) override + { + setup(object); + QBS_ASSERT(m_artifact || m_product, return nullptr); + const PropertyMapInternal *propertyMap; + std::vector additionalProperties({StringConstants::artifactsProperty(), + StringConstants::dependenciesProperty()}); + if (m_artifact) { + propertyMap = m_artifact->properties.get(); + } else { + propertyMap = m_product->moduleProperties.get(); + if (object.data().property(DependencyParametersKey).isValid()) + additionalProperties.push_back(StringConstants::parametersProperty()); + } + return new ScriptClassPropertyIterator(object, + propertyMap->value().value(m_moduleName).toMap(), + additionalProperties); + } + + void setup(const QScriptValue &object) + { + if (m_lastObjectId != object.objectId()) { + m_lastObjectId = object.objectId(); + const QScriptValue data = object.data(); + QBS_ASSERT(data.isValid(), return); + m_moduleName = data.property(ModuleNameKey).toString(); + m_product = reinterpret_cast( + data.property(ProductPtrKey).toVariant().value()); + m_artifact = reinterpret_cast( + data.property(ArtifactPtrKey).toVariant().value()); + } + } + + qint64 m_lastObjectId = 0; + QString m_moduleName; + const ResolvedProduct *m_product = nullptr; + const Artifact *m_artifact = nullptr; + QScriptValue m_result; +}; + +static QString ptrKey() { return QStringLiteral("__internalPtr"); } +static QString typeKey() { return QStringLiteral("__type"); } +static QString artifactType() { return QStringLiteral("artifact"); } + +static QScriptValue js_moduleDependencies(QScriptContext *, ScriptEngine *engine, + const ResolvedModule *module) +{ + QScriptValue result = engine->newArray(); + quint32 idx = 0; + for (const QString &depName : qAsConst(module->moduleDependencies)) { + for (const auto &dep : module->product->modules) { + if (dep->name != depName) + continue; + QScriptValue obj = engine->newObject(engine->modulePropertyScriptClass()); + obj.setPrototype(engine->moduleScriptValuePrototype(dep.get())); + QScriptValue data = getDataForModuleScriptValue(engine, module->product, nullptr, + dep.get()); + const QVariantMap ¶ms = module->product->moduleParameters.value(dep); + data.setProperty(DependencyParametersKey, dependencyParametersValue( + module->product->uniqueName(), dep->name, params, engine)); + obj.setData(data); + result.setProperty(idx++, obj); + break; + } + } + QBS_ASSERT(idx == quint32(module->moduleDependencies.size()),;); + return result; +} + +static QScriptValue setupModuleScriptValue(ScriptEngine *engine, + const ResolvedModule *module) +{ + QScriptValue &moduleScriptValue = engine->moduleScriptValuePrototype(module); + if (moduleScriptValue.isValid()) + return moduleScriptValue; + moduleScriptValue = engine->newObject(); + QScriptValue depfunc = engine->newFunction(&js_moduleDependencies, + module); + moduleScriptValue.setProperty(StringConstants::dependenciesProperty(), depfunc, + QScriptValue::ReadOnly | QScriptValue::Undeletable + | QScriptValue::PropertyGetter); + QScriptValue artifactsFunc = engine->newFunction(&artifactsScriptValueForModule, module); + moduleScriptValue.setProperty(StringConstants::artifactsProperty(), artifactsFunc, + QScriptValue::ReadOnly | QScriptValue::Undeletable + | QScriptValue::PropertyGetter); + return moduleScriptValue; +} + +void ModuleProperties::init(QScriptValue productObject, + const ResolvedProduct *product) +{ + init(productObject, product, StringConstants::productValue()); + setupModules(productObject, product, nullptr); +} + +void ModuleProperties::init(QScriptValue artifactObject, const Artifact *artifact) +{ + init(artifactObject, artifact, artifactType()); + const auto product = artifact->product; + const QVariantMap productProperties { + {StringConstants::buildDirectoryProperty(), product->buildDirectory()}, + {StringConstants::destinationDirProperty(), product->destinationDirectory}, + {StringConstants::nameProperty(), product->name}, + {StringConstants::sourceDirectoryProperty(), product->sourceDirectory}, + {StringConstants::targetNameProperty(), product->targetName}, + {StringConstants::typeProperty(), sorted(product->fileTags.toStringList())} + }; + QScriptEngine * const engine = artifactObject.engine(); + artifactObject.setProperty(StringConstants::productVar(), + engine->toScriptValue(productProperties)); + setupModules(artifactObject, artifact->product.get(), artifact); +} + +void ModuleProperties::setModuleScriptValue(QScriptValue targetObject, + const QScriptValue &moduleObject, const QString &moduleName) +{ + auto const e = static_cast(targetObject.engine()); + const QualifiedId name = QualifiedId::fromString(moduleName); + QScriptValue obj = targetObject; + for (int i = 0; i < name.size() - 1; ++i) { + QScriptValue tmp = obj.property(name.at(i)); + if (!tmp.isObject()) + tmp = e->newObject(); + obj.setProperty(name.at(i), tmp); + obj = tmp; + } + obj.setProperty(name.last(), moduleObject); + if (moduleName.size() > 1) + targetObject.setProperty(moduleName, moduleObject); +} + +void ModuleProperties::init(QScriptValue objectWithProperties, const void *ptr, + const QString &type) +{ + QScriptEngine * const engine = objectWithProperties.engine(); + objectWithProperties.setProperty(QStringLiteral("moduleProperty"), + engine->newFunction(ModuleProperties::js_moduleProperty, 2)); + objectWithProperties.setProperty(ptrKey(), engine->toScriptValue(quintptr(ptr))); + objectWithProperties.setProperty(typeKey(), type); +} + +void ModuleProperties::setupModules(QScriptValue &object, const ResolvedProduct *product, + const Artifact *artifact) +{ + const auto engine = static_cast(object.engine()); + QScriptClass *modulePropertyScriptClass = engine->modulePropertyScriptClass(); + if (!modulePropertyScriptClass) { + modulePropertyScriptClass = new ModulePropertyScriptClass(engine); + engine->setModulePropertyScriptClass(modulePropertyScriptClass); + } + for (const auto &module : product->modules) { + QScriptValue moduleObjectPrototype = setupModuleScriptValue(engine, module.get()); + QScriptValue moduleObject = engine->newObject(modulePropertyScriptClass); + moduleObject.setPrototype(moduleObjectPrototype); + moduleObject.setData(getDataForModuleScriptValue(engine, product, artifact, module.get())); + setModuleScriptValue(object, moduleObject, module->name); + } +} + +QScriptValue ModuleProperties::js_moduleProperty(QScriptContext *context, QScriptEngine *engine) +{ + try { + return moduleProperty(context, engine); + } catch (const ErrorInfo &e) { + return context->throwError(e.toString()); + } +} + +QScriptValue ModuleProperties::moduleProperty(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 2)) { + return context->throwError(QScriptContext::SyntaxError, + Tr::tr("Function moduleProperty() expects 2 arguments")); + } + + const QScriptValue objectWithProperties = context->thisObject(); + const QScriptValue typeScriptValue = objectWithProperties.property(typeKey()); + if (Q_UNLIKELY(!typeScriptValue.isString())) { + return context->throwError(QScriptContext::TypeError, + QStringLiteral("Internal error: __type not set up")); + } + const QScriptValue ptrScriptValue = objectWithProperties.property(ptrKey()); + if (Q_UNLIKELY(!ptrScriptValue.isNumber())) { + return context->throwError(QScriptContext::TypeError, + QStringLiteral("Internal error: __internalPtr not set up")); + } + + const void *ptr = reinterpret_cast(qscriptvalue_cast(ptrScriptValue)); + const ResolvedProduct *product = nullptr; + const Artifact *artifact = nullptr; + if (typeScriptValue.toString() == StringConstants::productValue()) { + QBS_ASSERT(ptr, return {}); + product = static_cast(ptr); + } else if (typeScriptValue.toString() == artifactType()) { + QBS_ASSERT(ptr, return {}); + artifact = static_cast(ptr); + product = artifact->product.get(); + } else { + return context->throwError(QScriptContext::TypeError, + QStringLiteral("Internal error: invalid type")); + } + + const auto qbsEngine = static_cast(engine); + const QString moduleName = context->argument(0).toString(); + const QString propertyName = context->argument(1).toString(); + return getModuleProperty(product, artifact, qbsEngine, moduleName, propertyName); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/jsextensions/moduleproperties.h b/src/lib/corelib/jsextensions/moduleproperties.h new file mode 100644 index 00000000..3fe4fbfd --- /dev/null +++ b/src/lib/corelib/jsextensions/moduleproperties.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 QBS_MODULEPROPERTIES_H +#define QBS_MODULEPROPERTIES_H + +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { + +class ScriptEngine; + +enum ModulePropertiesScriptValueCommonPropertyKeys : quint32 +{ + ModuleNameKey, + ProductPtrKey, + ArtifactPtrKey, + DependencyParametersKey, +}; + +QScriptValue getDataForModuleScriptValue(QScriptEngine *engine, const ResolvedProduct *product, + const Artifact *artifact, const ResolvedModule *module); + +class ModuleProperties +{ +public: + static void init(QScriptValue productObject, const ResolvedProduct *product); + static void init(QScriptValue artifactObject, const Artifact *artifact); + static void setModuleScriptValue(QScriptValue targetObject, const QScriptValue &moduleObject, + const QString &moduleName); + +private: + static void init(QScriptValue objectWithProperties, const void *ptr, const QString &type); + static void setupModules(QScriptValue &object, const ResolvedProduct *product, + const Artifact *artifact); + + static QScriptValue js_moduleProperty(QScriptContext *context, QScriptEngine *engine); + + static QScriptValue moduleProperty(QScriptContext *context, QScriptEngine *engine); +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_MODULEPROPERTIES_H diff --git a/src/lib/corelib/jsextensions/process.cpp b/src/lib/corelib/jsextensions/process.cpp new file mode 100644 index 00000000..064297a3 --- /dev/null +++ b/src/lib/corelib/jsextensions/process.cpp @@ -0,0 +1,354 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +class Process : public QObject, public QScriptable, public ResourceAcquiringScriptObject +{ + Q_OBJECT +public: + static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); + Process(QScriptContext *context); + ~Process() override; + + Q_INVOKABLE QString getEnv(const QString &name); + Q_INVOKABLE void setEnv(const QString &name, const QString &value); + Q_INVOKABLE void setCodec(const QString &codec); + + Q_INVOKABLE QString workingDirectory(); + Q_INVOKABLE void setWorkingDirectory(const QString &dir); + + Q_INVOKABLE bool start(const QString &program, const QStringList &arguments); + Q_INVOKABLE int exec(const QString &program, const QStringList &arguments, + bool throwOnError = false); + Q_INVOKABLE void close(); + Q_INVOKABLE bool waitForFinished(int msecs = 30000); + Q_INVOKABLE void terminate(); + Q_INVOKABLE void kill(); + + Q_INVOKABLE QString readLine(); + Q_INVOKABLE bool atEnd() const; + Q_INVOKABLE QString readStdOut(); + Q_INVOKABLE QString readStdErr(); + + Q_INVOKABLE void closeWriteChannel(); + + Q_INVOKABLE void write(const QString &str); + Q_INVOKABLE void writeLine(const QString &str); + + Q_INVOKABLE int exitCode() const; + + static QScriptValue js_shellQuote(QScriptContext *context, QScriptEngine *engine); + +private: + QString findExecutable(const QString &filePath) const; + + // ResourceAcquiringScriptObject implementation + void releaseResources() override; + + QProcess *m_qProcess; + QProcessEnvironment m_environment; + QString m_workingDirectory; + QTextStream *m_textStream; +}; + +QScriptValue Process::ctor(QScriptContext *context, QScriptEngine *engine) +{ + Process *t; + switch (context->argumentCount()) { + case 0: + t = new Process(context); + break; + default: + return context->throwError(QStringLiteral("Process()")); + } + + const auto se = static_cast(engine); + se->addResourceAcquiringScriptObject(t); + const DubiousContextList dubiousContexts ({ + DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) + }); + se->checkContext(QStringLiteral("qbs.Process"), dubiousContexts); + + QScriptValue obj = engine->newQObject(t, QScriptEngine::QtOwnership); + + // Get environment + QVariant v = engine->property(StringConstants::qbsProcEnvVarInternal()); + if (v.isNull()) { + // The build environment is not initialized yet. + // This can happen if one uses Process on the RHS of a binding like Group.name. + t->m_environment = static_cast(engine)->environment(); + } else { + t->m_environment + = QProcessEnvironment(*reinterpret_cast(v.value())); + } + se->setUsesIo(); + + return obj; +} + +Process::~Process() +{ + delete m_textStream; + delete m_qProcess; +} + +Process::Process(QScriptContext *context) +{ + Q_UNUSED(context); + Q_ASSERT(thisObject().engine() == engine()); + + m_qProcess = new QProcess; + m_textStream = new QTextStream(m_qProcess); +} + +QString Process::getEnv(const QString &name) +{ + Q_ASSERT(thisObject().engine() == engine()); + return m_environment.value(name); +} + +void Process::setEnv(const QString &name, const QString &value) +{ + Q_ASSERT(thisObject().engine() == engine()); + m_environment.insert(name, value); +} + +QString Process::workingDirectory() +{ + Q_ASSERT(thisObject().engine() == engine()); + return m_workingDirectory; +} + +void Process::setWorkingDirectory(const QString &dir) +{ + Q_ASSERT(thisObject().engine() == engine()); + m_workingDirectory = dir; +} + +bool Process::start(const QString &program, const QStringList &arguments) +{ + Q_ASSERT(thisObject().engine() == engine()); + + if (!m_workingDirectory.isEmpty()) + m_qProcess->setWorkingDirectory(m_workingDirectory); + + m_qProcess->setProcessEnvironment(m_environment); + m_qProcess->start(findExecutable(program), arguments); + return m_qProcess->waitForStarted(); +} + +int Process::exec(const QString &program, const QStringList &arguments, bool throwOnError) +{ + Q_ASSERT(thisObject().engine() == engine()); + + if (!start(findExecutable(program), arguments)) { + if (throwOnError) { + context()->throwError(Tr::tr("Error running '%1': %2") + .arg(program, m_qProcess->errorString())); + } + return -1; + } + m_qProcess->closeWriteChannel(); + m_qProcess->waitForFinished(-1); + if (throwOnError) { + if (m_qProcess->error() != QProcess::UnknownError + && m_qProcess->error() != QProcess::Crashed) { + context()->throwError(Tr::tr("Error running '%1': %2") + .arg(program, m_qProcess->errorString())); + } else if (m_qProcess->exitStatus() == QProcess::CrashExit || m_qProcess->exitCode() != 0) { + QString errorMessage = m_qProcess->error() == QProcess::Crashed + ? Tr::tr("Error running '%1': %2").arg(program, m_qProcess->errorString()) + : Tr::tr("Process '%1' finished with exit code %2.").arg(program).arg( + m_qProcess->exitCode()); + const QString stdOut = readStdOut(); + if (!stdOut.isEmpty()) + errorMessage.append(Tr::tr(" The standard output was:\n")).append(stdOut); + const QString stdErr = readStdErr(); + if (!stdErr.isEmpty()) + errorMessage.append(Tr::tr(" The standard error output was:\n")).append(stdErr); + context()->throwError(errorMessage); + } + } + if (m_qProcess->error() != QProcess::UnknownError) + return -1; + return m_qProcess->exitCode(); +} + +void Process::close() +{ + if (!m_qProcess) + return; + Q_ASSERT(thisObject().engine() == engine()); + delete m_textStream; + m_textStream = nullptr; + delete m_qProcess; + m_qProcess = nullptr; +} + +bool Process::waitForFinished(int msecs) +{ + Q_ASSERT(thisObject().engine() == engine()); + + if (m_qProcess->state() == QProcess::NotRunning) + return true; + return m_qProcess->waitForFinished(msecs); +} + +void Process::terminate() +{ + m_qProcess->terminate(); +} + +void Process::kill() +{ + m_qProcess->kill(); +} + +void Process::setCodec(const QString &codec) +{ + Q_ASSERT(thisObject().engine() == engine()); + m_textStream->setCodec(qPrintable(codec)); +} + +QString Process::readLine() +{ + return m_textStream->readLine(); +} + +bool Process::atEnd() const +{ + return m_textStream->atEnd(); +} + +QString Process::readStdOut() +{ + return m_textStream->readAll(); +} + +QString Process::readStdErr() +{ + return m_textStream->codec()->toUnicode(m_qProcess->readAllStandardError()); +} + +void Process::closeWriteChannel() +{ + m_textStream->flush(); + m_qProcess->closeWriteChannel(); +} + +int Process::exitCode() const +{ + return m_qProcess->exitCode(); +} + +QString Process::findExecutable(const QString &filePath) const +{ + ExecutableFinder exeFinder(ResolvedProductPtr(), m_environment); + return exeFinder.findExecutable(filePath, m_workingDirectory); +} + +void Process::releaseResources() +{ + close(); + deleteLater(); +} + +void Process::write(const QString &str) +{ + (*m_textStream) << str; +} + +void Process::writeLine(const QString &str) +{ + (*m_textStream) << str; + if (HostOsInfo::isWindowsHost()) + (*m_textStream) << '\r'; + (*m_textStream) << '\n'; +} + +QScriptValue Process::js_shellQuote(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 2)) { + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("shellQuote expects at least 2 arguments")); + } + const QString program = context->argument(0).toString(); + const QStringList args = context->argument(1).toVariant().toStringList(); + HostOsInfo::HostOs hostOs = HostOsInfo::hostOs(); + if (context->argumentCount() > 2) { + hostOs = context->argument(2).toVariant().toStringList().contains(QLatin1String("windows")) + ? HostOsInfo::HostOsWindows : HostOsInfo::HostOsOtherUnix; + } + return engine->toScriptValue(shellQuote(program, args, hostOs)); +} + +} // namespace Internal +} // namespace qbs + +void initializeJsExtensionProcess(QScriptValue extensionObject) +{ + using namespace qbs::Internal; + QScriptEngine *engine = extensionObject.engine(); + QScriptValue obj = engine->newQMetaObject(&Process::staticMetaObject, engine->newFunction(&Process::ctor)); + extensionObject.setProperty(QStringLiteral("Process"), obj); + obj.setProperty(QStringLiteral("shellQuote"), engine->newFunction(Process::js_shellQuote, 3)); +} + +Q_DECLARE_METATYPE(qbs::Internal::Process *) + +#include "process.moc" diff --git a/src/lib/corelib/jsextensions/propertylist.cpp b/src/lib/corelib/jsextensions/propertylist.cpp new file mode 100644 index 00000000..197d5e99 --- /dev/null +++ b/src/lib/corelib/jsextensions/propertylist.cpp @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Petroules Corporation. +** 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 + +void initializeJsExtensionPropertyList(QScriptValue extensionObject) +{ + QScriptEngine *engine = extensionObject.engine(); + QScriptValue obj = engine->newObject(); // provide a fake object + extensionObject.setProperty(QStringLiteral("PropertyList"), obj); +} diff --git a/src/lib/corelib/jsextensions/propertylist.h b/src/lib/corelib/jsextensions/propertylist.h new file mode 100644 index 00000000..adb90f78 --- /dev/null +++ b/src/lib/corelib/jsextensions/propertylist.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Petroules Corporation. +** 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_PROPERTYLIST_H +#define QBS_PROPERTYLIST_H + +#include + +// ### remove when qbs requires qbs 1.6 to build itself +#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) && defined(__APPLE__) && !defined(Q_OS_MAC) +#define Q_OS_MAC +#endif + +#ifndef Q_OS_MAC + +#include + +namespace qbs { +namespace Internal { + +// provide a fake initializer for other platforms +void initializeJsExtensionPropertyList(QScriptValue extensionObject) +{ + // provide a fake object + QScriptEngine *engine = extensionObject.engine(); + extensionObject.setProperty(QLatin1String("PropertyList"), engine->newObject()); +} + +} // namespace Internal +} // namespace qbs + +#else // Q_OS_MAC + +#include +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { + +void initializeJsExtensionPropertyList(QScriptValue extensionObject); + +class PropertyListPrivate; + +class PropertyList : public QObject, public QScriptable +{ + Q_OBJECT +public: + static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); + PropertyList(QScriptContext *context); + ~PropertyList(); + Q_INVOKABLE bool isEmpty() const; + Q_INVOKABLE void clear(); + Q_INVOKABLE void readFromObject(const QScriptValue &value); + Q_INVOKABLE void readFromString(const QString &input); + Q_INVOKABLE void readFromFile(const QString &filePath); + Q_INVOKABLE void readFromData(const QByteArray &data); + Q_INVOKABLE void writeToFile(const QString &filePath, const QString &plistFormat); + Q_INVOKABLE QScriptValue format() const; + Q_INVOKABLE QScriptValue toObject() const; + Q_INVOKABLE QString toString(const QString &plistFormat) const; + Q_INVOKABLE QString toXMLString() const; + Q_INVOKABLE QString toJSON(const QString &style = QString()) const; +private: + PropertyListPrivate *d; +}; + +} // namespace Internal +} // namespace qbs + +Q_DECLARE_METATYPE(qbs::Internal::PropertyList *) + +#endif // Q_OS_MAC + +#endif // QBS_PROPERTYLIST_H diff --git a/src/lib/corelib/jsextensions/propertylist.mm b/src/lib/corelib/jsextensions/propertylist.mm new file mode 100644 index 00000000..2ae422c4 --- /dev/null +++ b/src/lib/corelib/jsextensions/propertylist.mm @@ -0,0 +1,373 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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 +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +// Same values as CoreFoundation and Foundation APIs +enum { + QPropertyListOpenStepFormat = 1, + QPropertyListXMLFormat_v1_0 = 100, + QPropertyListBinaryFormat_v1_0 = 200, + QPropertyListJSONFormat = 1000 // If this conflicts someday, just change it :) +}; + +namespace qbs { +namespace Internal { + +class PropertyListPrivate; + +class PropertyList : public QObject, public QScriptable +{ + Q_OBJECT +public: + static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); + PropertyList(QScriptContext *context); + ~PropertyList() override; + Q_INVOKABLE bool isEmpty() const; + Q_INVOKABLE void clear(); + Q_INVOKABLE void readFromObject(const QScriptValue &value); + Q_INVOKABLE void readFromString(const QString &input); + Q_INVOKABLE void readFromFile(const QString &filePath); + Q_INVOKABLE void readFromData(const QByteArray &data); + Q_INVOKABLE void writeToFile(const QString &filePath, const QString &plistFormat); + Q_INVOKABLE QScriptValue format() const; + Q_INVOKABLE QScriptValue toObject() const; + Q_INVOKABLE QString toString(const QString &plistFormat) const; + Q_INVOKABLE QString toXMLString() const; + Q_INVOKABLE QString toJSON(const QString &style = QString()) const; +private: + PropertyListPrivate *d; +}; + +class PropertyListPrivate +{ +public: + PropertyListPrivate(); + + QVariant propertyListObject; + int propertyListFormat; + + void readFromData(QScriptContext *context, const QByteArray &data); + QByteArray writeToData(QScriptContext *context, const QString &format); +}; + +QScriptValue PropertyList::ctor(QScriptContext *context, QScriptEngine *engine) +{ + auto const se = static_cast(engine); + const DubiousContextList dubiousContexts({ + DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) + }); + se->checkContext(QStringLiteral("qbs.PropertyList"), dubiousContexts); + + auto p = new PropertyList(context); + QScriptValue obj = engine->newQObject(p, QScriptEngine::ScriptOwnership); + return obj; +} + +PropertyListPrivate::PropertyListPrivate() + : propertyListObject(), propertyListFormat(0) +{ +} + +PropertyList::~PropertyList() +{ + delete d; +} + +PropertyList::PropertyList(QScriptContext *context) +: d(new PropertyListPrivate) +{ + Q_UNUSED(context); + Q_ASSERT(thisObject().engine() == engine()); +} + +bool PropertyList::isEmpty() const +{ + Q_ASSERT(thisObject().engine() == engine()); + auto p = qscriptvalue_cast(thisObject()); + return p->d->propertyListObject.isNull(); +} + +void PropertyList::clear() +{ + Q_ASSERT(thisObject().engine() == engine()); + auto p = qscriptvalue_cast(thisObject()); + p->d->propertyListObject = QVariant(); + p->d->propertyListFormat = 0; +} + +void PropertyList::readFromObject(const QScriptValue &value) +{ + Q_ASSERT(thisObject().engine() == engine()); + auto p = qscriptvalue_cast(thisObject()); + p->d->propertyListObject = value.toVariant(); + p->d->propertyListFormat = 0; // wasn't deserialized from any external format +} + +void PropertyList::readFromString(const QString &input) +{ + readFromData(input.toUtf8()); +} + +void PropertyList::readFromFile(const QString &filePath) +{ + Q_ASSERT(thisObject().engine() == engine()); + auto p = qscriptvalue_cast(thisObject()); + + QFile file(filePath); + if (file.open(QIODevice::ReadOnly)) { + const QByteArray data = file.readAll(); + if (file.error() == QFile::NoError) { + p->d->readFromData(p->context(), data); + return; + } + } + + p->context()->throwError(QStringLiteral("%1: %2").arg(filePath).arg(file.errorString())); +} + +void PropertyList::readFromData(const QByteArray &data) +{ + Q_ASSERT(thisObject().engine() == engine()); + auto p = qscriptvalue_cast(thisObject()); + p->d->readFromData(p->context(), data); +} + +void PropertyList::writeToFile(const QString &filePath, const QString &plistFormat) +{ + Q_ASSERT(thisObject().engine() == engine()); + auto p = qscriptvalue_cast(thisObject()); + + QFile file(filePath); + QByteArray data = p->d->writeToData(p->context(), plistFormat); + if (Q_LIKELY(!data.isEmpty())) { + if (file.open(QIODevice::WriteOnly) && file.write(data) == data.size()) { + return; + } + } + + p->context()->throwError(QStringLiteral("%1: %2").arg(filePath).arg(file.errorString())); +} + +QScriptValue PropertyList::format() const +{ + Q_ASSERT(thisObject().engine() == engine()); + auto p = qscriptvalue_cast(thisObject()); + switch (p->d->propertyListFormat) + { + case QPropertyListOpenStepFormat: + return QStringLiteral("openstep"); + case QPropertyListXMLFormat_v1_0: + return QStringLiteral("xml1"); + case QPropertyListBinaryFormat_v1_0: + return QStringLiteral("binary1"); + case QPropertyListJSONFormat: + return QStringLiteral("json"); + default: + return p->engine()->undefinedValue(); + } +} + +QScriptValue PropertyList::toObject() const +{ + Q_ASSERT(thisObject().engine() == engine()); + auto p = qscriptvalue_cast(thisObject()); + return p->engine()->toScriptValue(p->d->propertyListObject); +} + +QString PropertyList::toString(const QString &plistFormat) const +{ + Q_ASSERT(thisObject().engine() == engine()); + auto p = qscriptvalue_cast(thisObject()); + + if (plistFormat == QLatin1String("binary1")) { + p->context()->throwError(QStringLiteral("Property list object cannot be converted to a " + "string in the binary1 format; this format can only " + "be written directly to a file")); + return {}; + } + + if (!isEmpty()) + return QString::fromUtf8(p->d->writeToData(p->context(), plistFormat)); + + return {}; +} + +QString PropertyList::toXMLString() const +{ + return toString(QStringLiteral("xml1")); +} + +QString PropertyList::toJSON(const QString &style) const +{ + QString format = QLatin1String("json"); + if (!style.isEmpty()) + format += QLatin1String("-") + style; + + return toString(format); +} + +} // namespace Internal +} // namespace qbs + +#include "propertylistutils.h" + +namespace qbs { +namespace Internal { + +void PropertyListPrivate::readFromData(QScriptContext *context, const QByteArray &data) +{ + @autoreleasepool { + NSPropertyListFormat format; + int internalFormat = 0; + NSString *errorString = nil; + id plist = [NSPropertyListSerialization propertyListWithData:data.toNSData() + options:0 + format:&format error:nil]; + if (plist) { + internalFormat = format; + } else { + NSError *error = nil; + plist = [NSJSONSerialization JSONObjectWithData:data.toNSData() + options:0 + error:&error]; + if (Q_UNLIKELY(!plist)) { + errorString = [error localizedDescription]; + } else { + internalFormat = QPropertyListJSONFormat; + } + } + + if (Q_UNLIKELY(!plist)) { + context->throwError(QString::fromNSString(errorString)); + } else { + QVariant obj = QPropertyListUtils::fromPropertyList(plist); + if (!obj.isNull()) { + propertyListObject = obj; + propertyListFormat = internalFormat; + } else { + context->throwError(QStringLiteral("error converting property list")); + } + } + } +} + +QByteArray PropertyListPrivate::writeToData(QScriptContext *context, const QString &format) +{ + @autoreleasepool { + NSError *error = nil; + NSString *errorString = nil; + NSData *data = nil; + + id obj = QPropertyListUtils::toPropertyList(propertyListObject); + if (!obj) { + context->throwError(QStringLiteral("error converting property list")); + return QByteArray(); + } + + if (format == QLatin1String("json") || format == QLatin1String("json-pretty") || + format == QLatin1String("json-compact")) { + if ([NSJSONSerialization isValidJSONObject:obj]) { + error = nil; + errorString = nil; + const NSJSONWritingOptions options = format == QLatin1String("json-pretty") + ? NSJSONWritingPrettyPrinted : 0; + data = [NSJSONSerialization dataWithJSONObject:obj + options:options + error:&error]; + if (Q_UNLIKELY(!data)) { + errorString = [error localizedDescription]; + } + } else { + errorString = @"Property list object cannot be converted to JSON data"; + } + } else if (format == QLatin1String("xml1") || format == QLatin1String("binary1")) { + const NSPropertyListFormat plistFormat = format == QLatin1String("xml1") + ? NSPropertyListXMLFormat_v1_0 + : NSPropertyListBinaryFormat_v1_0; + + error = nil; + errorString = nil; + data = [NSPropertyListSerialization dataWithPropertyList:obj + format:plistFormat + options:0 + error:&error]; + if (Q_UNLIKELY(!data)) { + errorString = [error localizedDescription]; + } + } else { + errorString = [NSString stringWithFormat:@"Property lists cannot be written in the '%s' " + @"format", format.toUtf8().constData()]; + } + + if (Q_UNLIKELY(!data)) { + context->throwError(QString::fromNSString(errorString)); + } + + return QByteArray::fromNSData(data); + } +} + +} // namespace Internal +} // namespace qbs + +void initializeJsExtensionPropertyList(QScriptValue extensionObject) +{ + using namespace qbs::Internal; + QScriptEngine *engine = extensionObject.engine(); + QScriptValue obj = engine->newQMetaObject(&PropertyList::staticMetaObject, + engine->newFunction(&PropertyList::ctor)); + extensionObject.setProperty(QStringLiteral("PropertyList"), obj); +} + +Q_DECLARE_METATYPE(qbs::Internal::PropertyList *) + +#include "propertylist.moc" diff --git a/src/lib/corelib/jsextensions/propertylistutils.h b/src/lib/corelib/jsextensions/propertylistutils.h new file mode 100644 index 00000000..0fd9832c --- /dev/null +++ b/src/lib/corelib/jsextensions/propertylistutils.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2014 Petroules Corporation. +** 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 QPROPERTYLISTUTILS_H +#define QPROPERTYLISTUTILS_H + +#include +#import + +#if !defined(__OBJC__) || !defined(__cplusplus) +#error "This file must be included from Objective-C++" +#endif + +class QPropertyListUtils +{ + Q_DISABLE_COPY(QPropertyListUtils) + +public: + static QVariant fromPropertyList(id plist); + static id toPropertyList(const QVariant &map); + +private: + QPropertyListUtils(); +}; + +template +QMap qHashToMap(const QHash &hash) { + QMap map; + QHashIterator i(hash); + while (i.hasNext()) { + i.next(); + map.insert(i.key(), i.value()); + } + return map; +} + +#endif // QPROPERTYLISTUTILS_H diff --git a/src/lib/corelib/jsextensions/propertylistutils.mm b/src/lib/corelib/jsextensions/propertylistutils.mm new file mode 100644 index 00000000..b8ae1b8e --- /dev/null +++ b/src/lib/corelib/jsextensions/propertylistutils.mm @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Petroules Corporation. +** 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$ +** +****************************************************************************/ + +#import +#include "propertylistutils.h" +#include +#include +#include +#include + +static QVariant fromObject(id obj); +static QVariantMap fromDictionary(NSDictionary *dict); +static QVariantList fromArray(NSArray *array); + +static QVariant fromObject(id obj) +{ + QVariant value; + if (!obj) { + return value; + } else if ([obj isKindOfClass:[NSDictionary class]]) { + value = fromDictionary(obj); + } else if ([obj isKindOfClass:[NSArray class]]) { + value = fromArray(obj); + } else if ([obj isKindOfClass:[NSString class]]) { + value = QString::fromNSString(obj); + } else if ([obj isKindOfClass:[NSData class]]) { + value = QByteArray::fromNSData(obj); + } else if ([obj isKindOfClass:[NSDate class]]) { + value = QDateTime::fromNSDate(obj); + } else if ([obj isKindOfClass:[NSNumber class]]) { + if (strcmp([(NSNumber *)obj objCType], @encode(BOOL)) == 0) { + value = static_cast([obj boolValue]); + } else if (strcmp([(NSNumber *)obj objCType], @encode(signed char)) == 0) { + value = [obj charValue]; + } else if (strcmp([(NSNumber *)obj objCType], @encode(unsigned char)) == 0) { + value = [obj unsignedCharValue]; + } else if (strcmp([(NSNumber *)obj objCType], @encode(signed short)) == 0) { + value = [obj shortValue]; + } else if (strcmp([(NSNumber *)obj objCType], @encode(unsigned short)) == 0) { + value = [obj unsignedShortValue]; + } else if (strcmp([(NSNumber *)obj objCType], @encode(signed int)) == 0) { + value = [obj intValue]; + } else if (strcmp([(NSNumber *)obj objCType], @encode(unsigned int)) == 0) { + value = [obj unsignedIntValue]; + } else if (strcmp([(NSNumber *)obj objCType], @encode(signed long long)) == 0) { + value = [obj longLongValue]; + } else if (strcmp([(NSNumber *)obj objCType], @encode(unsigned long long)) == 0) { + value = [obj unsignedLongLongValue]; + } else if (strcmp([(NSNumber *)obj objCType], @encode(float)) == 0) { + value = [obj floatValue]; + } else if (strcmp([(NSNumber *)obj objCType], @encode(double)) == 0) { + value = [obj doubleValue]; + } else { + // NSDecimal or unknown + value = [obj doubleValue]; + } + } else if ([obj isKindOfClass:[NSNull class]]) { + // A null variant, close enough... + } else { + // unknown + } + + return value; +} + +static QVariantMap fromDictionary(NSDictionary *dict) +{ + QVariantMap map; + for (NSString *key in dict) + map[QString::fromNSString(key)] = fromObject([dict objectForKey:key]); + return map; +} + +static QVariantList fromArray(NSArray *array) +{ + QVariantList list; + for (id obj in array) + list.push_back(fromObject(obj)); + return list; +} + +QVariant QPropertyListUtils::fromPropertyList(id plist) +{ + return fromObject(plist); +} + +static id toObject(const QVariant &variant); +static NSDictionary *toDictionary(const QVariantMap &map); +static NSArray *toArray(const QVariantList &list); + +static id toObject(const QVariant &variant) +{ + if (variant.type() == QVariant::Hash) { + return toDictionary(qHashToMap(variant.toHash())); + } else if (variant.type() == QVariant::Map) { + return toDictionary(variant.toMap()); + } else if (variant.type() == QVariant::List) { + return toArray(variant.toList()); + } else if (variant.type() == QVariant::String) { + return variant.toString().toNSString(); + } else if (variant.type() == QVariant::ByteArray) { + return variant.toByteArray().toNSData(); + } else if (variant.type() == QVariant::Date || + variant.type() == QVariant::DateTime) { + return variant.toDateTime().toNSDate(); + } else if (variant.type() == QVariant::Bool) { + return variant.toBool() + ? [NSNumber numberWithBool:YES] + : [NSNumber numberWithBool:NO]; + } else if (variant.type() == QVariant::Char || + variant.type() == QVariant::Int) { + return [NSNumber numberWithInt:variant.toInt()]; + } else if (variant.type() == QVariant::UInt) { + return [NSNumber numberWithUnsignedInt:variant.toUInt()]; + } else if (variant.type() == QVariant::LongLong) { + return [NSNumber numberWithLongLong:variant.toLongLong()]; + } else if (variant.type() == QVariant::ULongLong) { + return [NSNumber numberWithUnsignedLongLong:variant.toULongLong()]; + } else if (variant.type() == QVariant::Double) { + return [NSNumber numberWithDouble:variant.toDouble()]; + } else { + return [NSNull null]; + } +} + +static NSDictionary *toDictionary(const QVariantMap &map) +{ + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + QMapIterator i(map); + while (i.hasNext()) { + i.next(); + [dict setObject:toObject(i.value()) forKey:i.key().toNSString()]; + } + return [NSDictionary dictionaryWithDictionary:dict]; +} + +static NSArray *toArray(const QVariantList &list) +{ + NSMutableArray *array = [NSMutableArray array]; + for (const QVariant &variant : list) + [array addObject:toObject(variant)]; + return [NSArray arrayWithArray:array]; +} + +id QPropertyListUtils::toPropertyList(const QVariant &variant) +{ + return toObject(variant); +} diff --git a/src/lib/corelib/jsextensions/temporarydir.cpp b/src/lib/corelib/jsextensions/temporarydir.cpp new file mode 100644 index 00000000..470d21d2 --- /dev/null +++ b/src/lib/corelib/jsextensions/temporarydir.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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 + +#include +#include +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +static bool tempDirIsCanonical() +{ +#if QT_VERSION >= 0x050c00 + return true; +#endif + return false; +} + +class TemporaryDir : public QObject, public QScriptable +{ + Q_OBJECT +public: + static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); + TemporaryDir(QScriptContext *context); + Q_INVOKABLE bool isValid() const; + Q_INVOKABLE QString path() const; + Q_INVOKABLE bool remove(); +private: + QTemporaryDir dir; +}; + +QScriptValue TemporaryDir::ctor(QScriptContext *context, QScriptEngine *engine) +{ + const auto se = static_cast(engine); + const DubiousContextList dubiousContexts({ + DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) + }); + se->checkContext(QStringLiteral("qbs.TemporaryDir"), dubiousContexts); + + const auto t = new TemporaryDir(context); + QScriptValue obj = engine->newQObject(t, QScriptEngine::ScriptOwnership); + return obj; +} + +TemporaryDir::TemporaryDir(QScriptContext *context) +{ + Q_UNUSED(context); + dir.setAutoRemove(false); +} + +bool TemporaryDir::isValid() const +{ + return dir.isValid(); +} + +QString TemporaryDir::path() const +{ + return tempDirIsCanonical() ? dir.path() : QFileInfo(dir.path()).canonicalFilePath(); +} + +bool TemporaryDir::remove() +{ + return dir.remove(); +} + +} // namespace Internal +} // namespace qbs + +void initializeJsExtensionTemporaryDir(QScriptValue extensionObject) +{ + using namespace qbs::Internal; + QScriptEngine *engine = extensionObject.engine(); + QScriptValue obj = engine->newQMetaObject(&TemporaryDir::staticMetaObject, + engine->newFunction(&TemporaryDir::ctor)); + extensionObject.setProperty(QStringLiteral("TemporaryDir"), obj); +} + +#include "temporarydir.moc" diff --git a/src/lib/corelib/jsextensions/textfile.cpp b/src/lib/corelib/jsextensions/textfile.cpp new file mode 100644 index 00000000..7c67f901 --- /dev/null +++ b/src/lib/corelib/jsextensions/textfile.cpp @@ -0,0 +1,263 @@ +/**************************************************************************** +** +** 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 +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +class TextFile : public QObject, public QScriptable, public ResourceAcquiringScriptObject +{ + Q_OBJECT + Q_ENUMS(OpenMode) +public: + enum OpenMode + { + ReadOnly = 1, + WriteOnly = 2, + ReadWrite = ReadOnly | WriteOnly, + Append = 4 + }; + + static QScriptValue ctor(QScriptContext *context, QScriptEngine *engine); + ~TextFile() override; + + Q_INVOKABLE void close(); + Q_INVOKABLE QString filePath(); + Q_INVOKABLE void setCodec(const QString &codec); + Q_INVOKABLE QString readLine(); + Q_INVOKABLE QString readAll(); + Q_INVOKABLE bool atEof() const; + Q_INVOKABLE void truncate(); + Q_INVOKABLE void write(const QString &str); + Q_INVOKABLE void writeLine(const QString &str); + +private: + TextFile(QScriptContext *context, const QString &filePath, OpenMode mode = ReadOnly, + const QString &codec = QLatin1String("UTF8")); + + bool checkForClosed() const; + + // ResourceAcquiringScriptObject implementation + void releaseResources() override; + + QFile *m_file; + QTextStream *m_stream; +}; + +QScriptValue TextFile::ctor(QScriptContext *context, QScriptEngine *engine) +{ + TextFile *t; + switch (context->argumentCount()) { + case 0: + return context->throwError(Tr::tr("TextFile constructor needs path of file to be opened.")); + case 1: + t = new TextFile(context, context->argument(0).toString()); + break; + case 2: + t = new TextFile(context, + context->argument(0).toString(), + static_cast(context->argument(1).toInt32()) + ); + break; + case 3: + t = new TextFile(context, + context->argument(0).toString(), + static_cast(context->argument(1).toInt32()), + context->argument(2).toString() + ); + break; + default: + return context->throwError(Tr::tr("TextFile constructor takes at most three parameters.")); + } + + const auto se = static_cast(engine); + se->addResourceAcquiringScriptObject(t); + const DubiousContextList dubiousContexts({ + DubiousContext(EvalContext::PropertyEvaluation, DubiousContext::SuggestMoving) + }); + se->checkContext(QStringLiteral("qbs.TextFile"), dubiousContexts); + se->setUsesIo(); + + return engine->newQObject(t, QScriptEngine::QtOwnership); +} + +TextFile::~TextFile() +{ + delete m_stream; + delete m_file; +} + +TextFile::TextFile(QScriptContext *context, const QString &filePath, OpenMode mode, + const QString &codec) +{ + Q_UNUSED(codec) + Q_ASSERT(thisObject().engine() == engine()); + + m_file = new QFile(filePath); + m_stream = new QTextStream(m_file); + 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())); + delete m_file; + m_file = nullptr; + } +} + +void TextFile::close() +{ + if (checkForClosed()) + return; + delete m_stream; + m_stream = nullptr; + m_file->close(); + delete m_file; + m_file = nullptr; +} + +QString TextFile::filePath() +{ + if (checkForClosed()) + return {}; + return QFileInfo(*m_file).absoluteFilePath(); +} + +void TextFile::setCodec(const QString &codec) +{ + if (checkForClosed()) + return; + m_stream->setCodec(qPrintable(codec)); +} + +QString TextFile::readLine() +{ + if (checkForClosed()) + return {}; + return m_stream->readLine(); +} + +QString TextFile::readAll() +{ + if (checkForClosed()) + return {}; + return m_stream->readAll(); +} + +bool TextFile::atEof() const +{ + if (checkForClosed()) + return true; + return m_stream->atEnd(); +} + +void TextFile::truncate() +{ + if (checkForClosed()) + return; + m_file->resize(0); + m_stream->reset(); +} + +void TextFile::write(const QString &str) +{ + if (checkForClosed()) + return; + (*m_stream) << str; +} + +void TextFile::writeLine(const QString &str) +{ + if (checkForClosed()) + return; + (*m_stream) << str; + if (HostOsInfo::isWindowsHost()) + (*m_stream) << '\r'; + (*m_stream) << '\n'; +} + +bool TextFile::checkForClosed() const +{ + if (m_file) + return false; + 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 + +void initializeJsExtensionTextFile(QScriptValue extensionObject) +{ + using namespace qbs::Internal; + QScriptEngine *engine = extensionObject.engine(); + QScriptValue obj = engine->newQMetaObject(&TextFile::staticMetaObject, + engine->newFunction(&TextFile::ctor)); + extensionObject.setProperty(QStringLiteral("TextFile"), obj); +} + +Q_DECLARE_METATYPE(qbs::Internal::TextFile *) + +#include "textfile.moc" diff --git a/src/lib/corelib/jsextensions/utilitiesextension.cpp b/src/lib/corelib/jsextensions/utilitiesextension.cpp new file mode 100644 index 00000000..6c693cb6 --- /dev/null +++ b/src/lib/corelib/jsextensions/utilitiesextension.cpp @@ -0,0 +1,945 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(Q_OS_MACOS) || defined(Q_OS_OSX) +#include +#endif + +#ifdef __APPLE__ +#include +#include +#include +#include +#ifndef FAT_MAGIC_64 +#define FAT_MAGIC_64 0xcafebabf +#define FAT_CIGAM_64 0xbfbafeca +struct fat_arch_64 { + cpu_type_t cputype; + cpu_subtype_t cpusubtype; + uint64_t offset; + uint64_t size; + uint32_t align; + uint32_t reserved; +}; +#endif +#endif + + +#ifdef Q_OS_WIN +#include +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { + +class DummyLogSink : public ILogSink { + void doPrintMessage(LoggerLevel, const QString &, const QString &) override { } +}; + +class UtilitiesExtension : public QObject, QScriptable +{ + Q_OBJECT +public: + static QScriptValue js_ctor(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_canonicalArchitecture(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_canonicalPlatform(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_canonicalTargetArchitecture(QScriptContext *context, + QScriptEngine *engine); + static QScriptValue js_canonicalToolchain(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_cStringQuote(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_getHash(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_getNativeSetting(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_kernelVersion(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_nativeSettingGroups(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_rfc1034identifier(QScriptContext *context, QScriptEngine *engine); + + static QScriptValue js_smimeMessageContent(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_certificateInfo(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_signingIdentities(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_msvcCompilerInfo(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_clangClCompilerInfo(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_installedMSVCs(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_installedClangCls(QScriptContext *context, QScriptEngine *engine); + + static QScriptValue js_versionCompare(QScriptContext *context, QScriptEngine *engine); + + static QScriptValue js_qmlTypeInfo(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_builtinExtensionNames(QScriptContext *context, QScriptEngine *engine); + static QScriptValue js_isSharedLibrary(QScriptContext *context, QScriptEngine *engine); + + static QScriptValue js_getArchitecturesFromBinary(QScriptContext *context, + QScriptEngine *engine); +}; + +QScriptValue UtilitiesExtension::js_ctor(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(engine); + return context->throwError(Tr::tr("'Utilities' cannot be instantiated.")); +} + +QScriptValue UtilitiesExtension::js_canonicalPlatform(QScriptContext *context, + QScriptEngine *engine) +{ + const QScriptValue value = context->argument(0); + if (value.isUndefined() || value.isNull()) + return engine->toScriptValue(QStringList()); + + if (context->argumentCount() == 1 && value.isString()) { + return engine->toScriptValue([&value] { + QStringList list; + for (const auto &s : HostOsInfo::canonicalOSIdentifiers(value.toString().toStdString())) + list.push_back(QString::fromStdString(s)); + return list; + }()); + } + + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("canonicalPlatform expects one argument of type string")); +} + +QScriptValue UtilitiesExtension::js_canonicalTargetArchitecture(QScriptContext *context, + QScriptEngine *engine) +{ + const QScriptValue arch = context->argument(0); + if (arch.isUndefined() || arch.isNull()) + return arch; + + QScriptValue endianness = context->argument(1); + if (endianness.isUndefined() || endianness.isNull()) + endianness = QString(); + const QScriptValue vendor = context->argument(2); + const QScriptValue system = context->argument(3); + const QScriptValue abi = context->argument(4); + + if (!arch.isString() || !endianness.isString() + || !vendor.isString() || !system.isString() || !abi.isString()) + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("canonicalTargetArchitecture expects 1 to 5 arguments of type string")); + + return engine->toScriptValue(canonicalTargetArchitecture(arch.toString(), endianness.toString(), + vendor.toString(), + system.toString(), abi.toString())); +} + +QScriptValue UtilitiesExtension::js_canonicalArchitecture(QScriptContext *context, + QScriptEngine *engine) +{ + const QScriptValue value = context->argument(0); + if (value.isUndefined() || value.isNull()) + return value; + + if (context->argumentCount() == 1 && value.isString()) + return engine->toScriptValue(canonicalArchitecture(value.toString())); + + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("canonicalArchitecture expects one argument of type string")); +} + +QScriptValue UtilitiesExtension::js_canonicalToolchain(QScriptContext *context, + QScriptEngine *engine) +{ + QStringList toolchain; + for (int i = 0; i < context->argumentCount(); ++i) + toolchain << context->argument(i).toString(); + return engine->toScriptValue(canonicalToolchain(toolchain)); +} + +// copied from src/corelib/tools/qtools_p.h +Q_DECL_CONSTEXPR inline char toHexUpper(uint value) Q_DECL_NOTHROW +{ + return "0123456789ABCDEF"[value & 0xF]; +} + +Q_DECL_CONSTEXPR inline int fromHex(uint c) Q_DECL_NOTHROW +{ + return ((c >= '0') && (c <= '9')) ? int(c - '0') : + ((c >= 'A') && (c <= 'F')) ? int(c - 'A' + 10) : + ((c >= 'a') && (c <= 'f')) ? int(c - 'a' + 10) : + /* otherwise */ -1; +} + +// copied from src/corelib/io/qdebug.cpp +static inline bool isPrintable(uchar c) +{ return c >= ' ' && c < 0x7f; } + +// modified +template +static inline QString escapedString(const Char *begin, int length, bool isUnicode = true) +{ + QChar quote(QLatin1Char('"')); + QString out = quote; + + bool lastWasHexEscape = false; + const Char *end = begin + length; + for (const Char *p = begin; p != end; ++p) { + // check if we need to insert "" to break an hex escape sequence + if (Q_UNLIKELY(lastWasHexEscape)) { + if (fromHex(*p) != -1) { + // yes, insert it + out += QLatin1Char('"'); + out += QLatin1Char('"'); + } + lastWasHexEscape = false; + } + + if (sizeof(Char) == sizeof(QChar)) { + // Surrogate characters are category Cs (Other_Surrogate), so isPrintable = false for them + int runLength = 0; + while (p + runLength != end && + QChar::isPrint(p[runLength]) && p[runLength] != '\\' && p[runLength] != '"') + ++runLength; + if (runLength) { + out += QString(reinterpret_cast(p), runLength); + p += runLength - 1; + continue; + } + } else if (isPrintable(*p) && *p != '\\' && *p != '"') { + QChar c = QLatin1Char(*p); + out += c; + continue; + } + + // print as an escape sequence (maybe, see below for surrogate pairs) + int buflen = 2; + ushort buf[sizeof "\\U12345678" - 1]; + buf[0] = '\\'; + + switch (*p) { + case '"': + case '\\': + buf[1] = *p; + break; + case '\b': + buf[1] = 'b'; + break; + case '\f': + buf[1] = 'f'; + break; + case '\n': + buf[1] = 'n'; + break; + case '\r': + buf[1] = 'r'; + break; + case '\t': + buf[1] = 't'; + break; + default: + if (!isUnicode) { + // print as hex escape + buf[1] = 'x'; + buf[2] = toHexUpper(uchar(*p) >> 4); + buf[3] = toHexUpper(uchar(*p)); + buflen = 4; + lastWasHexEscape = true; + break; + } + if (QChar::isHighSurrogate(*p)) { + if ((p + 1) != end && QChar::isLowSurrogate(p[1])) { + // properly-paired surrogates + uint ucs4 = QChar::surrogateToUcs4(*p, p[1]); + if (QChar::isPrint(ucs4)) { + buf[0] = *p; + buf[1] = p[1]; + buflen = 2; + } else { + buf[1] = 'U'; + buf[2] = '0'; // toHexUpper(ucs4 >> 32); + buf[3] = '0'; // toHexUpper(ucs4 >> 28); + buf[4] = toHexUpper(ucs4 >> 20); + buf[5] = toHexUpper(ucs4 >> 16); + buf[6] = toHexUpper(ucs4 >> 12); + buf[7] = toHexUpper(ucs4 >> 8); + buf[8] = toHexUpper(ucs4 >> 4); + buf[9] = toHexUpper(ucs4); + buflen = 10; + } + ++p; + break; + } + // improperly-paired surrogates, fall through + } + buf[1] = 'u'; + buf[2] = toHexUpper(ushort(*p) >> 12); + buf[3] = toHexUpper(ushort(*p) >> 8); + buf[4] = toHexUpper(*p >> 4); + buf[5] = toHexUpper(*p); + buflen = 6; + } + out += QString(reinterpret_cast(buf), buflen); + } + + out += quote; + return out; +} + +QScriptValue UtilitiesExtension::js_cStringQuote(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("cStringQuote expects 1 argument")); + } + QString value = context->argument(0).toString(); + return engine->toScriptValue(escapedString(reinterpret_cast(value.constData()), value.size())); +} + +QScriptValue UtilitiesExtension::js_getHash(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 1)) { + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("getHash expects 1 argument")); + } + const QByteArray input = context->argument(0).toString().toLatin1(); + const QByteArray hash + = QCryptographicHash::hash(input, QCryptographicHash::Sha1).toHex().left(16); + return engine->toScriptValue(QString::fromLatin1(hash)); +} + +QScriptValue UtilitiesExtension::js_getNativeSetting(QScriptContext *context, QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() < 1 || context->argumentCount() > 3)) { + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("getNativeSetting expects between 1 and 3 arguments")); + } + + QString key = context->argumentCount() > 1 ? context->argument(1).toString() : QString(); + + // We'll let empty string represent the default registry value + if (HostOsInfo::isWindowsHost() && key.isEmpty()) + key = StringConstants::dot(); + + QVariant defaultValue = context->argumentCount() > 2 ? context->argument(2).toVariant() : QVariant(); + + QSettings settings(context->argument(0).toString(), QSettings::NativeFormat); + QVariant value = settings.value(key, defaultValue); + return value.isNull() ? engine->undefinedValue() : engine->toScriptValue(value); +} + +QScriptValue UtilitiesExtension::js_kernelVersion(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(context); + return engine->toScriptValue(QSysInfo::kernelVersion()); +} + +QScriptValue UtilitiesExtension::js_nativeSettingGroups(QScriptContext *context, + QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() != 1)) { + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("nativeSettingGroups expects 1 argument")); + } + + QSettings settings(context->argument(0).toString(), QSettings::NativeFormat); + return engine->toScriptValue(settings.childGroups()); +} + +QScriptValue UtilitiesExtension::js_rfc1034identifier(QScriptContext *context, + QScriptEngine *engine) +{ + if (Q_UNLIKELY(context->argumentCount() != 1)) + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("rfc1034Identifier expects 1 argument")); + const QString identifier = context->argument(0).toString(); + return engine->toScriptValue(HostOsInfo::rfc1034Identifier(identifier)); +} + +/** + * Reads the contents of the S/MIME message located at \p filePath. + * An equivalent command line would be: + * \code security cms -D -i -o \endcode + * or: + * \code openssl smime -verify -noverify -inform DER -in -out \endcode + * + * \note A provisioning profile is an S/MIME message whose contents are an XML property list, + * so this method can be used to read such files. + */ +QScriptValue UtilitiesExtension::js_smimeMessageContent(QScriptContext *context, + QScriptEngine *engine) +{ +#if !defined(Q_OS_MACOS) && !defined(Q_OS_OSX) + Q_UNUSED(engine); + return context->throwError(QScriptContext::UnknownError, + QStringLiteral("smimeMessageContent is not available on this platform")); +#else + if (Q_UNLIKELY(context->argumentCount() != 1)) + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("smimeMessageContent expects 1 argument")); + + const QString filePath = context->argument(0).toString(); + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) + return engine->undefinedValue(); + + QByteArray content = smimeMessageContent(file.readAll()); + if (content.isEmpty()) + return engine->undefinedValue(); + return engine->toScriptValue(content); +#endif +} + +QScriptValue UtilitiesExtension::js_certificateInfo(QScriptContext *context, + QScriptEngine *engine) +{ +#if !defined(Q_OS_MACOS) && !defined(Q_OS_OSX) + Q_UNUSED(engine); + return context->throwError(QScriptContext::UnknownError, + QStringLiteral("certificateInfo is not available on this platform")); +#else + if (Q_UNLIKELY(context->argumentCount() != 1)) + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("certificateInfo expects 1 argument")); + return engine->toScriptValue(certificateInfo(context->argument(0).toVariant().toByteArray())); +#endif +} + +// Rough command line equivalent: security find-identity -p codesigning -v +QScriptValue UtilitiesExtension::js_signingIdentities(QScriptContext *context, + QScriptEngine *engine) +{ +#if !defined(Q_OS_MACOS) && !defined(Q_OS_OSX) + Q_UNUSED(engine); + return context->throwError(QScriptContext::UnknownError, + QStringLiteral("signingIdentities is not available on this platform")); +#else + Q_UNUSED(context); + return engine->toScriptValue(identitiesProperties()); +#endif +} + +#ifdef Q_OS_WIN +static std::pair msvcCompilerInfoHelper( + const QString &compilerFilePath, + MSVC::CompilerLanguage language, + const QString &vcvarsallPath, + const QString &arch) +{ + MSVC msvc(compilerFilePath, arch); + VsEnvironmentDetector envdetector(vcvarsallPath); + if (!envdetector.start(&msvc)) + return { {}, QStringLiteral("Detecting the MSVC build environment failed: ") + + envdetector.errorString() }; + + try { + QVariantMap envMap; + for (const QString &key : msvc.environment.keys()) + envMap.insert(key, msvc.environment.value(key)); + + return { + QVariantMap { + {QStringLiteral("buildEnvironment"), envMap}, + {QStringLiteral("macros"), msvc.compilerDefines(compilerFilePath, language)}, + }, + {} + }; + } catch (const qbs::ErrorInfo &info) { + return { {}, info.toString() }; + } +} +#endif + +QScriptValue UtilitiesExtension::js_msvcCompilerInfo(QScriptContext *context, QScriptEngine *engine) +{ +#ifndef Q_OS_WIN + Q_UNUSED(engine); + return context->throwError(QScriptContext::UnknownError, + QStringLiteral("msvcCompilerInfo is not available on this platform")); +#else + if (Q_UNLIKELY(context->argumentCount() < 2)) + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("msvcCompilerInfo expects 2 arguments")); + + const QString compilerFilePath = context->argument(0).toString(); + const QString compilerLanguage = context->argument(1).toString(); + MSVC::CompilerLanguage language; + if (compilerLanguage == QStringLiteral("c")) + language = MSVC::CLanguage; + else if (compilerLanguage == StringConstants::cppLang()) + language = MSVC::CPlusPlusLanguage; + else + return context->throwError(QScriptContext::TypeError, + QStringLiteral("msvcCompilerInfo expects \"c\" or \"cpp\" as its second argument")); + + const auto result = msvcCompilerInfoHelper( + compilerFilePath, language, {}, MSVC::architectureFromClPath(compilerFilePath)); + if (result.first.isEmpty()) + return context->throwError(QScriptContext::UnknownError, result.second); + return engine->toScriptValue(result.first); +#endif +} + +QScriptValue UtilitiesExtension::js_clangClCompilerInfo(QScriptContext *context, QScriptEngine *engine) +{ +#ifndef Q_OS_WIN + Q_UNUSED(engine); + return context->throwError(QScriptContext::UnknownError, + QStringLiteral("clangClCompilerInfo is not available on this platform")); +#else + if (Q_UNLIKELY(context->argumentCount() < 4)) + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("clangClCompilerInfo expects 4 arguments")); + + const QString compilerFilePath = context->argument(0).toString(); + // architecture cannot be empty as vcvarsall.bat requires at least 1 arg, so fallback + // to host architecture if none is present + QString arch = !context->argument(1).isNull() && !context->argument(1).isUndefined() + ? context->argument(1).toString() + : QString::fromStdString(HostOsInfo::hostOSArchitecture()); + QString vcvarsallPath = context->argument(2).toString(); + const QString compilerLanguage = context->argumentCount() > 3 + ? context->argument(3).toString() + : QString(); + MSVC::CompilerLanguage language; + if (compilerLanguage == QStringLiteral("c")) + language = MSVC::CLanguage; + else if (compilerLanguage == StringConstants::cppLang()) + language = MSVC::CPlusPlusLanguage; + else + return context->throwError(QScriptContext::TypeError, + QStringLiteral("clangClCompilerInfo expects \"c\" or \"cpp\" as its fourth argument")); + + const auto result = msvcCompilerInfoHelper( + compilerFilePath, language, vcvarsallPath, arch); + if (result.first.isEmpty()) + return context->throwError(QScriptContext::UnknownError, result.second); + return engine->toScriptValue(result.first); +#endif +} + +QScriptValue UtilitiesExtension::js_installedMSVCs(QScriptContext *context, QScriptEngine *engine) +{ +#ifndef Q_OS_WIN + Q_UNUSED(engine); + return context->throwError(QScriptContext::UnknownError, + QStringLiteral("installedMSVCs is not available on this platform")); +#else + if (Q_UNLIKELY(context->argumentCount() != 1)) { + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("installedMSVCs expects 1 arguments")); + } + + const auto value0 = context->argument(0); + const auto hostArch = QString::fromStdString(HostOsInfo::hostOSArchitecture()); + const auto preferredArch = !value0.isNull() && !value0.isUndefined() + ? value0.toString() + : hostArch; + + DummyLogSink dummySink; + Logger dummyLogger(&dummySink); + auto msvcs = MSVC::installedCompilers(dummyLogger); + + const auto predicate = [&preferredArch, &hostArch](const MSVC &msvc) + { + auto archPair = MSVC::getHostTargetArchPair(msvc.architecture); + return archPair.first != hostArch || preferredArch != archPair.second; + }; + msvcs.erase(std::remove_if(msvcs.begin(), msvcs.end(), predicate), msvcs.end()); + QVariantList result; + for (const auto &msvc: msvcs) + result.append(msvc.toVariantMap()); + return engine->toScriptValue(result); +#endif +} + +QScriptValue UtilitiesExtension::js_installedClangCls( + QScriptContext *context, QScriptEngine *engine) +{ +#ifndef Q_OS_WIN + Q_UNUSED(engine); + return context->throwError(QScriptContext::UnknownError, + QStringLiteral("installedClangCls is not available on this platform")); +#else + if (Q_UNLIKELY(context->argumentCount() != 1)) { + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("installedClangCls expects 1 arguments")); + } + + const auto value0 = context->argument(0); + const auto path = !value0.isNull() && !value0.isUndefined() ? value0.toString() : QString(); + + DummyLogSink dummySink; + Logger dummyLogger(&dummySink); + auto compilers = ClangClInfo::installedCompilers({path}, dummyLogger); + QVariantList result; + for (const auto &compiler: compilers) + result.append(compiler.toVariantMap()); + return engine->toScriptValue(result); +#endif +} + +QScriptValue UtilitiesExtension::js_versionCompare(QScriptContext *context, QScriptEngine *engine) +{ + if (context->argumentCount() == 2) { + const QScriptValue value1 = context->argument(0); + const QScriptValue value2 = context->argument(1); + if (value1.isString() && value2.isString()) { + const auto a = Version::fromString(value1.toString()); + const auto b = Version::fromString(value2.toString()); + return engine->toScriptValue(compare(a, b)); + } + } + + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("versionCompare expects two arguments of type string")); +} + +QScriptValue UtilitiesExtension::js_qmlTypeInfo(QScriptContext *context, QScriptEngine *engine) +{ + Q_UNUSED(context); + return engine->toScriptValue(QString::fromStdString(qbs::LanguageInfo::qmlTypeInfo())); +} + +QScriptValue UtilitiesExtension::js_builtinExtensionNames(QScriptContext *context, + QScriptEngine *engine) +{ + Q_UNUSED(context); + return engine->toScriptValue(JsExtensions::extensionNames()); +} + +QScriptValue UtilitiesExtension::js_isSharedLibrary(QScriptContext *context, QScriptEngine *engine) +{ + if (context->argumentCount() == 1) { + const QScriptValue value = context->argument(0); + if (value.isString()) + return engine->toScriptValue(QLibrary::isLibrary(value.toString())); + } + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("isSharedLibrary expects one argument of type string")); +} + +#ifdef __APPLE__ +template T readInt(QIODevice *ioDevice, bool *ok, + bool swap, bool peek = false) { + const auto bytes = peek + ? ioDevice->peek(sizeof(T)) + : ioDevice->read(sizeof(T)); + if (bytes.size() != sizeof(T)) { + if (ok) + *ok = false; + return T(); + } + if (ok) + *ok = true; + T n = *reinterpret_cast(bytes.constData()); + return swap ? qbswap(n) : n; +} + +static QString archName(cpu_type_t cputype, cpu_subtype_t cpusubtype) +{ + switch (cputype) { + case CPU_TYPE_X86: + switch (cpusubtype) { + case CPU_SUBTYPE_X86_ALL: + return QStringLiteral("i386"); + default: + return {}; + } + case CPU_TYPE_X86_64: + switch (cpusubtype) { + case CPU_SUBTYPE_X86_64_ALL: + return QStringLiteral("x86_64"); + case CPU_SUBTYPE_X86_64_H: + return QStringLiteral("x86_64h"); + default: + return {}; + } + case CPU_TYPE_ARM: + switch (cpusubtype) { + case CPU_SUBTYPE_ARM_V7: + return QStringLiteral("armv7a"); + case CPU_SUBTYPE_ARM_V7S: + return QStringLiteral("armv7s"); + case CPU_SUBTYPE_ARM_V7K: + return QStringLiteral("armv7k"); + default: + return {}; + } + case CPU_TYPE_ARM64: + switch (cpusubtype) { + case CPU_SUBTYPE_ARM64_ALL: + return QStringLiteral("arm64"); + default: + return {}; + } + default: + return {}; + } +} + +static QStringList detectMachOArchs(QIODevice *device) +{ + bool ok; + bool foundMachO = false; + qint64 pos = device->pos(); + + char ar_header[SARMAG]; + if (device->read(ar_header, SARMAG) == SARMAG) { + if (strncmp(ar_header, ARMAG, SARMAG) == 0) { + while (!device->atEnd()) { + static_assert(sizeof(ar_hdr) == 60, "sizeof(ar_hdr) != 60"); + ar_hdr header; + if (device->read(reinterpret_cast(&header), + sizeof(ar_hdr)) != sizeof(ar_hdr)) + return {}; + + // If the file name is stored in the "extended format" manner, + // the real filename is prepended to the data section, so skip that many bytes + int filenameLength = 0; + if (strncmp(header.ar_name, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0) { + char arName[sizeof(header.ar_name)] = { 0 }; + memcpy(arName, header.ar_name + sizeof(AR_EFMT1) - 1, + sizeof(header.ar_name) - (sizeof(AR_EFMT1) - 1) - 1); + filenameLength = strtoul(arName, nullptr, 10); + if (device->read(filenameLength).size() != filenameLength) + return {}; + } + + switch (readInt(device, nullptr, false, true)) { + case MH_CIGAM: + case MH_CIGAM_64: + case MH_MAGIC: + case MH_MAGIC_64: + foundMachO = true; + break; + default: { + // Skip the data and go to the next archive member... + char szBuf[sizeof(header.ar_size) + 1] = { 0 }; + memcpy(szBuf, header.ar_size, sizeof(header.ar_size)); + int sz = static_cast(strtoul(szBuf, nullptr, 10)); + if (sz % 2 != 0) + ++sz; + sz -= filenameLength; + const auto data = device->read(sz); + if (data.size() != sz) + return {}; + } + } + + if (foundMachO) + break; + } + } + } + + // Wasn't an archive file, so try a fat file + if (!foundMachO && !device->seek(pos)) + return {}; + + pos = device->pos(); + + fat_header fatheader; + fatheader.magic = readInt(device, nullptr, false); + if (fatheader.magic == FAT_MAGIC || fatheader.magic == FAT_CIGAM || + fatheader.magic == FAT_MAGIC_64 || fatheader.magic == FAT_CIGAM_64) { + const bool swap = fatheader.magic == FAT_CIGAM || fatheader.magic == FAT_CIGAM_64; + const bool is64bit = fatheader.magic == FAT_MAGIC_64 || fatheader.magic == FAT_CIGAM_64; + fatheader.nfat_arch = readInt(device, &ok, swap); + if (!ok) + return {}; + + QStringList archs; + + for (uint32_t n = 0; n < fatheader.nfat_arch; ++n) { + fat_arch_64 fatarch; + static_assert(sizeof(fat_arch_64) == 32, "sizeof(fat_arch_64) != 32"); + static_assert(sizeof(fat_arch) == 20, "sizeof(fat_arch) != 20"); + const qint64 expectedBytes = is64bit ? sizeof(fat_arch_64) : sizeof(fat_arch); + if (device->read(reinterpret_cast(&fatarch), expectedBytes) != expectedBytes) + return {}; + + if (swap) { + fatarch.cputype = qbswap(fatarch.cputype); + fatarch.cpusubtype = qbswap(fatarch.cpusubtype); + } + + const QString name = archName(fatarch.cputype, fatarch.cpusubtype); + if (name.isEmpty()) { + qWarning("Unknown cputype %d and cpusubtype %d", + fatarch.cputype, fatarch.cpusubtype); + return {}; + } + archs.push_back(name); + } + + std::sort(archs.begin(), archs.end()); + return archs; + } + + // Wasn't a fat file, so we just read a thin Mach-O from the original offset + if (!device->seek(pos)) + return {}; + + bool swap = false; + mach_header header; + header.magic = readInt(device, nullptr, swap); + switch (header.magic) { + case MH_CIGAM: + case MH_CIGAM_64: + swap = true; + break; + case MH_MAGIC: + case MH_MAGIC_64: + break; + default: + return {}; + } + + header.cputype = static_cast(readInt(device, &ok, swap)); + if (!ok) + return {}; + + header.cpusubtype = static_cast(readInt(device, &ok, swap)); + if (!ok) + return {}; + + const QString name = archName(header.cputype, header.cpusubtype); + if (name.isEmpty()) { + qWarning("Unknown cputype %d and cpusubtype %d", + header.cputype, header.cpusubtype); + return {}; + } + return {name}; +} +#endif + +QScriptValue UtilitiesExtension::js_getArchitecturesFromBinary(QScriptContext *context, + QScriptEngine *engine) +{ + if (context->argumentCount() != 1) { + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("getArchitecturesFromBinary expects exactly one argument")); + } + const QScriptValue arg = context->argument(0); + if (!arg.isString()) { + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("getArchitecturesFromBinary expects a string argument")); + } + QStringList archs; +#ifdef __APPLE__ + QFile file(arg.toString()); + if (!file.open(QIODevice::ReadOnly)) { + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("Failed to open file '%1': %2") + .arg(file.fileName(), file.errorString())); + } + archs = detectMachOArchs(&file); +#endif // __APPLE__ + return engine->toScriptValue(archs); +} + +} // namespace Internal +} // namespace qbs + +void initializeJsExtensionUtilities(QScriptValue extensionObject) +{ + using namespace qbs::Internal; + QScriptEngine *engine = extensionObject.engine(); + QScriptValue environmentObj = engine->newQMetaObject(&UtilitiesExtension::staticMetaObject, + engine->newFunction(&UtilitiesExtension::js_ctor)); + environmentObj.setProperty(QStringLiteral("canonicalArchitecture"), + engine->newFunction(UtilitiesExtension::js_canonicalArchitecture, 1)); + environmentObj.setProperty(QStringLiteral("canonicalPlatform"), + engine->newFunction(UtilitiesExtension::js_canonicalPlatform, 1)); + environmentObj.setProperty(QStringLiteral("canonicalTargetArchitecture"), + engine->newFunction( + UtilitiesExtension::js_canonicalTargetArchitecture, 4)); + environmentObj.setProperty(QStringLiteral("canonicalToolchain"), + engine->newFunction(UtilitiesExtension::js_canonicalToolchain)); + environmentObj.setProperty(QStringLiteral("cStringQuote"), + engine->newFunction(UtilitiesExtension::js_cStringQuote, 1)); + environmentObj.setProperty(QStringLiteral("getHash"), + engine->newFunction(UtilitiesExtension::js_getHash, 1)); + environmentObj.setProperty(QStringLiteral("getNativeSetting"), + engine->newFunction(UtilitiesExtension::js_getNativeSetting, 3)); + environmentObj.setProperty(QStringLiteral("kernelVersion"), + engine->newFunction(UtilitiesExtension::js_kernelVersion, 0)); + environmentObj.setProperty(QStringLiteral("nativeSettingGroups"), + engine->newFunction(UtilitiesExtension::js_nativeSettingGroups, 1)); + environmentObj.setProperty(QStringLiteral("rfc1034Identifier"), + engine->newFunction(UtilitiesExtension::js_rfc1034identifier, 1)); + environmentObj.setProperty(QStringLiteral("smimeMessageContent"), + engine->newFunction(UtilitiesExtension::js_smimeMessageContent, 1)); + environmentObj.setProperty(QStringLiteral("certificateInfo"), + engine->newFunction(UtilitiesExtension::js_certificateInfo, 1)); + environmentObj.setProperty(QStringLiteral("signingIdentities"), + engine->newFunction(UtilitiesExtension::js_signingIdentities, 0)); + environmentObj.setProperty(QStringLiteral("msvcCompilerInfo"), + engine->newFunction(UtilitiesExtension::js_msvcCompilerInfo, 1)); + environmentObj.setProperty(QStringLiteral("clangClCompilerInfo"), + engine->newFunction(UtilitiesExtension::js_clangClCompilerInfo, 1)); + environmentObj.setProperty(QStringLiteral("installedMSVCs"), + engine->newFunction(UtilitiesExtension::js_installedMSVCs, 1)); + environmentObj.setProperty(QStringLiteral("installedClangCls"), + engine->newFunction(UtilitiesExtension::js_installedClangCls, 1)); + environmentObj.setProperty(QStringLiteral("versionCompare"), + engine->newFunction(UtilitiesExtension::js_versionCompare, 2)); + environmentObj.setProperty(QStringLiteral("qmlTypeInfo"), + engine->newFunction(UtilitiesExtension::js_qmlTypeInfo, 0)); + environmentObj.setProperty(QStringLiteral("builtinExtensionNames"), + engine->newFunction(UtilitiesExtension::js_builtinExtensionNames, 0)); + environmentObj.setProperty(QStringLiteral("isSharedLibrary"), + engine->newFunction(UtilitiesExtension::js_isSharedLibrary, 1)); + environmentObj.setProperty(QStringLiteral("getArchitecturesFromBinary"), + engine->newFunction(UtilitiesExtension::js_getArchitecturesFromBinary, 1)); + extensionObject.setProperty(QStringLiteral("Utilities"), environmentObj); +} + +Q_DECLARE_METATYPE(qbs::Internal::UtilitiesExtension *) + +#include "utilitiesextension.moc" diff --git a/src/lib/corelib/language/artifactproperties.cpp b/src/lib/corelib/language/artifactproperties.cpp new file mode 100644 index 00000000..011e58d8 --- /dev/null +++ b/src/lib/corelib/language/artifactproperties.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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 "artifactproperties.h" +#include + +namespace qbs { +namespace Internal { + +ArtifactPropertiesPtr ArtifactProperties::create() +{ + return ArtifactPropertiesPtr(new ArtifactProperties); +} + +ArtifactProperties::ArtifactProperties() = default; + +FileTags ArtifactProperties::extraFileTags() const +{ + return m_extraFileTags; +} + +void ArtifactProperties::addExtraFileTags(const FileTags &extraFileTags) +{ + m_extraFileTags.unite(extraFileTags); +} + +bool operator==(const ArtifactProperties &ap1, const ArtifactProperties &ap2) +{ + return ap1.fileTagsFilter() == ap2.fileTagsFilter() + && ap1.extraFileTags() == ap2.extraFileTags() + && !ap1.propertyMap() == !ap2.propertyMap() + && *ap1.propertyMap() == *ap2.propertyMap(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/artifactproperties.h b/src/lib/corelib/language/artifactproperties.h new file mode 100644 index 00000000..856aa80c --- /dev/null +++ b/src/lib/corelib/language/artifactproperties.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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 QBS_ARTIFACTPROPERTIES_H +#define QBS_ARTIFACTPROPERTIES_H + +#include "filetags.h" +#include "forward_decls.h" +#include + +namespace qbs { +namespace Internal { + +class ArtifactProperties +{ +public: + static ArtifactPropertiesPtr create(); + + void setFileTagsFilter(const FileTags &filter) { m_fileTagsFilter = filter; } + FileTags fileTagsFilter() const { return m_fileTagsFilter; } + + PropertyMapPtr propertyMap() const { return m_propertyMap; } + void setPropertyMapInternal(const PropertyMapPtr &pmap) { m_propertyMap = pmap; } + + FileTags extraFileTags() const; + void addExtraFileTags(const FileTags &extraFileTags); + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(m_fileTagsFilter, m_extraFileTags, m_propertyMap); + } + +private: + ArtifactProperties(); + + FileTags m_fileTagsFilter; + FileTags m_extraFileTags; + PropertyMapPtr m_propertyMap; +}; + +bool operator==(const ArtifactProperties &ap1, const ArtifactProperties &ap2); +inline bool operator!=(const ArtifactProperties &ap1, const ArtifactProperties &ap2) { + return !(ap1 == ap2); +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ARTIFACTPROPERTIES_H diff --git a/src/lib/corelib/language/astimportshandler.cpp b/src/lib/corelib/language/astimportshandler.cpp new file mode 100644 index 00000000..d634af7e --- /dev/null +++ b/src/lib/corelib/language/astimportshandler.cpp @@ -0,0 +1,305 @@ +/**************************************************************************** +** +** 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 "astimportshandler.h" + +#include "asttools.h" +#include "builtindeclarations.h" +#include "filecontext.h" +#include "itemreadervisitorstate.h" +#include "jsextensions/jsextensions.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +ASTImportsHandler::ASTImportsHandler(ItemReaderVisitorState &visitorState, Logger &logger, + const FileContextPtr &file) + : m_visitorState(visitorState) + , m_logger(logger) + , m_file(file) + , m_directory(FileInfo::path(m_file->filePath())) +{ +} + +void ASTImportsHandler::handleImports(const QbsQmlJS::AST::UiImportList *uiImportList) +{ + const auto searchPaths = m_file->searchPaths(); + for (const QString &searchPath : searchPaths) + collectPrototypes(searchPath + QStringLiteral("/imports"), QString()); + + // files in the same directory are available as prototypes + collectPrototypes(m_directory, QString()); + + bool baseImported = false; + for (const auto *it = uiImportList; it; it = it->next) + handleImport(it->import, &baseImported); + if (!baseImported) { + QStringRef qbsref(&StringConstants::qbsModule()); + QbsQmlJS::AST::UiQualifiedId qbsURI(qbsref); + qbsURI.finish(); + QbsQmlJS::AST::UiImport imp(&qbsURI); + handleImport(&imp, &baseImported); + } + + for (auto it = m_jsImports.constBegin(); it != m_jsImports.constEnd(); ++it) + m_file->addJsImport(it.value()); +} + +void ASTImportsHandler::handleImport(const QbsQmlJS::AST::UiImport *import, bool *baseImported) +{ + QStringList importUri; + bool isBase = false; + if (import->importUri) { + importUri = toStringList(import->importUri); + isBase = (importUri.size() == 1 && importUri.front() == StringConstants::qbsModule()) + || (importUri.size() == 2 && importUri.front() == StringConstants::qbsModule() + && importUri.last() == StringConstants::baseVar()); + if (isBase) { + *baseImported = true; + checkImportVersion(import->versionToken); + } else if (import->versionToken.length) { + m_logger.printWarning(ErrorInfo(Tr::tr("Superfluous version specification."), + toCodeLocation(m_file->filePath(), import->versionToken))); + } + } + + QString as; + if (isBase) { + if (Q_UNLIKELY(!import->importId.isNull())) { + throw ErrorInfo(Tr::tr("Import of qbs.base must have no 'as '"), + toCodeLocation(m_file->filePath(), import->importIdToken)); + } + } else { + if (importUri.size() == 2 && importUri.front() == StringConstants::qbsModule()) { + const QString extensionName = importUri.last(); + if (JsExtensions::hasExtension(extensionName)) { + if (Q_UNLIKELY(!import->importId.isNull())) { + throw ErrorInfo(Tr::tr("Import of built-in extension '%1' " + "must not have 'as' specifier.").arg(extensionName), + toCodeLocation(m_file->filePath(), import->asToken)); + } + if (Q_UNLIKELY(m_file->jsExtensions().contains(extensionName))) { + m_logger.printWarning(ErrorInfo(Tr::tr("Built-in extension '%1' already " + "imported.").arg(extensionName), + toCodeLocation(m_file->filePath(), + import->importToken))); + } else { + m_file->addJsExtension(extensionName); + } + return; + } + } + + if (import->importId.isNull()) { + if (!import->fileName.isNull()) { + throw ErrorInfo(Tr::tr("File imports require 'as '"), + toCodeLocation(m_file->filePath(), import->importToken)); + } + if (importUri.empty()) { + throw ErrorInfo(Tr::tr("Invalid import URI."), + toCodeLocation(m_file->filePath(), import->importToken)); + } + as = importUri.last(); + } else { + as = import->importId.toString(); + } + + if (Q_UNLIKELY(JsExtensions::hasExtension(as))) + throw ErrorInfo(Tr::tr("Cannot reuse the name of built-in extension '%1'.").arg(as), + toCodeLocation(m_file->filePath(), import->importIdToken)); + if (Q_UNLIKELY(!m_importAsNames.insert(as).second)) { + throw ErrorInfo(Tr::tr("Cannot import into the same name more than once."), + toCodeLocation(m_file->filePath(), import->importIdToken)); + } + } + + if (!import->fileName.isNull()) { + QString filePath = FileInfo::resolvePath(m_directory, import->fileName.toString()); + + QFileInfo fi(filePath); + if (Q_UNLIKELY(!fi.exists())) + throw ErrorInfo(Tr::tr("Cannot find imported file %0.") + .arg(QDir::toNativeSeparators(filePath)), + CodeLocation(m_file->filePath(), import->fileNameToken.startLine, + import->fileNameToken.startColumn)); + filePath = fi.canonicalFilePath(); + if (fi.isDir()) { + collectPrototypesAndJsCollections(filePath, as, + toCodeLocation(m_file->filePath(), import->fileNameToken)); + } else { + if (filePath.endsWith(QStringLiteral(".js"), Qt::CaseInsensitive)) { + JsImport &jsImport = m_jsImports[as]; + jsImport.scopeName = as; + jsImport.filePaths.push_back(filePath); + jsImport.location + = toCodeLocation(m_file->filePath(), import->firstSourceLocation()); + } else if (filePath.endsWith(QStringLiteral(".qbs"), Qt::CaseInsensitive)) { + m_typeNameToFile.insert(QStringList(as), filePath); + } else { + throw ErrorInfo(Tr::tr("Can only import .qbs and .js files"), + CodeLocation(m_file->filePath(), import->fileNameToken.startLine, + import->fileNameToken.startColumn)); + } + } + } else if (!importUri.empty()) { + const QString importPath = isBase + ? QStringLiteral("qbs/base") : importUri.join(QDir::separator()); + bool found = m_typeNameToFile.contains(importUri); + if (!found) { + const auto searchPaths = m_file->searchPaths(); + for (const QString &searchPath : searchPaths) { + const QFileInfo fi(FileInfo::resolvePath( + FileInfo::resolvePath(searchPath, + StringConstants::importsDir()), + importPath)); + if (fi.isDir()) { + // ### versioning, qbsdir file, etc. + const QString &resultPath = fi.absoluteFilePath(); + collectPrototypesAndJsCollections(resultPath, as, + toCodeLocation(m_file->filePath(), import->fileNameToken)); + found = true; + break; + } + } + } + if (Q_UNLIKELY(!found)) { + throw ErrorInfo(Tr::tr("import %1 not found") + .arg(importUri.join(QLatin1Char('.'))), + toCodeLocation(m_file->filePath(), import->fileNameToken)); + } + } +} + +Version ASTImportsHandler::readImportVersion(const QString &str, const CodeLocation &location) +{ + const Version v = Version::fromString(str); + if (Q_UNLIKELY(!v.isValid())) + throw ErrorInfo(Tr::tr("Cannot parse version number in import statement."), location); + if (Q_UNLIKELY(v.patchLevel() != 0)) { + throw ErrorInfo(Tr::tr("Version number in import statement cannot have more than " + "two components."), location); + } + return v; +} + +bool ASTImportsHandler::addPrototype(const QString &fileName, const QString &filePath, + const QString &as, bool needsCheck) +{ + if (needsCheck && fileName.size() <= 4) + return false; + + const QString componentName = fileName.left(fileName.size() - 4); + // ### validate componentName + + if (needsCheck && !componentName.at(0).isUpper()) + return false; + + QStringList prototypeName; + if (!as.isEmpty()) + prototypeName.push_back(as); + prototypeName.push_back(componentName); + if (!m_typeNameToFile.contains(prototypeName)) + m_typeNameToFile.insert(prototypeName, filePath); + return true; +} + +void ASTImportsHandler::checkImportVersion(const QbsQmlJS::AST::SourceLocation &versionToken) const +{ + if (!versionToken.length) + return; + const QString importVersionString + = m_file->content().mid(versionToken.offset, versionToken.length); + const Version importVersion = readImportVersion(importVersionString, + toCodeLocation(m_file->filePath(), versionToken)); + if (Q_UNLIKELY(importVersion != BuiltinDeclarations::instance().languageVersion())) + throw ErrorInfo(Tr::tr("Incompatible qbs language version %1. This is version %2.").arg( + importVersionString, + BuiltinDeclarations::instance().languageVersion().toString()), + toCodeLocation(m_file->filePath(), versionToken)); + +} + +void ASTImportsHandler::collectPrototypes(const QString &path, const QString &as) +{ + QStringList fileNames; // Yes, file *names*. + if (m_visitorState.findDirectoryEntries(path, &fileNames)) { + for (const QString &fileName : qAsConst(fileNames)) + addPrototype(fileName, path + QLatin1Char('/') + fileName, as, false); + return; + } + + QDirIterator dirIter(path, StringConstants::qbsFileWildcards()); + while (dirIter.hasNext()) { + const QString filePath = dirIter.next(); + const QString fileName = dirIter.fileName(); + if (addPrototype(fileName, filePath, as, true)) + fileNames << fileName; + } + m_visitorState.cacheDirectoryEntries(path, fileNames); + +} + +void ASTImportsHandler::collectPrototypesAndJsCollections(const QString &path, const QString &as, + const CodeLocation &location) +{ + collectPrototypes(path, as); + QDirIterator dirIter(path, StringConstants::jsFileWildcards()); + while (dirIter.hasNext()) { + dirIter.next(); + JsImport &jsImport = m_jsImports[as]; + if (jsImport.scopeName.isNull()) { + jsImport.scopeName = as; + jsImport.location = location; + } + jsImport.filePaths.push_back(dirIter.filePath()); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/astimportshandler.h b/src/lib/corelib/language/astimportshandler.h new file mode 100644 index 00000000..e9c2b6c2 --- /dev/null +++ b/src/lib/corelib/language/astimportshandler.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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 QBS_ASTIMPORTSHANDLER_H +#define QBS_ASTIMPORTSHANDLER_H + +#include "forward_decls.h" + +#include +#include + +#include +#include + +namespace qbs { +class CodeLocation; +class Version; + +namespace Internal { +class ItemReaderVisitorState; +class JsImport; +class Logger; + +class ASTImportsHandler +{ +public: + ASTImportsHandler(ItemReaderVisitorState &visitorState, Logger &logger, + const FileContextPtr &file); + + void handleImports(const QbsQmlJS::AST::UiImportList *uiImportList); + + QHash typeNameFileMap() const { return m_typeNameToFile; } + +private: + static Version readImportVersion(const QString &str, const CodeLocation &location); + + bool addPrototype(const QString &fileName, const QString &filePath, const QString &as, + bool needsCheck); + void checkImportVersion(const QbsQmlJS::AST::SourceLocation &versionToken) const; + void collectPrototypes(const QString &path, const QString &as); + void collectPrototypesAndJsCollections(const QString &path, const QString &as, + const CodeLocation &location); + void handleImport(const QbsQmlJS::AST::UiImport *import, bool *baseImported); + + ItemReaderVisitorState &m_visitorState; + Logger &m_logger; + const FileContextPtr &m_file; + const QString m_directory; + QHash m_typeNameToFile; + Set m_importAsNames; + + using JsImportsHash = QHash; + JsImportsHash m_jsImports; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/language/astpropertiesitemhandler.cpp b/src/lib/corelib/language/astpropertiesitemhandler.cpp new file mode 100644 index 00000000..1ea78bf7 --- /dev/null +++ b/src/lib/corelib/language/astpropertiesitemhandler.cpp @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** 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 "astpropertiesitemhandler.h" + +#include "item.h" +#include "value.h" + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +ASTPropertiesItemHandler::ASTPropertiesItemHandler(Item *parentItem) : m_parentItem(parentItem) +{ +} + +void ASTPropertiesItemHandler::handlePropertiesItems() +{ + // TODO: Simply forbid Properties items to have child items and get rid of this check. + if (m_parentItem->type() != ItemType::Properties) + setupAlternatives(); +} + +void ASTPropertiesItemHandler::setupAlternatives() +{ + auto it = m_parentItem->m_children.begin(); + while (it != m_parentItem->m_children.end()) { + Item * const child = *it; + bool remove = false; + if (child->type() == ItemType::Properties) { + handlePropertiesBlock(child); + remove = m_parentItem->type() != ItemType::Export; + } + if (remove) + it = m_parentItem->m_children.erase(it); + else + ++it; + } +} + +class PropertiesBlockConverter +{ +public: + PropertiesBlockConverter(const JSSourceValue::AltProperty &condition, + const JSSourceValue::AltProperty &overrideListProperties, + Item *propertiesBlockContainer, const Item *propertiesBlock) + : m_propertiesBlockContainer(propertiesBlockContainer) + , m_propertiesBlock(propertiesBlock) + { + m_alternative.condition = condition; + m_alternative.overrideListProperties = overrideListProperties; + } + + void apply() + { + doApply(m_propertiesBlockContainer, m_propertiesBlock); + } + +private: + JSSourceValue::Alternative m_alternative; + Item * const m_propertiesBlockContainer; + const Item * const m_propertiesBlock; + + void doApply(Item *outer, const Item *inner) + { + for (auto it = inner->properties().constBegin(); + it != inner->properties().constEnd(); ++it) { + if (inner == m_propertiesBlock + && (it.key() == StringConstants::conditionProperty() + || it.key() == StringConstants::overrideListPropertiesProperty())) { + continue; + } + if (it.value()->type() == Value::ItemValueType) { + Item * const innerVal = std::static_pointer_cast(it.value())->item(); + ItemValuePtr outerVal = outer->itemProperty(it.key()); + if (!outerVal) { + outerVal = ItemValue::create(Item::create(outer->pool(), innerVal->type()), + true); + outer->setProperty(it.key(), outerVal); + } + doApply(outerVal->item(), innerVal); + } else if (it.value()->type() == Value::JSSourceValueType) { + const ValuePtr outerVal = outer->property(it.key()); + if (Q_UNLIKELY(outerVal && outerVal->type() != Value::JSSourceValueType)) { + throw ErrorInfo(Tr::tr("Incompatible value type in unconditional value at %1.") + .arg(outerVal->location().toString())); + } + doApply(it.key(), outer, std::static_pointer_cast(outerVal), + std::static_pointer_cast(it.value())); + } else { + QBS_CHECK(!"Unexpected value type in conditional value."); + } + } + } + + void doApply(const QString &propertyName, Item *item, JSSourceValuePtr value, + const JSSourceValuePtr &conditionalValue) + { + if (!value) { + value = JSSourceValue::create(true); + value->setFile(conditionalValue->file()); + item->setProperty(propertyName, value); + value->setSourceCode(QStringRef(&StringConstants::baseVar())); + value->setSourceUsesBaseFlag(); + } + m_alternative.value = conditionalValue; + value->addAlternative(m_alternative); + } +}; + +static JSSourceValue::AltProperty getPropertyData(const Item *propertiesItem, const QString &name) +{ + const ValuePtr value = propertiesItem->property(name); + if (!value) { + if (name == StringConstants::conditionProperty()) { + throw ErrorInfo(Tr::tr("Properties.condition must be provided."), + propertiesItem->location()); + } + return JSSourceValue::AltProperty(StringConstants::falseValue(), + propertiesItem->location()); + } + if (Q_UNLIKELY(value->type() != Value::JSSourceValueType)) { + throw ErrorInfo(Tr::tr("Properties.%1 must be a value binding.").arg(name), + propertiesItem->location()); + } + if (name == StringConstants::overrideListPropertiesProperty()) { + const Item *parent = propertiesItem->parent(); + while (parent) { + if (parent->type() == ItemType::Product) + break; + parent = parent->parent(); + } + if (!parent) { + throw ErrorInfo(Tr::tr("Properties.overrideListProperties can only be set " + "in a Product item.")); + } + + } + const JSSourceValuePtr srcval = std::static_pointer_cast(value); + return JSSourceValue::AltProperty(srcval->sourceCodeForEvaluation(), srcval->location()); +} + +void ASTPropertiesItemHandler::handlePropertiesBlock(const Item *propertiesItem) +{ + const auto condition = getPropertyData(propertiesItem, StringConstants::conditionProperty()); + const auto overrideListProperties = getPropertyData(propertiesItem, + StringConstants::overrideListPropertiesProperty()); + PropertiesBlockConverter(condition, overrideListProperties, m_parentItem, + propertiesItem).apply(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/astpropertiesitemhandler.h b/src/lib/corelib/language/astpropertiesitemhandler.h new file mode 100644 index 00000000..413512ee --- /dev/null +++ b/src/lib/corelib/language/astpropertiesitemhandler.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** 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 QBS_ASTPROPERTIESITEMHANDLER_H +#define QBS_ASTPROPERTIESITEMHANDLER_H + +namespace qbs { +namespace Internal { +class Item; + +class ASTPropertiesItemHandler +{ +public: + ASTPropertiesItemHandler(Item *parentItem); + + void handlePropertiesItems(); + +private: + void setupAlternatives(); + void handlePropertiesBlock(const Item *propertiesItem); + + Item * const m_parentItem; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/language/asttools.cpp b/src/lib/corelib/language/asttools.cpp new file mode 100644 index 00000000..1b6abac7 --- /dev/null +++ b/src/lib/corelib/language/asttools.cpp @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** 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 "asttools.h" +#include + +namespace qbs { +namespace Internal { + +QStringList toStringList(QbsQmlJS::AST::UiQualifiedId *qid) +{ + QStringList result; + for (; qid; qid = qid->next) + result.push_back(qid->name.toString()); + return result; +} + +CodeLocation toCodeLocation(const QString &filePath, const QbsQmlJS::AST::SourceLocation &location) +{ + return CodeLocation(filePath, location.startLine, location.startColumn); +} + +QString textOf(const QString &source, QbsQmlJS::AST::Node *node) +{ + if (!node) + return {}; + return source.mid(node->firstSourceLocation().begin(), + int(node->lastSourceLocation().end() - node->firstSourceLocation().begin())); +} + +QStringRef textRefOf(const QString &source, QbsQmlJS::AST::Node *node) +{ + const quint32 firstBegin = node->firstSourceLocation().begin(); + return source.midRef(firstBegin, int(node->lastSourceLocation().end() - firstBegin)); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/asttools.h b/src/lib/corelib/language/asttools.h new file mode 100644 index 00000000..b4f5c4d9 --- /dev/null +++ b/src/lib/corelib/language/asttools.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** 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 QBS_ASTTOOLS_H +#define QBS_ASTTOOLS_H + +#include +#include +#include + +namespace qbs { +namespace Internal { + +QStringList toStringList(QbsQmlJS::AST::UiQualifiedId *qid); +CodeLocation toCodeLocation(const QString &filePath, const QbsQmlJS::AST::SourceLocation &location); +QString textOf(const QString &source, QbsQmlJS::AST::Node *node); +QStringRef textRefOf(const QString &source, QbsQmlJS::AST::Node *node); + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ASTTOOLS_H diff --git a/src/lib/corelib/language/builtindeclarations.cpp b/src/lib/corelib/language/builtindeclarations.cpp new file mode 100644 index 00000000..13783d3b --- /dev/null +++ b/src/lib/corelib/language/builtindeclarations.cpp @@ -0,0 +1,603 @@ +/**************************************************************************** +** +** 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 "builtindeclarations.h" + +#include "deprecationinfo.h" + +#include +#include +#include +#include + +#include + +#include +#include + +namespace qbs { +namespace Internal { + +class AClassWithPublicConstructor : public BuiltinDeclarations { }; +Q_GLOBAL_STATIC(AClassWithPublicConstructor, theInstance) + +const char QBS_LANGUAGE_VERSION[] = "1.0"; + +BuiltinDeclarations::BuiltinDeclarations() + : m_languageVersion(Version::fromString(QLatin1String(QBS_LANGUAGE_VERSION))) + , m_typeMap(std::initializer_list>({ + { QStringLiteral("Artifact"), ItemType::Artifact }, + { QStringLiteral("Depends"), ItemType::Depends }, + { QStringLiteral("Export"), ItemType::Export }, + { QStringLiteral("FileTagger"), ItemType::FileTagger }, + { QStringLiteral("Group"), ItemType::Group }, + { QStringLiteral("JobLimit"), ItemType::JobLimit }, + { QStringLiteral("Module"), ItemType::Module }, + { QStringLiteral("ModuleProvider"), ItemType::ModuleProvider }, + { QStringLiteral("Parameter"), ItemType::Parameter }, + { QStringLiteral("Parameters"), ItemType::Parameters }, + { QStringLiteral("Probe"), ItemType::Probe }, + { QStringLiteral("Product"), ItemType::Product }, + { QStringLiteral("Profile"), ItemType::Profile }, + { QStringLiteral("Project"), ItemType::Project }, + { QStringLiteral("Properties"), ItemType::Properties }, // Callers have to handle the SubProject case. + { QStringLiteral("PropertyOptions"), ItemType::PropertyOptions }, + { QStringLiteral("Rule"), ItemType::Rule }, + { QStringLiteral("Scanner"), ItemType::Scanner }, + { QStringLiteral("SubProject"), ItemType::SubProject }, + { QStringLiteral("Transformer"), ItemType::Transformer } + })) +{ + addArtifactItem(); + addDependsItem(); + addExportItem(); + addFileTaggerItem(); + addGroupItem(); + addJobLimitItem(); + addModuleItem(); + addModuleProviderItem(); + addProbeItem(); + addProductItem(); + addProfileItem(); + addProjectItem(); + addPropertiesItem(); + addPropertyOptionsItem(); + addRuleItem(); + addSubprojectItem(); + addTransformerItem(); + addScannerItem(); +} + +const BuiltinDeclarations &BuiltinDeclarations::instance() +{ + return *theInstance; +} + +Version BuiltinDeclarations::languageVersion() const +{ + return m_languageVersion; +} + +QStringList BuiltinDeclarations::allTypeNames() const +{ + return m_typeMap.keys(); +} + +ItemDeclaration BuiltinDeclarations::declarationsForType(ItemType type) const +{ + return m_builtins.value(type); +} + +ItemType BuiltinDeclarations::typeForName(const QString &typeName, + const CodeLocation &location) const +{ + const auto it = m_typeMap.constFind(typeName); + if (it == m_typeMap.constEnd()) + throw ErrorInfo(Tr::tr("Unexpected item type '%1'.").arg(typeName), location); + return it.value(); +} + +QString BuiltinDeclarations::nameForType(ItemType itemType) const +{ + // Iterating is okay here, as this mapping is not used in hot code paths. + if (itemType == ItemType::PropertiesInSubProject) + return QStringLiteral("Properties"); + for (auto it = m_typeMap.constBegin(); it != m_typeMap.constEnd(); ++it) { + if (it.value() == itemType) + return it.key(); + } + QBS_CHECK(false); + return {}; +} + +QStringList BuiltinDeclarations::argumentNamesForScriptFunction(ItemType itemType, + const QString &scriptName) const +{ + const ItemDeclaration itemDecl = declarationsForType(itemType); + const auto properties = itemDecl.properties(); + for (const PropertyDeclaration &propDecl : properties) { + if (propDecl.name() == scriptName) + return propDecl.functionArgumentNames(); + } + QBS_CHECK(false); + return {}; +} + +void BuiltinDeclarations::insert(const ItemDeclaration &decl) +{ + m_builtins.insert(decl.type(), decl); +} + +static PropertyDeclaration conditionProperty() +{ + return PropertyDeclaration(StringConstants::conditionProperty(), PropertyDeclaration::Boolean, + StringConstants::trueValue()); +} + +static PropertyDeclaration alwaysRunProperty() +{ + return PropertyDeclaration(StringConstants::alwaysRunProperty(), PropertyDeclaration::Boolean, + StringConstants::falseValue()); +} + +static PropertyDeclaration nameProperty() +{ + return PropertyDeclaration(StringConstants::nameProperty(), PropertyDeclaration::String); +} + +static PropertyDeclaration buildDirProperty() +{ + return PropertyDeclaration(StringConstants::buildDirectoryProperty(), + PropertyDeclaration::Path); +} + +static PropertyDeclaration prepareScriptProperty() +{ + PropertyDeclaration decl(StringConstants::prepareProperty(), PropertyDeclaration::Variant, + QString(), PropertyDeclaration::PropertyNotAvailableInConfig); + decl.setFunctionArgumentNames( + QStringList() + << StringConstants::projectVar() << StringConstants::productValue() + << StringConstants::inputsVar() << StringConstants::outputsVar() + << StringConstants::inputVar() << StringConstants::outputVar() + << StringConstants::explicitlyDependsOnVar()); + return decl; +} + +static PropertyDeclaration priorityProperty() +{ + return {StringConstants::priorityProperty(), PropertyDeclaration::Integer}; +} + +void BuiltinDeclarations::addArtifactItem() +{ + ItemDeclaration item(ItemType::Artifact); + PropertyDeclaration conditionDecl = conditionProperty(); + conditionDecl.setDeprecationInfo(DeprecationInfo(Version(1, 4), Tr::tr("If you need " + "dynamic artifacts, use the Rule.outputArtifacts script instead of Artifact items."))); + item << conditionDecl; + PropertyDeclaration fileNameDecl(StringConstants::fileNameProperty(), + PropertyDeclaration::String); + fileNameDecl.setDeprecationInfo(DeprecationInfo(Version(1, 4), + Tr::tr("Please use 'filePath' instead."))); + item << fileNameDecl; + item << PropertyDeclaration(StringConstants::filePathProperty(), PropertyDeclaration::String); + item << PropertyDeclaration(StringConstants::fileTagsProperty(), + PropertyDeclaration::StringList); + item << PropertyDeclaration(StringConstants::alwaysUpdatedProperty(), + PropertyDeclaration::Boolean, StringConstants::trueValue()); + insert(item); +} + +void BuiltinDeclarations::addDependsItem() +{ + ItemDeclaration item(ItemType::Depends); + item << conditionProperty(); + item << nameProperty(); + item << PropertyDeclaration(StringConstants::submodulesProperty(), + PropertyDeclaration::StringList); + item << PropertyDeclaration(StringConstants::requiredProperty(), PropertyDeclaration::Boolean, + StringConstants::trueValue()); + item << PropertyDeclaration(StringConstants::versionAtLeastProperty(), + PropertyDeclaration::String); + item << PropertyDeclaration(StringConstants::versionBelowProperty(), + PropertyDeclaration::String); + item << PropertyDeclaration(StringConstants::profilesProperty(), + PropertyDeclaration::StringList); + item << PropertyDeclaration(StringConstants::productTypesProperty(), + PropertyDeclaration::StringList); + item << PropertyDeclaration(StringConstants::limitToSubProjectProperty(), + PropertyDeclaration::Boolean, StringConstants::falseValue()); + item << PropertyDeclaration(StringConstants::multiplexConfigurationIdsProperty(), + PropertyDeclaration::StringList, QString(), + PropertyDeclaration::ReadOnlyFlag); + item << PropertyDeclaration(StringConstants::enableFallbackProperty(), + PropertyDeclaration::Boolean, StringConstants::trueValue()); + insert(item); +} + +void BuiltinDeclarations::addExportItem() +{ + ItemDeclaration item = moduleLikeItem(ItemType::Export); + item << PropertyDeclaration(StringConstants::prefixMappingProperty(), + PropertyDeclaration::Variant); + auto allowedChildTypes = item.allowedChildTypes(); + allowedChildTypes.insert(ItemType::Parameters); + allowedChildTypes.insert(ItemType::Properties); + item.setAllowedChildTypes(allowedChildTypes); + insert(item); +} + +void BuiltinDeclarations::addFileTaggerItem() +{ + ItemDeclaration item(ItemType::FileTagger); + item << conditionProperty(); + item << PropertyDeclaration(StringConstants::patternsProperty(), + PropertyDeclaration::StringList); + item << PropertyDeclaration(StringConstants::fileTagsProperty(), + PropertyDeclaration::StringList); + item << priorityProperty(); + insert(item); +} + +void BuiltinDeclarations::addGroupItem() +{ + ItemDeclaration item(ItemType::Group); + item.setAllowedChildTypes({ ItemType::Group }); + item << conditionProperty(); + item << PropertyDeclaration(StringConstants::nameProperty(), PropertyDeclaration::String, + QString(), PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(StringConstants::filesProperty(), PropertyDeclaration::PathList, + QString(), PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(StringConstants::fileTagsFilterProperty(), + PropertyDeclaration::StringList, + QString(), PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(StringConstants::excludeFilesProperty(), + PropertyDeclaration::PathList, + QString(), PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(StringConstants::fileTagsProperty(), + PropertyDeclaration::StringList, QString(), + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(StringConstants::prefixProperty(), PropertyDeclaration::String, + QString(), PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(StringConstants::overrideTagsProperty(), + PropertyDeclaration::Boolean, StringConstants::trueValue(), + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(StringConstants::filesAreTargetsProperty(), + PropertyDeclaration::Boolean, StringConstants::falseValue(), + PropertyDeclaration::PropertyNotAvailableInConfig); + insert(item); +} + +void BuiltinDeclarations::addJobLimitItem() +{ + ItemDeclaration item(ItemType::JobLimit); + item << conditionProperty(); + item << PropertyDeclaration(StringConstants::jobPoolProperty(), PropertyDeclaration::String); + item << PropertyDeclaration(StringConstants::jobCountProperty(), PropertyDeclaration::Integer); + insert(item); +} + +void BuiltinDeclarations::addModuleItem() +{ + ItemDeclaration item = moduleLikeItem(ItemType::Module); + item << priorityProperty(); + insert(item); +} + +void BuiltinDeclarations::addModuleProviderItem() +{ + ItemDeclaration item(ItemType::ModuleProvider); + item << nameProperty() + << PropertyDeclaration(QStringLiteral("outputBaseDir"), PropertyDeclaration::String) + << PropertyDeclaration(QStringLiteral("relativeSearchPaths"), + PropertyDeclaration::StringList); + insert(item); +} + +ItemDeclaration BuiltinDeclarations::moduleLikeItem(ItemType type) +{ + ItemDeclaration item(type); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << ItemType::Group + << ItemType::Depends + << ItemType::FileTagger + << ItemType::JobLimit + << ItemType::Rule + << ItemType::Parameter + << ItemType::Probe + << ItemType::PropertyOptions + << ItemType::Scanner); + item << nameProperty(); + item << conditionProperty(); + PropertyDeclaration setupBuildEnvDecl(StringConstants::setupBuildEnvironmentProperty(), + PropertyDeclaration::Variant, QString(), + PropertyDeclaration::PropertyNotAvailableInConfig); + setupBuildEnvDecl.setFunctionArgumentNames(QStringList{StringConstants::projectVar(), + StringConstants::productVar()}); + item << setupBuildEnvDecl; + PropertyDeclaration setupRunEnvDecl(StringConstants::setupRunEnvironmentProperty(), + PropertyDeclaration::Variant, QString(), + PropertyDeclaration::PropertyNotAvailableInConfig); + setupRunEnvDecl.setFunctionArgumentNames(QStringList{StringConstants::projectVar(), + StringConstants::productVar()}); + item << setupRunEnvDecl; + item << PropertyDeclaration(StringConstants::validateProperty(), + PropertyDeclaration::Boolean, QString(), + PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(StringConstants::additionalProductTypesProperty(), + PropertyDeclaration::StringList); + item << PropertyDeclaration(StringConstants::versionProperty(), PropertyDeclaration::String); + item << PropertyDeclaration(StringConstants::presentProperty(), PropertyDeclaration::Boolean, + StringConstants::trueValue()); + return item; +} + +void BuiltinDeclarations::addProbeItem() +{ + ItemDeclaration item(ItemType::Probe); + item << conditionProperty(); + item << PropertyDeclaration(StringConstants::foundProperty(), PropertyDeclaration::Boolean, + StringConstants::falseValue()); + item << PropertyDeclaration(StringConstants::configureProperty(), PropertyDeclaration::Variant, + QString(), PropertyDeclaration::PropertyNotAvailableInConfig); + insert(item); +} + +void BuiltinDeclarations::addProductItem() +{ + ItemDeclaration item(ItemType::Product); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << ItemType::Depends + << ItemType::Group + << ItemType::FileTagger + << ItemType::JobLimit + << ItemType::Export + << ItemType::Probe + << ItemType::Profile + << ItemType::PropertyOptions + << ItemType::Rule); + item << conditionProperty(); + item << PropertyDeclaration(StringConstants::typeProperty(), PropertyDeclaration::StringList, + StringConstants::emptyArrayValue()); + item << nameProperty(); + item << PropertyDeclaration(StringConstants::builtByDefaultProperty(), + PropertyDeclaration::Boolean, StringConstants::trueValue()); + PropertyDeclaration profilesDecl(StringConstants::profilesProperty(), + PropertyDeclaration::StringList); + profilesDecl.setDeprecationInfo(DeprecationInfo(Version::fromString(QStringLiteral("1.9.0")), + Tr::tr("Use qbs.profiles instead."))); + item << profilesDecl; + item << PropertyDeclaration(StringConstants::targetNameProperty(), PropertyDeclaration::String, + QStringLiteral("new String(name)" + ".replace(/[/\\\\?%*:|\"<>]/g, '_').valueOf()")); + item << buildDirProperty(); + item << PropertyDeclaration(StringConstants::destinationDirProperty(), + PropertyDeclaration::String, + StringConstants::buildDirectoryProperty()); + item << PropertyDeclaration(StringConstants::consoleApplicationProperty(), + PropertyDeclaration::Boolean); + item << PropertyDeclaration(StringConstants::filesProperty(), PropertyDeclaration::PathList, + QString(), PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(StringConstants::excludeFilesProperty(), + PropertyDeclaration::PathList, + QString(), PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(StringConstants::qbsSearchPathsProperty(), + PropertyDeclaration::StringList); + item << PropertyDeclaration(StringConstants::versionProperty(), PropertyDeclaration::String); + + item << PropertyDeclaration(StringConstants::multiplexByQbsPropertiesProperty(), + PropertyDeclaration::StringList, QStringLiteral("[\"profiles\"]")); + item << PropertyDeclaration(StringConstants::multiplexedTypeProperty(), + PropertyDeclaration::StringList); + item << PropertyDeclaration(StringConstants::aggregateProperty(), PropertyDeclaration::Boolean); + item << PropertyDeclaration(StringConstants::multiplexedProperty(), + PropertyDeclaration::Boolean, + QString(), PropertyDeclaration::ReadOnlyFlag); + item << PropertyDeclaration(StringConstants::multiplexConfigurationIdProperty(), + PropertyDeclaration::String, QString(), + PropertyDeclaration::ReadOnlyFlag); + insert(item); +} + +void BuiltinDeclarations::addProfileItem() +{ + ItemDeclaration item(ItemType::Profile); + item << conditionProperty(); + item << nameProperty(); + item << PropertyDeclaration(StringConstants::baseProfileProperty(), + PropertyDeclaration::String); + insert(item); +} + +void BuiltinDeclarations::addProjectItem() +{ + ItemDeclaration item(ItemType::Project); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << ItemType::Project + << ItemType::PropertyOptions + << ItemType::SubProject + << ItemType::Product + << ItemType::Profile + << ItemType::Probe + << ItemType::FileTagger + << ItemType::JobLimit + << ItemType::Rule); + item << nameProperty(); + item << conditionProperty(); + item << buildDirProperty(); + item << PropertyDeclaration(StringConstants::minimumQbsVersionProperty(), + PropertyDeclaration::String); + item << PropertyDeclaration(StringConstants::sourceDirectoryProperty(), + PropertyDeclaration::Path); + item << PropertyDeclaration(StringConstants::profileProperty(), PropertyDeclaration::String); + item << PropertyDeclaration(StringConstants::referencesProperty(), + PropertyDeclaration::PathList, + QString(), PropertyDeclaration::PropertyNotAvailableInConfig); + item << PropertyDeclaration(StringConstants::qbsSearchPathsProperty(), + PropertyDeclaration::StringList, + QString(), PropertyDeclaration::PropertyNotAvailableInConfig); + insert(item); +} + +void BuiltinDeclarations::addPropertiesItem() +{ + ItemDeclaration item(ItemType::Properties); + item << conditionProperty(); + insert(item); +} + +void BuiltinDeclarations::addPropertyOptionsItem() +{ + ItemDeclaration item(ItemType::PropertyOptions); + item << nameProperty(); + item << PropertyDeclaration(StringConstants::allowedValuesProperty(), + PropertyDeclaration::Variant); + item << PropertyDeclaration(StringConstants::descriptionProperty(), + PropertyDeclaration::String); + item << PropertyDeclaration(StringConstants::removalVersionProperty(), + PropertyDeclaration::String); + insert(item); +} + +void BuiltinDeclarations::addRuleItem() +{ + ItemDeclaration item(ItemType::Rule); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << ItemType::Artifact); + item << conditionProperty(); + item << alwaysRunProperty(); + item << PropertyDeclaration(StringConstants::multiplexProperty(), PropertyDeclaration::Boolean, + StringConstants::falseValue()); + item << PropertyDeclaration(StringConstants::requiresInputsProperty(), + PropertyDeclaration::Boolean); + item << nameProperty(); + item << PropertyDeclaration(StringConstants::inputsProperty(), PropertyDeclaration::StringList); + item << PropertyDeclaration(StringConstants::outputFileTagsProperty(), + PropertyDeclaration::StringList); + PropertyDeclaration outputArtifactsDecl(StringConstants::outputArtifactsProperty(), + PropertyDeclaration::Variant, QString(), + PropertyDeclaration::PropertyNotAvailableInConfig); + outputArtifactsDecl.setFunctionArgumentNames( + QStringList() + << StringConstants::projectVar() << StringConstants::productVar() + << StringConstants::inputsVar() << StringConstants::inputVar()); + item << outputArtifactsDecl; + PropertyDeclaration usingsDecl(QStringLiteral("usings"), PropertyDeclaration::StringList); + usingsDecl.setDeprecationInfo(DeprecationInfo(Version(1, 5), + Tr::tr("Use 'inputsFromDependencies' instead"))); + item << usingsDecl; + item << PropertyDeclaration(StringConstants::inputsFromDependenciesProperty(), + PropertyDeclaration::StringList); + item << PropertyDeclaration(StringConstants::auxiliaryInputsProperty(), + PropertyDeclaration::StringList); + PropertyDeclaration excludedAuxInputs(StringConstants::excludedAuxiliaryInputsProperty(), + PropertyDeclaration::StringList); + excludedAuxInputs.setDeprecationInfo(DeprecationInfo(Version(1, 14), + Tr::tr("Use 'excludedInputs' instead"))); + item << excludedAuxInputs; + item << PropertyDeclaration(StringConstants::excludedInputsProperty(), + PropertyDeclaration::StringList); + item << PropertyDeclaration(StringConstants::explicitlyDependsOnProperty(), + PropertyDeclaration::StringList); + item << PropertyDeclaration(StringConstants::explicitlyDependsOnFromDependenciesProperty(), + PropertyDeclaration::StringList); + item << prepareScriptProperty(); + insert(item); +} + +void BuiltinDeclarations::addSubprojectItem() +{ + ItemDeclaration item(ItemType::SubProject); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << ItemType::Project // needed, because we're adding this internally + << ItemType::PropertiesInSubProject + << ItemType::PropertyOptions); + item << conditionProperty(); + item << PropertyDeclaration(StringConstants::filePathProperty(), PropertyDeclaration::Path); + item << PropertyDeclaration(StringConstants::inheritPropertiesProperty(), + PropertyDeclaration::Boolean, + StringConstants::trueValue()); + insert(item); +} + +void BuiltinDeclarations::addTransformerItem() +{ + ItemDeclaration item(ItemType::Transformer); + item.setDeprecationInfo(DeprecationInfo(Version(1, 7), Tr::tr("Use the 'Rule' item instead."))); + item.setAllowedChildTypes(ItemDeclaration::TypeNames() + << ItemType::Artifact); + item << conditionProperty(); + item << alwaysRunProperty(); + item << PropertyDeclaration(StringConstants::inputsProperty(), PropertyDeclaration::PathList); + item << prepareScriptProperty(); + item << PropertyDeclaration(StringConstants::explicitlyDependsOnProperty(), + PropertyDeclaration::StringList); + insert(item); +} + +void BuiltinDeclarations::addScannerItem() +{ + ItemDeclaration item(ItemType::Scanner); + item << conditionProperty(); + item << PropertyDeclaration(StringConstants::inputsProperty(), PropertyDeclaration::StringList); + item << PropertyDeclaration(StringConstants::recursiveProperty(), PropertyDeclaration::Boolean, + StringConstants::falseValue()); + PropertyDeclaration searchPaths(StringConstants::searchPathsProperty(), + PropertyDeclaration::StringList); + searchPaths.setFunctionArgumentNames( + QStringList() + << StringConstants::projectVar() + << StringConstants::productVar() + << StringConstants::inputVar()); + item << searchPaths; + PropertyDeclaration scan(StringConstants::scanProperty(), PropertyDeclaration::Variant, + QString(), PropertyDeclaration::PropertyNotAvailableInConfig); + scan.setFunctionArgumentNames( + QStringList() + << StringConstants::projectVar() + << StringConstants::productVar() + << StringConstants::inputVar() + << StringConstants::filePathVar()); + item << scan; + insert(item); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/builtindeclarations.h b/src/lib/corelib/language/builtindeclarations.h new file mode 100644 index 00000000..9d7aee98 --- /dev/null +++ b/src/lib/corelib/language/builtindeclarations.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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 QBS_BUILTINDECLARATIONS_H +#define QBS_BUILTINDECLARATIONS_H + +#include "itemdeclaration.h" +#include "itemtype.h" + +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +class BuiltinDeclarations +{ +public: + static const BuiltinDeclarations &instance(); + + Version languageVersion() const; + QStringList allTypeNames() const; + ItemDeclaration declarationsForType(ItemType type) const; + ItemType typeForName(const QString &typeName, + const CodeLocation &location = CodeLocation()) const; + QString nameForType(ItemType itemType) const; + QStringList argumentNamesForScriptFunction(ItemType itemType, const QString &scriptName) const; + +protected: + BuiltinDeclarations(); + +private: + void insert(const ItemDeclaration &decl); + void addArtifactItem(); + void addDependsItem(); + void addExportItem(); + void addFileTaggerItem(); + void addGroupItem(); + void addJobLimitItem(); + void addModuleItem(); + void addModuleProviderItem(); + static ItemDeclaration moduleLikeItem(ItemType type); + void addProbeItem(); + void addProductItem(); + void addProfileItem(); + void addProjectItem(); + void addPropertiesItem(); + void addPropertyOptionsItem(); + void addRuleItem(); + void addSubprojectItem(); + void addTransformerItem(); + void addScannerItem(); + + const Version m_languageVersion; + QMap m_builtins; + const QHash m_typeMap; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_BUILTINDECLARATIONS_H diff --git a/src/lib/corelib/language/deprecationinfo.h b/src/lib/corelib/language/deprecationinfo.h new file mode 100644 index 00000000..89cd07f4 --- /dev/null +++ b/src/lib/corelib/language/deprecationinfo.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 QBS_DEPRECATIONINFO_H +#define QBS_DEPRECATIONINFO_H + +#include + +#include + +namespace qbs { +namespace Internal { + +class DeprecationInfo +{ +public: + explicit DeprecationInfo(const Version &removalVersion, + QString additionalUserInfo = QString()) + : m_removalVersion(removalVersion) + , m_additionalUserInfo(std::move(additionalUserInfo)) + {} + DeprecationInfo() = default; + + bool isValid() const { return m_removalVersion.isValid(); } + Version removalVersion() const { return m_removalVersion; } + QString additionalUserInfo() const { return m_additionalUserInfo; } + +private: + Version m_removalVersion; + QString m_additionalUserInfo; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/language/evaluationdata.h b/src/lib/corelib/language/evaluationdata.h new file mode 100644 index 00000000..791b2f23 --- /dev/null +++ b/src/lib/corelib/language/evaluationdata.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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 QBS_EVALUATIONDATA_H +#define QBS_EVALUATIONDATA_H + +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { + +class Evaluator; +class Item; + +class EvaluationData +{ +public: + Evaluator *evaluator = nullptr; + const Item *item = nullptr; + mutable QHash valueCache; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_EVALUATIONDATA_H diff --git a/src/lib/corelib/language/evaluator.cpp b/src/lib/corelib/language/evaluator.cpp new file mode 100644 index 00000000..5ea506d6 --- /dev/null +++ b/src/lib/corelib/language/evaluator.cpp @@ -0,0 +1,278 @@ +/**************************************************************************** +** +** 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 "evaluator.h" + +#include "evaluationdata.h" +#include "evaluatorscriptclass.h" +#include "filecontext.h" +#include "filetags.h" +#include "item.h" +#include "scriptengine.h" +#include "value.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +Evaluator::Evaluator(ScriptEngine *scriptEngine) + : m_scriptEngine(scriptEngine) + , m_scriptClass(new EvaluatorScriptClass(scriptEngine)) +{ +} + +Evaluator::~Evaluator() +{ + for (const auto &data : qAsConst(m_scriptValueMap)) + delete attachedPointer(data); + delete m_scriptClass; +} + +QScriptValue Evaluator::property(const Item *item, const QString &name) +{ + return scriptValue(item).property(name); +} + +QScriptValue Evaluator::value(const Item *item, const QString &name, bool *propertyWasSet) +{ + QScriptValue v; + evaluateProperty(&v, item, name, propertyWasSet); + return v; +} + +bool Evaluator::boolValue(const Item *item, const QString &name, + bool *propertyWasSet) +{ + return value(item, name, propertyWasSet).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)); +} + +QString Evaluator::stringValue(const Item *item, const QString &name, + const QString &defaultValue, bool *propertyWasSet) +{ + QScriptValue v; + if (!evaluateProperty(&v, item, name, propertyWasSet)) + return defaultValue; + return v.toString(); +} + +static QStringList toStringList(const QScriptValue &scriptValue) +{ + if (scriptValue.isString()) { + return {scriptValue.toString()}; + } else if (scriptValue.isArray()) { + QStringList lst; + int i = 0; + forever { + QScriptValue elem = scriptValue.property(i++); + if (!elem.isValid()) + break; + lst.push_back(elem.toString()); + } + return lst; + } + return {}; +} + +QStringList Evaluator::stringListValue(const Item *item, const QString &name, bool *propertyWasSet) +{ + QScriptValue v = property(item, name); + handleEvaluationError(item, name, v); + if (propertyWasSet) + *propertyWasSet = isNonDefaultValue(item, name); + return toStringList(v); +} + +bool Evaluator::isNonDefaultValue(const Item *item, const QString &name) const +{ + const ValueConstPtr v = item->property(name); + return v && (v->type() != Value::JSSourceValueType + || !static_cast(v.get())->isBuiltinDefaultValue()); +} + +void Evaluator::convertToPropertyType(const PropertyDeclaration &decl, const CodeLocation &loc, + QScriptValue &v) +{ + m_scriptClass->convertToPropertyType(decl, loc, v); +} + +QScriptValue Evaluator::scriptValue(const Item *item) +{ + QScriptValue &scriptValue = m_scriptValueMap[item]; + if (scriptValue.isObject()) { + // already initialized + return scriptValue; + } + + const auto edata = new EvaluationData; + edata->evaluator = this; + edata->item = item; + edata->item->setObserver(this); + + scriptValue = m_scriptEngine->newObject(m_scriptClass); + attachPointerTo(scriptValue, edata); + return scriptValue; +} + +void Evaluator::onItemPropertyChanged(Item *item) +{ + auto data = attachedPointer(m_scriptValueMap.value(item)); + if (data) + data->valueCache.clear(); +} + +void Evaluator::handleEvaluationError(const Item *item, const QString &name, + const QScriptValue &scriptValue) +{ + throwOnEvaluationError(m_scriptEngine, scriptValue, [&item, &name] () { + const ValueConstPtr &value = item->property(name); + return value ? value->location() : CodeLocation(); + }); +} + +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) +{ + *result = property(item, name); + handleEvaluationError(item, name, *result); + if (propertyWasSet) + *propertyWasSet = isNonDefaultValue(item, name); + return result->isValid() && !result->isUndefined(); +} + +Evaluator::FileContextScopes Evaluator::fileContextScopes(const FileContextConstPtr &file) +{ + FileContextScopes &result = m_fileContextScopesMap[file]; + if (!result.fileScope.isObject()) { + if (file->idScope()) + result.fileScope = scriptValue(file->idScope()); + else + result.fileScope = m_scriptEngine->newObject(); + result.fileScope.setProperty(StringConstants::filePathGlobalVar(), file->filePath()); + result.fileScope.setProperty(StringConstants::pathGlobalVar(), file->dirPath()); + } + if (!result.importScope.isObject()) { + try { + result.importScope = m_scriptEngine->newObject(); + setupScriptEngineForFile(m_scriptEngine, file, result.importScope, + ObserveMode::Enabled); + } catch (const ErrorInfo &e) { + result.importScope = m_scriptEngine->currentContext()->throwError(e.toString()); + } + } + return result; +} + +void Evaluator::setCachingEnabled(bool enabled) +{ + m_scriptClass->setValueCacheEnabled(enabled); +} + +PropertyDependencies Evaluator::propertyDependencies() const +{ + return m_scriptClass->propertyDependencies(); +} + +void Evaluator::clearPropertyDependencies() +{ + m_scriptClass->clearPropertyDependencies(); +} + +void throwOnEvaluationError(ScriptEngine *engine, const QScriptValue &scriptValue, + const std::function &provideFallbackCodeLocation) +{ + if (Q_LIKELY(!engine->hasErrorOrException(scriptValue))) + return; + QString message; + QString filePath; + int line = -1; + const QScriptValue value = scriptValue.isError() ? scriptValue + : engine->uncaughtException(); + if (value.isError()) { + QScriptValue v = value.property(QStringLiteral("message")); + if (v.isString()) + message = v.toString(); + v = value.property(StringConstants::fileNameProperty()); + if (v.isString()) + filePath = v.toString(); + v = value.property(QStringLiteral("lineNumber")); + if (v.isNumber()) + line = v.toInt32(); + throw ErrorInfo(message, CodeLocation(filePath, line, -1, false)); + } else { + message = value.toString(); + throw ErrorInfo(message, provideFallbackCodeLocation()); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/evaluator.h b/src/lib/corelib/language/evaluator.h new file mode 100644 index 00000000..f8535d0d --- /dev/null +++ b/src/lib/corelib/language/evaluator.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** 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 QBS_EVALUATOR_H +#define QBS_EVALUATOR_H + +#include "forward_decls.h" +#include "itemobserver.h" +#include "qualifiedid.h" + +#include + +#include + +#include + +namespace qbs { +namespace Internal { +class EvaluatorScriptClass; +class FileTags; +class Logger; +class PropertyDeclaration; +class ScriptEngine; + +class QBS_AUTOTEST_EXPORT Evaluator : private ItemObserver +{ + friend class SVConverter; + +public: + Evaluator(ScriptEngine *scriptEngine); + ~Evaluator() override; + + ScriptEngine *engine() const { return m_scriptEngine; } + QScriptValue property(const Item *item, const QString &name); + + QScriptValue value(const Item *item, const QString &name, bool *propertySet = nullptr); + bool boolValue(const Item *item, const QString &name, bool *propertyWasSet = nullptr); + int intValue(const Item *item, const QString &name, int defaultValue = 0, + bool *propertyWasSet = nullptr); + FileTags fileTagsValue(const Item *item, const QString &name, bool *propertySet = nullptr); + QString stringValue(const Item *item, const QString &name, + const QString &defaultValue = QString(), bool *propertyWasSet = nullptr); + QStringList stringListValue(const Item *item, const QString &name, + bool *propertyWasSet = nullptr); + + void convertToPropertyType(const PropertyDeclaration& decl, const CodeLocation &loc, + QScriptValue &v); + + QScriptValue scriptValue(const Item *item); + + struct FileContextScopes + { + QScriptValue fileScope; + QScriptValue importScope; + }; + + FileContextScopes fileContextScopes(const FileContextConstPtr &file); + + void setCachingEnabled(bool enabled); + + PropertyDependencies propertyDependencies() const; + void clearPropertyDependencies(); + + void handleEvaluationError(const Item *item, const QString &name, + const QScriptValue &scriptValue); + + void setPathPropertiesBaseDir(const QString &dirPath); + void clearPathPropertiesBaseDir(); + + bool isNonDefaultValue(const Item *item, const QString &name) const; +private: + void onItemPropertyChanged(Item *item) override; + bool evaluateProperty(QScriptValue *result, const Item *item, const QString &name, + bool *propertyWasSet); + + ScriptEngine *m_scriptEngine; + EvaluatorScriptClass *m_scriptClass; + mutable QHash m_scriptValueMap; + mutable QHash m_fileContextScopesMap; +}; + +void throwOnEvaluationError(ScriptEngine *engine, const QScriptValue &scriptValue, + const std::function &provideFallbackCodeLocation); + +class EvalCacheEnabler +{ +public: + EvalCacheEnabler(Evaluator *evaluator) : m_evaluator(evaluator) + { + m_evaluator->setCachingEnabled(true); + } + + ~EvalCacheEnabler() { m_evaluator->setCachingEnabled(false); } + +private: + Evaluator * const m_evaluator; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_EVALUATOR_H diff --git a/src/lib/corelib/language/evaluatorscriptclass.cpp b/src/lib/corelib/language/evaluatorscriptclass.cpp new file mode 100644 index 00000000..37595413 --- /dev/null +++ b/src/lib/corelib/language/evaluatorscriptclass.cpp @@ -0,0 +1,776 @@ +/**************************************************************************** +** +** 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 "evaluatorscriptclass.h" + +#include "evaluationdata.h" +#include "evaluator.h" +#include "filecontext.h" +#include "item.h" +#include "scriptengine.h" +#include "propertydeclaration.h" +#include "value.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class SVConverter : ValueHandler +{ + EvaluatorScriptClass * const scriptClass; + ScriptEngine * const engine; + QScriptContext * const scriptContext; + const QScriptValue * const object; + Value * const valuePtr; + const Item * const itemOfProperty; + const QScriptString * const propertyName; + const EvaluationData * const data; + QScriptValue * const result; + char pushedScopesCount; + +public: + + SVConverter(EvaluatorScriptClass *esc, const QScriptValue *obj, const ValuePtr &v, + const Item *_itemOfProperty, const QScriptString *propertyName, const EvaluationData *data, + QScriptValue *result) + : scriptClass(esc) + , engine(static_cast(esc->engine())) + , scriptContext(esc->engine()->currentContext()) + , object(obj) + , valuePtr(v.get()) + , itemOfProperty(_itemOfProperty) + , propertyName(propertyName) + , data(data) + , result(result) + , pushedScopesCount(0) + { + } + + void start() + { + valuePtr->apply(this); + } + +private: + friend class AutoScopePopper; + + class AutoScopePopper + { + public: + AutoScopePopper(SVConverter *converter) + : m_converter(converter) + { + } + + ~AutoScopePopper() + { + m_converter->popScopes(); + } + + private: + SVConverter *m_converter; + }; + + void setupConvenienceProperty(const QString &conveniencePropertyName, QScriptValue *extraScope, + const QScriptValue &scriptValue) + { + if (!extraScope->isObject()) + *extraScope = engine->newObject(); + const PropertyDeclaration::Type type + = itemOfProperty->propertyDeclaration(propertyName->toString()).type(); + const bool isArray = type == PropertyDeclaration::StringList + || type == PropertyDeclaration::PathList + || type == PropertyDeclaration::Variant // TODO: Why? + || type == PropertyDeclaration::VariantList; + QScriptValue valueToSet = scriptValue; + if (isArray) { + if (!valueToSet.isValid() || valueToSet.isUndefined()) + valueToSet = engine->newArray(); + } else if (!valueToSet.isValid()) { + valueToSet = engine->undefinedValue(); + } + extraScope->setProperty(conveniencePropertyName, valueToSet); + } + + std::pair createExtraScope(const JSSourceValue *value, Item *outerItem, + QScriptValue *outerScriptValue) + { + std::pair result; + auto &extraScope = result.first; + result.second = true; + if (value->sourceUsesBase()) { + QScriptValue baseValue; + if (value->baseValue()) { + SVConverter converter(scriptClass, object, value->baseValue(), itemOfProperty, + propertyName, data, &baseValue); + converter.start(); + } + setupConvenienceProperty(StringConstants::baseVar(), &extraScope, baseValue); + } + if (value->sourceUsesOuter()) { + QScriptValue v; + if (outerItem) { + v = data->evaluator->property(outerItem, *propertyName); + if (engine->hasErrorOrException(v)) { + extraScope = engine->lastErrorValue(v); + result.second = false; + return result; + } + } else if (outerScriptValue) { + v = *outerScriptValue; + } + if (v.isValid()) + setupConvenienceProperty(StringConstants::outerVar(), &extraScope, v); + } + if (value->sourceUsesOriginal()) { + QScriptValue originalValue; + if (data->item->propertyDeclaration(propertyName->toString()).isScalar()) { + const Item *item = itemOfProperty; + if (item->type() == ItemType::Module || item->type() == ItemType::Export) { + const QString errorMessage = Tr::tr("The special value 'original' cannot " + "be used on the right-hand side of a property declaration."); + extraScope = engine->currentContext()->throwError(errorMessage); + result.second = false; + return result; + } + + // TODO: Provide a dedicated item type for not-yet-instantiated things that + // look like module instances in the AST visitor. + if (item->type() == ItemType::ModuleInstance + && !item->hasProperty(StringConstants::presentProperty())) { + const QString errorMessage = Tr::tr("Trying to assign property '%1' " + "on something that is not a module.").arg(propertyName->toString()); + extraScope = engine->currentContext()->throwError(errorMessage); + result.second = false; + return result; + } + + while (item->type() == ItemType::ModuleInstance) + item = item->prototype(); + if (item->type() != ItemType::Module && item->type() != ItemType::Export) { + const QString errorMessage = Tr::tr("The special value 'original' can only " + "be used with module properties."); + extraScope = engine->currentContext()->throwError(errorMessage); + result.second = false; + return result; + } + const ValuePtr v = item->property(*propertyName); + + // This can happen when resolving shadow products. The error will be ignored + // in that case. + if (!v) { + const QString errorMessage = Tr::tr("Error setting up 'original'."); + extraScope = engine->currentContext()->throwError(errorMessage); + result.second = false; + return result; + } + + SVConverter converter(scriptClass, object, v, item, + propertyName, data, &originalValue); + converter.start(); + } else { + originalValue = engine->newArray(0); + } + setupConvenienceProperty(StringConstants::originalVar(), &extraScope, originalValue); + } + return result; + } + + void pushScope(const QScriptValue &scope) + { + if (scope.isObject()) { + scriptContext->pushScope(scope); + ++pushedScopesCount; + } + } + + void pushItemScopes(const Item *item) + { + const Item *scope = item->scope(); + if (scope) { + pushItemScopes(scope); + pushScope(data->evaluator->scriptValue(scope)); + } + } + + void popScopes() + { + for (; pushedScopesCount; --pushedScopesCount) + scriptContext->popScope(); + } + + void handle(JSSourceValue *value) override + { + QScriptValue outerScriptValue; + for (const JSSourceValue::Alternative &alternative : value->alternatives()) { + if (alternative.value->sourceUsesOuter() + && !data->item->outerItem() + && !outerScriptValue.isValid()) { + JSSourceValueEvaluationResult sver = evaluateJSSourceValue(value, nullptr); + if (sver.hasError) { + *result = sver.scriptValue; + return; + } + outerScriptValue = sver.scriptValue; + } + JSSourceValueEvaluationResult sver = evaluateJSSourceValue(alternative.value.get(), + data->item->outerItem(), + &alternative, + value, &outerScriptValue); + if (!sver.tryNextAlternative || sver.hasError) { + *result = sver.scriptValue; + return; + } + } + *result = evaluateJSSourceValue(value, data->item->outerItem()).scriptValue; + } + + struct JSSourceValueEvaluationResult + { + QScriptValue scriptValue; + bool tryNextAlternative = true; + bool hasError = false; + }; + + void injectErrorLocation(QScriptValue &sv, const CodeLocation &loc) + { + if (sv.isError() && !engine->lastErrorLocation(sv).isValid()) + sv = engine->currentContext()->throwError(engine->lastError(sv, loc).toString()); + } + + JSSourceValueEvaluationResult evaluateJSSourceValue(const JSSourceValue *value, Item *outerItem, + const JSSourceValue::Alternative *alternative = nullptr, + JSSourceValue *elseCaseValue = nullptr, QScriptValue *outerScriptValue = nullptr) + { + JSSourceValueEvaluationResult result; + QBS_ASSERT(!alternative || value == alternative->value.get(), return result); + AutoScopePopper autoScopePopper(this); + auto maybeExtraScope = createExtraScope(value, outerItem, outerScriptValue); + if (!maybeExtraScope.second) { + result.scriptValue = maybeExtraScope.first; + result.hasError = true; + return result; + } + const Evaluator::FileContextScopes fileCtxScopes + = data->evaluator->fileContextScopes(value->file()); + if (fileCtxScopes.importScope.isError()) { + result.scriptValue = fileCtxScopes.importScope; + result.hasError = true; + return result; + } + pushScope(fileCtxScopes.fileScope); + pushItemScopes(data->item); + if (itemOfProperty->type() != ItemType::ModuleInstance) { + // Own properties of module instances must not have the instance itself in the scope. + pushScope(*object); + } + if (value->definingItem()) + pushItemScopes(value->definingItem()); + pushScope(maybeExtraScope.first); + pushScope(fileCtxScopes.importScope); + if (alternative) { + QScriptValue sv = engine->evaluate(alternative->condition.value); + if (engine->hasErrorOrException(sv)) { + result.scriptValue = sv; + result.hasError = true; + injectErrorLocation(result.scriptValue, alternative->condition.location); + return result; + } + if (sv.toBool()) { + // The condition is true. Continue evaluating the value. + result.tryNextAlternative = false; + } else { + // The condition is false. Try the next alternative or the else value. + result.tryNextAlternative = true; + return result; + } + sv = engine->evaluate(alternative->overrideListProperties.value); + if (engine->hasErrorOrException(sv)) { + result.scriptValue = sv; + result.hasError = true; + injectErrorLocation(result.scriptValue, + alternative->overrideListProperties.location); + return result; + } + if (sv.toBool()) + elseCaseValue->setIsExclusiveListValue(); + } + result.scriptValue = engine->evaluate(value->sourceCodeForEvaluation(), + value->file()->filePath(), value->line()); + return result; + } + + void handle(ItemValue *value) override + { + *result = data->evaluator->scriptValue(value->item()); + if (!result->isValid()) + qDebug() << "SVConverter returned invalid script value."; + } + + void handle(VariantValue *variantValue) override + { + *result = engine->toScriptValue(variantValue->value()); + } +}; + +bool debugProperties = false; + +enum QueryPropertyType +{ + QPTDefault, + QPTParentProperty +}; + +EvaluatorScriptClass::EvaluatorScriptClass(ScriptEngine *scriptEngine) + : QScriptClass(scriptEngine) + , m_valueCacheEnabled(false) +{ +} + +QScriptClass::QueryFlags EvaluatorScriptClass::queryProperty(const QScriptValue &object, + const QScriptString &name, + QScriptClass::QueryFlags flags, + uint *id) +{ + Q_UNUSED(flags); + + // We assume that it's safe to save the result of the query in a member of the scriptclass. + // It must be cleared in the property method before doing any further lookup. + QBS_ASSERT(m_queryResult.isNull(), return {}); + + if (debugProperties) + qDebug() << "[SC] queryProperty " << object.objectId() << " " << name; + + auto const data = attachedPointer(object); + const QString nameString = name.toString(); + if (nameString == QStringLiteral("parent")) { + *id = QPTParentProperty; + m_queryResult.data = data; + return QScriptClass::HandlesReadAccess; + } + + *id = QPTDefault; + if (!data) { + if (debugProperties) + qDebug() << "[SC] queryProperty: no data attached"; + return {}; + } + + return queryItemProperty(data, nameString); +} + +QScriptClass::QueryFlags EvaluatorScriptClass::queryItemProperty(const EvaluationData *data, + const QString &name, + bool ignoreParent) +{ + for (const Item *item = data->item; item; item = item->prototype()) { + m_queryResult.value = item->ownProperty(name); + if (m_queryResult.value) { + m_queryResult.data = data; + m_queryResult.itemOfProperty = item; + return HandlesReadAccess; + } + } + + if (!ignoreParent && data->item && data->item->parent()) { + if (debugProperties) + qDebug() << "[SC] queryProperty: query parent"; + EvaluationData parentdata = *data; + parentdata.item = data->item->parent(); + const QueryFlags qf = queryItemProperty(&parentdata, name, true); + if (qf.testFlag(HandlesReadAccess)) { + m_queryResult.foundInParent = true; + m_queryResult.data = data; + return qf; + } + } + + if (debugProperties) + qDebug() << "[SC] queryProperty: no such property"; + return {}; +} + +QString EvaluatorScriptClass::resultToString(const QScriptValue &scriptValue) +{ + return (scriptValue.isObject() + ? QStringLiteral("[Object: ") + + QString::number(scriptValue.objectId()) + QLatin1Char(']') + : scriptValue.toVariant().toString()); +} + +void EvaluatorScriptClass::collectValuesFromNextChain(const EvaluationData *data, QScriptValue *result, + const QString &propertyName, const ValuePtr &value) +{ + QScriptValueList lst; + Set oldNextChain = m_currentNextChain; + for (ValuePtr next = value; next; next = next->next()) + m_currentNextChain.insert(next.get()); + + for (ValuePtr next = value; next; next = next->next()) { + QScriptValue v = data->evaluator->property(next->definingItem(), propertyName); + const auto se = static_cast(engine()); + if (se->hasErrorOrException(v)) { + *result = se->lastErrorValue(v); + return; + } + if (v.isUndefined()) + continue; + lst << v; + if (next->type() == Value::JSSourceValueType + && std::static_pointer_cast(next)->isExclusiveListValue()) { + lst = lst.mid(lst.length() - 2); + break; + } + } + m_currentNextChain = oldNextChain; + + *result = engine()->newArray(); + quint32 k = 0; + for (const QScriptValue &v : qAsConst(lst)) { + QBS_ASSERT(!v.isError(), continue); + if (v.isArray()) { + const quint32 vlen = v.property(StringConstants::lengthProperty()).toInt32(); + for (quint32 j = 0; j < vlen; ++j) + result->setProperty(k++, v.property(j)); + } else { + result->setProperty(k++, v); + } + } +} + +static QString overriddenSourceDirectory(const Item *item, const QString &defaultValue) +{ + const VariantValuePtr v = item->variantProperty + (StringConstants::qbsSourceDirPropertyInternal()); + return v ? v->value().toString() : defaultValue; +} + +static void makeTypeError(const ErrorInfo &error, QScriptValue &v) +{ + v = v.engine()->currentContext()->throwError(QScriptContext::TypeError, + error.toString()); +} + +static void makeTypeError(const PropertyDeclaration &decl, const CodeLocation &location, + QScriptValue &v) +{ + const ErrorInfo error(Tr::tr("Value assigned to property '%1' does not have type '%2'.") + .arg(decl.name(), decl.typeString()), location); + makeTypeError(error, v); +} + +static void convertToPropertyType_impl(const QString &pathPropertiesBaseDir, const Item *item, + const PropertyDeclaration& decl, + const CodeLocation &location, QScriptValue &v) +{ + if (v.isUndefined() || v.isError()) + return; + QString srcDir; + QString actualBaseDir; + if (item && !pathPropertiesBaseDir.isEmpty()) { + const VariantValueConstPtr itemSourceDir + = item->variantProperty(QStringLiteral("sourceDirectory")); + actualBaseDir = itemSourceDir ? itemSourceDir->value().toString() : pathPropertiesBaseDir; + } + switch (decl.type()) { + case PropertyDeclaration::UnknownType: + case PropertyDeclaration::Variant: + break; + case PropertyDeclaration::Boolean: + if (!v.isBool()) + v = v.toBool(); + break; + case PropertyDeclaration::Integer: + if (!v.isNumber()) + makeTypeError(decl, location, v); + break; + case PropertyDeclaration::Path: + { + if (!v.isString()) { + makeTypeError(decl, location, v); + break; + } + const QString srcDir = item ? overriddenSourceDirectory(item, actualBaseDir) + : pathPropertiesBaseDir; + if (!srcDir.isEmpty()) + v = v.engine()->toScriptValue(QDir::cleanPath( + FileInfo::resolvePath(srcDir, v.toString()))); + break; + } + case PropertyDeclaration::String: + if (!v.isString()) + makeTypeError(decl, location, v); + break; + case PropertyDeclaration::PathList: + srcDir = item ? overriddenSourceDirectory(item, actualBaseDir) + : pathPropertiesBaseDir; + // Fall-through. + case PropertyDeclaration::StringList: + { + if (!v.isArray()) { + QScriptValue x = v.engine()->newArray(1); + x.setProperty(0, v); + v = x; + } + const quint32 c = v.property(StringConstants::lengthProperty()).toUInt32(); + for (quint32 i = 0; i < c; ++i) { + QScriptValue elem = v.property(i); + if (elem.isUndefined()) { + ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' is undefined. " + "String expected.").arg(i).arg(decl.name()), location); + makeTypeError(error, v); + break; + } + if (elem.isNull()) { + ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' is null. " + "String expected.").arg(i).arg(decl.name()), location); + makeTypeError(error, v); + break; + } + if (!elem.isString()) { + ErrorInfo error(Tr::tr("Element at index %1 of list property '%2' does not have " + "string type.").arg(i).arg(decl.name()), location); + makeTypeError(error, v); + break; + } + if (srcDir.isEmpty()) + continue; + elem = v.engine()->toScriptValue( + QDir::cleanPath(FileInfo::resolvePath(srcDir, elem.toString()))); + v.setProperty(i, elem); + } + break; + } + case PropertyDeclaration::VariantList: + if (!v.isArray()) { + QScriptValue x = v.engine()->newArray(1); + x.setProperty(0, v); + v = x; + } + break; + } +} + +void EvaluatorScriptClass::convertToPropertyType(const PropertyDeclaration &decl, + const CodeLocation &loc, QScriptValue &v) +{ + convertToPropertyType_impl(QString(), nullptr, decl, loc, v); +} + +void EvaluatorScriptClass::convertToPropertyType(const Item *item, const PropertyDeclaration& decl, + const Value *value, QScriptValue &v) +{ + if (value->type() == Value::VariantValueType && v.isUndefined() && !decl.isScalar()) { + v = v.engine()->newArray(); // QTBUG-51237 + return; + } + convertToPropertyType_impl(m_pathPropertiesBaseDir, item, decl, value->location(), v); +} + +class PropertyStackManager +{ +public: + PropertyStackManager(const Item *itemOfProperty, const QScriptString &name, const Value *value, + std::stack &requestedProperties, + PropertyDependencies &propertyDependencies) + : m_requestedProperties(requestedProperties) + { + if (value->type() == Value::JSSourceValueType + && (itemOfProperty->type() == ItemType::ModuleInstance + || itemOfProperty->type() == ItemType::Module + || itemOfProperty->type() == ItemType::Export)) { + const VariantValueConstPtr varValue + = itemOfProperty->variantProperty(StringConstants::nameProperty()); + if (!varValue) + return; + m_stackUpdate = true; + const QualifiedId fullPropName + = QualifiedId::fromString(varValue->value().toString()) << name.toString(); + if (!requestedProperties.empty()) + propertyDependencies[fullPropName].insert(requestedProperties.top()); + m_requestedProperties.push(fullPropName); + } + } + + ~PropertyStackManager() + { + if (m_stackUpdate) + m_requestedProperties.pop(); + } + +private: + std::stack &m_requestedProperties; + bool m_stackUpdate = false; +}; + +QScriptValue EvaluatorScriptClass::property(const QScriptValue &object, const QScriptString &name, + uint id) +{ + const bool foundInParent = m_queryResult.foundInParent; + const EvaluationData *data = m_queryResult.data; + const Item * const itemOfProperty = m_queryResult.itemOfProperty; + m_queryResult.foundInParent = false; + m_queryResult.data = nullptr; + m_queryResult.itemOfProperty = nullptr; + QBS_ASSERT(data, {}); + + const auto qpt = static_cast(id); + if (qpt == QPTParentProperty) { + return data->item->parent() + ? data->evaluator->scriptValue(data->item->parent()) + : engine()->undefinedValue(); + } + + ValuePtr value; + m_queryResult.value.swap(value); + QBS_ASSERT(value, return {}); + QBS_ASSERT(m_queryResult.isNull(), return {}); + + if (debugProperties) + qDebug() << "[SC] property " << name; + + PropertyStackManager propStackmanager(itemOfProperty, name, value.get(), + m_requestedProperties, m_propertyDependencies); + + QScriptValue result; + if (m_valueCacheEnabled) { + result = data->valueCache.value(name); + if (result.isValid()) { + if (debugProperties) + qDebug() << "[SC] cache hit " << name << ": " << resultToString(result); + return result; + } + } + + if (value->next() && !m_currentNextChain.contains(value.get())) { + collectValuesFromNextChain(data, &result, name.toString(), value); + } else { + QScriptValue parentObject; + if (foundInParent) + parentObject = data->evaluator->scriptValue(data->item->parent()); + SVConverter converter(this, foundInParent ? &parentObject : &object, value, itemOfProperty, + &name, data, &result); + converter.start(); + + const PropertyDeclaration decl = data->item->propertyDeclaration(name.toString()); + convertToPropertyType(data->item, decl, value.get(), result); + } + + if (debugProperties) + qDebug() << "[SC] cache miss " << name << ": " << resultToString(result); + if (m_valueCacheEnabled) + data->valueCache.insert(name, result); + return result; +} + +class EvaluatorScriptClassPropertyIterator : public QScriptClassPropertyIterator +{ +public: + EvaluatorScriptClassPropertyIterator(const QScriptValue &object, EvaluationData *data) + : QScriptClassPropertyIterator(object), m_it(data->item->properties()) + { + } + + bool hasNext() const override + { + return m_it.hasNext(); + } + + void next() override + { + m_it.next(); + } + + bool hasPrevious() const override + { + return m_it.hasPrevious(); + } + + void previous() override + { + m_it.previous(); + } + + void toFront() override + { + m_it.toFront(); + } + + void toBack() override + { + m_it.toBack(); + } + + QScriptString name() const override + { + return object().engine()->toStringHandle(m_it.key()); + } + +private: + QMapIterator m_it; +}; + +QScriptClassPropertyIterator *EvaluatorScriptClass::newIterator(const QScriptValue &object) +{ + auto const data = attachedPointer(object); + return data ? new EvaluatorScriptClassPropertyIterator(object, data) : nullptr; +} + +void EvaluatorScriptClass::setValueCacheEnabled(bool enabled) +{ + m_valueCacheEnabled = enabled; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/evaluatorscriptclass.h b/src/lib/corelib/language/evaluatorscriptclass.h new file mode 100755 index 00000000..c234c17f --- /dev/null +++ b/src/lib/corelib/language/evaluatorscriptclass.h @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** 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 QBS_EVALUATORSCRIPTCLASS_H +#define QBS_EVALUATORSCRIPTCLASS_H + +#include "forward_decls.h" +#include "qualifiedid.h" + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE +class QScriptContext; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class EvaluationData; +class Item; +class PropertyDeclaration; +class ScriptEngine; + +class EvaluatorScriptClass : public QScriptClass +{ +public: + EvaluatorScriptClass(ScriptEngine *scriptEngine); + + QueryFlags queryProperty(const QScriptValue &object, + const QScriptString &name, + QueryFlags flags, uint *id) override; + QScriptValue property(const QScriptValue &object, + const QScriptString &name, uint id) override; + QScriptClassPropertyIterator *newIterator(const QScriptValue &object) override; + + void setValueCacheEnabled(bool enabled); + + void convertToPropertyType(const PropertyDeclaration& decl, const CodeLocation &loc, + QScriptValue &v); + + 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, + bool ignoreParent = false); + static QString resultToString(const QScriptValue &scriptValue); + void collectValuesFromNextChain(const EvaluationData *data, QScriptValue *result, const QString &propertyName, const ValuePtr &value); + + void convertToPropertyType(const Item *item, + const PropertyDeclaration& decl, const Value *value, + QScriptValue &v); + + struct QueryResult + { + QueryResult() + : data(nullptr), itemOfProperty(nullptr) + {} + + bool isNull() const + { + static const QueryResult pristine; + return *this == pristine; + } + + bool operator==(const QueryResult &rhs) const + { + return foundInParent == rhs.foundInParent + && data == rhs.data + && itemOfProperty == rhs.itemOfProperty + && value == rhs.value; + } + + bool foundInParent = false; + const EvaluationData *data; + const Item *itemOfProperty; // The item that owns the property. + ValuePtr value; + }; + QueryResult m_queryResult; + bool m_valueCacheEnabled; + Set m_currentNextChain; + PropertyDependencies m_propertyDependencies; + std::stack m_requestedProperties; + QString m_pathPropertiesBaseDir; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_EVALUATORSCRIPTCLASS_H diff --git a/src/lib/corelib/language/filecontext.cpp b/src/lib/corelib/language/filecontext.cpp new file mode 100644 index 00000000..6daf8c87 --- /dev/null +++ b/src/lib/corelib/language/filecontext.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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 "filecontext.h" + +#include "item.h" + +namespace qbs { +namespace Internal { + +FileContext::FileContext() + : m_idScope(nullptr) +{ +} + +FileContextPtr FileContext::create() +{ + return FileContextPtr(new FileContext); +} + +void FileContext::ensureIdScope(ItemPool *itemPool) +{ + if (!m_idScope) + m_idScope = Item::create(itemPool, ItemType::IdScope); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/filecontext.h b/src/lib/corelib/language/filecontext.h new file mode 100644 index 00000000..001e6406 --- /dev/null +++ b/src/lib/corelib/language/filecontext.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** 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 QBS_FILECONTEXT_H +#define QBS_FILECONTEXT_H + +#include "filecontextbase.h" +#include "forward_decls.h" + +#include + +namespace qbs { +namespace Internal { +class Item; +class ItemPool; + +class FileContext : public FileContextBase +{ +public: + static FileContextPtr QBS_AUTOTEST_EXPORT create(); + + void setContent(const QString &content) { m_content = content; } + const QString &content() const { return m_content; } + + Item *idScope() const { return m_idScope; } + void ensureIdScope(ItemPool *itemPool); + +private: + FileContext(); + + QString m_content; + Item *m_idScope; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_FILECONTEXT_H diff --git a/src/lib/corelib/language/filecontextbase.cpp b/src/lib/corelib/language/filecontextbase.cpp new file mode 100644 index 00000000..70dd04a0 --- /dev/null +++ b/src/lib/corelib/language/filecontextbase.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** 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 "filecontextbase.h" + +#include + +namespace qbs { +namespace Internal { + +QString FileContextBase::dirPath() const +{ + return FileInfo::path(m_filePath); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/filecontextbase.h b/src/lib/corelib/language/filecontextbase.h new file mode 100644 index 00000000..335e5425 --- /dev/null +++ b/src/lib/corelib/language/filecontextbase.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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 QBS_FILECONTEXTBASE_H +#define QBS_FILECONTEXTBASE_H + +#include "jsimports.h" + +namespace qbs { +namespace Internal { + +class FileContextBase +{ +public: + void setFilePath(const QString &filePath) { m_filePath = filePath; } + QString filePath() const { return m_filePath; } + + void addJsImport(const JsImport &jsImport) { m_jsImports.push_back(jsImport); } + const JsImports &jsImports() const { return m_jsImports; } + + void addJsExtension(const QString &extension) { m_jsExtensions << extension; } + QStringList jsExtensions() const { return m_jsExtensions; } + + void setSearchPaths(const QStringList &paths) { m_searchPaths = paths; } + QStringList searchPaths() const { return m_searchPaths; } + + QString dirPath() const; + +protected: + FileContextBase() = default; + FileContextBase(const FileContextBase &other) = default; + FileContextBase(FileContextBase &&other) = default; + + QString m_filePath; + JsImports m_jsImports; + QStringList m_jsExtensions; + QStringList m_searchPaths; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_FILECONTEXTBASE_H diff --git a/src/lib/corelib/language/filetags.cpp b/src/lib/corelib/language/filetags.cpp new file mode 100644 index 00000000..83086b89 --- /dev/null +++ b/src/lib/corelib/language/filetags.cpp @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** 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 "filetags.h" +#include + +#include + +namespace qbs { +namespace Internal { + +void FileTag::clear() +{ + Id::operator=(Id()); +} + +void FileTag::store(PersistentPool &pool) const +{ + pool.store(toString()); +} + +void FileTag::load(PersistentPool &pool) +{ + *this = FileTag(pool.load().toUtf8()); +} + +QDebug operator<<(QDebug debug, const FileTag &tag) +{ + QDebugStateSaver saver(debug); + return debug.resetFormat().noquote() << tag.toString(); +} + +FileTags FileTags::fromStringList(const QStringList &strings) +{ + FileTags result; + for (const QString &str : strings) + result += FileTag(str.toUtf8()); + return result; +} + +LogWriter operator <<(LogWriter w, const FileTags &tags) +{ + bool firstLoop = true; + w.write('('); + for (const FileTag &tag : tags) { + if (firstLoop) + firstLoop = false; + else + w.write(QStringLiteral(", ")); + w.write(tag.toString()); + } + w.write(')'); + 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 new file mode 100644 index 00000000..9f712e19 --- /dev/null +++ b/src/lib/corelib/language/filetags.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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 QBS_FILETAGS_H +#define QBS_FILETAGS_H + +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { +class PersistentPool; + +class FileTag : public Id +{ +public: + FileTag() + : Id() + {} + + FileTag(const Id &other) + : Id(other) + {} + + FileTag(const char *str) + : Id(str) + {} + + explicit FileTag(const QByteArray &ba) + : Id(ba) + {} + + void clear(); + + void store(PersistentPool &pool) const; + void load(PersistentPool &pool); +}; + +template<> inline bool Set::sortAfterLoadRequired() const { return true; } +QDebug operator<<(QDebug debug, const FileTag &tag); + +class FileTags : public Set +{ +public: + FileTags() : Set() {} + FileTags(const std::initializer_list &list) : Set(list) {} + static FileTags fromStringList(const QStringList &strings); +}; + +LogWriter operator <<(LogWriter w, const FileTags &tags); +QDebug operator<<(QDebug debug, const FileTags &tags); + +} // namespace Internal +} // namespace qbs + +#endif // QBS_FILETAGS_H + diff --git a/src/lib/corelib/language/forward_decls.h b/src/lib/corelib/language/forward_decls.h new file mode 100644 index 00000000..6697ac8c --- /dev/null +++ b/src/lib/corelib/language/forward_decls.h @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** 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 QBS_LANG_FORWARD_DECLS_H +#define QBS_LANG_FORWARD_DECLS_H + +#include + +namespace qbs { +namespace Internal { + +enum class ObserveMode; + +class Value; +using ValuePtr = std::shared_ptr; +using ValueConstPtr = std::shared_ptr; + +class ItemValue; +using ItemValuePtr = std::shared_ptr; +using ItemValueConstPtr = std::shared_ptr; + +class JSSourceValue; +using JSSourceValuePtr = std::shared_ptr; +using JSSourceValueConstPtr = std::shared_ptr; + +class VariantValue; +using VariantValuePtr = std::shared_ptr; +using VariantValueConstPtr = std::shared_ptr; + +class FileContext; +using FileContextPtr = std::shared_ptr; +using FileContextConstPtr = std::shared_ptr; + +class FileContextBase; +using FileContextBasePtr = std::shared_ptr; +using FileContextBaseConstPtr = std::shared_ptr; + +class Probe; +using ProbePtr = std::shared_ptr; +using ProbeConstPtr = std::shared_ptr; + +class PropertyMapInternal; +using PropertyMapPtr = std::shared_ptr; +using PropertyMapConstPtr = std::shared_ptr; + +class FileTagger; +using FileTaggerPtr = std::shared_ptr; +using FileTaggerConstPtr = std::shared_ptr; + +class ResolvedProduct; +using ResolvedProductPtr = std::shared_ptr; +using ResolvedProductConstPtr = std::shared_ptr; + +class ResolvedProject; +using ResolvedProjectPtr = std::shared_ptr; +using ResolvedProjectConstPtr = std::shared_ptr; + +class TopLevelProject; +using TopLevelProjectPtr = std::shared_ptr; +using TopLevelProjectConstPtr = std::shared_ptr; + +class ResolvedFileContext; +using ResolvedFileContextPtr = std::shared_ptr; +using ResolvedFileContextConstPtr = std::shared_ptr; + +class Rule; +using RulePtr = std::shared_ptr; +using RuleConstPtr = std::shared_ptr; + +class ResolvedScanner; +using ResolvedScannerPtr = std::shared_ptr; +using ResolvedScannerConstPtr = std::shared_ptr; + +class SourceArtifactInternal; +using SourceArtifactPtr = std::shared_ptr; +using SourceArtifactConstPtr = std::shared_ptr; + +class ScriptFunction; +using ScriptFunctionPtr = std::shared_ptr; +using ScriptFunctionConstPtr = std::shared_ptr; +class PrivateScriptFunction; + +class RuleArtifact; +using RuleArtifactPtr = std::shared_ptr; +using RuleArtifactConstPtr = std::shared_ptr; + +class ResolvedModule; +using ResolvedModulePtr = std::shared_ptr; +using ResolvedModuleConstPtr = std::shared_ptr; + +class ResolvedGroup; +using GroupPtr = std::shared_ptr; +using GroupConstPtr = std::shared_ptr; + +class ArtifactProperties; +using ArtifactPropertiesPtr = std::shared_ptr; +using ArtifactPropertiesConstPtr = std::shared_ptr; + +class ExportedItem; +using ExportedItemPtr = std::shared_ptr; +class ExportedModule; +class ExportedModuleDependency; +class ExportedProperty; +class PersistentPool; + +} // namespace Internal +} // namespace qbs + +#ifdef QT_CORE_LIB +#include + +namespace qbs { +namespace Internal { + +template inline static uint qHash(const std::shared_ptr &p, uint seed = 0) +{ + return ::qHash(p.get(), seed); +} + +} // namespace Internal +} // namespace qbs +#endif + +#endif // QBS_LANG_FORWARD_DECLS_H diff --git a/src/lib/corelib/language/identifiersearch.cpp b/src/lib/corelib/language/identifiersearch.cpp new file mode 100644 index 00000000..49ceab36 --- /dev/null +++ b/src/lib/corelib/language/identifiersearch.cpp @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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 "identifiersearch.h" +#include + +namespace qbs { +namespace Internal { + +IdentifierSearch::IdentifierSearch() = default; + +void IdentifierSearch::start(QbsQmlJS::AST::Node *node) +{ + for (auto it = m_requests.cbegin(); it != m_requests.cend(); ++it) + *it.value() = false; + m_numberOfFoundIds = 0; + node->accept(this); +} + +void IdentifierSearch::add(const QString &name, bool *found) +{ + m_requests.insert(name, found); +} + +bool IdentifierSearch::preVisit(QbsQmlJS::AST::Node *) +{ + return m_numberOfFoundIds < m_requests.size(); +} + +bool IdentifierSearch::visit(QbsQmlJS::AST::IdentifierExpression *e) +{ + bool *found = m_requests.value(e->name.toString()); + if (found && !*found) { + *found = true; + m_numberOfFoundIds++; + } + return m_numberOfFoundIds < m_requests.size(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/identifiersearch.h b/src/lib/corelib/language/identifiersearch.h new file mode 100644 index 00000000..7d99c0f1 --- /dev/null +++ b/src/lib/corelib/language/identifiersearch.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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 QBS_IDENTIFIERSEARCHVISITOR_H +#define QBS_IDENTIFIERSEARCHVISITOR_H + +#include +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +class QBS_AUTOTEST_EXPORT IdentifierSearch : private QbsQmlJS::AST::Visitor +{ +public: + IdentifierSearch(); + void start(QbsQmlJS::AST::Node *node); + void add(const QString &name, bool *found); + +private: + bool preVisit(QbsQmlJS::AST::Node *) override; + bool visit(QbsQmlJS::AST::IdentifierExpression *e) override; + + QMap m_requests; + int m_numberOfFoundIds = 0; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_IDENTIFIERSEARCHVISITOR_H diff --git a/src/lib/corelib/language/item.cpp b/src/lib/corelib/language/item.cpp new file mode 100644 index 00000000..a86cfeac --- /dev/null +++ b/src/lib/corelib/language/item.cpp @@ -0,0 +1,391 @@ +/**************************************************************************** +** +** 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 "item.h" + +#include "builtindeclarations.h" +#include "deprecationinfo.h" +#include "filecontext.h" +#include "itemobserver.h" +#include "itempool.h" +#include "value.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +Item::Item(ItemPool *pool, ItemType type) + : m_pool(pool) + , m_observer(nullptr) + , m_prototype(nullptr) + , m_scope(nullptr) + , m_outerItem(nullptr) + , m_parent(nullptr) + , m_type(type) +{ +} + +Item *Item::create(ItemPool *pool, ItemType type) +{ + return pool->allocateItem(type); +} + +Item *Item::clone() const +{ + Item *dup = create(pool(), type()); + dup->m_id = m_id; + dup->m_location = m_location; + dup->m_prototype = m_prototype; + dup->m_scope = m_scope; + dup->m_outerItem = m_outerItem; + dup->m_parent = m_parent; + dup->m_file = m_file; + dup->m_propertyDeclarations = m_propertyDeclarations; + dup->m_modules = m_modules; + + dup->m_children.reserve(m_children.size()); + for (const Item * const child : qAsConst(m_children)) { + Item *clonedChild = child->clone(); + clonedChild->m_parent = dup; + dup->m_children.push_back(clonedChild); + } + + for (PropertyMap::const_iterator it = m_properties.constBegin(); it != m_properties.constEnd(); + ++it) { + dup->m_properties.insert(it.key(), it.value()->clone()); + } + + return dup; +} + +QString Item::typeName() const +{ + switch (type()) { + case ItemType::IdScope: return QStringLiteral("[IdScope]"); + case ItemType::ModuleInstance: return QStringLiteral("[ModuleInstance]"); + case ItemType::ModuleParameters: return QStringLiteral("[ModuleParametersInstance]"); + case ItemType::ModulePrefix: return QStringLiteral("[ModulePrefix]"); + case ItemType::Outer: return QStringLiteral("[Outer]"); + case ItemType::Scope: return QStringLiteral("[Scope]"); + default: return BuiltinDeclarations::instance().nameForType(type()); + } +} + +bool Item::hasProperty(const QString &name) const +{ + const Item *item = this; + do { + if (item->m_properties.contains(name)) + return true; + item = item->m_prototype; + } while (item); + return false; +} + +bool Item::hasOwnProperty(const QString &name) const +{ + return m_properties.contains(name); +} + +ValuePtr Item::property(const QString &name) const +{ + ValuePtr value; + const Item *item = this; + do { + if ((value = item->m_properties.value(name))) + break; + item = item->m_prototype; + } while (item); + return value; +} + +ValuePtr Item::ownProperty(const QString &name) const +{ + return m_properties.value(name); +} + +ItemValuePtr Item::itemProperty(const QString &name, const Item *itemTemplate) +{ + return itemProperty(name, itemTemplate, ItemValueConstPtr()); +} + +ItemValuePtr Item::itemProperty(const QString &name, const ItemValueConstPtr &value) +{ + return itemProperty(name, value->item(), value); +} + +ItemValuePtr Item::itemProperty(const QString &name, const Item *itemTemplate, + const ItemValueConstPtr &itemValue) +{ + const ValuePtr v = property(name); + if (v && v->type() == Value::ItemValueType) + return std::static_pointer_cast(v); + if (!itemTemplate) + return ItemValuePtr(); + const bool createdByPropertiesBlock = itemValue && itemValue->createdByPropertiesBlock(); + const ItemValuePtr result = ItemValue::create(Item::create(m_pool, itemTemplate->type()), + createdByPropertiesBlock); + setProperty(name, result); + return result; +} + +JSSourceValuePtr Item::sourceProperty(const QString &name) const +{ + ValuePtr v = property(name); + if (!v || v->type() != Value::JSSourceValueType) + return JSSourceValuePtr(); + return std::static_pointer_cast(v); +} + +VariantValuePtr Item::variantProperty(const QString &name) const +{ + ValuePtr v = property(name); + if (!v || v->type() != Value::VariantValueType) + return VariantValuePtr(); + return std::static_pointer_cast(v); +} + +bool Item::isOfTypeOrhasParentOfType(ItemType type) const +{ + const Item *item = this; + do { + if (item->type() == type) + return true; + item = item->parent(); + } while (item); + return false; +} + +PropertyDeclaration Item::propertyDeclaration(const QString &name, bool allowExpired) const +{ + auto it = m_propertyDeclarations.find(name); + if (it != m_propertyDeclarations.end()) + return it.value(); + if (allowExpired) { + it = m_expiredPropertyDeclarations.find(name); + if (it != m_expiredPropertyDeclarations.end()) + return it.value(); + } + return m_prototype ? m_prototype->propertyDeclaration(name) : PropertyDeclaration(); +} + +void Item::addModule(const Item::Module &module) +{ + const auto it = std::lower_bound(m_modules.begin(), m_modules.end(), module); + QBS_CHECK(it == m_modules.end() || (module.name != it->name && module.item != it->item)); + m_modules.insert(it, module); +} + +void Item::setObserver(ItemObserver *observer) const +{ + QBS_ASSERT(!observer || !m_observer, return); // warn if accidentally overwritten + m_observer = observer; +} + +void Item::setProperty(const QString &name, const ValuePtr &value) +{ + m_properties.insert(name, value); + if (m_observer) + m_observer->onItemPropertyChanged(this); +} + +void Item::dump() const +{ + dump(0); +} + +bool Item::isPresentModule() const +{ + // Initial value is "true" as JS source, overwritten one is always QVariant(false). + const ValueConstPtr v = property(StringConstants::presentProperty()); + return v && v->type() == Value::JSSourceValueType; +} + +void Item::setupForBuiltinType(Logger &logger) +{ + const BuiltinDeclarations &builtins = BuiltinDeclarations::instance(); + const auto properties = builtins.declarationsForType(type()).properties(); + for (const PropertyDeclaration &pd : properties) { + m_propertyDeclarations.insert(pd.name(), pd); + const ValuePtr value = m_properties.value(pd.name()); + if (!value) { + if (pd.isDeprecated()) + continue; + JSSourceValuePtr sourceValue = JSSourceValue::create(); + sourceValue->setIsBuiltinDefaultValue(); + sourceValue->setFile(file()); + sourceValue->setSourceCode(pd.initialValueSource().isEmpty() + ? QStringRef(&StringConstants::undefinedValue()) + : QStringRef(&pd.initialValueSource())); + m_properties.insert(pd.name(), sourceValue); + } else if (pd.isDeprecated()) { + const DeprecationInfo &di = pd.deprecationInfo(); + if (di.removalVersion() <= LanguageInfo::qbsVersion()) { + QString message = Tr::tr("The property '%1' is no longer valid for %2 items. " + "It was removed in qbs %3.") + .arg(pd.name(), typeName(), di.removalVersion().toString()); + ErrorInfo error(message, value->location()); + if (!di.additionalUserInfo().isEmpty()) + error.append(di.additionalUserInfo()); + throw error; + } + QString warning = Tr::tr("The property '%1' is deprecated and will be removed in " + "qbs %2.").arg(pd.name(), di.removalVersion().toString()); + ErrorInfo error(warning, value->location()); + if (!di.additionalUserInfo().isEmpty()) + error.append(di.additionalUserInfo()); + logger.printWarning(error); + } + } +} + +void Item::copyProperty(const QString &propertyName, Item *target) const +{ + target->setProperty(propertyName, property(propertyName)); +} + +static const char *valueType(const Value *v) +{ + switch (v->type()) { + case Value::JSSourceValueType: return "JS source"; + case Value::ItemValueType: return "Item"; + case Value::VariantValueType: return "Variant"; + } + return ""; // For dumb compilers. +} + +void Item::dump(int indentation) const +{ + const QByteArray indent(indentation, ' '); + qDebug("%stype: %s, pointer value: %p", indent.constData(), qPrintable(typeName()), this); + if (!m_properties.empty()) + qDebug("%sproperties:", indent.constData()); + for (auto it = m_properties.constBegin(); it != m_properties.constEnd(); ++it) { + const QByteArray nextIndent(indentation + 4, ' '); + qDebug("%skey: %s, value type: %s", nextIndent.constData(), qPrintable(it.key()), + valueType(it.value().get())); + switch (it.value()->type()) { + case Value::JSSourceValueType: + qDebug("%svalue: %s", nextIndent.constData(), + qPrintable(std::static_pointer_cast(it.value())->sourceCodeForEvaluation())); + break; + case Value::ItemValueType: + qDebug("%svalue:", nextIndent.constData()); + std::static_pointer_cast(it.value())->item()->dump(indentation + 8); + break; + case Value::VariantValueType: + qDebug("%svalue: %s", nextIndent.constData(), + qPrintable(std::static_pointer_cast(it.value())->value().toString())); + break; + } + } + if (!m_children.empty()) + qDebug("%schildren:", indent.constData()); + for (const Item * const child : qAsConst(m_children)) + child->dump(indentation + 4); + if (prototype()) { + qDebug("%sprototype:", indent.constData()); + prototype()->dump(indentation + 4); + } +} + +void Item::removeProperty(const QString &name) +{ + m_properties.remove(name); +} + +Item *Item::child(ItemType type, bool checkForMultiple) const +{ + Item *child = nullptr; + for (Item * const currentChild : children()) { + if (currentChild->type() == type) { + if (!checkForMultiple) + return currentChild; + if (child) { + ErrorInfo error(Tr::tr("Multiple instances of item '%1' found where at most one " + "is allowed.") + .arg(BuiltinDeclarations::instance().nameForType(type))); + error.append(Tr::tr("First item"), child->location()); + error.append(Tr::tr("Second item"), currentChild->location()); + throw error; + } + child = currentChild; + } + } + return child; +} + +void Item::addChild(Item *parent, Item *child) +{ + parent->m_children.push_back(child); + child->setParent(parent); +} + +void Item::removeChild(Item *parent, Item *child) +{ + parent->m_children.removeOne(child); + child->setParent(nullptr); +} + +void Item::setPropertyDeclaration(const QString &name, const PropertyDeclaration &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) +{ + m_propertyDeclarations = decls; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/item.h b/src/lib/corelib/language/item.h new file mode 100644 index 00000000..2d676a7f --- /dev/null +++ b/src/lib/corelib/language/item.h @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** 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 QBS_ITEM_H +#define QBS_ITEM_H + +#include "forward_decls.h" +#include "itemtype.h" +#include "propertydeclaration.h" +#include "qualifiedid.h" +#include +#include +#include +#include + +#include +#include + +#include + +namespace qbs { +namespace Internal { +class ItemObserver; +class ItemPool; +class Logger; + +class QBS_AUTOTEST_EXPORT Item : public QbsQmlJS::Managed +{ + friend class ASTPropertiesItemHandler; + friend class ItemPool; + friend class ItemReaderASTVisitor; + Q_DISABLE_COPY(Item) + Item(ItemPool *pool, ItemType type); + +public: + struct Module + { + Module() + : item(nullptr), isProduct(false), required(true) + {} + + QualifiedId name; + Item *item; + bool isProduct; + bool requiredValue = true; // base value of the required prop + bool required; + QVariantMap parameters; + VersionRange versionRange; + }; + using Modules = std::vector; + using PropertyDeclarationMap = QMap; + using PropertyMap = QMap; + + static Item *create(ItemPool *pool, ItemType type); + Item *clone() const; + ItemPool *pool() const { return m_pool; } + + const QString &id() const { return m_id; } + const CodeLocation &location() const { return m_location; } + Item *prototype() const { return m_prototype; } + Item *scope() const { return m_scope; } + Item *outerItem() const { return m_outerItem; } + Item *parent() const { return m_parent; } + const FileContextPtr &file() const { return m_file; } + const QList &children() const { return m_children; } + 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, bool allowExpired = true) const; + const Modules &modules() const { return m_modules; } + void addModule(const Module &module); + void removeModules() { m_modules.clear(); } + void setModules(const Modules &modules) { m_modules = modules; } + + ItemType type() const { return m_type; } + void setType(ItemType type) { m_type = type; } + QString typeName() const; + + bool hasProperty(const QString &name) const; + bool hasOwnProperty(const QString &name) const; + ValuePtr property(const QString &name) const; + ValuePtr ownProperty(const QString &name) const; + ItemValuePtr itemProperty(const QString &name, const Item *itemTemplate = nullptr); + ItemValuePtr itemProperty(const QString &name, const ItemValueConstPtr &value); + JSSourceValuePtr sourceProperty(const QString &name) const; + VariantValuePtr variantProperty(const QString &name) const; + bool isOfTypeOrhasParentOfType(ItemType type) const; + void setObserver(ItemObserver *observer) const; + void setProperty(const QString &name, const ValuePtr &value); + void setProperties(const PropertyMap &props) { m_properties = props; } + void removeProperty(const QString &name); + void setPropertyDeclaration(const QString &name, const PropertyDeclaration &declaration); + void setPropertyDeclarations(const PropertyDeclarationMap &decls); + void setLocation(const CodeLocation &location) { m_location = location; } + void setPrototype(Item *prototype) { m_prototype = prototype; } + void setFile(const FileContextPtr &file) { m_file = file; } + void setId(const QString &id) { m_id = id; } + void setScope(Item *item) { m_scope = item; } + void setOuterItem(Item *item) { m_outerItem = item; } + void setChildren(const QList &children) { m_children = children; } + void setParent(Item *item) { m_parent = item; } + void childrenReserve(int size) { m_children.reserve(size); } + static void addChild(Item *parent, Item *child); + static void removeChild(Item *parent, Item *child); + void dump() const; + bool isPresentModule() const; + void setupForBuiltinType(Logger &logger); + void copyProperty(const QString &propertyName, Item *target) const; + +private: + ItemValuePtr itemProperty(const QString &name, const Item *itemTemplate, + const ItemValueConstPtr &itemValue); + + void dump(int indentation) const; + + ItemPool *m_pool; + mutable ItemObserver *m_observer; + QString m_id; + CodeLocation m_location; + Item *m_prototype; + Item *m_scope; + Item *m_outerItem; + Item *m_parent; + QList m_children; + FileContextPtr m_file; + PropertyMap m_properties; + PropertyDeclarationMap m_propertyDeclarations; + PropertyDeclarationMap m_expiredPropertyDeclarations; + Modules m_modules; + ItemType m_type; +}; + +inline bool operator<(const Item::Module &m1, const Item::Module &m2) { return m1.name < m2.name; } + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEM_H diff --git a/src/lib/corelib/language/itemdeclaration.cpp b/src/lib/corelib/language/itemdeclaration.cpp new file mode 100644 index 00000000..d7230e9d --- /dev/null +++ b/src/lib/corelib/language/itemdeclaration.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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 "itemdeclaration.h" + +namespace qbs { +namespace Internal { + +ItemDeclaration::ItemDeclaration(ItemType type) + : m_type(type) +{ +} + +ItemDeclaration &ItemDeclaration::operator<<(const PropertyDeclaration &decl) +{ + m_properties.push_back(decl); + return *this; +} + +bool ItemDeclaration::isChildTypeAllowed(ItemType type) const +{ + if (m_type > ItemType::LastActualItem || type > ItemType::LastActualItem) + return true; + return m_allowedChildTypes.contains(type); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itemdeclaration.h b/src/lib/corelib/language/itemdeclaration.h new file mode 100644 index 00000000..6da699d2 --- /dev/null +++ b/src/lib/corelib/language/itemdeclaration.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** 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 QBS_ITEMDECLARATION_H +#define QBS_ITEMDECLARATION_H + +#include "deprecationinfo.h" +#include "itemtype.h" +#include "propertydeclaration.h" +#include + +#include + +namespace qbs { +namespace Internal { + +class ItemDeclaration +{ +public: + ItemDeclaration(ItemType type = ItemType::Unknown); + + ItemType type() const { return m_type; } + + using Properties = QList; + void setProperties(const Properties &props) { m_properties = props; } + Properties properties() const { return m_properties; } + + void setDeprecationInfo(const DeprecationInfo &di) { m_deprecationInfo = di; } + DeprecationInfo deprecationInfo() const { return m_deprecationInfo; } + + ItemDeclaration &operator<<(const PropertyDeclaration &decl); + + using TypeNames = Set; + void setAllowedChildTypes(const TypeNames &typeNames) { m_allowedChildTypes = typeNames; } + const TypeNames &allowedChildTypes() const { return m_allowedChildTypes; } + bool isChildTypeAllowed(ItemType type) const; + +private: + ItemType m_type; + Properties m_properties; + TypeNames m_allowedChildTypes; + DeprecationInfo m_deprecationInfo; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMDECLARATION_H diff --git a/src/lib/corelib/language/itemobserver.h b/src/lib/corelib/language/itemobserver.h new file mode 100644 index 00000000..fca3a2d8 --- /dev/null +++ b/src/lib/corelib/language/itemobserver.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** 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 QBS_ITEMOBSERVER_H +#define QBS_ITEMOBSERVER_H + +namespace qbs { +namespace Internal { + +class Item; + +class ItemObserver +{ +public: + virtual ~ItemObserver() = default; + virtual void onItemPropertyChanged(Item *item) = 0; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMOBSERVER_H diff --git a/src/lib/corelib/language/itempool.cpp b/src/lib/corelib/language/itempool.cpp new file mode 100644 index 00000000..ccd22fe2 --- /dev/null +++ b/src/lib/corelib/language/itempool.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** 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 "itempool.h" +#include "item.h" + +namespace qbs { +namespace Internal { + +ItemPool::ItemPool() = default; + +ItemPool::~ItemPool() +{ + for (Item *item : m_items) + item->~Item(); +} + +Item *ItemPool::allocateItem(const ItemType &type) +{ + const auto item = new (&m_pool) Item(this, type); + m_items.push_back(item); + return item; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itempool.h b/src/lib/corelib/language/itempool.h new file mode 100644 index 00000000..ef4be763 --- /dev/null +++ b/src/lib/corelib/language/itempool.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 QBS_ITEMPOOL_H +#define QBS_ITEMPOOL_H + +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class Item; +enum class ItemType; + +class QBS_AUTOTEST_EXPORT ItemPool +{ + Q_DISABLE_COPY(ItemPool) +public: + ItemPool(); + ~ItemPool(); + + Item *allocateItem(const ItemType &type); + +private: + QbsQmlJS::MemoryPool m_pool; + std::vector m_items; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMPOOL_H diff --git a/src/lib/corelib/language/itemreader.cpp b/src/lib/corelib/language/itemreader.cpp new file mode 100644 index 00000000..578f194b --- /dev/null +++ b/src/lib/corelib/language/itemreader.cpp @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** 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 "itemreader.h" + +#include "itemreadervisitorstate.h" + +#include + +#include + +#include + +namespace qbs { +namespace Internal { + +static void makePathsCanonical(QStringList &paths) +{ + auto it = std::remove_if(paths.begin(), paths.end(), [](QString &p) { + p = QFileInfo(p).canonicalFilePath(); + return p.isEmpty(); + }); + auto e = paths.end(); + if (it != e) + paths.erase(it, e); +} + +ItemReader::ItemReader(Logger &logger) : m_visitorState(new ItemReaderVisitorState(logger)) +{ +} + +ItemReader::~ItemReader() +{ + delete m_visitorState; +} + +void ItemReader::setSearchPaths(const QStringList &searchPaths) +{ + m_searchPaths = searchPaths; + makePathsCanonical(m_searchPaths); + m_allSearchPaths.clear(); +} + +void ItemReader::pushExtraSearchPaths(const QStringList &extraSearchPaths) +{ + m_extraSearchPaths.push_back(extraSearchPaths); + makePathsCanonical(m_extraSearchPaths.back()); + m_allSearchPaths.clear(); +} + +void ItemReader::popExtraSearchPaths() +{ + m_extraSearchPaths.pop_back(); + m_allSearchPaths.clear(); +} + +std::vector ItemReader::extraSearchPathsStack() const +{ + return m_extraSearchPaths; +} + +void ItemReader::setExtraSearchPathsStack(const std::vector &s) +{ + m_extraSearchPaths = s; + m_allSearchPaths.clear(); +} + +void ItemReader::clearExtraSearchPathsStack() +{ + m_extraSearchPaths.clear(); + m_allSearchPaths.clear(); +} + +const QStringList &ItemReader::allSearchPaths() const +{ + if (m_allSearchPaths.empty()) { + 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, allSearchPaths(), m_pool); +} + +Set ItemReader::filesRead() const +{ + return m_visitorState->filesRead(); +} + +void ItemReader::setEnableTiming(bool on) +{ + m_elapsedTime = on ? 0 : -1; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itemreader.h b/src/lib/corelib/language/itemreader.h new file mode 100644 index 00000000..6ec99fcb --- /dev/null +++ b/src/lib/corelib/language/itemreader.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** 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 QBS_ITEMREADER_H +#define QBS_ITEMREADER_H + +#include "forward_decls.h" +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class Item; +class ItemPool; +class ItemReaderVisitorState; + +/* + * Reads a qbs file and creates a tree of Item objects. + * + * In this stage the following steps are performed: + * - The QML/JS parser creates the AST. + * - The AST is converted to a tree of Item objects. + * + * This class is also responsible for the QMLish inheritance semantics. + */ +class ItemReader +{ +public: + ItemReader(Logger &logger); + ~ItemReader(); + + void setPool(ItemPool *pool) { m_pool = pool; } + void setSearchPaths(const QStringList &searchPaths); + void pushExtraSearchPaths(const QStringList &extraSearchPaths); + void popExtraSearchPaths(); + std::vector extraSearchPathsStack() const; + void setExtraSearchPathsStack(const std::vector &s); + void clearExtraSearchPathsStack(); + const QStringList &allSearchPaths() const; + + Item *readFile(const QString &filePath); + + Set filesRead() const; + + void setEnableTiming(bool on); + qint64 elapsedTime() const { return m_elapsedTime; } + +private: + ItemPool *m_pool = nullptr; + QStringList m_searchPaths; + std::vector m_extraSearchPaths; + mutable QStringList m_allSearchPaths; + ItemReaderVisitorState * const m_visitorState; + qint64 m_elapsedTime = -1; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMREADER_H diff --git a/src/lib/corelib/language/itemreaderastvisitor.cpp b/src/lib/corelib/language/itemreaderastvisitor.cpp new file mode 100644 index 00000000..901772d1 --- /dev/null +++ b/src/lib/corelib/language/itemreaderastvisitor.cpp @@ -0,0 +1,405 @@ +/**************************************************************************** +** +** 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 "itemreaderastvisitor.h" + +#include "astimportshandler.h" +#include "astpropertiesitemhandler.h" +#include "asttools.h" +#include "builtindeclarations.h" +#include "filecontext.h" +#include "identifiersearch.h" +#include "item.h" +#include "itemreadervisitorstate.h" +#include "value.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace QbsQmlJS; + +namespace qbs { +namespace Internal { + +ItemReaderASTVisitor::ItemReaderASTVisitor(ItemReaderVisitorState &visitorState, + FileContextPtr file, ItemPool *itemPool, Logger &logger) + : m_visitorState(visitorState) + , m_file(std::move(file)) + , m_itemPool(itemPool) + , m_logger(logger) +{ +} + +bool ItemReaderASTVisitor::visit(AST::UiProgram *uiProgram) +{ + ASTImportsHandler importsHandler(m_visitorState, m_logger, m_file); + importsHandler.handleImports(uiProgram->imports); + m_typeNameToFile = importsHandler.typeNameFileMap(); + return true; +} + +static ItemValuePtr findItemProperty(const Item *container, const Item *item) +{ + ItemValuePtr itemValue; + const auto &srcprops = container->properties(); + auto it = std::find_if(srcprops.begin(), srcprops.end(), [item] (const ValuePtr &v) { + return v->type() == Value::ItemValueType + && std::static_pointer_cast(v)->item() == item; + }); + if (it != srcprops.end()) + itemValue = std::static_pointer_cast(it.value()); + return itemValue; +} + +bool ItemReaderASTVisitor::visit(AST::UiObjectDefinition *ast) +{ + const QString typeName = ast->qualifiedTypeNameId->name.toString(); + const CodeLocation itemLocation = toCodeLocation(ast->qualifiedTypeNameId->identifierToken); + const Item *baseItem = nullptr; + Item *mostDerivingItem = nullptr; + + Item *item = Item::create(m_itemPool, ItemType::Unknown); + item->setFile(m_file); + item->setLocation(itemLocation); + + // Inheritance resolving, part 1: Find out our actual type name (needed for setting + // up children and alternatives). + const QStringList fullTypeName = toStringList(ast->qualifiedTypeNameId); + const QString baseTypeFileName = m_typeNameToFile.value(fullTypeName); + ItemType itemType; + if (!baseTypeFileName.isEmpty()) { + const bool isMostDerivingItem = (m_visitorState.mostDerivingItem() == nullptr); + if (isMostDerivingItem) + m_visitorState.setMostDerivingItem(item); + mostDerivingItem = m_visitorState.mostDerivingItem(); + baseItem = m_visitorState.readFile(baseTypeFileName, m_file->searchPaths(), m_itemPool); + if (isMostDerivingItem) + m_visitorState.setMostDerivingItem(nullptr); + QBS_CHECK(baseItem->type() <= ItemType::LastActualItem); + itemType = baseItem->type(); + } else { + if (fullTypeName.size() > 1) { + throw ErrorInfo(Tr::tr("Invalid item '%1'. Did you mean to set a module property?") + .arg(fullTypeName.join(QLatin1Char('.'))), itemLocation); + } + itemType = BuiltinDeclarations::instance().typeForName(typeName, itemLocation); + checkDeprecationStatus(itemType, typeName, itemLocation); + if (itemType == ItemType::Properties && m_item && m_item->type() == ItemType::SubProject) + itemType = ItemType::PropertiesInSubProject; + } + + item->m_type = itemType; + + if (m_item) + Item::addChild(m_item, item); // Add this item to the children of the parent item. + else + m_item = item; // This is the root item. + + if (ast->initializer) { + Item *mdi = m_visitorState.mostDerivingItem(); + m_visitorState.setMostDerivingItem(nullptr); + qSwap(m_item, item); + const ItemType oldInstanceItemType = m_instanceItemType; + if (itemType == ItemType::Parameters || itemType == ItemType::Depends) + m_instanceItemType = ItemType::ModuleParameters; + ast->initializer->accept(this); + m_instanceItemType = oldInstanceItemType; + qSwap(m_item, item); + m_visitorState.setMostDerivingItem(mdi); + } + + ASTPropertiesItemHandler(item).handlePropertiesItems(); + + // Inheritance resolving, part 2 (depends on alternatives having been set up). + if (baseItem) { + inheritItem(item, baseItem); + if (baseItem->file()->idScope()) { + // Make ids from the derived file visible in the base file. + // ### Do we want to turn off this feature? It's QMLish but kind of strange. + item->file()->ensureIdScope(m_itemPool); + baseItem->file()->idScope()->setPrototype(item->file()->idScope()); + + // Replace the base item with the most deriving item. + ItemValuePtr baseItemIdValue = findItemProperty(baseItem->file()->idScope(), baseItem); + if (baseItemIdValue) + baseItemIdValue->setItem(mostDerivingItem); + } + } else { + // Only the item at the top of the inheritance chain is a built-in item. + // We cannot do this in "part 1", because then the visitor would complain about duplicate + // bindings. + item->setupForBuiltinType(m_logger); + } + + return false; +} + +void ItemReaderASTVisitor::checkDuplicateBinding(Item *item, const QStringList &bindingName, + const AST::SourceLocation &sourceLocation) +{ + if (Q_UNLIKELY(item->hasOwnProperty(bindingName.last()))) { + QString msg = Tr::tr("Duplicate binding for '%1'"); + throw ErrorInfo(msg.arg(bindingName.join(QLatin1Char('.'))), + toCodeLocation(sourceLocation)); + } +} + +bool ItemReaderASTVisitor::visit(AST::UiPublicMember *ast) +{ + PropertyDeclaration p; + if (Q_UNLIKELY(ast->name.isEmpty())) + throw ErrorInfo(Tr::tr("public member without name")); + if (Q_UNLIKELY(ast->memberType.isEmpty())) + throw ErrorInfo(Tr::tr("public member without type")); + if (Q_UNLIKELY(ast->type == AST::UiPublicMember::Signal)) + throw ErrorInfo(Tr::tr("public member with signal type not supported")); + p.setName(ast->name.toString()); + p.setType(PropertyDeclaration::propertyTypeFromString(ast->memberType.toString())); + if (p.type() == PropertyDeclaration::UnknownType) { + throw ErrorInfo(Tr::tr("Unknown type '%1' in property declaration.") + .arg(ast->memberType.toString()), toCodeLocation(ast->typeToken)); + } + if (Q_UNLIKELY(!ast->typeModifier.isEmpty())) { + throw ErrorInfo(Tr::tr("public member with type modifier '%1' not supported").arg( + ast->typeModifier.toString())); + } + if (ast->isReadonlyMember) + p.setFlags(PropertyDeclaration::ReadOnlyFlag); + + m_item->m_propertyDeclarations.insert(p.name(), p); + + const JSSourceValuePtr value = JSSourceValue::create(); + value->setFile(m_file); + if (ast->statement) { + handleBindingRhs(ast->statement, value); + const QStringList bindingName(p.name()); + checkDuplicateBinding(m_item, bindingName, ast->colonToken); + } + + m_item->setProperty(p.name(), value); + return false; +} + +bool ItemReaderASTVisitor::visit(AST::UiScriptBinding *ast) +{ + QBS_CHECK(ast->qualifiedId); + QBS_CHECK(!ast->qualifiedId->name.isEmpty()); + + const QStringList bindingName = toStringList(ast->qualifiedId); + + if (bindingName.length() == 1 && bindingName.front() == QStringLiteral("id")) { + const auto * const expStmt = AST::cast(ast->statement); + if (Q_UNLIKELY(!expStmt)) + throw ErrorInfo(Tr::tr("id: must be followed by identifier")); + const auto * const idExp = AST::cast(expStmt->expression); + if (Q_UNLIKELY(!idExp || idExp->name.isEmpty())) + throw ErrorInfo(Tr::tr("id: must be followed by identifier")); + m_item->m_id = idExp->name.toString(); + m_file->ensureIdScope(m_itemPool); + ItemValueConstPtr existingId = m_file->idScope()->itemProperty(m_item->id()); + if (existingId) { + ErrorInfo e(Tr::tr("The id '%1' is not unique.").arg(m_item->id())); + e.append(Tr::tr("First occurrence is here."), existingId->item()->location()); + e.append(Tr::tr("Next occurrence is here."), m_item->location()); + throw e; + } + m_file->idScope()->setProperty(m_item->id(), ItemValue::create(m_item)); + return false; + } + + const JSSourceValuePtr value = JSSourceValue::create(); + handleBindingRhs(ast->statement, value); + + Item * const targetItem = targetItemForBinding(bindingName, value); + checkDuplicateBinding(targetItem, bindingName, ast->qualifiedId->identifierToken); + targetItem->setProperty(bindingName.last(), value); + return false; +} + +bool ItemReaderASTVisitor::handleBindingRhs(AST::Statement *statement, + const JSSourceValuePtr &value) +{ + QBS_CHECK(statement); + QBS_CHECK(value); + + if (AST::cast(statement)) + value->m_flags |= JSSourceValue::HasFunctionForm; + + value->setFile(m_file); + value->setSourceCode(textRefOf(m_file->content(), statement)); + value->setLocation(statement->firstSourceLocation().startLine, + statement->firstSourceLocation().startColumn); + + bool usesBase, usesOuter, usesOriginal; + IdentifierSearch idsearch; + idsearch.add(StringConstants::baseVar(), &usesBase); + idsearch.add(StringConstants::outerVar(), &usesOuter); + idsearch.add(StringConstants::originalVar(), &usesOriginal); + idsearch.start(statement); + if (usesBase) + value->m_flags |= JSSourceValue::SourceUsesBase; + if (usesOuter) + value->m_flags |= JSSourceValue::SourceUsesOuter; + if (usesOriginal) + value->m_flags |= JSSourceValue::SourceUsesOriginal; + return false; +} + +CodeLocation ItemReaderASTVisitor::toCodeLocation(const AST::SourceLocation &location) const +{ + return CodeLocation(m_file->filePath(), location.startLine, location.startColumn); +} + +Item *ItemReaderASTVisitor::targetItemForBinding(const QStringList &bindingName, + const JSSourceValueConstPtr &value) +{ + Item *targetItem = m_item; + const int c = bindingName.size() - 1; + for (int i = 0; i < c; ++i) { + ValuePtr v = targetItem->ownProperty(bindingName.at(i)); + if (!v) { + const ItemType itemType = i < c - 1 ? ItemType::ModulePrefix : m_instanceItemType; + Item *newItem = Item::create(m_itemPool, itemType); + newItem->setLocation(value->location()); + v = ItemValue::create(newItem); + targetItem->setProperty(bindingName.at(i), v); + } + if (Q_UNLIKELY(v->type() != Value::ItemValueType)) { + QString msg = Tr::tr("Binding to non-item property."); + throw ErrorInfo(msg, value->location()); + } + targetItem = std::static_pointer_cast(v)->item(); + } + return targetItem; +} + +void ItemReaderASTVisitor::inheritItem(Item *dst, const Item *src) +{ + int insertPos = 0; + for (Item *child : qAsConst(src->m_children)) { + dst->m_children.insert(insertPos++, child); + child->m_parent = dst; + } + + for (const PropertyDeclaration &pd : src->propertyDeclarations()) { + if (pd.flags().testFlag(PropertyDeclaration::ReadOnlyFlag) + && dst->hasOwnProperty(pd.name())) { + throw ErrorInfo(Tr::tr("Cannot set read-only property '%1'.").arg(pd.name()), + dst->property(pd.name())->location()); + } + dst->setPropertyDeclaration(pd.name(), pd); + } + + for (auto it = src->properties().constBegin(); it != src->properties().constEnd(); ++it) { + ValuePtr &v = dst->m_properties[it.key()]; + if (!v) { + v = it.value(); + continue; + } + if (v->type() == Value::ItemValueType && it.value()->type() != Value::ItemValueType) + throw ErrorInfo(Tr::tr("Binding to non-item property."), v->location()); + if (v->type() != it.value()->type()) + continue; + switch (v->type()) { + case Value::JSSourceValueType: { + JSSourceValuePtr sv = std::static_pointer_cast(v); + QBS_CHECK(!sv->baseValue()); + const JSSourceValuePtr baseValue = std::static_pointer_cast(it.value()); + sv->setBaseValue(baseValue); + for (const JSSourceValue::Alternative &alt : sv->m_alternatives) + alt.value->setBaseValue(baseValue); + break; + } + case Value::ItemValueType: + inheritItem(std::static_pointer_cast(v)->item(), + std::static_pointer_cast(it.value())->item()); + break; + default: + QBS_CHECK(!"unexpected value type"); + } + } +} + +void ItemReaderASTVisitor::checkDeprecationStatus(ItemType itemType, const QString &itemName, + const CodeLocation &itemLocation) +{ + const ItemDeclaration itemDecl = BuiltinDeclarations::instance().declarationsForType(itemType); + const DeprecationInfo &di = itemDecl.deprecationInfo(); + if (!di.isValid()) + return; + if (di.removalVersion() <= LanguageInfo::qbsVersion()) { + QString message = Tr::tr("The item '%1' cannot be used anymore. " + "It was removed in qbs %2.") + .arg(itemName, di.removalVersion().toString()); + ErrorInfo error(message, itemLocation); + if (!di.additionalUserInfo().isEmpty()) + error.append(di.additionalUserInfo()); + throw error; + } + QString warning = Tr::tr("The item '%1' is deprecated and will be removed in " + "qbs %2.").arg(itemName, di.removalVersion().toString()); + ErrorInfo error(warning, itemLocation); + if (!di.additionalUserInfo().isEmpty()) + error.append(di.additionalUserInfo()); + m_logger.printWarning(error); +} + +void ItemReaderASTVisitor::doCheckItemTypes(const Item *item) +{ + const ItemDeclaration decl = BuiltinDeclarations::instance().declarationsForType(item->type()); + for (const Item * const child : item->children()) { + if (!decl.isChildTypeAllowed(child->type())) { + throw ErrorInfo(Tr::tr("Items of type '%1' cannot contain items of type '%2'.") + .arg(item->typeName(), child->typeName()), child->location()); + } + doCheckItemTypes(child); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itemreaderastvisitor.h b/src/lib/corelib/language/itemreaderastvisitor.h new file mode 100644 index 00000000..963b7847 --- /dev/null +++ b/src/lib/corelib/language/itemreaderastvisitor.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** 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 QBS_ITEMREADERASTVISITOR_H +#define QBS_ITEMREADERASTVISITOR_H + +#include "forward_decls.h" +#include "itemtype.h" + +#include +#include + +#include +#include + +namespace qbs { +class CodeLocation; + +namespace Internal { +class Item; +class ItemPool; +class ItemReaderVisitorState; + +class ItemReaderASTVisitor : public QbsQmlJS::AST::Visitor +{ +public: + ItemReaderASTVisitor(ItemReaderVisitorState &visitorState, FileContextPtr file, + ItemPool *itemPool, Logger &logger); + void checkItemTypes() { doCheckItemTypes(rootItem()); } + + Item *rootItem() const { return m_item; } + +private: + bool visit(QbsQmlJS::AST::UiProgram *uiProgram) override; + bool visit(QbsQmlJS::AST::UiObjectDefinition *ast) override; + bool visit(QbsQmlJS::AST::UiPublicMember *ast) override; + bool visit(QbsQmlJS::AST::UiScriptBinding *ast) override; + + bool handleBindingRhs(QbsQmlJS::AST::Statement *statement, const JSSourceValuePtr &value); + CodeLocation toCodeLocation(const QbsQmlJS::AST::SourceLocation &location) const; + void checkDuplicateBinding(Item *item, const QStringList &bindingName, + const QbsQmlJS::AST::SourceLocation &sourceLocation); + Item *targetItemForBinding(const QStringList &binding, const JSSourceValueConstPtr &value); + static void inheritItem(Item *dst, const Item *src); + void checkDeprecationStatus(ItemType itemType, const QString &itemName, + const CodeLocation &itemLocation); + void doCheckItemTypes(const Item *item); + + ItemReaderVisitorState &m_visitorState; + const FileContextPtr m_file; + ItemPool * const m_itemPool; + Logger &m_logger; + QHash m_typeNameToFile; + Item *m_item = nullptr; + ItemType m_instanceItemType = ItemType::ModuleInstance; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_ITEMREADERASTVISITOR_H diff --git a/src/lib/corelib/language/itemreadervisitorstate.cpp b/src/lib/corelib/language/itemreadervisitorstate.cpp new file mode 100644 index 00000000..20ddb5cf --- /dev/null +++ b/src/lib/corelib/language/itemreadervisitorstate.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** 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 "itemreadervisitorstate.h" + +#include "asttools.h" +#include "filecontext.h" +#include "itemreaderastvisitor.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +class ASTCacheValueData : public QSharedData +{ + Q_DISABLE_COPY(ASTCacheValueData) +public: + ASTCacheValueData() + : ast(nullptr) + , processing(false) + { + } + + QString code; + QbsQmlJS::Engine engine; + QbsQmlJS::AST::UiProgram *ast; + bool processing; +}; + +class ASTCacheValue +{ +public: + ASTCacheValue() + : d(new ASTCacheValueData) + { + } + + ASTCacheValue(const ASTCacheValue &other) = default; + + void setProcessingFlag(bool b) { d->processing = b; } + bool isProcessing() const { return d->processing; } + + void setCode(const QString &code) { d->code = code; } + QString code() const { return d->code; } + + QbsQmlJS::Engine *engine() const { return &d->engine; } + + void setAst(QbsQmlJS::AST::UiProgram *ast) { d->ast = ast; } + QbsQmlJS::AST::UiProgram *ast() const { return d->ast; } + bool isValid() const { return d->ast; } + +private: + QExplicitlySharedDataPointer d; +}; + +class ItemReaderVisitorState::ASTCache : public QHash {}; + + +ItemReaderVisitorState::ItemReaderVisitorState(Logger &logger) + : m_logger(logger) + , m_astCache(new ASTCache) +{ + +} + +ItemReaderVisitorState::~ItemReaderVisitorState() +{ + delete m_astCache; +} + +Item *ItemReaderVisitorState::readFile(const QString &filePath, const QStringList &searchPaths, + ItemPool *itemPool) +{ + ASTCacheValue &cacheValue = (*m_astCache)[filePath]; + if (cacheValue.isValid()) { + if (Q_UNLIKELY(cacheValue.isProcessing())) + throw ErrorInfo(Tr::tr("Loop detected when importing '%1'.").arg(filePath)); + } else { + QFile file(filePath); + if (Q_UNLIKELY(!file.open(QFile::ReadOnly))) + throw ErrorInfo(Tr::tr("Cannot open '%1'.").arg(filePath)); + + m_filesRead.insert(filePath); + QTextStream stream(&file); + stream.setCodec("UTF-8"); + const QString &code = stream.readAll(); + QbsQmlJS::Lexer lexer(cacheValue.engine()); + lexer.setCode(code, 1); + QbsQmlJS::Parser parser(cacheValue.engine()); + + file.close(); + if (!parser.parse()) { + const QList &parserMessages = parser.diagnosticMessages(); + if (Q_UNLIKELY(!parserMessages.empty())) { + ErrorInfo err; + for (const QbsQmlJS::DiagnosticMessage &msg : parserMessages) + err.append(msg.message, toCodeLocation(filePath, msg.loc)); + throw err; + } + } + + cacheValue.setCode(code); + cacheValue.setAst(parser.ast()); + } + + const FileContextPtr file = FileContext::create(); + file->setFilePath(QFileInfo(filePath).absoluteFilePath()); + file->setContent(cacheValue.code()); + file->setSearchPaths(searchPaths); + + ItemReaderASTVisitor astVisitor(*this, file, itemPool, m_logger); + { + class ProcessingFlagManager { + public: + ProcessingFlagManager(ASTCacheValue &v) : m_cacheValue(v) { v.setProcessingFlag(true); } + ~ProcessingFlagManager() { m_cacheValue.setProcessingFlag(false); } + private: + ASTCacheValue &m_cacheValue; + } processingFlagManager(cacheValue); + cacheValue.ast()->accept(&astVisitor); + } + astVisitor.checkItemTypes(); + return astVisitor.rootItem(); +} + +void ItemReaderVisitorState::cacheDirectoryEntries(const QString &dirPath, const QStringList &entries) +{ + m_directoryEntries.insert(dirPath, entries); +} + +bool ItemReaderVisitorState::findDirectoryEntries(const QString &dirPath, QStringList *entries) const +{ + const auto it = m_directoryEntries.constFind(dirPath); + if (it == m_directoryEntries.constEnd()) + return false; + *entries = it.value(); + return true; +} + +Item *ItemReaderVisitorState::mostDerivingItem() const +{ + return m_mostDerivingItem; +} + +void ItemReaderVisitorState::setMostDerivingItem(Item *item) +{ + m_mostDerivingItem = item; +} + + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/itemreadervisitorstate.h b/src/lib/corelib/language/itemreadervisitorstate.h new file mode 100644 index 00000000..6db9d361 --- /dev/null +++ b/src/lib/corelib/language/itemreadervisitorstate.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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 QBS_ITEMREADERVISITORSTATE_H +#define QBS_ITEMREADERVISITORSTATE_H + +#include +#include + +#include + +namespace qbs { +namespace Internal { +class Item; +class ItemPool; + +class ItemReaderVisitorState +{ +public: + ItemReaderVisitorState(Logger &logger); + ~ItemReaderVisitorState(); + + Set filesRead() const { return m_filesRead; } + + Item *readFile(const QString &filePath, const QStringList &searchPaths, ItemPool *itemPool); + + void cacheDirectoryEntries(const QString &dirPath, const QStringList &entries); + bool findDirectoryEntries(const QString &dirPath, QStringList *entries) const; + + Item *mostDerivingItem() const; + void setMostDerivingItem(Item *item); + +private: + Logger &m_logger; + Set m_filesRead; + QHash m_directoryEntries; + Item *m_mostDerivingItem = nullptr; + + class ASTCache; + ASTCache * const m_astCache; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/language/itemtype.h b/src/lib/corelib/language/itemtype.h new file mode 100644 index 00000000..724666cb --- /dev/null +++ b/src/lib/corelib/language/itemtype.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 QBS_ITEMTYPE_H +#define QBS_ITEMTYPE_H + +#include + +namespace qbs { +namespace Internal { + +enum class ItemType { + // Actual user-visible items. + FirstActualItem, + Artifact = FirstActualItem, + Depends, + Export, + FileTagger, + Group, + JobLimit, + Module, + ModuleProvider, + Parameter, + Parameters, + Probe, + Product, + Profile, + Project, + Properties, + PropertiesInSubProject, + PropertyOptions, + Rule, + Scanner, + SubProject, + Transformer, + LastActualItem = Transformer, + + // Internal items created mainly by the module loader. + IdScope, + ModuleInstance, + ModuleParameters, + ModulePrefix, + Outer, + Scope, + + Unknown +}; + +inline uint qHash(ItemType t) { return QT_PREPEND_NAMESPACE(qHash)(uint(t)); } + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. + diff --git a/src/lib/corelib/language/jsimports.h b/src/lib/corelib/language/jsimports.h new file mode 100644 index 00000000..a892e0ec --- /dev/null +++ b/src/lib/corelib/language/jsimports.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 QBS_JSIMPORTS_H +#define QBS_JSIMPORTS_H + +#include +#include +#include + +#include +#include + +#include + +namespace qbs { +namespace Internal { + +/** + * Represents JavaScript import of the form + * import 'fileOrDirectory' as scopeName + * + * There can be several filenames per scope + * if we import a whole directory. + */ +class JsImport +{ +public: + QString scopeName; + QStringList filePaths; + CodeLocation location; + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(scopeName, filePaths, location); + } +}; +inline uint qHash(const JsImport &jsi) { return qHash(jsi.scopeName); } + +inline bool operator<(const JsImport &lhs, const JsImport &rhs) +{ + return lhs.scopeName < rhs.scopeName; +} + +inline bool operator==(const JsImport &jsi1, const JsImport &jsi2) +{ + return jsi1.scopeName == jsi2.scopeName && toSet(jsi1.filePaths) == toSet(jsi2.filePaths); +} + +using JsImports = std::vector; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_JSIMPORTS_H diff --git a/src/lib/corelib/language/language.cpp b/src/lib/corelib/language/language.cpp new file mode 100644 index 00000000..7b21bc12 --- /dev/null +++ b/src/lib/corelib/language/language.cpp @@ -0,0 +1,982 @@ +/**************************************************************************** +** +** 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 "language.h" + +#include "artifactproperties.h" +#include "builtindeclarations.h" +#include "propertymapinternal.h" +#include "scriptengine.h" + +#include +#include +#include +#include +#include // TODO: Move to language? +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +template bool equals(const T *v1, const T *v2) +{ + if (v1 == v2) + return true; + if (!v1 != !v2) + return false; + return *v1 == *v2; +} + + +/*! + * \class FileTagger + * \brief The \c FileTagger class maps 1:1 to the respective item in a qbs source file. + */ + +FileTagger::FileTagger(const QStringList &patterns, FileTags fileTags, int priority) + : m_fileTags(std::move(fileTags)), m_priority(priority) +{ + setPatterns(patterns); +} + +void FileTagger::setPatterns(const QStringList &patterns) +{ + m_patterns.clear(); + for (const QString &pattern : patterns) { + QBS_CHECK(!pattern.isEmpty()); + m_patterns << QRegExp(pattern, Qt::CaseSensitive, QRegExp::Wildcard); + } +} + + +bool Probe::needsReconfigure(const FileTime &referenceTime) const +{ + const auto criterion = [referenceTime](const QString &filePath) { + FileInfo fi(filePath); + return !fi.exists() || fi.lastModified() > referenceTime; + }; + return std::any_of(m_importedFilesUsed.cbegin(), m_importedFilesUsed.cend(), criterion); +} + + +/*! + * \class SourceArtifact + * \brief The \c SourceArtifact class represents a source file. + * Everything except the file path is inherited from the surrounding \c ResolvedGroup. + * (TODO: Not quite true. Artifacts in transformers will be generated by the transformer, but are + * still represented as source artifacts. We may or may not want to change this; if we do, + * SourceArtifact could simply have a back pointer to the group in addition to the file path.) + * \sa ResolvedGroup + */ + + +/*! + * \class ResolvedGroup + * \brief The \c ResolvedGroup class corresponds to the Group item in a qbs source file. + */ + + /*! + * \variable ResolvedGroup::files + * \brief The files listed in the group item's "files" binding. + * Note that these do not include expanded wildcards. + */ + +/*! + * \variable ResolvedGroup::wildcards + * \brief Represents the wildcard elements in this group's "files" binding. + * If no wildcards are specified there, this variable is null. + * \sa SourceWildCards + */ + +/*! + * \brief Returns all files specified in the group item as source artifacts. + * This includes the expanded list of wildcards. + */ +std::vector ResolvedGroup::allFiles() const +{ + std::vector lst = files; + if (wildcards) + lst << wildcards->files; + return lst; +} + +void ResolvedGroup::load(PersistentPool &pool) +{ + serializationOp(pool); + if (wildcards) + wildcards->group = this; +} + +void ResolvedGroup::store(PersistentPool &pool) +{ + serializationOp(pool); +} + +/*! + * \class RuleArtifact + * \brief The \c RuleArtifact class represents an Artifact item encountered in the context + * of a Rule item. + * When applying the rule, one \c Artifact object will be constructed from each \c RuleArtifact + * object. During that process, the \c RuleArtifact's bindings are evaluated and the results + * are inserted into the corresponding \c Artifact's properties. + * \sa Rule + */ + + +/*! + * \class ScriptFunction + * \brief The \c ScriptFunction class represents the JavaScript code found in the "prepare" binding + * of a \c Rule item in a qbs file. + * \sa Rule + */ + +ScriptFunction::ScriptFunction() = default; + +ScriptFunction::~ScriptFunction() = default; + + /*! + * \variable ScriptFunction::script + * \brief The actual Javascript code, taken verbatim from the qbs source file. + */ + + /*! + * \variable ScriptFunction::location + * \brief The exact location of the script in the qbs source file. + * This is mostly needed for diagnostics. + */ + +bool ScriptFunction::isValid() const +{ + return location.line() != -1; +} + +bool operator==(const ScriptFunction &a, const ScriptFunction &b) +{ + return a.sourceCode == b.sourceCode + && a.location == b.location + && equals(a.fileContext.get(), b.fileContext.get()); +} + +QStringList ResolvedModule::argumentNamesForSetupBuildEnv() +{ + static const QStringList argNames = BuiltinDeclarations::instance() + .argumentNamesForScriptFunction(ItemType::Module, + StringConstants::setupBuildEnvironmentProperty()); + return argNames; +} + +QStringList ResolvedModule::argumentNamesForSetupRunEnv() +{ + static const QStringList argNames = BuiltinDeclarations::instance() + .argumentNamesForScriptFunction(ItemType::Module, + StringConstants::setupRunEnvironmentProperty()); + return argNames; +} + +bool operator==(const ResolvedModule &m1, const ResolvedModule &m2) +{ + return m1.name == m2.name + && m1.isProduct == m2.isProduct + && toSet(m1.moduleDependencies) == toSet(m2.moduleDependencies) + && m1.setupBuildEnvironmentScript == m2.setupBuildEnvironmentScript + && m1.setupRunEnvironmentScript == m2.setupRunEnvironmentScript; +} + +RulePtr Rule::clone() const +{ + return std::make_shared(*this); +} + +QStringList Rule::argumentNamesForOutputArtifacts() +{ + static const QStringList argNames = BuiltinDeclarations::instance() + .argumentNamesForScriptFunction(ItemType::Rule, + StringConstants::outputArtifactsProperty()); + return argNames; +} + +QStringList Rule::argumentNamesForPrepare() +{ + static const QStringList argNames = BuiltinDeclarations::instance() + .argumentNamesForScriptFunction(ItemType::Rule, StringConstants::prepareProperty()); + return argNames; +} + +QString Rule::toString() const +{ + QStringList outputTagsSorted = collectedOutputFileTags().toStringList(); + outputTagsSorted.sort(); + FileTags inputTags = inputs; + inputTags.unite(inputsFromDependencies); + QStringList inputTagsSorted = inputTags.toStringList(); + inputTagsSorted.sort(); + return QLatin1Char('[') + outputTagsSorted.join(QLatin1Char(',')) + + QLatin1String("][") + + inputTagsSorted.join(QLatin1Char(',')) + QLatin1Char(']'); +} + +FileTags Rule::staticOutputFileTags() const +{ + FileTags result; + for (const auto &artifact : artifacts) + result.unite(artifact->fileTags); + return result; +} + +FileTags Rule::collectedOutputFileTags() const +{ + FileTags result = outputFileTags.empty() ? staticOutputFileTags() : outputFileTags; + for (const auto &ap : product->artifactProperties) { + if (ap->fileTagsFilter().intersects(result)) + result += ap->extraFileTags(); + } + return result; +} + +bool Rule::isDynamic() const +{ + return outputArtifactsScript.isValid(); +} + +bool Rule::declaresInputs() const +{ + return !inputs.empty() || !inputsFromDependencies.empty(); +} + +ResolvedProduct::ResolvedProduct() + : enabled(true) +{ +} + +ResolvedProduct::~ResolvedProduct() = default; + +void ResolvedProduct::accept(BuildGraphVisitor *visitor) const +{ + if (!buildData) + return; + for (BuildGraphNode * const node : qAsConst(buildData->rootNodes())) + node->accept(visitor); +} + +/*! + * \brief Returns all files of all groups as source artifacts. + * This includes the expanded list of wildcards. + */ +std::vector ResolvedProduct::allFiles() const +{ + std::vector lst; + for (const auto &group : groups) + lst << group->allFiles(); + return lst; +} + +/*! + * \brief Returns all files of all enabled groups as source artifacts. + * \sa ResolvedProduct::allFiles() + */ +std::vector ResolvedProduct::allEnabledFiles() const +{ + std::vector lst; + for (const auto &group : groups) { + if (group->enabled) + lst << group->allFiles(); + } + return lst; +} + +FileTags ResolvedProduct::fileTagsForFileName(const QString &fileName) const +{ + FileTags result; + std::unique_ptr 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 = std::make_unique(tagger->priority()); + } + result.unite(tagger->fileTags()); + break; + } + } + } + return result; +} + +void ResolvedProduct::load(PersistentPool &pool) +{ + serializationOp(pool); + for (const RulePtr &rule : rules) + rule->product = this; + for (const ResolvedModulePtr &module : modules) + module->product = this; +} + +void ResolvedProduct::store(PersistentPool &pool) +{ + serializationOp(pool); +} + +ArtifactSet ResolvedProduct::lookupArtifactsByFileTag(const FileTag &tag) const +{ + QBS_CHECK(buildData); + return buildData->artifactsByFileTag().value(tag); +} + +ArtifactSet ResolvedProduct::lookupArtifactsByFileTags(const FileTags &tags) const +{ + QBS_CHECK(buildData); + ArtifactSet set; + for (const FileTag &tag : tags) + set = set.unite(buildData->artifactsByFileTag().value(tag)); + return set; +} + +ArtifactSet ResolvedProduct::targetArtifacts() const +{ + QBS_CHECK(buildData); + ArtifactSet taSet; + for (Artifact * const a : buildData->rootArtifacts()) { + QBS_CHECK(a->fileTags().intersects(fileTags)); + taSet << a; + } + return taSet; +} + +TopLevelProject *ResolvedProduct::topLevelProject() const +{ + return project->topLevelProject(); +} + +QString ResolvedProduct::uniqueName(const QString &name, const QString &multiplexConfigurationId) +{ + QString result = name; + if (!multiplexConfigurationId.isEmpty()) + result.append(QLatin1Char('.')).append(multiplexConfigurationId); + return result; +} + +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); +} + +QString ResolvedProduct::profile() const +{ + return moduleProperties->qbsPropertyValue(StringConstants::profileProperty()).toString(); +} + +static QStringList findGeneratedFiles(const Artifact *base, bool recursive, const FileTags &tags) +{ + QStringList result; + for (const Artifact *parent : base->parentArtifacts()) { + if (tags.empty() || parent->fileTags().intersects(tags)) + result << parent->filePath(); + if (recursive) + result << findGeneratedFiles(parent, true, tags); + } + return result; +} + +QStringList ResolvedProduct::generatedFiles(const QString &baseFile, bool recursive, + const FileTags &tags) const +{ + ProductBuildData *data = buildData.get(); + if (!data) + return {}; + + for (const Artifact *art : filterByType(data->allNodes())) { + if (art->filePath() == baseFile) + return findGeneratedFiles(art, recursive, tags); + } + return {}; +} + +QString ResolvedProduct::deriveBuildDirectoryName(const QString &name, + const QString &multiplexConfigurationId) +{ + QString dirName = uniqueName(name, multiplexConfigurationId); + const QByteArray hash = QCryptographicHash::hash(dirName.toUtf8(), QCryptographicHash::Sha1); + return HostOsInfo::rfc1034Identifier(dirName) + .append(QLatin1Char('.')) + .append(QString::fromLatin1(hash.toHex().left(8))); +} + +QString ResolvedProduct::buildDirectory() const +{ + return productProperties.value(StringConstants::buildDirectoryProperty()).toString(); +} + +bool ResolvedProduct::isInParentProject(const ResolvedProductConstPtr &other) const +{ + for (const ResolvedProject *otherParent = other->project.get(); otherParent; + otherParent = otherParent->parentProject.get()) { + if (otherParent == project.get()) + return true; + } + return false; +} + +bool ResolvedProduct::builtByDefault() const +{ + return productProperties.value(StringConstants::builtByDefaultProperty(), true).toBool(); +} + +void ResolvedProduct::cacheExecutablePath(const QString &origFilePath, const QString &fullFilePath) +{ + std::lock_guard locker(m_executablePathCacheLock); + m_executablePathCache.insert(origFilePath, fullFilePath); +} + +QString ResolvedProduct::cachedExecutablePath(const QString &origFilePath) const +{ + std::lock_guard locker(m_executablePathCacheLock); + return m_executablePathCache.value(origFilePath); +} + + +ResolvedProject::ResolvedProject() : enabled(true), m_topLevelProject(nullptr) +{ +} + +ResolvedProject::~ResolvedProject() = default; + +void ResolvedProject::accept(BuildGraphVisitor *visitor) const +{ + for (const ResolvedProductPtr &product : products) + product->accept(visitor); + for (const ResolvedProjectPtr &subProject : qAsConst(subProjects)) + subProject->accept(visitor); +} + +TopLevelProject *ResolvedProject::topLevelProject() +{ + if (m_topLevelProject) + return m_topLevelProject; + if (parentProject.expired()) { + m_topLevelProject = static_cast(this); + return m_topLevelProject; + } + m_topLevelProject = parentProject->topLevelProject(); + return m_topLevelProject; +} + +std::vector ResolvedProject::allSubProjects() const +{ + std::vector projectList = subProjects; + for (const auto &subProject : subProjects) + projectList << subProject->allSubProjects(); + return projectList; +} + +std::vector ResolvedProject::allProducts() const +{ + std::vector productList = products; + for (const auto &subProject : qAsConst(subProjects)) + productList << subProject->allProducts(); + return productList; +} + +void ResolvedProject::load(PersistentPool &pool) +{ + serializationOp(pool); + std::for_each(products.cbegin(), products.cend(), + [](const ResolvedProductPtr &p) { + if (!p->buildData) + return; + for (BuildGraphNode * const node : qAsConst(p->buildData->allNodes())) { + node->product = p; + + // restore parent links + for (BuildGraphNode * const child : qAsConst(node->children)) + child->parents.insert(node); + } + }); +} + +void ResolvedProject::store(PersistentPool &pool) +{ + serializationOp(pool); +} + + +TopLevelProject::TopLevelProject() + : bgLocker(nullptr), locked(false), lastStartResolveTime(FileTime::oldestTime()) +{ +} + +TopLevelProject::~TopLevelProject() +{ + cleanupModuleProviderOutput(); + delete bgLocker; +} + +QString TopLevelProject::deriveId(const QVariantMap &config) +{ + const QVariantMap qbsProperties = config.value(StringConstants::qbsModule()).toMap(); + const QString configurationName = qbsProperties.value( + StringConstants::configurationNameProperty()).toString(); + return configurationName; +} + +QString TopLevelProject::deriveBuildDirectory(const QString &buildRoot, const QString &id) +{ + return buildRoot + QLatin1Char('/') + id; +} + +void TopLevelProject::setBuildConfiguration(const QVariantMap &config) +{ + m_buildConfiguration = config; + m_id = deriveId(config); +} + +QString TopLevelProject::profile() const +{ + return projectProperties().value(StringConstants::profileProperty()).toString(); +} + +void TopLevelProject::makeModuleProvidersNonTransient() +{ + for (ModuleProviderInfo &m : moduleProviderInfo) + m.transientOutput = false; +} + +QString TopLevelProject::buildGraphFilePath() const +{ + return ProjectBuildData::deriveBuildGraphFilePath(buildDirectory, id()); +} + +void TopLevelProject::store(Logger logger) +{ + // TODO: Use progress observer here. + + if (!buildData) + return; + if (!buildData->isDirty()) { + qCDebug(lcBuildGraph) << "build graph is unchanged in project" << id(); + return; + } + + makeModuleProvidersNonTransient(); + + const QString fileName = buildGraphFilePath(); + qCDebug(lcBuildGraph) << "storing:" << fileName; + PersistentPool pool(logger); + PersistentPool::HeadData headData; + headData.projectConfig = buildConfiguration(); + pool.setHeadData(headData); + pool.setupWriteStream(fileName); + store(pool); + pool.finalizeWriteStream(); + buildData->setClean(); +} + +void TopLevelProject::load(PersistentPool &pool) +{ + ResolvedProject::load(pool); + serializationOp(pool); + QBS_CHECK(buildData); +} + +void TopLevelProject::store(PersistentPool &pool) +{ + ResolvedProject::store(pool); + serializationOp(pool); +} + +void TopLevelProject::cleanupModuleProviderOutput() +{ + QString error; + for (const ModuleProviderInfo &m : moduleProviderInfo) { + if (m.transientOutput) { + if (!removeDirectoryWithContents(m.outputDirPath(buildDirectory), &error)) + qCWarning(lcBuildGraph) << "Error removing module provider output:" << error; + } + } + QDir moduleProviderBaseDir(buildDirectory + QLatin1Char('/') + + ModuleProviderInfo::outputBaseDirName()); + if (moduleProviderBaseDir.exists() && moduleProviderBaseDir.isEmpty() + && !removeDirectoryWithContents(moduleProviderBaseDir.path(), &error)) { + qCWarning(lcBuildGraph) << "Error removing module provider output:" << error; + } +} + +/*! + * \class SourceWildCards + * \brief Objects of the \c SourceWildCards class result from giving wildcards in a + * \c ResolvedGroup's "files" binding. + * \sa ResolvedGroup + */ + +/*! + * \variable SourceWildCards::prefix + * \brief Inherited from the \c ResolvedGroup + * \sa ResolvedGroup + */ + +/*! + * \variable SourceWildCards::patterns + * \brief All elements of the \c ResolvedGroup's "files" binding that contain wildcards. + * \sa ResolvedGroup + */ + +/*! + * \variable SourceWildCards::excludePatterns + * \brief Corresponds to the \c ResolvedGroup's "excludeFiles" binding. + * \sa ResolvedGroup + */ + +/*! + * \variable SourceWildCards::files + * \brief The \c SourceArtifacts resulting from the expanded list of matching files. + */ + +Set SourceWildCards::expandPatterns(const GroupConstPtr &group, + const QString &baseDir, const QString &buildDir) +{ + Set files = expandPatterns(group, patterns, baseDir, buildDir); + files -= expandPatterns(group, excludePatterns, baseDir, buildDir); + return files; +} + +Set SourceWildCards::expandPatterns(const GroupConstPtr &group, + const QStringList &patterns, const QString &baseDir, const QString &buildDir) +{ + Set files; + QString expandedPrefix = group->prefix; + if (expandedPrefix.startsWith(StringConstants::tildeSlash())) + expandedPrefix.replace(0, 1, QDir::homePath()); + for (QString pattern : patterns) { + pattern.prepend(expandedPrefix); + pattern.replace(QLatin1Char('\\'), QLatin1Char('/')); + QStringList parts = pattern.split(QLatin1Char('/'), QBS_SKIP_EMPTY_PARTS); + if (FileInfo::isAbsolute(pattern)) { + QString rootDir; + if (HostOsInfo::isWindowsHost() && pattern.at(0) != QLatin1Char('/')) { + rootDir = parts.takeFirst(); + if (!rootDir.endsWith(QLatin1Char('/'))) + rootDir.append(QLatin1Char('/')); + } else { + rootDir = QLatin1Char('/'); + } + expandPatterns(files, group, parts, rootDir, buildDir); + } else { + expandPatterns(files, group, parts, baseDir, buildDir); + } + } + + return files; +} + +void SourceWildCards::expandPatterns(Set &result, const GroupConstPtr &group, + const QStringList &parts, + const QString &baseDir, const QString &buildDir) +{ + // People might build directly in the project source directory. This is okay, since + // we keep the build data in a "container" directory. However, we must make sure we don't + // match any generated files therein as source files. + if (baseDir.startsWith(buildDir)) + return; + + dirTimeStamps.emplace_back(baseDir, FileInfo(baseDir).lastModified()); + + QStringList changed_parts = parts; + bool recursive = false; + QString part = changed_parts.takeFirst(); + + while (part == QStringLiteral("**")) { + recursive = true; + + if (changed_parts.empty()) { + part = StringConstants::star(); + break; + } + + part = changed_parts.takeFirst(); + } + + const bool isDir = !changed_parts.empty(); + + const QString &filePattern = part; + const QDirIterator::IteratorFlags itFlags = recursive + ? QDirIterator::Subdirectories + : QDirIterator::NoIteratorFlags; + QDir::Filters itFilters = isDir + ? QDir::Dirs + : QDir::Files | QDir::System + | QDir::Dirs; // This one is needed to get symbolic links to directories + + if (isDir && !FileInfo::isPattern(filePattern)) + itFilters |= QDir::Hidden; + if (filePattern != StringConstants::dotDot() && filePattern != StringConstants::dot()) + itFilters |= QDir::NoDotAndDotDot; + + QDirIterator it(baseDir, QStringList(filePattern), itFilters, itFlags); + while (it.hasNext()) { + const QString filePath = it.next(); + const QString parentDir = it.fileInfo().dir().path(); + if (parentDir.startsWith(buildDir)) + continue; // See above. + if (!isDir && it.fileInfo().isDir() && !it.fileInfo().isSymLink()) + continue; + if (isDir) { + expandPatterns(result, group, changed_parts, filePath, buildDir); + } else { + if (parentDir != baseDir) + dirTimeStamps.emplace_back(parentDir, FileInfo(baseDir).lastModified()); + result += QDir::cleanPath(filePath); + } + } +} + +template +QMap listToMap(const L &list) +{ + using V = typename L::value_type; + QMap map; + for (const V &elem : list) + map.insert(keyFromElem(elem), elem); + return map; +} + +template +bool listsAreEqual(const L &l1, const L &l2) +{ + if (l1.size() != l2.size()) + return false; + using V = typename L::value_type; + const QMap map1 = listToMap(l1); + const QMap map2 = listToMap(l2); + for (const QString &key : map1.keys()) { + const V &value2 = map2.value(key); + if (!value2) + return false; + if (!equals(map1.value(key).get(), value2.get())) + return false; + } + return true; +} + +QString keyFromElem(const SourceArtifactPtr &sa) { return sa->absoluteFilePath; } +QString keyFromElem(const RulePtr &r) { + QString key = r->toString() + r->prepareScript.sourceCode(); + if (r->outputArtifactsScript.isValid()) + key += r->outputArtifactsScript.sourceCode(); + for (const auto &a : r->artifacts) + key += a->filePath; + return key; +} + +QString keyFromElem(const ArtifactPropertiesPtr &ap) +{ + QStringList lst = ap->fileTagsFilter().toStringList(); + lst.sort(); + return lst.join(QLatin1Char(',')); +} + +bool operator==(const SourceArtifactInternal &sa1, const SourceArtifactInternal &sa2) +{ + return sa1.absoluteFilePath == sa2.absoluteFilePath + && sa1.fileTags == sa2.fileTags + && sa1.overrideFileTags == sa2.overrideFileTags + && sa1.targetOfModule == sa2.targetOfModule + && !sa1.properties == !sa2.properties + && *sa1.properties == *sa2.properties; +} + +bool operator==(const Rule &r1, const Rule &r2) +{ + if (r1.artifacts.size() != r2.artifacts.size()) + return false; + for (size_t i = 0; i < r1.artifacts.size(); ++i) { + if (!equals(r1.artifacts.at(i).get(), r2.artifacts.at(i).get())) + return false; + } + + return r1.module->name == r2.module->name + && r1.prepareScript == r2.prepareScript + && r1.outputArtifactsScript == r2.outputArtifactsScript + && r1.inputs == r2.inputs + && r1.outputFileTags == r2.outputFileTags + && r1.auxiliaryInputs == r2.auxiliaryInputs + && r1.excludedInputs == r2.excludedInputs + && r1.inputsFromDependencies == r2.inputsFromDependencies + && r1.explicitlyDependsOn == r2.explicitlyDependsOn + && r1.explicitlyDependsOnFromDependencies == r2.explicitlyDependsOnFromDependencies + && r1.multiplex == r2.multiplex + && r1.requiresInputs == r2.requiresInputs + && r1.alwaysRun == r2.alwaysRun; +} + +bool ruleListsAreEqual(const std::vector &l1, const std::vector &l2) +{ + return listsAreEqual(l1, l2); +} + +bool operator==(const RuleArtifact &a1, const RuleArtifact &a2) +{ + return a1.filePath == a2.filePath + && a1.fileTags == a2.fileTags + && a1.alwaysUpdated == a2.alwaysUpdated + && Set::fromStdVector(a1.bindings) == + Set::fromStdVector(a2.bindings); +} + +bool operator==(const RuleArtifact::Binding &b1, const RuleArtifact::Binding &b2) +{ + return b1.code == b2.code && b1.name == b2.name; +} + +uint qHash(const RuleArtifact::Binding &b) +{ + return qHash(std::make_pair(b.code, b.name.join(QLatin1Char(',')))); +} + +bool artifactPropertyListsAreEqual(const std::vector &l1, + const std::vector &l2) +{ + return listsAreEqual(l1, l2); +} + +QString multiplexIdToString(const QString &id) +{ + return QString::fromUtf8(QByteArray::fromBase64(id.toUtf8())); +} + +bool operator==(const PrivateScriptFunction &a, const PrivateScriptFunction &b) +{ + return equals(a.m_sharedData.get(), b.m_sharedData.get()); +} + +bool operator==(const ExportedProperty &p1, const ExportedProperty &p2) +{ + return p1.fullName == p2.fullName + && p1.type == p2.type + && p1.sourceCode == p2.sourceCode + && p1.isBuiltin == p2.isBuiltin; +} + +bool operator==(const ExportedModuleDependency &d1, const ExportedModuleDependency &d2) +{ + return d1.name == d2.name && d1.moduleProperties == d2.moduleProperties; +} + +bool equals(const std::vector &l1, const std::vector &l2) +{ + static const auto cmp = [](const ExportedItemPtr &p1, const ExportedItemPtr &p2) { + return *p1 == *p2; + }; + return l1.size() == l2.size() && std::equal(l1.cbegin(), l1.cend(), l2.cbegin(), cmp); +} + +bool operator==(const ExportedItem &i1, const ExportedItem &i2) +{ + return i1.name == i2.name + && i1.properties == i2.properties + && equals(i1.children, i2.children); +} + +bool operator==(const ExportedModule &m1, const ExportedModule &m2) +{ + static const auto depMapsEqual = [](const QMap &m1, + const QMap &m2) { + if (m1.size() != m2.size()) + return false; + for (auto it1 = m1.cbegin(), it2 = m2.cbegin(); it1 != m1.cend(); ++it1, ++it2) { + if (it1.key()->name != it2.key()->name) + return false; + if (it1.value() != it2.value()) + return false; + } + return true; + }; + + return m1.propertyValues == m2.propertyValues + && m1.modulePropertyValues == m2.modulePropertyValues + && equals(m1.children, m2.children) + && m1.m_properties == m2.m_properties + && m1.importStatements == m2.importStatements + && m1.productDependencies.size() == m2.productDependencies.size() + && m1.productDependencies == m2.productDependencies + && depMapsEqual(m1.dependencyParameters, m2.dependencyParameters); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/language.h b/src/lib/corelib/language/language.h new file mode 100644 index 00000000..bbd85133 --- /dev/null +++ b/src/lib/corelib/language/language.h @@ -0,0 +1,751 @@ +/**************************************************************************** +** +** 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 QBS_LANGUAGE_H +#define QBS_LANGUAGE_H + +#include "filetags.h" +#include "forward_decls.h" +#include "jsimports.h" +#include "moduleproviderinfo.h" +#include "propertydeclaration.h" +#include "resolvedfilecontext.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QScriptEngine; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class BuildGraphLocker; +class BuildGraphLoader; +class BuildGraphVisitor; + +class FileTagger +{ +public: + static FileTaggerPtr create() { return FileTaggerPtr(new FileTagger); } + static FileTaggerPtr create(const QStringList &patterns, const FileTags &fileTags, + int priority) { + return FileTaggerPtr(new FileTagger(patterns, fileTags, priority)); + } + + const QList &patterns() const { return m_patterns; } + const FileTags &fileTags() const { return m_fileTags; } + int priority() const { return m_priority; } + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(m_patterns, m_fileTags, m_priority); + } + +private: + FileTagger(const QStringList &patterns, FileTags fileTags, int priority); + FileTagger() = default; + + void setPatterns(const QStringList &patterns); + + QList m_patterns; + FileTags m_fileTags; + int m_priority = 0; +}; + +class Probe +{ +public: + static ProbePtr create() { return ProbePtr(new Probe); } + static ProbeConstPtr create(const QString &globalId, + const CodeLocation &location, + bool condition, + const QString &configureScript, + const QVariantMap &properties, + const QVariantMap &initialProperties, + const std::vector &importedFilesUsed) + { + return ProbeConstPtr(new Probe(globalId, location, condition, configureScript, properties, + initialProperties, importedFilesUsed)); + } + + const QString &globalId() const { return m_globalId; } + bool condition() const { return m_condition; } + const QString &configureScript() const { return m_configureScript; } + const QVariantMap &properties() const { return m_properties; } + const QVariantMap &initialProperties() const { return m_initialProperties; } + const std::vector &importedFilesUsed() const { return m_importedFilesUsed; } + bool needsReconfigure(const FileTime &referenceTime) const; + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(m_globalId, m_location, m_condition, m_configureScript, + m_properties, m_initialProperties, m_importedFilesUsed); + } + +private: + Probe() = default; + Probe(QString globalId, + const CodeLocation &location, + bool condition, + QString configureScript, + QVariantMap properties, + QVariantMap initialProperties, + std::vector importedFilesUsed) + : m_globalId(std::move(globalId)) + , m_location(location) + , m_configureScript(std::move(configureScript)) + , m_properties(std::move(properties)) + , m_initialProperties(std::move(initialProperties)) + , m_importedFilesUsed(std::move(importedFilesUsed)) + , m_condition(condition) + {} + + QString m_globalId; + CodeLocation m_location; + QString m_configureScript; + QVariantMap m_properties; + QVariantMap m_initialProperties; + std::vector m_importedFilesUsed; + bool m_condition = false; +}; + +class RuleArtifact +{ +public: + static RuleArtifactPtr create() { return RuleArtifactPtr(new RuleArtifact); } + + QString filePath; + FileTags fileTags; + bool alwaysUpdated; + CodeLocation location; + CodeLocation filePathLocation; + + class Binding + { + public: + QStringList name; + QString code; + CodeLocation location; + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(name, code, location); + } + + bool operator<(const Binding &other) const + { + if (name == other.name) { + if (code == other.code) + return location < other.location; + return code < other.code; + } + return name < other.name; + } + }; + + std::vector bindings; + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(filePath, fileTags, alwaysUpdated, location, filePathLocation, + bindings); + } + +private: + RuleArtifact() + : alwaysUpdated(true) + {} +}; +uint qHash(const RuleArtifact::Binding &b); +bool operator==(const RuleArtifact::Binding &b1, const RuleArtifact::Binding &b2); +inline bool operator!=(const RuleArtifact::Binding &b1, const RuleArtifact::Binding &b2) { + return !(b1 == b2); +} +bool operator==(const RuleArtifact &a1, const RuleArtifact &a2); +inline bool operator!=(const RuleArtifact &a1, const RuleArtifact &a2) { return !(a1 == a2); } + +class SourceArtifactInternal +{ +public: + static SourceArtifactPtr create() { return SourceArtifactPtr(new SourceArtifactInternal); } + + bool isTargetOfModule() const { return !targetOfModule.isEmpty(); } + + QString absoluteFilePath; + FileTags fileTags; + bool overrideFileTags; + QString targetOfModule; + PropertyMapPtr properties; + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(absoluteFilePath, fileTags, overrideFileTags, properties, + targetOfModule); + } + +private: + SourceArtifactInternal() : overrideFileTags(true) {} +}; +bool operator==(const SourceArtifactInternal &sa1, const SourceArtifactInternal &sa2); +inline bool operator!=(const SourceArtifactInternal &sa1, const SourceArtifactInternal &sa2) { + return !(sa1 == sa2); +} + +class SourceWildCards +{ +public: + Set expandPatterns(const GroupConstPtr &group, const QString &baseDir, + const QString &buildDir); + + const ResolvedGroup *group = nullptr; // The owning group. + QStringList patterns; + QStringList excludePatterns; + std::vector> dirTimeStamps; + std::vector files; + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(patterns, excludePatterns, dirTimeStamps, files); + } + +private: + Set expandPatterns(const GroupConstPtr &group, const QStringList &patterns, + const QString &baseDir, const QString &buildDir); + void expandPatterns(Set &result, const GroupConstPtr &group, + const QStringList &parts, const QString &baseDir, + const QString &buildDir); +}; + +class QBS_AUTOTEST_EXPORT ResolvedGroup +{ +public: + static GroupPtr create() { return GroupPtr(new ResolvedGroup); } + + CodeLocation location; + + QString name; + bool enabled = true; + QString prefix; + std::vector files; + std::unique_ptr wildcards; + PropertyMapPtr properties; + FileTags fileTags; + QString targetOfModule; + bool overrideTags = false; + + std::vector allFiles() const; + + void load(PersistentPool &pool); + void store(PersistentPool &pool); + +private: + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(name, enabled, location, prefix, files, wildcards, properties, + fileTags, targetOfModule, overrideTags); + } +}; + +class ScriptFunction +{ +public: + static ScriptFunctionPtr create() { return ScriptFunctionPtr(new ScriptFunction); } + + ~ScriptFunction(); + + QString sourceCode; + CodeLocation location; + ResolvedFileContextConstPtr fileContext; + + bool isValid() const; + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(sourceCode, location, fileContext); + } + +private: + ScriptFunction(); +}; + +bool operator==(const ScriptFunction &a, const ScriptFunction &b); +inline bool operator!=(const ScriptFunction &a, const ScriptFunction &b) { return !(a == b); } + +bool operator==(const PrivateScriptFunction &a, const PrivateScriptFunction &b); + +class PrivateScriptFunction +{ + friend bool operator==(const PrivateScriptFunction &a, const PrivateScriptFunction &b); +public: + void initialize(const ScriptFunctionPtr &sharedData) { m_sharedData = sharedData; } + mutable QScriptValue scriptFunction; // not stored + + QString &sourceCode() const { return m_sharedData->sourceCode; } + CodeLocation &location() const { return m_sharedData->location; } + ResolvedFileContextConstPtr &fileContext() const { return m_sharedData->fileContext; } + bool isValid() const { return m_sharedData->isValid(); } + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(m_sharedData); + } + +private: + ScriptFunctionPtr m_sharedData; +}; + +bool operator==(const PrivateScriptFunction &a, const PrivateScriptFunction &b); +inline bool operator!=(const PrivateScriptFunction &a, const PrivateScriptFunction &b) +{ + return !(a == b); +} + +class ResolvedModule +{ +public: + static ResolvedModulePtr create() { return ResolvedModulePtr(new ResolvedModule); } + + QString name; + QStringList moduleDependencies; + PrivateScriptFunction setupBuildEnvironmentScript; + PrivateScriptFunction setupRunEnvironmentScript; + ResolvedProduct *product = nullptr; + bool isProduct = false; + + static QStringList argumentNamesForSetupBuildEnv(); + static QStringList argumentNamesForSetupRunEnv(); + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(name, moduleDependencies, setupBuildEnvironmentScript, + setupRunEnvironmentScript, isProduct); + } + +private: + ResolvedModule() = default; +}; +bool operator==(const ResolvedModule &m1, const ResolvedModule &m2); +inline bool operator!=(const ResolvedModule &m1, const ResolvedModule &m2) { return !(m1 == m2); } + +/** + * Per default each rule is a "non-multiplex rule". + * + * A "multiplex rule" creates one transformer that takes all + * input artifacts with the matching input file tag and creates + * one or more artifacts. (e.g. linker rule) + * + * A "non-multiplex rule" creates one transformer per matching input file. + */ +class Rule +{ +public: + static RulePtr create() { return RulePtr(new Rule); } + RulePtr clone() const; + + ResolvedProduct *product = nullptr; // The owning product. + ResolvedModuleConstPtr module; + QString name; + PrivateScriptFunction prepareScript; + FileTags outputFileTags; // unused, if artifacts is non-empty + PrivateScriptFunction outputArtifactsScript; // unused, if artifacts is non-empty + FileTags inputs; + FileTags auxiliaryInputs; + FileTags excludedInputs; + FileTags inputsFromDependencies; + FileTags explicitlyDependsOn; + FileTags explicitlyDependsOnFromDependencies; + bool multiplex = false; + bool requiresInputs = false; + std::vector artifacts; // unused, if outputFileTags/outputArtifactsScript is non-empty + bool alwaysRun = false; + + // members that we don't need to save + int ruleGraphId = -1; + + static QStringList argumentNamesForOutputArtifacts(); + static QStringList argumentNamesForPrepare(); + + QString toString() const; + FileTags staticOutputFileTags() const; + FileTags collectedOutputFileTags() const; + bool isDynamic() const; + bool declaresInputs() const; + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(name, prepareScript, outputArtifactsScript, module, inputs, + outputFileTags, auxiliaryInputs, excludedInputs, + inputsFromDependencies, explicitlyDependsOn, + explicitlyDependsOnFromDependencies, multiplex, + requiresInputs, alwaysRun, artifacts); + } +private: + Rule() = default; +}; +bool operator==(const Rule &r1, const Rule &r2); +inline bool operator!=(const Rule &r1, const Rule &r2) { return !(r1 == r2); } +bool ruleListsAreEqual(const std::vector &l1, const std::vector &l2); + +class ResolvedScanner +{ +public: + static ResolvedScannerPtr create() { return ResolvedScannerPtr(new ResolvedScanner); } + + ResolvedModuleConstPtr module; + FileTags inputs; + bool recursive; + PrivateScriptFunction searchPathsScript; + PrivateScriptFunction scanScript; + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(module, inputs, recursive, searchPathsScript, scanScript); + } + +private: + ResolvedScanner() : + recursive(false) + {} +}; + +class ExportedProperty +{ +public: + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(fullName, type, sourceCode, isBuiltin); + } + + QString fullName; + PropertyDeclaration::Type type = PropertyDeclaration::Type::UnknownType; + QString sourceCode; + bool isBuiltin = false; +}; + +bool operator==(const ExportedProperty &p1, const ExportedProperty &p2); +inline bool operator!=(const ExportedProperty &p1, const ExportedProperty &p2) +{ + return !(p1 == p2); +} + +class ExportedItem +{ +public: + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(name, properties, children); + } + + static ExportedItemPtr create() { return std::make_shared(); } + + QString name; + std::vector properties; + std::vector children; +}; + +bool equals(const std::vector &l1, const std::vector &l2); +bool operator==(const ExportedItem &i1, const ExportedItem &i2); +inline bool operator!=(const ExportedItem &i1, const ExportedItem &i2) { return !(i1 == i2); } + +class ExportedModuleDependency +{ +public: + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(name, moduleProperties); + }; + + QString name; + QVariantMap moduleProperties; +}; + +bool operator==(const ExportedModuleDependency &d1, const ExportedModuleDependency &d2); +inline bool operator!=(const ExportedModuleDependency &d1, const ExportedModuleDependency &d2) +{ + return !(d1 == d2); +} + +class ExportedModule +{ +public: + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(propertyValues, modulePropertyValues, children, + productDependencies, moduleDependencies, m_properties, + dependencyParameters, importStatements); + }; + + QVariantMap propertyValues; + QVariantMap modulePropertyValues; + std::vector children; + std::vector productDependencies; + std::vector moduleDependencies; + std::vector m_properties; + QMap dependencyParameters; + QStringList importStatements; +}; + +bool operator==(const ExportedModule &m1, const ExportedModule &m2); +inline bool operator!=(const ExportedModule &m1, const ExportedModule &m2) { return !(m1 == m2); } + +class TopLevelProject; +class ScriptEngine; + +class QBS_AUTOTEST_EXPORT ResolvedProduct +{ +public: + static ResolvedProductPtr create() { return ResolvedProductPtr(new ResolvedProduct); } + + ~ResolvedProduct(); + + bool enabled; + FileTags fileTags; + QString name; + QString targetName; + QString multiplexConfigurationId; + QString sourceDirectory; + QString destinationDirectory; + CodeLocation location; + WeakPointer project; + QVariantMap productProperties; + PropertyMapPtr moduleProperties; + std::vector rules; + std::vector dependencies; + QHash dependencyParameters; + std::vector fileTaggers; + JobLimits jobLimits; + std::vector modules; + QHash moduleParameters; + std::vector scanners; + std::vector groups; + std::vector probes; + std::vector artifactProperties; + QStringList missingSourceFiles; + std::unique_ptr buildData; + + ExportedModule exportedModule; + + QProcessEnvironment buildEnvironment; // must not be saved + QProcessEnvironment runEnvironment; // must not be saved + + void accept(BuildGraphVisitor *visitor) const; + std::vector allFiles() const; + std::vector allEnabledFiles() const; + FileTags fileTagsForFileName(const QString &fileName) const; + + ArtifactSet lookupArtifactsByFileTag(const FileTag &tag) const; + ArtifactSet lookupArtifactsByFileTags(const FileTags &tags) const; + ArtifactSet targetArtifacts() const; + + TopLevelProject *topLevelProject() const; + + static QString uniqueName(const QString &name, + const QString &multiplexConfigurationId); + QString uniqueName() const; + static QString fullDisplayName(const QString &name, const QString &multiplexConfigurationId); + QString fullDisplayName() const; + QString profile() const; + + QStringList generatedFiles(const QString &baseFile, bool recursive, const FileTags &tags) const; + + static QString deriveBuildDirectoryName(const QString &name, + const QString &multiplexConfigurationId); + QString buildDirectory() const; + + bool isInParentProject(const ResolvedProductConstPtr &other) const; + bool builtByDefault() const; + + void cacheExecutablePath(const QString &origFilePath, const QString &fullFilePath); + QString cachedExecutablePath(const QString &origFilePath) const; + + void load(PersistentPool &pool); + void store(PersistentPool &pool); + +private: + ResolvedProduct(); + + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(enabled, fileTags, name, multiplexConfigurationId, + targetName, sourceDirectory, destinationDirectory, + missingSourceFiles, location, productProperties, + moduleProperties, rules, dependencies, dependencyParameters, + fileTaggers, modules, moduleParameters, scanners, groups, + artifactProperties, probes, exportedModule, buildData, + jobLimits); + } + + QHash m_executablePathCache; + mutable std::mutex m_executablePathCacheLock; +}; + +class QBS_AUTOTEST_EXPORT ResolvedProject +{ +public: + virtual ~ResolvedProject(); + static ResolvedProjectPtr create() { return ResolvedProjectPtr(new ResolvedProject); } + + QString name; + CodeLocation location; + bool enabled; + std::vector products; + std::vector subProjects; + WeakPointer parentProject; + + void accept(BuildGraphVisitor *visitor) const; + + void setProjectProperties(const QVariantMap &config) { m_projectProperties = config; } + const QVariantMap &projectProperties() const { return m_projectProperties; } + + TopLevelProject *topLevelProject(); + std::vector allSubProjects() const; + std::vector allProducts() const; + + virtual void load(PersistentPool &pool); + virtual void store(PersistentPool &pool); + +protected: + ResolvedProject(); + +private: + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(name, location, enabled, products, subProjects, + m_projectProperties); + } + + QVariantMap m_projectProperties; + TopLevelProject *m_topLevelProject; +}; + +class QBS_AUTOTEST_EXPORT TopLevelProject : public ResolvedProject +{ + friend class BuildGraphLoader; +public: + ~TopLevelProject() override; + + static TopLevelProjectPtr create() { return TopLevelProjectPtr(new TopLevelProject); } + + static QString deriveId(const QVariantMap &config); + static QString deriveBuildDirectory(const QString &buildRoot, const QString &id); + + QString buildDirectory; // Not saved + QProcessEnvironment environment; + std::vector probes; + ModuleProviderInfoList moduleProviderInfo; + + QHash canonicalFilePathResults; // Results of calls to "File.canonicalFilePath()." + QHash fileExistsResults; // Results of calls to "File.exists()". + QHash, QStringList> directoryEntriesResults; // Results of calls to "File.directoryEntries()". + QHash fileLastModifiedResults; // Results of calls to "File.lastModified()". + std::unique_ptr buildData; + BuildGraphLocker *bgLocker; // This holds the system-wide build graph file lock. + bool locked; // This is the API-level lock for the project instance. + + Set buildSystemFiles; + FileTime lastStartResolveTime; + FileTime lastEndResolveTime; + QList warningsEncountered; + + void setBuildConfiguration(const QVariantMap &config); + const QVariantMap &buildConfiguration() const { return m_buildConfiguration; } + QString id() const { return m_id; } + QString profile() const; + void makeModuleProvidersNonTransient(); + + QVariantMap profileConfigs; + QVariantMap overriddenValues; + + QString buildGraphFilePath() const; + void store(Logger logger); + +private: + TopLevelProject(); + + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(m_id, canonicalFilePathResults, fileExistsResults, + directoryEntriesResults, fileLastModifiedResults, environment, + probes, profileConfigs, overriddenValues, buildSystemFiles, + lastStartResolveTime, lastEndResolveTime, warningsEncountered, + buildData, moduleProviderInfo); + } + void load(PersistentPool &pool) override; + void store(PersistentPool &pool) override; + + void cleanupModuleProviderOutput(); + + QString m_id; + QVariantMap m_buildConfiguration; +}; + +bool artifactPropertyListsAreEqual(const std::vector &l1, + const std::vector &l2); + +QString multiplexIdToString(const QString &id); + +} // namespace Internal +} // namespace qbs + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(qbs::Internal::JsImport, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(qbs::Internal::RuleArtifact::Binding, Q_MOVABLE_TYPE); +QT_END_NAMESPACE + +#endif // QBS_LANGUAGE_H diff --git a/src/lib/corelib/language/language.pri b/src/lib/corelib/language/language.pri new file mode 100644 index 00000000..e07a671b --- /dev/null +++ b/src/lib/corelib/language/language.pri @@ -0,0 +1,82 @@ +include(../../../install_prefix.pri) + +HEADERS += \ + $$PWD/artifactproperties.h \ + $$PWD/astimportshandler.h \ + $$PWD/astpropertiesitemhandler.h \ + $$PWD/asttools.h \ + $$PWD/builtindeclarations.h \ + $$PWD/deprecationinfo.h \ + $$PWD/evaluationdata.h \ + $$PWD/evaluator.h \ + $$PWD/evaluatorscriptclass.h \ + $$PWD/filecontext.h \ + $$PWD/filecontextbase.h \ + $$PWD/filetags.h \ + $$PWD/forward_decls.h \ + $$PWD/identifiersearch.h \ + $$PWD/item.h \ + $$PWD/itemdeclaration.h \ + $$PWD/itemobserver.h \ + $$PWD/itempool.h \ + $$PWD/itemreader.h \ + $$PWD/itemreaderastvisitor.h \ + $$PWD/itemreadervisitorstate.h \ + $$PWD/itemtype.h \ + $$PWD/jsimports.h \ + $$PWD/language.h \ + $$PWD/loader.h \ + $$PWD/moduleloader.h \ + $$PWD/modulemerger.h \ + $$PWD/moduleproviderinfo.h \ + $$PWD/preparescriptobserver.h \ + $$PWD/projectresolver.h \ + $$PWD/property.h \ + $$PWD/propertydeclaration.h \ + $$PWD/propertymapinternal.h \ + $$PWD/qualifiedid.h \ + $$PWD/resolvedfilecontext.h \ + $$PWD/scriptengine.h \ + $$PWD/scriptimporter.h \ + $$PWD/scriptpropertyobserver.h \ + $$PWD/value.h + +SOURCES += \ + $$PWD/artifactproperties.cpp \ + $$PWD/astimportshandler.cpp \ + $$PWD/astpropertiesitemhandler.cpp \ + $$PWD/asttools.cpp \ + $$PWD/builtindeclarations.cpp \ + $$PWD/evaluator.cpp \ + $$PWD/evaluatorscriptclass.cpp \ + $$PWD/filecontext.cpp \ + $$PWD/filecontextbase.cpp \ + $$PWD/filetags.cpp \ + $$PWD/identifiersearch.cpp \ + $$PWD/item.cpp \ + $$PWD/itemdeclaration.cpp \ + $$PWD/itempool.cpp \ + $$PWD/itemreader.cpp \ + $$PWD/itemreaderastvisitor.cpp \ + $$PWD/itemreadervisitorstate.cpp \ + $$PWD/language.cpp \ + $$PWD/loader.cpp \ + $$PWD/moduleloader.cpp \ + $$PWD/modulemerger.cpp \ + $$PWD/preparescriptobserver.cpp \ + $$PWD/scriptpropertyobserver.cpp \ + $$PWD/projectresolver.cpp \ + $$PWD/property.cpp \ + $$PWD/propertydeclaration.cpp \ + $$PWD/propertymapinternal.cpp \ + $$PWD/qualifiedid.cpp \ + $$PWD/resolvedfilecontext.cpp \ + $$PWD/scriptengine.cpp \ + $$PWD/scriptimporter.cpp \ + $$PWD/value.cpp + +!qbs_no_dev_install { + language_headers.files = $$PWD/forward_decls.h + language_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/language + INSTALLS += language_headers +} diff --git a/src/lib/corelib/language/loader.cpp b/src/lib/corelib/language/loader.cpp new file mode 100644 index 00000000..f248fbb1 --- /dev/null +++ b/src/lib/corelib/language/loader.cpp @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** 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 "loader.h" + +#include "evaluator.h" +#include "language.h" +#include "moduleloader.h" +#include "projectresolver.h" +#include "scriptengine.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +Loader::Loader(ScriptEngine *engine, Logger logger) + : m_logger(std::move(logger)) + , m_progressObserver(nullptr) + , m_engine(engine) +{ + m_logger.storeWarnings(); +} + +void Loader::setProgressObserver(ProgressObserver *observer) +{ + m_progressObserver = observer; +} + +void Loader::setSearchPaths(const QStringList &_searchPaths) +{ + QStringList searchPaths; + for (const QString &searchPath : _searchPaths) { + if (!FileInfo::exists(searchPath)) { + m_logger.qbsWarning() << Tr::tr("Search path '%1' does not exist.") + .arg(QDir::toNativeSeparators(searchPath)); + } else { + searchPaths += searchPath; + } + } + + m_searchPaths = searchPaths; +} + +void Loader::setOldProjectProbes(const std::vector &oldProbes) +{ + m_oldProjectProbes = oldProbes; +} + +void Loader::setOldProductProbes(const QHash> &oldProbes) +{ + m_oldProductProbes = oldProbes; +} + +void Loader::setStoredProfiles(const QVariantMap &profiles) +{ + m_storedProfiles = profiles; +} + +void Loader::setStoredModuleProviderInfo(const ModuleProviderInfoList &providerInfo) +{ + m_storedModuleProviderInfo = providerInfo; +} + +TopLevelProjectPtr Loader::loadProject(const SetupProjectParameters &_parameters) +{ + SetupProjectParameters parameters = _parameters; + + if (parameters.topLevelProfile().isEmpty()) { + Settings settings(parameters.settingsDirectory()); + QString profileName = settings.defaultProfile(); + if (profileName.isEmpty()) { + m_logger.qbsDebug() << Tr::tr("No profile specified and no default profile exists. " + "Using default property values."); + profileName = Profile::fallbackName(); + } + parameters.setTopLevelProfile(profileName); + parameters.expandBuildConfiguration(); + } + + setupProjectFilePath(parameters); + QBS_CHECK(QFileInfo(parameters.projectFilePath()).isAbsolute()); + m_logger.qbsDebug() << "Using project file '" + << QDir::toNativeSeparators(parameters.projectFilePath()) << "'."; + + m_engine->setEnvironment(parameters.adjustedEnvironment()); + m_engine->clearExceptions(); + m_engine->clearImportsCache(); + m_engine->clearRequestedProperties(); + m_engine->enableProfiling(parameters.logElapsedTime()); + m_logger.clearWarnings(); + EvalContextSwitcher evalContextSwitcher(m_engine, EvalContext::PropertyEvaluation); + + QTimer cancelationTimer; + + // At this point, we cannot set a sensible total effort, because we know nothing about + // the project yet. That's why we use a placeholder here, so the user at least + // sees that an operation is starting. The real total effort will be set later when + // we have enough information. + if (m_progressObserver) { + m_progressObserver->initialize(Tr::tr("Resolving project for configuration %1") + .arg(TopLevelProject::deriveId(parameters.finalBuildConfigurationTree())), 1); + cancelationTimer.setSingleShot(false); + QObject::connect(&cancelationTimer, &QTimer::timeout, [this]() { + QBS_ASSERT(m_progressObserver, return); + if (m_progressObserver->canceled()) + m_engine->cancel(); + }); + cancelationTimer.start(1000); + } + + const FileTime resolveTime = FileTime::currentTime(); + Evaluator evaluator(m_engine); + ModuleLoader moduleLoader(&evaluator, m_logger); + moduleLoader.setProgressObserver(m_progressObserver); + moduleLoader.setSearchPaths(m_searchPaths); + moduleLoader.setOldProjectProbes(m_oldProjectProbes); + moduleLoader.setOldProductProbes(m_oldProductProbes); + moduleLoader.setLastResolveTime(m_lastResolveTime); + moduleLoader.setStoredProfiles(m_storedProfiles); + moduleLoader.setStoredModuleProviderInfo(m_storedModuleProviderInfo); + const ModuleLoaderResult loadResult = moduleLoader.load(parameters); + ProjectResolver resolver(&evaluator, loadResult, std::move(parameters), m_logger); + resolver.setProgressObserver(m_progressObserver); + const TopLevelProjectPtr project = resolver.resolve(); + project->lastStartResolveTime = resolveTime; + project->lastEndResolveTime = FileTime::currentTime(); + + // E.g. if the top-level project is disabled. + if (m_progressObserver) + m_progressObserver->setFinished(); + + return project; +} + +void Loader::setupProjectFilePath(SetupProjectParameters ¶meters) +{ + QString projectFilePath = parameters.projectFilePath(); + if (projectFilePath.isEmpty()) + projectFilePath = QDir::currentPath(); + const QFileInfo projectFileInfo(projectFilePath); + if (!projectFileInfo.exists()) + throw ErrorInfo(Tr::tr("Project file '%1' cannot be found.").arg(projectFilePath)); + if (projectFileInfo.isRelative()) + projectFilePath = projectFileInfo.absoluteFilePath(); + if (projectFileInfo.isFile()) { + parameters.setProjectFilePath(projectFilePath); + return; + } + if (!projectFileInfo.isDir()) + throw ErrorInfo(Tr::tr("Project file '%1' has invalid type.").arg(projectFilePath)); + + const QStringList &actualFileNames + = QDir(projectFilePath).entryList(StringConstants::qbsFileWildcards(), QDir::Files); + if (actualFileNames.empty()) { + QString error; + if (parameters.projectFilePath().isEmpty()) + error = Tr::tr("No project file given and none found in current directory.\n"); + else + error = Tr::tr("No project file found in directory '%1'.").arg(projectFilePath); + throw ErrorInfo(error); + } + if (actualFileNames.size() > 1) { + throw ErrorInfo(Tr::tr("More than one project file found in directory '%1'.") + .arg(projectFilePath)); + } + projectFilePath.append(QLatin1Char('/')).append(actualFileNames.front()); + + projectFilePath = QDir::current().filePath(projectFilePath); + projectFilePath = QDir::cleanPath(projectFilePath); + parameters.setProjectFilePath(projectFilePath); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/loader.h b/src/lib/corelib/language/loader.h new file mode 100644 index 00000000..d172a74e --- /dev/null +++ b/src/lib/corelib/language/loader.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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 QBS_LOADER_H +#define QBS_LOADER_H + +#include "forward_decls.h" +#include "moduleproviderinfo.h" +#include +#include + +#include + +namespace qbs { +class Settings; +class SetupProjectParameters; +namespace Internal { +class Logger; +class ProgressObserver; +class ScriptEngine; + +class QBS_AUTOTEST_EXPORT Loader +{ +public: + Loader(ScriptEngine *engine, Logger logger); + + void setProgressObserver(ProgressObserver *observer); + void setSearchPaths(const QStringList &searchPaths); + void setOldProjectProbes(const std::vector &oldProbes); + void setOldProductProbes(const QHash> &oldProbes); + void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; } + void setStoredProfiles(const QVariantMap &profiles); + void setStoredModuleProviderInfo(const ModuleProviderInfoList &providerInfo); + TopLevelProjectPtr loadProject(const SetupProjectParameters ¶meters); + + static void setupProjectFilePath(SetupProjectParameters ¶meters); + +private: + Logger m_logger; + ProgressObserver *m_progressObserver; + ScriptEngine * const m_engine; + QStringList m_searchPaths; + std::vector m_oldProjectProbes; + QHash> m_oldProductProbes; + ModuleProviderInfoList m_storedModuleProviderInfo; + QVariantMap m_storedProfiles; + FileTime m_lastResolveTime; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_LOADER_H diff --git a/src/lib/corelib/language/moduleloader.cpp b/src/lib/corelib/language/moduleloader.cpp new file mode 100644 index 00000000..1c331c60 --- /dev/null +++ b/src/lib/corelib/language/moduleloader.cpp @@ -0,0 +1,4271 @@ +/**************************************************************************** +** +** 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 "moduleloader.h" + +#include "builtindeclarations.h" +#include "evaluator.h" +#include "filecontext.h" +#include "item.h" +#include "itemreader.h" +#include "language.h" +#include "modulemerger.h" +#include "qualifiedid.h" +#include "scriptengine.h" +#include "value.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +using MultiplexConfigurationByIdTable = QThreadStorage >; +Q_GLOBAL_STATIC(MultiplexConfigurationByIdTable, multiplexConfigurationsById); + +static void handlePropertyError(const ErrorInfo &error, const SetupProjectParameters ¶ms, + Logger &logger) +{ + if (params.propertyCheckingMode() == ErrorHandlingMode::Strict) + throw error; + logger.printWarning(error); +} + +static bool multiplexConfigurationIntersects(const QVariantMap &lhs, const QVariantMap &rhs) +{ + QBS_CHECK(!lhs.isEmpty() && !rhs.isEmpty()); + + for (auto lhsProperty = lhs.constBegin(); lhsProperty != lhs.constEnd(); lhsProperty++) { + const auto rhsProperty = rhs.find(lhsProperty.key()); + const bool isCommonProperty = rhsProperty != rhs.constEnd(); + if (isCommonProperty && lhsProperty.value() != rhsProperty.value()) + return false; + } + + return true; +} + +class ModuleLoader::ItemModuleList : public QList {}; + +static QString probeGlobalId(Item *probe) +{ + QString id; + + for (Item *obj = probe; obj; obj = obj->prototype()) { + if (!obj->id().isEmpty()) { + id = obj->id(); + break; + } + } + + if (id.isEmpty()) + return {}; + + QBS_CHECK(probe->file()); + return id + QLatin1Char('_') + probe->file()->filePath(); +} + +class ModuleLoader::ProductSortByDependencies +{ +public: + ProductSortByDependencies(TopLevelProjectContext &tlp) : m_tlp(tlp) + { + } + + void apply() + { + QHash> productsMap; + QList allProducts; + for (ProjectContext * const projectContext : qAsConst(m_tlp.projects)) { + for (auto &product : projectContext->products) { + allProducts.push_back(&product); + productsMap[product.name].push_back(&product); + } + } + Set allDependencies; + for (auto productContext : qAsConst(allProducts)) { + auto &productDependencies = m_dependencyMap[productContext]; + for (const auto &dep : qAsConst(productContext->info.usedProducts)) { + QBS_CHECK(!dep.name.isEmpty()); + const auto &deps = productsMap.value(dep.name); + if (dep.profile == StringConstants::star()) { + QBS_CHECK(!deps.empty()); + for (ProductContext *depProduct : deps) { + if (depProduct == productContext) + continue; + productDependencies.push_back(depProduct); + allDependencies << depProduct; + } + } else { + auto it = std::find_if(deps.begin(), deps.end(), [&dep] (ProductContext *p) { + return p->multiplexConfigurationId == dep.multiplexConfigurationId; + }); + if (it == deps.end()) { + QBS_CHECK(!productContext->multiplexConfigurationId.isEmpty()); + 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; + } + } + } + const Set rootProducts + = Set::fromList(allProducts) - allDependencies; + for (ProductContext * const rootProduct : rootProducts) + traverse(rootProduct); + if (m_sortedProducts.size() < allProducts.size()) { + for (auto const product : qAsConst(allProducts)) { + QList path; + findCycle(product, path); + } + } + QBS_CHECK(m_sortedProducts.size() == allProducts.size()); + } + + // No product at position i has dependencies to a product at position j > i. + const QList &sortedProducts() const + { + return m_sortedProducts; + } + +private: + void traverse(ModuleLoader::ProductContext *product) + { + if (!m_seenProducts.insert(product).second) + return; + for (const auto &dependency : m_dependencyMap.value(product)) + traverse(dependency); + m_sortedProducts << product; + } + + void findCycle(ModuleLoader::ProductContext *product, + QList &path) + { + if (path.contains(product)) { + ErrorInfo error(Tr::tr("Cyclic dependencies detected.")); + for (const auto * const p : path) + error.append(p->name, p->item->location()); + error.append(product->name, product->item->location()); + throw error; + } + path << product; + for (auto const dep : m_dependencyMap.value(product)) + findCycle(dep, path); + path.removeLast(); + } + + TopLevelProjectContext &m_tlp; + QHash> m_dependencyMap; + Set m_seenProducts; + QList m_sortedProducts; +}; + +class SearchPathsManager { +public: + SearchPathsManager(ItemReader *itemReader, const QStringList &extraSearchPaths) + : m_itemReader(itemReader) + { + m_itemReader->pushExtraSearchPaths(extraSearchPaths); + } + ~SearchPathsManager() { m_itemReader->popExtraSearchPaths(); } + +private: + ItemReader * const m_itemReader; +}; + +ModuleLoader::ModuleLoader(Evaluator *evaluator, Logger &logger) + : m_pool(nullptr) + , m_logger(logger) + , m_progressObserver(nullptr) + , m_reader(new ItemReader(logger)) + , m_evaluator(evaluator) +{ +} + +ModuleLoader::~ModuleLoader() +{ + delete m_reader; +} + +void ModuleLoader::setProgressObserver(ProgressObserver *progressObserver) +{ + m_progressObserver = progressObserver; +} + +void ModuleLoader::setSearchPaths(const QStringList &searchPaths) +{ + m_reader->setSearchPaths(searchPaths); + qCDebug(lcModuleLoader) << "initial search paths:" << searchPaths; +} + +void ModuleLoader::setOldProjectProbes(const std::vector &oldProbes) +{ + m_oldProjectProbes.clear(); + for (const ProbeConstPtr& probe : oldProbes) + m_oldProjectProbes[probe->globalId()] << probe; +} + +void ModuleLoader::setOldProductProbes(const QHash> &oldProbes) +{ + m_oldProductProbes = oldProbes; +} + +void ModuleLoader::setStoredProfiles(const QVariantMap &profiles) +{ + m_storedProfiles = profiles; +} + +void ModuleLoader::setStoredModuleProviderInfo(const ModuleProviderInfoList &moduleProviderInfo) +{ + m_moduleProviderInfo = moduleProviderInfo; +} + +ModuleLoaderResult ModuleLoader::load(const SetupProjectParameters ¶meters) +{ + TimedActivityLogger moduleLoaderTimer(m_logger, Tr::tr("ModuleLoader"), + parameters.logElapsedTime()); + qCDebug(lcModuleLoader) << "load" << parameters.projectFilePath(); + m_parameters = parameters; + m_modulePrototypes.clear(); + m_modulePrototypeEnabledInfo.clear(); + m_parameterDeclarations.clear(); + m_disabledItems.clear(); + m_reader->clearExtraSearchPathsStack(); + m_reader->setEnableTiming(parameters.logElapsedTime()); + m_elapsedTimeProbes = m_elapsedTimePrepareProducts = m_elapsedTimeHandleProducts + = m_elapsedTimeProductDependencies = m_elapsedTimeTransitiveDependencies + = m_elapsedTimePropertyChecking = 0; + m_elapsedTimeProbes = 0; + m_probesEncountered = m_probesRun = m_probesCachedCurrent = m_probesCachedOld = 0; + m_settings = std::make_unique(parameters.settingsDirectory()); + + const auto keys = m_parameters.overriddenValues().keys(); + for (const QString &key : keys) { + static const QStringList prefixes({ StringConstants::projectPrefix(), + QStringLiteral("projects"), + QStringLiteral("products"), QStringLiteral("modules"), + StringConstants::moduleProviders(), + StringConstants::qbsModule()}); + bool ok = false; + for (const auto &prefix : prefixes) { + if (key.startsWith(prefix + QLatin1Char('.'))) { + ok = true; + break; + } + } + if (ok) { + collectNameFromOverride(key); + continue; + } + ErrorInfo e(Tr::tr("Property override key '%1' not understood.").arg(key)); + e.append(Tr::tr("Please use one of the following:")); + e.append(QLatin1Char('\t') + Tr::tr("projects..:value")); + e.append(QLatin1Char('\t') + Tr::tr("products..:value")); + e.append(QLatin1Char('\t') + Tr::tr("modules..:value")); + e.append(QLatin1Char('\t') + Tr::tr("products..." + ":value")); + e.append(QLatin1Char('\t') + Tr::tr("moduleProviders.." + ":value")); + handlePropertyError(e, m_parameters, m_logger); + } + + ModuleLoaderResult result; + result.profileConfigs = m_storedProfiles; + m_pool = result.itemPool.get(); + m_reader->setPool(m_pool); + + const QStringList topLevelSearchPaths = parameters.finalBuildConfigurationTree() + .value(StringConstants::projectPrefix()).toMap() + .value(StringConstants::qbsSearchPathsProperty()).toStringList(); + Item *root; + { + SearchPathsManager searchPathsManager(m_reader, topLevelSearchPaths); + root = loadItemFromFile(parameters.projectFilePath(), CodeLocation()); + if (!root) + return ModuleLoaderResult(); + } + + switch (root->type()) { + case ItemType::Product: + root = wrapInProjectIfNecessary(root); + break; + case ItemType::Project: + break; + default: + throw ErrorInfo(Tr::tr("The top-level item must be of type 'Project' or 'Product', but it" + " is of type '%1'.").arg(root->typeName()), root->location()); + } + + const QString buildDirectory = TopLevelProject::deriveBuildDirectory(parameters.buildRoot(), + TopLevelProject::deriveId(parameters.finalBuildConfigurationTree())); + root->setProperty(StringConstants::sourceDirectoryProperty(), + VariantValue::create(QFileInfo(root->file()->filePath()).absolutePath())); + root->setProperty(StringConstants::buildDirectoryProperty(), + VariantValue::create(buildDirectory)); + root->setProperty(StringConstants::profileProperty(), + VariantValue::create(m_parameters.topLevelProfile())); + handleTopLevelProject(&result, root, buildDirectory, + Set() << QDir::cleanPath(parameters.projectFilePath())); + result.root = root; + result.qbsFiles = m_reader->filesRead() - m_tempQbsFiles; + for (auto it = m_localProfiles.cbegin(); it != m_localProfiles.cend(); ++it) + result.profileConfigs.remove(it.key()); + printProfilingInfo(); + return result; +} + +class PropertyDeclarationCheck : public ValueHandler +{ + const Set &m_disabledItems; + Set m_handledItems; + std::vector m_parentItems; + Item *m_currentModuleInstance = nullptr; + QualifiedId m_currentModuleName; + QString m_currentName; + SetupProjectParameters m_params; + Logger &m_logger; +public: + PropertyDeclarationCheck(const Set &disabledItems, + SetupProjectParameters params, Logger &logger) + : m_disabledItems(disabledItems) + , m_params(std::move(params)) + , m_logger(logger) + { + } + + void operator()(Item *item) + { + handleItem(item); + } + +private: + void handle(JSSourceValue *value) override + { + if (!value->createdByPropertiesBlock()) { + const ErrorInfo error(Tr::tr("Property '%1' is not declared.") + .arg(m_currentName), value->location()); + handlePropertyError(error, m_params, m_logger); + } + } + + void handle(ItemValue *value) override + { + if (checkItemValue(value)) + handleItem(value->item()); + } + + bool checkItemValue(ItemValue *value) + { + // TODO: Remove once QBS-1030 is fixed. + if (parentItem()->type() == ItemType::Artifact) + return false; + + if (parentItem()->type() == ItemType::Properties) + return false; + + if (parentItem()->isOfTypeOrhasParentOfType(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 hasCurrentModuleName = [this](const Item::Module &m) { + return m.name == m_currentModuleName; + }; + if (any_of(m_currentModuleInstance->modules(), hasCurrentModuleName)) + return true; + } + + // TODO: We really should have a dedicated item type for "pre-instantiated" item values + // and only use ModuleInstance for actual module instances. + const bool itemIsModuleInstance = value->item()->type() == ItemType::ModuleInstance + && value->item()->hasProperty(StringConstants::presentProperty()); + + if (!itemIsModuleInstance + && value->item()->type() != ItemType::ModulePrefix + && (!parentItem()->file() || !parentItem()->file()->idScope() + || !parentItem()->file()->idScope()->hasProperty(m_currentName)) + && !value->createdByPropertiesBlock()) { + CodeLocation location = value->location(); + for (int i = int(m_parentItems.size() - 1); !location.isValid() && i >= 0; --i) + location = m_parentItems.at(i)->location(); + const ErrorInfo error(Tr::tr("Item '%1' is not declared. " + "Did you forget to add a Depends item?") + .arg(m_currentModuleName.toString()), location); + handlePropertyError(error, m_params, m_logger); + return false; + } + + return true; + } + + void handleItem(Item *item) + { + if (!m_handledItems.insert(item).second) + return; + if (m_disabledItems.contains(item) + || (item->type() == ItemType::ModuleInstance && !item->isPresentModule()) + || item->type() == ItemType::Properties + + // The Properties child of a SubProject item is not a regular item. + || item->type() == ItemType::PropertiesInSubProject) { + return; + } + + // If a module was found but its validate script failed, only the canonical + // module instance will have the "non-present" flag set, so we need to locate it. + if (item->type() == ItemType::ModuleInstance) { + const Item *productItem = nullptr; + for (auto it = m_parentItems.rbegin(); it != m_parentItems.rend(); ++it) { + if ((*it)->type() == ItemType::Product) { + productItem = *it; + break; + } + } + if (productItem) { + for (const Item::Module &m : productItem->modules()) { + if (m.name == m_currentModuleName) { + if (!m.item->isPresentModule()) + return; + break; + } + } + } + } + + m_parentItems.push_back(item); + for (Item::PropertyMap::const_iterator it = item->properties().constBegin(); + it != item->properties().constEnd(); ++it) { + if (item->type() == ItemType::Product && it.key() == StringConstants::moduleProviders() + && it.value()->type() == Value::ItemValueType) + continue; + const PropertyDeclaration decl = item->propertyDeclaration(it.key()); + if (decl.isValid()) { + if (!decl.isDeprecated()) + continue; + const DeprecationInfo &di = decl.deprecationInfo(); + QString message; + bool warningOnly; + if (decl.isExpired()) { + message = Tr::tr("The property '%1' can no longer be used. " + "It was removed in Qbs %2.") + .arg(decl.name(), di.removalVersion().toString()); + warningOnly = false; + } else { + message = Tr::tr("The property '%1' is deprecated and will be removed " + "in Qbs %2.").arg(decl.name(), di.removalVersion().toString()); + warningOnly = true; + } + ErrorInfo error(message, it.value()->location()); + if (!di.additionalUserInfo().isEmpty()) + error.append(di.additionalUserInfo()); + if (warningOnly) + m_logger.printWarning(error); + else + handlePropertyError(error, m_params, m_logger); + continue; + } + m_currentName = it.key(); + const QualifiedId oldModuleName = m_currentModuleName; + if (parentItem()->type() != ItemType::ModulePrefix) + m_currentModuleName.clear(); + m_currentModuleName.push_back(m_currentName); + it.value()->apply(this); + m_currentModuleName = oldModuleName; + } + m_parentItems.pop_back(); + for (Item * const child : item->children()) { + switch (child->type()) { + case ItemType::Export: + case ItemType::Depends: + case ItemType::Parameter: + case ItemType::Parameters: + break; + case ItemType::Group: + if (item->type() == ItemType::Module || item->type() == ItemType::ModuleInstance) + break; + Q_FALLTHROUGH(); + default: + handleItem(child); + } + } + + // Properties that don't refer to an existing module with a matching Depends item + // 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()) { + Item *oldInstance = m_currentModuleInstance; + m_currentModuleInstance = item; + handleItem(item->prototype()); + m_currentModuleInstance = oldInstance; + } + } + + void handle(VariantValue *) override { /* only created internally - no need to check */ } + + Item *parentItem() const { return m_parentItems.back(); } +}; + +void ModuleLoader::handleTopLevelProject(ModuleLoaderResult *loadResult, Item *projectItem, + const QString &buildDirectory, const Set &referencedFilePaths) +{ + TopLevelProjectContext tlp; + tlp.buildDirectory = buildDirectory; + handleProject(loadResult, &tlp, projectItem, referencedFilePaths); + checkProjectNamesInOverrides(tlp); + collectProductsByName(tlp); + checkProductNamesInOverrides(); + + adjustDependenciesForMultiplexing(tlp); + + m_dependencyResolvingPass = 1; + for (ProjectContext * const projectContext : qAsConst(tlp.projects)) { + m_reader->setExtraSearchPathsStack(projectContext->searchPathsStack); + for (ProductContext &productContext : projectContext->products) { + try { + setupProductDependencies(&productContext, Set()); + } catch (const ErrorInfo &err) { + if (productContext.name.isEmpty()) + throw err; + handleProductError(err, &productContext); + } + for (std::size_t i = 0; i < productContext.newlyAddedModuleProviderSearchPaths.size(); ++i) + m_reader->popExtraSearchPaths(); + productContext.newlyAddedModuleProviderSearchPaths.clear(); + } + } + if (!m_productsWithDeferredDependsItems.empty() || !m_exportsWithDeferredDependsItems.empty()) { + collectProductsByType(tlp); + m_dependencyResolvingPass = 2; + + // Doing the normalization for the Export items themselves (as opposed to doing it only + // for the corresponding module instances) serves two purposes: + // (1) It makes recursive use of Depends.productTypes via Export items work; otherwise, + // we'd need an additional dependency resolving pass for every export level. + // (2) The "expanded" Depends items are available to the Exporter.qbs module. + for (Item * const exportItem : m_exportsWithDeferredDependsItems) + normalizeDependencies(nullptr, DeferredDependsContext(nullptr, exportItem)); + + for (const auto &deferredDependsData : m_productsWithDeferredDependsItems) { + ProductContext * const productContext = deferredDependsData.first; + m_reader->setExtraSearchPathsStack(productContext->project->searchPathsStack); + try { + setupProductDependencies(productContext, deferredDependsData.second); + } catch (const ErrorInfo &err) { + handleProductError(err, productContext); + } + } + } + + ProductSortByDependencies productSorter(tlp); + productSorter.apply(); + for (ProductContext * const p : productSorter.sortedProducts()) { + try { + handleProduct(p); + if (p->name.startsWith(StringConstants::shadowProductPrefix())) + tlp.probes << p->info.probes; + } catch (const ErrorInfo &err) { + handleProductError(err, p); + } + } + + loadResult->projectProbes = tlp.probes; + loadResult->moduleProviderInfo = m_moduleProviderInfo; + + m_reader->clearExtraSearchPathsStack(); + AccumulatingTimer timer(m_parameters.logElapsedTime() + ? &m_elapsedTimePropertyChecking : nullptr); + PropertyDeclarationCheck check(m_disabledItems, m_parameters, m_logger); + check(projectItem); +} + +void ModuleLoader::handleProject(ModuleLoaderResult *loadResult, + TopLevelProjectContext *topLevelProjectContext, Item *projectItem, + const Set &referencedFilePaths) +{ + QScopedPointer p(new ProjectContext); + auto &projectContext = *p; + projectContext.topLevelProject = topLevelProjectContext; + projectContext.result = loadResult; + ItemValuePtr itemValue = ItemValue::create(projectItem); + projectContext.scope = Item::create(m_pool, ItemType::Scope); + projectContext.scope->setFile(projectItem->file()); + projectContext.scope->setProperty(StringConstants::projectVar(), itemValue); + ProductContext dummyProductContext; + dummyProductContext.project = &projectContext; + dummyProductContext.moduleProperties = m_parameters.finalBuildConfigurationTree(); + projectItem->addModule(loadBaseModule(&dummyProductContext, projectItem)); + overrideItemProperties(projectItem, StringConstants::projectPrefix(), + m_parameters.overriddenValuesTree()); + projectContext.name = m_evaluator->stringValue(projectItem, + StringConstants::nameProperty()); + if (projectContext.name.isEmpty()) { + projectContext.name = FileInfo::baseName(projectItem->location().filePath()); + projectItem->setProperty(StringConstants::nameProperty(), + VariantValue::create(projectContext.name)); + } + overrideItemProperties(projectItem, + StringConstants::projectsOverridePrefix() + projectContext.name, + m_parameters.overriddenValuesTree()); + if (!checkItemCondition(projectItem)) { + m_disabledProjects.insert(projectContext.name); + return; + } + p.take(); + topLevelProjectContext->projects.push_back(&projectContext); + m_reader->pushExtraSearchPaths(readExtraSearchPaths(projectItem) + << projectItem->file()->dirPath()); + projectContext.searchPathsStack = m_reader->extraSearchPathsStack(); + projectContext.item = projectItem; + + const QString minVersionStr + = m_evaluator->stringValue(projectItem, StringConstants::minimumQbsVersionProperty(), + QStringLiteral("1.3.0")); + const Version minVersion = Version::fromString(minVersionStr); + if (!minVersion.isValid()) { + throw ErrorInfo(Tr::tr("The value '%1' of Project.minimumQbsVersion " + "is not a valid version string.").arg(minVersionStr), projectItem->location()); + } + if (!m_qbsVersion.isValid()) + m_qbsVersion = Version::fromString(QLatin1String(QBS_VERSION)); + if (m_qbsVersion < minVersion) { + throw ErrorInfo(Tr::tr("The project requires at least qbs version %1, but " + "this is qbs version %2.").arg(minVersion.toString(), + m_qbsVersion.toString())); + } + + for (Item * const child : projectItem->children()) + child->setScope(projectContext.scope); + + resolveProbes(&dummyProductContext, projectItem); + projectContext.topLevelProject->probes << dummyProductContext.info.probes; + + handleProfileItems(projectItem, &projectContext); + + QList multiplexedProducts; + for (Item * const child : projectItem->children()) { + if (child->type() == ItemType::Product) + multiplexedProducts << multiplexProductItem(&dummyProductContext, child); + } + for (Item * const additionalProductItem : qAsConst(multiplexedProducts)) + Item::addChild(projectItem, additionalProductItem); + + const QList originalChildren = projectItem->children(); + for (Item * const child : originalChildren) { + switch (child->type()) { + case ItemType::Product: + prepareProduct(&projectContext, child); + break; + case ItemType::SubProject: + handleSubProject(&projectContext, child, referencedFilePaths); + break; + case ItemType::Project: + copyProperties(projectItem, child); + handleProject(loadResult, topLevelProjectContext, child, referencedFilePaths); + break; + default: + break; + } + } + + const QStringList refs = m_evaluator->stringListValue( + projectItem, StringConstants::referencesProperty()); + const CodeLocation referencingLocation + = projectItem->property(StringConstants::referencesProperty())->location(); + QList additionalProjectChildren; + for (const QString &filePath : refs) { + try { + additionalProjectChildren << loadReferencedFile(filePath, referencingLocation, + referencedFilePaths, dummyProductContext); + } catch (const ErrorInfo &error) { + if (m_parameters.productErrorMode() == ErrorHandlingMode::Strict) + throw; + m_logger.printWarning(error); + } + } + for (Item * const subItem : qAsConst(additionalProjectChildren)) { + Item::addChild(projectContext.item, subItem); + switch (subItem->type()) { + case ItemType::Product: + prepareProduct(&projectContext, subItem); + break; + case ItemType::Project: + copyProperties(projectItem, subItem); + handleProject(loadResult, topLevelProjectContext, subItem, + Set(referencedFilePaths) << subItem->file()->filePath()); + break; + default: + break; + } + } + m_reader->popExtraSearchPaths(); +} + +QString ModuleLoader::MultiplexInfo::toIdString(size_t row) const +{ + const auto &mprow = table.at(row); + QVariantMap multiplexConfiguration; + for (size_t column = 0; column < mprow.size(); ++column) { + const QString &propertyName = properties.at(column); + const VariantValuePtr &mpvalue = mprow.at(column); + multiplexConfiguration.insert(propertyName, mpvalue->value()); + } + QString id = QString::fromUtf8(QJsonDocument::fromVariant(multiplexConfiguration) + .toJson(QJsonDocument::Compact) + .toBase64()); + // Cache for later use in:multiplexIdToVariantMap() + multiplexConfigurationsById->localData().insert(id, multiplexConfiguration); + return id; +} + +QVariantMap ModuleLoader::MultiplexInfo::multiplexIdToVariantMap(const QString &multiplexId) +{ + if (multiplexId.isEmpty()) + return QVariantMap(); + + QVariantMap result = multiplexConfigurationsById->localData().value(multiplexId); + // We assume that MultiplexInfo::toIdString() has been called for this + // particular multiplex configuration. + QBS_CHECK(!result.isEmpty()); + return result; +} + +void qbs::Internal::ModuleLoader::ModuleLoader::dump(const ModuleLoader::MultiplexInfo &mpi) +{ + QStringList header; + for (const auto &str : mpi.properties) + header << str; + qDebug() << header; + + for (const auto &row : mpi.table) { + QVariantList values; + for (const auto &elem : row) { + values << elem->value(); + } + qDebug() << values; + } +} + +ModuleLoader::MultiplexTable ModuleLoader::combine(const MultiplexTable &table, + const MultiplexRow &values) +{ + MultiplexTable result; + if (table.empty()) { + result.resize(values.size()); + for (size_t i = 0; i < values.size(); ++i) { + MultiplexRow row; + row.resize(1); + row[0] = values.at(i); + result[i] = row; + } + } else { + for (const auto &row : table) { + for (const auto &value : values) { + MultiplexRow newRow = row; + newRow.push_back(value); + result.push_back(newRow); + } + } + } + return result; +} + +ModuleLoader::MultiplexInfo ModuleLoader::extractMultiplexInfo(Item *productItem, + Item *qbsModuleItem) +{ + static const QString mpmKey = QStringLiteral("multiplexMap"); + + const QScriptValue multiplexMap = m_evaluator->value(qbsModuleItem, mpmKey); + const QStringList multiplexByQbsProperties = m_evaluator->stringListValue( + productItem, StringConstants::multiplexByQbsPropertiesProperty()); + + MultiplexInfo multiplexInfo; + multiplexInfo.aggregate = m_evaluator->boolValue( + productItem, StringConstants::aggregateProperty()); + + const QString multiplexedType = m_evaluator->stringValue( + productItem, StringConstants::multiplexedTypeProperty()); + if (!multiplexedType.isEmpty()) + multiplexInfo.multiplexedType = VariantValue::create(multiplexedType); + + Set uniqueMultiplexByQbsProperties; + for (const QString &key : multiplexByQbsProperties) { + const QString mappedKey = multiplexMap.property(key).toString(); + if (mappedKey.isEmpty()) + throw ErrorInfo(Tr::tr("There is no entry for '%1' in 'qbs.multiplexMap'.").arg(key)); + + if (!uniqueMultiplexByQbsProperties.insert(mappedKey).second) { + throw ErrorInfo(Tr::tr("Duplicate entry '%1' in Product.%2.") + .arg(mappedKey, StringConstants::multiplexByQbsPropertiesProperty()), + productItem->location()); + } + + const QScriptValue arr = m_evaluator->value(qbsModuleItem, key); + if (arr.isUndefined()) + continue; + if (!arr.isArray()) + throw ErrorInfo(Tr::tr("Property '%1' must be an array.").arg(key)); + + const quint32 arrlen = arr.property(StringConstants::lengthProperty()).toUInt32(); + if (arrlen == 0) + continue; + + MultiplexRow mprow; + mprow.resize(arrlen); + QVariantList entriesForKey; + for (quint32 i = 0; i < arrlen; ++i) { + const QVariant value = arr.property(i).toVariant(); + if (entriesForKey.contains(value)) { + throw ErrorInfo(Tr::tr("Duplicate entry '%1' in qbs.%2.") + .arg(value.toString(), key), productItem->location()); + } + entriesForKey << value; + mprow[i] = VariantValue::create(value); + } + multiplexInfo.table = combine(multiplexInfo.table, mprow); + multiplexInfo.properties.push_back(mappedKey); + } + return multiplexInfo; +} + +template +T ModuleLoader::callWithTemporaryBaseModule(ProductContext *productContext, const F &func) +{ + // Temporarily attach the qbs module here, in case we need to access one of its properties + // to evaluate properties. + const QString &qbsKey = StringConstants::qbsModule(); + Item *productItem = productContext->item; + ValuePtr qbsValue = productItem->property(qbsKey); // Retrieve now to restore later. + if (qbsValue) + qbsValue = qbsValue->clone(); + const Item::Module qbsModule = loadBaseModule(productContext, productItem); + productItem->addModule(qbsModule); + + auto &&result = func(qbsModule); + + // "Unload" the qbs module again. + if (qbsValue) + productItem->setProperty(qbsKey, qbsValue); + else + productItem->removeProperty(qbsKey); + productItem->removeModules(); + + return std::forward(result); +} + +QList ModuleLoader::multiplexProductItem(ProductContext *dummyContext, Item *productItem) +{ + QString productName; + dummyContext->item = productItem; + auto extractMultiplexInfoFromProduct + = [this, productItem, &productName](const Item::Module &qbsModule) { + // Overriding the product item properties must be done here already, because multiplexing + // properties might depend on product properties. + const QString &nameKey = StringConstants::nameProperty(); + productName = m_evaluator->stringValue(productItem, nameKey); + if (productName.isEmpty()) { + productName = FileInfo::completeBaseName(productItem->file()->filePath()); + productItem->setProperty(nameKey, VariantValue::create(productName)); + } + overrideItemProperties(productItem, StringConstants::productsOverridePrefix() + productName, + m_parameters.overriddenValuesTree()); + + return extractMultiplexInfo(productItem, qbsModule.item); + }; + const MultiplexInfo multiplexInfo + = callWithTemporaryBaseModule(dummyContext, + extractMultiplexInfoFromProduct); + + if (multiplexInfo.table.size() > 1) + productItem->setProperty(StringConstants::multiplexedProperty(), VariantValue::trueValue()); + + VariantValuePtr productNameValue = VariantValue::create(productName); + + Item *aggregator = multiplexInfo.aggregate ? productItem->clone() : nullptr; + QList additionalProductItems; + std::vector multiplexConfigurationIdValues; + for (size_t row = 0; row < multiplexInfo.table.size(); ++row) { + Item *item = productItem; + const auto &mprow = multiplexInfo.table.at(row); + QBS_CHECK(mprow.size() == multiplexInfo.properties.size()); + if (row > 0) { + item = productItem->clone(); + additionalProductItems.push_back(item); + } + const QString multiplexConfigurationId = multiplexInfo.toIdString(row); + const VariantValuePtr multiplexConfigurationIdValue + = VariantValue::create(multiplexConfigurationId); + if (multiplexInfo.table.size() > 1 || aggregator) { + multiplexConfigurationIdValues.push_back(multiplexConfigurationIdValue); + item->setProperty(StringConstants::multiplexConfigurationIdProperty(), + multiplexConfigurationIdValue); + } + if (multiplexInfo.multiplexedType) + item->setProperty(StringConstants::typeProperty(), multiplexInfo.multiplexedType); + for (size_t column = 0; column < mprow.size(); ++column) { + Item *qbsItem = moduleInstanceItem(item, StringConstants::qbsModule()); + const QString &propertyName = multiplexInfo.properties.at(column); + const VariantValuePtr &mpvalue = mprow.at(column); + qbsItem->setProperty(propertyName, mpvalue); + } + } + + if (aggregator) { + additionalProductItems << aggregator; + + // Add dependencies to all multiplexed instances. + for (const auto &v : multiplexConfigurationIdValues) { + Item *dependsItem = Item::create(aggregator->pool(), ItemType::Depends); + dependsItem->setProperty(StringConstants::nameProperty(), productNameValue); + dependsItem->setProperty(StringConstants::multiplexConfigurationIdProperty(), v); + dependsItem->setProperty(StringConstants::profilesProperty(), + VariantValue::create(QStringList())); + dependsItem->setFile(aggregator->file()); + dependsItem->setupForBuiltinType(m_logger); + Item::addChild(aggregator, dependsItem); + } + } + + return additionalProductItems; +} + +void ModuleLoader::normalizeDependencies(ProductContext *product, + const DeferredDependsContext &dependsContext) +{ + std::vector dependsItemsToAdd; + std::vector dependsItemsToRemove; + std::vector deferredDependsItems; + for (Item *dependsItem : dependsContext.parentItem->children()) { + if (dependsItem->type() != ItemType::Depends) + continue; + bool productTypesIsSet; + const FileTags productTypes = m_evaluator->fileTagsValue(dependsItem, + StringConstants::productTypesProperty(), &productTypesIsSet); + if (productTypesIsSet) { + bool nameIsSet; + m_evaluator->stringValue(dependsItem, StringConstants::nameProperty(), QString(), + &nameIsSet); + + // The second condition is for the case where the dependency comes from an Export item + // that has itself been normalized in the mean time. + if (nameIsSet && !dependsItem->variantProperty(StringConstants::nameProperty())) { + throw ErrorInfo(Tr::tr("The 'productTypes' and 'name' properties are mutually " + "exclusive."), dependsItem->location()); + } + + bool submodulesPropertySet; + m_evaluator->stringListValue( dependsItem, StringConstants::submodulesProperty(), + &submodulesPropertySet); + if (submodulesPropertySet) { + throw ErrorInfo(Tr::tr("The 'productTypes' and 'subModules' properties are " + "mutually exclusive."), dependsItem->location()); + } + + // We ignore the "limitToSubProject" property for dependencies from Export items, + // because we cannot make it work consistently, as the importing product is not + // yet known when normalizing via an Export item. + const bool limitToSubProject = dependsContext.parentItem->type() == ItemType::Product + && m_evaluator->boolValue(dependsItem, + StringConstants::limitToSubProjectProperty()); + static const auto hasSameSubProject + = [](const ProductContext &product, const ProductContext &other) { + for (const Item *otherParent = other.item->parent(); otherParent; + otherParent = otherParent->parent()) { + if (otherParent == product.item->parent()) + return true; + } + return false; + }; + std::vector matchingProducts; + for (const FileTag &typeTag : productTypes) { + const auto range = m_productsByType.equal_range(typeTag); + for (auto it = range.first; it != range.second; ++it) { + if (it->second != product + && (!product || it->second->name != product->name) + && (!limitToSubProject || hasSameSubProject(*product, *it->second))) { + matchingProducts.push_back(it->second); + } + } + } + if (matchingProducts.empty()) { + qCDebug(lcModuleLoader) << "Depends.productTypes does not match anything." + << dependsItem->location(); + dependsItemsToRemove.push_back(dependsItem); + continue; + } + if (dependsContext.parentItem->type() != ItemType::Export) + deferredDependsItems.push_back(dependsItem); + for (std::size_t i = 1; i < matchingProducts.size(); ++i) { + Item * const dependsClone = dependsItem->clone(); + dependsClone->setProperty(StringConstants::nameProperty(), + VariantValue::create(matchingProducts.at(i)->name)); + dependsItemsToAdd.push_back(dependsClone); + if (dependsContext.parentItem->type() != ItemType::Export) + deferredDependsItems.push_back(dependsClone); + + } + dependsItem->setProperty(StringConstants::nameProperty(), + VariantValue::create(matchingProducts.front()->name)); + } + } + for (Item * const newDependsItem : dependsItemsToAdd) + Item::addChild(dependsContext.parentItem, newDependsItem); + for (Item * const dependsItem : dependsItemsToRemove) + Item::removeChild(dependsContext.parentItem, dependsItem); + if (!deferredDependsItems.empty()) { + auto &allDeferredDependsItems + = product->deferredDependsItems[dependsContext.exportingProductItem]; + allDeferredDependsItems.insert(allDeferredDependsItems.end(), deferredDependsItems.cbegin(), + deferredDependsItems.cend()); + } +} + +void ModuleLoader::adjustDependenciesForMultiplexing(const TopLevelProjectContext &tlp) +{ + for (const ProjectContext * const project : tlp.projects) { + for (const ProductContext &product : project->products) + adjustDependenciesForMultiplexing(product); + } +} + +void ModuleLoader::adjustDependenciesForMultiplexing(const ModuleLoader::ProductContext &product) +{ + for (Item *dependsItem : product.item->children()) { + if (dependsItem->type() == ItemType::Depends) + adjustDependenciesForMultiplexing(product, dependsItem); + } +} + +void ModuleLoader::adjustDependenciesForMultiplexing(const ProductContext &product, + Item *dependsItem) +{ + const QString name = m_evaluator->stringValue(dependsItem, StringConstants::nameProperty()); + const bool productIsMultiplexed = !product.multiplexConfigurationId.isEmpty(); + if (name == product.name) { + QBS_CHECK(!productIsMultiplexed); // This product must be an aggregator. + return; + } + + bool profilesPropertyIsSet; + const QStringList profiles = m_evaluator->stringListValue(dependsItem, + StringConstants::profilesProperty(), &profilesPropertyIsSet); + + const auto productRange = m_productsByName.equal_range(name); + if (productRange.first == productRange.second) { + // Dependency is a module. Nothing to adjust. + return; + } + + std::vector multiplexedDependencies; + bool hasNonMultiplexedDependency = false; + for (auto it = productRange.first; it != productRange.second; ++it) { + if (!it->second->multiplexConfigurationId.isEmpty()) + multiplexedDependencies.push_back(it->second); + else + hasNonMultiplexedDependency = true; + } + bool hasMultiplexedDependencies = !multiplexedDependencies.empty(); + + // These are the allowed cases: + // (1) Normal dependency with no multiplexing whatsoever. + // (2) Both product and dependency are multiplexed. + // (2a) The profiles property is not set, we want to depend on the best + // matching variant. + // (2b) The profiles property is set, we want to depend on all variants + // with a matching profile. + // (3) The product is not multiplexed, but the dependency is. + // (3a) The profiles property is not set, the dependency has an aggregator. + // We want to depend on the aggregator. + // (3b) The profiles property is not set, the dependency does not have an + // aggregator. We want to depend on all the multiplexed variants. + // (3c) The profiles property is set, we want to depend on all variants + // with a matching profile regardless of whether an aggregator exists or not. + // (4) The product is multiplexed, but the dependency is not. We don't have to adapt + // any Depends items. + // (5) The product is a "shadow product". In that case, we know which product + // it should have a dependency on, and we make sure we depend on that. + + // (1) and (4) + if (!hasMultiplexedDependencies) + return; + + // (3a) + if (!productIsMultiplexed && hasNonMultiplexedDependency && !profilesPropertyIsSet) + return; + + QStringList multiplexIds; + const ShadowProductInfo shadowProductInfo = getShadowProductInfo(product); + const bool isShadowProduct = shadowProductInfo.first && shadowProductInfo.second == name; + const auto productMultiplexConfig = + MultiplexInfo::multiplexIdToVariantMap(product.multiplexConfigurationId); + + for (const ProductContext *dependency : multiplexedDependencies) { + const bool depMatchesShadowProduct = isShadowProduct + && dependency->item == product.item->parent(); + const QString depMultiplexId = dependency->multiplexConfigurationId; + if (depMatchesShadowProduct) { // (5) + dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), + VariantValue::create(depMultiplexId)); + return; + } + if (productIsMultiplexed && !profilesPropertyIsSet) { // 2a + if (dependency->multiplexConfigurationId == product.multiplexConfigurationId) { + const ValuePtr &multiplexId = product.item->property( + StringConstants::multiplexConfigurationIdProperty()); + dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), + multiplexId); + return; + + } else { + // Otherwise collect partial matches and decide later + const auto dependencyMultiplexConfig = + MultiplexInfo::multiplexIdToVariantMap(dependency->multiplexConfigurationId); + + if (multiplexConfigurationIntersects(dependencyMultiplexConfig, productMultiplexConfig)) + multiplexIds << dependency->multiplexConfigurationId; + } + } else { + // (2b), (3b) or (3c) + const bool profileMatch = !profilesPropertyIsSet || profiles.empty() + || profiles.contains(dependency->profileName); + if (profileMatch) + multiplexIds << depMultiplexId; + } + } + if (multiplexIds.empty()) { + const QString productName = ResolvedProduct::fullDisplayName( + product.name, product.multiplexConfigurationId); + throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not fulfilled. " + "There are no eligible multiplex candidates.").arg(productName, + name), + dependsItem->location()); + } + + // In case of (2a), at most 1 match is allowed + if (productIsMultiplexed && !profilesPropertyIsSet && multiplexIds.size() > 1) { + const QString productName = ResolvedProduct::fullDisplayName( + product.name, product.multiplexConfigurationId); + QStringList candidateNames; + for (const auto &id : qAsConst(multiplexIds)) + candidateNames << ResolvedProduct::fullDisplayName(name, id); + throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' is ambiguous. " + "Eligible multiplex candidates: %3.").arg( + productName, name, candidateNames.join(QLatin1String(", "))), + dependsItem->location()); + } + + dependsItem->setProperty(StringConstants::multiplexConfigurationIdsProperty(), + VariantValue::create(multiplexIds)); +} + +void ModuleLoader::prepareProduct(ProjectContext *projectContext, Item *productItem) +{ + AccumulatingTimer timer(m_parameters.logElapsedTime() + ? &m_elapsedTimePrepareProducts : nullptr); + checkCancelation(); + qCDebug(lcModuleLoader) << "prepareProduct" << productItem->file()->filePath(); + + ProductContext productContext; + productContext.item = productItem; + productContext.project = projectContext; + productContext.name = m_evaluator->stringValue(productItem, StringConstants::nameProperty()); + QBS_CHECK(!productContext.name.isEmpty()); + const ItemValueConstPtr qbsItemValue = productItem->itemProperty(StringConstants::qbsModule()); + if (!!qbsItemValue && qbsItemValue->item()->hasProperty(StringConstants::profileProperty())) { + qbsItemValue->item()->setProperty(StringConstants::nameProperty(), + VariantValue::create(StringConstants::nameProperty())); + auto evaluateQbsProfileProperty = [this](const Item::Module &qbsModule) { + return m_evaluator->stringValue(qbsModule.item, + StringConstants::profileProperty(), QString()); + }; + productContext.profileName + = callWithTemporaryBaseModule(&productContext, + evaluateQbsProfileProperty); + } else { + productContext.profileName = m_parameters.topLevelProfile(); + } + productContext.multiplexConfigurationId = m_evaluator->stringValue( + productItem, StringConstants::multiplexConfigurationIdProperty()); + QBS_CHECK(!productContext.profileName.isEmpty()); + 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( + profile, m_parameters.configurationName()); + productContext.moduleProperties = SetupProjectParameters::finalBuildConfigurationTree( + buildConfig, m_parameters.overriddenValues()); + projectContext->result->profileConfigs.insert(productContext.profileName, + productContext.moduleProperties); + } else { + productContext.moduleProperties = it.value().toMap(); + } + initProductProperties(productContext); + + ItemValuePtr itemValue = ItemValue::create(productItem); + productContext.scope = Item::create(m_pool, ItemType::Scope); + productContext.scope->setProperty(StringConstants::productVar(), itemValue); + productContext.scope->setFile(productItem->file()); + productContext.scope->setScope(productContext.project->scope); + + const bool hasExportItems = mergeExportItems(productContext); + + setScopeForDescendants(productItem, productContext.scope); + + projectContext->products.push_back(productContext); + + if (!hasExportItems || getShadowProductInfo(productContext).first) + return; + + // This "shadow product" exists only to pull in a dependency on the actual product + // and nothing else, thus providing us with the pure environment that we need to + // evaluate the product's exported properties in isolation in the project resolver. + Item * const importer = Item::create(productItem->pool(), ItemType::Product); + importer->setProperty(QStringLiteral("name"), + VariantValue::create(StringConstants::shadowProductPrefix() + + productContext.name)); + importer->setFile(productItem->file()); + importer->setLocation(productItem->location()); + importer->setScope(projectContext->scope); + importer->setupForBuiltinType(m_logger); + Item * const dependsItem = Item::create(productItem->pool(), ItemType::Depends); + dependsItem->setProperty(QStringLiteral("name"), VariantValue::create(productContext.name)); + dependsItem->setProperty(QStringLiteral("required"), VariantValue::create(false)); + dependsItem->setFile(importer->file()); + dependsItem->setLocation(importer->location()); + dependsItem->setupForBuiltinType(m_logger); + Item::addChild(importer, dependsItem); + Item::addChild(productItem, importer); + prepareProduct(projectContext, importer); +} + +void ModuleLoader::setupProductDependencies(ProductContext *productContext, + const Set &deferredDependsContext) +{ + if (m_dependencyResolvingPass == 2) { + for (const DeferredDependsContext &ctx : deferredDependsContext) + normalizeDependencies(productContext, ctx); + for (const auto &deferralData : productContext->deferredDependsItems) { + for (Item * const deferredDependsItem : deferralData.second) { + + // Dependencies from Export items are handled in addProductModuleDependencies(). + if (deferredDependsItem->parent() == productContext->item) + adjustDependenciesForMultiplexing(*productContext, deferredDependsItem); + } + } + } + AccumulatingTimer timer(m_parameters.logElapsedTime() + ? &m_elapsedTimeProductDependencies : nullptr); + checkCancelation(); + Item *item = productContext->item; + qCDebug(lcModuleLoader) << "setupProductDependencies" << productContext->name + << productContext->item->location(); + + if (m_dependencyResolvingPass == 1) + setSearchPathsForProduct(productContext); + SearchPathsManager searchPathsManager(m_reader, productContext->searchPaths); + + DependsContext dependsContext; + dependsContext.product = productContext; + dependsContext.productDependencies = &productContext->info.usedProducts; + resolveDependencies(&dependsContext, item, productContext); + if (m_dependencyResolvingPass == 2 + || !containsKey(m_productsWithDeferredDependsItems, productContext)) { + addProductModuleDependencies(productContext); + } + productContext->project->result->productInfos.insert(item, productContext->info); +} + +// Leaf modules first. +// TODO: Can this be merged with addTransitiveDependencies? Looks suspiciously similar. +void ModuleLoader::createSortedModuleList(const Item::Module &parentModule, Item::Modules &modules) +{ + if (std::find_if(modules.cbegin(), modules.cend(), + [parentModule](const Item::Module &m) { return m.name == parentModule.name;}) + != modules.cend()) { + return; + } + for (const Item::Module &dep : parentModule.item->modules()) + createSortedModuleList(dep, modules); + modules.push_back(parentModule); +} + +Item::Modules ModuleLoader::modulesSortedByDependency(const Item *productItem) +{ + QBS_CHECK(productItem->type() == ItemType::Product); + Item::Modules sortedModules; + const Item::Modules &unsortedModules = productItem->modules(); + for (const Item::Module &module : unsortedModules) + createSortedModuleList(module, sortedModules); + QBS_CHECK(sortedModules.size() == unsortedModules.size()); + + // Make sure the top-level items stay the same. + for (Item::Module &s : sortedModules) { + for (const Item::Module &u : unsortedModules) { + if (s.name == u.name) { + s.item = u.item; + break; + } + } + } + return sortedModules; +} + + +template bool insertIntoSet(Set &set, const T &value) +{ + const auto insertionResult = set.insert(value); + return insertionResult.second; +} + +void ModuleLoader::setupReverseModuleDependencies(const Item::Module &module, + ModuleDependencies &deps, + QualifiedIdSet &seenModules) +{ + if (!insertIntoSet(seenModules, module.name)) + return; + for (const Item::Module &m : module.item->modules()) { + deps[m.name].insert(module.name); + setupReverseModuleDependencies(m, deps, seenModules); + } +} + +ModuleLoader::ModuleDependencies ModuleLoader::setupReverseModuleDependencies(const Item *product) +{ + ModuleDependencies deps; + QualifiedIdSet seenModules; + for (const Item::Module &m : product->modules()) + setupReverseModuleDependencies(m, deps, seenModules); + return deps; +} + +void ModuleLoader::handleProduct(ModuleLoader::ProductContext *productContext) +{ + AccumulatingTimer timer(m_parameters.logElapsedTime() ? &m_elapsedTimeHandleProducts : nullptr); + if (productContext->info.delayedError.hasError()) + return; + + Item * const item = productContext->item; + + m_reader->setExtraSearchPathsStack(productContext->project->searchPathsStack); + SearchPathsManager searchPathsManager(m_reader, productContext->searchPaths); + addTransitiveDependencies(productContext); + + // It is important that dependent modules are merged after their dependency, because + // the dependent module's merger potentially needs to replace module items that were + // set by the dependency module's merger (namely, scopes of defining items; see + // ModuleMerger::replaceItemInScopes()). + Item::Modules topSortedModules = modulesSortedByDependency(item); + ModuleMerger::merge(m_logger, item, productContext->name, &topSortedModules); + + // Re-sort the modules by name. This is more stable; see QBS-818. + // The list of modules in the product now has the same order as before, + // only the items have been replaced by their merged counterparts. + Item::Modules lexicographicallySortedModules = topSortedModules; + std::sort(lexicographicallySortedModules.begin(), lexicographicallySortedModules.end()); + item->setModules(lexicographicallySortedModules); + + for (const Item::Module &module : topSortedModules) { + if (!module.item->isPresentModule()) + continue; + try { + resolveProbes(productContext, module.item); + if (module.versionRange.minimum.isValid() + || module.versionRange.maximum.isValid()) { + if (module.versionRange.maximum.isValid() + && module.versionRange.minimum >= module.versionRange.maximum) { + throw ErrorInfo(Tr::tr("Impossible version constraint [%1,%2) set for module " + "'%3'").arg(module.versionRange.minimum.toString(), + module.versionRange.maximum.toString(), + module.name.toString())); + } + const Version moduleVersion = Version::fromString( + m_evaluator->stringValue(module.item, + StringConstants::versionProperty())); + if (moduleVersion < module.versionRange.minimum) { + throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be " + "at least %3.").arg(module.name.toString(), + moduleVersion.toString(), + module.versionRange.minimum.toString())); + } + if (module.versionRange.maximum.isValid() + && moduleVersion >= module.versionRange.maximum) { + throw ErrorInfo(Tr::tr("Module '%1' has version %2, but it needs to be " + "lower than %3.").arg(module.name.toString(), + moduleVersion.toString(), + module.versionRange.maximum.toString())); + } + } + } catch (const ErrorInfo &error) { + handleModuleSetupError(productContext, module, error); + if (productContext->info.delayedError.hasError()) + return; + } + } + + resolveProbes(productContext, item); + + // Module validation must happen in an extra pass, after all Probes have been resolved. + EvalCacheEnabler cacheEnabler(m_evaluator); + for (const Item::Module &module : topSortedModules) { + if (!module.item->isPresentModule()) + continue; + try { + m_evaluator->boolValue(module.item, StringConstants::validateProperty()); + for (const auto &dep : module.item->modules()) { + if (dep.requiredValue && !dep.item->isPresentModule()) { + throw ErrorInfo(Tr::tr("Module '%1' depends on module '%2', which was not " + "loaded successfully") + .arg(module.name.toString(), dep.name.toString())); + } + } + } catch (const ErrorInfo &error) { + handleModuleSetupError(productContext, module, error); + if (productContext->info.delayedError.hasError()) + return; + } + } + + if (!checkItemCondition(item)) { + const auto &exportsData = productContext->project->topLevelProject->productModules; + for (auto it = exportsData.find(productContext->name); + it != exportsData.end() && it.key() == productContext->name; ++it) { + if (it.value().multiplexId == productContext->multiplexConfigurationId) { + createNonPresentModule(productContext->name, QStringLiteral("disabled"), + it.value().exportItem); + break; + } + } + } + + checkDependencyParameterDeclarations(productContext); + copyGroupsFromModulesToProduct(*productContext); + + ModuleDependencies reverseModuleDeps; + for (Item * const child : item->children()) { + if (child->type() == ItemType::Group) { + if (reverseModuleDeps.empty()) + reverseModuleDeps = setupReverseModuleDependencies(item); + handleGroup(productContext, child, reverseModuleDeps); + } + } + productContext->project->result->productInfos.insert(item, productContext->info); +} + +static Item *rootPrototype(Item *item) +{ + Item *modulePrototype = item; + while (modulePrototype->prototype()) + modulePrototype = modulePrototype->prototype(); + return modulePrototype; +} + +class DependencyParameterDeclarationCheck +{ +public: + DependencyParameterDeclarationCheck(const QString &productName, const Item *productItem, + const QHash &decls) + : m_productName(productName), m_productItem(productItem), m_parameterDeclarations(decls) + { + } + + void operator()(const QVariantMap ¶meters) const + { + check(parameters, QualifiedId()); + } + +private: + void check(const QVariantMap ¶meters, const QualifiedId &moduleName) const + { + for (auto it = parameters.begin(); it != parameters.end(); ++it) { + if (it.value().type() == QVariant::Map) { + check(it.value().toMap(), QualifiedId(moduleName) << it.key()); + } else { + const auto &deps = m_productItem->modules(); + auto m = std::find_if(deps.begin(), deps.end(), + [&moduleName] (const Item::Module &module) { + return module.name == moduleName; + }); + + if (m == deps.end()) { + const QualifiedId fullName = QualifiedId(moduleName) << it.key(); + throw ErrorInfo(Tr::tr("Cannot set parameter '%1', " + "because '%2' does not have a dependency on '%3'.") + .arg(fullName.toString(), m_productName, moduleName.toString()), + m_productItem->location()); + } + + auto decls = m_parameterDeclarations.value(rootPrototype(m->item)); + + if (!decls.contains(it.key())) { + const QualifiedId fullName = QualifiedId(moduleName) << it.key(); + throw ErrorInfo(Tr::tr("Parameter '%1' is not declared.") + .arg(fullName.toString()), m_productItem->location()); + } + } + } + } + + bool moduleExists(const QualifiedId &name) const + { + const auto &deps = m_productItem->modules(); + return any_of(deps, [&name](const Item::Module &module) { + return module.name == name; + }); + } + + const QString &m_productName; + const Item *m_productItem; + const QHash &m_parameterDeclarations; +}; + +void ModuleLoader::checkDependencyParameterDeclarations(const ProductContext *productContext) const +{ + DependencyParameterDeclarationCheck dpdc(productContext->name, productContext->item, + m_parameterDeclarations); + for (const Item::Module &dep : productContext->item->modules()) { + if (!dep.parameters.empty()) + dpdc(dep.parameters); + } +} + +void ModuleLoader::handleModuleSetupError(ModuleLoader::ProductContext *productContext, + const Item::Module &module, const ErrorInfo &error) +{ + if (module.required) { + handleProductError(error, productContext); + } else { + qCDebug(lcModuleLoader()) << "non-required module" << module.name.toString() + << "found, but not usable in product" << productContext->name + << error.toString(); + createNonPresentModule(module.name.toString(), QStringLiteral("failed validation"), + module.item); + } +} + +void ModuleLoader::initProductProperties(const ProductContext &product) +{ + QString buildDir = ResolvedProduct::deriveBuildDirectoryName(product.name, + product.multiplexConfigurationId); + buildDir = FileInfo::resolvePath(product.project->topLevelProject->buildDirectory, buildDir); + product.item->setProperty(StringConstants::buildDirectoryProperty(), + VariantValue::create(buildDir)); + const QString sourceDir = QFileInfo(product.item->file()->filePath()).absolutePath(); + product.item->setProperty(StringConstants::sourceDirectoryProperty(), + VariantValue::create(sourceDir)); +} + +void ModuleLoader::handleSubProject(ModuleLoader::ProjectContext *projectContext, Item *projectItem, + const Set &referencedFilePaths) +{ + qCDebug(lcModuleLoader) << "handleSubProject" << projectItem->file()->filePath(); + + Item * const propertiesItem = projectItem->child(ItemType::PropertiesInSubProject); + if (!checkItemCondition(projectItem)) + return; + if (propertiesItem) { + propertiesItem->setScope(projectItem); + if (!checkItemCondition(propertiesItem)) + return; + } + + Item *loadedItem; + QString subProjectFilePath; + try { + const QString projectFileDirPath = FileInfo::path(projectItem->file()->filePath()); + const QString relativeFilePath + = m_evaluator->stringValue(projectItem, StringConstants::filePathProperty()); + subProjectFilePath = FileInfo::resolvePath(projectFileDirPath, relativeFilePath); + if (referencedFilePaths.contains(subProjectFilePath)) + throw ErrorInfo(Tr::tr("Cycle detected while loading subproject file '%1'.") + .arg(relativeFilePath), projectItem->location()); + loadedItem = loadItemFromFile(subProjectFilePath, projectItem->location()); + } catch (const ErrorInfo &error) { + if (m_parameters.productErrorMode() == ErrorHandlingMode::Strict) + throw; + m_logger.printWarning(error); + return; + } + + loadedItem = wrapInProjectIfNecessary(loadedItem); + const bool inheritProperties = m_evaluator->boolValue( + projectItem, StringConstants::inheritPropertiesProperty()); + + if (inheritProperties) + copyProperties(projectItem->parent(), loadedItem); + if (propertiesItem) { + const Item::PropertyMap &overriddenProperties = propertiesItem->properties(); + for (Item::PropertyMap::ConstIterator it = overriddenProperties.constBegin(); + it != overriddenProperties.constEnd(); ++it) { + loadedItem->setProperty(it.key(), it.value()); + } + } + + Item::addChild(projectItem, loadedItem); + projectItem->setScope(projectContext->scope); + handleProject(projectContext->result, projectContext->topLevelProject, loadedItem, + Set(referencedFilePaths) << subProjectFilePath); +} + +QList ModuleLoader::loadReferencedFile(const QString &relativePath, + const CodeLocation &referencingLocation, + const Set &referencedFilePaths, + ModuleLoader::ProductContext &dummyContext) +{ + QString absReferencePath = FileInfo::resolvePath(FileInfo::path(referencingLocation.filePath()), + relativePath); + if (FileInfo(absReferencePath).isDir()) { + QString qbsFilePath; + + QDirIterator dit(absReferencePath, StringConstants::qbsFileWildcards()); + while (dit.hasNext()) { + if (!qbsFilePath.isEmpty()) { + throw ErrorInfo(Tr::tr("Referenced directory '%1' contains more than one " + "qbs file.").arg(absReferencePath), referencingLocation); + } + qbsFilePath = dit.next(); + } + if (qbsFilePath.isEmpty()) { + throw ErrorInfo(Tr::tr("Referenced directory '%1' does not contain a qbs file.") + .arg(absReferencePath), referencingLocation); + } + absReferencePath = qbsFilePath; + } + if (referencedFilePaths.contains(absReferencePath)) + throw ErrorInfo(Tr::tr("Cycle detected while referencing file '%1'.").arg(relativePath), + referencingLocation); + Item * const subItem = loadItemFromFile(absReferencePath, referencingLocation); + if (subItem->type() != ItemType::Project && subItem->type() != ItemType::Product) { + ErrorInfo error(Tr::tr("Item type should be 'Product' or 'Project', but is '%1'.") + .arg(subItem->typeName())); + error.append(Tr::tr("Item is defined here."), subItem->location()); + error.append(Tr::tr("File is referenced here."), referencingLocation); + throw error; + } + subItem->setScope(dummyContext.project->scope); + subItem->setParent(dummyContext.project->item); + QList loadedItems; + loadedItems << subItem; + if (subItem->type() == ItemType::Product) { + handleProfileItems(subItem, dummyContext.project); + loadedItems << multiplexProductItem(&dummyContext, subItem); + } + return loadedItems; +} + +void ModuleLoader::handleGroup(ProductContext *productContext, Item *groupItem, + const ModuleDependencies &reverseDepencencies) +{ + checkCancelation(); + propagateModulesFromParent(productContext, groupItem, reverseDepencencies); + checkItemCondition(groupItem); + for (Item * const child : groupItem->children()) { + if (child->type() == ItemType::Group) + handleGroup(productContext, child, reverseDepencencies); + } +} + +void ModuleLoader::handleAllPropertyOptionsItems(Item *item) +{ + QList childItems = item->children(); + auto childIt = childItems.begin(); + while (childIt != childItems.end()) { + Item * const child = *childIt; + if (child->type() == ItemType::PropertyOptions) { + handlePropertyOptions(child); + childIt = childItems.erase(childIt); + } else { + handleAllPropertyOptionsItems(child); + ++childIt; + } + } + item->setChildren(childItems); +} + +void ModuleLoader::handlePropertyOptions(Item *optionsItem) +{ + const QString name = m_evaluator->stringValue(optionsItem, StringConstants::nameProperty()); + if (name.isEmpty()) { + throw ErrorInfo(Tr::tr("PropertyOptions item needs a name property"), + optionsItem->location()); + } + const QString description = m_evaluator->stringValue( + optionsItem, StringConstants::descriptionProperty()); + const auto removalVersion = Version::fromString(m_evaluator->stringValue(optionsItem, + StringConstants::removalVersionProperty())); + PropertyDeclaration decl = optionsItem->parent()->propertyDeclaration(name); + if (!decl.isValid()) { + decl.setName(name); + decl.setType(PropertyDeclaration::Variant); + } + decl.setDescription(description); + if (removalVersion.isValid()) { + DeprecationInfo di(removalVersion, description); + decl.setDeprecationInfo(di); + } + const ValuePtr property = optionsItem->parent()->property(name); + if (!property && !decl.isExpired()) { + throw ErrorInfo(Tr::tr("PropertyOptions item refers to non-existing property '%1'") + .arg(name), optionsItem->location()); + } + 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); +} + +static void mergeProperty(Item *dst, const QString &name, const ValuePtr &value) +{ + if (value->type() == Value::ItemValueType) { + const ItemValueConstPtr itemValue = std::static_pointer_cast(value); + const Item * const valueItem = itemValue->item(); + Item * const subItem = dst->itemProperty(name, itemValue)->item(); + for (QMap::const_iterator it = valueItem->properties().constBegin(); + it != valueItem->properties().constEnd(); ++it) + mergeProperty(subItem, it.key(), it.value()); + } else { + // If the property already exists set up the base value. + if (value->type() == Value::JSSourceValueType) { + const auto jsValue = static_cast(value.get()); + if (jsValue->isBuiltinDefaultValue()) + return; + const ValuePtr baseValue = dst->property(name); + if (baseValue) { + QBS_CHECK(baseValue->type() == Value::JSSourceValueType); + const JSSourceValuePtr jsBaseValue = std::static_pointer_cast( + baseValue->clone()); + jsValue->setBaseValue(jsBaseValue); + std::vector alternatives = jsValue->alternatives(); + jsValue->clearAlternatives(); + for (JSSourceValue::Alternative &a : alternatives) { + a.value->setBaseValue(jsBaseValue); + jsValue->addAlternative(a); + } + } + } + dst->setProperty(name, value); + } +} + +bool ModuleLoader::checkExportItemCondition(Item *exportItem, const ProductContext &productContext) +{ + class ScopeHandler { + public: + ScopeHandler(Item *exportItem, const ProductContext &productContext, Item **cachedScopeItem) + : m_exportItem(exportItem) + { + if (!*cachedScopeItem) + *cachedScopeItem = Item::create(exportItem->pool(), ItemType::Scope); + Item * const scope = *cachedScopeItem; + QBS_CHECK(productContext.item->file()); + scope->setFile(productContext.item->file()); + scope->setScope(productContext.item); + productContext.project->scope->copyProperty(StringConstants::projectVar(), scope); + productContext.scope->copyProperty(StringConstants::productVar(), scope); + QBS_CHECK(!exportItem->scope()); + exportItem->setScope(scope); + } + ~ScopeHandler() { m_exportItem->setScope(nullptr); } + + private: + Item * const m_exportItem; + } scopeHandler(exportItem, productContext, &m_tempScopeItem); + return checkItemCondition(exportItem); +} + +ProbeConstPtr ModuleLoader::findOldProjectProbe( + const QString &globalId, + bool condition, + const QVariantMap &initialProperties, + const QString &sourceCode) const +{ + if (m_parameters.forceProbeExecution()) + return {}; + + for (const ProbeConstPtr &oldProbe : m_oldProjectProbes.value(globalId)) { + if (probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes)) + return oldProbe; + } + + return {}; +} + +ProbeConstPtr ModuleLoader::findOldProductProbe( + const QString &productName, + bool condition, + const QVariantMap &initialProperties, + const QString &sourceCode) const +{ + if (m_parameters.forceProbeExecution()) + return {}; + + for (const ProbeConstPtr &oldProbe : m_oldProductProbes.value(productName)) { + if (probeMatches(oldProbe, condition, initialProperties, sourceCode, CompareScript::Yes)) + return oldProbe; + } + + return {}; +} + +ProbeConstPtr ModuleLoader::findCurrentProbe( + const CodeLocation &location, + bool condition, + const QVariantMap &initialProperties) const +{ + const std::vector &cachedProbes = m_currentProbes.value(location); + for (const ProbeConstPtr &probe : cachedProbes) { + if (probeMatches(probe, condition, initialProperties, QString(), CompareScript::No)) + return probe; + } + return {}; +} + +bool ModuleLoader::probeMatches(const ProbeConstPtr &probe, bool condition, + const QVariantMap &initialProperties, const QString &configureScript, + CompareScript compareScript) const +{ + return probe->condition() == condition + && probe->initialProperties() == initialProperties + && (compareScript == CompareScript::No + || (probe->configureScript() == configureScript + && !probe->needsReconfigure(m_lastResolveTime))); +} + +void ModuleLoader::printProfilingInfo() +{ + if (!m_parameters.logElapsedTime()) + return; + m_logger.qbsLog(LoggerInfo, true) << "\t" + << Tr::tr("Project file loading and parsing took %1.") + .arg(elapsedTimeString(m_reader->elapsedTime())); + m_logger.qbsLog(LoggerInfo, true) << "\t" + << Tr::tr("Preparing products took %1.") + .arg(elapsedTimeString(m_elapsedTimePrepareProducts)); + m_logger.qbsLog(LoggerInfo, true) << "\t" + << Tr::tr("Setting up product dependencies took %1.") + .arg(elapsedTimeString(m_elapsedTimeProductDependencies)); + m_logger.qbsLog(LoggerInfo, true) << "\t\t" + << Tr::tr("Setting up transitive product dependencies took %1.") + .arg(elapsedTimeString(m_elapsedTimeTransitiveDependencies)); + m_logger.qbsLog(LoggerInfo, true) << "\t" + << Tr::tr("Handling products took %1.") + .arg(elapsedTimeString(m_elapsedTimeHandleProducts)); + m_logger.qbsLog(LoggerInfo, true) << "\t\t" + << Tr::tr("Running Probes took %1.") + .arg(elapsedTimeString(m_elapsedTimeProbes)); + m_logger.qbsLog(LoggerInfo, true) << "\t\t" + << Tr::tr("%1 probes encountered, %2 configure scripts executed, " + "%3 re-used from current run, %4 re-used from earlier run.") + .arg(m_probesEncountered).arg(m_probesRun).arg(m_probesCachedCurrent) + .arg(m_probesCachedOld); + m_logger.qbsLog(LoggerInfo, true) << "\t" + << Tr::tr("Property checking took %1.") + .arg(elapsedTimeString(m_elapsedTimePropertyChecking)); +} + +static void mergeParameters(QVariantMap &dst, const QVariantMap &src) +{ + for (auto it = src.begin(); it != src.end(); ++it) { + if (it.value().type() == QVariant::Map) { + QVariant &vdst = dst[it.key()]; + QVariantMap mdst = vdst.toMap(); + mergeParameters(mdst, it.value().toMap()); + vdst = mdst; + } else { + dst[it.key()] = it.value(); + } + } +} + +static void adjustParametersScopes(Item *item, Item *scope) +{ + if (item->type() == ItemType::ModuleParameters) { + item->setScope(scope); + return; + } + + for (const auto &value : item->properties()) { + if (value->type() != Value::ItemValueType) + continue; + adjustParametersScopes(std::static_pointer_cast(value)->item(), scope); + } +} + +bool ModuleLoader::mergeExportItems(const ProductContext &productContext) +{ + std::vector exportItems; + QList children = productContext.item->children(); + for (int i = 0; i < children.size();) { + Item * const child = children.at(i); + if (child->type() == ItemType::Export) { + exportItems.push_back(child); + children.removeAt(i); + } else { + ++i; + } + } + + // Note that we do not return if there are no Export items: The "merged" item becomes the + // "product module", which always needs to exist, regardless of whether the product sources + // actually contain an Export item or not. + if (!exportItems.empty()) + productContext.item->setChildren(children); + + Item *merged = Item::create(productContext.item->pool(), ItemType::Export); + const QString &nameKey = StringConstants::nameProperty(); + const ValuePtr nameValue = VariantValue::create(productContext.name); + merged->setProperty(nameKey, nameValue); + Set filesWithExportItem; + ProductModuleInfo pmi; + bool hasDependenciesOnProductType = false; + for (Item * const exportItem : qAsConst(exportItems)) { + checkCancelation(); + if (Q_UNLIKELY(filesWithExportItem.contains(exportItem->file()))) + throw ErrorInfo(Tr::tr("Multiple Export items in one product are prohibited."), + exportItem->location()); + exportItem->setProperty(nameKey, nameValue); + if (!checkExportItemCondition(exportItem, productContext)) + continue; + filesWithExportItem += exportItem->file(); + for (Item * const child : exportItem->children()) { + if (child->type() == ItemType::Parameters) { + adjustParametersScopes(child, child); + mergeParameters(pmi.defaultParameters, + m_evaluator->scriptValue(child).toVariant().toMap()); + } else { + if (child->type() == ItemType::Depends) { + bool productTypesIsSet; + m_evaluator->stringValue(child, StringConstants::productTypesProperty(), + QString(), &productTypesIsSet); + if (productTypesIsSet) + hasDependenciesOnProductType = true; + } + Item::addChild(merged, child); + } + } + const Item::PropertyDeclarationMap &decls = exportItem->propertyDeclarations(); + for (auto it = decls.constBegin(); it != decls.constEnd(); ++it) { + const PropertyDeclaration &newDecl = it.value(); + const PropertyDeclaration &existingDecl = merged->propertyDeclaration(it.key()); + if (existingDecl.isValid() && existingDecl.type() != newDecl.type()) { + ErrorInfo error(Tr::tr("Export item in inherited item redeclares property " + "'%1' with different type.").arg(it.key()), exportItem->location()); + handlePropertyError(error, m_parameters, m_logger); + } + merged->setPropertyDeclaration(newDecl.name(), newDecl); + } + for (QMap::const_iterator it = exportItem->properties().constBegin(); + it != exportItem->properties().constEnd(); ++it) { + mergeProperty(merged, it.key(), it.value()); + } + } + merged->setFile(exportItems.empty() + ? productContext.item->file() : exportItems.back()->file()); + merged->setLocation(exportItems.empty() + ? productContext.item->location() : exportItems.back()->location()); + Item::addChild(productContext.item, merged); + merged->setupForBuiltinType(m_logger); + pmi.exportItem = merged; + pmi.multiplexId = productContext.multiplexConfigurationId; + productContext.project->topLevelProject->productModules.insert(productContext.name, pmi); + if (hasDependenciesOnProductType) + m_exportsWithDeferredDependsItems.insert(merged); + return !exportItems.empty(); +} + +Item *ModuleLoader::loadItemFromFile(const QString &filePath, + const CodeLocation &referencingLocation) +{ + Item *item; + try { + item = m_reader->readFile(filePath); + } catch (const ErrorInfo &e) { + if (e.hasLocation()) + throw; + throw ErrorInfo(e.toString(), referencingLocation); + } + handleAllPropertyOptionsItems(item); + return item; +} + +void ModuleLoader::handleProfileItems(Item *item, ProjectContext *projectContext) +{ + const std::vector profileItems = collectProfileItems(item, projectContext); + for (Item * const profileItem : profileItems) { + try { + handleProfile(profileItem); + } catch (const ErrorInfo &e) { + handlePropertyError(e, m_parameters, m_logger); + } + } +} + +std::vector ModuleLoader::collectProfileItems(Item *item, ProjectContext *projectContext) +{ + QList childItems = item->children(); + std::vector 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(StringConstants::productVar(), 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(it.value())->item(), + profileItem, values); + break; + case Value::VariantValueType: + values.insert(name.join(QLatin1Char('.')), + std::static_pointer_cast(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(StringConstants::conditionProperty()).toBool(); + if (!condition) + return; + const QString profileName = values.take(StringConstants::nameProperty()).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::collectNameFromOverride(const QString &overrideString) +{ + static const auto extract = [](const QString &prefix, const QString &overrideString) { + if (!overrideString.startsWith(prefix)) + return QString(); + const int startPos = prefix.length(); + const int endPos = overrideString.lastIndexOf(StringConstants::dot()); + if (endPos == -1) + return QString(); + return overrideString.mid(startPos, endPos - startPos); + }; + const QString &projectName = extract(StringConstants::projectsOverridePrefix(), overrideString); + if (!projectName.isEmpty()) { + m_projectNamesUsedInOverrides.insert(projectName); + return; + } + const QString &productName = extract(StringConstants::productsOverridePrefix(), overrideString); + if (!productName.isEmpty()) { + m_productNamesUsedInOverrides.insert(productName.left( + productName.indexOf(StringConstants::dot()))); + return; + } +} + +void ModuleLoader::checkProjectNamesInOverrides(const ModuleLoader::TopLevelProjectContext &tlp) +{ + for (const QString &projectNameInOverride : m_projectNamesUsedInOverrides) { + if (m_disabledProjects.contains(projectNameInOverride)) + continue; + bool found = false; + for (const ProjectContext * const p : tlp.projects) { + if (p->name == projectNameInOverride) { + found = true; + break; + } + } + if (!found) { + handlePropertyError(Tr::tr("Unknown project '%1' in property override.") + .arg(projectNameInOverride), m_parameters, m_logger); + } + } +} + +void ModuleLoader::checkProductNamesInOverrides() +{ + for (const QString &productNameInOverride : m_productNamesUsedInOverrides) { + if (m_erroneousProducts.contains(productNameInOverride)) + continue; + bool found = false; + for (const auto &kv : m_productsByName) { + // In an override string such as "a.b.c:d, we cannot tell whether we have a product + // "a" and a module "b.c" or a product "a.b" and a module "c", so we need to take + // care not to emit false positives here. + if (kv.first == productNameInOverride + || kv.first.startsWith(productNameInOverride + StringConstants::dot())) { + found = true; + break; + } + } + if (!found) { + handlePropertyError(Tr::tr("Unknown product '%1' in property override.") + .arg(productNameInOverride), m_parameters, m_logger); + } + } +} + +void ModuleLoader::setSearchPathsForProduct(ModuleLoader::ProductContext *product) +{ + product->searchPaths = readExtraSearchPaths(product->item); + Settings settings(m_parameters.settingsDirectory()); + const QVariantMap profileContents = product->project->result->profileConfigs + .value(product->profileName).toMap(); + const QStringList prefsSearchPaths = Preferences(&settings, profileContents).searchPaths(); + const QStringList ¤tSearchPaths = m_reader->allSearchPaths(); + for (const QString &p : prefsSearchPaths) { + if (!currentSearchPaths.contains(p) && FileInfo(p).exists()) + product->searchPaths << p; + } + + // Existing module provider search paths are re-used if and only if the provider configuration + // at setup time was the same as the current one for the respective module provider. + if (!m_moduleProviderInfo.empty()) { + const QVariantMap configForProduct = moduleProviderConfig(*product); + for (const ModuleProviderInfo &c : m_moduleProviderInfo) { + if (configForProduct.value(c.name.toString()).toMap() == c.config) { + qCDebug(lcModuleLoader) << "re-using search paths" << c.searchPaths + << "from module provider" << c.name + << "for product" << product->name; + product->knownModuleProviders.insert(c.name); + product->searchPaths << c.searchPaths; + } + } + } +} + +ModuleLoader::ShadowProductInfo ModuleLoader::getShadowProductInfo( + const ModuleLoader::ProductContext &product) const +{ + const bool isShadowProduct = product.name.startsWith(StringConstants::shadowProductPrefix()); + return std::make_pair(isShadowProduct, isShadowProduct + ? product.name.mid(StringConstants::shadowProductPrefix().size()) + : QString()); +} + +void ModuleLoader::collectProductsByName(const TopLevelProjectContext &topLevelProject) +{ + for (ProjectContext * const project : topLevelProject.projects) { + for (ProductContext &product : project->products) + m_productsByName.insert({ product.name, &product }); + } +} + +void ModuleLoader::collectProductsByType(const ModuleLoader::TopLevelProjectContext &topLevelProject) +{ + for (ProjectContext * const project : topLevelProject.projects) { + for (ProductContext &product : project->products) { + try { + const FileTags productTags + = m_evaluator->fileTagsValue(product.item, StringConstants::typeProperty()); + for (const FileTag &tag : productTags) + m_productsByType.insert({ tag, &product}); + } catch (const ErrorInfo &) { + qCDebug(lcModuleLoader) << "product" << product.name << "has complex type " + " and won't get an entry in the type map"; + } + } + } +} + +void ModuleLoader::propagateModulesFromParent(ProductContext *productContext, Item *groupItem, + const ModuleDependencies &reverseDepencencies) +{ + QBS_CHECK(groupItem->type() == ItemType::Group); + QHash moduleInstancesForGroup; + + // Step 1: Instantiate the product's modules for the group. + for (Item::Module m : groupItem->parent()->modules()) { + Item *targetItem = moduleInstanceItem(groupItem, m.name); + targetItem->setPrototype(m.item); + + Item * const moduleScope = Item::create(targetItem->pool(), ItemType::Scope); + moduleScope->setFile(groupItem->file()); + moduleScope->setProperties(m.item->scope()->properties()); // "project", "product", ids + moduleScope->setScope(groupItem); + targetItem->setScope(moduleScope); + + targetItem->setFile(m.item->file()); + + // "parent" should point to the group/artifact parent + targetItem->setParent(groupItem->parent()); + + targetItem->setOuterItem(m.item); + + m.item = targetItem; + groupItem->addModule(m); + moduleInstancesForGroup.insert(m.name, targetItem); + } + + // Step 2: Make the inter-module references point to the instances created in step 1. + for (const Item::Module &module : groupItem->modules()) { + Item::Modules adaptedModules; + const Item::Modules &oldModules = module.item->prototype()->modules(); + for (Item::Module depMod : oldModules) { + depMod.item = moduleInstancesForGroup.value(depMod.name); + adaptedModules << depMod; + if (depMod.name.front() == module.name.front()) + continue; + const ItemValuePtr &modulePrefix = groupItem->itemProperty(depMod.name.front()); + QBS_CHECK(modulePrefix); + module.item->setProperty(depMod.name.front(), modulePrefix); + } + module.item->setModules(adaptedModules); + } + + const QualifiedIdSet &propsSetInGroup = gatherModulePropertiesSetInGroup(groupItem); + if (propsSetInGroup.empty()) + return; + productContext->info.modulePropertiesSetInGroups + .insert(std::make_pair(groupItem, propsSetInGroup)); + + // Step 3: Adapt defining items in values. This is potentially necessary if module properties + // get assigned on the group level. + for (const Item::Module &module : groupItem->modules()) { + const QualifiedIdSet &dependents = reverseDepencencies.value(module.name); + Item::Modules dependentModules; + dependentModules.reserve(int(dependents.size())); + for (const QualifiedId &depName : dependents) { + Item * const itemOfDependent = moduleInstancesForGroup.value(depName); + QBS_CHECK(itemOfDependent); + Item::Module depMod; + depMod.name = depName; + depMod.item = itemOfDependent; + dependentModules << depMod; + } + adjustDefiningItemsInGroupModuleInstances(module, dependentModules); + } +} + +static Item *createReplacementForDefiningItem(const Item *definingItem, ItemType type) +{ + Item *replacement = Item::create(definingItem->pool(), type); + replacement->setLocation(definingItem->location()); + definingItem->copyProperty(StringConstants::nameProperty(), replacement); + return replacement; +} + +void ModuleLoader::adjustDefiningItemsInGroupModuleInstances(const Item::Module &module, + const Item::Modules &dependentModules) +{ + if (!module.item->isPresentModule()) + return; + + // There are three cases: + // a) The defining item is the "main" module instance, i.e. the one instantiated in the + // product directly (or a parent group). + // b) The defining item refers to the module prototype (or the replacement of it + // created in the module merger [for products] or in this function [for parent groups]). + // c) The defining item is a different instance of the module, i.e. it was instantiated + // in some other module. + + QHash definingItemReplacements; + + Item *modulePrototype = rootPrototype(module.item->prototype()); + QBS_CHECK(modulePrototype->type() == ItemType::Module + || modulePrototype->type() == ItemType::Export); + + const Item::PropertyDeclarationMap &propDecls = modulePrototype->propertyDeclarations(); + for (const auto &decl : propDecls) { + const QString &propName = decl.name(); + + // Module properties assigned in the group are not relevant here, as nothing + // gets inherited in that case. In particular, setting a list property + // overwrites the value from the product's (or parent group's) instance completely, + // rather than appending to it (concatenation happens via outer.concat()). + ValueConstPtr propValue = module.item->ownProperty(propName); + if (propValue) + continue; + + // Find the nearest prototype instance that has the value assigned. + // The result is either an instance of a parent group (or the parent group's + // parent group and so on) or the instance of the product or the module prototype. + // In the latter case, we don't have to do anything. + const Item *instanceWithProperty = module.item; + int prototypeChainLen = 0; + do { + instanceWithProperty = instanceWithProperty->prototype(); + QBS_CHECK(instanceWithProperty); + ++prototypeChainLen; + propValue = instanceWithProperty->ownProperty(propName); + } while (!propValue); + QBS_CHECK(propValue); + + if (propValue->type() != Value::JSSourceValueType) + continue; + + bool hasDefiningItem = false; + for (ValueConstPtr v = propValue; v && !hasDefiningItem; v = v->next()) + hasDefiningItem = v->definingItem(); + if (!hasDefiningItem) + continue; + + const ValuePtr clonedValue = propValue->clone(); + for (ValuePtr v = clonedValue; v; v = v->next()) { + QBS_CHECK(v->definingItem()); + + Item *& replacement = definingItemReplacements[v->definingItem()]; + static const QString caseA = QStringLiteral("__group_case_a"); + if (v->definingItem() == instanceWithProperty + || v->definingItem()->variantProperty(caseA)) { + // Case a) + // For values whose defining item is the product's (or parent group's) instance, + // we take its scope and replace references to module instances with those from the + // group's instance. This handles cases like the following: + // Product { + // name: "theProduct" + // aModule.listProp: [name, otherModule.stringProp] + // Group { name: "theGroup"; otherModule.stringProp: name } + // ... + // } + // In the above example, aModule.listProp is set to ["theProduct", "theGroup"] + // (plus potential values from the prototype and other module instances, + // which are different Value objects in the "next chain"). + if (!replacement) { + replacement = createReplacementForDefiningItem(v->definingItem(), + v->definingItem()->type()); + Item * const scope = Item::create(v->definingItem()->pool(), ItemType::Scope); + scope->setProperties(module.item->scope()->properties()); + Item * const scopeScope + = Item::create(v->definingItem()->pool(), ItemType::Scope); + scopeScope->setProperties(v->definingItem()->scope()->scope()->properties()); + scope->setScope(scopeScope); + replacement->setScope(scope); + const Item::PropertyMap &groupScopeProperties + = module.item->scope()->scope()->properties(); + for (auto propIt = groupScopeProperties.begin(); + propIt != groupScopeProperties.end(); ++propIt) { + if (propIt.value()->type() == Value::ItemValueType) + scopeScope->setProperty(propIt.key(), propIt.value()); + } + } + replacement->setPropertyDeclaration(propName, decl); + replacement->setProperty(propName, v); + replacement->setProperty(caseA, VariantValue::invalidValue()); + } else if (v->definingItem()->type() == ItemType::Module) { + // Case b) + // For values whose defining item is the module prototype, we change the scope to + // the group's instance, analogous to what we do in + // ModuleMerger::appendPrototypeValueToNextChain(). + QBS_CHECK(!decl.isScalar()); + QBS_CHECK(!v->next()); + Item *& replacement = definingItemReplacements[v->definingItem()]; + if (!replacement) { + replacement = createReplacementForDefiningItem(v->definingItem(), + ItemType::Module); + replacement->setScope(module.item); + } + QBS_CHECK(!replacement->hasOwnProperty(caseA)); + 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(); + if (v->type() == Value::JSSourceValueType) { + qCDebug(lcModuleLoader) << "value source code is" + << std::static_pointer_cast(v)->sourceCode().toString(); + } + replacement->setPropertyDeclaration(propName, decl); + replacement->setProperty(propName, v); + } else { + // Look for instance scopes of other module instances in defining items and + // replace the affected values. + // This is case c) as introduced above. See ModuleMerger::replaceItemInScopes() + // for a detailed explanation. + + QBS_CHECK(v->definingItem()->scope() && v->definingItem()->scope()->scope()); + bool found = false; + for (const Item::Module &depMod : dependentModules) { + const Item *depModPrototype = depMod.item->prototype(); + for (int i = 1; i < prototypeChainLen; ++i) + depModPrototype = depModPrototype->prototype(); + if (v->definingItem()->scope()->scope() != depModPrototype) + continue; + + found = true; + Item *& replacement = definingItemReplacements[v->definingItem()]; + if (!replacement) { + replacement = createReplacementForDefiningItem(v->definingItem(), + v->definingItem()->type()); + replacement->setProperties(v->definingItem()->properties()); + for (const auto &decl : v->definingItem()->propertyDeclarations()) + replacement->setPropertyDeclaration(decl.name(), decl); + replacement->setPrototype(v->definingItem()->prototype()); + replacement->setScope(Item::create(v->definingItem()->pool(), + ItemType::Scope)); + replacement->scope()->setScope(depMod.item); + } + QBS_CHECK(!replacement->hasOwnProperty(caseA)); + qCDebug(lcModuleLoader) << "reset instance scope of module" + << depMod.name.toString() << "in property" + << propName << "of module" << module.name; + } + QBS_CHECK(found); + } + QBS_CHECK(replacement); + v->setDefiningItem(replacement); + } + module.item->setProperty(propName, clonedValue); + } +} + +void ModuleLoader::resolveDependencies(DependsContext *dependsContext, Item *item, + ProductContext *productContext) +{ + QBS_CHECK(m_dependencyResolvingPass == 1 || m_dependencyResolvingPass == 2); + + if (!productContext || m_dependencyResolvingPass == 1) { + const Item::Module baseModule = loadBaseModule(dependsContext->product, item); + item->addModule(baseModule); + } + + // Resolve all Depends items. + ItemModuleList loadedModules; + QList dependsItemPerLoadedModule; + ProductDependencies productDependencies; + const auto handleDependsItem = [&](Item *child) { + if (child->type() != ItemType::Depends) + return; + + int lastModulesCount = loadedModules.size(); + try { + resolveDependsItem(dependsContext, child->parent(), child, &loadedModules, + &productDependencies); + } catch (const ErrorInfo &e) { + if (!productContext) + throw; + handleProductError(e, productContext); + } + for (int i = lastModulesCount; i < loadedModules.size(); ++i) + dependsItemPerLoadedModule.push_back(child); + + }; + if (productContext && m_dependencyResolvingPass == 2) { + for (const auto &deferData : productContext->deferredDependsItems) { + dependsContext->exportingProductItem = deferData.first; + for (Item * const dependsItem : deferData.second) + handleDependsItem(dependsItem); + } + } else { + for (Item * const child : item->children()) + handleDependsItem(child); + } + QBS_CHECK(loadedModules.size() == dependsItemPerLoadedModule.size()); + + Item *lastDependsItem = nullptr; + for (Item * const dependsItem : dependsItemPerLoadedModule) { + if (dependsItem == lastDependsItem) + continue; + adjustParametersScopes(dependsItem, dependsItem); + forwardParameterDeclarations(dependsItem, loadedModules); + lastDependsItem = dependsItem; + } + + for (int i = 0; i < loadedModules.size(); ++i) { + Item::Module &module = loadedModules[i]; + mergeParameters(module.parameters, extractParameters(dependsItemPerLoadedModule.at(i))); + item->addModule(module); + + const QString moduleName = module.name.toString(); + std::for_each(productDependencies.begin(), productDependencies.end(), + [&module, &moduleName] (ModuleLoaderResult::ProductInfo::Dependency &dep) { + if (dep.name == moduleName) + dep.parameters = module.parameters; + }); + } + + dependsContext->productDependencies->insert( + dependsContext->productDependencies->end(), + productDependencies.cbegin(), productDependencies.cend()); +} + +class RequiredChainManager +{ +public: + RequiredChainManager(std::vector &requiredChain, bool required) + : m_requiredChain(requiredChain) + { + m_requiredChain.push_back(required); + } + + ~RequiredChainManager() { m_requiredChain.pop_back(); } + +private: + std::vector &m_requiredChain; +}; + +void ModuleLoader::resolveDependsItem(DependsContext *dependsContext, Item *parentItem, + Item *dependsItem, ItemModuleList *moduleResults, + ProductDependencies *productResults) +{ + checkCancelation(); + if (!checkItemCondition(dependsItem)) { + qCDebug(lcModuleLoader) << "Depends item disabled, ignoring."; + return; + } + bool nameIsSet; + const QString name = m_evaluator->stringValue(dependsItem, StringConstants::nameProperty(), + QString(), &nameIsSet); + bool submodulesPropertySet; + const QStringList submodules = m_evaluator->stringListValue( + dependsItem, StringConstants::submodulesProperty(), &submodulesPropertySet); + if (submodules.empty() && submodulesPropertySet) { + qCDebug(lcModuleLoader) << "Ignoring Depends item with empty submodules list."; + return; + } + if (Q_UNLIKELY(submodules.size() > 1 && !dependsItem->id().isEmpty())) { + QString msg = Tr::tr("A Depends item with more than one module cannot have an id."); + throw ErrorInfo(msg, dependsItem->location()); + } + const FallbackMode fallbackMode = m_parameters.fallbackProviderEnabled() + && m_evaluator->boolValue(dependsItem, StringConstants::enableFallbackProperty()) + ? FallbackMode::Enabled : FallbackMode::Disabled; + + QList moduleNames; + const QualifiedId nameParts = QualifiedId::fromString(name); + if (submodules.empty()) { + // Ignore explicit dependencies on the base module, which has already been loaded. + if (name == StringConstants::qbsModule()) + return; + + moduleNames << nameParts; + } else { + for (const QString &submodule : submodules) + moduleNames << nameParts + QualifiedId::fromString(submodule); + } + + Item::Module result; + bool productTypesIsSet; + m_evaluator->stringValue(dependsItem, StringConstants::productTypesProperty(), + QString(), &productTypesIsSet); + if (m_dependencyResolvingPass == 1 && productTypesIsSet) { + qCDebug(lcModuleLoader) << "queuing product" << dependsContext->product->name + << "for a second dependencies resolving pass"; + m_productsWithDeferredDependsItems[dependsContext->product].insert( + DeferredDependsContext(dependsContext->exportingProductItem, parentItem)); + return; + } + + const bool isRequiredValue = + m_evaluator->boolValue(dependsItem, StringConstants::requiredProperty()); + const bool isRequired = !productTypesIsSet + && isRequiredValue + && !contains(m_requiredChain, false); + const Version minVersion = Version::fromString( + m_evaluator->stringValue(dependsItem, + StringConstants::versionAtLeastProperty())); + const Version maxVersion = Version::fromString( + m_evaluator->stringValue(dependsItem, StringConstants::versionBelowProperty())); + const VersionRange versionRange(minVersion, maxVersion); + QStringList multiplexConfigurationIds = m_evaluator->stringListValue( + dependsItem, + StringConstants::multiplexConfigurationIdsProperty()); + if (multiplexConfigurationIds.empty()) + multiplexConfigurationIds << QString(); + + for (const QualifiedId &moduleName : qAsConst(moduleNames)) { + // Don't load the same module twice. Duplicate Depends statements can easily + // happen due to inheritance. + const auto it = std::find_if(moduleResults->begin(), moduleResults->end(), + [moduleName](const Item::Module &m) { return m.name == moduleName; }); + if (it != moduleResults->end()) { + it->required = it->required || isRequired; + it->requiredValue = it->requiredValue || isRequiredValue; + it->versionRange.narrowDown(versionRange); + continue; + } + + QVariantMap defaultParameters; + Item *moduleItem = loadModule(dependsContext->product, dependsContext->exportingProductItem, + parentItem, dependsItem->location(), dependsItem->id(), + moduleName, multiplexConfigurationIds.first(), fallbackMode, + isRequired, &result.isProduct, &defaultParameters); + if (!moduleItem) { + const QString productName = ResolvedProduct::fullDisplayName( + dependsContext->product->name, + dependsContext->product->multiplexConfigurationId); + if (!multiplexConfigurationIds.first().isEmpty()) { + const QString depName = ResolvedProduct::fullDisplayName( + moduleName.toString(), multiplexConfigurationIds.first()); + throw ErrorInfo(Tr::tr("Dependency from product '%1' to product '%2' not " + "fulfilled.").arg(productName, depName)); + } + ErrorInfo e(Tr::tr("Dependency '%1' not found for product '%2'.") + .arg(moduleName.toString(), productName), dependsItem->location()); + throw e; + } + 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()); + } + qCDebug(lcModuleLoader) << "module loaded:" << moduleName.toString(); + result.name = moduleName; + result.item = moduleItem; + result.requiredValue = isRequiredValue; + result.required = isRequired; + result.parameters = defaultParameters; + result.versionRange = versionRange; + moduleResults->push_back(result); + if (result.isProduct) { + qCDebug(lcModuleLoader) << "product dependency loaded:" << moduleName.toString(); + bool profilesPropertyWasSet = false; + QStringList profiles = m_evaluator->stringListValue(dependsItem, + StringConstants::profilesProperty(), + &profilesPropertyWasSet); + if (profiles.empty()) { + if (profilesPropertyWasSet) + profiles.push_back(StringConstants::star()); + else + profiles.push_back(QString()); + } + for (const QString &profile : qAsConst(profiles)) { + for (const QString &multiplexId : qAsConst(multiplexConfigurationIds)) { + ModuleLoaderResult::ProductInfo::Dependency dependency; + dependency.name = moduleName.toString(); + dependency.profile = profile; + dependency.multiplexConfigurationId = multiplexId; + dependency.isRequired = isRequired; + productResults->push_back(dependency); + } + } + } + } +} + +void ModuleLoader::forwardParameterDeclarations(const Item *dependsItem, + const ItemModuleList &modules) +{ + for (auto it = dependsItem->properties().begin(); it != dependsItem->properties().end(); ++it) { + if (it.value()->type() != Value::ItemValueType) + continue; + forwardParameterDeclarations(it.key(), + std::static_pointer_cast(it.value())->item(), + modules); + } +} + +void ModuleLoader::forwardParameterDeclarations(const QualifiedId &moduleName, Item *item, + const ItemModuleList &modules) +{ + auto it = std::find_if(modules.begin(), modules.end(), [&moduleName] (const Item::Module &m) { + return m.name == moduleName; + }); + if (it != modules.end()) { + item->setPropertyDeclarations(m_parameterDeclarations.value(rootPrototype(it->item))); + } else { + for (auto it = item->properties().begin(); it != item->properties().end(); ++it) { + if (it.value()->type() != Value::ItemValueType) + continue; + forwardParameterDeclarations(QualifiedId(moduleName) << it.key(), + std::static_pointer_cast(it.value())->item(), + modules); + } + } +} + +void ModuleLoader::resolveParameterDeclarations(const Item *module) +{ + Item::PropertyDeclarationMap decls; + const auto &moduleChildren = module->children(); + for (Item *param : moduleChildren) { + if (param->type() != ItemType::Parameter) + continue; + const auto ¶mDecls = param->propertyDeclarations(); + for (auto it = paramDecls.begin(); it != paramDecls.end(); ++it) + decls.insert(it.key(), it.value()); + } + m_parameterDeclarations.insert(module, decls); +} + +static bool isItemValue(const ValuePtr &v) +{ + return v->type() == Value::ItemValueType; +} + +static Item::PropertyMap filterItemProperties(const Item::PropertyMap &properties) +{ + Item::PropertyMap result; + auto itEnd = properties.end(); + for (auto it = properties.begin(); it != itEnd; ++it) { + if (isItemValue(it.value())) + result.insert(it.key(), it.value()); + } + return result; +} + +static QVariantMap safeToVariant(const QScriptValue &v) +{ + QVariantMap result; + QScriptValueIterator it(v); + while (it.hasNext()) { + it.next(); + QScriptValue u = it.value(); + if (u.isError()) + throw ErrorInfo(u.toString()); + result[it.name()] = (u.isObject() && !u.isArray() && !u.isRegExp()) + ? safeToVariant(u) : it.value().toVariant(); + } + return result; +} + +QVariantMap ModuleLoader::extractParameters(Item *dependsItem) const +{ + QVariantMap result; + const Item::PropertyMap &itemProperties = filterItemProperties( + rootPrototype(dependsItem)->properties()); + if (itemProperties.empty()) + return result; + + auto origProperties = dependsItem->properties(); + dependsItem->setProperties(itemProperties); + QScriptValue sv = m_evaluator->scriptValue(dependsItem); + try { + result = safeToVariant(sv); + } catch (const ErrorInfo &exception) { + auto ei = exception; + ei.prepend(Tr::tr("Error in dependency parameter."), dependsItem->location()); + throw ei; + } + dependsItem->setProperties(origProperties); + return result; +} + +[[noreturn]] static void throwModuleNamePrefixError(const QualifiedId &shortName, + const QualifiedId &longName, const CodeLocation &codeLocation) +{ + throw ErrorInfo(Tr::tr("The name of module '%1' is equal to the first component of the " + "name of module '%2', which is not allowed") + .arg(shortName.toString(), longName.toString()), codeLocation); +} + +Item *ModuleLoader::moduleInstanceItem(Item *containerItem, const QualifiedId &moduleName) +{ + QBS_CHECK(!moduleName.empty()); + Item *instance = containerItem; + for (int i = 0; i < moduleName.size(); ++i) { + const QString &moduleNameSegment = moduleName.at(i); + const ValuePtr v = instance->ownProperty(moduleName.at(i)); + if (v && v->type() == Value::ItemValueType) { + instance = std::static_pointer_cast(v)->item(); + } else { + const ItemType itemType = i < moduleName.size() - 1 ? ItemType::ModulePrefix + : ItemType::ModuleInstance; + auto newItem = Item::create(m_pool, itemType); + instance->setProperty(moduleNameSegment, ItemValue::create(newItem)); + instance = newItem; + } + if (i < moduleName.size() - 1) { + if (instance->type() == ItemType::ModuleInstance) { + QualifiedId conflictingName = QStringList(moduleName.mid(0, i + 1)); + throwModuleNamePrefixError(conflictingName, moduleName, CodeLocation()); + } + QBS_CHECK(instance->type() == ItemType::ModulePrefix); + } + } + QBS_CHECK(instance != containerItem); + return instance; +} + +ModuleLoader::ProductModuleInfo *ModuleLoader::productModule(ProductContext *productContext, + const QString &name, const QString &multiplexId, bool &productNameMatch) +{ + auto &exportsData = productContext->project->topLevelProject->productModules; + const auto firstIt = exportsData.find(name); + productNameMatch = firstIt != exportsData.end(); + for (auto it = firstIt; it != exportsData.end() && it.key() == name; ++it) { + if (it.value().multiplexId == multiplexId) + return &it.value(); + } + if (multiplexId.isEmpty() && firstIt != exportsData.end()) + return &firstIt.value(); + return nullptr; +} + +ModuleLoader::ProductContext *ModuleLoader::product(ProjectContext *projectContext, + const QString &name) +{ + 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 result; +} + +class ModuleLoader::DependsChainManager +{ +public: + DependsChainManager(std::vector &dependsChain, const QualifiedId &module, + const CodeLocation &dependsLocation) + : m_dependsChain(dependsChain) + { + const bool alreadyInChain = std::any_of(dependsChain.cbegin(), dependsChain.cend(), + [&module](const DependsChainEntry &e) { + 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.name.toString(), e.location); + error.append(module.toString(), dependsLocation); + throw error; + } + m_dependsChain.emplace_back(module, dependsLocation); + } + + ~DependsChainManager() { m_dependsChain.pop_back(); } + +private: + std::vector &m_dependsChain; +}; + +static bool isBaseModule(const QualifiedId &moduleName) +{ + return moduleName.size() == 1 && moduleName.front() == StringConstants::qbsModule(); +} + +class DelayedPropertyChanger +{ +public: + ~DelayedPropertyChanger() + { + applyNow(); + } + + void setLater(Item *item, const QString &name, const ValuePtr &value) + { + QBS_CHECK(m_item == nullptr); + m_item = item; + m_name = name; + m_value = value; + } + + void removeLater(Item *item, const QString &name) + { + QBS_CHECK(m_item == nullptr); + m_item = item; + m_name = name; + } + + void applyNow() + { + if (!m_item || m_name.isEmpty()) + return; + if (m_value) + m_item->setProperty(m_name, m_value); + else + m_item->removeProperty(m_name); + m_item = nullptr; + m_name.clear(); + m_value.reset(); + } + +private: + Item *m_item = nullptr; + QString m_name; + ValuePtr m_value; +}; + +Item *ModuleLoader::loadModule(ProductContext *productContext, Item *exportingProductItem, + Item *item, const CodeLocation &dependsItemLocation, + const QString &moduleId, const QualifiedId &moduleName, + const QString &multiplexId, FallbackMode fallbackMode, + bool isRequired, bool *isProductDependency, + QVariantMap *defaultParameters) +{ + qCDebug(lcModuleLoader) << "loadModule name:" << moduleName.toString() << "id:" << moduleId; + + RequiredChainManager requiredChainManager(m_requiredChain, isRequired); + DependsChainManager dependsChainManager(m_dependsChain, moduleName, dependsItemLocation); + + Item *moduleInstance = moduleId.isEmpty() + ? moduleInstanceItem(item, moduleName) + : moduleInstanceItem(item, QStringList(moduleId)); + if (moduleInstance->scope()) + return moduleInstance; // already handled + + if (Q_UNLIKELY(moduleInstance->type() == ItemType::ModulePrefix)) { + for (const Item::Module &m : item->modules()) { + if (m.name.front() == moduleName.front()) + throwModuleNamePrefixError(moduleName, m.name, dependsItemLocation); + } + } + QBS_CHECK(moduleInstance->type() == ItemType::ModuleInstance); + + // Prepare module instance for evaluating Module.condition. + DelayedPropertyChanger delayedPropertyChanger; + const QString &qbsModuleName = StringConstants::qbsModule(); + if (!isBaseModule(moduleName)) { + ItemValuePtr qbsProp = productContext->item->itemProperty(qbsModuleName); + if (qbsProp) { + ValuePtr qbsModuleValue = moduleInstance->ownProperty(qbsModuleName); + if (qbsModuleValue) + delayedPropertyChanger.setLater(moduleInstance, qbsModuleName, qbsModuleValue); + else + delayedPropertyChanger.removeLater(moduleInstance, qbsModuleName); + moduleInstance->setProperty(qbsModuleName, qbsProp); + } + } + + Item *modulePrototype = nullptr; + ProductModuleInfo * const pmi = productModule(productContext, moduleName.toString(), + multiplexId, *isProductDependency); + if (pmi) { + m_dependsChain.back().isProduct = true; + modulePrototype = pmi->exportItem; + if (defaultParameters) + *defaultParameters = pmi->defaultParameters; + } else if (!*isProductDependency) { + modulePrototype = searchAndLoadModuleFile(productContext, dependsItemLocation, + moduleName, fallbackMode, isRequired, moduleInstance); + } + delayedPropertyChanger.applyNow(); + if (!modulePrototype) + return nullptr; + + instantiateModule(productContext, exportingProductItem, item, moduleInstance, modulePrototype, + moduleName, pmi); + return moduleInstance; +} + +struct PrioritizedItem +{ + PrioritizedItem(Item *item, int priority, int searchPathIndex) + : item(item), priority(priority), searchPathIndex(searchPathIndex) + { + } + + Item *item = nullptr; + int priority = 0; + int searchPathIndex = 0; +}; + +static Item *chooseModuleCandidate(const std::vector &candidates, + const QString &moduleName) +{ + auto maxIt = std::max_element(candidates.begin(), candidates.end(), + [] (const PrioritizedItem &a, const PrioritizedItem &b) { + if (a.priority < b.priority) + return true; + if (a.priority > b.priority) + return false; + return a.searchPathIndex > b.searchPathIndex; + }); + + size_t nmax = std::count_if(candidates.begin(), candidates.end(), + [maxIt] (const PrioritizedItem &i) { + return i.priority == maxIt->priority && i.searchPathIndex == maxIt->searchPathIndex; + }); + + if (nmax > 1) { + ErrorInfo e(Tr::tr("There is more than one equally prioritized candidate for module '%1'.") + .arg(moduleName)); + for (size_t i = 0; i < candidates.size(); ++i) { + const auto candidate = candidates.at(i); + if (candidate.priority == maxIt->priority) { + //: The %1 denotes the number of the candidate. + e.append(Tr::tr("candidate %1").arg(i + 1), candidates.at(i).item->location()); + } + } + throw e; + } + + return maxIt->item; +} + +Item *ModuleLoader::searchAndLoadModuleFile(ProductContext *productContext, + const CodeLocation &dependsItemLocation, const QualifiedId &moduleName, + FallbackMode fallbackMode, bool isRequired, Item *moduleInstance) +{ + auto existingPaths = findExistingModulePaths(m_reader->allSearchPaths(), moduleName); + + if (existingPaths.isEmpty()) { // no suitable names found, try to use providers + bool moduleAlreadyKnown = false; + ModuleProviderResult result; + for (QualifiedId providerName = moduleName; !providerName.empty(); + providerName.pop_back()) { + if (!productContext->knownModuleProviders.insert(providerName).second) { + moduleAlreadyKnown = true; + break; + } + qCDebug(lcModuleLoader) << "Module" << moduleName.toString() + << "not found, checking for module providers"; + result = findModuleProvider(providerName, *productContext, + ModuleProviderLookup::Regular, dependsItemLocation); + if (result.providerFound) + break; + } + if (fallbackMode == FallbackMode::Enabled && !result.providerFound + && !moduleAlreadyKnown) { + qCDebug(lcModuleLoader) << "Specific module provider not found for" + << moduleName.toString() << ", setting up fallback."; + result = findModuleProvider(moduleName, *productContext, + ModuleProviderLookup::Fallback, dependsItemLocation); + } + if (result.providerAddedSearchPaths) { + qCDebug(lcModuleLoader) << "Re-checking for module" << moduleName.toString() + << "with newly added search paths from module provider"; + existingPaths = findExistingModulePaths(m_reader->allSearchPaths(), moduleName); + } + } + + const QString fullName = moduleName.toString(); + bool triedToLoadModule = false; + std::vector candidates; + candidates.reserve(size_t(existingPaths.size())); + for (int i = 0; i < existingPaths.size(); ++i) { + const QString &dirPath = existingPaths.at(i); + QStringList &moduleFileNames = getModuleFileNames(dirPath); + for (auto it = moduleFileNames.begin(); it != moduleFileNames.end(); ) { + const QString &filePath = *it; + bool triedToLoad = true; + Item *module = loadModuleFile(productContext, fullName, isBaseModule(moduleName), + filePath, &triedToLoad, moduleInstance); + if (module) + candidates.emplace_back(module, 0, i); + if (!triedToLoad) + it = moduleFileNames.erase(it); + else + ++it; + triedToLoadModule = triedToLoadModule || triedToLoad; + } + } + + if (candidates.empty()) { + if (!isRequired) + return createNonPresentModule(fullName, QStringLiteral("not found"), nullptr); + if (Q_UNLIKELY(triedToLoadModule)) { + throw ErrorInfo(Tr::tr("Module %1 could not be loaded.").arg(fullName), + dependsItemLocation); + } + return nullptr; + } + + Item *moduleItem; + if (candidates.size() == 1) { + moduleItem = candidates.at(0).item; + } else { + for (auto &candidate : candidates) { + candidate.priority = m_evaluator->intValue(candidate.item, + StringConstants::priorityProperty(), + candidate.priority); + } + moduleItem = chooseModuleCandidate(candidates, fullName); + } + + const auto it = productContext->unknownProfilePropertyErrors.find(moduleItem); + if (it != productContext->unknownProfilePropertyErrors.cend()) { + const QString fullProductName = ResolvedProduct::fullDisplayName + (productContext->name, productContext->multiplexConfigurationId); + ErrorInfo error(Tr::tr("Loading module '%1' for product '%2' failed due to invalid values " + "in profile '%3':").arg(fullName, fullProductName, + productContext->profileName)); + for (const ErrorInfo &e : it->second) + error.append(e.toString()); + handlePropertyError(error, m_parameters, m_logger); + } + return moduleItem; +} + +QStringList &ModuleLoader::getModuleFileNames(const QString &dirPath) +{ + QStringList &moduleFileNames = m_moduleDirListCache[dirPath]; + if (moduleFileNames.empty()) { + QDirIterator dirIter(dirPath, StringConstants::qbsFileWildcards()); + while (dirIter.hasNext()) + moduleFileNames += dirIter.next(); + } + return moduleFileNames; +} + +// returns QVariant::Invalid for types that do not need conversion +static QVariant::Type variantType(PropertyDeclaration::Type t) +{ + switch (t) { + case PropertyDeclaration::UnknownType: + break; + case PropertyDeclaration::Boolean: + return QVariant::Bool; + case PropertyDeclaration::Integer: + return QVariant::Int; + case PropertyDeclaration::Path: + return QVariant::String; + case PropertyDeclaration::PathList: + return QVariant::StringList; + case PropertyDeclaration::String: + return QVariant::String; + case PropertyDeclaration::StringList: + return QVariant::StringList; + case PropertyDeclaration::VariantList: + return QVariant::List; + case PropertyDeclaration::Variant: + break; + } + return QVariant::Invalid; +} + +static QVariant convertToPropertyType(const QVariant &v, PropertyDeclaration::Type t, + const QStringList &namePrefix, const QString &key) +{ + if (v.isNull() || !v.isValid()) + return v; + const QVariant::Type vt = variantType(t); + if (vt == QVariant::Invalid) + return v; + + // Handle the foo,bar,bla stringlist syntax. + if (t == PropertyDeclaration::StringList && v.type() == QVariant::String) + return v.toString().split(QLatin1Char(',')); + + QVariant c = v; + if (!c.convert(vt)) { + QStringList name = namePrefix; + name << key; + throw ErrorInfo(Tr::tr("Value '%1' of property '%2' has incompatible type.") + .arg(v.toString(), name.join(QLatin1Char('.')))); + } + return c; +} + +static Item *findDeepestModuleInstance(Item *instance) +{ + while (instance->prototype() && instance->prototype()->type() == ItemType::ModuleInstance) + instance = instance->prototype(); + return instance; +} + +Item *ModuleLoader::loadModuleFile(ProductContext *productContext, const QString &fullModuleName, + bool isBaseModule, const QString &filePath, bool *triedToLoad, Item *moduleInstance) +{ + checkCancelation(); + + qCDebug(lcModuleLoader) << "loadModuleFile" << fullModuleName << "from" << filePath; + + Item * const module = getModulePrototype(productContext, fullModuleName, filePath, triedToLoad); + if (!module) + return nullptr; + + const auto key = std::make_pair(module, productContext); + const auto it = m_modulePrototypeEnabledInfo.find(key); + if (it != m_modulePrototypeEnabledInfo.end()) { + qCDebug(lcModuleLoader) << "prototype cache hit (level 2)"; + return it.value() ? module : nullptr; + } + + // Set the name before evaluating any properties. EvaluatorScriptClass reads the module name. + module->setProperty(StringConstants::nameProperty(), VariantValue::create(fullModuleName)); + + Item *deepestModuleInstance = findDeepestModuleInstance(moduleInstance); + Item *origDeepestModuleInstancePrototype = deepestModuleInstance->prototype(); + deepestModuleInstance->setPrototype(module); + bool enabled = checkItemCondition(moduleInstance, module); + deepestModuleInstance->setPrototype(origDeepestModuleInstancePrototype); + if (!enabled) { + qCDebug(lcModuleLoader) << "condition of module" << fullModuleName << "is false"; + m_modulePrototypeEnabledInfo.insert(key, false); + return nullptr; + } + + if (isBaseModule) + setupBaseModulePrototype(module); + else + resolveParameterDeclarations(module); + + m_modulePrototypeEnabledInfo.insert(key, true); + return module; +} + +Item *ModuleLoader::getModulePrototype(ProductContext *productContext, + const QString &fullModuleName, const QString &filePath, bool *triedToLoad) +{ + auto &prototypeList = m_modulePrototypes[filePath]; + for (const auto &prototype : prototypeList) { + if (prototype.second == productContext->profileName) { + qCDebug(lcModuleLoader) << "prototype cache hit (level 1)"; + return prototype.first; + } + } + Item * const module = loadItemFromFile(filePath, CodeLocation()); + prototypeList.emplace_back(module, productContext->profileName); + if (module->type() != ItemType::Module) { + qCDebug(lcModuleLoader).nospace() + << "Alleged module " << fullModuleName << " has type '" + << module->typeName() << "', so it's not a module after all."; + *triedToLoad = false; + return nullptr; + } + + // Module properties that are defined in the profile are used as default values. + // This is the reason we need to have different items per profile. + const QVariantMap profileModuleProperties + = productContext->moduleProperties.value(fullModuleName).toMap(); + for (QVariantMap::const_iterator vmit = profileModuleProperties.begin(); + vmit != profileModuleProperties.end(); ++vmit) + { + if (Q_UNLIKELY(!module->hasProperty(vmit.key()))) { + productContext->unknownProfilePropertyErrors[module].emplace_back + (Tr::tr("Unknown property: %1.%2").arg(fullModuleName, vmit.key())); + continue; + } + const PropertyDeclaration decl = module->propertyDeclaration(vmit.key()); + VariantValuePtr v = VariantValue::create(convertToPropertyType(vmit.value(), decl.type(), + QStringList(fullModuleName), vmit.key())); + module->setProperty(vmit.key(), v); + } + + return module; +} + +Item::Module ModuleLoader::loadBaseModule(ProductContext *productContext, Item *item) +{ + const QualifiedId baseModuleName(StringConstants::qbsModule()); + Item::Module baseModuleDesc; + baseModuleDesc.name = baseModuleName; + baseModuleDesc.item = loadModule(productContext, nullptr, item, CodeLocation(), QString(), + baseModuleName, QString(), FallbackMode::Disabled, true, + &baseModuleDesc.isProduct, nullptr); + if (productContext->item) { + const Item * const qbsInstanceItem + = moduleInstanceItem(productContext->item, baseModuleName); + const Item::PropertyMap &props = qbsInstanceItem->properties(); + for (auto it = props.cbegin(); it != props.cend(); ++it) { + if (it.value()->type() == Value::VariantValueType) + baseModuleDesc.item->setProperty(it.key(), it.value()); + } + } + QBS_CHECK(!baseModuleDesc.isProduct); + if (Q_UNLIKELY(!baseModuleDesc.item)) + throw ErrorInfo(Tr::tr("Cannot load base qbs module.")); + return baseModuleDesc; +} + +void ModuleLoader::setupBaseModulePrototype(Item *prototype) +{ + prototype->setProperty(QStringLiteral("hostPlatform"), + VariantValue::create(QString::fromStdString( + HostOsInfo::hostOSIdentifier()))); + prototype->setProperty(QStringLiteral("hostArchitecture"), + VariantValue::create(QString::fromStdString( + HostOsInfo::hostOSArchitecture()))); + prototype->setProperty(QStringLiteral("libexecPath"), + VariantValue::create(m_parameters.libexecPath())); + + const Version qbsVersion = LanguageInfo::qbsVersion(); + prototype->setProperty(QStringLiteral("versionMajor"), + VariantValue::create(qbsVersion.majorVersion())); + prototype->setProperty(QStringLiteral("versionMinor"), + VariantValue::create(qbsVersion.minorVersion())); + prototype->setProperty(QStringLiteral("versionPatch"), + VariantValue::create(qbsVersion.patchLevel())); +} + +static void collectItemsWithId_impl(Item *item, QList *result) +{ + if (!item->id().isEmpty()) + result->push_back(item); + for (Item * const child : item->children()) + collectItemsWithId_impl(child, result); +} + +static QList collectItemsWithId(Item *item) +{ + QList result; + collectItemsWithId_impl(item, &result); + return result; +} + +static std::vector> instanceItemProperties(Item *item) +{ + std::vector> result; + QualifiedId name; + std::function 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(it.value()); + if (!itemValue->item()) + continue; + name.push_back(it.key()); + if (itemValue->item()->type() == ItemType::ModulePrefix) + f(itemValue->item()); + else + result.emplace_back(name, itemValue); + name.removeLast(); + } + }; + f(item); + return result; +} + +void ModuleLoader::instantiateModule(ProductContext *productContext, Item *exportingProduct, + Item *instanceScope, Item *moduleInstance, Item *modulePrototype, + const QualifiedId &moduleName, ProductModuleInfo *productModuleInfo) +{ + Item *deepestModuleInstance = findDeepestModuleInstance(moduleInstance); + deepestModuleInstance->setPrototype(modulePrototype); + const QString fullName = moduleName.toString(); + const QString generalOverrideKey = QStringLiteral("modules.") + fullName; + const QString perProductOverrideKey = StringConstants::productsOverridePrefix() + + productContext->name + QLatin1Char('.') + fullName; + for (Item *instance = moduleInstance; instance; instance = instance->prototype()) { + overrideItemProperties(instance, generalOverrideKey, m_parameters.overriddenValuesTree()); + if (fullName == QStringLiteral("qbs")) + overrideItemProperties(instance, fullName, 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); + + // create module scope + Item *moduleScope = Item::create(m_pool, ItemType::Scope); + QBS_CHECK(instanceScope->file()); + moduleScope->setFile(instanceScope->file()); + moduleScope->setScope(instanceScope); + QBS_CHECK(productContext->project->scope); + productContext->project->scope->copyProperty(StringConstants::projectVar(), moduleScope); + if (productContext->scope) + productContext->scope->copyProperty(StringConstants::productVar(), moduleScope); + else + QBS_CHECK(fullName == StringConstants::qbsModule()); // Dummy product. + + if (productModuleInfo) { + exportingProduct = productModuleInfo->exportItem->parent(); + QBS_CHECK(exportingProduct); + QBS_CHECK(exportingProduct->type() == ItemType::Product); + } + + if (exportingProduct) { + // TODO: For consistency with modules, it should be the other way around, i.e. + // "exportingProduct" and just "product". + moduleScope->setProperty(StringConstants::productVar(), + ItemValue::create(exportingProduct)); + moduleScope->setProperty(QStringLiteral("importingProduct"), + ItemValue::create(productContext->item)); + + moduleScope->setProperty(StringConstants::projectVar(), + ItemValue::create(exportingProduct->parent())); + + PropertyDeclaration pd(StringConstants::qbsSourceDirPropertyInternal(), + PropertyDeclaration::String, QString(), + PropertyDeclaration::PropertyNotAvailableInConfig); + moduleInstance->setPropertyDeclaration(pd.name(), pd); + ValuePtr v = exportingProduct + ->property(StringConstants::sourceDirectoryProperty())->clone(); + moduleInstance->setProperty(pd.name(), v); + } + moduleInstance->setScope(moduleScope); + + QHash prototypeInstanceMap; + prototypeInstanceMap[modulePrototype] = moduleInstance; + + // create instances for every child of the prototype + createChildInstances(moduleInstance, modulePrototype, &prototypeInstanceMap); + + // create ids from from the prototype in the instance + if (modulePrototype->file()->idScope()) { + const auto items = collectItemsWithId(modulePrototype); + for (Item * const itemWithId : items) { + Item *idProto = itemWithId; + Item *idInstance = prototypeInstanceMap.value(idProto); + QBS_ASSERT(idInstance, continue); + ItemValuePtr idInstanceValue = ItemValue::create(idInstance); + moduleScope->setProperty(itemWithId->id(), idInstanceValue); + } + } + + // For foo.bar in modulePrototype create an item foo in moduleInstance. + for (const auto &iip : instanceItemProperties(modulePrototype)) { + if (iip.second->item()->properties().empty()) + 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.front()); + for (int i = 1; i < iip.first.size(); ++i) + itemValue = itemValue->item()->itemProperty(iip.first.at(i)); + itemValue->setCreatedByPropertiesBlock(true); + } + } + + // 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. + const auto end = moduleInstance->properties().cend(); + for (auto it = moduleInstance->properties().cbegin(); it != end; ++it) { + const PropertyDeclaration &pd = moduleInstance->propertyDeclaration(it.key()); + if (!pd.flags().testFlag(PropertyDeclaration::ReadOnlyFlag)) + continue; + throw ErrorInfo(Tr::tr("Cannot set read-only property '%1'.").arg(pd.name()), + moduleInstance->property(pd.name())->location()); + } +} + +void ModuleLoader::createChildInstances(Item *instance, Item *prototype, + QHash *prototypeInstanceMap) const +{ + instance->childrenReserve(instance->children().size() + prototype->children().size()); + + for (Item * const childPrototype : prototype->children()) { + Item *childInstance = Item::create(m_pool, childPrototype->type()); + prototypeInstanceMap->insert(childPrototype, childInstance); + childInstance->setPrototype(childPrototype); + childInstance->setFile(childPrototype->file()); + childInstance->setId(childPrototype->id()); + childInstance->setLocation(childPrototype->location()); + childInstance->setScope(instance->scope()); + Item::addChild(instance, childInstance); + createChildInstances(childInstance, childPrototype, prototypeInstanceMap); + } +} + +void ModuleLoader::resolveProbes(ProductContext *productContext, Item *item) +{ + AccumulatingTimer probesTimer(m_parameters.logElapsedTime() ? &m_elapsedTimeProbes : nullptr); + EvalContextSwitcher evalContextSwitcher(m_evaluator->engine(), EvalContext::ProbeExecution); + for (Item * const child : item->children()) + if (child->type() == ItemType::Probe) + resolveProbe(productContext, item, child); +} + +void ModuleLoader::resolveProbe(ProductContext *productContext, Item *parent, Item *probe) +{ + qCDebug(lcModuleLoader) << "Resolving Probe at " << probe->location().toString(); + ++m_probesEncountered; + const QString &probeId = probeGlobalId(probe); + if (Q_UNLIKELY(probeId.isEmpty())) + throw ErrorInfo(Tr::tr("Probe.id must be set."), probe->location()); + const JSSourceValueConstPtr configureScript + = probe->sourceProperty(StringConstants::configureProperty()); + QBS_CHECK(configureScript); + if (Q_UNLIKELY(configureScript->sourceCode() == StringConstants::undefinedValue())) + throw ErrorInfo(Tr::tr("Probe.configure must be set."), probe->location()); + using ProbeProperty = std::pair; + std::vector probeBindings; + QVariantMap initialProperties; + for (Item *obj = probe; obj; obj = obj->prototype()) { + const Item::PropertyMap &props = obj->properties(); + for (auto it = props.cbegin(); it != props.cend(); ++it) { + const QString &name = it.key(); + if (name == StringConstants::configureProperty()) + continue; + const QScriptValue value = m_evaluator->value(probe, name); + probeBindings << ProbeProperty(name, value); + if (name != StringConstants::conditionProperty()) + initialProperties.insert(name, value.toVariant()); + } + } + ScriptEngine * const engine = m_evaluator->engine(); + QScriptValue configureScope; + const bool condition = m_evaluator->boolValue(probe, StringConstants::conditionProperty()); + const QString &sourceCode = configureScript->sourceCode().toString(); + ProbeConstPtr resolvedProbe; + if (parent->type() == ItemType::Project + || productContext->name.startsWith(StringConstants::shadowProductPrefix())) { + resolvedProbe = findOldProjectProbe(probeId, condition, initialProperties, sourceCode); + } else { + const QString &uniqueProductName = productContext->uniqueName(); + resolvedProbe + = findOldProductProbe(uniqueProductName, condition, initialProperties, sourceCode); + } + if (!resolvedProbe) { + resolvedProbe = findCurrentProbe(probe->location(), condition, initialProperties); + if (resolvedProbe) { + qCDebug(lcModuleLoader) << "probe results cached from current run"; + ++m_probesCachedCurrent; + } + } else { + qCDebug(lcModuleLoader) << "probe results cached from earlier run"; + ++m_probesCachedOld; + } + std::vector importedFilesUsedInConfigure; + if (!condition) { + qCDebug(lcModuleLoader) << "Probe disabled; skipping"; + } else if (!resolvedProbe) { + ++m_probesRun; + qCDebug(lcModuleLoader) << "configure script needs to run"; + const Evaluator::FileContextScopes fileCtxScopes + = m_evaluator->fileContextScopes(configureScript->file()); + engine->currentContext()->pushScope(fileCtxScopes.fileScope); + engine->currentContext()->pushScope(fileCtxScopes.importScope); + configureScope = engine->newObject(); + for (const ProbeProperty &b : probeBindings) + configureScope.setProperty(b.first, b.second); + engine->currentContext()->pushScope(configureScope); + engine->clearRequestedProperties(); + QScriptValue sv = engine->evaluate(configureScript->sourceCodeForEvaluation()); + engine->currentContext()->popScope(); + engine->currentContext()->popScope(); + engine->currentContext()->popScope(); + engine->releaseResourcesOfScriptObjects(); + if (Q_UNLIKELY(engine->hasErrorOrException(sv))) + throw ErrorInfo(engine->lastErrorString(sv), configureScript->location()); + importedFilesUsedInConfigure = engine->importedFilesUsedInScript(); + } else { + importedFilesUsedInConfigure = resolvedProbe->importedFilesUsed(); + } + QVariantMap properties; + for (const ProbeProperty &b : probeBindings) { + QVariant newValue; + if (resolvedProbe) { + newValue = resolvedProbe->properties().value(b.first); + } else { + if (condition) { + QScriptValue v = configureScope.property(b.first); + m_evaluator->convertToPropertyType(probe->propertyDeclaration( + b.first), probe->location(), v); + if (Q_UNLIKELY(engine->hasErrorOrException(v))) + throw ErrorInfo(engine->lastError(v)); + newValue = v.toVariant(); + } else { + newValue = initialProperties.value(b.first); + } + } + if (newValue != b.second.toVariant()) + probe->setProperty(b.first, VariantValue::create(newValue)); + if (!resolvedProbe) + properties.insert(b.first, newValue); + } + if (!resolvedProbe) { + resolvedProbe = Probe::create(probeId, probe->location(), condition, + sourceCode, properties, initialProperties, + importedFilesUsedInConfigure); + m_currentProbes[probe->location()] << resolvedProbe; + } + productContext->info.probes << resolvedProbe; +} + +void ModuleLoader::checkCancelation() const +{ + if (m_progressObserver && m_progressObserver->canceled()) { + throw ErrorInfo(Tr::tr("Project resolving canceled for configuration %1.") + .arg(TopLevelProject::deriveId(m_parameters.finalBuildConfigurationTree()))); + } +} + +bool ModuleLoader::checkItemCondition(Item *item, Item *itemToDisable) +{ + if (m_evaluator->boolValue(item, StringConstants::conditionProperty())) + return true; + m_disabledItems += itemToDisable ? itemToDisable : item; + return false; +} + +QStringList ModuleLoader::readExtraSearchPaths(Item *item, bool *wasSet) +{ + QStringList result; + const QStringList paths = m_evaluator->stringListValue( + item, StringConstants::qbsSearchPathsProperty(), wasSet); + const JSSourceValueConstPtr prop = item->sourceProperty( + StringConstants::qbsSearchPathsProperty()); + + // Value can come from within a project file or as an overridden value from the user + // (e.g command line). + const QString basePath = FileInfo::path(prop ? prop->file()->filePath() + : m_parameters.projectFilePath()); + for (const QString &path : paths) + result += FileInfo::resolvePath(basePath, path); + return result; +} + +void ModuleLoader::copyProperties(const Item *sourceProject, Item *targetProject) +{ + if (!sourceProject) + return; + const QList builtinProjectProperties = BuiltinDeclarations::instance() + .declarationsForType(ItemType::Project).properties(); + Set builtinProjectPropertyNames; + for (const PropertyDeclaration &p : builtinProjectProperties) + builtinProjectPropertyNames << p.name(); + + for (Item::PropertyDeclarationMap::ConstIterator it + = sourceProject->propertyDeclarations().constBegin(); + it != sourceProject->propertyDeclarations().constEnd(); ++it) { + + // We must not inherit built-in properties such as "name", + // but there are exceptions. + if (it.key() == StringConstants::qbsSearchPathsProperty() + || it.key() == StringConstants::profileProperty() + || it.key() == StringConstants::buildDirectoryProperty() + || it.key() == StringConstants::sourceDirectoryProperty() + || it.key() == StringConstants::minimumQbsVersionProperty()) { + const JSSourceValueConstPtr &v = targetProject->sourceProperty(it.key()); + QBS_ASSERT(v, continue); + if (v->sourceCode() == StringConstants::undefinedValue()) + sourceProject->copyProperty(it.key(), targetProject); + continue; + } + + if (builtinProjectPropertyNames.contains(it.key())) + continue; + + if (targetProject->hasOwnProperty(it.key())) + continue; // Ignore stuff the target project already has. + + targetProject->setPropertyDeclaration(it.key(), it.value()); + sourceProject->copyProperty(it.key(), targetProject); + } +} + +Item *ModuleLoader::wrapInProjectIfNecessary(Item *item) +{ + if (item->type() == ItemType::Project) + return item; + Item *prj = Item::create(item->pool(), ItemType::Project); + Item::addChild(prj, item); + prj->setFile(item->file()); + prj->setLocation(item->location()); + prj->setupForBuiltinType(m_logger); + return prj; +} + +QString ModuleLoader::findExistingModulePath(const QString &searchPath, + const QualifiedId &moduleName) +{ + QString dirPath = searchPath + QStringLiteral("/modules"); + + // isFileCaseCorrect is a very expensive call on macOS, so we cache the value for the + // modules and search paths we've already processed + auto &moduleInfo = m_existingModulePathCache[{searchPath, moduleName}]; + if (moduleInfo.first) // poor man's std::optional + return moduleInfo.second; + + for (const QString &moduleNamePart : moduleName) { + dirPath = FileInfo::resolvePath(dirPath, moduleNamePart); + if (!FileInfo::exists(dirPath) || !FileInfo::isFileCaseCorrect(dirPath)) { + moduleInfo.first = true; + return moduleInfo.second = QString(); + } + } + + moduleInfo.first = true; + return moduleInfo.second = dirPath; +} + +QStringList ModuleLoader::findExistingModulePaths( + const QStringList &searchPaths, const QualifiedId &moduleName) +{ + QStringList result; + result.reserve(searchPaths.size()); + for (const auto &path: searchPaths) { + const QString dirPath = findExistingModulePath(path, moduleName); + if (!dirPath.isEmpty()) + result.append(dirPath); + } + return result; +} + +QVariantMap ModuleLoader::moduleProviderConfig(ModuleLoader::ProductContext &product) +{ + if (product.moduleProviderConfigRetrieved) + return product.theModuleProviderConfig; + const ItemValueConstPtr configItemValue + = product.item->itemProperty(StringConstants::moduleProviders()); + if (configItemValue) { + const std::function collectMap + = [this, &product, &collectMap](const Item *item, const QualifiedId &name) { + const Item::PropertyMap &props = item->properties(); + for (auto it = props.begin(); it != props.end(); ++it) { + QVariant value; + switch (it.value()->type()) { + case Value::ItemValueType: { + const auto childItem = static_cast(it.value().get())->item(); + childItem->setScope(item->scope()); + collectMap(childItem, QualifiedId(name) << it.key()); + continue; + } + case Value::JSSourceValueType: + value = m_evaluator->value(item, it.key()).toVariant(); + break; + case Value::VariantValueType: + value = static_cast(it.value().get())->value(); + break; + } + QVariantMap m = product.theModuleProviderConfig.value(name.toString()).toMap(); + m.insert(it.key(), value); + product.theModuleProviderConfig.insert(name.toString(), m); + } + }; + configItemValue->item()->setScope(product.item); + collectMap(configItemValue->item(), QualifiedId()); + } + for (auto it = product.moduleProperties.begin(); it != product.moduleProperties.end(); ++it) { + if (!it.key().startsWith(QStringLiteral("moduleProviders."))) + continue; + const QString provider = it.key().mid(QStringLiteral("moduleProviders.").size()); + const QVariantMap providerConfigFromBuildConfig = it.value().toMap(); + if (providerConfigFromBuildConfig.empty()) + continue; + QVariantMap currentMapForProvider = product.theModuleProviderConfig.value(provider).toMap(); + for (auto propIt = providerConfigFromBuildConfig.begin(); + propIt != providerConfigFromBuildConfig.end(); ++propIt) { + currentMapForProvider.insert(propIt.key(), propIt.value()); + } + product.theModuleProviderConfig.insert(provider, currentMapForProvider); + } + product.moduleProviderConfigRetrieved = true; + return product.theModuleProviderConfig; +} + +ModuleLoader::ModuleProviderResult ModuleLoader::findModuleProvider(const QualifiedId &name, + ModuleLoader::ProductContext &product, ModuleProviderLookup lookupType, + const CodeLocation &dependsItemLocation) +{ + for (const QString &path : m_reader->allSearchPaths()) { + QString fullPath = FileInfo::resolvePath(path, QStringLiteral("module-providers")); + switch (lookupType) { + case ModuleProviderLookup::Regular: + for (const QString &component : name) + fullPath = FileInfo::resolvePath(fullPath, component); + break; + case ModuleProviderLookup::Fallback: + fullPath = FileInfo::resolvePath(fullPath, QStringLiteral("__fallback")); + break; + } + const QString providerFile = FileInfo::resolvePath(fullPath, + QStringLiteral("provider.qbs")); + if (!FileInfo::exists(providerFile)) { + qCDebug(lcModuleLoader) << "No module provider found at" << providerFile; + continue; + } + QTemporaryFile dummyItemFile; + if (!dummyItemFile.open()) { + throw ErrorInfo(Tr::tr("Failed to create temporary file for running module provider " + "for dependency '%1': %2").arg(name.toString(), + dummyItemFile.errorString())); + } + m_tempQbsFiles << dummyItemFile.fileName(); + qCDebug(lcModuleLoader) << "Instantiating module provider at" << providerFile; + const QString projectBuildDir = product.project->item->variantProperty( + StringConstants::buildDirectoryProperty())->value().toString(); + const QString searchPathBaseDir = ModuleProviderInfo::outputDirPath(projectBuildDir, name); + const QVariant moduleConfig = moduleProviderConfig(product).value(name.toString()); + QTextStream stream(&dummyItemFile); + using Qt::endl; + stream.setCodec("UTF-8"); + stream << "import qbs.FileInfo" << endl; + stream << "import qbs.Utilities" << endl; + stream << "import '" << providerFile << "' as Provider" << endl; + stream << "Provider {" << endl; + stream << " name: " << toJSLiteral(name.toString()) << endl; + stream << " property var config: (" << toJSLiteral(moduleConfig) << ')' << endl; + stream << " outputBaseDir: FileInfo.joinPaths(baseDirPrefix, " + " Utilities.getHash(JSON.stringify(config)))" << endl; + stream << " property string baseDirPrefix: " << toJSLiteral(searchPathBaseDir) << endl; + stream << " property stringList searchPaths: (relativeSearchPaths || [])" + " .map(function(p) { return FileInfo.joinPaths(outputBaseDir, p); })" + << endl; + stream << "}" << endl; + stream.flush(); + Item * const providerItem = loadItemFromFile(dummyItemFile.fileName(), dependsItemLocation); + if (providerItem->type() != ItemType::ModuleProvider) { + throw ErrorInfo(Tr::tr("File '%1' declares an item of type '%2', " + "but '%3' was expected.") + .arg(providerFile, providerItem->typeName(), + BuiltinDeclarations::instance().nameForType(ItemType::ModuleProvider))); + } + providerItem->setParent(product.item); + const QVariantMap configMap = moduleConfig.toMap(); + for (auto it = configMap.begin(); it != configMap.end(); ++it) { + const PropertyDeclaration decl = providerItem->propertyDeclaration(it.key()); + if (!decl.isValid()) { + throw ErrorInfo(Tr::tr("No such property '%1' in module provider '%2'.") + .arg(it.key(), name.toString())); + } + providerItem->setProperty(it.key(), VariantValue::create(it.value())); + } + EvalContextSwitcher contextSwitcher(m_evaluator->engine(), EvalContext::ModuleProvider); + const QStringList searchPaths + = m_evaluator->stringListValue(providerItem, QStringLiteral("searchPaths")); + const auto addToGlobalInfo = [=] { + m_moduleProviderInfo.emplace_back(ModuleProviderInfo(name, moduleConfig.toMap(), + searchPaths, m_parameters.dryRun())); + }; + if (searchPaths.empty()) { + qCDebug(lcModuleLoader) << "Module provider did run, but did not set up " + "any modules."; + addToGlobalInfo(); + return {true, false}; + } + qCDebug(lcModuleLoader) << "Module provider added" << searchPaths.size() + << "new search path(s)"; + + // (1) is needed so the immediate new look-up works. + // (2) is needed so the next use of SearchPathManager considers the new paths. + // (3) is needed for the code that removes the product-specific search paths when + // product handling is done. + // (4) is needed for possible re-use in subsequent products and builds. + m_reader->pushExtraSearchPaths(searchPaths); // (1) + product.searchPaths << searchPaths; // (2) + product.newlyAddedModuleProviderSearchPaths.push_back(searchPaths); // (3) + addToGlobalInfo(); // (4) + return {true, true}; + } + return {}; +} + +void ModuleLoader::setScopeForDescendants(Item *item, Item *scope) +{ + for (Item * const child : item->children()) { + child->setScope(scope); + setScopeForDescendants(child, scope); + } +} + +void ModuleLoader::overrideItemProperties(Item *item, const QString &buildConfigKey, + const QVariantMap &buildConfig) +{ + const QVariant buildConfigValue = buildConfig.value(buildConfigKey); + if (buildConfigValue.isNull()) + return; + const QVariantMap overridden = buildConfigValue.toMap(); + for (QVariantMap::const_iterator it = overridden.constBegin(); it != overridden.constEnd(); + ++it) { + const PropertyDeclaration decl = item->propertyDeclaration(it.key()); + if (!decl.isValid()) { + ErrorInfo error(Tr::tr("Unknown property: %1.%2").arg(buildConfigKey, it.key())); + handlePropertyError(error, m_parameters, m_logger); + continue; + } + item->setProperty(it.key(), + VariantValue::create(convertToPropertyType(it.value(), decl.type(), + QStringList(buildConfigKey), it.key()))); + } +} + +void ModuleLoader::collectAllModules(Item *item, std::vector *modules) +{ + for (const Item::Module &m : item->modules()) { + if (moduleRepresentsDisabledProduct(m)) + m.item->removeModules(); + auto it = std::find_if(modules->begin(), modules->end(), + [m] (const Item::Module &m2) { return m.name == m2.name; }); + if (it != modules->end()) { + // If a module is required somewhere, it is required in the top-level item. + if (m.required) + it->required = true; + it->versionRange.narrowDown(m.versionRange); + continue; + } + modules->push_back(m); + collectAllModules(m.item, modules); + } +} + +std::vector ModuleLoader::allModules(Item *item) +{ + std::vector lst; + collectAllModules(item, &lst); + return lst; +} + +bool ModuleLoader::moduleRepresentsDisabledProduct(const Item::Module &module) +{ + if (!module.isProduct) + return false; + const Item *exportItem = module.item->prototype(); + while (exportItem && exportItem->type() != ItemType::Export) + exportItem = exportItem->prototype(); + QBS_CHECK(exportItem); + Item * const productItem = exportItem->parent(); + QBS_CHECK(productItem->type() == ItemType::Product); + return m_disabledItems.contains(productItem) || !checkItemCondition(productItem); +} + +void ModuleLoader::addProductModuleDependencies(ProductContext *productContext, const QString &name) +{ + auto deps = productContext->productModuleDependencies.at(name); + QList depsToAdd; + const bool productIsMultiplexed = !productContext->multiplexConfigurationId.isEmpty(); + for (auto &dep : deps) { + const auto productRange = m_productsByName.equal_range(dep.name); + std::vector dependencies; + bool hasNonMultiplexedDependency = false; + for (auto it = productRange.first; it != productRange.second; ++it) { + if (!it->second->multiplexConfigurationId.isEmpty()) { + dependencies.push_back(it->second); + if (productIsMultiplexed && dep.profile.isEmpty()) + break; + } else { + hasNonMultiplexedDependency = true; + break; + } + } + + if (hasNonMultiplexedDependency) { + depsToAdd.push_back(dep); + continue; + } + + for (std::size_t i = 0; i < dependencies.size(); ++i) { + const bool profileMatch = dep.profile.isEmpty() + || dep.profile == StringConstants::star() + || dep.profile == dependencies.at(i)->profileName; + if (i == 0) { + if (productIsMultiplexed && dep.profile.isEmpty()) { + const ValuePtr &multiplexConfigIdProp = productContext->item->property( + StringConstants::multiplexConfigurationIdProperty()); + dep.multiplexConfigurationId = std::static_pointer_cast( + multiplexConfigIdProp)->value().toString(); + depsToAdd.push_back(dep); + break; + } else if (profileMatch) { + dep.multiplexConfigurationId = dependencies.at(i)->multiplexConfigurationId; + depsToAdd.push_back(dep); + } + } else if (profileMatch) { + ModuleLoaderResult::ProductInfo::Dependency newDependency = dep; + newDependency.multiplexConfigurationId + = dependencies.at(i)->multiplexConfigurationId; + depsToAdd << newDependency; + } + } + } + productContext->info.usedProducts.insert(productContext->info.usedProducts.end(), + depsToAdd.cbegin(), depsToAdd.cend()); +} + +static void collectProductModuleDependencies(Item *item, Set &allDeps) +{ + for (const Item::Module &m : item->modules()) { + if (m.isProduct && allDeps.insert(m.name).second) + collectProductModuleDependencies(m.item, allDeps); + } +} + +void ModuleLoader::addProductModuleDependencies(ModuleLoader::ProductContext *ctx) +{ + Set deps; + collectProductModuleDependencies(ctx->item, deps); + for (const QualifiedId &dep : deps) + addProductModuleDependencies(ctx, dep.toString()); +} + +void ModuleLoader::addTransitiveDependencies(ProductContext *ctx) +{ + AccumulatingTimer timer(m_parameters.logElapsedTime() + ? &m_elapsedTimeTransitiveDependencies : nullptr); + qCDebug(lcModuleLoader) << "addTransitiveDependencies"; + + std::vector transitiveDeps = allModules(ctx->item); + std::sort(transitiveDeps.begin(), transitiveDeps.end()); + for (const Item::Module &m : ctx->item->modules()) { + auto it = std::lower_bound(transitiveDeps.begin(), transitiveDeps.end(), m); + QBS_CHECK(it != transitiveDeps.end() && it->name == m.name); + transitiveDeps.erase(it); + } + for (const Item::Module &module : qAsConst(transitiveDeps)) { + if (module.isProduct) { + ctx->item->addModule(module); + } else { + Item::Module dep; + dep.item = loadModule(ctx, nullptr, ctx->item, ctx->item->location(), QString(), + module.name, QString(), FallbackMode::Disabled, + 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(), + ctx->name), + ctx->item->location()); + } + dep.name = module.name; + dep.required = module.required; + dep.versionRange = module.versionRange; + ctx->item->addModule(dep); + } + } +} + +Item *ModuleLoader::createNonPresentModule(const QString &name, const QString &reason, Item *module) +{ + 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()); + module->setProperty(StringConstants::nameProperty(), VariantValue::create(name)); + } + module->setProperty(StringConstants::presentProperty(), VariantValue::falseValue()); + return module; +} + +void ModuleLoader::handleProductError(const ErrorInfo &error, + ModuleLoader::ProductContext *productContext) +{ + const bool alreadyHadError = productContext->info.delayedError.hasError(); + if (!alreadyHadError) { + productContext->info.delayedError.append(Tr::tr("Error while handling product '%1':") + .arg(productContext->name), + productContext->item->location()); + } + if (error.isInternalError()) { + if (alreadyHadError) { + qCDebug(lcModuleLoader()) << "ignoring subsequent internal error" << error.toString() + << "in product" << productContext->name; + return; + } + for (const auto &kv : productContext->productModuleDependencies) { + const auto rangeForName = m_productsByName.equal_range(kv.first); + for (auto rangeIt = rangeForName.first; rangeIt != rangeForName.second; ++rangeIt) { + const ProductContext * const dep = rangeIt->second; + if (dep->info.delayedError.hasError()) { + qCDebug(lcModuleLoader()) << "ignoring internal error" << error.toString() + << "in product" << productContext->name + << "assumed to be caused by erroneous dependency" + << dep->name; + return; + } + } + } + } + const auto errorItems = error.items(); + for (const ErrorItem &ei : errorItems) + productContext->info.delayedError.append(ei.description(), ei.codeLocation()); + productContext->project->result->productInfos.insert(productContext->item, + productContext->info); + m_disabledItems << productContext->item; + m_erroneousProducts.insert(productContext->name); +} + +static void gatherAssignedProperties(ItemValue *iv, const QualifiedId &prefix, + QualifiedIdSet &properties) +{ + const Item::PropertyMap &props = iv->item()->properties(); + for (auto it = props.cbegin(); it != props.cend(); ++it) { + switch (it.value()->type()) { + case Value::JSSourceValueType: + properties << (QualifiedId(prefix) << it.key()); + break; + case Value::ItemValueType: + if (iv->item()->type() == ItemType::ModulePrefix) { + gatherAssignedProperties(std::static_pointer_cast(it.value()).get(), + QualifiedId(prefix) << it.key(), properties); + } + break; + default: + break; + } + } +} + +QualifiedIdSet ModuleLoader::gatherModulePropertiesSetInGroup(const Item *group) +{ + QualifiedIdSet propsSetInGroup; + const Item::PropertyMap &props = group->properties(); + for (auto it = props.cbegin(); it != props.cend(); ++it) { + if (it.value()->type() == Value::ItemValueType) { + gatherAssignedProperties(std::static_pointer_cast(it.value()).get(), + QualifiedId(it.key()), propsSetInGroup); + } + } + return propsSetInGroup; +} + +void ModuleLoader::markModuleTargetGroups(Item *group, const Item::Module &module) +{ + QBS_CHECK(group->type() == ItemType::Group); + if (m_evaluator->boolValue(group, StringConstants::filesAreTargetsProperty())) { + group->setProperty(StringConstants::modulePropertyInternal(), + VariantValue::create(module.name.toString())); + } + for (Item * const child : group->children()) + markModuleTargetGroups(child, module); +} + +void ModuleLoader::copyGroupsFromModuleToProduct(const ProductContext &productContext, + const Item::Module &module, + const Item *modulePrototype) +{ + for (Item * const child : modulePrototype->children()) { + if (child->type() == ItemType::Group) { + Item * const clonedGroup = child->clone(); + clonedGroup->setScope(productContext.scope); + setScopeForDescendants(clonedGroup, productContext.scope); + Item::addChild(productContext.item, clonedGroup); + markModuleTargetGroups(clonedGroup, module); + } + } +} + +void ModuleLoader::copyGroupsFromModulesToProduct(const ProductContext &productContext) +{ + for (const Item::Module &module : productContext.item->modules()) { + Item *prototype = module.item; + bool modulePassedValidation; + while ((modulePassedValidation = prototype->isPresentModule()) && prototype->prototype()) + prototype = prototype->prototype(); + if (modulePassedValidation) + copyGroupsFromModuleToProduct(productContext, module, prototype); + } +} + +QString ModuleLoaderResult::ProductInfo::Dependency::uniqueName() const +{ + return ResolvedProduct::uniqueName(name, multiplexConfigurationId); +} + +QString ModuleLoader::ProductContext::uniqueName() const +{ + return ResolvedProduct::uniqueName(name, multiplexConfigurationId); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/moduleloader.h b/src/lib/corelib/language/moduleloader.h new file mode 100644 index 00000000..942f93c8 --- /dev/null +++ b/src/lib/corelib/language/moduleloader.h @@ -0,0 +1,490 @@ +/**************************************************************************** +** +** 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 QBS_MODULELOADER_H +#define QBS_MODULELOADER_H + +#include "filetags.h" +#include "forward_decls.h" +#include "item.h" +#include "itempool.h" +#include "moduleproviderinfo.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace qbs { + +class CodeLocation; +class Settings; + +namespace Internal { + +class Evaluator; +class Item; +class ItemReader; +class ProgressObserver; +class QualifiedId; + +using ModulePropertiesPerGroup = std::unordered_map; + +struct ModuleLoaderResult +{ + ModuleLoaderResult() + : itemPool(new ItemPool), root(nullptr) + {} + + struct ProductInfo + { + struct Dependency + { + QString name; + QString profile; // "*" <=> Match all profiles. + QString multiplexConfigurationId; + QVariantMap parameters; + bool limitToSubProject = false; + bool isRequired = true; + + QString uniqueName() const; + }; + + std::vector probes; + std::vector usedProducts; + ModulePropertiesPerGroup modulePropertiesSetInGroups; + ErrorInfo delayedError; + }; + + std::shared_ptr itemPool; + Item *root; + QHash productInfos; + std::vector projectProbes; + ModuleProviderInfoList moduleProviderInfo; + Set qbsFiles; + QVariantMap profileConfigs; +}; + +/* + * Loader stage II. Responsible for + * - loading modules and module dependencies, + * - project references, + * - Probe items. + */ +class ModuleLoader +{ +public: + ModuleLoader(Evaluator *evaluator, Logger &logger); + ~ModuleLoader(); + + void setProgressObserver(ProgressObserver *progressObserver); + void setSearchPaths(const QStringList &searchPaths); + void setOldProjectProbes(const std::vector &oldProbes); + void setOldProductProbes(const QHash> &oldProbes); + void setLastResolveTime(const FileTime &time) { m_lastResolveTime = time; } + void setStoredProfiles(const QVariantMap &profiles); + void setStoredModuleProviderInfo(const ModuleProviderInfoList &moduleProviderInfo); + Evaluator *evaluator() const { return m_evaluator; } + + ModuleLoaderResult load(const SetupProjectParameters ¶meters); + +private: + class ProductSortByDependencies; + + class ContextBase + { + public: + ContextBase() + : item(nullptr), scope(nullptr) + {} + + Item *item; + Item *scope; + QString name; + }; + + class ProjectContext; + + using ProductDependencies = std::vector; + + // This is the data we need to store at the point where a dependency is deferred + // in order to properly resolve the dependency in pass 2. + struct DeferredDependsContext { + DeferredDependsContext(Item *exportingProduct, Item *parent) + : exportingProductItem(exportingProduct), parentItem(parent) {} + Item *exportingProductItem = nullptr; + Item *parentItem = nullptr; + bool operator==(const DeferredDependsContext &other) const + { + return exportingProductItem == other.exportingProductItem + && parentItem == other.parentItem; + } + bool operator<(const DeferredDependsContext &other) const + { + return parentItem < other.parentItem; + } + }; + + class ProductContext : public ContextBase + { + public: + ProjectContext *project = nullptr; + ModuleLoaderResult::ProductInfo info; + QString profileName; + QString multiplexConfigurationId; + QVariantMap moduleProperties; + std::map productModuleDependencies; + std::unordered_map> unknownProfilePropertyErrors; + QStringList searchPaths; + + std::vector newlyAddedModuleProviderSearchPaths; + Set knownModuleProviders; + QVariantMap theModuleProviderConfig; + bool moduleProviderConfigRetrieved = false; + + // The key corresponds to DeferredDependsContext.exportingProductItem, which is the + // only value from that data structure that we still need here. + std::unordered_map> deferredDependsItems; + + QString uniqueName() const; + }; + + class TopLevelProjectContext; + + class ProjectContext : public ContextBase + { + public: + TopLevelProjectContext *topLevelProject = nullptr; + ModuleLoaderResult *result = nullptr; + std::vector products; + std::vector searchPathsStack; + }; + + struct ProductModuleInfo + { + Item *exportItem = nullptr; + QString multiplexId; + QVariantMap defaultParameters; + }; + + class TopLevelProjectContext + { + Q_DISABLE_COPY(TopLevelProjectContext) + public: + TopLevelProjectContext() = default; + ~TopLevelProjectContext() { qDeleteAll(projects); } + + std::vector projects; + QMultiHash productModules; + std::vector probes; + QString buildDirectory; + }; + + class DependsContext + { + public: + ProductContext *product = nullptr; + Item *exportingProductItem = nullptr; + ProductDependencies *productDependencies = nullptr; + }; + + void handleTopLevelProject(ModuleLoaderResult *loadResult, Item *projectItem, + const QString &buildDirectory, const Set &referencedFilePaths); + void handleProject(ModuleLoaderResult *loadResult, + TopLevelProjectContext *topLevelProjectContext, Item *projectItem, + const Set &referencedFilePaths); + + using MultiplexRow = std::vector; + using MultiplexTable = std::vector; + + struct MultiplexInfo + { + std::vector properties; + MultiplexTable table; + bool aggregate = false; + VariantValuePtr multiplexedType; + + QString toIdString(size_t row) const; + static QVariantMap multiplexIdToVariantMap(const QString &multiplexId); + }; + + void dump(const MultiplexInfo &mpi); + static MultiplexTable combine(const MultiplexTable &table, const MultiplexRow &values); + MultiplexInfo extractMultiplexInfo(Item *productItem, Item *qbsModuleItem); + QList multiplexProductItem(ProductContext *dummyContext, Item *productItem); + void normalizeDependencies(ProductContext *product, + const DeferredDependsContext &dependsContext); + void adjustDependenciesForMultiplexing(const TopLevelProjectContext &tlp); + void adjustDependenciesForMultiplexing(const ProductContext &product); + void adjustDependenciesForMultiplexing(const ProductContext &product, Item *dependsItem); + + void prepareProduct(ProjectContext *projectContext, Item *productItem); + void setupProductDependencies(ProductContext *productContext, + const Set &deferredDependsContext); + void handleProduct(ProductContext *productContext); + void checkDependencyParameterDeclarations(const ProductContext *productContext) const; + void handleModuleSetupError(ProductContext *productContext, const Item::Module &module, + const ErrorInfo &error); + void initProductProperties(const ProductContext &product); + void handleSubProject(ProjectContext *projectContext, Item *projectItem, + const Set &referencedFilePaths); + QList loadReferencedFile(const QString &relativePath, + const CodeLocation &referencingLocation, + const Set &referencedFilePaths, + ProductContext &dummyContext); + void handleAllPropertyOptionsItems(Item *item); + void handlePropertyOptions(Item *optionsItem); + + using ModuleDependencies = QHash; + void setupReverseModuleDependencies(const Item::Module &module, ModuleDependencies &deps, + QualifiedIdSet &seenModules); + ModuleDependencies setupReverseModuleDependencies(const Item *product); + void handleGroup(ProductContext *productContext, Item *groupItem, + const ModuleDependencies &reverseDepencencies); + void propagateModulesFromParent(ProductContext *productContext, Item *groupItem, + const ModuleDependencies &reverseDepencencies); + void adjustDefiningItemsInGroupModuleInstances(const Item::Module &module, + const Item::Modules &dependentModules); + + bool mergeExportItems(const ProductContext &productContext); + void resolveDependencies(DependsContext *dependsContext, Item *item, + ProductContext *productContext = nullptr); + class ItemModuleList; + void resolveDependsItem(DependsContext *dependsContext, Item *parentItem, Item *dependsItem, + 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); + static ProductModuleInfo *productModule(ProductContext *productContext, const QString &name, + const QString &multiplexId, bool &productNameMatch); + static ProductContext *product(ProjectContext *projectContext, const QString &name); + static ProductContext *product(TopLevelProjectContext *tlpContext, const QString &name); + + enum class FallbackMode { Enabled, Disabled }; + Item *loadModule(ProductContext *productContext, Item *exportingProductItem, Item *item, + const CodeLocation &dependsItemLocation, const QString &moduleId, + const QualifiedId &moduleName, const QString &multiplexId, FallbackMode fallbackMode, + bool isRequired, bool *isProductDependency, QVariantMap *defaultParameters); + Item *searchAndLoadModuleFile(ProductContext *productContext, + const CodeLocation &dependsItemLocation, const QualifiedId &moduleName, + FallbackMode fallbackMode, bool isRequired, Item *moduleInstance); + QStringList &getModuleFileNames(const QString &dirPath); + Item *loadModuleFile(ProductContext *productContext, const QString &fullModuleName, + bool isBaseModule, const QString &filePath, bool *triedToLoad, Item *moduleInstance); + Item *getModulePrototype(ProductContext *productContext, const QString &fullModuleName, + const QString &filePath, bool *triedToLoad); + Item::Module loadBaseModule(ProductContext *productContext, Item *item); + void setupBaseModulePrototype(Item *prototype); + template + T callWithTemporaryBaseModule(ProductContext *productContext, const F &func); + void instantiateModule(ProductContext *productContext, Item *exportingProductItem, + Item *instanceScope, Item *moduleInstance, Item *modulePrototype, + const QualifiedId &moduleName, ProductModuleInfo *productModuleInfo); + void createChildInstances(Item *instance, Item *prototype, + QHash *prototypeInstanceMap) const; + void resolveProbes(ProductContext *productContext, Item *item); + void resolveProbe(ProductContext *productContext, Item *parent, Item *probe); + void checkCancelation() const; + bool checkItemCondition(Item *item, Item *itemToDisable = nullptr); + QStringList readExtraSearchPaths(Item *item, bool *wasSet = nullptr); + void copyProperties(const Item *sourceProject, Item *targetProject); + Item *wrapInProjectIfNecessary(Item *item); + QString findExistingModulePath(const QString &searchPath, const QualifiedId &moduleName); + QStringList findExistingModulePaths( + const QStringList &searchPaths, const QualifiedId &moduleName); + + enum class ModuleProviderLookup { Regular, Fallback }; + struct ModuleProviderResult + { + ModuleProviderResult() = default; + ModuleProviderResult(bool ran, bool added) + : providerFound(ran), providerAddedSearchPaths(added) {} + bool providerFound = false; + bool providerAddedSearchPaths = false; + }; + ModuleProviderResult findModuleProvider(const QualifiedId &name, ProductContext &product, + ModuleProviderLookup lookupType, const CodeLocation &dependsItemLocation); + QVariantMap moduleProviderConfig(ProductContext &product); + + static void setScopeForDescendants(Item *item, Item *scope); + void overrideItemProperties(Item *item, const QString &buildConfigKey, + const QVariantMap &buildConfig); + void addProductModuleDependencies(ProductContext *ctx, const QString &name); + void addProductModuleDependencies(ProductContext *ctx); + void addTransitiveDependencies(ProductContext *ctx); + Item *createNonPresentModule(const QString &name, const QString &reason, Item *module); + void copyGroupsFromModuleToProduct(const ProductContext &productContext, + const Item::Module &module, const Item *modulePrototype); + void copyGroupsFromModulesToProduct(const ProductContext &productContext); + void markModuleTargetGroups(Item *group, const Item::Module &module); + bool checkExportItemCondition(Item *exportItem, const ProductContext &productContext); + ProbeConstPtr findOldProjectProbe(const QString &globalId, bool condition, + const QVariantMap &initialProperties, + const QString &sourceCode) const; + ProbeConstPtr findOldProductProbe(const QString &productName, bool condition, + const QVariantMap &initialProperties, + const QString &sourceCode) const; + ProbeConstPtr findCurrentProbe(const CodeLocation &location, bool condition, + const QVariantMap &initialProperties) const; + + enum class CompareScript { No, Yes }; + bool probeMatches(const ProbeConstPtr &probe, bool condition, + const QVariantMap &initialProperties, const QString &configureScript, + CompareScript compareScript) const; + + void printProfilingInfo(); + void handleProductError(const ErrorInfo &error, ProductContext *productContext); + QualifiedIdSet gatherModulePropertiesSetInGroup(const Item *group); + Item *loadItemFromFile(const QString &filePath, const CodeLocation &referencingLocation); + void collectProductsByName(const TopLevelProjectContext &topLevelProject); + void collectProductsByType(const TopLevelProjectContext &topLevelProject); + + void handleProfileItems(Item *item, ProjectContext *projectContext); + std::vector collectProfileItems(Item *item, ProjectContext *projectContext); + void evaluateProfileValues(const QualifiedId &namePrefix, Item *item, Item *profileItem, + QVariantMap &values); + void handleProfile(Item *profileItem); + void collectNameFromOverride(const QString &overrideString); + void checkProjectNamesInOverrides(const TopLevelProjectContext &tlp); + void checkProductNamesInOverrides(); + void setSearchPathsForProduct(ProductContext *product); + + Item::Modules modulesSortedByDependency(const Item *productItem); + void createSortedModuleList(const Item::Module &parentModule, Item::Modules &modules); + void collectAllModules(Item *item, std::vector *modules); + std::vector allModules(Item *item); + bool moduleRepresentsDisabledProduct(const Item::Module &module); + + using ShadowProductInfo = std::pair; + ShadowProductInfo getShadowProductInfo(const ProductContext &product) const; + + ItemPool *m_pool; + Logger &m_logger; + ProgressObserver *m_progressObserver; + ItemReader *m_reader; + Evaluator *m_evaluator; + QMap m_moduleDirListCache; + QHash, std::pair> m_existingModulePathCache; + + // The keys are file paths, the values are module prototype items accompanied by a profile. + std::unordered_map>> m_modulePrototypes; + + // The keys are module prototypes and products, the values specify whether the module's + // condition is true for that product. + QHash, bool> m_modulePrototypeEnabledInfo; + + QHash m_parameterDeclarations; + Set m_disabledItems; + std::vector m_requiredChain; + + struct DependsChainEntry + { + DependsChainEntry(QualifiedId name, const CodeLocation &location) + : name(std::move(name)), location(location) + { + } + + QualifiedId name; + CodeLocation location; + bool isProduct = false; + }; + class DependsChainManager; + std::vector m_dependsChain; + + QHash> m_oldProjectProbes; + QHash> m_oldProductProbes; + FileTime m_lastResolveTime; + QHash> m_currentProbes; + QVariantMap m_storedProfiles; + QVariantMap m_localProfiles; + std::multimap m_productsByName; + std::multimap m_productsByType; + + std::unordered_map> m_productsWithDeferredDependsItems; + Set m_exportsWithDeferredDependsItems; + + ModuleProviderInfoList m_moduleProviderInfo; + Set m_tempQbsFiles; + + SetupProjectParameters m_parameters; + std::unique_ptr m_settings; + Version m_qbsVersion; + Item *m_tempScopeItem = nullptr; + + qint64 m_elapsedTimeProbes = 0; + qint64 m_elapsedTimePrepareProducts = 0; + qint64 m_elapsedTimeProductDependencies = 0; + qint64 m_elapsedTimeTransitiveDependencies = 0; + qint64 m_elapsedTimeHandleProducts = 0; + qint64 m_elapsedTimePropertyChecking = 0; + quint64 m_probesEncountered = 0; + quint64 m_probesRun = 0; + quint64 m_probesCachedCurrent = 0; + quint64 m_probesCachedOld = 0; + Set m_projectNamesUsedInOverrides; + Set m_productNamesUsedInOverrides; + Set m_disabledProjects; + Set m_erroneousProducts; + + int m_dependencyResolvingPass = 0; +}; + +} // namespace Internal +} // namespace qbs + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(qbs::Internal::ModuleLoaderResult::ProductInfo, Q_MOVABLE_TYPE); +Q_DECLARE_TYPEINFO(qbs::Internal::ModuleLoaderResult::ProductInfo::Dependency, Q_MOVABLE_TYPE); +QT_END_NAMESPACE + +#endif // QBS_MODULELOADER_H diff --git a/src/lib/corelib/language/modulemerger.cpp b/src/lib/corelib/language/modulemerger.cpp new file mode 100644 index 00000000..c5deaae0 --- /dev/null +++ b/src/lib/corelib/language/modulemerger.cpp @@ -0,0 +1,264 @@ +/**************************************************************************** +** +** 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 "modulemerger.h" + +#include "value.h" + +#include +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +ModuleMerger::ModuleMerger(Logger &logger, Item *productItem, const QString &productName, + const Item::Modules::iterator &modulesBegin, + const Item::Modules::iterator &modulesEnd) + : m_logger(logger) + , m_productItem(productItem) + , m_mergedModule(*modulesBegin) + , m_isBaseModule(m_mergedModule.name.first() == StringConstants::qbsModule()) + , m_isShadowProduct(productName.startsWith(StringConstants::shadowProductPrefix())) + , m_modulesBegin(std::next(modulesBegin)) + , m_modulesEnd(modulesEnd) +{ + QBS_CHECK(modulesBegin->item->type() == ItemType::ModuleInstance); +} + +void ModuleMerger::replaceItemInValues(QualifiedId moduleName, Item *containerItem, Item *toReplace) +{ + QBS_CHECK(!moduleName.empty()); + QBS_CHECK(containerItem != m_mergedModule.item); + const QString moduleNamePrefix = moduleName.takeFirst(); + const Item::PropertyMap &properties = containerItem->properties(); + for (auto it = properties.begin(); it != properties.end(); ++it) { + if (it.key() != moduleNamePrefix) + continue; + Value * const val = it.value().get(); + QBS_CHECK(val); + QBS_CHECK(val->type() == Value::ItemValueType); + const auto itemVal = static_cast(val); + if (moduleName.empty()) { + QBS_CHECK(itemVal->item() == toReplace); + itemVal->setItem(m_mergedModule.item); + } else { + replaceItemInValues(moduleName, itemVal->item(), toReplace); + } + } +} + +void ModuleMerger::start() +{ + // Iterate over any module that our product depends on. These modules + // may depend on m_mergedModule and contribute property assignments. + Item::PropertyMap props; + for (auto module = m_modulesBegin; module != m_modulesEnd; module++) + mergeModule(&props, *module); + + // Module property assignments in the product have the highest priority + // and are thus prepended. + Item::Module m; + m.item = m_productItem; + mergeModule(&props, m); + + // The module's prototype is the essential unmodified module as loaded + // from the cache. + Item *moduleProto = m_mergedModule.item->prototype(); + while (moduleProto->prototype()) + moduleProto = moduleProto->prototype(); + + // The prototype item might contain default values which get appended in + // case of list properties. Scalar properties will only be set if not + // already specified above. + Item::PropertyMap mergedProps = m_mergedModule.item->properties(); + for (auto it = props.constBegin(); it != props.constEnd(); ++it) { + appendPrototypeValueToNextChain(moduleProto, it.key(), it.value()); + mergedProps[it.key()] = it.value(); + } + + m_mergedModule.item->setProperties(mergedProps); + + // Update all sibling instances of the to-be-merged module to behave identical + // to the merged module. + for (Item *moduleInstanceContainer : qAsConst(m_moduleInstanceContainers)) { + Item::Modules modules; + for (const Item::Module &dep : moduleInstanceContainer->modules()) { + const bool isTheModule = dep.name == m_mergedModule.name; + Item::Module m = dep; + if (isTheModule && m.item != m_mergedModule.item) { + QBS_CHECK(m.item->type() == ItemType::ModuleInstance); + replaceItemInValues(m.name, moduleInstanceContainer, m.item); + m.item = m_mergedModule.item; + m.required = m_mergedModule.required; + m.versionRange = m_mergedModule.versionRange; + } + modules << m; + } + moduleInstanceContainer->setModules(modules); + } +} + +void ModuleMerger::mergeModule(Item::PropertyMap *dstProps, const Item::Module &module) +{ + const Item::Module *dep = findModule(module.item, m_mergedModule.name); + if (!dep) + return; + + const bool mergingProductItem = (module.item == m_productItem); + Item *srcItem = dep->item; + Item *origSrcItem = srcItem; + do { + if (m_seenInstances.insert(srcItem).second) { + for (auto it = srcItem->properties().constBegin(); + it != srcItem->properties().constEnd(); ++it) { + const ValuePtr &srcVal = it.value(); + if (srcVal->type() == Value::ItemValueType) + continue; + if (it.key() == StringConstants::qbsSourceDirPropertyInternal()) + continue; + const PropertyDeclaration srcDecl = srcItem->propertyDeclaration(it.key()); + if (!srcDecl.isValid()) + continue; + + // Scalar variant values could stem from product multiplexing, in which case + // the merged qbs module instance needs to get that value. + if (srcVal->type() == Value::VariantValueType + && (!srcDecl.isScalar() || !m_isBaseModule)) { + continue; + } + + ValuePtr clonedSrcVal = srcVal->clone(); + clonedSrcVal->setDefiningItem(origSrcItem); + + ValuePtr &dstVal = (*dstProps)[it.key()]; + if (dstVal) { + if (srcDecl.isScalar()) { + // Scalar properties get replaced. + if ((dstVal->type() == Value::JSSourceValueType) + && (srcVal->type() == Value::JSSourceValueType)) { + // Warn only about conflicting source code values + const JSSourceValuePtr dstJsVal = + std::static_pointer_cast(dstVal); + const JSSourceValuePtr srcJsVal = + std::static_pointer_cast(srcVal); + const bool overriddenInProduct = + m_mergedModule.item->properties().contains(it.key()); + + if (dstJsVal->sourceCode() != srcJsVal->sourceCode() + && !mergingProductItem && !overriddenInProduct + && !m_isShadowProduct) { + m_logger.qbsWarning() + << Tr::tr("Conflicting scalar values at %1 and %2.").arg( + dstJsVal->location().toString(), + srcJsVal->location().toString()); + } + } + } else { + // List properties get prepended + QBS_CHECK(!clonedSrcVal->next()); + clonedSrcVal->setNext(dstVal); + } + } + dstVal = clonedSrcVal; + } + } + srcItem = srcItem->prototype(); + } while (srcItem && srcItem->type() == ItemType::ModuleInstance); + + // Update dependency constraints + if (dep->required) + m_mergedModule.required = true; + m_mergedModule.versionRange.narrowDown(dep->versionRange); + + // We need to touch the unmerged module instances later once more + m_moduleInstanceContainers << module.item; +} + +void ModuleMerger::appendPrototypeValueToNextChain(Item *moduleProto, const QString &propertyName, + const ValuePtr &sv) +{ + const PropertyDeclaration pd = m_mergedModule.item->propertyDeclaration(propertyName); + if (pd.isScalar()) + return; + if (!m_clonedModulePrototype) { + m_clonedModulePrototype = Item::create(moduleProto->pool(), ItemType::Module); + m_clonedModulePrototype->setScope(m_mergedModule.item); + m_clonedModulePrototype->setLocation(moduleProto->location()); + moduleProto->copyProperty(StringConstants::nameProperty(), m_clonedModulePrototype); + } + const ValuePtr &protoValue = moduleProto->property(propertyName); + QBS_CHECK(protoValue); + const ValuePtr clonedValue = protoValue->clone(); + lastInNextChain(sv)->setNext(clonedValue); + clonedValue->setDefiningItem(m_clonedModulePrototype); + m_clonedModulePrototype->setPropertyDeclaration(propertyName, pd); + m_clonedModulePrototype->setProperty(propertyName, clonedValue); +} + +ValuePtr ModuleMerger::lastInNextChain(const ValuePtr &v) +{ + ValuePtr n = v; + while (n->next()) + n = n->next(); + return n; +} + +const Item::Module *ModuleMerger::findModule(const Item *item, const QualifiedId &name) +{ + for (const auto &module : item->modules()) { + if (module.name == name) + return &module; + } + return nullptr; +} + +void ModuleMerger::merge(Logger &logger, Item *product, const QString &productName, + Item::Modules *topSortedModules) +{ + for (auto it = topSortedModules->begin(); it != topSortedModules->end(); ++it) + ModuleMerger(logger, product, productName, it, topSortedModules->end()).start(); +} + + + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/modulemerger.h b/src/lib/corelib/language/modulemerger.h new file mode 100644 index 00000000..469dc86c --- /dev/null +++ b/src/lib/corelib/language/modulemerger.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 QBS_MODULEMERGER_H +#define QBS_MODULEMERGER_H + +#include "item.h" +#include "qualifiedid.h" + +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class ModuleMerger { +public: + static void merge(Logger &logger, Item *productItem, const QString &productName, + Item::Modules *topSortedModules); + +private: + ModuleMerger(Logger &logger, Item *productItem, const QString &productName, + const Item::Modules::iterator &modulesBegin, + const Item::Modules::iterator &modulesEnd); + + void appendPrototypeValueToNextChain(Item *moduleProto, const QString &propertyName, + const ValuePtr &sv); + void mergeModule(Item::PropertyMap *props, const Item::Module &m); + void replaceItemInValues(QualifiedId moduleName, Item *containerItem, Item *toReplace); + void start(); + + static ValuePtr lastInNextChain(const ValuePtr &v); + static const Item::Module *findModule(const Item *item, const QualifiedId &name); + + Logger &m_logger; + Item * const m_productItem; + Item::Module &m_mergedModule; + Item *m_clonedModulePrototype = nullptr; + Set m_seenInstances; + Set m_moduleInstanceContainers; + const bool m_isBaseModule; + const bool m_isShadowProduct; + const Item::Modules::iterator m_modulesBegin; + const Item::Modules::iterator m_modulesEnd; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_MODULEMERGER_H diff --git a/src/lib/corelib/language/moduleproviderinfo.h b/src/lib/corelib/language/moduleproviderinfo.h new file mode 100644 index 00000000..4f757d3d --- /dev/null +++ b/src/lib/corelib/language/moduleproviderinfo.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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_MODULEPROVIDERINFO_H +#define QBS_MODULEPROVIDERINFO_H + +#include "qualifiedid.h" +#include + +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class ModuleProviderInfo +{ +public: + ModuleProviderInfo() = default; + ModuleProviderInfo(QualifiedId name, QVariantMap config, + QStringList searchPaths, bool transientOutput) + : name(std::move(name)) + , config(std::move(config)) + , searchPaths(std::move(searchPaths)) + , transientOutput(transientOutput) + {} + + static QString outputBaseDirName() { return QStringLiteral("genmodules"); } + static QString outputDirPath(const QString &baseDir, const QualifiedId &name) + { + return baseDir + QLatin1Char('/') + outputBaseDirName() + QLatin1Char('/') + + name.toString(); + } + QString outputDirPath(const QString &baseDir) const + { + return outputDirPath(baseDir, name); + } + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(reinterpret_cast(name), config, searchPaths); + } + + QualifiedId name; + QVariantMap config; + QStringList searchPaths; + bool transientOutput = false; // Not to be serialized. +}; + +using ModuleProviderInfoList = std::vector; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/language/preparescriptobserver.cpp b/src/lib/corelib/language/preparescriptobserver.cpp new file mode 100644 index 00000000..632cbfb5 --- /dev/null +++ b/src/lib/corelib/language/preparescriptobserver.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** 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 "preparescriptobserver.h" + +#include "property.h" +#include "scriptengine.h" + +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +PrepareScriptObserver::PrepareScriptObserver(ScriptEngine *engine, UnobserveMode unobserveMode) + : ScriptPropertyObserver(engine, unobserveMode) +{ +} + +void PrepareScriptObserver::onPropertyRead(const QScriptValue &object, const QString &name, + const QScriptValue &value) +{ + const auto objectId = object.objectId(); + const auto projectIt = m_projectObjectIds.find(objectId); + if (projectIt != m_projectObjectIds.cend()) { + engine()->addPropertyRequestedInScript( + Property(projectIt->second, QString(), name, value.toVariant(), + Property::PropertyInProject)); + return; + } + if (m_importIds.contains(objectId)) { + engine()->addImportRequestedInScript(object.objectId()); + return; + } + const auto exportsIt = m_exportsObjectIds.find(value.objectId()); + if (exportsIt != m_exportsObjectIds.cend()) { + engine()->addRequestedExport(exportsIt->second); + return; + } + const auto it = m_parameterObjects.find(objectId); + if (it != m_parameterObjects.cend()) { + engine()->addPropertyRequestedInScript( + Property(it->second.first, it->second.second, name, value.toVariant(), + Property::PropertyInParameters)); + } + if (name == StringConstants::fileTagsProperty() && m_artifactIds.contains(objectId)) { + const Artifact * const artifact = attachedPointer(object); + QBS_CHECK(artifact); + const Property p(artifact->product->uniqueName(), QString(), name, value.toVariant(), + Property::PropertyInArtifact); + engine()->addPropertyRequestedFromArtifact(artifact, p); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/preparescriptobserver.h b/src/lib/corelib/language/preparescriptobserver.h new file mode 100644 index 00000000..36e395ef --- /dev/null +++ b/src/lib/corelib/language/preparescriptobserver.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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 QBS_PREPARESCRIPTOBSERVER_H +#define QBS_PREPARESCRIPTOBSERVER_H + +#include "qualifiedid.h" +#include "scriptpropertyobserver.h" + +#include + +#include + +#include + +namespace qbs { +namespace Internal { +class ResolvedProduct; +class ScriptEngine; + +class PrepareScriptObserver : public ScriptPropertyObserver +{ +public: + PrepareScriptObserver(ScriptEngine *engine, UnobserveMode unobserveMode); + + void addProjectObjectId(qint64 projectId, const QString &projectName) + { + m_projectObjectIds.insert(std::make_pair(projectId, projectName)); + } + + void addExportsObjectId(qint64 exportsId, const ResolvedProduct *product) + { + m_exportsObjectIds.insert(std::make_pair(exportsId, product)); + } + + void addArtifactId(qint64 artifactId) { m_artifactIds.insert(artifactId); } + bool addImportId(qint64 importId) { return m_importIds.insert(importId).second; } + void clearImportIds() { m_importIds.clear(); } + void addParameterObjectId(qint64 id, const QString &productName, const QString &depName, + const QualifiedId &moduleName) + { + const QString depAndModuleName = depName + QLatin1Char(':') + moduleName.toString(); + const auto value = std::make_pair(productName, depAndModuleName); + m_parameterObjects.insert(std::make_pair(id, value)); + } + +private: + void onPropertyRead(const QScriptValue &object, const QString &name, + const QScriptValue &value) override; + + std::unordered_map m_projectObjectIds; + std::unordered_map> m_parameterObjects; + std::unordered_map m_exportsObjectIds; + Set m_importIds; + Set m_artifactIds; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/language/projectresolver.cpp b/src/lib/corelib/language/projectresolver.cpp new file mode 100644 index 00000000..fd606338 --- /dev/null +++ b/src/lib/corelib/language/projectresolver.cpp @@ -0,0 +1,1875 @@ +/**************************************************************************** +** +** 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 "projectresolver.h" + +#include "artifactproperties.h" +#include "builtindeclarations.h" +#include "evaluator.h" +#include "filecontext.h" +#include "item.h" +#include "language.h" +#include "propertymapinternal.h" +#include "resolvedfilecontext.h" +#include "scriptengine.h" +#include "value.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +extern bool debugProperties; + +static const FileTag unknownFileTag() +{ + static const FileTag tag("unknown-file-tag"); + return tag; +} + +struct ProjectResolver::ProjectContext +{ + ProjectContext *parentContext = nullptr; + ResolvedProjectPtr project; + std::vector fileTaggers; + std::vector rules; + JobLimits jobLimits; + ResolvedModulePtr dummyModule; +}; + +struct ProjectResolver::ProductContext +{ + ResolvedProductPtr product; + QString buildDirectory; + Item *item = nullptr; + using ArtifactPropertiesInfo = std::pair>; + QHash artifactPropertiesPerFilter; + ProjectResolver::FileLocations sourceArtifactLocations; + GroupConstPtr currentGroup; +}; + +struct ProjectResolver::ModuleContext +{ + ResolvedModulePtr module; + JobLimits jobLimits; +}; + +class CancelException { }; + + +ProjectResolver::ProjectResolver(Evaluator *evaluator, ModuleLoaderResult loadResult, + SetupProjectParameters setupParameters, Logger &logger) + : m_evaluator(evaluator) + , m_logger(logger) + , m_engine(m_evaluator->engine()) + , m_progressObserver(nullptr) + , m_setupParams(std::move(setupParameters)) + , m_loadResult(std::move(loadResult)) +{ + QBS_CHECK(FileInfo::isAbsolute(m_setupParams.buildRoot())); +} + +ProjectResolver::~ProjectResolver() = default; + +void ProjectResolver::setProgressObserver(ProgressObserver *observer) +{ + m_progressObserver = observer; +} + +static void checkForDuplicateProductNames(const TopLevelProjectConstPtr &project) +{ + const std::vector allProducts = project->allProducts(); + for (size_t i = 0; i < allProducts.size(); ++i) { + const ResolvedProductConstPtr product1 = allProducts.at(i); + const QString productName = product1->uniqueName(); + for (size_t j = i + 1; j < allProducts.size(); ++j) { + const ResolvedProductConstPtr product2 = allProducts.at(j); + if (product2->uniqueName() == productName) { + ErrorInfo error; + error.append(Tr::tr("Duplicate product name '%1'.").arg(product1->name)); + error.append(Tr::tr("First product defined here."), product1->location); + error.append(Tr::tr("Second product defined here."), product2->location); + throw error; + } + } + } +} + +TopLevelProjectPtr ProjectResolver::resolve() +{ + TimedActivityLogger projectResolverTimer(m_logger, Tr::tr("ProjectResolver"), + m_setupParams.logElapsedTime()); + qCDebug(lcProjectResolver) << "resolving" << m_loadResult.root->file()->filePath(); + + m_productContext = nullptr; + m_moduleContext = nullptr; + m_elapsedTimeModPropEval = m_elapsedTimeAllPropEval = m_elapsedTimeGroups = 0; + TopLevelProjectPtr tlp; + try { + tlp = resolveTopLevelProject(); + printProfilingInfo(); + } catch (const CancelException &) { + throw ErrorInfo(Tr::tr("Project resolving canceled for configuration '%1'.") + .arg(TopLevelProject::deriveId(m_setupParams.finalBuildConfigurationTree()))); + } + return tlp; +} + +void ProjectResolver::checkCancelation() const +{ + if (m_progressObserver && m_progressObserver->canceled()) + throw CancelException(); +} + +QString ProjectResolver::verbatimValue(const ValueConstPtr &value, bool *propertyWasSet) const +{ + QString result; + if (value && value->type() == Value::JSSourceValueType) { + const JSSourceValueConstPtr sourceValue = std::static_pointer_cast( + value); + result = sourceCodeForEvaluation(sourceValue); + if (propertyWasSet) + *propertyWasSet = !sourceValue->isBuiltinDefaultValue(); + } else { + if (propertyWasSet) + *propertyWasSet = false; + } + return result; +} + +QString ProjectResolver::verbatimValue(Item *item, const QString &name, bool *propertyWasSet) const +{ + return verbatimValue(item->property(name), propertyWasSet); +} + +void ProjectResolver::ignoreItem(Item *item, ProjectContext *projectContext) +{ + Q_UNUSED(item); + Q_UNUSED(projectContext); +} + +static void makeSubProjectNamesUniqe(const ResolvedProjectPtr &parentProject) +{ + Set subProjectNames; + Set projectsInNeedOfNameChange; + for (const ResolvedProjectPtr &p : qAsConst(parentProject->subProjects)) { + if (!subProjectNames.insert(p->name).second) + projectsInNeedOfNameChange << p; + makeSubProjectNamesUniqe(p); + } + while (!projectsInNeedOfNameChange.empty()) { + auto it = projectsInNeedOfNameChange.begin(); + while (it != projectsInNeedOfNameChange.end()) { + const ResolvedProjectPtr p = *it; + p->name += QLatin1Char('_'); + if (subProjectNames.insert(p->name).second) { + it = projectsInNeedOfNameChange.erase(it); + } else { + ++it; + } + } + } +} + +TopLevelProjectPtr ProjectResolver::resolveTopLevelProject() +{ + if (m_progressObserver) + m_progressObserver->setMaximum(m_loadResult.productInfos.size()); + const TopLevelProjectPtr project = TopLevelProject::create(); + project->buildDirectory = TopLevelProject::deriveBuildDirectory(m_setupParams.buildRoot(), + TopLevelProject::deriveId(m_setupParams.finalBuildConfigurationTree())); + project->buildSystemFiles = m_loadResult.qbsFiles; + project->profileConfigs = m_loadResult.profileConfigs; + project->probes = m_loadResult.projectProbes; + project->moduleProviderInfo = m_loadResult.moduleProviderInfo; + ProjectContext projectContext; + projectContext.project = project; + + resolveProject(m_loadResult.root, &projectContext); + ErrorInfo accumulatedErrors; + for (const ErrorInfo &e : m_queuedErrors) + appendError(accumulatedErrors, e); + if (accumulatedErrors.hasError()) + throw accumulatedErrors; + + project->setBuildConfiguration(m_setupParams.finalBuildConfigurationTree()); + project->overriddenValues = m_setupParams.overriddenValues(); + project->canonicalFilePathResults = m_engine->canonicalFilePathResults(); + project->fileExistsResults = m_engine->fileExistsResults(); + project->directoryEntriesResults = m_engine->directoryEntriesResults(); + project->fileLastModifiedResults = m_engine->fileLastModifiedResults(); + project->environment = m_engine->environment(); + project->buildSystemFiles.unite(m_engine->imports()); + makeSubProjectNamesUniqe(project); + resolveProductDependencies(projectContext); + collectExportedProductDependencies(); + checkForDuplicateProductNames(project); + + for (const ResolvedProductPtr &product : project->allProducts()) { + if (!product->enabled) + continue; + + applyFileTaggers(product); + matchArtifactProperties(product, product->allEnabledFiles()); + + // Let a positive value of qbs.install imply the file tag "installable". + for (const SourceArtifactPtr &artifact : product->allFiles()) { + if (artifact->properties->qbsPropertyValue(StringConstants::installProperty()).toBool()) + artifact->fileTags += "installable"; + } + } + project->warningsEncountered = m_logger.warnings(); + return project; +} + +void ProjectResolver::resolveProject(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + + if (projectContext->parentContext) + projectContext->project->enabled = projectContext->parentContext->project->enabled; + projectContext->project->location = item->location(); + try { + resolveProjectFully(item, projectContext); + } catch (const ErrorInfo &error) { + if (!projectContext->project->enabled) { + qCDebug(lcProjectResolver) << "error resolving project" + << projectContext->project->location << error.toString(); + return; + } + if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) + throw; + m_logger.printWarning(error); + } +} + +void ProjectResolver::resolveProjectFully(Item *item, ProjectResolver::ProjectContext *projectContext) +{ + projectContext->project->enabled = projectContext->project->enabled + && m_evaluator->boolValue(item, StringConstants::conditionProperty()); + projectContext->project->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); + if (projectContext->project->name.isEmpty()) + projectContext->project->name = FileInfo::baseName(item->location().filePath()); // FIXME: Must also be changed in item? + QVariantMap projectProperties; + if (!projectContext->project->enabled) { + projectProperties.insert(StringConstants::profileProperty(), + m_evaluator->stringValue(item, + StringConstants::profileProperty())); + projectContext->project->setProjectProperties(projectProperties); + return; + } + + projectContext->dummyModule = ResolvedModule::create(); + + for (Item::PropertyDeclarationMap::const_iterator it + = item->propertyDeclarations().constBegin(); + it != item->propertyDeclarations().constEnd(); ++it) { + if (it.value().flags().testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) + continue; + const ValueConstPtr v = item->property(it.key()); + QBS_ASSERT(v && v->type() != Value::ItemValueType, continue); + projectProperties.insert(it.key(), m_evaluator->value(item, it.key()).toVariant()); + } + projectContext->project->setProjectProperties(projectProperties); + + static const ItemFuncMap mapping = { + { ItemType::Project, &ProjectResolver::resolveProject }, + { ItemType::SubProject, &ProjectResolver::resolveSubProject }, + { ItemType::Product, &ProjectResolver::resolveProduct }, + { ItemType::Probe, &ProjectResolver::ignoreItem }, + { ItemType::FileTagger, &ProjectResolver::resolveFileTagger }, + { ItemType::JobLimit, &ProjectResolver::resolveJobLimit }, + { ItemType::Rule, &ProjectResolver::resolveRule }, + { ItemType::PropertyOptions, &ProjectResolver::ignoreItem } + }; + + for (Item * const child : item->children()) { + try { + callItemFunction(mapping, child, projectContext); + } catch (const ErrorInfo &e) { + m_queuedErrors.push_back(e); + } + } + + for (const ResolvedProductPtr &product : projectContext->project->products) + postProcess(product, projectContext); +} + +void ProjectResolver::resolveSubProject(Item *item, ProjectResolver::ProjectContext *projectContext) +{ + ProjectContext subProjectContext = createProjectContext(projectContext); + + Item * const projectItem = item->child(ItemType::Project); + if (projectItem) { + resolveProject(projectItem, &subProjectContext); + return; + } + + // No project item was found, which means the project was disabled. + subProjectContext.project->enabled = false; + Item * const propertiesItem = item->child(ItemType::PropertiesInSubProject); + if (propertiesItem) { + subProjectContext.project->name + = m_evaluator->stringValue(propertiesItem, StringConstants::nameProperty()); + } +} + +class ProjectResolver::ProductContextSwitcher +{ +public: + ProductContextSwitcher(ProjectResolver *resolver, ProductContext *newContext, + ProgressObserver *progressObserver) + : m_resolver(resolver), m_progressObserver(progressObserver) + { + QBS_CHECK(!m_resolver->m_productContext); + m_resolver->m_productContext = newContext; + } + + ~ProductContextSwitcher() + { + if (m_progressObserver) + m_progressObserver->incrementProgressValue(); + m_resolver->m_productContext = nullptr; + } + +private: + ProjectResolver * const m_resolver; + ProgressObserver * const m_progressObserver; +}; + +void ProjectResolver::resolveProduct(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + m_evaluator->clearPropertyDependencies(); + ProductContext productContext; + productContext.item = item; + ResolvedProductPtr product = ResolvedProduct::create(); + product->enabled = projectContext->project->enabled; + product->moduleProperties = PropertyMapInternal::create(); + product->project = projectContext->project; + productContext.product = product; + product->location = item->location(); + ProductContextSwitcher contextSwitcher(this, &productContext, m_progressObserver); + try { + resolveProductFully(item, projectContext); + } catch (const ErrorInfo &e) { + QString mainErrorString = !product->name.isEmpty() + ? Tr::tr("Error while handling product '%1':").arg(product->name) + : Tr::tr("Error while handling product:"); + ErrorInfo fullError(mainErrorString, item->location()); + appendError(fullError, e); + if (!product->enabled) { + qCDebug(lcProjectResolver) << fullError.toString(); + return; + } + if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) + throw fullError; + m_logger.printWarning(fullError); + m_logger.printWarning(ErrorInfo(Tr::tr("Product '%1' had errors and was disabled.") + .arg(product->name), item->location())); + product->enabled = false; + } +} + +void ProjectResolver::resolveProductFully(Item *item, ProjectContext *projectContext) +{ + const ResolvedProductPtr product = m_productContext->product; + m_productItemMap.insert(product, item); + projectContext->project->products.push_back(product); + product->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); + + // product->buildDirectory() isn't valid yet, because the productProperties map is not ready. + m_productContext->buildDirectory + = m_evaluator->stringValue(item, StringConstants::buildDirectoryProperty()); + product->multiplexConfigurationId + = m_evaluator->stringValue(item, StringConstants::multiplexConfigurationIdProperty()); + qCDebug(lcProjectResolver) << "resolveProduct" << product->uniqueName(); + m_productsByName.insert(product->uniqueName(), product); + product->enabled = product->enabled + && m_evaluator->boolValue(item, StringConstants::conditionProperty()); + ModuleLoaderResult::ProductInfo &pi = m_loadResult.productInfos[item]; + if (pi.delayedError.hasError()) { + ErrorInfo errorInfo; + + // First item is "main error", gets prepended again in the catch clause. + const QList &items = pi.delayedError.items(); + for (int i = 1; i < items.size(); ++i) + errorInfo.append(items.at(i)); + + pi.delayedError.clear(); + throw errorInfo; + } + gatherProductTypes(product.get(), item); + product->targetName = m_evaluator->stringValue(item, StringConstants::targetNameProperty()); + product->sourceDirectory = m_evaluator->stringValue( + item, StringConstants::sourceDirectoryProperty()); + product->destinationDirectory = m_evaluator->stringValue( + item, StringConstants::destinationDirProperty()); + + if (product->destinationDirectory.isEmpty()) { + product->destinationDirectory = m_productContext->buildDirectory; + } else { + product->destinationDirectory = FileInfo::resolvePath( + product->topLevelProject()->buildDirectory, + product->destinationDirectory); + } + product->probes = pi.probes; + createProductConfig(product.get()); + product->productProperties.insert(StringConstants::destinationDirProperty(), + product->destinationDirectory); + ModuleProperties::init(m_evaluator->scriptValue(item), product.get()); + + QList subItems = item->children(); + const ValuePtr filesProperty = item->property(StringConstants::filesProperty()); + if (filesProperty) { + Item *fakeGroup = Item::create(item->pool(), ItemType::Group); + fakeGroup->setFile(item->file()); + fakeGroup->setLocation(item->location()); + fakeGroup->setScope(item); + fakeGroup->setProperty(StringConstants::nameProperty(), VariantValue::create(product->name)); + fakeGroup->setProperty(StringConstants::filesProperty(), filesProperty); + fakeGroup->setProperty(StringConstants::excludeFilesProperty(), + item->property(StringConstants::excludeFilesProperty())); + fakeGroup->setProperty(StringConstants::overrideTagsProperty(), + VariantValue::falseValue()); + fakeGroup->setupForBuiltinType(m_logger); + subItems.prepend(fakeGroup); + } + + static const ItemFuncMap mapping = { + { ItemType::Depends, &ProjectResolver::ignoreItem }, + { ItemType::Rule, &ProjectResolver::resolveRule }, + { ItemType::FileTagger, &ProjectResolver::resolveFileTagger }, + { ItemType::JobLimit, &ProjectResolver::resolveJobLimit }, + { ItemType::Group, &ProjectResolver::resolveGroup }, + { ItemType::Product, &ProjectResolver::resolveShadowProduct }, + { ItemType::Export, &ProjectResolver::resolveExport }, + { ItemType::Probe, &ProjectResolver::ignoreItem }, + { ItemType::PropertyOptions, &ProjectResolver::ignoreItem } + }; + + for (Item * const child : qAsConst(subItems)) + callItemFunction(mapping, child, projectContext); + + for (const ProjectContext *p = projectContext; p; p = p->parentContext) { + JobLimits tempLimits = p->jobLimits; + product->jobLimits = tempLimits.update(product->jobLimits); + } + + resolveModules(item, projectContext); + + for (const FileTag &t : qAsConst(product->fileTags)) + m_productsByType[t].push_back(product); +} + +void ProjectResolver::resolveModules(const Item *item, ProjectContext *projectContext) +{ + JobLimits jobLimits; + for (const Item::Module &m : item->modules()) + resolveModule(m.name, m.item, m.isProduct, m.parameters, jobLimits, projectContext); + for (int i = 0; i < jobLimits.count(); ++i) { + const JobLimit &moduleJobLimit = jobLimits.jobLimitAt(i); + if (m_productContext->product->jobLimits.getLimit(moduleJobLimit.pool()) == -1) + m_productContext->product->jobLimits.setJobLimit(moduleJobLimit); + } +} + +void ProjectResolver::resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct, + const QVariantMap ¶meters, JobLimits &jobLimits, + ProjectContext *projectContext) +{ + checkCancelation(); + if (!item->isPresentModule()) + return; + + ModuleContext * const oldModuleContext = m_moduleContext; + ModuleContext moduleContext; + moduleContext.module = ResolvedModule::create(); + m_moduleContext = &moduleContext; + + const ResolvedModulePtr &module = moduleContext.module; + module->name = moduleName.toString(); + module->isProduct = isProduct; + module->product = m_productContext->product.get(); + module->setupBuildEnvironmentScript.initialize( + scriptFunctionValue(item, StringConstants::setupBuildEnvironmentProperty())); + module->setupRunEnvironmentScript.initialize( + scriptFunctionValue(item, StringConstants::setupRunEnvironmentProperty())); + + for (const Item::Module &m : item->modules()) { + if (m.item->isPresentModule()) + module->moduleDependencies += m.name.toString(); + } + + m_productContext->product->modules.push_back(module); + if (!parameters.empty()) + m_productContext->product->moduleParameters[module] = parameters; + + static const ItemFuncMap mapping { + { ItemType::Group, &ProjectResolver::ignoreItem }, + { ItemType::Rule, &ProjectResolver::resolveRule }, + { ItemType::FileTagger, &ProjectResolver::resolveFileTagger }, + { ItemType::JobLimit, &ProjectResolver::resolveJobLimit }, + { ItemType::Scanner, &ProjectResolver::resolveScanner }, + { ItemType::PropertyOptions, &ProjectResolver::ignoreItem }, + { ItemType::Depends, &ProjectResolver::ignoreItem }, + { ItemType::Parameter, &ProjectResolver::ignoreItem }, + { ItemType::Properties, &ProjectResolver::ignoreItem }, + { ItemType::Probe, &ProjectResolver::ignoreItem } + }; + for (Item *child : item->children()) + callItemFunction(mapping, child, projectContext); + for (int i = 0; i < moduleContext.jobLimits.count(); ++i) { + const JobLimit &newJobLimit = moduleContext.jobLimits.jobLimitAt(i); + const int oldLimit = jobLimits.getLimit(newJobLimit.pool()); + if (oldLimit == -1 || oldLimit > newJobLimit.limit()) + jobLimits.setJobLimit(newJobLimit); + } + + m_moduleContext = oldModuleContext; +} + +void ProjectResolver::gatherProductTypes(ResolvedProduct *product, Item *item) +{ + product->fileTags = m_evaluator->fileTagsValue(item, StringConstants::typeProperty()); + for (const Item::Module &m : item->modules()) { + if (m.item->isPresentModule()) { + product->fileTags += m_evaluator->fileTagsValue(m.item, + StringConstants::additionalProductTypesProperty()); + } + } + item->setProperty(StringConstants::typeProperty(), + VariantValue::create(sorted(product->fileTags.toStringList()))); +} + +SourceArtifactPtr ProjectResolver::createSourceArtifact(const ResolvedProductPtr &rproduct, + const QString &fileName, const GroupPtr &group, bool wildcard, + const CodeLocation &filesLocation, FileLocations *fileLocations, + ErrorInfo *errorInfo) +{ + const QString &baseDir = FileInfo::path(group->location.filePath()); + const QString absFilePath = QDir::cleanPath(FileInfo::resolvePath(baseDir, fileName)); + if (!wildcard && !FileInfo(absFilePath).exists()) { + if (errorInfo) + errorInfo->append(Tr::tr("File '%1' does not exist.").arg(absFilePath), filesLocation); + rproduct->missingSourceFiles << absFilePath; + return {}; + } + if (group->enabled && fileLocations) { + CodeLocation &loc = (*fileLocations)[std::make_pair(group->targetOfModule, absFilePath)]; + if (loc.isValid()) { + if (errorInfo) { + errorInfo->append(Tr::tr("Duplicate source file '%1'.").arg(absFilePath)); + errorInfo->append(Tr::tr("First occurrence is here."), loc); + errorInfo->append(Tr::tr("Next occurrence is here."), filesLocation); + } + return {}; + } + loc = filesLocation; + } + SourceArtifactPtr artifact = SourceArtifactInternal::create(); + artifact->absoluteFilePath = absFilePath; + artifact->fileTags = group->fileTags; + artifact->overrideFileTags = group->overrideTags; + artifact->properties = group->properties; + artifact->targetOfModule = group->targetOfModule; + (wildcard ? group->wildcards->files : group->files).push_back(artifact); + return artifact; +} + +static QualifiedIdSet propertiesToEvaluate(const QList &initialProps, + const PropertyDependencies &deps) +{ + QList remainingProps = initialProps; + QualifiedIdSet allProperties; + while (!remainingProps.empty()) { + const QualifiedId prop = remainingProps.takeFirst(); + const auto insertResult = allProperties.insert(prop); + if (!insertResult.second) + continue; + for (const QualifiedId &directDep : deps.value(prop)) + remainingProps.push_back(directDep); + } + return allProperties; +} + +QVariantMap ProjectResolver::resolveAdditionalModuleProperties(const Item *group, + const QVariantMap ¤tValues) +{ + // Step 1: Retrieve the properties directly set in the group + const ModulePropertiesPerGroup &mp = m_loadResult.productInfos.value(m_productContext->item) + .modulePropertiesSetInGroups; + const auto it = mp.find(group); + if (it == mp.end()) + return {}; + const QualifiedIdSet &propsSetInGroup = it->second; + + // Step 2: Gather all properties that depend on these properties. + const QualifiedIdSet &propsToEval + = propertiesToEvaluate(propsSetInGroup.toList(), m_evaluator->propertyDependencies()); + + // Step 3: Evaluate all these properties and replace their values in the map + QVariantMap modulesMap = currentValues; + QHash propsPerModule; + for (auto fullPropName : propsToEval) { + const QString moduleName + = QualifiedId(fullPropName.mid(0, fullPropName.size() - 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); + if (propsForModule.empty()) + continue; + QVariantMap reusableValues = modulesMap.value(fullModName).toMap(); + for (const QString &prop : qAsConst(propsForModule)) + reusableValues.remove(prop); + modulesMap.insert(fullModName, + evaluateProperties(module.item, module.item, reusableValues, true, true)); + } + m_evaluator->clearPathPropertiesBaseDir(); + return modulesMap; +} + +void ProjectResolver::resolveGroup(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + const bool parentEnabled = m_productContext->currentGroup + ? m_productContext->currentGroup->enabled + : m_productContext->product->enabled; + const bool isEnabled = parentEnabled + && m_evaluator->boolValue(item, StringConstants::conditionProperty()); + try { + resolveGroupFully(item, projectContext, isEnabled); + } catch (const ErrorInfo &error) { + if (!isEnabled) { + qCDebug(lcProjectResolver) << "error resolving group at" << item->location() + << error.toString(); + return; + } + if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) + throw; + m_logger.printWarning(error); + } +} + +void ProjectResolver::resolveGroupFully(Item *item, ProjectResolver::ProjectContext *projectContext, + bool isEnabled) +{ + AccumulatingTimer groupTimer(m_setupParams.logElapsedTime() + ? &m_elapsedTimeGroups : nullptr); + + const auto getGroupPropertyMap = [this, item](const ArtifactProperties *existingProps) { + PropertyMapPtr moduleProperties; + bool newPropertyMapRequired = false; + if (existingProps) + moduleProperties = existingProps->propertyMap(); + if (!moduleProperties) { + newPropertyMapRequired = true; + moduleProperties = m_productContext->currentGroup + ? m_productContext->currentGroup->properties + : m_productContext->product->moduleProperties; + } + const QVariantMap newModuleProperties + = resolveAdditionalModuleProperties(item, moduleProperties->value()); + if (!newModuleProperties.empty()) { + if (newPropertyMapRequired) + moduleProperties = PropertyMapInternal::create(); + moduleProperties->setValue(newModuleProperties); + } + return moduleProperties; + }; + + QStringList files = m_evaluator->stringListValue(item, StringConstants::filesProperty()); + bool fileTagsSet; + const FileTags fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty(), + &fileTagsSet); + const QStringList fileTagsFilter + = m_evaluator->stringListValue(item, StringConstants::fileTagsFilterProperty()); + if (!fileTagsFilter.empty()) { + if (Q_UNLIKELY(!files.empty())) + throw ErrorInfo(Tr::tr("Group.files and Group.fileTagsFilters are exclusive."), + item->location()); + + if (!isEnabled) + return; + + ProductContext::ArtifactPropertiesInfo &apinfo + = m_productContext->artifactPropertiesPerFilter[fileTagsFilter]; + if (apinfo.first) { + const auto it = std::find_if(apinfo.second.cbegin(), apinfo.second.cend(), + [item](const CodeLocation &loc) { + return item->location().filePath() == loc.filePath(); + }); + if (it != apinfo.second.cend()) { + ErrorInfo error(Tr::tr("Conflicting fileTagsFilter in Group items.")); + error.append(Tr::tr("First item"), *it); + error.append(Tr::tr("Second item"), item->location()); + throw error; + } + } else { + apinfo.first = ArtifactProperties::create(); + apinfo.first->setFileTagsFilter(FileTags::fromStringList(fileTagsFilter)); + m_productContext->product->artifactProperties.push_back(apinfo.first); + } + apinfo.second.push_back(item->location()); + apinfo.first->setPropertyMapInternal(getGroupPropertyMap(apinfo.first.get())); + apinfo.first->addExtraFileTags(fileTags); + return; + } + QStringList patterns; + for (int i = files.size(); --i >= 0;) { + if (FileInfo::isPattern(files[i])) + patterns.push_back(files.takeAt(i)); + } + GroupPtr group = ResolvedGroup::create(); + bool prefixWasSet = false; + group->prefix = m_evaluator->stringValue(item, StringConstants::prefixProperty(), QString(), + &prefixWasSet); + if (!prefixWasSet && m_productContext->currentGroup) + group->prefix = m_productContext->currentGroup->prefix; + if (!group->prefix.isEmpty()) { + for (auto it = files.rbegin(), end = files.rend(); it != end; ++it) + it->prepend(group->prefix); + } + group->location = item->location(); + group->enabled = isEnabled; + group->properties = getGroupPropertyMap(nullptr); + group->fileTags = fileTags; + group->overrideTags = m_evaluator->boolValue(item, StringConstants::overrideTagsProperty()); + if (group->overrideTags && fileTagsSet) { + if (group->fileTags.empty() ) + group->fileTags.insert(unknownFileTag()); + } else if (m_productContext->currentGroup) { + group->fileTags.unite(m_productContext->currentGroup->fileTags); + } + + const CodeLocation filesLocation = item->property(StringConstants::filesProperty())->location(); + const VariantValueConstPtr moduleProp = item->variantProperty( + StringConstants::modulePropertyInternal()); + if (moduleProp) + group->targetOfModule = moduleProp->value().toString(); + ErrorInfo fileError; + if (!patterns.empty()) { + group->wildcards = std::make_unique(); + SourceWildCards *wildcards = group->wildcards.get(); + wildcards->group = group.get(); + wildcards->excludePatterns = m_evaluator->stringListValue( + item, StringConstants::excludeFilesProperty()); + wildcards->patterns = patterns; + const Set files = wildcards->expandPatterns(group, + FileInfo::path(item->file()->filePath()), + projectContext->project->topLevelProject()->buildDirectory); + for (const QString &fileName : files) + createSourceArtifact(m_productContext->product, fileName, group, true, filesLocation, + &m_productContext->sourceArtifactLocations, &fileError); + } + + for (const QString &fileName : qAsConst(files)) { + createSourceArtifact(m_productContext->product, fileName, group, false, filesLocation, + &m_productContext->sourceArtifactLocations, &fileError); + } + if (fileError.hasError()) { + if (group->enabled) { + if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) + throw ErrorInfo(fileError); + m_logger.printWarning(fileError); + } else { + qCDebug(lcProjectResolver) << "error for disabled group:" << fileError.toString(); + } + } + group->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); + if (group->name.isEmpty()) + group->name = Tr::tr("Group %1").arg(m_productContext->product->groups.size()); + m_productContext->product->groups.push_back(group); + + class GroupContextSwitcher { + public: + GroupContextSwitcher(ProductContext &context, const GroupConstPtr &newGroup) + : m_context(context), m_oldGroup(context.currentGroup) { + m_context.currentGroup = newGroup; + } + ~GroupContextSwitcher() { m_context.currentGroup = m_oldGroup; } + private: + ProductContext &m_context; + const GroupConstPtr m_oldGroup; + }; + GroupContextSwitcher groupSwitcher(*m_productContext, group); + for (Item * const childItem : item->children()) + resolveGroup(childItem, projectContext); +} + +void ProjectResolver::adaptExportedPropertyValues(const Item *shadowProductItem) +{ + ExportedModule &m = m_productContext->product->exportedModule; + const QVariantList prefixList = m.propertyValues.take( + StringConstants::prefixMappingProperty()).toList(); + const QString shadowProductName = m_evaluator->stringValue( + shadowProductItem, StringConstants::nameProperty()); + const QString shadowProductBuildDir = m_evaluator->stringValue( + shadowProductItem, StringConstants::buildDirectoryProperty()); + QVariantMap prefixMap; + for (const QVariant &v : prefixList) { + const QVariantMap o = v.toMap(); + prefixMap.insert(o.value(QStringLiteral("prefix")).toString(), + o.value(QStringLiteral("replacement")).toString()); + } + const auto valueRefersToImportingProduct + = [shadowProductName, shadowProductBuildDir](const QString &value) { + return value.toLower().contains(shadowProductName.toLower()) + || value.contains(shadowProductBuildDir); + }; + static const auto stringMapper = [](const QVariantMap &mappings, const QString &value) + -> QString { + for (auto it = mappings.cbegin(); it != mappings.cend(); ++it) { + if (value.startsWith(it.key())) + return it.value().toString() + value.mid(it.key().size()); + } + return value; + }; + const auto stringListMapper = [&valueRefersToImportingProduct]( + const QVariantMap &mappings, const QStringList &value) -> QStringList { + QStringList result; + result.reserve(value.size()); + for (const QString &s : value) { + if (!valueRefersToImportingProduct(s)) + result.push_back(stringMapper(mappings, s)); + } + return result; + }; + const std::function mapper + = [&stringListMapper, &mapper]( + const QVariantMap &mappings, const QVariant &value) -> QVariant { + switch (static_cast(value.type())) { + case QMetaType::QString: + return stringMapper(mappings, value.toString()); + case QMetaType::QStringList: + return stringListMapper(mappings, value.toStringList()); + case QMetaType::QVariantMap: { + QVariantMap m = value.toMap(); + for (auto it = m.begin(); it != m.end(); ++it) + it.value() = mapper(mappings, it.value()); + return m; + } + default: + return value; + } + }; + for (auto it = m.propertyValues.begin(); it != m.propertyValues.end(); ++it) + it.value() = mapper(prefixMap, it.value()); + for (auto it = m.modulePropertyValues.begin(); it != m.modulePropertyValues.end(); ++it) + it.value() = mapper(prefixMap, it.value()); + for (ExportedModuleDependency &dep : m.moduleDependencies) { + for (auto it = dep.moduleProperties.begin(); it != dep.moduleProperties.end(); ++it) + it.value() = mapper(prefixMap, it.value()); + } +} + +void ProjectResolver::collectExportedProductDependencies() +{ + ResolvedProductPtr dummyProduct = ResolvedProduct::create(); + dummyProduct->enabled = false; + for (const auto &exportingProductInfo : qAsConst(m_productExportInfo)) { + const ResolvedProductPtr exportingProduct = exportingProductInfo.first; + if (!exportingProduct->enabled) + continue; + Item * const importingProductItem = exportingProductInfo.second; + std::vector directDepNames; + for (const Item::Module &m : importingProductItem->modules()) { + if (m.name.toString() == exportingProduct->name) { + for (const Item::Module &dep : m.item->modules()) { + if (dep.isProduct) + directDepNames.push_back(dep.name.toString()); + } + break; + } + } + const ModuleLoaderResult::ProductInfo &importingProductInfo + = m_loadResult.productInfos.value(importingProductItem); + const ProductDependencyInfos &depInfos + = getProductDependencies(dummyProduct, importingProductInfo); + for (const auto &dep : depInfos.dependencies) { + if (dep.product == exportingProduct) + continue; + + // Filter out indirect dependencies. + // TODO: Depends items using "profile" or "productTypes" will not work. + if (!contains(directDepNames, dep.product->name)) + continue; + + if (!contains(exportingProduct->exportedModule.productDependencies, + dep.product->uniqueName())) { + exportingProduct->exportedModule.productDependencies.push_back( + dep.product->uniqueName()); + } + if (!dep.parameters.isEmpty()) { + exportingProduct->exportedModule.dependencyParameters.insert(dep.product, + dep.parameters); + } + } + auto &productDeps = exportingProduct->exportedModule.productDependencies; + std::sort(productDeps.begin(), productDeps.end()); + } +} + +void ProjectResolver::resolveShadowProduct(Item *item, ProjectResolver::ProjectContext *) +{ + if (!m_productContext->product->enabled) + return; + for (const auto &m : item->modules()) { + if (m.name.toString() != m_productContext->product->name) + continue; + collectPropertiesForExportItem(m.item); + for (const auto &dep : m.item->modules()) + collectPropertiesForModuleInExportItem(dep); + break; + } + try { + adaptExportedPropertyValues(item); + } catch (const ErrorInfo &) {} + m_productExportInfo.emplace_back(m_productContext->product, item); +} + +void ProjectResolver::setupExportedProperties(const Item *item, const QString &namePrefix, + std::vector &properties) +{ + const auto &props = item->properties(); + for (auto it = props.cbegin(); it != props.cend(); ++it) { + const QString qualifiedName = namePrefix.isEmpty() + ? it.key() : namePrefix + QLatin1Char('.') + it.key(); + if ((item->type() == ItemType::Export || item->type() == ItemType::Properties) + && qualifiedName == StringConstants::prefixMappingProperty()) { + continue; + } + const ValuePtr &v = it.value(); + if (v->type() == Value::ItemValueType) { + setupExportedProperties(std::static_pointer_cast(v)->item(), + qualifiedName, properties); + continue; + } + ExportedProperty exportedProperty; + exportedProperty.fullName = qualifiedName; + exportedProperty.type = item->propertyDeclaration(it.key()).type(); + if (v->type() == Value::VariantValueType) { + exportedProperty.sourceCode = toJSLiteral( + std::static_pointer_cast(v)->value()); + } else { + QBS_CHECK(v->type() == Value::JSSourceValueType); + const JSSourceValue * const sv = static_cast(v.get()); + exportedProperty.sourceCode = sv->sourceCode().toString(); + } + const ItemDeclaration itemDecl + = BuiltinDeclarations::instance().declarationsForType(item->type()); + PropertyDeclaration propertyDecl; + const auto itemProperties = itemDecl.properties(); + for (const PropertyDeclaration &decl : itemProperties) { + if (decl.name() == it.key()) { + propertyDecl = decl; + exportedProperty.isBuiltin = true; + break; + } + } + + // Do not add built-in properties that were left at their default value. + if (!exportedProperty.isBuiltin || m_evaluator->isNonDefaultValue(item, it.key())) + properties.push_back(exportedProperty); + } + + // Order the list of properties, so the output won't look so random. + static const auto less = [](const ExportedProperty &p1, const ExportedProperty &p2) -> bool { + const int p1ComponentCount = p1.fullName.count(QLatin1Char('.')); + const int p2ComponentCount = p2.fullName.count(QLatin1Char('.')); + if (p1.isBuiltin && !p2.isBuiltin) + return true; + if (!p1.isBuiltin && p2.isBuiltin) + return false; + if (p1ComponentCount < p2ComponentCount) + return true; + if (p1ComponentCount > p2ComponentCount) + return false; + return p1.fullName < p2.fullName; + }; + std::sort(properties.begin(), properties.end(), less); +} + +static bool usesImport(const ExportedProperty &prop, const QRegExp ®ex) +{ + return regex.indexIn(prop.sourceCode) != -1; +} + +static bool usesImport(const ExportedItem &item, const QRegExp ®ex) +{ + return any_of(item.properties, + [regex](const ExportedProperty &p) { return usesImport(p, regex); }) + || any_of(item.children, + [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); }); +} + +static bool usesImport(const ExportedModule &module, const QString &name) +{ + // Imports are used in three ways: + // (1) var f = new TextFile(...); + // (2) var path = FileInfo.joinPaths(...) + // (3) var obj = DataCollection; + const QString pattern = QStringLiteral("\\b%1\\b"); + + const QRegExp regex(pattern.arg(name)); // std::regex is much slower + return any_of(module.m_properties, + [regex](const ExportedProperty &p) { return usesImport(p, regex); }) + || any_of(module.children, + [regex](const ExportedItemPtr &child) { return usesImport(*child, regex); }); +} + +static QString getLineAtLocation(const CodeLocation &loc, const QString &content) +{ + int pos = 0; + int currentLine = 1; + while (currentLine < loc.line()) { + while (content.at(pos++) != QLatin1Char('\n')) + ; + ++currentLine; + } + const int eolPos = content.indexOf(QLatin1Char('\n'), pos); + return content.mid(pos, eolPos - pos); +} + +void ProjectResolver::resolveExport(Item *exportItem, ProjectContext *) +{ + ExportedModule &exportedModule = m_productContext->product->exportedModule; + setupExportedProperties(exportItem, QString(), exportedModule.m_properties); + static const auto cmpFunc = [](const ExportedProperty &p1, const ExportedProperty &p2) { + return p1.fullName < p2.fullName; + }; + std::sort(exportedModule.m_properties.begin(), exportedModule.m_properties.end(), cmpFunc); + for (const Item * const child : exportItem->children()) + exportedModule.children.push_back(resolveExportChild(child, exportedModule)); + for (const JsImport &jsImport : exportItem->file()->jsImports()) { + if (usesImport(exportedModule, jsImport.scopeName)) { + exportedModule.importStatements << getLineAtLocation(jsImport.location, + exportItem->file()->content()); + } + } + const auto builtInImports = JsExtensions::extensionNames(); + for (const QString &builtinImport: builtInImports) { + if (usesImport(exportedModule, builtinImport)) + exportedModule.importStatements << QStringLiteral("import qbs.") + builtinImport; + } + exportedModule.importStatements.sort(); +} + +// TODO: This probably wouldn't be necessary if we had item serialization. +std::unique_ptr ProjectResolver::resolveExportChild(const Item *item, + const ExportedModule &module) +{ + std::unique_ptr exportedItem(new ExportedItem); + + // This is the type of the built-in base item. It may turn out that we need to support + // derived items under Export. In that case, we probably need a new Item member holding + // the original type name. + exportedItem->name = item->typeName(); + + for (const Item * const child : item->children()) + exportedItem->children.push_back(resolveExportChild(child, module)); + setupExportedProperties(item, QString(), exportedItem->properties); + return exportedItem; +} + + +QString ProjectResolver::sourceCodeAsFunction(const JSSourceValueConstPtr &value, + const PropertyDeclaration &decl) const +{ + QString &scriptFunction = m_scriptFunctions[std::make_pair(value->sourceCode(), + decl.functionArgumentNames())]; + if (!scriptFunction.isNull()) + return scriptFunction; + const QString args = decl.functionArgumentNames().join(QLatin1Char(',')); + if (value->hasFunctionForm()) { + // Insert the argument list. + scriptFunction = value->sourceCodeForEvaluation(); + scriptFunction.insert(10, args); + // Remove the function application "()" that has been + // added in ItemReaderASTVisitor::visitStatement. + scriptFunction.chop(2); + } else { + scriptFunction = QLatin1String("(function(") + args + QLatin1String("){return ") + + value->sourceCode().toString() + QLatin1String(";})"); + } + return scriptFunction; +} + +QString ProjectResolver::sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const +{ + QString &code = m_sourceCode[value->sourceCode()]; + if (!code.isNull()) + return code; + code = value->sourceCodeForEvaluation(); + return code; +} + +ScriptFunctionPtr ProjectResolver::scriptFunctionValue(Item *item, const QString &name) const +{ + JSSourceValuePtr value = item->sourceProperty(name); + ScriptFunctionPtr &script = m_scriptFunctionMap[value ? value->location() : CodeLocation()]; + if (!script.get()) { + script = ScriptFunction::create(); + const PropertyDeclaration decl = item->propertyDeclaration(name); + script->sourceCode = sourceCodeAsFunction(value, decl); + script->location = value->location(); + script->fileContext = resolvedFileContext(value->file()); + } + return script; +} + +ResolvedFileContextPtr ProjectResolver::resolvedFileContext(const FileContextConstPtr &ctx) const +{ + ResolvedFileContextPtr &result = m_fileContextMap[ctx]; + if (!result) + result = ResolvedFileContext::create(*ctx); + return result; +} + +void ProjectResolver::resolveRule(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + + if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) + return; + + RulePtr rule = Rule::create(); + + // read artifacts + bool hasArtifactChildren = false; + for (Item * const child : item->children()) { + if (Q_UNLIKELY(child->type() != ItemType::Artifact)) { + throw ErrorInfo(Tr::tr("'Rule' can only have children of type 'Artifact'."), + child->location()); + } + hasArtifactChildren = true; + resolveRuleArtifact(rule, child); + } + + rule->name = m_evaluator->stringValue(item, StringConstants::nameProperty()); + rule->prepareScript.initialize(scriptFunctionValue(item, StringConstants::prepareProperty())); + rule->outputArtifactsScript.initialize(scriptFunctionValue( + item, StringConstants::outputArtifactsProperty())); + rule->outputFileTags = m_evaluator->fileTagsValue( + item, StringConstants::outputFileTagsProperty()); + if (rule->outputArtifactsScript.isValid()) { + if (hasArtifactChildren) + throw ErrorInfo(Tr::tr("The Rule.outputArtifacts script is not allowed in rules " + "that contain Artifact items."), + item->location()); + } + if (!hasArtifactChildren && rule->outputFileTags.empty()) { + throw ErrorInfo(Tr::tr("A rule needs to have Artifact items or a non-empty " + "outputFileTags property."), item->location()); + } + rule->multiplex = m_evaluator->boolValue(item, StringConstants::multiplexProperty()); + rule->alwaysRun = m_evaluator->boolValue(item, StringConstants::alwaysRunProperty()); + rule->inputs = m_evaluator->fileTagsValue(item, StringConstants::inputsProperty()); + rule->inputsFromDependencies + = m_evaluator->fileTagsValue(item, StringConstants::inputsFromDependenciesProperty()); + bool requiresInputsSet = false; + rule->requiresInputs = m_evaluator->boolValue(item, StringConstants::requiresInputsProperty(), + &requiresInputsSet); + if (!requiresInputsSet) + rule->requiresInputs = rule->declaresInputs(); + rule->auxiliaryInputs + = m_evaluator->fileTagsValue(item, StringConstants::auxiliaryInputsProperty()); + rule->excludedInputs + = m_evaluator->fileTagsValue(item, StringConstants::excludedInputsProperty()); + if (rule->excludedInputs.empty()) { + rule->excludedInputs = m_evaluator->fileTagsValue( + item, StringConstants::excludedAuxiliaryInputsProperty()); + } + rule->explicitlyDependsOn + = m_evaluator->fileTagsValue(item, StringConstants::explicitlyDependsOnProperty()); + rule->explicitlyDependsOnFromDependencies = m_evaluator->fileTagsValue( + item, StringConstants::explicitlyDependsOnFromDependenciesProperty()); + rule->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule; + if (!rule->multiplex && !rule->declaresInputs()) { + throw ErrorInfo(Tr::tr("Rule has no inputs, but is not a multiplex rule."), + item->location()); + } + if (!rule->multiplex && !rule->requiresInputs) { + throw ErrorInfo(Tr::tr("Rule.requiresInputs is false for non-multiplex rule."), + item->location()); + } + if (!rule->declaresInputs() && rule->requiresInputs) { + throw ErrorInfo(Tr::tr("Rule.requiresInputs is true, but the rule " + "does not declare any input tags."), item->location()); + } + if (m_productContext) { + rule->product = m_productContext->product.get(); + m_productContext->product->rules.push_back(rule); + } else { + projectContext->rules.push_back(rule); + } +} + +void ProjectResolver::resolveRuleArtifact(const RulePtr &rule, Item *item) +{ + RuleArtifactPtr artifact = RuleArtifact::create(); + rule->artifacts.push_back(artifact); + artifact->location = item->location(); + + if (const auto sourceProperty = item->sourceProperty(StringConstants::filePathProperty())) + artifact->filePathLocation = sourceProperty->location(); + + artifact->filePath = verbatimValue(item, StringConstants::filePathProperty()); + artifact->fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty()); + artifact->alwaysUpdated = m_evaluator->boolValue(item, + StringConstants::alwaysUpdatedProperty()); + + QualifiedIdSet seenBindings; + for (Item *obj = item; obj; obj = obj->prototype()) { + for (QMap::const_iterator it = obj->properties().constBegin(); + it != obj->properties().constEnd(); ++it) + { + if (it.value()->type() != Value::ItemValueType) + continue; + resolveRuleArtifactBinding(artifact, + std::static_pointer_cast(it.value())->item(), + QStringList(it.key()), &seenBindings); + } + } +} + +void ProjectResolver::resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, + Item *item, + const QStringList &namePrefix, + QualifiedIdSet *seenBindings) +{ + for (QMap::const_iterator it = item->properties().constBegin(); + it != item->properties().constEnd(); ++it) + { + const QStringList name = QStringList(namePrefix) << it.key(); + if (it.value()->type() == Value::ItemValueType) { + resolveRuleArtifactBinding(ruleArtifact, + std::static_pointer_cast(it.value())->item(), name, + seenBindings); + } else if (it.value()->type() == Value::JSSourceValueType) { + const auto insertResult = seenBindings->insert(name); + if (!insertResult.second) + continue; + JSSourceValuePtr sourceValue = std::static_pointer_cast(it.value()); + RuleArtifact::Binding rab; + rab.name = name; + rab.code = sourceCodeForEvaluation(sourceValue); + rab.location = sourceValue->location(); + ruleArtifact->bindings.push_back(rab); + } else { + QBS_ASSERT(!"unexpected value type", continue); + } + } +} + +void ProjectResolver::resolveFileTagger(Item *item, ProjectContext *projectContext) +{ + checkCancelation(); + if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) + return; + std::vector &fileTaggers = m_productContext + ? m_productContext->product->fileTaggers + : projectContext->fileTaggers; + const QStringList patterns = m_evaluator->stringListValue(item, + StringConstants::patternsProperty()); + if (patterns.empty()) + throw ErrorInfo(Tr::tr("FileTagger.patterns must be a non-empty list."), item->location()); + + const FileTags fileTags = m_evaluator->fileTagsValue(item, StringConstants::fileTagsProperty()); + if (fileTags.empty()) + throw ErrorInfo(Tr::tr("FileTagger.fileTags must not be empty."), item->location()); + + for (const QString &pattern : patterns) { + if (pattern.isEmpty()) + throw ErrorInfo(Tr::tr("A FileTagger pattern must not be empty."), item->location()); + } + + const int priority = m_evaluator->intValue(item, StringConstants::priorityProperty()); + fileTaggers.push_back(FileTagger::create(patterns, fileTags, priority)); +} + +void ProjectResolver::resolveJobLimit(Item *item, ProjectResolver::ProjectContext *projectContext) +{ + if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) + return; + const QString jobPool = m_evaluator->stringValue(item, StringConstants::jobPoolProperty()); + if (jobPool.isEmpty()) + throw ErrorInfo(Tr::tr("A JobLimit item needs to have a non-empty '%1' property.") + .arg(StringConstants::jobPoolProperty()), item->location()); + bool jobCountWasSet; + const int jobCount = m_evaluator->intValue(item, StringConstants::jobCountProperty(), -1, + &jobCountWasSet); + if (!jobCountWasSet) { + throw ErrorInfo(Tr::tr("A JobLimit item needs to have a '%1' property.") + .arg(StringConstants::jobCountProperty()), item->location()); + } + if (jobCount < 0) { + throw ErrorInfo(Tr::tr("A JobLimit item must have a non-negative '%1' property.") + .arg(StringConstants::jobCountProperty()), item->location()); + } + JobLimits &jobLimits = m_moduleContext + ? m_moduleContext->jobLimits + : m_productContext ? m_productContext->product->jobLimits + : projectContext->jobLimits; + JobLimit jobLimit(jobPool, jobCount); + const int oldLimit = jobLimits.getLimit(jobPool); + if (oldLimit == -1 || oldLimit > jobCount) + jobLimits.setJobLimit(jobLimit); +} + +void ProjectResolver::resolveScanner(Item *item, ProjectResolver::ProjectContext *projectContext) +{ + checkCancelation(); + if (!m_evaluator->boolValue(item, StringConstants::conditionProperty())) { + qCDebug(lcProjectResolver) << "scanner condition is false"; + return; + } + + ResolvedScannerPtr scanner = ResolvedScanner::create(); + scanner->module = m_moduleContext ? m_moduleContext->module : projectContext->dummyModule; + scanner->inputs = m_evaluator->fileTagsValue(item, StringConstants::inputsProperty()); + scanner->recursive = m_evaluator->boolValue(item, StringConstants::recursiveProperty()); + scanner->searchPathsScript.initialize(scriptFunctionValue( + item, StringConstants::searchPathsProperty())); + scanner->scanScript.initialize(scriptFunctionValue(item, StringConstants::scanProperty())); + m_productContext->product->scanners.push_back(scanner); +} + +ProjectResolver::ProductDependencyInfos ProjectResolver::getProductDependencies( + const ResolvedProductConstPtr &product, const ModuleLoaderResult::ProductInfo &productInfo) +{ + ProductDependencyInfos result; + result.dependencies.reserve(productInfo.usedProducts.size()); + for (const auto &dependency : productInfo.usedProducts) { + QBS_CHECK(!dependency.name.isEmpty()); + if (dependency.profile == StringConstants::star()) { + for (const ResolvedProductPtr &p : qAsConst(m_productsByName)) { + if (p->name != dependency.name || p == product || !p->enabled + || (dependency.limitToSubProject && !product->isInParentProject(p))) { + continue; + } + result.dependencies.emplace_back(p, dependency.parameters); + } + } 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) { + 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) + continue; + ErrorInfo e; + e.append(Tr::tr("Product '%1' depends on '%2',") + .arg(product->name, usedProduct->name), product->location); + e.append(Tr::tr("but product '%1' is disabled.").arg(usedProduct->name), + usedProduct->location); + if (m_setupParams.productErrorMode() == ErrorHandlingMode::Strict) + throw e; + result.hasDisabledDependency = true; + } + result.dependencies.emplace_back(usedProduct, dependency.parameters); + } + } + return result; +} + +void ProjectResolver::matchArtifactProperties(const ResolvedProductPtr &product, + const std::vector &artifacts) +{ + for (const SourceArtifactPtr &artifact : artifacts) { + for (const auto &artifactProperties : product->artifactProperties) { + if (!artifact->isTargetOfModule() + && artifact->fileTags.intersects(artifactProperties->fileTagsFilter())) { + artifact->properties = artifactProperties->propertyMap(); + } + } + } +} + +void ProjectResolver::printProfilingInfo() +{ + if (!m_setupParams.logElapsedTime()) + return; + m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("All property evaluation took %1.") + .arg(elapsedTimeString(m_elapsedTimeAllPropEval)); + m_logger.qbsLog(LoggerInfo, true) << "\t" << Tr::tr("Module property evaluation took %1.") + .arg(elapsedTimeString(m_elapsedTimeModPropEval)); + m_logger.qbsLog(LoggerInfo, true) << "\t" + << Tr::tr("Resolving groups (without module property " + "evaluation) took %1.") + .arg(elapsedTimeString(m_elapsedTimeGroups)); +} + +class TempScopeSetter +{ +public: + TempScopeSetter(Item * item, Item *newScope) : m_item(item), m_oldScope(item->scope()) + { + item->setScope(newScope); + } + ~TempScopeSetter() { m_item->setScope(m_oldScope); } +private: + Item * const m_item; + Item * const m_oldScope; +}; + +void ProjectResolver::collectPropertiesForExportItem(Item *productModuleInstance) +{ + if (!productModuleInstance->isPresentModule()) + return; + Item * const exportItem = productModuleInstance->prototype(); + QBS_CHECK(exportItem && exportItem->type() == ItemType::Export); + TempScopeSetter tempScopeSetter(exportItem, productModuleInstance->scope()); + const ItemDeclaration::Properties exportDecls = BuiltinDeclarations::instance() + .declarationsForType(ItemType::Export).properties(); + ExportedModule &exportedModule = m_productContext->product->exportedModule; + const auto &props = exportItem->properties(); + for (auto it = props.begin(); it != props.end(); ++it) { + const auto match + = [it](const PropertyDeclaration &decl) { return decl.name() == it.key(); }; + if (it.key() != StringConstants::prefixMappingProperty() && + std::find_if(exportDecls.begin(), exportDecls.end(), match) != exportDecls.end()) { + continue; + } + if (it.value()->type() == Value::ItemValueType) { + collectPropertiesForExportItem(it.key(), it.value(), productModuleInstance, + exportedModule.modulePropertyValues); + } else { + evaluateProperty(exportItem, it.key(), it.value(), exportedModule.propertyValues, + false); + } + } +} + +// Collects module properties assigned to in other (higher-level) modules. +void ProjectResolver::collectPropertiesForModuleInExportItem(const Item::Module &module) +{ + if (!module.item->isPresentModule()) + return; + ExportedModule &exportedModule = m_productContext->product->exportedModule; + if (module.isProduct || module.name.first() == StringConstants::qbsModule()) + return; + const auto checkName = [module](const ExportedModuleDependency &d) { + return module.name.toString() == d.name; + }; + if (any_of(exportedModule.moduleDependencies, checkName)) + return; + + Item *modulePrototype = module.item->prototype(); + while (modulePrototype && modulePrototype->type() != ItemType::Module) + modulePrototype = modulePrototype->prototype(); + if (!modulePrototype) // Can happen for broken products in relaxed mode. + return; + TempScopeSetter tempScopeSetter(modulePrototype, module.item->scope()); + const Item::PropertyMap &props = modulePrototype->properties(); + ExportedModuleDependency dep; + dep.name = module.name.toString(); + for (auto it = props.begin(); it != props.end(); ++it) { + if (it.value()->type() == Value::ItemValueType) + collectPropertiesForExportItem(it.key(), it.value(), module.item, dep.moduleProperties); + } + exportedModule.moduleDependencies.push_back(dep); + + for (const auto &dep : module.item->modules()) + collectPropertiesForModuleInExportItem(dep); +} + +static bool hasDependencyCycle(Set *checked, + Set *branch, + const ResolvedProductPtr &product, + ErrorInfo *error) +{ + if (branch->contains(product.get())) + return true; + if (checked->contains(product.get())) + return false; + checked->insert(product.get()); + branch->insert(product.get()); + for (const ResolvedProductPtr &dep : qAsConst(product->dependencies)) { + if (hasDependencyCycle(checked, branch, dep, error)) { + error->prepend(dep->name, dep->location); + return true; + } + } + branch->remove(product.get()); + return false; +} + +using DependencyMap = QHash>; +void gatherDependencies(ResolvedProduct *product, DependencyMap &dependencies) +{ + if (dependencies.contains(product)) + return; + Set &productDeps = dependencies[product]; + for (const ResolvedProductPtr &dep : qAsConst(product->dependencies)) { + productDeps << dep.get(); + gatherDependencies(dep.get(), dependencies); + productDeps += dependencies.value(dep.get()); + } +} + + + +static DependencyMap allDependencies(const std::vector &products) +{ + DependencyMap dependencies; + for (const ResolvedProductPtr &product : products) + gatherDependencies(product.get(), dependencies); + return dependencies; +} + +void ProjectResolver::resolveProductDependencies(const ProjectContext &projectContext) +{ + // Resolve all inter-product dependencies. + const std::vector allProducts = projectContext.project->allProducts(); + bool disabledDependency = false; + for (const ResolvedProductPtr &rproduct : allProducts) { + if (!rproduct->enabled) + continue; + Item *productItem = m_productItemMap.value(rproduct); + const ModuleLoaderResult::ProductInfo &productInfo + = m_loadResult.productInfos.value(productItem); + const ProductDependencyInfos &depInfos = getProductDependencies(rproduct, productInfo); + if (depInfos.hasDisabledDependency) + disabledDependency = true; + for (const auto &dep : depInfos.dependencies) { + if (!contains(rproduct->dependencies, dep.product)) + rproduct->dependencies.push_back(dep.product); + if (!dep.parameters.empty()) + rproduct->dependencyParameters.insert(dep.product, dep.parameters); + } + } + + // Check for cyclic dependencies. + Set checked; + for (const ResolvedProductPtr &rproduct : allProducts) { + Set branch; + ErrorInfo error; + if (hasDependencyCycle(&checked, &branch, rproduct, &error)) { + error.prepend(rproduct->name, rproduct->location); + error.prepend(Tr::tr("Cyclic dependencies detected.")); + throw error; + } + } + + // Mark all products as disabled that have a disabled dependency. + if (disabledDependency && m_setupParams.productErrorMode() == ErrorHandlingMode::Relaxed) { + const DependencyMap allDeps = allDependencies(allProducts); + DependencyMap allDepsReversed; + for (auto it = allDeps.constBegin(); it != allDeps.constEnd(); ++it) { + for (ResolvedProduct *dep : qAsConst(it.value())) + allDepsReversed[dep] << it.key(); + } + for (auto it = allDepsReversed.constBegin(); it != allDepsReversed.constEnd(); ++it) { + if (it.key()->enabled) + continue; + for (ResolvedProduct * const dependingProduct : qAsConst(it.value())) { + if (dependingProduct->enabled) { + m_logger.qbsWarning() << Tr::tr("Disabling product '%1', because it depends on " + "disabled product '%2'.") + .arg(dependingProduct->name, it.key()->name); + dependingProduct->enabled = false; + } + } + } + } +} + +void ProjectResolver::postProcess(const ResolvedProductPtr &product, + ProjectContext *projectContext) const +{ + product->fileTaggers << projectContext->fileTaggers; + 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 : projectContext->rules) { + RulePtr clonedRule = rule->clone(); + clonedRule->product = product.get(); + product->rules.push_back(clonedRule); + } +} + +void ProjectResolver::applyFileTaggers(const ResolvedProductPtr &product) const +{ + for (const SourceArtifactPtr &artifact : product->allEnabledFiles()) + applyFileTaggers(artifact, product); +} + +void ProjectResolver::applyFileTaggers(const SourceArtifactPtr &artifact, + const ResolvedProductConstPtr &product) +{ + if (!artifact->overrideFileTags || artifact->fileTags.empty()) { + const QString fileName = FileInfo::fileName(artifact->absoluteFilePath); + const FileTags fileTags = product->fileTagsForFileName(fileName); + artifact->fileTags.unite(fileTags); + if (artifact->fileTags.empty()) + artifact->fileTags.insert(unknownFileTag()); + qCDebug(lcProjectResolver) << "adding file tags" << artifact->fileTags + << "to" << fileName; + } +} + +QVariantMap ProjectResolver::evaluateModuleValues(Item *item, bool lookupPrototype) +{ + AccumulatingTimer modPropEvalTimer(m_setupParams.logElapsedTime() + ? &m_elapsedTimeModPropEval : nullptr); + QVariantMap moduleValues; + for (const Item::Module &module : item->modules()) { + if (!module.item->isPresentModule()) + continue; + const QString fullName = module.name.toString(); + moduleValues[fullName] = evaluateProperties(module.item, lookupPrototype, true); + } + + return moduleValues; +} + +QVariantMap ProjectResolver::evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors) +{ + const QVariantMap tmplt; + return evaluateProperties(item, item, tmplt, lookupPrototype, checkErrors); +} + +QVariantMap ProjectResolver::evaluateProperties(const Item *item, const Item *propertiesContainer, + const QVariantMap &tmplt, bool lookupPrototype, bool checkErrors) +{ + AccumulatingTimer propEvalTimer(m_setupParams.logElapsedTime() + ? &m_elapsedTimeAllPropEval : nullptr); + QVariantMap result = tmplt; + for (QMap::const_iterator it = propertiesContainer->properties().begin(); + it != propertiesContainer->properties().end(); ++it) { + checkCancelation(); + evaluateProperty(item, it.key(), it.value(), result, checkErrors); + } + return lookupPrototype && propertiesContainer->prototype() + ? evaluateProperties(item, propertiesContainer->prototype(), result, true, checkErrors) + : result; +} + +void ProjectResolver::evaluateProperty(const Item *item, const QString &propName, + const ValuePtr &propValue, QVariantMap &result, bool checkErrors) +{ + switch (propValue->type()) { + case Value::ItemValueType: + { + // Ignore items. Those point to module instances + // and are handled in evaluateModuleValues(). + break; + } + case Value::JSSourceValueType: + { + if (result.contains(propName)) + break; + const PropertyDeclaration pd = item->propertyDeclaration(propName); + if (pd.flags().testFlag(PropertyDeclaration::PropertyNotAvailableInConfig)) { + break; + } + const QScriptValue scriptValue = m_evaluator->property(item, propName); + if (checkErrors && Q_UNLIKELY(m_evaluator->engine()->hasErrorOrException(scriptValue))) { + throw ErrorInfo(m_evaluator->engine()->lastError(scriptValue, + propValue->location())); + } + + // NOTE: Loses type information if scriptValue.isUndefined == true, + // as such QScriptValues become invalid QVariants. + QVariant v; + if (scriptValue.isFunction()) { + v = scriptValue.toString(); + } else { + v = scriptValue.toVariant(); + QVariantMap m = v.toMap(); + if (m.contains(StringConstants::importScopeNamePropertyInternal())) { + QVariantMap tmp = m; + m = scriptValue.prototype().toVariant().toMap(); + for (auto it = tmp.begin(); it != tmp.end(); ++it) + m.insert(it.key(), it.value()); + v = m; + } + } + + if (pd.type() == PropertyDeclaration::Path && v.isValid()) { + v = v.toString(); + } else if (pd.type() == PropertyDeclaration::PathList + || pd.type() == PropertyDeclaration::StringList) { + v = v.toStringList(); + } else if (pd.type() == PropertyDeclaration::VariantList) { + v = v.toList(); + } + result[propName] = v; + break; + } + case Value::VariantValueType: + { + if (result.contains(propName)) + break; + VariantValuePtr vvp = std::static_pointer_cast(propValue); + QVariant v = vvp->value(); + + if (v.isNull() && !item->propertyDeclaration(propName).isScalar()) // QTBUG-51237 + v = QStringList(); + + result[propName] = v; + break; + } + } +} + +void ProjectResolver::collectPropertiesForExportItem(const QualifiedId &moduleName, + const ValuePtr &value, Item *moduleInstance, QVariantMap &moduleProps) +{ + QBS_CHECK(value->type() == Value::ItemValueType); + Item * const itemValueItem = std::static_pointer_cast(value)->item(); + if (itemValueItem->type() == ItemType::ModuleInstance) { + struct EvalPreparer { + EvalPreparer(Item *valueItem, Item *moduleInstance, const QualifiedId &moduleName) + : valueItem(valueItem), oldScope(valueItem->scope()), + hadName(!!valueItem->variantProperty(StringConstants::nameProperty())) + { + valueItem->setScope(moduleInstance); + if (!hadName) { + // EvaluatorScriptClass expects a name here. + valueItem->setProperty(StringConstants::nameProperty(), + VariantValue::create(moduleName.toString())); + } + } + ~EvalPreparer() + { + valueItem->setScope(oldScope); + if (!hadName) + valueItem->setProperty(StringConstants::nameProperty(), VariantValuePtr()); + } + Item * const valueItem; + Item * const oldScope; + const bool hadName; + }; + EvalPreparer ep(itemValueItem, moduleInstance, moduleName); + moduleProps.insert(moduleName.toString(), evaluateProperties(itemValueItem, false, false)); + return; + } + QBS_CHECK(itemValueItem->type() == ItemType::ModulePrefix); + const Item::PropertyMap &props = itemValueItem->properties(); + for (auto it = props.begin(); it != props.end(); ++it) { + QualifiedId fullModuleName = moduleName; + fullModuleName << it.key(); + collectPropertiesForExportItem(fullModuleName, it.value(), moduleInstance, moduleProps); + } +} + +void ProjectResolver::createProductConfig(ResolvedProduct *product) +{ + EvalCacheEnabler cachingEnabler(m_evaluator); + m_evaluator->setPathPropertiesBaseDir(m_productContext->product->sourceDirectory); + product->moduleProperties->setValue(evaluateModuleValues(m_productContext->item)); + product->productProperties = evaluateProperties(m_productContext->item, m_productContext->item, + QVariantMap(), true, true); + m_evaluator->clearPathPropertiesBaseDir(); +} + +void ProjectResolver::callItemFunction(const ItemFuncMap &mappings, Item *item, + ProjectContext *projectContext) +{ + const ItemFuncPtr f = mappings.value(item->type()); + QBS_CHECK(f); + if (item->type() == ItemType::Project) { + ProjectContext subProjectContext = createProjectContext(projectContext); + (this->*f)(item, &subProjectContext); + } else { + (this->*f)(item, projectContext); + } +} + +ProjectResolver::ProjectContext ProjectResolver::createProjectContext(ProjectContext *parentProjectContext) const +{ + ProjectContext subProjectContext; + subProjectContext.parentContext = parentProjectContext; + subProjectContext.project = ResolvedProject::create(); + parentProjectContext->project->subProjects.push_back(subProjectContext.project); + subProjectContext.project->parentProject = parentProjectContext->project; + return subProjectContext; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/projectresolver.h b/src/lib/corelib/language/projectresolver.h new file mode 100644 index 00000000..a1e24a55 --- /dev/null +++ b/src/lib/corelib/language/projectresolver.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** 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 PROJECTRESOLVER_H +#define PROJECTRESOLVER_H + +#include "filetags.h" +#include "itemtype.h" +#include "moduleloader.h" +#include "qualifiedid.h" + +#include +#include + +#include +#include +#include + +#include +#include + +namespace qbs { +class JobLimits; +namespace Internal { + +class Evaluator; +class Item; +class ProgressObserver; +class ScriptEngine; + +class ProjectResolver +{ +public: + ProjectResolver(Evaluator *evaluator, ModuleLoaderResult loadResult, + SetupProjectParameters setupParameters, Logger &logger); + ~ProjectResolver(); + + void setProgressObserver(ProgressObserver *observer); + TopLevelProjectPtr resolve(); + + static void applyFileTaggers(const SourceArtifactPtr &artifact, + const ResolvedProductConstPtr &product); + + using FileLocations = QHash, CodeLocation>; + static SourceArtifactPtr createSourceArtifact(const ResolvedProductPtr &rproduct, + const QString &fileName, const GroupPtr &group, bool wildcard, + const CodeLocation &filesLocation = CodeLocation(), + FileLocations *fileLocations = nullptr, ErrorInfo *errorInfo = nullptr); + +private: + struct ProjectContext; + struct ProductContext; + struct ModuleContext; + class ProductContextSwitcher; + + void checkCancelation() const; + QString verbatimValue(const ValueConstPtr &value, bool *propertyWasSet = nullptr) const; + QString verbatimValue(Item *item, const QString &name, bool *propertyWasSet = nullptr) const; + ScriptFunctionPtr scriptFunctionValue(Item *item, const QString &name) const; + ResolvedFileContextPtr resolvedFileContext(const FileContextConstPtr &ctx) const; + void ignoreItem(Item *item, ProjectContext *projectContext); + TopLevelProjectPtr resolveTopLevelProject(); + void resolveProject(Item *item, ProjectContext *projectContext); + void resolveProjectFully(Item *item, ProjectContext *projectContext); + void resolveSubProject(Item *item, ProjectContext *projectContext); + void resolveProduct(Item *item, ProjectContext *projectContext); + void resolveProductFully(Item *item, ProjectContext *projectContext); + void resolveModules(const Item *item, ProjectContext *projectContext); + void resolveModule(const QualifiedId &moduleName, Item *item, bool isProduct, + const QVariantMap ¶meters, JobLimits &jobLimits, + ProjectContext *projectContext); + void gatherProductTypes(ResolvedProduct *product, Item *item); + QVariantMap resolveAdditionalModuleProperties(const Item *group, + const QVariantMap ¤tValues); + void resolveGroup(Item *item, ProjectContext *projectContext); + void resolveGroupFully(Item *item, ProjectContext *projectContext, bool isEnabled); + void resolveShadowProduct(Item *item, ProjectContext *); + void resolveExport(Item *exportItem, ProjectContext *); + std::unique_ptr resolveExportChild(const Item *item, + const ExportedModule &module); + void resolveRule(Item *item, ProjectContext *projectContext); + void resolveRuleArtifact(const RulePtr &rule, Item *item); + void resolveRuleArtifactBinding(const RuleArtifactPtr &ruleArtifact, Item *item, + const QStringList &namePrefix, + QualifiedIdSet *seenBindings); + void resolveFileTagger(Item *item, ProjectContext *projectContext); + void resolveJobLimit(Item *item, ProjectContext *projectContext); + void resolveScanner(Item *item, ProjectContext *projectContext); + void resolveProductDependencies(const ProjectContext &projectContext); + void postProcess(const ResolvedProductPtr &product, ProjectContext *projectContext) const; + void applyFileTaggers(const ResolvedProductPtr &product) const; + QVariantMap evaluateModuleValues(Item *item, bool lookupPrototype = true); + QVariantMap evaluateProperties(Item *item, bool lookupPrototype, bool checkErrors); + QVariantMap evaluateProperties(const Item *item, const Item *propertiesContainer, + const QVariantMap &tmplt, bool lookupPrototype, + bool checkErrors); + void evaluateProperty(const Item *item, const QString &propName, const ValuePtr &propValue, + QVariantMap &result, bool checkErrors); + void createProductConfig(ResolvedProduct *product); + ProjectContext createProjectContext(ProjectContext *parentProjectContext) const; + void adaptExportedPropertyValues(const Item *shadowProductItem); + void collectExportedProductDependencies(); + + struct ProductDependencyInfo + { + ProductDependencyInfo(ResolvedProductPtr product, + QVariantMap parameters = QVariantMap()) + : product(std::move(product)), parameters(std::move(parameters)) + { + } + + ResolvedProductPtr product; + QVariantMap parameters; + }; + + struct ProductDependencyInfos + { + std::vector dependencies; + bool hasDisabledDependency = false; + }; + + ProductDependencyInfos getProductDependencies(const ResolvedProductConstPtr &product, + const ModuleLoaderResult::ProductInfo &productInfo); + QString sourceCodeAsFunction(const JSSourceValueConstPtr &value, + const PropertyDeclaration &decl) const; + QString sourceCodeForEvaluation(const JSSourceValueConstPtr &value) const; + static void matchArtifactProperties(const ResolvedProductPtr &product, + const std::vector &artifacts); + void printProfilingInfo(); + + void collectPropertiesForExportItem(Item *productModuleInstance); + void collectPropertiesForModuleInExportItem(const Item::Module &module); + + void collectPropertiesForExportItem(const QualifiedId &moduleName, + const ValuePtr &value, Item *moduleInstance, QVariantMap &moduleProps); + void setupExportedProperties(const Item *item, const QString &namePrefix, + std::vector &properties); + + Evaluator *m_evaluator = nullptr; + Logger &m_logger; + ScriptEngine *m_engine = nullptr; + ProgressObserver *m_progressObserver = nullptr; + ProductContext *m_productContext = nullptr; + ModuleContext *m_moduleContext = nullptr; + QMap m_productsByName; + QHash > m_productsByType; + QHash m_productItemMap; + mutable QHash m_fileContextMap; + mutable QHash m_scriptFunctionMap; + mutable QHash, QString> m_scriptFunctions; + mutable QHash m_sourceCode; + const SetupProjectParameters m_setupParams; + ModuleLoaderResult m_loadResult; + Set m_groupLocationWarnings; + std::vector> m_productExportInfo; + std::vector m_queuedErrors; + qint64 m_elapsedTimeModPropEval = 0; + qint64 m_elapsedTimeAllPropEval = 0; + qint64 m_elapsedTimeGroups = 0; + + typedef void (ProjectResolver::*ItemFuncPtr)(Item *item, ProjectContext *projectContext); + using ItemFuncMap = QMap; + void callItemFunction(const ItemFuncMap &mappings, Item *item, ProjectContext *projectContext); +}; + +} // namespace Internal +} // namespace qbs + +#endif // PROJECTRESOLVER_H diff --git a/src/lib/corelib/language/property.cpp b/src/lib/corelib/language/property.cpp new file mode 100644 index 00000000..8349e201 --- /dev/null +++ b/src/lib/corelib/language/property.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** 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 "property.h" + +namespace qbs { +namespace Internal { + +bool operator<(const Property &p1, const Property &p2) +{ + int cmpResult = QString::compare(p1.productName, p2.productName); + if (cmpResult < 0) + return true; + if (cmpResult > 0) + return false; + cmpResult = QString::compare(p1.moduleName, p2.moduleName); + if (cmpResult < 0) + return true; + if (cmpResult > 0) + return false; + return p1.propertyName < p2.propertyName; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/property.h b/src/lib/corelib/language/property.h new file mode 100644 index 00000000..78061bf6 --- /dev/null +++ b/src/lib/corelib/language/property.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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 QBS_PROPERTY_H +#define QBS_PROPERTY_H + +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { + +class Property +{ +public: + enum Kind + { + PropertyInModule, + PropertyInProduct, + PropertyInProject, + PropertyInParameters, + PropertyInArtifact, + }; + + Property() + : kind(PropertyInModule) + { + } + + Property(QString product, QString module, QString property, + QVariant v, Kind k) + : productName(std::move(product)) + , moduleName(std::move(module)) + , propertyName(std::move(property)) + , value(std::move(v)) + , kind(k) + { + } + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(productName, moduleName, propertyName, value, kind); + } + + QString productName; // In case of kind == PropertyInProject, this is the project name. + QString moduleName; + QString propertyName; + QVariant value; + Kind kind; +}; + +inline bool operator==(const Property &p1, const Property &p2) +{ + return p1.productName == p2.productName && p1.moduleName == p2.moduleName + && p1.propertyName == p2.propertyName; +} +bool operator<(const Property &p1, const Property &p2); + +inline uint qHash(const Property &p) +{ + return QT_PREPEND_NAMESPACE(qHash)(p.productName + p.moduleName + p.propertyName); +} + +using PropertySet = Set; +using PropertyHash = QHash; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/language/propertydeclaration.cpp b/src/lib/corelib/language/propertydeclaration.cpp new file mode 100644 index 00000000..abe6a162 --- /dev/null +++ b/src/lib/corelib/language/propertydeclaration.cpp @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** 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 "propertydeclaration.h" + +#include "deprecationinfo.h" +#include + +#include + +#include +#include + +namespace qbs { +namespace Internal { + +class PropertyDeclarationData : public QSharedData +{ +public: + PropertyDeclarationData() + : type(PropertyDeclaration::UnknownType) + , flags(PropertyDeclaration::DefaultFlags) + { + } + + QString name; + PropertyDeclaration::Type type; + PropertyDeclaration::Flags flags; + QString description; + QString initialValueSource; + QStringList functionArgumentNames; + DeprecationInfo deprecationInfo; +}; + + +PropertyDeclaration::PropertyDeclaration() + : d(new PropertyDeclarationData) +{ +} + +PropertyDeclaration::PropertyDeclaration(const QString &name, Type type, + const QString &initialValue, Flags flags) + : d(new PropertyDeclarationData) +{ + d->name = name; + d->type = type; + d->initialValueSource = initialValue; + d->flags = flags; +} + +PropertyDeclaration::PropertyDeclaration(const PropertyDeclaration &other) = default; + +PropertyDeclaration::~PropertyDeclaration() = default; + +PropertyDeclaration &PropertyDeclaration::operator=(const PropertyDeclaration &other) = default; + +bool PropertyDeclaration::isValid() const +{ + return d && d->type != UnknownType; +} + +bool PropertyDeclaration::isScalar() const +{ + // ### Should be determined by a PropertyOption in the future. + return d->type != PathList && d->type != StringList && d->type != VariantList; +} + +static QString boolString() { return QStringLiteral("bool"); } +static QString intString() { return QStringLiteral("int"); } +static QString pathListString() { return QStringLiteral("pathList"); } +static QString stringString() { return QStringLiteral("string"); } +static QString stringListString() { return QStringLiteral("stringList"); } +static QString varString() { return QStringLiteral("var"); } +static QString variantString() { return QStringLiteral("variant"); } +static QString varListString() { return QStringLiteral("varList"); } + +PropertyDeclaration::Type PropertyDeclaration::propertyTypeFromString(const QString &typeName) +{ + if (typeName == boolString()) + return PropertyDeclaration::Boolean; + if (typeName == intString()) + return PropertyDeclaration::Integer; + if (typeName == StringConstants::pathType()) + return PropertyDeclaration::Path; + if (typeName == pathListString()) + return PropertyDeclaration::PathList; + if (typeName == stringString()) + return PropertyDeclaration::String; + if (typeName == stringListString()) + return PropertyDeclaration::StringList; + if (typeName == varString() || typeName == variantString()) + return PropertyDeclaration::Variant; + if (typeName == varListString()) + return PropertyDeclaration::VariantList; + return PropertyDeclaration::UnknownType; +} + +QString PropertyDeclaration::typeString() const +{ + return typeString(type()); +} + +QString PropertyDeclaration::typeString(PropertyDeclaration::Type t) +{ + switch (t) { + case Boolean: return boolString(); + case Integer: return intString(); + case Path: return StringConstants::pathType(); + case PathList: return pathListString(); + case String: return stringString(); + case StringList: return stringListString(); + case Variant: return variantString(); + case VariantList: return varListString(); + case UnknownType: return QStringLiteral("unknown"); + } + Q_UNREACHABLE(); // For stupid compilers. +} + +const QString &PropertyDeclaration::name() const +{ + return d->name; +} + +void PropertyDeclaration::setName(const QString &name) +{ + d->name = name; +} + +PropertyDeclaration::Type PropertyDeclaration::type() const +{ + return d->type; +} + +void PropertyDeclaration::setType(PropertyDeclaration::Type t) +{ + d->type = t; +} + +PropertyDeclaration::Flags PropertyDeclaration::flags() const +{ + return d->flags; +} + +void PropertyDeclaration::setFlags(Flags f) +{ + d->flags = f; +} + +const QString &PropertyDeclaration::description() const +{ + return d->description; +} + +void PropertyDeclaration::setDescription(const QString &str) +{ + d->description = str; +} + +const QString &PropertyDeclaration::initialValueSource() const +{ + return d->initialValueSource; +} + +void PropertyDeclaration::setInitialValueSource(const QString &str) +{ + d->initialValueSource = str; +} + +const QStringList &PropertyDeclaration::functionArgumentNames() const +{ + return d->functionArgumentNames; +} + +void PropertyDeclaration::setFunctionArgumentNames(const QStringList &lst) +{ + d->functionArgumentNames = lst; +} + +bool PropertyDeclaration::isDeprecated() const +{ + return d->deprecationInfo.isValid(); +} + +bool PropertyDeclaration::isExpired() const +{ + return isDeprecated() && deprecationInfo().removalVersion() <= LanguageInfo::qbsVersion(); +} + +const DeprecationInfo &PropertyDeclaration::deprecationInfo() const +{ + return d->deprecationInfo; +} + +void PropertyDeclaration::setDeprecationInfo(const DeprecationInfo &deprecationInfo) +{ + d->deprecationInfo = deprecationInfo; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/propertydeclaration.h b/src/lib/corelib/language/propertydeclaration.h new file mode 100644 index 00000000..874275bd --- /dev/null +++ b/src/lib/corelib/language/propertydeclaration.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** 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 QBS_PROPERTYDECLARATION_H +#define QBS_PROPERTYDECLARATION_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QStringList; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class DeprecationInfo; +class PropertyDeclarationData; + +class PropertyDeclaration +{ +public: + enum Type + { + UnknownType, + Boolean, + Integer, + Path, + PathList, + String, + StringList, + Variant, + VariantList, + }; + + enum Flag + { + DefaultFlags = 0, + ReadOnlyFlag = 0x1, + PropertyNotAvailableInConfig = 0x2 // Is this property part of a project, product or file configuration? + }; + Q_DECLARE_FLAGS(Flags, Flag) + + PropertyDeclaration(); + PropertyDeclaration(const QString &name, Type type, const QString &initialValue = QString(), + Flags flags = DefaultFlags); + PropertyDeclaration(const PropertyDeclaration &other); + ~PropertyDeclaration(); + + PropertyDeclaration &operator=(const PropertyDeclaration &other); + + bool isValid() const; + bool isScalar() const; + + static Type propertyTypeFromString(const QString &typeName); + QString typeString() const; + static QString typeString(Type t); + + const QString &name() const; + void setName(const QString &name); + + Type type() const; + void setType(Type t); + + Flags flags() const; + void setFlags(Flags f); + + const QString &description() const; + void setDescription(const QString &str); + + const QString &initialValueSource() const; + void setInitialValueSource(const QString &str); + + const QStringList &functionArgumentNames() const; + void setFunctionArgumentNames(const QStringList &lst); + + bool isDeprecated() const; + bool isExpired() const; + const DeprecationInfo &deprecationInfo() const; + void setDeprecationInfo(const DeprecationInfo &deprecationInfo); + +private: + QSharedDataPointer d; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROPERTYDECLARATION_H diff --git a/src/lib/corelib/language/propertymapinternal.cpp b/src/lib/corelib/language/propertymapinternal.cpp new file mode 100644 index 00000000..fe0f672c --- /dev/null +++ b/src/lib/corelib/language/propertymapinternal.cpp @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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 "propertymapinternal.h" + +#include +#include +#include + +namespace qbs { +namespace Internal { + +/*! + * \class PropertyMapInternal + * \brief The \c PropertyMapInternal class contains a set of properties and their values. + * An instance of this class is attached to every \c ResolvedProduct. + * \c ResolvedGroups inherit their properties from the respective \c ResolvedProduct, \c SourceArtifacts + * inherit theirs from the respective \c ResolvedGroup. \c ResolvedGroups can override the value of an + * inherited property, \c SourceArtifacts cannot. If a property value is overridden, a new + * \c PropertyMapInternal object is allocated, otherwise the pointer is shared. + * \sa ResolvedGroup + * \sa ResolvedProduct + * \sa SourceArtifact + */ +PropertyMapInternal::PropertyMapInternal() = default; + +PropertyMapInternal::PropertyMapInternal(const PropertyMapInternal &other) = default; + +QVariant PropertyMapInternal::moduleProperty(const QString &moduleName, const QString &key, + bool *isPresent) const +{ + return ::qbs::Internal::moduleProperty(m_value, moduleName, key, isPresent); +} + +QVariant PropertyMapInternal::qbsPropertyValue(const QString &key) const +{ + return moduleProperty(StringConstants::qbsModule(), key); +} + +QVariant PropertyMapInternal::property(const QStringList &name) const +{ + return getConfigProperty(m_value, name); +} + +void PropertyMapInternal::setValue(const QVariantMap &map) +{ + m_value = map; +} + +QVariant moduleProperty(const QVariantMap &properties, const QString &moduleName, + const QString &key, bool *isPresent) +{ + const auto moduleIt = properties.find(moduleName); + if (moduleIt == properties.end()) { + if (isPresent) + *isPresent = false; + return {}; + } + const QVariantMap &moduleMap = moduleIt.value().toMap(); + const auto propertyIt = moduleMap.find(key); + if (propertyIt == moduleMap.end()) { + if (isPresent) + *isPresent = false; + return {}; + } + if (isPresent) + *isPresent = true; + return propertyIt.value(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/propertymapinternal.h b/src/lib/corelib/language/propertymapinternal.h new file mode 100644 index 00000000..83e18ba4 --- /dev/null +++ b/src/lib/corelib/language/propertymapinternal.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** 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 QBS_PROPERTYMAPINTERNAL_H +#define QBS_PROPERTYMAPINTERNAL_H + +#include "forward_decls.h" +#include +#include +#include + +namespace qbs { +namespace Internal { + +class QBS_AUTOTEST_EXPORT PropertyMapInternal +{ +public: + static PropertyMapPtr create() { return PropertyMapPtr(new PropertyMapInternal); } + PropertyMapPtr clone() const { return PropertyMapPtr(new PropertyMapInternal(*this)); } + + const QVariantMap &value() const { return m_value; } + QVariant moduleProperty(const QString &moduleName, + const QString &key, bool *isPresent = nullptr) const; + QVariant qbsPropertyValue(const QString &key) const; // Convenience function. + QVariant property(const QStringList &name) const; + void setValue(const QVariantMap &value); + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(m_value); + } + +private: + friend bool operator==(const PropertyMapInternal &lhs, const PropertyMapInternal &rhs); + + PropertyMapInternal(); + PropertyMapInternal(const PropertyMapInternal &other); + + QVariantMap m_value; +}; + +inline bool operator==(const PropertyMapInternal &lhs, const PropertyMapInternal &rhs) +{ + return lhs.m_value == rhs.m_value; +} + +QVariant QBS_AUTOTEST_EXPORT moduleProperty(const QVariantMap &properties, + const QString &moduleName, + const QString &key, bool *isPresent = nullptr); + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROPERTYMAPINTERNAL_H diff --git a/src/lib/corelib/language/qualifiedid.cpp b/src/lib/corelib/language/qualifiedid.cpp new file mode 100644 index 00000000..9eb0e946 --- /dev/null +++ b/src/lib/corelib/language/qualifiedid.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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 "qualifiedid.h" + +#include + +namespace qbs { +namespace Internal { + +QualifiedId::QualifiedId() = default; + +QualifiedId::QualifiedId(const QString &singlePartName) + : QStringList(singlePartName) +{ +} + +QualifiedId::QualifiedId(const QStringList &nameParts) + : QStringList(nameParts) +{ +} + +QualifiedId QualifiedId::fromString(const QString &str) +{ + return QualifiedId(str.split(QLatin1Char('.'))); +} + +QString QualifiedId::toString() const +{ + return join(QLatin1Char('.')); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/qualifiedid.h b/src/lib/corelib/language/qualifiedid.h new file mode 100644 index 00000000..2b4c9d28 --- /dev/null +++ b/src/lib/corelib/language/qualifiedid.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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 QBS_QUALIFIEDID_H +#define QBS_QUALIFIEDID_H + +#include + +#include +#include + + +namespace qbs { +namespace Internal { + +class QBS_AUTOTEST_EXPORT QualifiedId : public QStringList +{ +public: + QualifiedId(); + QualifiedId(const QString &singlePartName); + QualifiedId(const QStringList &nameParts); + + static QualifiedId fromString(const QString &str); + QString toString() const; +}; + +inline uint qHash(const QualifiedId &qid) { return qHash(qid.toString()); } + +using QualifiedIdSet = Set; + +// Values are the properties with a dependency on the key property +using PropertyDependencies = QHash; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_QUALIFIEDID_H diff --git a/src/lib/corelib/language/resolvedfilecontext.cpp b/src/lib/corelib/language/resolvedfilecontext.cpp new file mode 100644 index 00000000..db63a8ef --- /dev/null +++ b/src/lib/corelib/language/resolvedfilecontext.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** 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 "resolvedfilecontext.h" + +#include "jsimports.h" +#include + +namespace qbs { +namespace Internal { + +ResolvedFileContext::ResolvedFileContext(const FileContextBase &ctx) + : FileContextBase(ctx) +{ +} + +bool operator==(const ResolvedFileContext &a, const ResolvedFileContext &b) +{ + return a.filePath() == b.filePath() + && toSet(a.jsExtensions()) == toSet(b.jsExtensions()) + && sorted(a.jsImports()) == sorted(b.jsImports()); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/resolvedfilecontext.h b/src/lib/corelib/language/resolvedfilecontext.h new file mode 100644 index 00000000..d783cf72 --- /dev/null +++ b/src/lib/corelib/language/resolvedfilecontext.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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 QBS_RESOLVEDFILECONTEXT_H +#define QBS_RESOLVEDFILECONTEXT_H + +#include "forward_decls.h" +#include "filecontextbase.h" +#include + +namespace qbs { +namespace Internal { + +class ResolvedFileContext : public FileContextBase +{ +public: + static ResolvedFileContextPtr create() + { + return ResolvedFileContextPtr(new ResolvedFileContext); + } + + static ResolvedFileContextPtr create(const FileContextBase &baseContext) + { + return ResolvedFileContextPtr(new ResolvedFileContext(baseContext)); + } + + template void completeSerializationOp(PersistentPool &pool) + { + pool.serializationOp(m_filePath, m_jsExtensions, m_searchPaths, m_jsImports); + } +private: + ResolvedFileContext() = default; + ResolvedFileContext(const FileContextBase &ctx); +}; + +bool operator==(const ResolvedFileContext &a, const ResolvedFileContext &b); +inline bool operator!=(const ResolvedFileContext &a, const ResolvedFileContext &b) +{ return !(a == b); } + +} // namespace Internal +} // namespace qbs + +#endif // QBS_RESOLVEDFILECONTEXT_H diff --git a/src/lib/corelib/language/scriptengine.cpp b/src/lib/corelib/language/scriptengine.cpp new file mode 100644 index 00000000..e79ec54d --- /dev/null +++ b/src/lib/corelib/language/scriptengine.cpp @@ -0,0 +1,828 @@ +/**************************************************************************** +** +** 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 "scriptengine.h" + +#include "filecontextbase.h" +#include "jsimports.h" +#include "propertymapinternal.h" +#include "scriptimporter.h" +#include "preparescriptobserver.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +static QString getterFuncHelperProperty() { return QStringLiteral("qbsdata"); } + +const bool debugJSImports = false; + +bool operator==(const ScriptEngine::PropertyCacheKey &lhs, + const ScriptEngine::PropertyCacheKey &rhs) +{ + return lhs.m_propertyMap == rhs.m_propertyMap + && lhs.m_moduleName == rhs.m_moduleName + && lhs.m_propertyName == rhs.m_propertyName; +} + +static inline uint combineHash(uint h1, uint h2, uint seed) +{ + // stolen from qHash(QPair) + return ((h1 << 16) | (h1 >> 16)) ^ h2 ^ seed; +} + +uint qHash(const ScriptEngine::PropertyCacheKey &k, uint seed = 0) +{ + return combineHash(qHash(k.m_moduleName), + combineHash(qHash(k.m_propertyName), qHash(k.m_propertyMap), seed), seed); +} + +std::mutex ScriptEngine::m_creationDestructionMutex; + +ScriptEngine::ScriptEngine(Logger &logger, EvalContext evalContext, QObject *parent) + : QScriptEngine(parent), m_scriptImporter(new ScriptImporter(this)), + m_modulePropertyScriptClass(nullptr), + m_propertyCacheEnabled(true), m_active(false), m_logger(logger), m_evalContext(evalContext), + m_observer(new PrepareScriptObserver(this, UnobserveMode::Disabled)) +{ + setProcessEventsInterval(1000); // For the cancelation mechanism to work. + m_cancelationError = currentContext()->throwValue(tr("Execution canceled")); + QScriptValue objectProto = globalObject().property(QStringLiteral("Object")); + m_definePropertyFunction = objectProto.property(QStringLiteral("defineProperty")); + QBS_ASSERT(m_definePropertyFunction.isFunction(), /* ignore */); + m_emptyFunction = evaluate(QStringLiteral("(function(){})")); + QBS_ASSERT(m_emptyFunction.isFunction(), /* ignore */); + // Initially push a new context to turn off scope chain insanity mode. + QScriptEngine::pushContext(); + installQbsBuiltins(); + extendJavaScriptBuiltins(); +} + +ScriptEngine *ScriptEngine::create(Logger &logger, EvalContext evalContext, QObject *parent) +{ + std::lock_guard lock(m_creationDestructionMutex); + return new ScriptEngine(logger, evalContext, parent); +} + +ScriptEngine::~ScriptEngine() +{ + m_creationDestructionMutex.lock(); + connect(this, &QObject::destroyed, std::bind(&std::mutex::unlock, &m_creationDestructionMutex)); + + releaseResourcesOfScriptObjects(); + delete (m_scriptImporter); + if (m_elapsedTimeImporting != -1) { + m_logger.qbsLog(LoggerInfo, true) << Tr::tr("Setting up imports took %1.") + .arg(elapsedTimeString(m_elapsedTimeImporting)); + } + delete m_modulePropertyScriptClass; + delete m_productPropertyScriptClass; +} + +void ScriptEngine::import(const FileContextBaseConstPtr &fileCtx, QScriptValue &targetObject, + ObserveMode observeMode) +{ + installImportFunctions(); + m_currentDirPathStack.push(FileInfo::path(fileCtx->filePath())); + m_extensionSearchPathsStack.push(fileCtx->searchPaths()); + m_observeMode = observeMode; + + for (const JsImport &jsImport : fileCtx->jsImports()) + import(jsImport, targetObject); + if (m_observeMode == ObserveMode::Enabled) { + for (QScriptValue &sv : m_requireResults) + observeImport(sv); + m_requireResults.clear(); + } + + m_currentDirPathStack.pop(); + m_extensionSearchPathsStack.pop(); + uninstallImportFunctions(); +} + +void ScriptEngine::import(const JsImport &jsImport, QScriptValue &targetObject) +{ + QBS_ASSERT(targetObject.isObject(), return); + QBS_ASSERT(targetObject.engine() == this, return); + + if (debugJSImports) + qDebug() << "[ENGINE] import into " << jsImport.scopeName; + + QScriptValue jsImportValue = m_jsImportCache.value(jsImport); + if (jsImportValue.isValid()) { + if (debugJSImports) + qDebug() << "[ENGINE] " << jsImport.filePaths << " (cache hit)"; + } else { + if (debugJSImports) + qDebug() << "[ENGINE] " << jsImport.filePaths << " (cache miss)"; + jsImportValue = newObject(); + for (const QString &filePath : jsImport.filePaths) + importFile(filePath, jsImportValue); + m_jsImportCache.insert(jsImport, jsImportValue); + std::vector &filePathsForScriptValue + = m_filePathsPerImport[jsImportValue.objectId()]; + for (const QString &fp : jsImport.filePaths) + filePathsForScriptValue.push_back(fp); + } + + QScriptValue sv = newObject(); + sv.setPrototype(jsImportValue); + sv.setProperty(StringConstants::importScopeNamePropertyInternal(), jsImport.scopeName); + targetObject.setProperty(jsImport.scopeName, sv); + if (m_observeMode == ObserveMode::Enabled) + observeImport(jsImportValue); +} + +void ScriptEngine::observeImport(QScriptValue &jsImport) +{ + if (!m_observer->addImportId(jsImport.objectId())) + return; + QScriptValueIterator it(jsImport); + while (it.hasNext()) { + it.next(); + if (it.flags() & QScriptValue::PropertyGetter) + continue; + QScriptValue property = it.value(); + if (!property.isFunction()) + continue; + setObservedProperty(jsImport, it.name(), property); + } +} + +void ScriptEngine::clearImportsCache() +{ + m_jsImportCache.clear(); +} + +void ScriptEngine::checkContext(const QString &operation, + const DubiousContextList &dubiousContexts) +{ + for (const DubiousContext &info : dubiousContexts) { + if (info.context != evalContext()) + continue; + QString warning; + switch (info.context) { + case EvalContext::PropertyEvaluation: + warning = Tr::tr("Suspicious use of %1 during property evaluation.").arg(operation); + if (info.suggestion == DubiousContext::SuggestMoving) + warning += QLatin1Char(' ') + Tr::tr("Should this call be in a Probe instead?"); + break; + case EvalContext::RuleExecution: + warning = Tr::tr("Suspicious use of %1 during rule execution.").arg(operation); + if (info.suggestion == DubiousContext::SuggestMoving) { + warning += QLatin1Char(' ') + + Tr::tr("Should this call be in a JavaScriptCommand instead?"); + } + break; + case EvalContext::ModuleProvider: + case EvalContext::ProbeExecution: + case EvalContext::JsCommand: + QBS_ASSERT(false, continue); + break; + } + m_logger.printWarning(ErrorInfo(warning, currentContext()->backtrace())); + return; + } +} + +void ScriptEngine::addPropertyRequestedFromArtifact(const Artifact *artifact, + const Property &property) +{ + m_propertiesRequestedFromArtifact[artifact->filePath()] << property; +} + +void ScriptEngine::addImportRequestedInScript(qint64 importValueId) +{ + // Import list is assumed to be small, so let's not use a set. + if (!contains(m_importsRequestedInScript, importValueId)) + m_importsRequestedInScript.push_back(importValueId); +} + +std::vector ScriptEngine::importedFilesUsedInScript() const +{ + std::vector files; + for (qint64 usedImport : m_importsRequestedInScript) { + const auto it = m_filePathsPerImport.find(usedImport); + QBS_CHECK(it != m_filePathsPerImport.cend()); + const std::vector &filePathsForImport = it->second; + for (const QString &fp : filePathsForImport) + if (!contains(files, fp)) + files.push_back(fp); + } + return files; +} + +void ScriptEngine::enableProfiling(bool enable) +{ + m_elapsedTimeImporting = enable ? 0 : -1; +} + +void ScriptEngine::addToPropertyCache(const QString &moduleName, const QString &propertyName, + const PropertyMapConstPtr &propertyMap, const QVariant &value) +{ + m_propertyCache.insert(PropertyCacheKey(moduleName, propertyName, propertyMap), value); +} + +QVariant ScriptEngine::retrieveFromPropertyCache(const QString &moduleName, + const QString &propertyName, const PropertyMapConstPtr &propertyMap) +{ + return m_propertyCache.value(PropertyCacheKey(moduleName, propertyName, propertyMap)); +} + +void ScriptEngine::defineProperty(QScriptValue &object, const QString &name, + const QScriptValue &descriptor) +{ + QScriptValue arguments = newArray(); + arguments.setProperty(0, object); + arguments.setProperty(1, name); + arguments.setProperty(2, descriptor); + QScriptValue result = m_definePropertyFunction.call(QScriptValue(), arguments); + QBS_ASSERT(!hasErrorOrException(result), qDebug() << name << result.toString()); +} + +static QScriptValue js_observedGet(QScriptContext *context, QScriptEngine *, + ScriptPropertyObserver * const observer) +{ + const QScriptValue data = context->callee().property(getterFuncHelperProperty()); + const QScriptValue value = data.property(2); + observer->onPropertyRead(data.property(0), data.property(1).toVariant().toString(), value); + return value; +} + +void ScriptEngine::setObservedProperty(QScriptValue &object, const QString &name, + const QScriptValue &value) +{ + QScriptValue data = newArray(); + data.setProperty(0, object); + data.setProperty(1, name); + data.setProperty(2, value); + QScriptValue getterFunc = newFunction(js_observedGet, + static_cast(m_observer.get())); + getterFunc.setProperty(getterFuncHelperProperty(), data); + object.setProperty(name, getterFunc, QScriptValue::PropertyGetter); + if (m_observer->unobserveMode() == UnobserveMode::Enabled) + m_observedProperties.emplace_back(object, name, value); +} + +void ScriptEngine::unobserveProperties() +{ + for (auto &elem : m_observedProperties) { + QScriptValue &object = std::get<0>(elem); + const QString &name = std::get<1>(elem); + const QScriptValue &value = std::get<2>(elem); + object.setProperty(name, QScriptValue(), QScriptValue::PropertyGetter); + object.setProperty(name, value, QScriptValue::PropertyFlags()); + } + m_observedProperties.clear(); +} + +static QScriptValue js_deprecatedGet(QScriptContext *context, QScriptEngine *qtengine) +{ + const auto engine = static_cast(qtengine); + const QScriptValue data = context->callee().property(getterFuncHelperProperty()); + engine->logger().qbsWarning() + << ScriptEngine::tr("Property %1 is deprecated. Please use %2 instead.").arg( + data.property(0).toString(), data.property(1).toString()); + return data.property(2); +} + +void ScriptEngine::setDeprecatedProperty(QScriptValue &object, const QString &oldName, + const QString &newName, const QScriptValue &value) +{ + QScriptValue data = newArray(); + data.setProperty(0, oldName); + data.setProperty(1, newName); + data.setProperty(2, value); + QScriptValue getterFunc = newFunction(js_deprecatedGet); + getterFunc.setProperty(getterFuncHelperProperty(), data); + object.setProperty(oldName, getterFunc, QScriptValue::PropertyGetter + | QScriptValue::SkipInEnumeration); +} + +QProcessEnvironment ScriptEngine::environment() const +{ + return m_environment; +} + +void ScriptEngine::setEnvironment(const QProcessEnvironment &env) +{ + m_environment = env; +} + +void ScriptEngine::importFile(const QString &filePath, QScriptValue &targetObject) +{ + AccumulatingTimer importTimer(m_elapsedTimeImporting != -1 ? &m_elapsedTimeImporting : nullptr); + QScriptValue &evaluationResult = m_jsFileCache[filePath]; + if (evaluationResult.isValid()) { + ScriptImporter::copyProperties(evaluationResult, targetObject); + return; + } + QFile file(filePath); + if (Q_UNLIKELY(!file.open(QFile::ReadOnly))) + throw ErrorInfo(tr("Cannot open '%1'.").arg(filePath)); + QTextStream stream(&file); + stream.setCodec("UTF-8"); + const QString sourceCode = stream.readAll(); + file.close(); + m_currentDirPathStack.push(FileInfo::path(filePath)); + evaluationResult = m_scriptImporter->importSourceCode(sourceCode, filePath, targetObject); + m_currentDirPathStack.pop(); +} + +static QString findExtensionDir(const QStringList &searchPaths, const QString &extensionPath) +{ + for (const QString &searchPath : searchPaths) { + const QString dirPath = searchPath + QStringLiteral("/imports/") + extensionPath; + QFileInfo fi(dirPath); + if (fi.exists() && fi.isDir()) + return dirPath; + } + return {}; +} + +static QScriptValue mergeExtensionObjects(const QScriptValueList &lst) +{ + QScriptValue result; + for (const QScriptValue &v : lst) { + if (!result.isValid()) { + result = v; + continue; + } + QScriptValueIterator svit(v); + while (svit.hasNext()) { + svit.next(); + result.setProperty(svit.name(), svit.value()); + } + } + return result; +} + +static QScriptValue loadInternalExtension(QScriptContext *context, ScriptEngine *engine, + const QString &uri) +{ + const QString name = uri.mid(4); // remove the "qbs." part + QScriptValue extensionObj = JsExtensions::loadExtension(engine, name); + if (!extensionObj.isValid()) { + return context->throwError(ScriptEngine::tr("loadExtension: " + "cannot load extension '%1'.").arg(uri)); + } + return extensionObj; +} + +QScriptValue ScriptEngine::js_loadExtension(QScriptContext *context, QScriptEngine *qtengine) +{ + if (context->argumentCount() < 1) { + return context->throwError( + ScriptEngine::tr("The loadExtension function requires " + "an extension name.")); + } + + const auto engine = static_cast(qtengine); + ErrorInfo deprWarning(Tr::tr("The loadExtension() function is deprecated and will be " + "removed in a future version of Qbs. Use require() " + "instead."), context->backtrace()); + engine->logger().printWarning(deprWarning); + + return js_require(context, qtengine); +} + +QScriptValue ScriptEngine::js_loadFile(QScriptContext *context, QScriptEngine *qtengine) +{ + if (context->argumentCount() < 1) { + return context->throwError( + ScriptEngine::tr("The loadFile function requires a file path.")); + } + + const auto engine = static_cast(qtengine); + ErrorInfo deprWarning(Tr::tr("The loadFile() function is deprecated and will be " + "removed in a future version of Qbs. Use require() " + "instead."), context->backtrace()); + engine->logger().printWarning(deprWarning); + + return js_require(context, qtengine); +} + +QScriptValue ScriptEngine::js_require(QScriptContext *context, QScriptEngine *qtengine) +{ + const auto engine = static_cast(qtengine); + if (context->argumentCount() < 1) { + return context->throwError( + ScriptEngine::tr("The require function requires a module name or path.")); + } + + const QString moduleName = context->argument(0).toString(); + + // First try to load a named module if the argument doesn't look like a file path + if (!moduleName.contains(QLatin1Char('/'))) { + if (engine->m_extensionSearchPathsStack.empty()) + return context->throwError( + ScriptEngine::tr("require: internal error. No search paths.")); + + if (engine->m_logger.debugEnabled()) { + engine->m_logger.qbsDebug() + << "[require] loading extension " << moduleName; + } + + QString moduleNameAsPath = moduleName; + moduleNameAsPath.replace(QLatin1Char('.'), QLatin1Char('/')); + const QStringList searchPaths = engine->m_extensionSearchPathsStack.top(); + const QString dirPath = findExtensionDir(searchPaths, moduleNameAsPath); + if (dirPath.isEmpty()) { + if (moduleName.startsWith(QStringLiteral("qbs."))) + return loadInternalExtension(context, engine, moduleName); + } else { + QDirIterator dit(dirPath, StringConstants::jsFileWildcards(), + QDir::Files | QDir::Readable); + QScriptValueList values; + std::vector filePaths; + try { + while (dit.hasNext()) { + const QString filePath = dit.next(); + if (engine->m_logger.debugEnabled()) { + engine->m_logger.qbsDebug() + << "[require] importing file " << filePath; + } + QScriptValue obj = engine->newObject(); + engine->importFile(filePath, obj); + values << obj; + filePaths.push_back(filePath); + } + } catch (const ErrorInfo &e) { + return context->throwError(e.toString()); + } + + if (!values.empty()) { + const QScriptValue mergedValue = mergeExtensionObjects(values); + engine->m_requireResults.push_back(mergedValue); + engine->m_filePathsPerImport[mergedValue.objectId()] = filePaths; + return mergedValue; + } + } + + // The module name might be a file name component, which is assumed to be to a JavaScript + // file located in the current directory search path; try that next + } + + if (engine->m_currentDirPathStack.empty()) { + return context->throwError( + ScriptEngine::tr("require: internal error. No current directory.")); + } + + QScriptValue result; + try { + const QString filePath = FileInfo::resolvePath(engine->m_currentDirPathStack.top(), + moduleName); + result = engine->newObject(); + engine->importFile(filePath, result); + static const QString scopeNamePrefix = QStringLiteral("_qbs_scope_"); + const QString scopeName = scopeNamePrefix + QString::number(qHash(filePath), 16); + result.setProperty(StringConstants::importScopeNamePropertyInternal(), scopeName); + context->thisObject().setProperty(scopeName, result); + engine->m_requireResults.push_back(result); + engine->m_filePathsPerImport[result.objectId()] = { filePath }; + } catch (const ErrorInfo &e) { + result = context->throwError(e.toString()); + } + + return result; +} + +QScriptClass *ScriptEngine::modulePropertyScriptClass() const +{ + return m_modulePropertyScriptClass; +} + +void ScriptEngine::setModulePropertyScriptClass(QScriptClass *modulePropertyScriptClass) +{ + m_modulePropertyScriptClass = modulePropertyScriptClass; +} + +void ScriptEngine::addResourceAcquiringScriptObject(ResourceAcquiringScriptObject *obj) +{ + m_resourceAcquiringScriptObjects.push_back(obj); +} + +void ScriptEngine::releaseResourcesOfScriptObjects() +{ + if (m_resourceAcquiringScriptObjects.empty()) + return; + std::for_each(m_resourceAcquiringScriptObjects.begin(), m_resourceAcquiringScriptObjects.end(), + std::mem_fn(&ResourceAcquiringScriptObject::releaseResources)); + m_resourceAcquiringScriptObjects.clear(); +} + +void ScriptEngine::addCanonicalFilePathResult(const QString &filePath, + const QString &resultFilePath) +{ + if (gatherFileResults()) + m_canonicalFilePathResult.insert(filePath, resultFilePath); +} + +void ScriptEngine::addFileExistsResult(const QString &filePath, bool exists) +{ + if (gatherFileResults()) + m_fileExistsResult.insert(filePath, exists); +} + +void ScriptEngine::addDirectoryEntriesResult(const QString &path, QDir::Filters filters, + const QStringList &entries) +{ + if (gatherFileResults()) { + m_directoryEntriesResult.insert( + std::pair(path, static_cast(filters)), + entries); + } +} + +void ScriptEngine::addFileLastModifiedResult(const QString &filePath, const FileTime &fileTime) +{ + if (gatherFileResults()) + m_fileLastModifiedResult.insert(filePath, fileTime); +} + +Set ScriptEngine::imports() const +{ + Set filePaths; + for (auto it = m_jsImportCache.cbegin(); it != m_jsImportCache.cend(); ++it) { + const JsImport &jsImport = it.key(); + for (const QString &filePath : jsImport.filePaths) + filePaths << filePath; + } + for (const auto &kv : m_filePathsPerImport) { + for (const QString &fp : kv.second) + filePaths << fp; + } + return filePaths; +} + +QScriptValueList ScriptEngine::argumentList(const QStringList &argumentNames, + const QScriptValue &context) +{ + QScriptValueList result; + for (const auto &name : argumentNames) + result += context.property(name); + return result; +} + +CodeLocation ScriptEngine::lastErrorLocation(const QScriptValue &v, + const CodeLocation &fallbackLocation) const +{ + const QScriptValue &errorVal = lastErrorValue(v); + const CodeLocation errorLoc(errorVal.property(StringConstants::fileNameProperty()).toString(), + errorVal.property(QStringLiteral("lineNumber")).toInt32(), + errorVal.property(QStringLiteral("expressionCaretOffset")).toInt32(), + false); + return errorLoc.isValid() ? errorLoc : fallbackLocation; +} + +ErrorInfo ScriptEngine::lastError(const QScriptValue &v, const CodeLocation &fallbackLocation) const +{ + const QString msg = lastErrorString(v); + CodeLocation errorLocation = lastErrorLocation(v); + if (errorLocation.isValid()) + return ErrorInfo(msg, errorLocation); + const QStringList backtrace = uncaughtExceptionBacktraceOrEmpty(); + if (!backtrace.empty()) { + ErrorInfo e(msg, backtrace); + if (e.hasLocation()) + return e; + } + return ErrorInfo(msg, fallbackLocation); +} + +void ScriptEngine::cancel() +{ + QTimer::singleShot(0, this, [this] { abort(); }); +} + +void ScriptEngine::abort() +{ + abortEvaluation(m_cancelationError); +} + +bool ScriptEngine::gatherFileResults() const +{ + return evalContext() == EvalContext::PropertyEvaluation + || evalContext() == EvalContext::ProbeExecution; +} + +class JSTypeExtender +{ +public: + JSTypeExtender(ScriptEngine *engine, const QString &typeName) + : m_engine(engine) + { + m_proto = engine->globalObject().property(typeName) + .property(QStringLiteral("prototype")); + QBS_ASSERT(m_proto.isObject(), return); + m_descriptor = engine->newObject(); + } + + void addFunction(const QString &name, const QString &code) + { + QScriptValue f = m_engine->evaluate(code); + QBS_ASSERT(f.isFunction(), return); + m_descriptor.setProperty(QStringLiteral("value"), f); + m_engine->defineProperty(m_proto, name, m_descriptor); + } + +private: + ScriptEngine *const m_engine; + QScriptValue m_proto; + QScriptValue m_descriptor; +}; + +static QScriptValue js_consoleError(QScriptContext *context, QScriptEngine *engine, Logger *logger) +{ + if (Q_UNLIKELY(context->argumentCount() != 1)) + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("console.error() expects 1 argument")); + logger->qbsLog(LoggerError) << context->argument(0).toString(); + return engine->undefinedValue(); +} + +static QScriptValue js_consoleWarn(QScriptContext *context, QScriptEngine *engine, Logger *logger) +{ + if (Q_UNLIKELY(context->argumentCount() != 1)) + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("console.warn() expects 1 argument")); + logger->qbsWarning() << context->argument(0).toString(); + return engine->undefinedValue(); +} + +static QScriptValue js_consoleInfo(QScriptContext *context, QScriptEngine *engine, Logger *logger) +{ + if (Q_UNLIKELY(context->argumentCount() != 1)) + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("console.info() expects 1 argument")); + logger->qbsInfo() << context->argument(0).toString(); + return engine->undefinedValue(); +} + +static QScriptValue js_consoleDebug(QScriptContext *context, QScriptEngine *engine, Logger *logger) +{ + if (Q_UNLIKELY(context->argumentCount() != 1)) + return context->throwError(QScriptContext::SyntaxError, + QStringLiteral("console.debug() expects 1 argument")); + logger->qbsDebug() << context->argument(0).toString(); + return engine->undefinedValue(); +} + +static QScriptValue js_consoleLog(QScriptContext *context, QScriptEngine *engine, Logger *logger) +{ + return js_consoleDebug(context, engine, logger); +} + +void ScriptEngine::installQbsBuiltins() +{ + globalObject().setProperty(StringConstants::qbsModule(), m_qbsObject = newObject()); + + globalObject().setProperty(QStringLiteral("console"), m_consoleObject = newObject()); + installConsoleFunction(QStringLiteral("debug"), &js_consoleDebug); + installConsoleFunction(QStringLiteral("error"), &js_consoleError); + installConsoleFunction(QStringLiteral("info"), &js_consoleInfo); + installConsoleFunction(QStringLiteral("log"), &js_consoleLog); + installConsoleFunction(QStringLiteral("warn"), &js_consoleWarn); +} + +void ScriptEngine::extendJavaScriptBuiltins() +{ + JSTypeExtender arrayExtender(this, QStringLiteral("Array")); + arrayExtender.addFunction(QStringLiteral("contains"), + QStringLiteral("(function(e){return this.indexOf(e) !== -1;})")); + arrayExtender.addFunction(QStringLiteral("containsAll"), + QStringLiteral("(function(e){var $this = this;" + "return e.every(function (v) { return $this.contains(v) });})")); + arrayExtender.addFunction(QStringLiteral("containsAny"), + QStringLiteral("(function(e){var $this = this;" + "return e.some(function (v) { return $this.contains(v) });})")); + arrayExtender.addFunction(QStringLiteral("uniqueConcat"), + QStringLiteral("(function(other){" + "var r = this.concat();" + "var s = {};" + "r.forEach(function(x){ s[x] = true; });" + "other.forEach(function(x){" + "if (!s[x]) {" + "s[x] = true;" + "r.push(x);" + "}" + "});" + "return r;})")); + + JSTypeExtender stringExtender(this, QStringLiteral("String")); + stringExtender.addFunction(QStringLiteral("contains"), + QStringLiteral("(function(e){return this.indexOf(e) !== -1;})")); + stringExtender.addFunction(QStringLiteral("startsWith"), + QStringLiteral("(function(e){return this.slice(0, e.length) === e;})")); + stringExtender.addFunction(QStringLiteral("endsWith"), + QStringLiteral("(function(e){return this.slice(-e.length) === e;})")); +} + +void ScriptEngine::installFunction(const QString &name, int length, QScriptValue *functionValue, + FunctionSignature f, QScriptValue *targetObject = nullptr) +{ + if (!functionValue->isValid()) + *functionValue = newFunction(f, length); + (targetObject ? *targetObject : globalObject()).setProperty(name, *functionValue); +} + +void ScriptEngine::installQbsFunction(const QString &name, int length, FunctionSignature f) +{ + QScriptValue functionValue; + installFunction(name, length, &functionValue, f, &m_qbsObject); +} + +void ScriptEngine::installConsoleFunction(const QString &name, + QScriptValue (*f)(QScriptContext *, QScriptEngine *, Logger *)) +{ + m_consoleObject.setProperty(name, newFunction(f, &m_logger)); +} + +static QString loadFileString() { return QStringLiteral("loadFile"); } +static QString loadExtensionString() { return QStringLiteral("loadExtension"); } +static QString requireString() { return QStringLiteral("require"); } + +void ScriptEngine::installImportFunctions() +{ + installFunction(loadFileString(), 1, &m_loadFileFunction, js_loadFile); + installFunction(loadExtensionString(), 1, &m_loadExtensionFunction, js_loadExtension); + installFunction(requireString(), 1, &m_requireFunction, js_require); +} + +void ScriptEngine::uninstallImportFunctions() +{ + globalObject().setProperty(loadFileString(), QScriptValue()); + globalObject().setProperty(loadExtensionString(), QScriptValue()); + globalObject().setProperty(requireString(), QScriptValue()); +} + +ScriptEngine::PropertyCacheKey::PropertyCacheKey(QString moduleName, + QString propertyName, PropertyMapConstPtr propertyMap) + : m_moduleName(std::move(moduleName)) + , m_propertyName(std::move(propertyName)) + , m_propertyMap(std::move(propertyMap)) +{ +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/scriptengine.h b/src/lib/corelib/language/scriptengine.h new file mode 100644 index 00000000..24e133df --- /dev/null +++ b/src/lib/corelib/language/scriptengine.h @@ -0,0 +1,385 @@ +/**************************************************************************** +** +** 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 QBS_SCRIPTENGINE_H +#define QBS_SCRIPTENGINE_H + +#include "forward_decls.h" +#include "property.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace qbs { +namespace Internal { +class Artifact; +class JsImport; +class PrepareScriptObserver; +class ScriptImporter; +class ScriptPropertyObserver; + +enum class EvalContext { + PropertyEvaluation, ProbeExecution, ModuleProvider, RuleExecution, JsCommand +}; +class DubiousContext +{ +public: + enum Suggestion { NoSuggestion, SuggestMoving }; + DubiousContext(EvalContext c, Suggestion s = NoSuggestion) : context(c), suggestion(s) { } + EvalContext context; + Suggestion suggestion; +}; +using DubiousContextList = std::vector; + + +/* + * ScriptObject that acquires resources, for example a file handle. + * The ScriptObject should have QtOwnership and deleteLater() itself in releaseResources. + */ +class ResourceAcquiringScriptObject +{ +public: + virtual ~ResourceAcquiringScriptObject() = default; + virtual void releaseResources() = 0; +}; + +enum class ObserveMode { Enabled, Disabled }; + +class QBS_AUTOTEST_EXPORT ScriptEngine : public QScriptEngine +{ + Q_OBJECT + ScriptEngine(Logger &logger, EvalContext evalContext, QObject *parent = nullptr); +public: + static ScriptEngine *create(Logger &logger, EvalContext evalContext, QObject *parent = nullptr); + ~ScriptEngine() override; + + Logger &logger() const { return m_logger; } + void import(const FileContextBaseConstPtr &fileCtx, QScriptValue &targetObject, + ObserveMode observeMode); + void clearImportsCache(); + + void setEvalContext(EvalContext c) { m_evalContext = c; } + EvalContext evalContext() const { return m_evalContext; } + void checkContext(const QString &operation, const DubiousContextList &dubiousContexts); + + void addPropertyRequestedInScript(const Property &property) { + m_propertiesRequestedInScript += property; + } + void addDependenciesArrayRequested(const ResolvedProduct *p) + { + m_productsWithRequestedDependencies.insert(p); + } + void setArtifactsMapRequested(const ResolvedProduct *product, bool forceUpdate) + { + m_requestedArtifacts.setAllArtifactTags(product, forceUpdate); + } + void setArtifactSetRequestedForTag(const ResolvedProduct *product, const FileTag &tag) + { + m_requestedArtifacts.setArtifactsForTag(product, tag); + } + void setNonExistingArtifactSetRequested(const ResolvedProduct *product, const QString &tag) + { + m_requestedArtifacts.setNonExistingTagRequested(product, tag); + } + void setArtifactsEnumerated(const ResolvedProduct *product) + { + m_requestedArtifacts.setArtifactsEnumerated(product); + } + void addPropertyRequestedFromArtifact(const Artifact *artifact, const Property &property); + void addRequestedExport(const ResolvedProduct *product) { m_requestedExports.insert(product); } + void clearRequestedProperties() { + m_propertiesRequestedInScript.clear(); + m_propertiesRequestedFromArtifact.clear(); + m_importsRequestedInScript.clear(); + m_productsWithRequestedDependencies.clear(); + m_requestedArtifacts.clear(); + m_requestedExports.clear(); + } + PropertySet propertiesRequestedInScript() const { return m_propertiesRequestedInScript; } + QHash propertiesRequestedFromArtifact() const { + return m_propertiesRequestedFromArtifact; + } + Set productsWithRequestedDependencies() const + { + return m_productsWithRequestedDependencies; + } + RequestedDependencies requestedDependencies() const + { + return RequestedDependencies(m_productsWithRequestedDependencies); + } + RequestedArtifacts requestedArtifacts() const { return m_requestedArtifacts; } + Set requestedExports() const { return m_requestedExports; } + + void addImportRequestedInScript(qint64 importValueId); + std::vector importedFilesUsedInScript() const; + + void setUsesIo() { m_usesIo = true; } + void clearUsesIo() { m_usesIo = false; } + bool usesIo() const { return m_usesIo; } + + void enableProfiling(bool enable); + + void setPropertyCacheEnabled(bool enable) { m_propertyCacheEnabled = enable; } + bool isPropertyCacheEnabled() const { return m_propertyCacheEnabled; } + void addToPropertyCache(const QString &moduleName, const QString &propertyName, + const PropertyMapConstPtr &propertyMap, const QVariant &value); + QVariant retrieveFromPropertyCache(const QString &moduleName, const QString &propertyName, + const PropertyMapConstPtr &propertyMap); + + void defineProperty(QScriptValue &object, const QString &name, const QScriptValue &descriptor); + void setObservedProperty(QScriptValue &object, const QString &name, const QScriptValue &value); + void unobserveProperties(); + void setDeprecatedProperty(QScriptValue &object, const QString &name, const QString &newName, + const QScriptValue &value); + PrepareScriptObserver *observer() const { return m_observer.get(); } + + QProcessEnvironment environment() const; + void setEnvironment(const QProcessEnvironment &env); + void addCanonicalFilePathResult(const QString &filePath, const QString &resultFilePath); + void addFileExistsResult(const QString &filePath, bool exists); + void addDirectoryEntriesResult(const QString &path, QDir::Filters filters, + const QStringList &entries); + void addFileLastModifiedResult(const QString &filePath, const FileTime &fileTime); + QHash canonicalFilePathResults() const { return m_canonicalFilePathResult; } + QHash fileExistsResults() const { return m_fileExistsResult; } + QHash, QStringList> directoryEntriesResults() const + { + return m_directoryEntriesResult; + } + + QHash fileLastModifiedResults() const { return m_fileLastModifiedResult; } + Set imports() const; + static QScriptValueList argumentList(const QStringList &argumentNames, + const QScriptValue &context); + + QStringList uncaughtExceptionBacktraceOrEmpty() const { + return hasUncaughtException() ? uncaughtExceptionBacktrace() : QStringList(); + } + bool hasErrorOrException(const QScriptValue &v) const { + return v.isError() || hasUncaughtException(); + } + QScriptValue lastErrorValue(const QScriptValue &v) const { + return v.isError() ? v : uncaughtException(); + } + QString lastErrorString(const QScriptValue &v) const { return lastErrorValue(v).toString(); } + CodeLocation lastErrorLocation(const QScriptValue &v, + const CodeLocation &fallbackLocation = CodeLocation()) const; + ErrorInfo lastError(const QScriptValue &v, + const CodeLocation &fallbackLocation = CodeLocation()) const; + + void cancel(); + + // The active flag is different from QScriptEngine::isEvaluating. + // It is set and cleared externally for example by the rule execution code. + bool isActive() const { return m_active; } + void setActive(bool on) { m_active = on; } + + using QScriptEngine::newFunction; + + template ::value>, + typename = std::enable_if_t::value>, + typename = std::enable_if_t>::value> + > QScriptValue newFunction(QScriptValue (*signature)(QScriptContext *, E, T), T arg) { + return QScriptEngine::newFunction( + reinterpret_cast(signature), + reinterpret_cast(const_cast< + std::add_pointer_t< + std::remove_const_t< + std::remove_pointer_t>>>(arg))); + } + + QScriptClass *modulePropertyScriptClass() const; + void setModulePropertyScriptClass(QScriptClass *modulePropertyScriptClass); + + QScriptClass *productPropertyScriptClass() const { return m_productPropertyScriptClass; } + void setProductPropertyScriptClass(QScriptClass *productPropertyScriptClass) + { + m_productPropertyScriptClass = productPropertyScriptClass; + } + + QScriptClass *artifactsScriptClass() const { return m_artifactsScriptClass; } + void setArtifactsScriptClass(QScriptClass *artifactsScriptClass) + { + m_artifactsScriptClass = artifactsScriptClass; + } + + void addResourceAcquiringScriptObject(ResourceAcquiringScriptObject *obj); + void releaseResourcesOfScriptObjects(); + + QScriptValue &productScriptValuePrototype(const ResolvedProduct *product) + { + return m_productScriptValues[product]; + } + + QScriptValue &projectScriptValue(const ResolvedProject *project) + { + return m_projectScriptValues[project]; + } + + QScriptValue &moduleScriptValuePrototype(const ResolvedModule *module) + { + return m_moduleScriptValues[module]; + } + +private: + QScriptValue newFunction(FunctionWithArgSignature signature, void *arg) Q_DECL_EQ_DELETE; + + void abort(); + + bool gatherFileResults() const; + + void installQbsBuiltins(); + void extendJavaScriptBuiltins(); + void installFunction(const QString &name, int length, QScriptValue *functionValue, + FunctionSignature f, QScriptValue *targetObject); + void installQbsFunction(const QString &name, int length, FunctionSignature f); + void installConsoleFunction(const QString &name, + QScriptValue (*f)(QScriptContext *, QScriptEngine *, Logger *)); + void installImportFunctions(); + void uninstallImportFunctions(); + void import(const JsImport &jsImport, QScriptValue &targetObject); + void observeImport(QScriptValue &jsImport); + void importFile(const QString &filePath, QScriptValue &targetObject); + static QScriptValue js_loadExtension(QScriptContext *context, QScriptEngine *qtengine); + static QScriptValue js_loadFile(QScriptContext *context, QScriptEngine *qtengine); + static QScriptValue js_require(QScriptContext *context, QScriptEngine *qtengine); + + class PropertyCacheKey + { + public: + PropertyCacheKey(QString moduleName, QString propertyName, + PropertyMapConstPtr propertyMap); + private: + const QString m_moduleName; + const QString m_propertyName; + const PropertyMapConstPtr m_propertyMap; + + friend bool operator==(const PropertyCacheKey &lhs, const PropertyCacheKey &rhs); + friend uint qHash(const ScriptEngine::PropertyCacheKey &k, uint seed); + }; + + friend bool operator==(const PropertyCacheKey &lhs, const PropertyCacheKey &rhs); + friend uint qHash(const ScriptEngine::PropertyCacheKey &k, uint seed); + + static std::mutex m_creationDestructionMutex; + ScriptImporter *m_scriptImporter; + QScriptClass *m_modulePropertyScriptClass; + QScriptClass *m_productPropertyScriptClass = nullptr; + QScriptClass *m_artifactsScriptClass = nullptr; + QHash m_jsImportCache; + std::unordered_map m_jsFileCache; + bool m_propertyCacheEnabled; + bool m_active; + QHash m_propertyCache; + PropertySet m_propertiesRequestedInScript; + QHash m_propertiesRequestedFromArtifact; + Logger &m_logger; + QScriptValue m_definePropertyFunction; + QScriptValue m_emptyFunction; + QProcessEnvironment m_environment; + QHash m_canonicalFilePathResult; + QHash m_fileExistsResult; + QHash, QStringList> m_directoryEntriesResult; + QHash m_fileLastModifiedResult; + std::stack m_currentDirPathStack; + std::stack m_extensionSearchPathsStack; + QScriptValue m_loadFileFunction; + QScriptValue m_loadExtensionFunction; + QScriptValue m_requireFunction; + QScriptValue m_qbsObject; + QScriptValue m_consoleObject; + QScriptValue m_cancelationError; + qint64 m_elapsedTimeImporting = -1; + bool m_usesIo = false; + EvalContext m_evalContext; + std::vector m_resourceAcquiringScriptObjects; + const std::unique_ptr m_observer; + std::vector> m_observedProperties; + std::vector m_requireResults; + std::unordered_map> m_filePathsPerImport; + std::vector m_importsRequestedInScript; + Set m_productsWithRequestedDependencies; + RequestedArtifacts m_requestedArtifacts; + Set m_requestedExports; + ObserveMode m_observeMode = ObserveMode::Disabled; + std::unordered_map m_productScriptValues; + std::unordered_map m_projectScriptValues; + std::unordered_map m_moduleScriptValues; +}; + +class EvalContextSwitcher +{ +public: + EvalContextSwitcher(ScriptEngine *engine, EvalContext newContext) + : m_engine(engine), m_oldContext(engine->evalContext()) + { + engine->setEvalContext(newContext); + } + + ~EvalContextSwitcher() { m_engine->setEvalContext(m_oldContext); } + +private: + ScriptEngine * const m_engine; + const EvalContext m_oldContext; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_SCRIPTENGINE_H diff --git a/src/lib/corelib/language/scriptimporter.cpp b/src/lib/corelib/language/scriptimporter.cpp new file mode 100644 index 00000000..9c6d4d38 --- /dev/null +++ b/src/lib/corelib/language/scriptimporter.cpp @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** 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 "scriptimporter.h" + +#include "evaluator.h" +#include "scriptengine.h" + +#include +#include +#include +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class IdentifierExtractor : private QbsQmlJS::AST::Visitor +{ +public: + void start(QbsQmlJS::AST::Node *node) + { + m_first = true; + m_barrier = false; + m_suffix += QLatin1String("\nreturn {"); + if (node) + node->accept(this); + m_suffix += QLatin1String("}})()"); + } + + const QString &suffix() const { return m_suffix; } + +private: + bool visit(QbsQmlJS::AST::SourceElements *) override + { + // Only consider the top level of source elements. + if (m_barrier) + return false; + m_barrier = true; + return true; + } + + void endVisit(QbsQmlJS::AST::SourceElements *) override + { + m_barrier = false; + } + + bool visit(QbsQmlJS::AST::FunctionSourceElement *fse) override + { + add(fse->declaration->name); + return false; + } + + bool visit(QbsQmlJS::AST::VariableDeclaration *vd) override + { + add(vd->name); + return false; + } + + void add(const QStringRef &name) + { + if (m_first) { + m_first = false; + m_suffix.reserve(m_suffix.length() + name.length() * 2 + 1); + } else { + m_suffix.reserve(m_suffix.length() + name.length() * 2 + 2); + m_suffix += QLatin1Char(','); + } + m_suffix += name; + m_suffix += QLatin1Char(':'); + m_suffix += name; + } + + bool m_first = false; + bool m_barrier = false; + QString m_suffix; +}; + + +ScriptImporter::ScriptImporter(ScriptEngine *scriptEngine) + : m_engine(scriptEngine) +{ +} + +QScriptValue ScriptImporter::importSourceCode(const QString &sourceCode, const QString &filePath, + QScriptValue &targetObject) +{ + Q_ASSERT(targetObject.isObject()); + // The targetObject doesn't get overwritten but enhanced by the contents of the .js file. + // This is necessary for library imports that consist of multiple js files. + + QString &code = m_sourceCodeCache[filePath]; + if (code.isEmpty()) { + QbsQmlJS::Engine engine; + QbsQmlJS::Lexer lexer(&engine); + lexer.setCode(sourceCode, 1, false); + QbsQmlJS::Parser parser(&engine); + if (!parser.parseProgram()) { + throw ErrorInfo(parser.errorMessage(), CodeLocation(filePath, parser.errorLineNumber(), + parser.errorColumnNumber())); + } + + IdentifierExtractor extractor; + extractor.start(parser.rootNode()); + code = QLatin1String("(function(){\n") + sourceCode + extractor.suffix(); + } + + QScriptValue result = m_engine->evaluate(code, filePath, 0); + throwOnEvaluationError(m_engine, result, [&filePath] () { return CodeLocation(filePath, 0); }); + copyProperties(result, targetObject); + return result; +} + +void ScriptImporter::copyProperties(const QScriptValue &src, QScriptValue &dst) +{ + QScriptValueIterator it(src); + while (it.hasNext()) { + it.next(); + dst.setProperty(it.name(), it.value()); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/scriptimporter.h b/src/lib/corelib/language/scriptimporter.h new file mode 100644 index 00000000..8cff0938 --- /dev/null +++ b/src/lib/corelib/language/scriptimporter.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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 SCRIPTIMPORTER_H +#define SCRIPTIMPORTER_H + +#include + +#include + +namespace qbs { +namespace Internal { + +class ScriptEngine; + +class ScriptImporter +{ +public: + ScriptImporter(ScriptEngine *scriptEngine); + QScriptValue importSourceCode(const QString &sourceCode, const QString &filePath, QScriptValue &targetObject); + + static void copyProperties(const QScriptValue &src, QScriptValue &dst); + +private: + ScriptEngine *m_engine; + QHash m_sourceCodeCache; +}; + +} // namespace Internal +} // namespace qbs + +#endif // SCRIPTIMPORTER_H diff --git a/src/lib/corelib/language/scriptpropertyobserver.cpp b/src/lib/corelib/language/scriptpropertyobserver.cpp new file mode 100644 index 00000000..4b2a0faf --- /dev/null +++ b/src/lib/corelib/language/scriptpropertyobserver.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** 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 "scriptpropertyobserver.h" + +#include "scriptengine.h" + +namespace qbs { +namespace Internal { + +ScriptPropertyObserver::~ScriptPropertyObserver() +{ + if (m_unobserveMode == UnobserveMode::Enabled) + m_engine->unobserveProperties(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/scriptpropertyobserver.h b/src/lib/corelib/language/scriptpropertyobserver.h new file mode 100644 index 00000000..7fb362b9 --- /dev/null +++ b/src/lib/corelib/language/scriptpropertyobserver.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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 QBS_SCRIPTPROPERTYOBSERVER_H +#define QBS_SCRIPTPROPERTYOBSERVER_H + +#include + +QT_BEGIN_NAMESPACE +class QScriptValue; +class QString; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class ScriptEngine; + +enum class UnobserveMode { Enabled, Disabled }; + +class ScriptPropertyObserver +{ +public: + ScriptPropertyObserver(ScriptEngine *engine, UnobserveMode unobserveMode) + : m_engine(engine), m_unobserveMode(unobserveMode) + {} + + UnobserveMode unobserveMode() const { return m_unobserveMode; } + + virtual ~ScriptPropertyObserver(); + + virtual void onPropertyRead(const QScriptValue &object, const QString &name, + const QScriptValue &value) = 0; + +protected: + ScriptEngine * engine() const { return m_engine; } + +private: + ScriptEngine * const m_engine; + const UnobserveMode m_unobserveMode; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_SCRIPTPROPERTYOBSERVER_H diff --git a/src/lib/corelib/language/value.cpp b/src/lib/corelib/language/value.cpp new file mode 100644 index 00000000..342fbd89 --- /dev/null +++ b/src/lib/corelib/language/value.cpp @@ -0,0 +1,221 @@ +/**************************************************************************** +** +** 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 "value.h" + +#include "filecontext.h" +#include "item.h" + +#include +#include + +namespace qbs { +namespace Internal { + +Value::Value(Type t, bool createdByPropertiesBlock) + : m_type(t), m_definingItem(nullptr), m_createdByPropertiesBlock(createdByPropertiesBlock) +{ +} + +Value::Value(const Value &other) + : m_type(other.m_type), + m_definingItem(other.m_definingItem), + m_next(other.m_next ? other.m_next->clone() : ValuePtr()), + m_createdByPropertiesBlock(other.m_createdByPropertiesBlock) +{ +} + +Value::~Value() = default; + +Item *Value::definingItem() const +{ + return m_definingItem; +} + +void Value::setDefiningItem(Item *item) +{ + m_definingItem = item; +} + +ValuePtr Value::next() const +{ + return m_next; +} + +void Value::setNext(const ValuePtr &next) +{ + QBS_ASSERT(next.get() != this, return); + QBS_CHECK(type() != VariantValueType); + m_next = next; +} + + +JSSourceValue::JSSourceValue(bool createdByPropertiesBlock) + : Value(JSSourceValueType, createdByPropertiesBlock) + , m_line(-1) + , m_column(-1) +{ +} + +JSSourceValue::JSSourceValue(const JSSourceValue &other) : Value(other) +{ + m_sourceCode = other.m_sourceCode; + m_line = other.m_line; + m_column = other.m_column; + m_file = other.m_file; + m_flags = other.m_flags; + m_baseValue = other.m_baseValue + ? std::static_pointer_cast(other.m_baseValue->clone()) + : JSSourceValuePtr(); + m_alternatives.reserve(other.m_alternatives.size()); + for (const Alternative &otherAlt : other.m_alternatives) + m_alternatives.push_back(otherAlt.clone()); +} + +JSSourceValuePtr JSSourceValue::create(bool createdByPropertiesBlock) +{ + return JSSourceValuePtr(new JSSourceValue(createdByPropertiesBlock)); +} + +JSSourceValue::~JSSourceValue() = default; + +ValuePtr JSSourceValue::clone() const +{ + return JSSourceValuePtr(new JSSourceValue(*this)); +} + +QString JSSourceValue::sourceCodeForEvaluation() const +{ + if (!hasFunctionForm()) + return m_sourceCode.toString(); + + // rewrite blocks to be able to use return statements in property assignments + static const QString prefix = QStringLiteral("(function()"); + static const QString suffix = QStringLiteral(")()"); + return prefix + m_sourceCode.toString() + suffix; +} + +void JSSourceValue::setLocation(int line, int column) +{ + m_line = line; + m_column = column; +} + +CodeLocation JSSourceValue::location() const +{ + return CodeLocation(m_file->filePath(), m_line, m_column); +} + +void JSSourceValue::setHasFunctionForm(bool b) +{ + if (b) + m_flags |= HasFunctionForm; + else + m_flags &= ~HasFunctionForm; +} + +void JSSourceValue::clearAlternatives() +{ + m_alternatives.clear(); +} + +void JSSourceValue::setDefiningItem(Item *item) +{ + Value::setDefiningItem(item); + for (const JSSourceValue::Alternative &a : m_alternatives) + a.value->setDefiningItem(item); +} + +ItemValue::ItemValue(Item *item, bool createdByPropertiesBlock) + : Value(ItemValueType, createdByPropertiesBlock) + , m_item(item) +{ + QBS_CHECK(m_item); +} + +ItemValuePtr ItemValue::create(Item *item, bool createdByPropertiesBlock) +{ + return ItemValuePtr(new ItemValue(item, createdByPropertiesBlock)); +} + +ValuePtr ItemValue::clone() const +{ + return create(m_item->clone(), createdByPropertiesBlock()); +} + +VariantValue::VariantValue(QVariant v) + : Value(VariantValueType, false) + , m_value(std::move(v)) +{ +} + +VariantValuePtr VariantValue::create(const QVariant &v) +{ + if (!v.isValid()) + return invalidValue(); + if (static_cast(v.type()) == QMetaType::Bool) + return v.toBool() ? VariantValue::trueValue() : VariantValue::falseValue(); + return VariantValuePtr(new VariantValue(v)); +} + +ValuePtr VariantValue::clone() const +{ + return std::make_shared(*this); +} + +const VariantValuePtr &VariantValue::falseValue() +{ + static const VariantValuePtr v = VariantValuePtr(new VariantValue(false)); + return v; +} + +const VariantValuePtr &VariantValue::trueValue() +{ + static const VariantValuePtr v = VariantValuePtr(new VariantValue(true)); + return v; +} + +const VariantValuePtr &VariantValue::invalidValue() +{ + static const VariantValuePtr v = VariantValuePtr(new VariantValue(QVariant())); + return v; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/language/value.h b/src/lib/corelib/language/value.h new file mode 100644 index 00000000..d3a748d9 --- /dev/null +++ b/src/lib/corelib/language/value.h @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** 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 QBS_VALUE_H +#define QBS_VALUE_H + +#include "forward_decls.h" +#include +#include + +#include + +namespace qbs { +namespace Internal { +class Item; +class ValueHandler; + +class Value +{ +public: + enum Type + { + JSSourceValueType, + ItemValueType, + VariantValueType + }; + + Value(Type t, bool createdByPropertiesBlock); + Value(const Value &other); + virtual ~Value(); + + Type type() const { return m_type; } + virtual void apply(ValueHandler *) = 0; + virtual ValuePtr clone() const = 0; + virtual CodeLocation location() const { return {}; } + + Item *definingItem() const; + virtual void setDefiningItem(Item *item); + + ValuePtr next() const; + void setNext(const ValuePtr &next); + + bool createdByPropertiesBlock() const { return m_createdByPropertiesBlock; } + void setCreatedByPropertiesBlock(bool b) { m_createdByPropertiesBlock = b; } + void clearCreatedByPropertiesBlock() { m_createdByPropertiesBlock = false; } + +private: + Type m_type; + Item *m_definingItem; + ValuePtr m_next; + bool m_createdByPropertiesBlock; +}; + +class ValueHandler +{ +public: + virtual void handle(JSSourceValue *value) = 0; + virtual void handle(ItemValue *value) = 0; + virtual void handle(VariantValue *value) = 0; +}; + +class JSSourceValue : public Value +{ + friend class ItemReaderASTVisitor; + JSSourceValue(bool createdByPropertiesBlock); + JSSourceValue(const JSSourceValue &other); + + enum Flag + { + NoFlags = 0x00, + SourceUsesBase = 0x01, + SourceUsesOuter = 0x02, + SourceUsesOriginal = 0x04, + HasFunctionForm = 0x08, + ExclusiveListValue = 0x10, + BuiltinDefaultValue = 0x20, + }; + Q_DECLARE_FLAGS(Flags, Flag) + +public: + static JSSourceValuePtr QBS_AUTOTEST_EXPORT create(bool createdByPropertiesBlock = false); + ~JSSourceValue() override; + + void apply(ValueHandler *handler) override { handler->handle(this); } + ValuePtr clone() const override; + + void setSourceCode(const QStringRef &sourceCode) { m_sourceCode = sourceCode; } + const QStringRef &sourceCode() const { return m_sourceCode; } + QString sourceCodeForEvaluation() const; + + void setLocation(int line, int column); + int line() const { return m_line; } + int column() const { return m_column; } + CodeLocation location() const override; + + void setFile(const FileContextPtr &file) { m_file = file; } + const FileContextPtr &file() const { return m_file; } + + void setSourceUsesBaseFlag() { m_flags |= SourceUsesBase; } + bool sourceUsesBase() const { return m_flags.testFlag(SourceUsesBase); } + bool sourceUsesOuter() const { return m_flags.testFlag(SourceUsesOuter); } + bool sourceUsesOriginal() const { return m_flags.testFlag(SourceUsesOriginal); } + bool hasFunctionForm() const { return m_flags.testFlag(HasFunctionForm); } + void setHasFunctionForm(bool b); + void setIsExclusiveListValue() { m_flags |= ExclusiveListValue; } + bool isExclusiveListValue() { return m_flags.testFlag(ExclusiveListValue); } + void setIsBuiltinDefaultValue() { m_flags |= BuiltinDefaultValue; } + bool isBuiltinDefaultValue() const { return m_flags.testFlag(BuiltinDefaultValue); } + + const JSSourceValuePtr &baseValue() const { return m_baseValue; } + void setBaseValue(const JSSourceValuePtr &v) { m_baseValue = v; } + + struct Alternative + { + struct PropertyData + { + PropertyData() = default; + PropertyData(QString v, const CodeLocation &l) : value(std::move(v)), location(l) {} + QString value; + CodeLocation location; + }; + + Alternative() = default; + Alternative(PropertyData c, PropertyData o, JSSourceValuePtr v) + : condition(std::move(c)), overrideListProperties(std::move(o)), value(std::move(v)) {} + Alternative clone() const + { + return Alternative(condition, overrideListProperties, + std::static_pointer_cast(value->clone())); + } + + PropertyData condition; + PropertyData overrideListProperties; + JSSourceValuePtr value; + }; + using AltProperty = Alternative::PropertyData; + + const std::vector &alternatives() const { return m_alternatives; } + void addAlternative(const Alternative &alternative) { m_alternatives.push_back(alternative); } + void clearAlternatives(); + + void setDefiningItem(Item *item) override; + +private: + QStringRef m_sourceCode; + int m_line; + int m_column; + FileContextPtr m_file; + Flags m_flags; + JSSourceValuePtr m_baseValue; + std::vector m_alternatives; +}; + + +class ItemValue : public Value +{ + ItemValue(Item *item, bool createdByPropertiesBlock); +public: + static ItemValuePtr create(Item *item, bool createdByPropertiesBlock = false); + + Item *item() const { return m_item; } + void setItem(Item *item) { m_item = item; } + +private: + void apply(ValueHandler *handler) override { handler->handle(this); } + ValuePtr clone() const override; + + Item *m_item; +}; + + +class VariantValue : public Value +{ + VariantValue(QVariant v); +public: + static VariantValuePtr create(const QVariant &v = QVariant()); + + void apply(ValueHandler *handler) override { handler->handle(this); } + ValuePtr clone() const override; + + const QVariant &value() const { return m_value; } + + static const VariantValuePtr &falseValue(); + static const VariantValuePtr &trueValue(); + static const VariantValuePtr &invalidValue(); + +private: + QVariant m_value; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_VALUE_H diff --git a/src/lib/corelib/logging/categories.cpp b/src/lib/corelib/logging/categories.cpp new file mode 100644 index 00000000..0f844f5b --- /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 00000000..40c69845 --- /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 + +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/ilogsink.cpp b/src/lib/corelib/logging/ilogsink.cpp new file mode 100644 index 00000000..4eb930cb --- /dev/null +++ b/src/lib/corelib/logging/ilogsink.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** 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 "ilogsink.h" + +#include + +#include + +#include + +namespace qbs { + +QString logLevelTag(LoggerLevel level) +{ + if (level == LoggerInfo) + return {}; + QString str = logLevelName(level).toUpper(); + if (!str.isEmpty()) + str.append(QLatin1String(": ")); + return str; +} + +QString logLevelName(LoggerLevel level) +{ + switch (level) { + case qbs::LoggerError: + return QStringLiteral("error"); + case qbs::LoggerWarning: + return QStringLiteral("warning"); + case qbs::LoggerInfo: + return QStringLiteral("info"); + case qbs::LoggerDebug: + return QStringLiteral("debug"); + case qbs::LoggerTrace: + return QStringLiteral("trace"); + default: + break; + } + return {}; +} + +class ILogSink::ILogSinkPrivate +{ +public: + LoggerLevel logLevel = defaultLogLevel(); + std::mutex mutex; +}; + +ILogSink::ILogSink() : d(new ILogSinkPrivate) +{ +} + +ILogSink::~ILogSink() +{ + delete d; +} + +void ILogSink::setLogLevel(LoggerLevel level) +{ + d->logLevel = level; +} + +LoggerLevel ILogSink::logLevel() const +{ + return d->logLevel; +} + +void ILogSink::printWarning(const ErrorInfo &warning) +{ + if (willPrint(LoggerWarning)) { + d->mutex.lock(); + doPrintWarning(warning); + d->mutex.unlock(); + } +} + +void ILogSink::printMessage(LoggerLevel level, const QString &message, const QString &tag, + bool force) +{ + if (force || willPrint(level)) { + d->mutex.lock(); + doPrintMessage(level, message, tag); + d->mutex.unlock(); + } +} + +void ILogSink::doPrintWarning(const ErrorInfo &warning) +{ + doPrintMessage(LoggerWarning, warning.toString(), QString()); +} + +} // namespace qbs diff --git a/src/lib/corelib/logging/ilogsink.h b/src/lib/corelib/logging/ilogsink.h new file mode 100644 index 00000000..4b25aa7b --- /dev/null +++ b/src/lib/corelib/logging/ilogsink.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 QBS_ILOGSINK_H +#define QBS_ILOGSINK_H + +#include "../tools/qbs_export.h" + +#include + +namespace qbs { +class ErrorInfo; + +enum LoggerLevel +{ + LoggerMinLevel, + LoggerError = LoggerMinLevel, + LoggerWarning, + LoggerInfo, + LoggerDebug, + LoggerTrace, + LoggerMaxLevel = LoggerTrace +}; + +inline LoggerLevel defaultLogLevel() { return LoggerInfo; } +QBS_EXPORT QString logLevelTag(LoggerLevel level); +QBS_EXPORT QString logLevelName(LoggerLevel level); + +class QBS_EXPORT ILogSink +{ + Q_DISABLE_COPY(ILogSink) +public: + ILogSink(); + virtual ~ILogSink(); + + void setLogLevel(LoggerLevel level); + LoggerLevel logLevel() const; + + bool willPrint(LoggerLevel level) const { return level <= logLevel(); } + + void printWarning(const ErrorInfo &warning); + void printMessage(LoggerLevel level, const QString &message, + const QString &tag = QString(), bool force = false); + +private: + virtual void doPrintWarning(const ErrorInfo &warning); + virtual void doPrintMessage(LoggerLevel level, const QString &message, + const QString &tag) = 0; + + class ILogSinkPrivate; + ILogSinkPrivate * const d; +}; + +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/logging/logger.cpp b/src/lib/corelib/logging/logger.cpp new file mode 100644 index 00000000..2ed29c4c --- /dev/null +++ b/src/lib/corelib/logging/logger.cpp @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#if defined(_MSC_VER) && _MSC_VER > 0 +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "logger.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +LogWriter::LogWriter(ILogSink *logSink, LoggerLevel level, bool force) + : m_logSink(logSink), m_level(level), m_force(force) +{} + +LogWriter::LogWriter(const LogWriter &other) + : m_logSink(other.m_logSink) + , m_level(other.m_level) + , m_message(other.m_message) + , m_tag(other.m_tag) + , m_force(other.m_force) +{ + other.m_message.clear(); +} + +LogWriter::~LogWriter() +{ + if (!m_message.isEmpty()) + m_logSink->printMessage(m_level, m_message, m_tag, m_force); +} + +const LogWriter &LogWriter::operator=(const LogWriter &other) +{ + m_logSink = other.m_logSink; + m_level = other.m_level; + m_message = other.m_message; + m_tag = other.m_tag; + m_force = other.m_force; + other.m_message.clear(); + return *this; +} + +void LogWriter::write(char c) +{ + write(QLatin1Char(c)); +} + +void LogWriter::write(const char *str) +{ + write(QLatin1String(str)); +} + +void LogWriter::write(const QChar &c) +{ + if (m_force || m_logSink->logLevel() >= m_level) + m_message.append(c); +} + +void LogWriter::write(const QString &message) +{ + if (m_force || m_logSink->logLevel() >= m_level) + m_message += message; +} + +void LogWriter::setMessageTag(const QString &tag) +{ + m_tag = tag; +} + +LogWriter operator<<(LogWriter w, const char *str) +{ + w.write(str); + return w; +} + +LogWriter operator<<(LogWriter w, const QByteArray &byteArray) +{ + w.write(byteArray.data()); + return w; +} + +LogWriter operator<<(LogWriter w, const QString &str) +{ + w.write(str); + return w; +} + +LogWriter operator<<(LogWriter w, const QStringList &strList) +{ + w.write('['); + for (int i = 0; i < strList.size(); ++i) { + w.write(strList.at(i)); + if (i != strList.size() - 1) + w.write(QStringLiteral(", ")); + } + w.write(']'); + return w; +} + +LogWriter operator<<(LogWriter w, const Internal::Set &strSet) +{ + bool firstLoop = true; + w.write('('); + for (const QString &str : strSet) { + if (firstLoop) + firstLoop = false; + else + w.write(QStringLiteral(", ")); + w.write(str); + } + w.write(')'); + return w; +} + +LogWriter operator<<(LogWriter w, const QVariant &variant) +{ + QString str = QLatin1String(variant.typeName()) + QLatin1Char('('); + if (variant.type() == QVariant::List) { + bool firstLoop = true; + const auto list = variant.toList(); + for (const QVariant &item : list) { + str += item.toString(); + if (firstLoop) + firstLoop = false; + else + str += QLatin1String(", "); + } + } else { + str += variant.toString(); + } + str += QLatin1Char(')'); + w.write(str); + return w; +} + +LogWriter operator<<(LogWriter w, int n) +{ + w.write(QString::number(n)); + return w; +} + +LogWriter operator<<(LogWriter w, qint64 n) +{ + w.write(QString::number(n)); + return w; +} + +LogWriter operator<<(LogWriter w, bool b) +{ + w.write(QString::fromLatin1(b ? "true" : "false")); + return w; +} + +LogWriter operator<<(LogWriter w, const MessageTag &tag) +{ + w.setMessageTag(tag.tag()); + return w; +} + +Logger::Logger(ILogSink *logger) : m_logSink(logger) +{ +} + +bool Logger::debugEnabled() const +{ + return m_logSink->willPrint(LoggerDebug); +} + +bool Logger::traceEnabled() const +{ + return m_logSink->willPrint(LoggerTrace); +} + +void Logger::printWarning(const ErrorInfo &warning) +{ + if (m_storeWarnings) + m_warnings.push_back(warning); + logSink()->printWarning(warning); +} + +LogWriter Logger::qbsLog(LoggerLevel level, bool force) const +{ + return LogWriter(m_logSink, level, force); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/logging/logger.h b/src/lib/corelib/logging/logger.h new file mode 100644 index 00000000..389dc7e0 --- /dev/null +++ b/src/lib/corelib/logging/logger.h @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** 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 QBS_LOGGER_H +#define QBS_LOGGER_H + +#include "ilogsink.h" + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QVariant; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +template class Set; + +// Note that while these classes are not part of the API, we export some stuff for use by +// our command line tools for the sake of a uniform logging approach. + +class QBS_EXPORT LogWriter +{ +public: + LogWriter(ILogSink *logSink, LoggerLevel level, bool force = false); + + // log writer has move semantics and the last instance of + // a << chain prints the accumulated data + LogWriter(const LogWriter &other); + ~LogWriter(); + const LogWriter &operator=(const LogWriter &other); // NOLINT + + void write(char c); + void write(const char *str); + void write(const QChar &c); + void write(const QString &message); + + void setMessageTag(const QString &tag); + +private: + ILogSink *m_logSink; + LoggerLevel m_level; + mutable QString m_message; + QString m_tag; + bool m_force; +}; + +class QBS_EXPORT MessageTag +{ +public: + explicit MessageTag(QString tag) : m_tag(std::move(tag)) {} + + const QString &tag() const { return m_tag; } + +private: + QString m_tag; +}; + +QBS_EXPORT LogWriter operator<<(LogWriter w, const char *str); +QBS_EXPORT LogWriter operator<<(LogWriter w, const QByteArray &byteArray); +QBS_EXPORT LogWriter operator<<(LogWriter w, const QString &str); +QBS_EXPORT LogWriter operator<<(LogWriter w, const QStringList &strList); +QBS_EXPORT LogWriter operator<<(LogWriter w, const Internal::Set &strSet); +QBS_EXPORT LogWriter operator<<(LogWriter w, const QVariant &variant); +QBS_EXPORT LogWriter operator<<(LogWriter w, int n); +QBS_EXPORT LogWriter operator<<(LogWriter w, qint64 n); +QBS_EXPORT LogWriter operator<<(LogWriter w, bool b); +QBS_EXPORT LogWriter operator<<(LogWriter w, const MessageTag &tag); + + +class QBS_EXPORT Logger +{ +public: + Logger(ILogSink *logSink = 0); + + ILogSink *logSink() const { return m_logSink; } + + bool debugEnabled() const; + bool traceEnabled() const; + + void printWarning(const ErrorInfo &warning); + QList warnings() const { return m_warnings; } + void clearWarnings() { m_warnings.clear(); } + void storeWarnings() { m_storeWarnings = true; } + + LogWriter qbsLog(LoggerLevel level, bool force = false) const; + LogWriter qbsWarning() const { return qbsLog(LoggerWarning); } + LogWriter qbsInfo() const { return qbsLog(LoggerInfo); } + LogWriter qbsDebug() const { return qbsLog(LoggerDebug); } + LogWriter qbsTrace() const { return qbsLog(LoggerTrace); } + +private: + ILogSink *m_logSink; + QList m_warnings; + bool m_storeWarnings = false; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_LOGGER_H diff --git a/src/lib/corelib/logging/logging.pri b/src/lib/corelib/logging/logging.pri new file mode 100644 index 00000000..3a4379a9 --- /dev/null +++ b/src/lib/corelib/logging/logging.pri @@ -0,0 +1,18 @@ +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 + +!qbs_no_dev_install { + logging_headers.files = $$PWD/ilogsink.h + logging_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/logging + INSTALLS += logging_headers +} diff --git a/src/lib/corelib/logging/translator.h b/src/lib/corelib/logging/translator.h new file mode 100644 index 00000000..122457e5 --- /dev/null +++ b/src/lib/corelib/logging/translator.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** 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 QBS_TRANSLATOR_H +#define QBS_TRANSLATOR_H + +#include + +#include + +namespace qbs { +namespace Internal { + +class QBS_EXPORT Tr // Name intended to be short. Exported for use by command line tools. +{ + Q_DECLARE_TR_FUNCTIONS(Qbs) +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_TRANSLATOR_H diff --git a/src/lib/corelib/parser/parser.pri b/src/lib/corelib/parser/parser.pri new file mode 100644 index 00000000..e6a8a534 --- /dev/null +++ b/src/lib/corelib/parser/parser.pri @@ -0,0 +1,21 @@ +HEADERS += \ + $$PWD/qmljsast_p.h \ + $$PWD/qmljsastfwd_p.h \ + $$PWD/qmljsastvisitor_p.h \ + $$PWD/qmljsengine_p.h \ + $$PWD/qmljsgrammar_p.h \ + $$PWD/qmljslexer_p.h \ + $$PWD/qmljsmemorypool_p.h \ + $$PWD/qmljsparser_p.h \ + $$PWD/qmljsglobal_p.h \ + $$PWD/qmlerror.h \ + $$PWD/qmljskeywords_p.h \ + +SOURCES += \ + $$PWD/qmljsast.cpp \ + $$PWD/qmljsastvisitor.cpp \ + $$PWD/qmljsengine_p.cpp \ + $$PWD/qmljsgrammar.cpp \ + $$PWD/qmljslexer.cpp \ + $$PWD/qmljsparser.cpp \ + $$PWD/qmlerror.cpp \ diff --git a/src/lib/corelib/parser/qmlerror.cpp b/src/lib/corelib/parser/qmlerror.cpp new file mode 100644 index 00000000..e72e79f8 --- /dev/null +++ b/src/lib/corelib/parser/qmlerror.cpp @@ -0,0 +1,294 @@ +/**************************************************************************** +** +** 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 "qmlerror.h" + +#include +#include +#include + +#include + +namespace QbsQmlJS { + +/*! + \class QmlError + \since 5.0 + \inmodule QtQml + \brief The QmlError class encapsulates a QML error. + + QmlError includes a textual description of the error, as well + as location information (the file, line, and column). The toString() + method creates a single-line, human-readable string containing all of + this information, for example: + \code + file:///home/user/test.qml:7:8: Invalid property assignment: double expected + \endcode + + You can use qDebug() or qWarning() to output errors to the console. This method + will attempt to open the file indicated by the error + and include additional contextual information. + \code + file:///home/user/test.qml:7:8: Invalid property assignment: double expected + y: "hello" + ^ + \endcode + + Note that the QtQuick 1 version is named QDeclarativeError + + \sa QQuickView::errors(), QmlComponent::errors() +*/ +class QmlErrorPrivate +{ +public: + QmlErrorPrivate(); + + QUrl url; + QString description; + int line; + int column; +}; + +QmlErrorPrivate::QmlErrorPrivate() +: line(-1), column(-1) +{ +} + +/*! + Creates an empty error object. +*/ +QmlError::QmlError() +: d(nullptr) +{ +} + +/*! + Creates a copy of \a other. +*/ +QmlError::QmlError(const QmlError &other) +: d(nullptr) +{ + *this = other; +} + +/*! + Assigns \a other to this error object. +*/ +QmlError &QmlError::operator=(const QmlError &other) +{ + if (!other.d) { + delete d; + d = nullptr; + } else { + if (!d) d = new QmlErrorPrivate; + d->url = other.d->url; + d->description = other.d->description; + d->line = other.d->line; + d->column = other.d->column; + } + return *this; +} + +/*! + \internal +*/ +QmlError::~QmlError() +{ + delete d; d = nullptr; +} + +/*! + Returns true if this error is valid, otherwise false. +*/ +bool QmlError::isValid() const +{ + return d != nullptr; +} + +/*! + Returns the url for the file that caused this error. +*/ +QUrl QmlError::url() const +{ + if (d) return d->url; + else return {}; +} + +/*! + Sets the \a url for the file that caused this error. +*/ +void QmlError::setUrl(const QUrl &url) +{ + if (!d) d = new QmlErrorPrivate; + d->url = url; +} + +/*! + Returns the error description. +*/ +QString QmlError::description() const +{ + if (d) return d->description; + else return {}; +} + +/*! + Sets the error \a description. +*/ +void QmlError::setDescription(const QString &description) +{ + if (!d) d = new QmlErrorPrivate; + d->description = description; +} + +/*! + Returns the error line number. +*/ +int QmlError::line() const +{ + if (d) return d->line; + else return -1; +} + +/*! + Sets the error \a line number. +*/ +void QmlError::setLine(int line) +{ + if (!d) d = new QmlErrorPrivate; + d->line = line; +} + +/*! + Returns the error column number. +*/ +int QmlError::column() const +{ + if (d) return d->column; + else return -1; +} + +/*! + Sets the error \a column number. +*/ +void QmlError::setColumn(int column) +{ + if (!d) d = new QmlErrorPrivate; + d->column = column; +} + +/*! + Returns the error as a human readable string. +*/ +QString QmlError::toString() const +{ + QString rv; + if (url().isEmpty()) { + rv = QStringLiteral(""); + } else if (line() != -1) { + rv = url().toString() + QLatin1Char(':') + QString::number(line()); + if (column() != -1) + rv += QLatin1Char(':') + QString::number(column()); + } else { + rv = url().toString(); + } + + rv += QLatin1String(": ") + description(); + + return rv; +} + +} // namespace QbsQmlJS + +QT_BEGIN_NAMESPACE + +using namespace QbsQmlJS; + +/*! + \relates QmlError + \fn QDebug operator<<(QDebug debug, const QmlError &error) + + Outputs a human readable version of \a error to \a debug. +*/ + +QDebug operator<<(QDebug debug, const QmlError &error) +{ + debug << qPrintable(error.toString()); + + QUrl url = error.url(); + + if (error.line() > 0 && url.scheme() == QLatin1String("file")) { + QString file = url.toLocalFile(); + QFile f(file); + if (f.open(QIODevice::ReadOnly)) { + QByteArray data = f.readAll(); + QTextStream stream(data, QIODevice::ReadOnly); +#ifndef QT_NO_TEXTCODEC + stream.setCodec("UTF-8"); +#endif + const QString code = stream.readAll(); + const QStringList lines = code.split(QLatin1Char('\n')); + + if (lines.size() >= error.line()) { + const QString &line = lines.at(error.line() - 1); + debug << "\n " << qPrintable(line); + + if (error.column() > 0) { + int column = std::max(0, error.column() - 1); + column = std::min(column, line.length()); + + QByteArray ind; + ind.reserve(column); + for (int i = 0; i < column; ++i) { + const QChar ch = line.at(i); + if (ch.isSpace()) + ind.append(ch.unicode()); + else + ind.append(' '); + } + ind.append('^'); + debug << "\n " << ind.constData(); + } + } + } + } + return debug; +} + +QT_END_NAMESPACE diff --git a/src/lib/corelib/parser/qmlerror.h b/src/lib/corelib/parser/qmlerror.h new file mode 100644 index 00000000..cfac506b --- /dev/null +++ b/src/lib/corelib/parser/qmlerror.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 QQMLERROR_H +#define QQMLERROR_H + + + +#include +#include + +QT_BEGIN_NAMESPACE +class QDebug; +QT_END_NAMESPACE + +namespace QbsQmlJS { + +class QmlErrorPrivate; +class QmlError +{ +public: + QmlError(); + QmlError(const QmlError &); + QmlError &operator=(const QmlError &); + ~QmlError(); + + bool isValid() const; + + QUrl url() const; + void setUrl(const QUrl &); + QString description() const; + void setDescription(const QString &); + int line() const; + void setLine(int); + int column() const; + void setColumn(int); + + QString toString() const; +private: + QmlErrorPrivate *d; +}; + +} // namespace QbsQmlJS + +QT_BEGIN_NAMESPACE +QDebug operator<<(QDebug debug, const QbsQmlJS::QmlError &error); +Q_DECLARE_TYPEINFO(QbsQmlJS::QmlError, Q_MOVABLE_TYPE); +QT_END_NAMESPACE + +#endif // QQMLERROR_H diff --git a/src/lib/corelib/parser/qmljs.g b/src/lib/corelib/parser/qmljs.g new file mode 100644 index 00000000..956c6580 --- /dev/null +++ b/src/lib/corelib/parser/qmljs.g @@ -0,0 +1,3011 @@ +----------------------------------------------------------------------------- +-- +-- 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$ +-- +----------------------------------------------------------------------------- + +%parser QmlJSGrammar +%decl qmljsparser_p.h +%impl qdeclarativejsparser.cpp +%expect 2 +%expect-rr 2 + +%token T_AND "&" T_AND_AND "&&" T_AND_EQ "&=" +%token T_BREAK "break" T_CASE "case" T_CATCH "catch" +%token T_COLON ":" T_COMMA "," T_CONTINUE "continue" +%token T_DEFAULT "default" T_DELETE "delete" T_DIVIDE_ "/" +%token T_DIVIDE_EQ "/=" T_DO "do" T_DOT "." +%token T_ELSE "else" T_EQ "=" T_EQ_EQ "==" +%token T_EQ_EQ_EQ "===" T_FINALLY "finally" T_FOR "for" +%token T_FUNCTION "function" T_GE ">=" T_GT ">" +%token T_GT_GT ">>" T_GT_GT_EQ ">>=" T_GT_GT_GT ">>>" +%token T_GT_GT_GT_EQ ">>>=" T_IDENTIFIER "identifier" T_IF "if" +%token T_IN "in" T_INSTANCEOF "instanceof" T_LBRACE "{" +%token T_LBRACKET "[" T_LE "<=" T_LPAREN "(" +%token T_LT "<" T_LT_LT "<<" T_LT_LT_EQ "<<=" +%token T_MINUS "-" T_MINUS_EQ "-=" T_MINUS_MINUS "--" +%token T_NEW "new" T_NOT "!" T_NOT_EQ "!=" +%token T_NOT_EQ_EQ "!==" T_NUMERIC_LITERAL "numeric literal" T_OR "|" +%token T_OR_EQ "|=" T_OR_OR "||" T_PLUS "+" +%token T_PLUS_EQ "+=" T_PLUS_PLUS "++" T_QUESTION "?" +%token T_RBRACE "}" T_RBRACKET "]" T_REMAINDER "%" +%token T_REMAINDER_EQ "%=" T_RETURN "return" T_RPAREN ")" +%token T_SEMICOLON ";" T_AUTOMATIC_SEMICOLON T_STAR "*" +%token T_STAR_EQ "*=" T_STRING_LITERAL "string literal" +%token T_PROPERTY "property" T_SIGNAL "signal" T_READONLY "readonly" +%token T_SWITCH "switch" T_THIS "this" T_THROW "throw" +%token T_TILDE "~" T_TRY "try" T_TYPEOF "typeof" +%token T_VAR "var" T_VOID "void" T_WHILE "while" +%token T_WITH "with" T_XOR "^" T_XOR_EQ "^=" +%token T_NULL "null" T_TRUE "true" T_FALSE "false" +%token T_CONST "const" +%token T_DEBUGGER "debugger" +%token T_RESERVED_WORD "reserved word" +%token T_MULTILINE_STRING_LITERAL "multiline string literal" +%token T_COMMENT "comment" + +--- context keywords. +%token T_PUBLIC "public" +%token T_IMPORT "import" +%token T_AS "as" +%token T_ON "on" + +%token T_ERROR + +--- feed tokens +%token T_FEED_UI_PROGRAM +%token T_FEED_UI_OBJECT_MEMBER +%token T_FEED_JS_STATEMENT +%token T_FEED_JS_EXPRESSION +%token T_FEED_JS_SOURCE_ELEMENT +%token T_FEED_JS_PROGRAM + +%nonassoc SHIFT_THERE +%nonassoc T_IDENTIFIER T_COLON T_SIGNAL T_PROPERTY T_READONLY +%nonassoc REDUCE_HERE + +%start TopLevel + +/./**************************************************************************** +** +** 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 +#include + +#include + +#include "qmljsengine_p.h" +#include "qmljslexer_p.h" +#include "qmljsast_p.h" +#include "qmljsmemorypool_p.h" + +./ + +/:/**************************************************************************** +** +** 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 QMLJSPARSER_P_H +#define QMLJSPARSER_P_H + +#include "qmljsglobal_p.h" +#include "qmljsgrammar_p.h" +#include "qmljsast_p.h" +#include "qmljsengine_p.h" + +#include +#include + +QT_QML_BEGIN_NAMESPACE + +namespace QmlJS { + +class Engine; + +class QML_PARSER_EXPORT Parser: protected $table +{ +public: + union Value { + int ival; + double dval; + AST::ArgumentList *ArgumentList; + AST::CaseBlock *CaseBlock; + AST::CaseClause *CaseClause; + AST::CaseClauses *CaseClauses; + AST::Catch *Catch; + AST::DefaultClause *DefaultClause; + AST::ElementList *ElementList; + AST::Elision *Elision; + AST::ExpressionNode *Expression; + AST::Finally *Finally; + AST::FormalParameterList *FormalParameterList; + AST::FunctionBody *FunctionBody; + AST::FunctionDeclaration *FunctionDeclaration; + AST::Node *Node; + AST::PropertyName *PropertyName; + AST::PropertyNameAndValueList *PropertyNameAndValueList; + AST::SourceElement *SourceElement; + AST::SourceElements *SourceElements; + AST::Statement *Statement; + AST::StatementList *StatementList; + AST::Block *Block; + AST::VariableDeclaration *VariableDeclaration; + AST::VariableDeclarationList *VariableDeclarationList; + + AST::UiProgram *UiProgram; + AST::UiImportList *UiImportList; + AST::UiImport *UiImport; + AST::UiParameterList *UiParameterList; + AST::UiPublicMember *UiPublicMember; + AST::UiObjectDefinition *UiObjectDefinition; + AST::UiObjectInitializer *UiObjectInitializer; + AST::UiObjectBinding *UiObjectBinding; + AST::UiScriptBinding *UiScriptBinding; + AST::UiArrayBinding *UiArrayBinding; + AST::UiObjectMember *UiObjectMember; + AST::UiObjectMemberList *UiObjectMemberList; + AST::UiArrayMemberList *UiArrayMemberList; + AST::UiQualifiedId *UiQualifiedId; + }; + +public: + Parser(Engine *engine); + ~Parser(); + + // parse a UI program + bool parse() { return parse(T_FEED_UI_PROGRAM); } + bool parseStatement() { return parse(T_FEED_JS_STATEMENT); } + bool parseExpression() { return parse(T_FEED_JS_EXPRESSION); } + bool parseSourceElement() { return parse(T_FEED_JS_SOURCE_ELEMENT); } + bool parseUiObjectMember() { return parse(T_FEED_UI_OBJECT_MEMBER); } + bool parseProgram() { return parse(T_FEED_JS_PROGRAM); } + + AST::UiProgram *ast() const + { return AST::cast(program); } + + AST::Statement *statement() const + { + if (! program) + return 0; + + return program->statementCast(); + } + + AST::ExpressionNode *expression() const + { + if (! program) + return 0; + + return program->expressionCast(); + } + + AST::UiObjectMember *uiObjectMember() const + { + if (! program) + return 0; + + return program->uiObjectMemberCast(); + } + + AST::Node *rootNode() const + { return program; } + + QList diagnosticMessages() const + { return diagnostic_messages; } + + inline DiagnosticMessage diagnosticMessage() const + { + foreach (const DiagnosticMessage &d, diagnostic_messages) { + if (d.kind != DiagnosticMessage::Warning) + return d; + } + + return DiagnosticMessage(); + } + + inline QString errorMessage() const + { return diagnosticMessage().message; } + + inline int errorLineNumber() const + { return diagnosticMessage().loc.startLine; } + + inline int errorColumnNumber() const + { return diagnosticMessage().loc.startColumn; } + +protected: + bool parse(int startToken); + + void reallocateStack(); + + inline Value &sym(int index) + { return sym_stack [tos + index - 1]; } + + inline QStringRef &stringRef(int index) + { return string_stack [tos + index - 1]; } + + inline AST::SourceLocation &loc(int index) + { return location_stack [tos + index - 1]; } + + AST::UiQualifiedId *reparseAsQualifiedId(AST::ExpressionNode *expr); + +protected: + Engine *driver; + MemoryPool *pool; + int tos; + int stack_size; + Value *sym_stack; + int *state_stack; + AST::SourceLocation *location_stack; + QStringRef *string_stack; + + AST::Node *program; + + // error recovery + enum { TOKEN_BUFFER_SIZE = 3 }; + + struct SavedToken { + int token; + double dval; + AST::SourceLocation loc; + QStringRef spell; + }; + + double yylval; + QStringRef yytokenspell; + AST::SourceLocation yylloc; + AST::SourceLocation yyprevlloc; + + SavedToken token_buffer[TOKEN_BUFFER_SIZE]; + SavedToken *first_token; + SavedToken *last_token; + + QList diagnostic_messages; +}; + +} // end of namespace QmlJS + + +:/ + + +/. + +#include "qmljsparser_p.h" +#include + +// +// This file is automatically generated from qmljs.g. +// Changes will be lost. +// + +using namespace QmlJS; + +QT_QML_BEGIN_NAMESPACE + +void Parser::reallocateStack() +{ + if (! stack_size) + stack_size = 128; + else + stack_size <<= 1; + + sym_stack = reinterpret_cast (realloc(sym_stack, stack_size * sizeof(Value))); + state_stack = reinterpret_cast (realloc(state_stack, stack_size * sizeof(int))); + location_stack = reinterpret_cast (realloc(location_stack, stack_size * sizeof(AST::SourceLocation))); + string_stack = reinterpret_cast (realloc(string_stack, stack_size * sizeof(QStringRef))); +} + +Parser::Parser(Engine *engine): + driver(engine), + pool(engine->pool()), + tos(0), + stack_size(0), + sym_stack(0), + state_stack(0), + location_stack(0), + string_stack(0), + first_token(0), + last_token(0) +{ +} + +Parser::~Parser() +{ + if (stack_size) { + free(sym_stack); + free(state_stack); + free(location_stack); + free(string_stack); + } +} + +static inline AST::SourceLocation location(Lexer *lexer) +{ + AST::SourceLocation loc; + loc.offset = lexer->tokenOffset(); + loc.length = lexer->tokenLength(); + loc.startLine = lexer->tokenStartLine(); + loc.startColumn = lexer->tokenStartColumn(); + return loc; +} + +AST::UiQualifiedId *Parser::reparseAsQualifiedId(AST::ExpressionNode *expr) +{ + QVarLengthArray nameIds; + QVarLengthArray locations; + + AST::ExpressionNode *it = expr; + while (AST::FieldMemberExpression *m = AST::cast(it)) { + nameIds.append(m->name); + locations.append(m->identifierToken); + it = m->base; + } + + if (AST::IdentifierExpression *idExpr = AST::cast(it)) { + AST::UiQualifiedId *q = new (pool) AST::UiQualifiedId(idExpr->name); + q->identifierToken = idExpr->identifierToken; + + AST::UiQualifiedId *currentId = q; + for (int i = nameIds.size() - 1; i != -1; --i) { + currentId = new (pool) AST::UiQualifiedId(currentId, nameIds[i]); + currentId->identifierToken = locations[i]; + } + + return currentId->finish(); + } + + return 0; +} + +bool Parser::parse(int startToken) +{ + Lexer *lexer = driver->lexer(); + bool hadErrors = false; + int yytoken = -1; + int action = 0; + + token_buffer[0].token = startToken; + first_token = &token_buffer[0]; + last_token = &token_buffer[1]; + + tos = -1; + program = 0; + + do { + if (++tos == stack_size) + reallocateStack(); + + state_stack[tos] = action; + + _Lcheck_token: + if (yytoken == -1 && -TERMINAL_COUNT != action_index[action]) { + yyprevlloc = yylloc; + + if (first_token == last_token) { + yytoken = lexer->lex(); + yylval = lexer->tokenValue(); + yytokenspell = lexer->tokenSpell(); + yylloc = location(lexer); + } else { + yytoken = first_token->token; + yylval = first_token->dval; + yytokenspell = first_token->spell; + yylloc = first_token->loc; + ++first_token; + } + } + + action = t_action(action, yytoken); + if (action > 0) { + if (action != ACCEPT_STATE) { + yytoken = -1; + sym(1).dval = yylval; + stringRef(1) = yytokenspell; + loc(1) = yylloc; + } else { + --tos; + return ! hadErrors; + } + } else if (action < 0) { + const int r = -action - 1; + tos -= rhs[r]; + + switch (r) { +./ + +-------------------------------------------------------------------------------------------------------- +-- Declarative UI +-------------------------------------------------------------------------------------------------------- + +TopLevel: T_FEED_UI_PROGRAM UiProgram ; +/. +case $rule_number: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; +./ + +TopLevel: T_FEED_JS_STATEMENT Statement ; +/. +case $rule_number: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; +./ + +TopLevel: T_FEED_JS_EXPRESSION Expression ; +/. +case $rule_number: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; +./ + +TopLevel: T_FEED_JS_SOURCE_ELEMENT SourceElement ; +/. +case $rule_number: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; +./ + +TopLevel: T_FEED_UI_OBJECT_MEMBER UiObjectMember ; +/. +case $rule_number: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; +./ + +TopLevel: T_FEED_JS_PROGRAM Program ; +/. +case $rule_number: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; +./ + +UiProgram: UiImportListOpt UiRootMember ; +/. +case $rule_number: { + sym(1).UiProgram = new (pool) AST::UiProgram(sym(1).UiImportList, + sym(2).UiObjectMemberList->finish()); +} break; +./ + +UiImportListOpt: Empty ; +UiImportListOpt: UiImportList ; +/. +case $rule_number: { + sym(1).Node = sym(1).UiImportList->finish(); +} break; +./ + +UiImportList: UiImport ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiImportList(sym(1).UiImport); +} break; +./ + +UiImportList: UiImportList UiImport ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiImportList(sym(1).UiImportList, sym(2).UiImport); +} break; +./ + +ImportId: MemberExpression ; + +UiImport: UiImportHead T_AUTOMATIC_SEMICOLON ; +UiImport: UiImportHead T_SEMICOLON ; +/. +case $rule_number: { + sym(1).UiImport->semicolonToken = loc(2); +} break; +./ + +UiImport: UiImportHead T_NUMERIC_LITERAL T_AUTOMATIC_SEMICOLON ; +UiImport: UiImportHead T_NUMERIC_LITERAL T_SEMICOLON ; +/. +case $rule_number: { + sym(1).UiImport->versionToken = loc(2); + sym(1).UiImport->semicolonToken = loc(3); +} break; +./ + +UiImport: UiImportHead T_NUMERIC_LITERAL T_AS JsIdentifier T_AUTOMATIC_SEMICOLON ; +UiImport: UiImportHead T_NUMERIC_LITERAL T_AS JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + sym(1).UiImport->versionToken = loc(2); + sym(1).UiImport->asToken = loc(3); + sym(1).UiImport->importIdToken = loc(4); + sym(1).UiImport->importId = stringRef(4); + sym(1).UiImport->semicolonToken = loc(5); +} break; +./ + +UiImport: UiImportHead T_AS JsIdentifier T_AUTOMATIC_SEMICOLON ; +UiImport: UiImportHead T_AS JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + sym(1).UiImport->asToken = loc(2); + sym(1).UiImport->importIdToken = loc(3); + sym(1).UiImport->importId = stringRef(3); + sym(1).UiImport->semicolonToken = loc(4); +} break; +./ + + +UiImportHead: T_IMPORT ImportId ; +/. +case $rule_number: { + AST::UiImport *node = 0; + + if (AST::StringLiteral *importIdLiteral = AST::cast(sym(2).Expression)) { + node = new (pool) AST::UiImport(importIdLiteral->value); + node->fileNameToken = loc(2); + } else if (AST::UiQualifiedId *qualifiedId = reparseAsQualifiedId(sym(2).Expression)) { + node = new (pool) AST::UiImport(qualifiedId); + node->fileNameToken = loc(2); + } + + sym(1).Node = node; + + if (node) { + node->importToken = loc(1); + } else { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, loc(1), + QLatin1String("Expected a qualified name id or a string literal"))); + + return false; // ### remove me + } +} break; +./ + +Empty: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +UiRootMember: UiObjectDefinition ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiObjectMemberList(sym(1).UiObjectMember); +} break; +./ + +UiObjectMemberList: UiObjectMember ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiObjectMemberList(sym(1).UiObjectMember); +} break; +./ + +UiObjectMemberList: UiObjectMemberList UiObjectMember ; +/. +case $rule_number: { + AST::UiObjectMemberList *node = new (pool) AST:: UiObjectMemberList( + sym(1).UiObjectMemberList, sym(2).UiObjectMember); + sym(1).Node = node; +} break; +./ + +UiArrayMemberList: UiObjectDefinition ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiArrayMemberList(sym(1).UiObjectMember); +} break; +./ + +UiArrayMemberList: UiArrayMemberList T_COMMA UiObjectDefinition ; +/. +case $rule_number: { + AST::UiArrayMemberList *node = new (pool) AST::UiArrayMemberList( + sym(1).UiArrayMemberList, sym(3).UiObjectMember); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +UiObjectInitializer: T_LBRACE T_RBRACE ; +/. +case $rule_number: { + AST::UiObjectInitializer *node = new (pool) AST::UiObjectInitializer((AST::UiObjectMemberList*)0); + node->lbraceToken = loc(1); + node->rbraceToken = loc(2); + sym(1).Node = node; +} break; +./ + +UiObjectInitializer: T_LBRACE UiObjectMemberList T_RBRACE ; +/. +case $rule_number: { + AST::UiObjectInitializer *node = new (pool) AST::UiObjectInitializer(sym(2).UiObjectMemberList->finish()); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; +./ + +UiObjectDefinition: UiQualifiedId UiObjectInitializer ; +/. +case $rule_number: { + AST::UiObjectDefinition *node = new (pool) AST::UiObjectDefinition(sym(1).UiQualifiedId, + sym(2).UiObjectInitializer); + sym(1).Node = node; +} break; +./ + +UiObjectMember: UiObjectDefinition ; + +UiObjectMember: UiQualifiedId T_COLON T_LBRACKET UiArrayMemberList T_RBRACKET ; +/. +case $rule_number: { + AST::UiArrayBinding *node = new (pool) AST::UiArrayBinding( + sym(1).UiQualifiedId, sym(4).UiArrayMemberList->finish()); + node->colonToken = loc(2); + node->lbracketToken = loc(3); + node->rbracketToken = loc(5); + sym(1).Node = node; +} break; +./ + +UiObjectMember: UiQualifiedId T_COLON UiQualifiedId UiObjectInitializer ; +/. +case $rule_number: { + AST::UiObjectBinding *node = new (pool) AST::UiObjectBinding( + sym(1).UiQualifiedId, sym(3).UiQualifiedId, sym(4).UiObjectInitializer); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +./ + +UiObjectMember: UiQualifiedId T_ON UiQualifiedId UiObjectInitializer ; +/. +case $rule_number: { + AST::UiObjectBinding *node = new (pool) AST::UiObjectBinding( + sym(3).UiQualifiedId, sym(1).UiQualifiedId, sym(4).UiObjectInitializer); + node->colonToken = loc(2); + node->hasOnToken = true; + sym(1).Node = node; +} break; +./ + +UiScriptStatement: Block ; +UiScriptStatement: EmptyStatement ; +UiScriptStatement: ExpressionStatement ; +UiScriptStatement: IfStatement ; +UiScriptStatement: WithStatement ; +UiScriptStatement: SwitchStatement ; +UiScriptStatement: TryStatement ; + +UiObjectMember: UiQualifiedId T_COLON UiScriptStatement ; +/. +case $rule_number: +{ + AST::UiScriptBinding *node = new (pool) AST::UiScriptBinding( + sym(1).UiQualifiedId, sym(3).Statement); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +./ + +UiPropertyType: T_VAR ; +UiPropertyType: T_RESERVED_WORD ; +UiPropertyType: T_IDENTIFIER ; + +UiParameterListOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +UiParameterListOpt: UiParameterList ; +/. +case $rule_number: { + sym(1).Node = sym(1).UiParameterList->finish (); +} break; +./ + +UiParameterList: UiPropertyType JsIdentifier ; +/. +case $rule_number: { + AST::UiParameterList *node = new (pool) AST::UiParameterList(stringRef(1), stringRef(2)); + node->propertyTypeToken = loc(1); + node->identifierToken = loc(2); + sym(1).Node = node; +} break; +./ + +UiParameterList: UiParameterList T_COMMA UiPropertyType JsIdentifier ; +/. +case $rule_number: { + AST::UiParameterList *node = new (pool) AST::UiParameterList(sym(1).UiParameterList, stringRef(3), stringRef(4)); + node->commaToken = loc(2); + node->identifierToken = loc(4); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_SIGNAL T_IDENTIFIER T_LPAREN UiParameterListOpt T_RPAREN T_AUTOMATIC_SEMICOLON ; +UiObjectMember: T_SIGNAL T_IDENTIFIER T_LPAREN UiParameterListOpt T_RPAREN T_SEMICOLON ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(QStringRef(), stringRef(2)); + node->type = AST::UiPublicMember::Signal; + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(2); + node->parameters = sym(4).UiParameterList; + node->semicolonToken = loc(6); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_SIGNAL T_IDENTIFIER T_AUTOMATIC_SEMICOLON ; +UiObjectMember: T_SIGNAL T_IDENTIFIER T_SEMICOLON ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(QStringRef(), stringRef(2)); + node->type = AST::UiPublicMember::Signal; + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(2); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT JsIdentifier T_AUTOMATIC_SEMICOLON ; +UiObjectMember: T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(4), stringRef(6)); + node->typeModifier = stringRef(2); + node->propertyToken = loc(1); + node->typeModifierToken = loc(2); + node->typeToken = loc(4); + node->identifierToken = loc(6); + node->semicolonToken = loc(7); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_PROPERTY UiPropertyType JsIdentifier T_AUTOMATIC_SEMICOLON ; +UiObjectMember: T_PROPERTY UiPropertyType JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(2), stringRef(3)); + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(3); + node->semicolonToken = loc(4); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_DEFAULT T_PROPERTY UiPropertyType JsIdentifier T_AUTOMATIC_SEMICOLON ; +UiObjectMember: T_DEFAULT T_PROPERTY UiPropertyType JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(3), stringRef(4)); + node->isDefaultMember = true; + node->defaultToken = loc(1); + node->propertyToken = loc(2); + node->typeToken = loc(3); + node->identifierToken = loc(4); + node->semicolonToken = loc(5); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_PROPERTY UiPropertyType JsIdentifier T_COLON UiScriptStatement ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(2), stringRef(3), + sym(5).Statement); + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(3); + node->colonToken = loc(4); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_READONLY T_PROPERTY UiPropertyType JsIdentifier T_COLON UiScriptStatement ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(3), stringRef(4), + sym(6).Statement); + node->isReadonlyMember = true; + node->readonlyToken = loc(1); + node->propertyToken = loc(2); + node->typeToken = loc(3); + node->identifierToken = loc(4); + node->colonToken = loc(5); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_DEFAULT T_PROPERTY UiPropertyType JsIdentifier T_COLON UiScriptStatement ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(3), stringRef(4), + sym(6).Statement); + node->isDefaultMember = true; + node->defaultToken = loc(1); + node->propertyToken = loc(2); + node->typeToken = loc(3); + node->identifierToken = loc(4); + node->colonToken = loc(5); + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_PROPERTY T_IDENTIFIER T_LT UiPropertyType T_GT JsIdentifier T_COLON T_LBRACKET UiArrayMemberList T_RBRACKET ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(4), stringRef(6)); + node->typeModifier = stringRef(2); + node->propertyToken = loc(1); + node->typeModifierToken = loc(2); + node->typeToken = loc(4); + node->identifierToken = loc(6); + node->semicolonToken = loc(7); // insert a fake ';' before ':' + + AST::UiQualifiedId *propertyName = new (pool) AST::UiQualifiedId(stringRef(6)); + propertyName->identifierToken = loc(6); + propertyName->next = 0; + + AST::UiArrayBinding *binding = new (pool) AST::UiArrayBinding( + propertyName, sym(9).UiArrayMemberList->finish()); + binding->colonToken = loc(7); + binding->lbracketToken = loc(8); + binding->rbracketToken = loc(10); + + node->binding = binding; + + sym(1).Node = node; +} break; +./ + +UiObjectMember: T_PROPERTY UiPropertyType JsIdentifier T_COLON UiQualifiedId UiObjectInitializer ; +/. +case $rule_number: { + AST::UiPublicMember *node = new (pool) AST::UiPublicMember(stringRef(2), stringRef(3)); + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(3); + node->semicolonToken = loc(4); // insert a fake ';' before ':' + + AST::UiQualifiedId *propertyName = new (pool) AST::UiQualifiedId(stringRef(3)); + propertyName->identifierToken = loc(3); + propertyName->next = 0; + + AST::UiObjectBinding *binding = new (pool) AST::UiObjectBinding( + propertyName, sym(5).UiQualifiedId, sym(6).UiObjectInitializer); + binding->colonToken = loc(4); + + node->binding = binding; + + sym(1).Node = node; +} break; +./ + +UiObjectMember: FunctionDeclaration ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiSourceElement(sym(1).Node); +} break; +./ + +UiObjectMember: VariableStatement ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::UiSourceElement(sym(1).Node); +} break; +./ + +JsIdentifier: T_IDENTIFIER; + +JsIdentifier: T_PROPERTY ; +JsIdentifier: T_SIGNAL ; +JsIdentifier: T_READONLY ; +JsIdentifier: T_ON ; + +-------------------------------------------------------------------------------------------------------- +-- Expressions +-------------------------------------------------------------------------------------------------------- + +PrimaryExpression: T_THIS ; +/. +case $rule_number: { + AST::ThisExpression *node = new (pool) AST::ThisExpression(); + node->thisToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: JsIdentifier ; +/. +case $rule_number: { + AST::IdentifierExpression *node = new (pool) AST::IdentifierExpression(stringRef(1)); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_NULL ; +/. +case $rule_number: { + AST::NullExpression *node = new (pool) AST::NullExpression(); + node->nullToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_TRUE ; +/. +case $rule_number: { + AST::TrueLiteral *node = new (pool) AST::TrueLiteral(); + node->trueToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_FALSE ; +/. +case $rule_number: { + AST::FalseLiteral *node = new (pool) AST::FalseLiteral(); + node->falseToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_NUMERIC_LITERAL ; +/. +case $rule_number: { + AST::NumericLiteral *node = new (pool) AST::NumericLiteral(sym(1).dval); + node->literalToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_MULTILINE_STRING_LITERAL ; +/.case $rule_number:./ + +PrimaryExpression: T_STRING_LITERAL ; +/. +case $rule_number: { + AST::StringLiteral *node = new (pool) AST::StringLiteral(stringRef(1)); + node->literalToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_DIVIDE_ ; +/: +#define J_SCRIPT_REGEXPLITERAL_RULE1 $rule_number +:/ +/. +case $rule_number: { + bool rx = lexer->scanRegExp(Lexer::NoPrefix); + if (!rx) { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, location(lexer), lexer->errorMessage())); + return false; // ### remove me + } + + loc(1).length = lexer->tokenLength(); + yylloc = loc(1); // adjust the location of the current token + + AST::RegExpLiteral *node = new (pool) AST::RegExpLiteral( + driver->newStringRef(lexer->regExpPattern()), lexer->regExpFlags()); + node->literalToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_DIVIDE_EQ ; +/: +#define J_SCRIPT_REGEXPLITERAL_RULE2 $rule_number +:/ +/. +case $rule_number: { + bool rx = lexer->scanRegExp(Lexer::EqualPrefix); + if (!rx) { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, location(lexer), lexer->errorMessage())); + return false; + } + + loc(1).length = lexer->tokenLength(); + yylloc = loc(1); // adjust the location of the current token + + AST::RegExpLiteral *node = new (pool) AST::RegExpLiteral( + driver->newStringRef(lexer->regExpPattern()), lexer->regExpFlags()); + node->literalToken = loc(1); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LBRACKET T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral((AST::Elision *) 0); + node->lbracketToken = loc(1); + node->rbracketToken = loc(2); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LBRACKET Elision T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral(sym(2).Elision->finish()); + node->lbracketToken = loc(1); + node->rbracketToken = loc(3); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LBRACKET ElementList T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish ()); + node->lbracketToken = loc(1); + node->rbracketToken = loc(3); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LBRACKET ElementList T_COMMA T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish (), + (AST::Elision *) 0); + node->lbracketToken = loc(1); + node->commaToken = loc(3); + node->rbracketToken = loc(4); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LBRACKET ElementList T_COMMA Elision T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayLiteral *node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish (), + sym(4).Elision->finish()); + node->lbracketToken = loc(1); + node->commaToken = loc(3); + node->rbracketToken = loc(5); + sym(1).Node = node; +} break; +./ + +-- PrimaryExpression: T_LBRACE T_RBRACE ; +-- /. +-- case $rule_number: { +-- sym(1).Node = new (pool) AST::ObjectLiteral(); +-- } break; +-- ./ + +PrimaryExpression: T_LBRACE PropertyNameAndValueListOpt T_RBRACE ; +/. +case $rule_number: { + AST::ObjectLiteral *node = 0; + if (sym(2).Node) + node = new (pool) AST::ObjectLiteral( + sym(2).PropertyNameAndValueList->finish ()); + else + node = new (pool) AST::ObjectLiteral(); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LBRACE PropertyNameAndValueList T_COMMA T_RBRACE ; +/. +case $rule_number: { + AST::ObjectLiteral *node = new (pool) AST::ObjectLiteral( + sym(2).PropertyNameAndValueList->finish ()); + node->lbraceToken = loc(1); + node->rbraceToken = loc(4); + sym(1).Node = node; +} break; +./ + +PrimaryExpression: T_LPAREN Expression T_RPAREN ; +/. +case $rule_number: { + AST::NestedExpression *node = new (pool) AST::NestedExpression(sym(2).Expression); + node->lparenToken = loc(1); + node->rparenToken = loc(3); + sym(1).Node = node; +} break; +./ + +UiQualifiedId: MemberExpression ; +/. +case $rule_number: { + if (AST::ArrayMemberExpression *mem = AST::cast(sym(1).Expression)) { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Warning, mem->lbracketToken, + QLatin1String("Ignored annotation"))); + + sym(1).Expression = mem->base; + } + + if (AST::UiQualifiedId *qualifiedId = reparseAsQualifiedId(sym(1).Expression)) { + sym(1).UiQualifiedId = qualifiedId; + } else { + sym(1).UiQualifiedId = 0; + + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, loc(1), + QLatin1String("Expected a qualified name id"))); + + return false; // ### recover + } +} break; +./ + +ElementList: AssignmentExpression ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::ElementList((AST::Elision *) 0, sym(1).Expression); +} break; +./ + +ElementList: Elision AssignmentExpression ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::ElementList(sym(1).Elision->finish(), sym(2).Expression); +} break; +./ + +ElementList: ElementList T_COMMA AssignmentExpression ; +/. +case $rule_number: { + AST::ElementList *node = new (pool) AST::ElementList(sym(1).ElementList, + (AST::Elision *) 0, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +ElementList: ElementList T_COMMA Elision AssignmentExpression ; +/. +case $rule_number: { + AST::ElementList *node = new (pool) AST::ElementList(sym(1).ElementList, sym(3).Elision->finish(), + sym(4).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +Elision: T_COMMA ; +/. +case $rule_number: { + AST::Elision *node = new (pool) AST::Elision(); + node->commaToken = loc(1); + sym(1).Node = node; +} break; +./ + +Elision: Elision T_COMMA ; +/. +case $rule_number: { + AST::Elision *node = new (pool) AST::Elision(sym(1).Elision); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +PropertyNameAndValueList: PropertyName T_COLON AssignmentExpression ; +/. +case $rule_number: { + AST::PropertyNameAndValueList *node = new (pool) AST::PropertyNameAndValueList( + sym(1).PropertyName, sym(3).Expression); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +./ + +PropertyNameAndValueList: PropertyNameAndValueList T_COMMA PropertyName T_COLON AssignmentExpression ; +/. +case $rule_number: { + AST::PropertyNameAndValueList *node = new (pool) AST::PropertyNameAndValueList( + sym(1).PropertyNameAndValueList, sym(3).PropertyName, sym(5).Expression); + node->commaToken = loc(2); + node->colonToken = loc(4); + sym(1).Node = node; +} break; +./ + +PropertyName: T_IDENTIFIER %prec SHIFT_THERE ; +/. +case $rule_number: { + AST::IdentifierPropertyName *node = new (pool) AST::IdentifierPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; +./ + +PropertyName: T_SIGNAL ; +/.case $rule_number:./ + +PropertyName: T_PROPERTY ; +/. +case $rule_number: { + AST::IdentifierPropertyName *node = new (pool) AST::IdentifierPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; +./ + +PropertyName: T_STRING_LITERAL ; +/. +case $rule_number: { + AST::StringLiteralPropertyName *node = new (pool) AST::StringLiteralPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; +./ + +PropertyName: T_NUMERIC_LITERAL ; +/. +case $rule_number: { + AST::NumericLiteralPropertyName *node = new (pool) AST::NumericLiteralPropertyName(sym(1).dval); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; +./ + +PropertyName: ReservedIdentifier ; +/. +case $rule_number: { + AST::IdentifierPropertyName *node = new (pool) AST::IdentifierPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; +./ + +ReservedIdentifier: T_BREAK ; +ReservedIdentifier: T_CASE ; +ReservedIdentifier: T_CATCH ; +ReservedIdentifier: T_CONTINUE ; +ReservedIdentifier: T_DEFAULT ; +ReservedIdentifier: T_DELETE ; +ReservedIdentifier: T_DO ; +ReservedIdentifier: T_ELSE ; +ReservedIdentifier: T_FALSE ; +ReservedIdentifier: T_FINALLY ; +ReservedIdentifier: T_FOR ; +ReservedIdentifier: T_FUNCTION ; +ReservedIdentifier: T_IF ; +ReservedIdentifier: T_IN ; +ReservedIdentifier: T_INSTANCEOF ; +ReservedIdentifier: T_NEW ; +ReservedIdentifier: T_NULL ; +ReservedIdentifier: T_RETURN ; +ReservedIdentifier: T_SWITCH ; +ReservedIdentifier: T_THIS ; +ReservedIdentifier: T_THROW ; +ReservedIdentifier: T_TRUE ; +ReservedIdentifier: T_TRY ; +ReservedIdentifier: T_TYPEOF ; +ReservedIdentifier: T_VAR ; +ReservedIdentifier: T_VOID ; +ReservedIdentifier: T_WHILE ; +ReservedIdentifier: T_CONST ; +ReservedIdentifier: T_DEBUGGER ; +ReservedIdentifier: T_RESERVED_WORD ; +ReservedIdentifier: T_WITH ; + +PropertyIdentifier: JsIdentifier ; +PropertyIdentifier: ReservedIdentifier ; + +MemberExpression: PrimaryExpression ; +MemberExpression: FunctionExpression ; + +MemberExpression: MemberExpression T_LBRACKET Expression T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayMemberExpression *node = new (pool) AST::ArrayMemberExpression(sym(1).Expression, sym(3).Expression); + node->lbracketToken = loc(2); + node->rbracketToken = loc(4); + sym(1).Node = node; +} break; +./ + +MemberExpression: MemberExpression T_DOT PropertyIdentifier ; +/. +case $rule_number: { + AST::FieldMemberExpression *node = new (pool) AST::FieldMemberExpression(sym(1).Expression, stringRef(3)); + node->dotToken = loc(2); + node->identifierToken = loc(3); + sym(1).Node = node; +} break; +./ + +MemberExpression: T_NEW MemberExpression T_LPAREN ArgumentListOpt T_RPAREN ; +/. +case $rule_number: { + AST::NewMemberExpression *node = new (pool) AST::NewMemberExpression(sym(2).Expression, sym(4).ArgumentList); + node->newToken = loc(1); + node->lparenToken = loc(3); + node->rparenToken = loc(5); + sym(1).Node = node; +} break; +./ + +NewExpression: MemberExpression ; + +NewExpression: T_NEW NewExpression ; +/. +case $rule_number: { + AST::NewExpression *node = new (pool) AST::NewExpression(sym(2).Expression); + node->newToken = loc(1); + sym(1).Node = node; +} break; +./ + +CallExpression: MemberExpression T_LPAREN ArgumentListOpt T_RPAREN ; +/. +case $rule_number: { + AST::CallExpression *node = new (pool) AST::CallExpression(sym(1).Expression, sym(3).ArgumentList); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + +CallExpression: CallExpression T_LPAREN ArgumentListOpt T_RPAREN ; +/. +case $rule_number: { + AST::CallExpression *node = new (pool) AST::CallExpression(sym(1).Expression, sym(3).ArgumentList); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + +CallExpression: CallExpression T_LBRACKET Expression T_RBRACKET ; +/. +case $rule_number: { + AST::ArrayMemberExpression *node = new (pool) AST::ArrayMemberExpression(sym(1).Expression, sym(3).Expression); + node->lbracketToken = loc(2); + node->rbracketToken = loc(4); + sym(1).Node = node; +} break; +./ + +CallExpression: CallExpression T_DOT PropertyIdentifier ; +/. +case $rule_number: { + AST::FieldMemberExpression *node = new (pool) AST::FieldMemberExpression(sym(1).Expression, stringRef(3)); + node->dotToken = loc(2); + node->identifierToken = loc(3); + sym(1).Node = node; +} break; +./ + +ArgumentListOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +ArgumentListOpt: ArgumentList ; +/. +case $rule_number: { + sym(1).Node = sym(1).ArgumentList->finish(); +} break; +./ + +ArgumentList: AssignmentExpression ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::ArgumentList(sym(1).Expression); +} break; +./ + +ArgumentList: ArgumentList T_COMMA AssignmentExpression ; +/. +case $rule_number: { + AST::ArgumentList *node = new (pool) AST::ArgumentList(sym(1).ArgumentList, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +LeftHandSideExpression: NewExpression ; +LeftHandSideExpression: CallExpression ; +PostfixExpression: LeftHandSideExpression ; + +PostfixExpression: LeftHandSideExpression T_PLUS_PLUS ; +/. +case $rule_number: { + AST::PostIncrementExpression *node = new (pool) AST::PostIncrementExpression(sym(1).Expression); + node->incrementToken = loc(2); + sym(1).Node = node; +} break; +./ + +PostfixExpression: LeftHandSideExpression T_MINUS_MINUS ; +/. +case $rule_number: { + AST::PostDecrementExpression *node = new (pool) AST::PostDecrementExpression(sym(1).Expression); + node->decrementToken = loc(2); + sym(1).Node = node; +} break; +./ + +UnaryExpression: PostfixExpression ; + +UnaryExpression: T_DELETE UnaryExpression ; +/. +case $rule_number: { + AST::DeleteExpression *node = new (pool) AST::DeleteExpression(sym(2).Expression); + node->deleteToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_VOID UnaryExpression ; +/. +case $rule_number: { + AST::VoidExpression *node = new (pool) AST::VoidExpression(sym(2).Expression); + node->voidToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_TYPEOF UnaryExpression ; +/. +case $rule_number: { + AST::TypeOfExpression *node = new (pool) AST::TypeOfExpression(sym(2).Expression); + node->typeofToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_PLUS_PLUS UnaryExpression ; +/. +case $rule_number: { + AST::PreIncrementExpression *node = new (pool) AST::PreIncrementExpression(sym(2).Expression); + node->incrementToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_MINUS_MINUS UnaryExpression ; +/. +case $rule_number: { + AST::PreDecrementExpression *node = new (pool) AST::PreDecrementExpression(sym(2).Expression); + node->decrementToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_PLUS UnaryExpression ; +/. +case $rule_number: { + AST::UnaryPlusExpression *node = new (pool) AST::UnaryPlusExpression(sym(2).Expression); + node->plusToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_MINUS UnaryExpression ; +/. +case $rule_number: { + AST::UnaryMinusExpression *node = new (pool) AST::UnaryMinusExpression(sym(2).Expression); + node->minusToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_TILDE UnaryExpression ; +/. +case $rule_number: { + AST::TildeExpression *node = new (pool) AST::TildeExpression(sym(2).Expression); + node->tildeToken = loc(1); + sym(1).Node = node; +} break; +./ + +UnaryExpression: T_NOT UnaryExpression ; +/. +case $rule_number: { + AST::NotExpression *node = new (pool) AST::NotExpression(sym(2).Expression); + node->notToken = loc(1); + sym(1).Node = node; +} break; +./ + +MultiplicativeExpression: UnaryExpression ; + +MultiplicativeExpression: MultiplicativeExpression T_STAR UnaryExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Mul, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +MultiplicativeExpression: MultiplicativeExpression T_DIVIDE_ UnaryExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Div, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +MultiplicativeExpression: MultiplicativeExpression T_REMAINDER UnaryExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Mod, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +AdditiveExpression: MultiplicativeExpression ; + +AdditiveExpression: AdditiveExpression T_PLUS MultiplicativeExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Add, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +AdditiveExpression: AdditiveExpression T_MINUS MultiplicativeExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Sub, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +ShiftExpression: AdditiveExpression ; + +ShiftExpression: ShiftExpression T_LT_LT AdditiveExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::LShift, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +ShiftExpression: ShiftExpression T_GT_GT AdditiveExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::RShift, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +ShiftExpression: ShiftExpression T_GT_GT_GT AdditiveExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::URShift, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpression: ShiftExpression ; + +RelationalExpression: RelationalExpression T_LT ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Lt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpression: RelationalExpression T_GT ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Gt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpression: RelationalExpression T_LE ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Le, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpression: RelationalExpression T_GE ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Ge, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpression: RelationalExpression T_INSTANCEOF ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::InstanceOf, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpression: RelationalExpression T_IN ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::In, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpressionNotIn: ShiftExpression ; + +RelationalExpressionNotIn: RelationalExpressionNotIn T_LT ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Lt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpressionNotIn: RelationalExpressionNotIn T_GT ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Gt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpressionNotIn: RelationalExpressionNotIn T_LE ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Le, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpressionNotIn: RelationalExpressionNotIn T_GE ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Ge, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +RelationalExpressionNotIn: RelationalExpressionNotIn T_INSTANCEOF ShiftExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::InstanceOf, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpression: RelationalExpression ; + +EqualityExpression: EqualityExpression T_EQ_EQ RelationalExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Equal, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpression: EqualityExpression T_NOT_EQ RelationalExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::NotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpression: EqualityExpression T_EQ_EQ_EQ RelationalExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpression: EqualityExpression T_NOT_EQ_EQ RelationalExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictNotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpressionNotIn: RelationalExpressionNotIn ; + +EqualityExpressionNotIn: EqualityExpressionNotIn T_EQ_EQ RelationalExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Equal, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpressionNotIn: EqualityExpressionNotIn T_NOT_EQ RelationalExpressionNotIn; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::NotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpressionNotIn: EqualityExpressionNotIn T_EQ_EQ_EQ RelationalExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +EqualityExpressionNotIn: EqualityExpressionNotIn T_NOT_EQ_EQ RelationalExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictNotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +BitwiseANDExpression: EqualityExpression ; + +BitwiseANDExpression: BitwiseANDExpression T_AND EqualityExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitAnd, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +BitwiseANDExpressionNotIn: EqualityExpressionNotIn ; + +BitwiseANDExpressionNotIn: BitwiseANDExpressionNotIn T_AND EqualityExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitAnd, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +BitwiseXORExpression: BitwiseANDExpression ; + +BitwiseXORExpression: BitwiseXORExpression T_XOR BitwiseANDExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitXor, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +BitwiseXORExpressionNotIn: BitwiseANDExpressionNotIn ; + +BitwiseXORExpressionNotIn: BitwiseXORExpressionNotIn T_XOR BitwiseANDExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitXor, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +BitwiseORExpression: BitwiseXORExpression ; + +BitwiseORExpression: BitwiseORExpression T_OR BitwiseXORExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitOr, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +BitwiseORExpressionNotIn: BitwiseXORExpressionNotIn ; + +BitwiseORExpressionNotIn: BitwiseORExpressionNotIn T_OR BitwiseXORExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitOr, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +LogicalANDExpression: BitwiseORExpression ; + +LogicalANDExpression: LogicalANDExpression T_AND_AND BitwiseORExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::And, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +LogicalANDExpressionNotIn: BitwiseORExpressionNotIn ; + +LogicalANDExpressionNotIn: LogicalANDExpressionNotIn T_AND_AND BitwiseORExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::And, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +LogicalORExpression: LogicalANDExpression ; + +LogicalORExpression: LogicalORExpression T_OR_OR LogicalANDExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Or, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +LogicalORExpressionNotIn: LogicalANDExpressionNotIn ; + +LogicalORExpressionNotIn: LogicalORExpressionNotIn T_OR_OR LogicalANDExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Or, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +ConditionalExpression: LogicalORExpression ; + +ConditionalExpression: LogicalORExpression T_QUESTION AssignmentExpression T_COLON AssignmentExpression ; +/. +case $rule_number: { + AST::ConditionalExpression *node = new (pool) AST::ConditionalExpression(sym(1).Expression, + sym(3).Expression, sym(5).Expression); + node->questionToken = loc(2); + node->colonToken = loc(4); + sym(1).Node = node; +} break; +./ + +ConditionalExpressionNotIn: LogicalORExpressionNotIn ; + +ConditionalExpressionNotIn: LogicalORExpressionNotIn T_QUESTION AssignmentExpressionNotIn T_COLON AssignmentExpressionNotIn ; +/. +case $rule_number: { + AST::ConditionalExpression *node = new (pool) AST::ConditionalExpression(sym(1).Expression, + sym(3).Expression, sym(5).Expression); + node->questionToken = loc(2); + node->colonToken = loc(4); + sym(1).Node = node; +} break; +./ + +AssignmentExpression: ConditionalExpression ; + +AssignmentExpression: LeftHandSideExpression AssignmentOperator AssignmentExpression ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + sym(2).ival, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +AssignmentExpressionNotIn: ConditionalExpressionNotIn ; + +AssignmentExpressionNotIn: LeftHandSideExpression AssignmentOperator AssignmentExpressionNotIn ; +/. +case $rule_number: { + AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, + sym(2).ival, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; +./ + +AssignmentOperator: T_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::Assign; +} break; +./ + +AssignmentOperator: T_STAR_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceMul; +} break; +./ + +AssignmentOperator: T_DIVIDE_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceDiv; +} break; +./ + +AssignmentOperator: T_REMAINDER_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceMod; +} break; +./ + +AssignmentOperator: T_PLUS_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceAdd; +} break; +./ + +AssignmentOperator: T_MINUS_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceSub; +} break; +./ + +AssignmentOperator: T_LT_LT_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceLeftShift; +} break; +./ + +AssignmentOperator: T_GT_GT_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceRightShift; +} break; +./ + +AssignmentOperator: T_GT_GT_GT_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceURightShift; +} break; +./ + +AssignmentOperator: T_AND_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceAnd; +} break; +./ + +AssignmentOperator: T_XOR_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceXor; +} break; +./ + +AssignmentOperator: T_OR_EQ ; +/. +case $rule_number: { + sym(1).ival = QSOperator::InplaceOr; +} break; +./ + +Expression: AssignmentExpression ; + +Expression: Expression T_COMMA AssignmentExpression ; +/. +case $rule_number: { + AST::Expression *node = new (pool) AST::Expression(sym(1).Expression, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +ExpressionOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +ExpressionOpt: Expression ; + +ExpressionNotIn: AssignmentExpressionNotIn ; + +ExpressionNotIn: ExpressionNotIn T_COMMA AssignmentExpressionNotIn ; +/. +case $rule_number: { + AST::Expression *node = new (pool) AST::Expression(sym(1).Expression, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +ExpressionNotInOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +ExpressionNotInOpt: ExpressionNotIn ; + +Statement: Block ; +Statement: VariableStatement ; +Statement: EmptyStatement ; +Statement: ExpressionStatement ; +Statement: IfStatement ; +Statement: IterationStatement ; +Statement: ContinueStatement ; +Statement: BreakStatement ; +Statement: ReturnStatement ; +Statement: WithStatement ; +Statement: LabelledStatement ; +Statement: SwitchStatement ; +Statement: ThrowStatement ; +Statement: TryStatement ; +Statement: DebuggerStatement ; + + +Block: T_LBRACE StatementListOpt T_RBRACE ; +/. +case $rule_number: { + AST::Block *node = new (pool) AST::Block(sym(2).StatementList); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; +./ + +StatementList: Statement ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::StatementList(sym(1).Statement); +} break; +./ + +StatementList: StatementList Statement ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::StatementList(sym(1).StatementList, sym(2).Statement); +} break; +./ + +StatementListOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +StatementListOpt: StatementList ; +/. +case $rule_number: { + sym(1).Node = sym(1).StatementList->finish (); +} break; +./ + +VariableStatement: VariableDeclarationKind VariableDeclarationList T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +VariableStatement: VariableDeclarationKind VariableDeclarationList T_SEMICOLON ; +/. +case $rule_number: { + AST::VariableStatement *node = new (pool) AST::VariableStatement( + sym(2).VariableDeclarationList->finish (/*readOnly=*/sym(1).ival == T_CONST)); + node->declarationKindToken = loc(1); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; +./ + +VariableDeclarationKind: T_CONST ; +/. +case $rule_number: { + sym(1).ival = T_CONST; +} break; +./ + +VariableDeclarationKind: T_VAR ; +/. +case $rule_number: { + sym(1).ival = T_VAR; +} break; +./ + +VariableDeclarationList: VariableDeclaration ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::VariableDeclarationList(sym(1).VariableDeclaration); +} break; +./ + +VariableDeclarationList: VariableDeclarationList T_COMMA VariableDeclaration ; +/. +case $rule_number: { + AST::VariableDeclarationList *node = new (pool) AST::VariableDeclarationList( + sym(1).VariableDeclarationList, sym(3).VariableDeclaration); + node->commaToken = loc(2); + sym(1).Node = node; +} break; +./ + +VariableDeclarationListNotIn: VariableDeclarationNotIn ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::VariableDeclarationList(sym(1).VariableDeclaration); +} break; +./ + +VariableDeclarationListNotIn: VariableDeclarationListNotIn T_COMMA VariableDeclarationNotIn ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::VariableDeclarationList(sym(1).VariableDeclarationList, sym(3).VariableDeclaration); +} break; +./ + +VariableDeclaration: JsIdentifier InitialiserOpt ; +/. +case $rule_number: { + AST::VariableDeclaration *node = new (pool) AST::VariableDeclaration(stringRef(1), sym(2).Expression); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; +./ + +VariableDeclarationNotIn: JsIdentifier InitialiserNotInOpt ; +/. +case $rule_number: { + AST::VariableDeclaration *node = new (pool) AST::VariableDeclaration(stringRef(1), sym(2).Expression); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; +./ + +Initialiser: T_EQ AssignmentExpression ; +/. +case $rule_number: { + // ### TODO: AST for initializer + sym(1) = sym(2); +} break; +./ + +InitialiserOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +InitialiserOpt: Initialiser ; + +InitialiserNotIn: T_EQ AssignmentExpressionNotIn ; +/. +case $rule_number: { + // ### TODO: AST for initializer + sym(1) = sym(2); +} break; +./ + +InitialiserNotInOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +InitialiserNotInOpt: InitialiserNotIn ; + +EmptyStatement: T_SEMICOLON ; +/. +case $rule_number: { + AST::EmptyStatement *node = new (pool) AST::EmptyStatement(); + node->semicolonToken = loc(1); + sym(1).Node = node; +} break; +./ + +ExpressionStatement: Expression T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ExpressionStatement: Expression T_SEMICOLON ; +/. +case $rule_number: { + AST::ExpressionStatement *node = new (pool) AST::ExpressionStatement(sym(1).Expression); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; +./ + +IfStatement: T_IF T_LPAREN Expression T_RPAREN Statement T_ELSE Statement ; +/. +case $rule_number: { + AST::IfStatement *node = new (pool) AST::IfStatement(sym(3).Expression, sym(5).Statement, sym(7).Statement); + node->ifToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + node->elseToken = loc(6); + sym(1).Node = node; +} break; +./ + +IfStatement: T_IF T_LPAREN Expression T_RPAREN Statement ; +/. +case $rule_number: { + AST::IfStatement *node = new (pool) AST::IfStatement(sym(3).Expression, sym(5).Statement); + node->ifToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + + +IterationStatement: T_DO Statement T_WHILE T_LPAREN Expression T_RPAREN T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +IterationStatement: T_DO Statement T_WHILE T_LPAREN Expression T_RPAREN T_SEMICOLON ; +/. +case $rule_number: { + AST::DoWhileStatement *node = new (pool) AST::DoWhileStatement(sym(2).Statement, sym(5).Expression); + node->doToken = loc(1); + node->whileToken = loc(3); + node->lparenToken = loc(4); + node->rparenToken = loc(6); + node->semicolonToken = loc(7); + sym(1).Node = node; +} break; +./ + +IterationStatement: T_WHILE T_LPAREN Expression T_RPAREN Statement ; +/. +case $rule_number: { + AST::WhileStatement *node = new (pool) AST::WhileStatement(sym(3).Expression, sym(5).Statement); + node->whileToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + +IterationStatement: T_FOR T_LPAREN ExpressionNotInOpt T_SEMICOLON ExpressionOpt T_SEMICOLON ExpressionOpt T_RPAREN Statement ; +/. +case $rule_number: { + AST::ForStatement *node = new (pool) AST::ForStatement(sym(3).Expression, + sym(5).Expression, sym(7).Expression, sym(9).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->firstSemicolonToken = loc(4); + node->secondSemicolonToken = loc(6); + node->rparenToken = loc(8); + sym(1).Node = node; +} break; +./ + +IterationStatement: T_FOR T_LPAREN T_VAR VariableDeclarationListNotIn T_SEMICOLON ExpressionOpt T_SEMICOLON ExpressionOpt T_RPAREN Statement ; +/. +case $rule_number: { + AST::LocalForStatement *node = new (pool) AST::LocalForStatement( + sym(4).VariableDeclarationList->finish (/*readOnly=*/false), sym(6).Expression, + sym(8).Expression, sym(10).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->varToken = loc(3); + node->firstSemicolonToken = loc(5); + node->secondSemicolonToken = loc(7); + node->rparenToken = loc(9); + sym(1).Node = node; +} break; +./ + +IterationStatement: T_FOR T_LPAREN LeftHandSideExpression T_IN Expression T_RPAREN Statement ; +/. +case $rule_number: { + AST:: ForEachStatement *node = new (pool) AST::ForEachStatement(sym(3).Expression, + sym(5).Expression, sym(7).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->inToken = loc(4); + node->rparenToken = loc(6); + sym(1).Node = node; +} break; +./ + +IterationStatement: T_FOR T_LPAREN T_VAR VariableDeclarationNotIn T_IN Expression T_RPAREN Statement ; +/. +case $rule_number: { + AST::LocalForEachStatement *node = new (pool) AST::LocalForEachStatement( + sym(4).VariableDeclaration, sym(6).Expression, sym(8).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->varToken = loc(3); + node->inToken = loc(5); + node->rparenToken = loc(7); + sym(1).Node = node; +} break; +./ + +ContinueStatement: T_CONTINUE T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ContinueStatement: T_CONTINUE T_SEMICOLON ; +/. +case $rule_number: { + AST::ContinueStatement *node = new (pool) AST::ContinueStatement(); + node->continueToken = loc(1); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; +./ + +ContinueStatement: T_CONTINUE JsIdentifier T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ContinueStatement: T_CONTINUE JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + AST::ContinueStatement *node = new (pool) AST::ContinueStatement(stringRef(2)); + node->continueToken = loc(1); + node->identifierToken = loc(2); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; +./ + +BreakStatement: T_BREAK T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +BreakStatement: T_BREAK T_SEMICOLON ; +/. +case $rule_number: { + AST::BreakStatement *node = new (pool) AST::BreakStatement(QStringRef()); + node->breakToken = loc(1); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; +./ + +BreakStatement: T_BREAK JsIdentifier T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +BreakStatement: T_BREAK JsIdentifier T_SEMICOLON ; +/. +case $rule_number: { + AST::BreakStatement *node = new (pool) AST::BreakStatement(stringRef(2)); + node->breakToken = loc(1); + node->identifierToken = loc(2); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; +./ + +ReturnStatement: T_RETURN ExpressionOpt T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ReturnStatement: T_RETURN ExpressionOpt T_SEMICOLON ; +/. +case $rule_number: { + AST::ReturnStatement *node = new (pool) AST::ReturnStatement(sym(2).Expression); + node->returnToken = loc(1); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; +./ + +WithStatement: T_WITH T_LPAREN Expression T_RPAREN Statement ; +/. +case $rule_number: { + AST::WithStatement *node = new (pool) AST::WithStatement(sym(3).Expression, sym(5).Statement); + node->withToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + +SwitchStatement: T_SWITCH T_LPAREN Expression T_RPAREN CaseBlock ; +/. +case $rule_number: { + AST::SwitchStatement *node = new (pool) AST::SwitchStatement(sym(3).Expression, sym(5).CaseBlock); + node->switchToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + +CaseBlock: T_LBRACE CaseClausesOpt T_RBRACE ; +/. +case $rule_number: { + AST::CaseBlock *node = new (pool) AST::CaseBlock(sym(2).CaseClauses); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; +./ + +CaseBlock: T_LBRACE CaseClausesOpt DefaultClause CaseClausesOpt T_RBRACE ; +/. +case $rule_number: { + AST::CaseBlock *node = new (pool) AST::CaseBlock(sym(2).CaseClauses, sym(3).DefaultClause, sym(4).CaseClauses); + node->lbraceToken = loc(1); + node->rbraceToken = loc(5); + sym(1).Node = node; +} break; +./ + +CaseClauses: CaseClause ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::CaseClauses(sym(1).CaseClause); +} break; +./ + +CaseClauses: CaseClauses CaseClause ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::CaseClauses(sym(1).CaseClauses, sym(2).CaseClause); +} break; +./ + +CaseClausesOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +CaseClausesOpt: CaseClauses ; +/. +case $rule_number: { + sym(1).Node = sym(1).CaseClauses->finish (); +} break; +./ + +CaseClause: T_CASE Expression T_COLON StatementListOpt ; +/. +case $rule_number: { + AST::CaseClause *node = new (pool) AST::CaseClause(sym(2).Expression, sym(4).StatementList); + node->caseToken = loc(1); + node->colonToken = loc(3); + sym(1).Node = node; +} break; +./ + +DefaultClause: T_DEFAULT T_COLON StatementListOpt ; +/. +case $rule_number: { + AST::DefaultClause *node = new (pool) AST::DefaultClause(sym(3).StatementList); + node->defaultToken = loc(1); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +./ + +LabelledStatement: T_SIGNAL T_COLON Statement ; +/.case $rule_number:./ + +LabelledStatement: T_PROPERTY T_COLON Statement ; +/. +case $rule_number: { + AST::LabelledStatement *node = new (pool) AST::LabelledStatement(stringRef(1), sym(3).Statement); + node->identifierToken = loc(1); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +./ + +LabelledStatement: T_IDENTIFIER T_COLON Statement ; +/. +case $rule_number: { + AST::LabelledStatement *node = new (pool) AST::LabelledStatement(stringRef(1), sym(3).Statement); + node->identifierToken = loc(1); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +./ + +ThrowStatement: T_THROW Expression T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +ThrowStatement: T_THROW Expression T_SEMICOLON ; +/. +case $rule_number: { + AST::ThrowStatement *node = new (pool) AST::ThrowStatement(sym(2).Expression); + node->throwToken = loc(1); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; +./ + +TryStatement: T_TRY Block Catch ; +/. +case $rule_number: { + AST::TryStatement *node = new (pool) AST::TryStatement(sym(2).Statement, sym(3).Catch); + node->tryToken = loc(1); + sym(1).Node = node; +} break; +./ + +TryStatement: T_TRY Block Finally ; +/. +case $rule_number: { + AST::TryStatement *node = new (pool) AST::TryStatement(sym(2).Statement, sym(3).Finally); + node->tryToken = loc(1); + sym(1).Node = node; +} break; +./ + +TryStatement: T_TRY Block Catch Finally ; +/. +case $rule_number: { + AST::TryStatement *node = new (pool) AST::TryStatement(sym(2).Statement, sym(3).Catch, sym(4).Finally); + node->tryToken = loc(1); + sym(1).Node = node; +} break; +./ + +Catch: T_CATCH T_LPAREN JsIdentifier T_RPAREN Block ; +/. +case $rule_number: { + AST::Catch *node = new (pool) AST::Catch(stringRef(3), sym(5).Block); + node->catchToken = loc(1); + node->lparenToken = loc(2); + node->identifierToken = loc(3); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; +./ + +Finally: T_FINALLY Block ; +/. +case $rule_number: { + AST::Finally *node = new (pool) AST::Finally(sym(2).Block); + node->finallyToken = loc(1); + sym(1).Node = node; +} break; +./ + +DebuggerStatement: T_DEBUGGER T_AUTOMATIC_SEMICOLON ; -- automatic semicolon +DebuggerStatement: T_DEBUGGER T_SEMICOLON ; +/. +case $rule_number: { + AST::DebuggerStatement *node = new (pool) AST::DebuggerStatement(); + node->debuggerToken = loc(1); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; +./ + +FunctionDeclaration: T_FUNCTION JsIdentifier T_LPAREN FormalParameterListOpt T_RPAREN T_LBRACE FunctionBodyOpt T_RBRACE ; +/. +case $rule_number: { + AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(stringRef(2), sym(4).FormalParameterList, sym(7).FunctionBody); + node->functionToken = loc(1); + node->identifierToken = loc(2); + node->lparenToken = loc(3); + node->rparenToken = loc(5); + node->lbraceToken = loc(6); + node->rbraceToken = loc(8); + sym(1).Node = node; +} break; +./ + +FunctionExpression: T_FUNCTION IdentifierOpt T_LPAREN FormalParameterListOpt T_RPAREN T_LBRACE FunctionBodyOpt T_RBRACE ; +/. +case $rule_number: { + AST::FunctionExpression *node = new (pool) AST::FunctionExpression(stringRef(2), sym(4).FormalParameterList, sym(7).FunctionBody); + node->functionToken = loc(1); + if (! stringRef(2).isNull()) + node->identifierToken = loc(2); + node->lparenToken = loc(3); + node->rparenToken = loc(5); + node->lbraceToken = loc(6); + node->rbraceToken = loc(8); + sym(1).Node = node; +} break; +./ + +FormalParameterList: JsIdentifier ; +/. +case $rule_number: { + AST::FormalParameterList *node = new (pool) AST::FormalParameterList(stringRef(1)); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; +./ + +FormalParameterList: FormalParameterList T_COMMA JsIdentifier ; +/. +case $rule_number: { + AST::FormalParameterList *node = new (pool) AST::FormalParameterList(sym(1).FormalParameterList, stringRef(3)); + node->commaToken = loc(2); + node->identifierToken = loc(3); + sym(1).Node = node; +} break; +./ + +FormalParameterListOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +FormalParameterListOpt: FormalParameterList ; +/. +case $rule_number: { + sym(1).Node = sym(1).FormalParameterList->finish (); +} break; +./ + +FunctionBodyOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +FunctionBodyOpt: FunctionBody ; + +FunctionBody: SourceElements ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::FunctionBody(sym(1).SourceElements->finish ()); +} break; +./ + +Program: Empty ; + +Program: SourceElements ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::Program(sym(1).SourceElements->finish ()); +} break; +./ + +SourceElements: SourceElement ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::SourceElements(sym(1).SourceElement); +} break; +./ + +SourceElements: SourceElements SourceElement ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::SourceElements(sym(1).SourceElements, sym(2).SourceElement); +} break; +./ + +SourceElement: Statement ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::StatementSourceElement(sym(1).Statement); +} break; +./ + +SourceElement: FunctionDeclaration ; +/. +case $rule_number: { + sym(1).Node = new (pool) AST::FunctionSourceElement(sym(1).FunctionDeclaration); +} break; +./ + +IdentifierOpt: ; +/. +case $rule_number: { + stringRef(1) = QStringRef(); +} break; +./ + +IdentifierOpt: JsIdentifier ; + +PropertyNameAndValueListOpt: ; +/. +case $rule_number: { + sym(1).Node = 0; +} break; +./ + +PropertyNameAndValueListOpt: PropertyNameAndValueList ; + +/. + } // switch + action = nt_action(state_stack[tos], lhs[r] - TERMINAL_COUNT); + } // if + } while (action != 0); + + if (first_token == last_token) { + const int errorState = state_stack[tos]; + + // automatic insertion of `;' + if (yytoken != -1 && t_action(errorState, T_AUTOMATIC_SEMICOLON) && lexer->canInsertAutomaticSemicolon(yytoken)) { + SavedToken &tk = token_buffer[0]; + tk.token = yytoken; + tk.dval = yylval; + tk.spell = yytokenspell; + tk.loc = yylloc; + + yylloc = yyprevlloc; + yylloc.offset += yylloc.length; + yylloc.startColumn += yylloc.length; + yylloc.length = 0; + + //const QString msg = qApp->translate("QmlParser", "Missing `;'"); + //diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Warning, yylloc, msg)); + + first_token = &token_buffer[0]; + last_token = &token_buffer[1]; + + yytoken = T_SEMICOLON; + yylval = 0; + + action = errorState; + + goto _Lcheck_token; + } + + hadErrors = true; + + token_buffer[0].token = yytoken; + token_buffer[0].dval = yylval; + token_buffer[0].spell = yytokenspell; + token_buffer[0].loc = yylloc; + + token_buffer[1].token = yytoken = lexer->lex(); + token_buffer[1].dval = yylval = lexer->tokenValue(); + token_buffer[1].spell = yytokenspell = lexer->tokenSpell(); + token_buffer[1].loc = yylloc = location(lexer); + + if (t_action(errorState, yytoken)) { + QString msg; + int token = token_buffer[0].token; + if (token < 0 || token >= TERMINAL_COUNT) + msg = qApp->translate("QmlParser", "Syntax error"); + else + msg = qApp->translate("QmlParser", "Unexpected token `%1'").arg(QLatin1String(spell[token])); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + + action = errorState; + goto _Lcheck_token; + } + + static int tokens[] = { + T_PLUS, + T_EQ, + + T_COMMA, + T_COLON, + T_SEMICOLON, + + T_RPAREN, T_RBRACKET, T_RBRACE, + + T_NUMERIC_LITERAL, + T_IDENTIFIER, + + T_LPAREN, T_LBRACKET, T_LBRACE, + + EOF_SYMBOL + }; + + for (int *tk = tokens; *tk != EOF_SYMBOL; ++tk) { + int a = t_action(errorState, *tk); + if (a > 0 && t_action(a, yytoken)) { + const QString msg = qApp->translate("QmlParser", "Expected token `%1'").arg(QLatin1String(spell[*tk])); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + + yytoken = *tk; + yylval = 0; + yylloc = token_buffer[0].loc; + yylloc.length = 0; + + first_token = &token_buffer[0]; + last_token = &token_buffer[2]; + + action = errorState; + goto _Lcheck_token; + } + } + + for (int tk = 1; tk < TERMINAL_COUNT; ++tk) { + if (tk == T_AUTOMATIC_SEMICOLON || tk == T_FEED_UI_PROGRAM || + tk == T_FEED_JS_STATEMENT || tk == T_FEED_JS_EXPRESSION || + tk == T_FEED_JS_SOURCE_ELEMENT) + continue; + + int a = t_action(errorState, tk); + if (a > 0 && t_action(a, yytoken)) { + const QString msg = qApp->translate("QmlParser", "Expected token `%1'").arg(QLatin1String(spell[tk])); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + + yytoken = tk; + yylval = 0; + yylloc = token_buffer[0].loc; + yylloc.length = 0; + + action = errorState; + goto _Lcheck_token; + } + } + + const QString msg = qApp->translate("QmlParser", "Syntax error"); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + } + + return false; +} + +QT_QML_END_NAMESPACE + + +./ +/: +QT_QML_END_NAMESPACE + + + +#endif // QMLJSPARSER_P_H +:/ diff --git a/src/lib/corelib/parser/qmljsast.cpp b/src/lib/corelib/parser/qmljsast.cpp new file mode 100644 index 00000000..8c87f80b --- /dev/null +++ b/src/lib/corelib/parser/qmljsast.cpp @@ -0,0 +1,925 @@ +/**************************************************************************** +** +** 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 "qmljsast_p.h" + +#include "qmljsastvisitor_p.h" + +namespace QbsQmlJS { +namespace AST { + +void Node::accept(Visitor *visitor) +{ + if (visitor->preVisit(this)) { + accept0(visitor); + } + visitor->postVisit(this); +} + +void Node::accept(Node *node, Visitor *visitor) +{ + if (node) + node->accept(visitor); +} + +ExpressionNode *Node::expressionCast() +{ + return nullptr; +} + +BinaryExpression *Node::binaryExpressionCast() +{ + return nullptr; +} + +Statement *Node::statementCast() +{ + return nullptr; +} + +UiObjectMember *Node::uiObjectMemberCast() +{ + return nullptr; +} + +ExpressionNode *ExpressionNode::expressionCast() +{ + return this; +} + +BinaryExpression *BinaryExpression::binaryExpressionCast() +{ + return this; +} + +Statement *Statement::statementCast() +{ + return this; +} + +UiObjectMember *UiObjectMember::uiObjectMemberCast() +{ + return this; +} + +void NestedExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + visitor->endVisit(this); +} + +void ThisExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void IdentifierExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void NullExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void TrueLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void FalseLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void StringLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void NumericLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void RegExpLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void ArrayLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(elements, visitor); + accept(elision, visitor); + } + + visitor->endVisit(this); +} + +void ObjectLiteral::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(properties, visitor); + } + + visitor->endVisit(this); +} + +void ElementList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (ElementList *it = this; it; it = it->next) { + accept(it->elision, visitor); + accept(it->expression, visitor); + } + } + + visitor->endVisit(this); +} + +void Elision::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + // ### + } + + visitor->endVisit(this); +} + +void PropertyNameAndValueList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (PropertyNameAndValueList *it = this; it; it = it->next) { + accept(it->name, visitor); + accept(it->value, visitor); + } + } + + visitor->endVisit(this); +} + +void IdentifierPropertyName::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void StringLiteralPropertyName::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void NumericLiteralPropertyName::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void ArrayMemberExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(base, visitor); + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void FieldMemberExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(base, visitor); + } + + visitor->endVisit(this); +} + +void NewMemberExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(base, visitor); + accept(arguments, visitor); + } + + visitor->endVisit(this); +} + +void NewExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void CallExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(base, visitor); + accept(arguments, visitor); + } + + visitor->endVisit(this); +} + +void ArgumentList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (ArgumentList *it = this; it; it = it->next) { + accept(it->expression, visitor); + } + } + + visitor->endVisit(this); +} + +void PostIncrementExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(base, visitor); + } + + visitor->endVisit(this); +} + +void PostDecrementExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(base, visitor); + } + + visitor->endVisit(this); +} + +void DeleteExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void VoidExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void TypeOfExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void PreIncrementExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void PreDecrementExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void UnaryPlusExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void UnaryMinusExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void TildeExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void NotExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void BinaryExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(left, visitor); + accept(right, visitor); + } + + visitor->endVisit(this); +} + +void ConditionalExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + accept(ok, visitor); + accept(ko, visitor); + } + + visitor->endVisit(this); +} + +void Expression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(left, visitor); + accept(right, visitor); + } + + visitor->endVisit(this); +} + +void Block::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statements, visitor); + } + + visitor->endVisit(this); +} + +void StatementList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (StatementList *it = this; it; it = it->next) { + accept(it->statement, visitor); + } + } + + visitor->endVisit(this); +} + +void VariableStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(declarations, visitor); + } + + visitor->endVisit(this); +} + +void VariableDeclarationList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (VariableDeclarationList *it = this; it; it = it->next) { + accept(it->declaration, visitor); + } + } + + visitor->endVisit(this); +} + +void VariableDeclaration::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void EmptyStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void ExpressionStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void IfStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + accept(ok, visitor); + accept(ko, visitor); + } + + visitor->endVisit(this); +} + +void DoWhileStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void WhileStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void ForStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(initialiser, visitor); + accept(condition, visitor); + accept(expression, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void LocalForStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(declarations, visitor); + accept(condition, visitor); + accept(expression, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void ForEachStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(initialiser, visitor); + accept(expression, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void LocalForEachStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(declaration, visitor); + accept(expression, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void ContinueStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void BreakStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void ReturnStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void WithStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void SwitchStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + accept(block, visitor); + } + + visitor->endVisit(this); +} + +void CaseBlock::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(clauses, visitor); + accept(defaultClause, visitor); + accept(moreClauses, visitor); + } + + visitor->endVisit(this); +} + +void CaseClauses::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (CaseClauses *it = this; it; it = it->next) { + accept(it->clause, visitor); + } + } + + visitor->endVisit(this); +} + +void CaseClause::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + accept(statements, visitor); + } + + visitor->endVisit(this); +} + +void DefaultClause::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statements, visitor); + } + + visitor->endVisit(this); +} + +void LabelledStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void ThrowStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(expression, visitor); + } + + visitor->endVisit(this); +} + +void TryStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + accept(catchExpression, visitor); + accept(finallyExpression, visitor); + } + + visitor->endVisit(this); +} + +void Catch::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void Finally::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void FunctionDeclaration::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(formals, visitor); + accept(body, visitor); + } + + visitor->endVisit(this); +} + +void FunctionExpression::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(formals, visitor); + accept(body, visitor); + } + + visitor->endVisit(this); +} + +void FormalParameterList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + // ### + } + + visitor->endVisit(this); +} + +void FunctionBody::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(elements, visitor); + } + + visitor->endVisit(this); +} + +void Program::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(elements, visitor); + } + + visitor->endVisit(this); +} + +void SourceElements::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (SourceElements *it = this; it; it = it->next) { + accept(it->element, visitor); + } + } + + visitor->endVisit(this); +} + +void FunctionSourceElement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(declaration, visitor); + } + + visitor->endVisit(this); +} + +void StatementSourceElement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void DebuggerStatement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void UiProgram::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(imports, visitor); + accept(members, visitor); + } + + visitor->endVisit(this); +} + +void UiPublicMember::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(statement, visitor); + accept(binding, visitor); + } + + visitor->endVisit(this); +} + +void UiObjectDefinition::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(qualifiedTypeNameId, visitor); + accept(initializer, visitor); + } + + visitor->endVisit(this); +} + +void UiObjectInitializer::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(members, visitor); + } + + visitor->endVisit(this); +} + +void UiObjectBinding::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(qualifiedId, visitor); + accept(qualifiedTypeNameId, visitor); + accept(initializer, visitor); + } + + visitor->endVisit(this); +} + +void UiScriptBinding::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(qualifiedId, visitor); + accept(statement, visitor); + } + + visitor->endVisit(this); +} + +void UiArrayBinding::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(qualifiedId, visitor); + accept(members, visitor); + } + + visitor->endVisit(this); +} + +void UiObjectMemberList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (UiObjectMemberList *it = this; it; it = it->next) + accept(it->member, visitor); + } + + visitor->endVisit(this); +} + +void UiArrayMemberList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (UiArrayMemberList *it = this; it; it = it->next) + accept(it->member, visitor); + } + + visitor->endVisit(this); +} + +void UiQualifiedId::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + } + + visitor->endVisit(this); +} + +void UiImport::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(importUri, visitor); + } + + visitor->endVisit(this); +} + +void UiImportList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(import, visitor); + accept(next, visitor); + } + + visitor->endVisit(this); +} + +void UiSourceElement::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(sourceElement, visitor); + } + + visitor->endVisit(this); +} + +} // namespace AST +} // namespace QbsQmlJS diff --git a/src/lib/corelib/parser/qmljsast_p.h b/src/lib/corelib/parser/qmljsast_p.h new file mode 100644 index 00000000..dcee233d --- /dev/null +++ b/src/lib/corelib/parser/qmljsast_p.h @@ -0,0 +1,2633 @@ +/**************************************************************************** +** +** 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 QMLJSAST_P_H +#define QMLJSAST_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qmljsastvisitor_p.h" +#include "qmljsglobal_p.h" +#include "qmljsmemorypool_p.h" + +#include + +namespace QbsQmlJS { + +#define QMLJS_DECLARE_AST_NODE(name) \ + enum { K = Kind_##name }; + +namespace QSOperator // ### rename +{ + +enum Op { + Add, + And, + InplaceAnd, + Assign, + BitAnd, + BitOr, + BitXor, + InplaceSub, + Div, + InplaceDiv, + Equal, + Ge, + Gt, + In, + InplaceAdd, + InstanceOf, + Le, + LShift, + InplaceLeftShift, + Lt, + Mod, + InplaceMod, + Mul, + InplaceMul, + NotEqual, + Or, + InplaceOr, + RShift, + InplaceRightShift, + StrictEqual, + StrictNotEqual, + Sub, + URShift, + InplaceURightShift, + InplaceXor +}; + +} // namespace QSOperator + +namespace AST { + +template +_T1 cast(_T2 *ast) +{ + if (ast && ast->kind == static_cast<_T1>(nullptr)->K) + return static_cast<_T1>(ast); + + return nullptr; +} + +class QML_PARSER_EXPORT Node: public Managed +{ +public: + enum Kind { + Kind_Undefined, + + Kind_ArgumentList, + Kind_ArrayLiteral, + Kind_ArrayMemberExpression, + Kind_BinaryExpression, + Kind_Block, + Kind_BreakStatement, + Kind_CallExpression, + Kind_CaseBlock, + Kind_CaseClause, + Kind_CaseClauses, + Kind_Catch, + Kind_ConditionalExpression, + Kind_ContinueStatement, + Kind_DebuggerStatement, + Kind_DefaultClause, + Kind_DeleteExpression, + Kind_DoWhileStatement, + Kind_ElementList, + Kind_Elision, + Kind_EmptyStatement, + Kind_Expression, + Kind_ExpressionStatement, + Kind_FalseLiteral, + Kind_FieldMemberExpression, + Kind_Finally, + Kind_ForEachStatement, + Kind_ForStatement, + Kind_FormalParameterList, + Kind_FunctionBody, + Kind_FunctionDeclaration, + Kind_FunctionExpression, + Kind_FunctionSourceElement, + Kind_IdentifierExpression, + Kind_IdentifierPropertyName, + Kind_IfStatement, + Kind_LabelledStatement, + Kind_LocalForEachStatement, + Kind_LocalForStatement, + Kind_NewExpression, + Kind_NewMemberExpression, + Kind_NotExpression, + Kind_NullExpression, + Kind_NumericLiteral, + Kind_NumericLiteralPropertyName, + Kind_ObjectLiteral, + Kind_PostDecrementExpression, + Kind_PostIncrementExpression, + Kind_PreDecrementExpression, + Kind_PreIncrementExpression, + Kind_Program, + Kind_PropertyName, + Kind_PropertyNameAndValueList, + Kind_RegExpLiteral, + Kind_ReturnStatement, + Kind_SourceElement, + Kind_SourceElements, + Kind_StatementList, + Kind_StatementSourceElement, + Kind_StringLiteral, + Kind_StringLiteralPropertyName, + Kind_SwitchStatement, + Kind_ThisExpression, + Kind_ThrowStatement, + Kind_TildeExpression, + Kind_TrueLiteral, + Kind_TryStatement, + Kind_TypeOfExpression, + Kind_UnaryMinusExpression, + Kind_UnaryPlusExpression, + Kind_VariableDeclaration, + Kind_VariableDeclarationList, + Kind_VariableStatement, + Kind_VoidExpression, + Kind_WhileStatement, + Kind_WithStatement, + Kind_NestedExpression, + + Kind_UiArrayBinding, + Kind_UiImport, + Kind_UiImportList, + Kind_UiObjectBinding, + Kind_UiObjectDefinition, + Kind_UiObjectInitializer, + Kind_UiObjectMemberList, + Kind_UiArrayMemberList, + Kind_UiProgram, + Kind_UiParameterList, + Kind_UiPublicMember, + Kind_UiQualifiedId, + Kind_UiScriptBinding, + Kind_UiSourceElement + }; + + inline Node() + : kind(Kind_Undefined) {} + + // NOTE: node destructors are never called, + // instead we block free the memory + // (see the NodePool class) + virtual ~Node() = default; + + virtual ExpressionNode *expressionCast(); + virtual BinaryExpression *binaryExpressionCast(); + virtual Statement *statementCast(); + virtual UiObjectMember *uiObjectMemberCast(); + + void accept(Visitor *visitor); + static void accept(Node *node, Visitor *visitor); + + inline static void acceptChild(Node *node, Visitor *visitor) + { return accept(node, visitor); } // ### remove + + virtual void accept0(Visitor *visitor) = 0; + virtual SourceLocation firstSourceLocation() const = 0; + virtual SourceLocation lastSourceLocation() const = 0; + +// attributes + int kind; +}; + +class QML_PARSER_EXPORT ExpressionNode: public Node +{ +public: + ExpressionNode() = default; + + ExpressionNode *expressionCast() override; +}; + +class QML_PARSER_EXPORT Statement: public Node +{ +public: + Statement() = default; + + Statement *statementCast() override; +}; + +class QML_PARSER_EXPORT NestedExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(NestedExpression) + + NestedExpression(ExpressionNode *expression) + : expression(expression) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return lparenToken; } + + SourceLocation lastSourceLocation() const override + { return rparenToken; } + +// attributes + ExpressionNode *expression; + SourceLocation lparenToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT ThisExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(ThisExpression) + + ThisExpression() { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return thisToken; } + + SourceLocation lastSourceLocation() const override + { return thisToken; } + +// attributes + SourceLocation thisToken; +}; + +class QML_PARSER_EXPORT IdentifierExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(IdentifierExpression) + + IdentifierExpression(const QStringRef &n): + name (n) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return identifierToken; } + + SourceLocation lastSourceLocation() const override + { return identifierToken; } + +// attributes + QStringRef name; + SourceLocation identifierToken; +}; + +class QML_PARSER_EXPORT NullExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(NullExpression) + + NullExpression() { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return nullToken; } + + SourceLocation lastSourceLocation() const override + { return nullToken; } + +// attributes + SourceLocation nullToken; +}; + +class QML_PARSER_EXPORT TrueLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(TrueLiteral) + + TrueLiteral() { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return trueToken; } + + SourceLocation lastSourceLocation() const override + { return trueToken; } + +// attributes + SourceLocation trueToken; +}; + +class QML_PARSER_EXPORT FalseLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(FalseLiteral) + + FalseLiteral() { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return falseToken; } + + SourceLocation lastSourceLocation() const override + { return falseToken; } + +// attributes + SourceLocation falseToken; +}; + +class QML_PARSER_EXPORT NumericLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(NumericLiteral) + + NumericLiteral(double v): + value(v) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return literalToken; } + + SourceLocation lastSourceLocation() const override + { return literalToken; } + +// attributes: + double value; + SourceLocation literalToken; +}; + +class QML_PARSER_EXPORT StringLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(StringLiteral) + + StringLiteral(const QStringRef &v): + value (v) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return literalToken; } + + SourceLocation lastSourceLocation() const override + { return literalToken; } + +// attributes: + QStringRef value; + SourceLocation literalToken; +}; + +class QML_PARSER_EXPORT RegExpLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(RegExpLiteral) + + RegExpLiteral(const QStringRef &p, int f): + pattern (p), flags (f) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return literalToken; } + + SourceLocation lastSourceLocation() const override + { return literalToken; } + +// attributes: + QStringRef pattern; + int flags; + SourceLocation literalToken; +}; + +class QML_PARSER_EXPORT ArrayLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(ArrayLiteral) + + ArrayLiteral(Elision *e): + elements (nullptr), elision (e) + { kind = K; } + + ArrayLiteral(ElementList *elts): + elements (elts), elision (nullptr) + { kind = K; } + + ArrayLiteral(ElementList *elts, Elision *e): + elements (elts), elision (e) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return lbracketToken; } + + SourceLocation lastSourceLocation() const override + { return rbracketToken; } + +// attributes + ElementList *elements; + Elision *elision; + SourceLocation lbracketToken; + SourceLocation commaToken; + SourceLocation rbracketToken; +}; + +class QML_PARSER_EXPORT ObjectLiteral: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(ObjectLiteral) + + ObjectLiteral(): + properties (nullptr) { kind = K; } + + ObjectLiteral(PropertyNameAndValueList *plist): + properties (plist) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return lbraceToken; } + + SourceLocation lastSourceLocation() const override + { return rbraceToken; } + +// attributes + PropertyNameAndValueList *properties; + SourceLocation lbraceToken; + SourceLocation rbraceToken; +}; + +class QML_PARSER_EXPORT Elision: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(Elision) + + Elision(): + next (this) { kind = K; } + + Elision(Elision *previous) + { + kind = K; + next = previous->next; + previous->next = this; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return commaToken; } + + SourceLocation lastSourceLocation() const override + { return next ? next->lastSourceLocation() : commaToken; } + + inline Elision *finish () + { + Elision *front = next; + next = nullptr; + return front; + } + +// attributes + Elision *next; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT ElementList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(ElementList) + + ElementList(Elision *e, ExpressionNode *expr): + elision (e), expression (expr), next (this) + { kind = K; } + + ElementList(ElementList *previous, Elision *e, ExpressionNode *expr): + elision (e), expression (expr) + { + kind = K; + next = previous->next; + previous->next = this; + } + + inline ElementList *finish () + { + ElementList *front = next; + next = nullptr; + return front; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { + if (elision) + return elision->firstSourceLocation(); + return expression->firstSourceLocation(); + } + + SourceLocation lastSourceLocation() const override + { + if (next) + return next->lastSourceLocation(); + return expression->lastSourceLocation(); + } + +// attributes + Elision *elision; + ExpressionNode *expression; + ElementList *next; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT PropertyName: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(PropertyName) + + PropertyName() { kind = K; } + + SourceLocation firstSourceLocation() const override + { return propertyNameToken; } + + SourceLocation lastSourceLocation() const override + { return propertyNameToken; } + +// attributes + SourceLocation propertyNameToken; +}; + +class QML_PARSER_EXPORT PropertyNameAndValueList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(PropertyNameAndValueList) + + PropertyNameAndValueList(PropertyName *n, ExpressionNode *v): + name (n), value (v), next (this) + { kind = K; } + + PropertyNameAndValueList(PropertyNameAndValueList *previous, PropertyName *n, ExpressionNode *v): + name (n), value (v) + { + kind = K; + next = previous->next; + previous->next = this; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return name->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { + if (next) + return next->lastSourceLocation(); + return value->lastSourceLocation(); + } + + inline PropertyNameAndValueList *finish () + { + PropertyNameAndValueList *front = next; + next = nullptr; + return front; + } + +// attributes + PropertyName *name; + ExpressionNode *value; + PropertyNameAndValueList *next; + SourceLocation colonToken; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT IdentifierPropertyName: public PropertyName +{ +public: + QMLJS_DECLARE_AST_NODE(IdentifierPropertyName) + + IdentifierPropertyName(const QStringRef &n): + id (n) { kind = K; } + + void accept0(Visitor *visitor) override; + +// attributes + QStringRef id; +}; + +class QML_PARSER_EXPORT StringLiteralPropertyName: public PropertyName +{ +public: + QMLJS_DECLARE_AST_NODE(StringLiteralPropertyName) + + StringLiteralPropertyName(const QStringRef &n): + id (n) { kind = K; } + + void accept0(Visitor *visitor) override; + +// attributes + QStringRef id; +}; + +class QML_PARSER_EXPORT NumericLiteralPropertyName: public PropertyName +{ +public: + QMLJS_DECLARE_AST_NODE(NumericLiteralPropertyName) + + NumericLiteralPropertyName(double n): + id (n) { kind = K; } + + void accept0(Visitor *visitor) override; + +// attributes + double id; +}; + +class QML_PARSER_EXPORT ArrayMemberExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(ArrayMemberExpression) + + ArrayMemberExpression(ExpressionNode *b, ExpressionNode *e): + base (b), expression (e) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return base->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return rbracketToken; } + +// attributes + ExpressionNode *base; + ExpressionNode *expression; + SourceLocation lbracketToken; + SourceLocation rbracketToken; +}; + +class QML_PARSER_EXPORT FieldMemberExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(FieldMemberExpression) + + FieldMemberExpression(ExpressionNode *b, const QStringRef &n): + base (b), name (n) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return base->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return identifierToken; } + + // attributes + ExpressionNode *base; + QStringRef name; + SourceLocation dotToken; + SourceLocation identifierToken; +}; + +class QML_PARSER_EXPORT NewMemberExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(NewMemberExpression) + + NewMemberExpression(ExpressionNode *b, ArgumentList *a): + base (b), arguments (a) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return newToken; } + + SourceLocation lastSourceLocation() const override + { return rparenToken; } + + // attributes + ExpressionNode *base; + ArgumentList *arguments; + SourceLocation newToken; + SourceLocation lparenToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT NewExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(NewExpression) + + NewExpression(ExpressionNode *e): + expression (e) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return newToken; } + + SourceLocation lastSourceLocation() const override + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation newToken; +}; + +class QML_PARSER_EXPORT CallExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(CallExpression) + + CallExpression(ExpressionNode *b, ArgumentList *a): + base (b), arguments (a) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return base->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return rparenToken; } + +// attributes + ExpressionNode *base; + ArgumentList *arguments; + SourceLocation lparenToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT ArgumentList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(ArgumentList) + + ArgumentList(ExpressionNode *e): + expression (e), next (this) + { kind = K; } + + ArgumentList(ArgumentList *previous, ExpressionNode *e): + expression (e) + { + kind = K; + next = previous->next; + previous->next = this; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return expression->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { + if (next) + return next->lastSourceLocation(); + return expression->lastSourceLocation(); + } + + inline ArgumentList *finish () + { + ArgumentList *front = next; + next = nullptr; + return front; + } + +// attributes + ExpressionNode *expression; + ArgumentList *next; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT PostIncrementExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(PostIncrementExpression) + + PostIncrementExpression(ExpressionNode *b): + base (b) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return base->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return incrementToken; } + +// attributes + ExpressionNode *base; + SourceLocation incrementToken; +}; + +class QML_PARSER_EXPORT PostDecrementExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(PostDecrementExpression) + + PostDecrementExpression(ExpressionNode *b): + base (b) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return base->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return decrementToken; } + +// attributes + ExpressionNode *base; + SourceLocation decrementToken; +}; + +class QML_PARSER_EXPORT DeleteExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(DeleteExpression) + + DeleteExpression(ExpressionNode *e): + expression (e) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return deleteToken; } + + SourceLocation lastSourceLocation() const override + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation deleteToken; +}; + +class QML_PARSER_EXPORT VoidExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(VoidExpression) + + VoidExpression(ExpressionNode *e): + expression (e) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return voidToken; } + + SourceLocation lastSourceLocation() const override + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation voidToken; +}; + +class QML_PARSER_EXPORT TypeOfExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(TypeOfExpression) + + TypeOfExpression(ExpressionNode *e): + expression (e) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return typeofToken; } + + SourceLocation lastSourceLocation() const override + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation typeofToken; +}; + +class QML_PARSER_EXPORT PreIncrementExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(PreIncrementExpression) + + PreIncrementExpression(ExpressionNode *e): + expression (e) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return incrementToken; } + + SourceLocation lastSourceLocation() const override + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation incrementToken; +}; + +class QML_PARSER_EXPORT PreDecrementExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(PreDecrementExpression) + + PreDecrementExpression(ExpressionNode *e): + expression (e) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return decrementToken; } + + SourceLocation lastSourceLocation() const override + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation decrementToken; +}; + +class QML_PARSER_EXPORT UnaryPlusExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(UnaryPlusExpression) + + UnaryPlusExpression(ExpressionNode *e): + expression (e) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return plusToken; } + + SourceLocation lastSourceLocation() const override + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation plusToken; +}; + +class QML_PARSER_EXPORT UnaryMinusExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(UnaryMinusExpression) + + UnaryMinusExpression(ExpressionNode *e): + expression (e) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return minusToken; } + + SourceLocation lastSourceLocation() const override + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation minusToken; +}; + +class QML_PARSER_EXPORT TildeExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(TildeExpression) + + TildeExpression(ExpressionNode *e): + expression (e) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return tildeToken; } + + SourceLocation lastSourceLocation() const override + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation tildeToken; +}; + +class QML_PARSER_EXPORT NotExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(NotExpression) + + NotExpression(ExpressionNode *e): + expression (e) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return notToken; } + + SourceLocation lastSourceLocation() const override + { return expression->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + SourceLocation notToken; +}; + +class QML_PARSER_EXPORT BinaryExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(BinaryExpression) + + BinaryExpression(ExpressionNode *l, int o, ExpressionNode *r): + left (l), op (o), right (r) + { kind = K; } + + BinaryExpression *binaryExpressionCast() override; + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return left->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return right->lastSourceLocation(); } + +// attributes + ExpressionNode *left; + int op; + ExpressionNode *right; + SourceLocation operatorToken; +}; + +class QML_PARSER_EXPORT ConditionalExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(ConditionalExpression) + + ConditionalExpression(ExpressionNode *e, ExpressionNode *t, ExpressionNode *f): + expression (e), ok (t), ko (f) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return expression->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return ko->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + ExpressionNode *ok; + ExpressionNode *ko; + SourceLocation questionToken; + SourceLocation colonToken; +}; + +class QML_PARSER_EXPORT Expression: public ExpressionNode // ### rename +{ +public: + QMLJS_DECLARE_AST_NODE(Expression) + + Expression(ExpressionNode *l, ExpressionNode *r): + left (l), right (r) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return left->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return right->lastSourceLocation(); } + +// attributes + ExpressionNode *left; + ExpressionNode *right; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT Block: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(Block) + + Block(StatementList *slist): + statements (slist) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return lbraceToken; } + + SourceLocation lastSourceLocation() const override + { return rbraceToken; } + + // attributes + StatementList *statements; + SourceLocation lbraceToken; + SourceLocation rbraceToken; +}; + +class QML_PARSER_EXPORT StatementList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(StatementList) + + StatementList(Statement *stmt): + statement (stmt), next (this) + { kind = K; } + + StatementList(StatementList *previous, Statement *stmt): + statement (stmt) + { + kind = K; + next = previous->next; + previous->next = this; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return statement->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return next ? next->lastSourceLocation() : statement->lastSourceLocation(); } + + inline StatementList *finish () + { + StatementList *front = next; + next = nullptr; + return front; + } + +// attributes + Statement *statement; + StatementList *next; +}; + +class QML_PARSER_EXPORT VariableStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(VariableStatement) + + VariableStatement(VariableDeclarationList *vlist): + declarations (vlist) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return declarationKindToken; } + + SourceLocation lastSourceLocation() const override + { return semicolonToken; } + +// attributes + VariableDeclarationList *declarations; + SourceLocation declarationKindToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT VariableDeclaration: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(VariableDeclaration) + + VariableDeclaration(const QStringRef &n, ExpressionNode *e): + name (n), expression (e), readOnly(false) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return identifierToken; } + + SourceLocation lastSourceLocation() const override + { return expression ? expression->lastSourceLocation() : identifierToken; } + +// attributes + QStringRef name; + ExpressionNode *expression; + bool readOnly; + SourceLocation identifierToken; +}; + +class QML_PARSER_EXPORT VariableDeclarationList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(VariableDeclarationList) + + VariableDeclarationList(VariableDeclaration *decl): + declaration (decl), next (this) + { kind = K; } + + VariableDeclarationList(VariableDeclarationList *previous, VariableDeclaration *decl): + declaration (decl) + { + kind = K; + next = previous->next; + previous->next = this; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return declaration->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { + if (next) + return next->lastSourceLocation(); + return declaration->lastSourceLocation(); + } + + inline VariableDeclarationList *finish (bool readOnly) + { + VariableDeclarationList *front = next; + next = nullptr; + if (readOnly) { + VariableDeclarationList *vdl; + for (vdl = front; vdl != nullptr; vdl = vdl->next) + vdl->declaration->readOnly = true; + } + return front; + } + +// attributes + VariableDeclaration *declaration; + VariableDeclarationList *next; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT EmptyStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(EmptyStatement) + + EmptyStatement() { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return semicolonToken; } + + SourceLocation lastSourceLocation() const override + { return semicolonToken; } + +// attributes + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT ExpressionStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(ExpressionStatement) + + ExpressionStatement(ExpressionNode *e): + expression (e) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return expression->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return semicolonToken; } + +// attributes + ExpressionNode *expression; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT IfStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(IfStatement) + + IfStatement(ExpressionNode *e, Statement *t, Statement *f = 0): + expression (e), ok (t), ko (f) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return ifToken; } + + SourceLocation lastSourceLocation() const override + { + if (ko) + return ko->lastSourceLocation(); + + return ok->lastSourceLocation(); + } + +// attributes + ExpressionNode *expression; + Statement *ok; + Statement *ko; + SourceLocation ifToken; + SourceLocation lparenToken; + SourceLocation rparenToken; + SourceLocation elseToken; +}; + +class QML_PARSER_EXPORT DoWhileStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(DoWhileStatement) + + DoWhileStatement(Statement *stmt, ExpressionNode *e): + statement (stmt), expression (e) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return doToken; } + + SourceLocation lastSourceLocation() const override + { return semicolonToken; } + +// attributes + Statement *statement; + ExpressionNode *expression; + SourceLocation doToken; + SourceLocation whileToken; + SourceLocation lparenToken; + SourceLocation rparenToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT WhileStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(WhileStatement) + + WhileStatement(ExpressionNode *e, Statement *stmt): + expression (e), statement (stmt) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return whileToken; } + + SourceLocation lastSourceLocation() const override + { return statement->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + Statement *statement; + SourceLocation whileToken; + SourceLocation lparenToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT ForStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(ForStatement) + + ForStatement(ExpressionNode *i, ExpressionNode *c, ExpressionNode *e, Statement *stmt): + initialiser (i), condition (c), expression (e), statement (stmt) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return forToken; } + + SourceLocation lastSourceLocation() const override + { return statement->lastSourceLocation(); } + +// attributes + ExpressionNode *initialiser; + ExpressionNode *condition; + ExpressionNode *expression; + Statement *statement; + SourceLocation forToken; + SourceLocation lparenToken; + SourceLocation firstSemicolonToken; + SourceLocation secondSemicolonToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT LocalForStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(LocalForStatement) + + LocalForStatement(VariableDeclarationList *vlist, ExpressionNode *c, ExpressionNode *e, Statement *stmt): + declarations (vlist), condition (c), expression (e), statement (stmt) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return forToken; } + + SourceLocation lastSourceLocation() const override + { return statement->lastSourceLocation(); } + +// attributes + VariableDeclarationList *declarations; + ExpressionNode *condition; + ExpressionNode *expression; + Statement *statement; + SourceLocation forToken; + SourceLocation lparenToken; + SourceLocation varToken; + SourceLocation firstSemicolonToken; + SourceLocation secondSemicolonToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT ForEachStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(ForEachStatement) + + ForEachStatement(ExpressionNode *i, ExpressionNode *e, Statement *stmt): + initialiser (i), expression (e), statement (stmt) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return forToken; } + + SourceLocation lastSourceLocation() const override + { return statement->lastSourceLocation(); } + +// attributes + ExpressionNode *initialiser; + ExpressionNode *expression; + Statement *statement; + SourceLocation forToken; + SourceLocation lparenToken; + SourceLocation inToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT LocalForEachStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(LocalForEachStatement) + + LocalForEachStatement(VariableDeclaration *v, ExpressionNode *e, Statement *stmt): + declaration (v), expression (e), statement (stmt) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return forToken; } + + SourceLocation lastSourceLocation() const override + { return statement->lastSourceLocation(); } + +// attributes + VariableDeclaration *declaration; + ExpressionNode *expression; + Statement *statement; + SourceLocation forToken; + SourceLocation lparenToken; + SourceLocation varToken; + SourceLocation inToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT ContinueStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(ContinueStatement) + + ContinueStatement(const QStringRef &l = QStringRef()): + label (l) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return continueToken; } + + SourceLocation lastSourceLocation() const override + { return semicolonToken; } + +// attributes + QStringRef label; + SourceLocation continueToken; + SourceLocation identifierToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT BreakStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(BreakStatement) + + BreakStatement(const QStringRef &l): + label (l) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return breakToken; } + + SourceLocation lastSourceLocation() const override + { return semicolonToken; } + + // attributes + QStringRef label; + SourceLocation breakToken; + SourceLocation identifierToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT ReturnStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(ReturnStatement) + + ReturnStatement(ExpressionNode *e): + expression (e) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return returnToken; } + + SourceLocation lastSourceLocation() const override + { return semicolonToken; } + +// attributes + ExpressionNode *expression; + SourceLocation returnToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT WithStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(WithStatement) + + WithStatement(ExpressionNode *e, Statement *stmt): + expression (e), statement (stmt) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return withToken; } + + SourceLocation lastSourceLocation() const override + { return statement->lastSourceLocation(); } + +// attributes + ExpressionNode *expression; + Statement *statement; + SourceLocation withToken; + SourceLocation lparenToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT CaseBlock: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(CaseBlock) + + CaseBlock(CaseClauses *c, DefaultClause *d = 0, CaseClauses *r = 0): + clauses (c), defaultClause (d), moreClauses (r) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return lbraceToken; } + + SourceLocation lastSourceLocation() const override + { return rbraceToken; } + +// attributes + CaseClauses *clauses; + DefaultClause *defaultClause; + CaseClauses *moreClauses; + SourceLocation lbraceToken; + SourceLocation rbraceToken; +}; + +class QML_PARSER_EXPORT SwitchStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(SwitchStatement) + + SwitchStatement(ExpressionNode *e, CaseBlock *b): + expression (e), block (b) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return switchToken; } + + SourceLocation lastSourceLocation() const override + { return block->rbraceToken; } + +// attributes + ExpressionNode *expression; + CaseBlock *block; + SourceLocation switchToken; + SourceLocation lparenToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT CaseClause: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(CaseClause) + + CaseClause(ExpressionNode *e, StatementList *slist): + expression (e), statements (slist) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return caseToken; } + + SourceLocation lastSourceLocation() const override + { return statements ? statements->lastSourceLocation() : colonToken; } + +// attributes + ExpressionNode *expression; + StatementList *statements; + SourceLocation caseToken; + SourceLocation colonToken; +}; + +class QML_PARSER_EXPORT CaseClauses: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(CaseClauses) + + CaseClauses(CaseClause *c): + clause (c), next (this) + { kind = K; } + + CaseClauses(CaseClauses *previous, CaseClause *c): + clause (c) + { + kind = K; + next = previous->next; + previous->next = this; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return clause->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return next ? next->lastSourceLocation() : clause->lastSourceLocation(); } + + inline CaseClauses *finish () + { + CaseClauses *front = next; + next = nullptr; + return front; + } + +//attributes + CaseClause *clause; + CaseClauses *next; +}; + +class QML_PARSER_EXPORT DefaultClause: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(DefaultClause) + + DefaultClause(StatementList *slist): + statements (slist) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return defaultToken; } + + SourceLocation lastSourceLocation() const override + { return statements ? statements->lastSourceLocation() : colonToken; } + +// attributes + StatementList *statements; + SourceLocation defaultToken; + SourceLocation colonToken; +}; + +class QML_PARSER_EXPORT LabelledStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(LabelledStatement) + + LabelledStatement(const QStringRef &l, Statement *stmt): + label (l), statement (stmt) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return identifierToken; } + + SourceLocation lastSourceLocation() const override + { return statement->lastSourceLocation(); } + +// attributes + QStringRef label; + Statement *statement; + SourceLocation identifierToken; + SourceLocation colonToken; +}; + +class QML_PARSER_EXPORT ThrowStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(ThrowStatement) + + ThrowStatement(ExpressionNode *e): + expression (e) { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return throwToken; } + + SourceLocation lastSourceLocation() const override + { return semicolonToken; } + + // attributes + ExpressionNode *expression; + SourceLocation throwToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT Catch: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(Catch) + + Catch(const QStringRef &n, Block *stmt): + name (n), statement (stmt) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return catchToken; } + + SourceLocation lastSourceLocation() const override + { return statement->lastSourceLocation(); } + +// attributes + QStringRef name; + Block *statement; + SourceLocation catchToken; + SourceLocation lparenToken; + SourceLocation identifierToken; + SourceLocation rparenToken; +}; + +class QML_PARSER_EXPORT Finally: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(Finally) + + Finally(Block *stmt): + statement (stmt) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return finallyToken; } + + SourceLocation lastSourceLocation() const override + { return statement ? statement->lastSourceLocation() : finallyToken; } + +// attributes + Block *statement; + SourceLocation finallyToken; +}; + +class QML_PARSER_EXPORT TryStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(TryStatement) + + TryStatement(Statement *stmt, Catch *c, Finally *f): + statement (stmt), catchExpression (c), finallyExpression (f) + { kind = K; } + + TryStatement(Statement *stmt, Finally *f): + statement (stmt), catchExpression (nullptr), finallyExpression (f) + { kind = K; } + + TryStatement(Statement *stmt, Catch *c): + statement (stmt), catchExpression (c), finallyExpression (nullptr) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return tryToken; } + + SourceLocation lastSourceLocation() const override + { + if (finallyExpression) + return finallyExpression->statement->rbraceToken; + else if (catchExpression) + return catchExpression->statement->rbraceToken; + + return statement->lastSourceLocation(); + } + +// attributes + Statement *statement; + Catch *catchExpression; + Finally *finallyExpression; + SourceLocation tryToken; +}; + +class QML_PARSER_EXPORT FunctionExpression: public ExpressionNode +{ +public: + QMLJS_DECLARE_AST_NODE(FunctionExpression) + + FunctionExpression(const QStringRef &n, FormalParameterList *f, FunctionBody *b): + name (n), formals (f), body (b) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return functionToken; } + + SourceLocation lastSourceLocation() const override + { return rbraceToken; } + +// attributes + QStringRef name; + FormalParameterList *formals; + FunctionBody *body; + SourceLocation functionToken; + SourceLocation identifierToken; + SourceLocation lparenToken; + SourceLocation rparenToken; + SourceLocation lbraceToken; + SourceLocation rbraceToken; +}; + +class QML_PARSER_EXPORT FunctionDeclaration: public FunctionExpression +{ +public: + QMLJS_DECLARE_AST_NODE(FunctionDeclaration) + + FunctionDeclaration(const QStringRef &n, FormalParameterList *f, FunctionBody *b): + FunctionExpression(n, f, b) + { kind = K; } + + void accept0(Visitor *visitor) override; +}; + +class QML_PARSER_EXPORT FormalParameterList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(FormalParameterList) + + FormalParameterList(const QStringRef &n): + name (n), next (this) + { kind = K; } + + FormalParameterList(FormalParameterList *previous, const QStringRef &n): + name (n) + { + kind = K; + next = previous->next; + previous->next = this; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return identifierToken; } + + SourceLocation lastSourceLocation() const override + { return next ? next->lastSourceLocation() : identifierToken; } + + inline FormalParameterList *finish () + { + FormalParameterList *front = next; + next = nullptr; + return front; + } + +// attributes + QStringRef name; + FormalParameterList *next; + SourceLocation commaToken; + SourceLocation identifierToken; +}; + +class QML_PARSER_EXPORT SourceElement: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(SourceElement) + + inline SourceElement() + { kind = K; } +}; + +class QML_PARSER_EXPORT SourceElements: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(SourceElements) + + SourceElements(SourceElement *elt): + element (elt), next (this) + { kind = K; } + + SourceElements(SourceElements *previous, SourceElement *elt): + element (elt) + { + kind = K; + next = previous->next; + previous->next = this; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return element->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return next ? next->lastSourceLocation() : element->lastSourceLocation(); } + + inline SourceElements *finish () + { + SourceElements *front = next; + next = nullptr; + return front; + } + +// attributes + SourceElement *element; + SourceElements *next; +}; + +class QML_PARSER_EXPORT FunctionBody: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(FunctionBody) + + FunctionBody(SourceElements *elts): + elements (elts) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return elements ? elements->firstSourceLocation() : SourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return elements ? elements->lastSourceLocation() : SourceLocation(); } + +// attributes + SourceElements *elements; +}; + +class QML_PARSER_EXPORT Program: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(Program) + + Program(SourceElements *elts): + elements (elts) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return elements ? elements->firstSourceLocation() : SourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return elements ? elements->lastSourceLocation() : SourceLocation(); } + +// attributes + SourceElements *elements; +}; + +class QML_PARSER_EXPORT FunctionSourceElement: public SourceElement +{ +public: + QMLJS_DECLARE_AST_NODE(FunctionSourceElement) + + FunctionSourceElement(FunctionDeclaration *f): + declaration (f) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return declaration->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return declaration->lastSourceLocation(); } + +// attributes + FunctionDeclaration *declaration; +}; + +class QML_PARSER_EXPORT StatementSourceElement: public SourceElement +{ +public: + QMLJS_DECLARE_AST_NODE(StatementSourceElement) + + StatementSourceElement(Statement *stmt): + statement (stmt) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return statement->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return statement->lastSourceLocation(); } + +// attributes + Statement *statement; +}; + +class QML_PARSER_EXPORT DebuggerStatement: public Statement +{ +public: + QMLJS_DECLARE_AST_NODE(DebuggerStatement) + + DebuggerStatement() + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return debuggerToken; } + + SourceLocation lastSourceLocation() const override + { return semicolonToken; } + +// attributes + SourceLocation debuggerToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT UiQualifiedId: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiQualifiedId) + + UiQualifiedId(const QStringRef &name) + : next(this), name(name) + { kind = K; } + + UiQualifiedId(UiQualifiedId *previous, const QStringRef &name) + : name(name) + { + kind = K; + next = previous->next; + previous->next = this; + } + + UiQualifiedId *finish() + { + UiQualifiedId *head = next; + next = nullptr; + return head; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return identifierToken; } + + SourceLocation lastSourceLocation() const override + { return next ? next->lastSourceLocation() : identifierToken; } + +// attributes + UiQualifiedId *next; + QStringRef name; + SourceLocation identifierToken; +}; + +class QML_PARSER_EXPORT UiImport: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiImport) + + UiImport(const QStringRef &fileName) + : fileName(fileName), importUri(nullptr) + { kind = K; } + + UiImport(UiQualifiedId *uri) + : importUri(uri) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return importToken; } + + SourceLocation lastSourceLocation() const override + { return semicolonToken; } + +// attributes + QStringRef fileName; + UiQualifiedId *importUri; + QStringRef importId; + SourceLocation importToken; + SourceLocation fileNameToken; + SourceLocation versionToken; + SourceLocation asToken; + SourceLocation importIdToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT UiImportList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiImportList) + + UiImportList(UiImport *import) + : import(import), + next(this) + { kind = K; } + + UiImportList(UiImportList *previous, UiImport *import) + : import(import) + { + kind = K; + next = previous->next; + previous->next = this; + } + + UiImportList *finish() + { + UiImportList *head = next; + next = nullptr; + return head; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return import->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return next ? next->lastSourceLocation() : import->lastSourceLocation(); } + +// attributes + UiImport *import; + UiImportList *next; +}; + +class QML_PARSER_EXPORT UiObjectMember: public Node +{ +public: + SourceLocation firstSourceLocation() const override = 0; + SourceLocation lastSourceLocation() const override = 0; + + UiObjectMember *uiObjectMemberCast() override; +}; + +class QML_PARSER_EXPORT UiObjectMemberList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiObjectMemberList) + + UiObjectMemberList(UiObjectMember *member) + : next(this), member(member) + { kind = K; } + + UiObjectMemberList(UiObjectMemberList *previous, UiObjectMember *member) + : member(member) + { + kind = K; + next = previous->next; + previous->next = this; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return member->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return next ? next->lastSourceLocation() : member->lastSourceLocation(); } + + UiObjectMemberList *finish() + { + UiObjectMemberList *head = next; + next = nullptr; + return head; + } + +// attributes + UiObjectMemberList *next; + UiObjectMember *member; +}; + +class QML_PARSER_EXPORT UiProgram: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiProgram) + + UiProgram(UiImportList *imports, UiObjectMemberList *members) + : imports(imports), members(members) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { + if (imports) + return imports->firstSourceLocation(); + else if (members) + return members->firstSourceLocation(); + return {}; + } + + SourceLocation lastSourceLocation() const override + { + if (members) + return members->lastSourceLocation(); + else if (imports) + return imports->lastSourceLocation(); + return {}; + } + +// attributes + UiImportList *imports; + UiObjectMemberList *members; +}; + +class QML_PARSER_EXPORT UiArrayMemberList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiArrayMemberList) + + UiArrayMemberList(UiObjectMember *member) + : next(this), member(member) + { kind = K; } + + UiArrayMemberList(UiArrayMemberList *previous, UiObjectMember *member) + : member(member) + { + kind = K; + next = previous->next; + previous->next = this; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return member->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return next ? next->lastSourceLocation() : member->lastSourceLocation(); } + + UiArrayMemberList *finish() + { + UiArrayMemberList *head = next; + next = nullptr; + return head; + } + +// attributes + UiArrayMemberList *next; + UiObjectMember *member; + SourceLocation commaToken; +}; + +class QML_PARSER_EXPORT UiObjectInitializer: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiObjectInitializer) + + UiObjectInitializer(UiObjectMemberList *members) + : members(members) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return lbraceToken; } + + SourceLocation lastSourceLocation() const override + { return rbraceToken; } + +// attributes + SourceLocation lbraceToken; + UiObjectMemberList *members; + SourceLocation rbraceToken; +}; + +class QML_PARSER_EXPORT UiParameterList: public Node +{ +public: + QMLJS_DECLARE_AST_NODE(UiParameterList) + + UiParameterList(const QStringRef &t, const QStringRef &n): + type (t), name (n), next (this) + { kind = K; } + + UiParameterList(UiParameterList *previous, const QStringRef &t, const QStringRef &n): + type (t), name (n) + { + kind = K; + next = previous->next; + previous->next = this; + } + + void accept0(Visitor *) override {} + + SourceLocation firstSourceLocation() const override + { return propertyTypeToken; } + + SourceLocation lastSourceLocation() const override + { return next ? next->lastSourceLocation() : identifierToken; } + + inline UiParameterList *finish () + { + UiParameterList *front = next; + next = nullptr; + return front; + } + +// attributes + QStringRef type; + QStringRef name; + UiParameterList *next; + SourceLocation commaToken; + SourceLocation propertyTypeToken; + SourceLocation identifierToken; +}; + +class QML_PARSER_EXPORT UiPublicMember: public UiObjectMember +{ +public: + QMLJS_DECLARE_AST_NODE(UiPublicMember) + + UiPublicMember(const QStringRef &memberType, + const QStringRef &name) + : type(Property), memberType(memberType), name(name), statement(nullptr), binding(nullptr), isDefaultMember(false), isReadonlyMember(false), parameters(nullptr) + { kind = K; } + + UiPublicMember(const QStringRef &memberType, + const QStringRef &name, + Statement *statement) + : type(Property), memberType(memberType), name(name), statement(statement), binding(nullptr), isDefaultMember(false), isReadonlyMember(false), parameters(nullptr) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { + if (defaultToken.isValid()) + return defaultToken; + else if (readonlyToken.isValid()) + return readonlyToken; + + return propertyToken; + } + + SourceLocation lastSourceLocation() const override + { + if (binding) + return binding->lastSourceLocation(); + if (statement) + return statement->lastSourceLocation(); + + return semicolonToken; + } + +// attributes + enum { Signal, Property } type; + QStringRef typeModifier; + QStringRef memberType; + QStringRef name; + Statement *statement; // initialized with a JS expression + UiObjectMember *binding; // initialized with a QML object or array. + bool isDefaultMember; + bool isReadonlyMember; + UiParameterList *parameters; + SourceLocation defaultToken; + SourceLocation readonlyToken; + SourceLocation propertyToken; + SourceLocation typeModifierToken; + SourceLocation typeToken; + SourceLocation identifierToken; + SourceLocation colonToken; + SourceLocation semicolonToken; +}; + +class QML_PARSER_EXPORT UiObjectDefinition: public UiObjectMember +{ +public: + QMLJS_DECLARE_AST_NODE(UiObjectDefinition) + + UiObjectDefinition(UiQualifiedId *qualifiedTypeNameId, + UiObjectInitializer *initializer) + : qualifiedTypeNameId(qualifiedTypeNameId), initializer(initializer) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return qualifiedTypeNameId->identifierToken; } + + SourceLocation lastSourceLocation() const override + { return initializer->rbraceToken; } + +// attributes + UiQualifiedId *qualifiedTypeNameId; + UiObjectInitializer *initializer; +}; + +class QML_PARSER_EXPORT UiSourceElement: public UiObjectMember +{ +public: + QMLJS_DECLARE_AST_NODE(UiSourceElement) + + UiSourceElement(Node *sourceElement) + : sourceElement(sourceElement) + { kind = K; } + + SourceLocation firstSourceLocation() const override + { + if (const auto funDecl = cast(sourceElement)) + return funDecl->firstSourceLocation(); + else if (const auto varStmt = cast(sourceElement)) + return varStmt->firstSourceLocation(); + + return {}; + } + + SourceLocation lastSourceLocation() const override + { + if (const auto funDecl = cast(sourceElement)) + return funDecl->lastSourceLocation(); + else if (const auto varStmt = cast(sourceElement)) + return varStmt->lastSourceLocation(); + + return {}; + } + + void accept0(Visitor *visitor) override; + + +// attributes + Node *sourceElement; +}; + +class QML_PARSER_EXPORT UiObjectBinding: public UiObjectMember +{ +public: + QMLJS_DECLARE_AST_NODE(UiObjectBinding) + + UiObjectBinding(UiQualifiedId *qualifiedId, + UiQualifiedId *qualifiedTypeNameId, + UiObjectInitializer *initializer) + : qualifiedId(qualifiedId), + qualifiedTypeNameId(qualifiedTypeNameId), + initializer(initializer), + hasOnToken(false) + { kind = K; } + + SourceLocation firstSourceLocation() const override + { + if (hasOnToken && qualifiedTypeNameId) + return qualifiedTypeNameId->identifierToken; + + return qualifiedId->identifierToken; + } + + SourceLocation lastSourceLocation() const override + { return initializer->rbraceToken; } + + void accept0(Visitor *visitor) override; + + +// attributes + UiQualifiedId *qualifiedId; + UiQualifiedId *qualifiedTypeNameId; + UiObjectInitializer *initializer; + SourceLocation colonToken; + bool hasOnToken; +}; + +class QML_PARSER_EXPORT UiScriptBinding: public UiObjectMember +{ +public: + QMLJS_DECLARE_AST_NODE(UiScriptBinding) + + UiScriptBinding(UiQualifiedId *qualifiedId, + Statement *statement) + : qualifiedId(qualifiedId), + statement(statement) + { kind = K; } + + SourceLocation firstSourceLocation() const override + { return qualifiedId->identifierToken; } + + SourceLocation lastSourceLocation() const override + { return statement->lastSourceLocation(); } + + void accept0(Visitor *visitor) override; + +// attributes + UiQualifiedId *qualifiedId; + Statement *statement; + SourceLocation colonToken; +}; + +class QML_PARSER_EXPORT UiArrayBinding: public UiObjectMember +{ +public: + QMLJS_DECLARE_AST_NODE(UiArrayBinding) + + UiArrayBinding(UiQualifiedId *qualifiedId, + UiArrayMemberList *members) + : qualifiedId(qualifiedId), + members(members) + { kind = K; } + + SourceLocation firstSourceLocation() const override + { return qualifiedId->identifierToken; } + + SourceLocation lastSourceLocation() const override + { return rbracketToken; } + + void accept0(Visitor *visitor) override; + +// attributes + UiQualifiedId *qualifiedId; + UiArrayMemberList *members; + SourceLocation colonToken; + SourceLocation lbracketToken; + SourceLocation rbracketToken; +}; + +} // namespace AST +} // namespace QbsQmlJS + +#endif diff --git a/src/lib/corelib/parser/qmljsastfwd_p.h b/src/lib/corelib/parser/qmljsastfwd_p.h new file mode 100644 index 00000000..f032f352 --- /dev/null +++ b/src/lib/corelib/parser/qmljsastfwd_p.h @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** 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 QMLJSAST_FWD_P_H +#define QMLJSAST_FWD_P_H + +#include "qmljsglobal_p.h" + +#include + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +namespace QbsQmlJS { +namespace AST { + +class SourceLocation +{ +public: + SourceLocation(quint32 offset = 0, quint32 length = 0, quint32 line = 0, quint32 column = 0) + : offset(offset), length(length), + startLine(line), startColumn(column) + { } + + bool isValid() const { return length != 0; } + + quint32 begin() const { return offset; } + quint32 end() const { return offset + length; } + +// attributes + // ### encode + quint32 offset; + quint32 length; + quint32 startLine; + quint32 startColumn; +}; + +class Visitor; +class Node; +class ExpressionNode; +class Statement; +class ThisExpression; +class IdentifierExpression; +class NullExpression; +class TrueLiteral; +class FalseLiteral; +class NumericLiteral; +class StringLiteral; +class RegExpLiteral; +class ArrayLiteral; +class ObjectLiteral; +class ElementList; +class Elision; +class PropertyNameAndValueList; +class PropertyName; +class IdentifierPropertyName; +class StringLiteralPropertyName; +class NumericLiteralPropertyName; +class ArrayMemberExpression; +class FieldMemberExpression; +class NewMemberExpression; +class NewExpression; +class CallExpression; +class ArgumentList; +class PostIncrementExpression; +class PostDecrementExpression; +class DeleteExpression; +class VoidExpression; +class TypeOfExpression; +class PreIncrementExpression; +class PreDecrementExpression; +class UnaryPlusExpression; +class UnaryMinusExpression; +class TildeExpression; +class NotExpression; +class BinaryExpression; +class ConditionalExpression; +class Expression; // ### rename +class Block; +class StatementList; +class VariableStatement; +class VariableDeclarationList; +class VariableDeclaration; +class EmptyStatement; +class ExpressionStatement; +class IfStatement; +class DoWhileStatement; +class WhileStatement; +class ForStatement; +class LocalForStatement; +class ForEachStatement; +class LocalForEachStatement; +class ContinueStatement; +class BreakStatement; +class ReturnStatement; +class WithStatement; +class SwitchStatement; +class CaseBlock; +class CaseClauses; +class CaseClause; +class DefaultClause; +class LabelledStatement; +class ThrowStatement; +class TryStatement; +class Catch; +class Finally; +class FunctionDeclaration; +class FunctionExpression; +class FormalParameterList; +class FunctionBody; +class Program; +class SourceElements; +class SourceElement; +class FunctionSourceElement; +class StatementSourceElement; +class DebuggerStatement; +class NestedExpression; + +// ui elements +class UiProgram; +class UiImportList; +class UiImport; +class UiPublicMember; +class UiObjectDefinition; +class UiObjectInitializer; +class UiObjectBinding; +class UiScriptBinding; +class UiSourceElement; +class UiArrayBinding; +class UiObjectMember; +class UiObjectMemberList; +class UiArrayMemberList; +class UiQualifiedId; + +} // namespace AST +} // namespace QbsQmlJS + +#endif diff --git a/src/lib/corelib/parser/qmljsastvisitor.cpp b/src/lib/corelib/parser/qmljsastvisitor.cpp new file mode 100644 index 00000000..f034a147 --- /dev/null +++ b/src/lib/corelib/parser/qmljsastvisitor.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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 "qmljsastvisitor_p.h" + +namespace QbsQmlJS { +namespace AST { + +Visitor::Visitor() = default; + +Visitor::~Visitor() = default; + +} // namespace AST +} // namespace QbsQmlJS diff --git a/src/lib/corelib/parser/qmljsastvisitor_p.h b/src/lib/corelib/parser/qmljsastvisitor_p.h new file mode 100644 index 00000000..bec174c6 --- /dev/null +++ b/src/lib/corelib/parser/qmljsastvisitor_p.h @@ -0,0 +1,326 @@ +/**************************************************************************** +** +** 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 QMLJSASTVISITOR_P_H +#define QMLJSASTVISITOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qmljsastfwd_p.h" +#include "qmljsglobal_p.h" +#include + +namespace QbsQmlJS { +namespace AST { + +class QBS_AUTOTEST_EXPORT Visitor +{ +public: + Visitor(); + virtual ~Visitor(); + + virtual bool preVisit(Node *) { return true; } + virtual void postVisit(Node *) {} + + // Ui + virtual bool visit(UiProgram *) { return true; } + virtual bool visit(UiImportList *) { return true; } + virtual bool visit(UiImport *) { return true; } + virtual bool visit(UiPublicMember *) { return true; } + virtual bool visit(UiSourceElement *) { return true; } + virtual bool visit(UiObjectDefinition *) { return true; } + virtual bool visit(UiObjectInitializer *) { return true; } + virtual bool visit(UiObjectBinding *) { return true; } + virtual bool visit(UiScriptBinding *) { return true; } + virtual bool visit(UiArrayBinding *) { return true; } + virtual bool visit(UiObjectMemberList *) { return true; } + virtual bool visit(UiArrayMemberList *) { return true; } + virtual bool visit(UiQualifiedId *) { return true; } + + virtual void endVisit(UiProgram *) {} + virtual void endVisit(UiImportList *) {} + virtual void endVisit(UiImport *) {} + virtual void endVisit(UiPublicMember *) {} + virtual void endVisit(UiSourceElement *) {} + virtual void endVisit(UiObjectDefinition *) {} + virtual void endVisit(UiObjectInitializer *) {} + virtual void endVisit(UiObjectBinding *) {} + virtual void endVisit(UiScriptBinding *) {} + virtual void endVisit(UiArrayBinding *) {} + virtual void endVisit(UiObjectMemberList *) {} + virtual void endVisit(UiArrayMemberList *) {} + virtual void endVisit(UiQualifiedId *) {} + + // QbsQmlJS + virtual bool visit(ThisExpression *) { return true; } + virtual void endVisit(ThisExpression *) {} + + virtual bool visit(IdentifierExpression *) { return true; } + virtual void endVisit(IdentifierExpression *) {} + + virtual bool visit(NullExpression *) { return true; } + virtual void endVisit(NullExpression *) {} + + virtual bool visit(TrueLiteral *) { return true; } + virtual void endVisit(TrueLiteral *) {} + + virtual bool visit(FalseLiteral *) { return true; } + virtual void endVisit(FalseLiteral *) {} + + virtual bool visit(StringLiteral *) { return true; } + virtual void endVisit(StringLiteral *) {} + + virtual bool visit(NumericLiteral *) { return true; } + virtual void endVisit(NumericLiteral *) {} + + virtual bool visit(RegExpLiteral *) { return true; } + virtual void endVisit(RegExpLiteral *) {} + + virtual bool visit(ArrayLiteral *) { return true; } + virtual void endVisit(ArrayLiteral *) {} + + virtual bool visit(ObjectLiteral *) { return true; } + virtual void endVisit(ObjectLiteral *) {} + + virtual bool visit(ElementList *) { return true; } + virtual void endVisit(ElementList *) {} + + virtual bool visit(Elision *) { return true; } + virtual void endVisit(Elision *) {} + + virtual bool visit(PropertyNameAndValueList *) { return true; } + virtual void endVisit(PropertyNameAndValueList *) {} + + virtual bool visit(NestedExpression *) { return true; } + virtual void endVisit(NestedExpression *) {} + + virtual bool visit(IdentifierPropertyName *) { return true; } + virtual void endVisit(IdentifierPropertyName *) {} + + virtual bool visit(StringLiteralPropertyName *) { return true; } + virtual void endVisit(StringLiteralPropertyName *) {} + + virtual bool visit(NumericLiteralPropertyName *) { return true; } + virtual void endVisit(NumericLiteralPropertyName *) {} + + virtual bool visit(ArrayMemberExpression *) { return true; } + virtual void endVisit(ArrayMemberExpression *) {} + + virtual bool visit(FieldMemberExpression *) { return true; } + virtual void endVisit(FieldMemberExpression *) {} + + virtual bool visit(NewMemberExpression *) { return true; } + virtual void endVisit(NewMemberExpression *) {} + + virtual bool visit(NewExpression *) { return true; } + virtual void endVisit(NewExpression *) {} + + virtual bool visit(CallExpression *) { return true; } + virtual void endVisit(CallExpression *) {} + + virtual bool visit(ArgumentList *) { return true; } + virtual void endVisit(ArgumentList *) {} + + virtual bool visit(PostIncrementExpression *) { return true; } + virtual void endVisit(PostIncrementExpression *) {} + + virtual bool visit(PostDecrementExpression *) { return true; } + virtual void endVisit(PostDecrementExpression *) {} + + virtual bool visit(DeleteExpression *) { return true; } + virtual void endVisit(DeleteExpression *) {} + + virtual bool visit(VoidExpression *) { return true; } + virtual void endVisit(VoidExpression *) {} + + virtual bool visit(TypeOfExpression *) { return true; } + virtual void endVisit(TypeOfExpression *) {} + + virtual bool visit(PreIncrementExpression *) { return true; } + virtual void endVisit(PreIncrementExpression *) {} + + virtual bool visit(PreDecrementExpression *) { return true; } + virtual void endVisit(PreDecrementExpression *) {} + + virtual bool visit(UnaryPlusExpression *) { return true; } + virtual void endVisit(UnaryPlusExpression *) {} + + virtual bool visit(UnaryMinusExpression *) { return true; } + virtual void endVisit(UnaryMinusExpression *) {} + + virtual bool visit(TildeExpression *) { return true; } + virtual void endVisit(TildeExpression *) {} + + virtual bool visit(NotExpression *) { return true; } + virtual void endVisit(NotExpression *) {} + + virtual bool visit(BinaryExpression *) { return true; } + virtual void endVisit(BinaryExpression *) {} + + virtual bool visit(ConditionalExpression *) { return true; } + virtual void endVisit(ConditionalExpression *) {} + + virtual bool visit(Expression *) { return true; } + virtual void endVisit(Expression *) {} + + virtual bool visit(Block *) { return true; } + virtual void endVisit(Block *) {} + + virtual bool visit(StatementList *) { return true; } + virtual void endVisit(StatementList *) {} + + virtual bool visit(VariableStatement *) { return true; } + virtual void endVisit(VariableStatement *) {} + + virtual bool visit(VariableDeclarationList *) { return true; } + virtual void endVisit(VariableDeclarationList *) {} + + virtual bool visit(VariableDeclaration *) { return true; } + virtual void endVisit(VariableDeclaration *) {} + + virtual bool visit(EmptyStatement *) { return true; } + virtual void endVisit(EmptyStatement *) {} + + virtual bool visit(ExpressionStatement *) { return true; } + virtual void endVisit(ExpressionStatement *) {} + + virtual bool visit(IfStatement *) { return true; } + virtual void endVisit(IfStatement *) {} + + virtual bool visit(DoWhileStatement *) { return true; } + virtual void endVisit(DoWhileStatement *) {} + + virtual bool visit(WhileStatement *) { return true; } + virtual void endVisit(WhileStatement *) {} + + virtual bool visit(ForStatement *) { return true; } + virtual void endVisit(ForStatement *) {} + + virtual bool visit(LocalForStatement *) { return true; } + virtual void endVisit(LocalForStatement *) {} + + virtual bool visit(ForEachStatement *) { return true; } + virtual void endVisit(ForEachStatement *) {} + + virtual bool visit(LocalForEachStatement *) { return true; } + virtual void endVisit(LocalForEachStatement *) {} + + virtual bool visit(ContinueStatement *) { return true; } + virtual void endVisit(ContinueStatement *) {} + + virtual bool visit(BreakStatement *) { return true; } + virtual void endVisit(BreakStatement *) {} + + virtual bool visit(ReturnStatement *) { return true; } + virtual void endVisit(ReturnStatement *) {} + + virtual bool visit(WithStatement *) { return true; } + virtual void endVisit(WithStatement *) {} + + virtual bool visit(SwitchStatement *) { return true; } + virtual void endVisit(SwitchStatement *) {} + + virtual bool visit(CaseBlock *) { return true; } + virtual void endVisit(CaseBlock *) {} + + virtual bool visit(CaseClauses *) { return true; } + virtual void endVisit(CaseClauses *) {} + + virtual bool visit(CaseClause *) { return true; } + virtual void endVisit(CaseClause *) {} + + virtual bool visit(DefaultClause *) { return true; } + virtual void endVisit(DefaultClause *) {} + + virtual bool visit(LabelledStatement *) { return true; } + virtual void endVisit(LabelledStatement *) {} + + virtual bool visit(ThrowStatement *) { return true; } + virtual void endVisit(ThrowStatement *) {} + + virtual bool visit(TryStatement *) { return true; } + virtual void endVisit(TryStatement *) {} + + virtual bool visit(Catch *) { return true; } + virtual void endVisit(Catch *) {} + + virtual bool visit(Finally *) { return true; } + virtual void endVisit(Finally *) {} + + virtual bool visit(FunctionDeclaration *) { return true; } + virtual void endVisit(FunctionDeclaration *) {} + + virtual bool visit(FunctionExpression *) { return true; } + virtual void endVisit(FunctionExpression *) {} + + virtual bool visit(FormalParameterList *) { return true; } + virtual void endVisit(FormalParameterList *) {} + + virtual bool visit(FunctionBody *) { return true; } + virtual void endVisit(FunctionBody *) {} + + virtual bool visit(Program *) { return true; } + virtual void endVisit(Program *) {} + + virtual bool visit(SourceElements *) { return true; } + virtual void endVisit(SourceElements *) {} + + virtual bool visit(FunctionSourceElement *) { return true; } + virtual void endVisit(FunctionSourceElement *) {} + + virtual bool visit(StatementSourceElement *) { return true; } + virtual void endVisit(StatementSourceElement *) {} + + virtual bool visit(DebuggerStatement *) { return true; } + virtual void endVisit(DebuggerStatement *) {} +}; + +} // namespace AST +} // namespace QbsQmlJS + +#endif // QMLJSASTVISITOR_P_H diff --git a/src/lib/corelib/parser/qmljsengine_p.cpp b/src/lib/corelib/parser/qmljsengine_p.cpp new file mode 100644 index 00000000..92ac6452 --- /dev/null +++ b/src/lib/corelib/parser/qmljsengine_p.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** 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 "qmljsengine_p.h" +#include "qmljsglobal_p.h" + +#include +#include +#include + +namespace QbsQmlJS { + +static int toDigit(char c) +{ + if ((c >= '0') && (c <= '9')) + return c - '0'; + else if ((c >= 'a') && (c <= 'z')) + return 10 + c - 'a'; + else if ((c >= 'A') && (c <= 'Z')) + return 10 + c - 'A'; + return -1; +} + +double integerFromString(const char *buf, int size, int radix) +{ + if (size == 0) + return qSNaN(); + + double sign = 1.0; + int i = 0; + if (buf[0] == '+') { + ++i; + } else if (buf[0] == '-') { + sign = -1.0; + ++i; + } + + if (((size-i) >= 2) && (buf[i] == '0')) { + if (((buf[i+1] == 'x') || (buf[i+1] == 'X')) + && (radix < 34)) { + if ((radix != 0) && (radix != 16)) + return 0; + radix = 16; + i += 2; + } else { + if (radix == 0) { + radix = 8; + ++i; + } + } + } else if (radix == 0) { + radix = 10; + } + + int j = i; + for ( ; i < size; ++i) { + int d = toDigit(buf[i]); + if ((d == -1) || (d >= radix)) + break; + } + double result; + if (j == i) { + if (!qstrcmp(buf, "Infinity")) + result = qInf(); + else + result = qSNaN(); + } else { + result = 0; + double multiplier = 1; + for (--i ; i >= j; --i, multiplier *= radix) + result += toDigit(buf[i]) * multiplier; + } + result *= sign; + return result; +} + +double integerFromString(const QString &str, int radix) +{ + QByteArray ba = str.trimmed().toLatin1(); + return integerFromString(ba.constData(), ba.size(), radix); +} + + +Engine::Engine() = default; + +Engine::~Engine() = default; + +void Engine::setCode(const QString &code) +{ _code = code; } + +void Engine::addComment(int pos, int len, int line, int col) +{ if (len > 0) _comments.append(QbsQmlJS::AST::SourceLocation(pos, len, line, col)); } + +QList Engine::comments() const +{ return _comments; } + +Lexer *Engine::lexer() const +{ return _lexer; } + +void Engine::setLexer(Lexer *lexer) +{ _lexer = lexer; } + +void Engine::setDirectives(Directives *directives) +{ _directives = directives; } + +Directives *Engine::directives() const +{ return _directives; } + +MemoryPool *Engine::pool() +{ return &_pool; } + +QStringRef Engine::newStringRef(const QString &text) +{ + const int pos = _extraCode.length(); + _extraCode += text; + return _extraCode.midRef(pos, text.length()); +} + +QStringRef Engine::newStringRef(const QChar *chars, int size) +{ return newStringRef(QString(chars, size)); } + +} // end of namespace QbsQmlJS diff --git a/src/lib/corelib/parser/qmljsengine_p.h b/src/lib/corelib/parser/qmljsengine_p.h new file mode 100644 index 00000000..c55d525f --- /dev/null +++ b/src/lib/corelib/parser/qmljsengine_p.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** 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 QMLJSENGINE_P_H +#define QMLJSENGINE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qmljsglobal_p.h" +#include "qmljsastfwd_p.h" +#include "qmljsmemorypool_p.h" +#include + +#include + +#include +#include + +namespace QbsQmlJS { + +class Lexer; +class Directives; +class MemoryPool; + +class QML_PARSER_EXPORT DiagnosticMessage +{ +public: + enum Kind { Warning, Error }; + + DiagnosticMessage() + : kind(Error) {} + + DiagnosticMessage(Kind kind, const AST::SourceLocation &loc, QString message) + : kind(kind), loc(loc), message(std::move(message)) {} + + bool isWarning() const + { return kind == Warning; } + + bool isError() const + { return kind == Error; } + + Kind kind; + AST::SourceLocation loc; + QString message; +}; + +class QBS_AUTOTEST_EXPORT Engine +{ + Lexer *_lexer{nullptr}; + Directives *_directives{nullptr}; + MemoryPool _pool; + QList _comments; + QString _extraCode; + QString _code; + +public: + Engine(); + ~Engine(); + + void setCode(const QString &code); + + void addComment(int pos, int len, int line, int col); + QList comments() const; + + Lexer *lexer() const; + void setLexer(Lexer *lexer); + + void setDirectives(Directives *directives); + Directives *directives() const; + + MemoryPool *pool(); + + inline QStringRef midRef(int position, int size) { return _code.midRef(position, size); } + + QStringRef newStringRef(const QString &s); + QStringRef newStringRef(const QChar *chars, int size); +}; + +double integerFromString(const char *buf, int size, int radix); + +} // end of namespace QbsQmlJS + +#endif // QMLJSENGINE_P_H diff --git a/src/lib/corelib/parser/qmljsglobal_p.h b/src/lib/corelib/parser/qmljsglobal_p.h new file mode 100644 index 00000000..c3d198ea --- /dev/null +++ b/src/lib/corelib/parser/qmljsglobal_p.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 QMLJSGLOBAL_P_H +#define QMLJSGLOBAL_P_H + +#include + +// Force QML_PARSER_EXPORT to be always empty. +#ifndef QT_CREATOR +# define QT_CREATOR +#endif +#ifdef QML_BUILD_STATIC_LIB +# undef QML_BUILD_STATIC_LIB +#endif +#define QML_BUILD_STATIC_LIB 1 + +#ifdef QT_CREATOR +# ifdef QMLJS_BUILD_DIR +# define QML_PARSER_EXPORT Q_DECL_EXPORT +# elif QML_BUILD_STATIC_LIB +# define QML_PARSER_EXPORT +# else +# define QML_PARSER_EXPORT Q_DECL_IMPORT +# endif // QMLJS_BUILD_DIR + +#else // !QT_CREATOR +# if defined(QT_BUILD_QMLDEVTOOLS_LIB) || defined(QT_QMLDEVTOOLS_LIB) + // QmlDevTools is a static library +# define QML_PARSER_EXPORT +# else +# define QML_PARSER_EXPORT Q_AUTOTEST_EXPORT +# endif +#endif // QT_CREATOR + +#endif // QMLJSGLOBAL_P_H diff --git a/src/lib/corelib/parser/qmljsgrammar.cpp b/src/lib/corelib/parser/qmljsgrammar.cpp new file mode 100644 index 00000000..07a193f6 --- /dev/null +++ b/src/lib/corelib/parser/qmljsgrammar.cpp @@ -0,0 +1,1011 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// This file was generated by qlalr - DO NOT EDIT! +#include "qmljsgrammar_p.h" + +namespace QbsQmlJS { + +const char *const QmlJSGrammar::spell [] = { + "end of file", "&", "&&", "&=", "break", "case", "catch", ":", ",", "continue", + "default", "delete", "/", "/=", "do", ".", "else", "=", "==", "===", + "finally", "for", "function", ">=", ">", ">>", ">>=", ">>>", ">>>=", "identifier", + "if", "in", "instanceof", "{", "[", "<=", "(", "<", "<<", "<<=", + "-", "-=", "--", "new", "!", "!=", "!==", "numeric literal", "|", "|=", + "||", "+", "+=", "++", "?", "}", "]", "%", "%=", "return", + ")", ";", 0, "*", "*=", "string literal", "property", "signal", "readonly", "switch", + "this", "throw", "~", "try", "typeof", "var", "void", "while", "with", "^", + "^=", "null", "true", "false", "const", "debugger", "reserved word", "multiline string literal", "comment", "public", + "import", "as", "on", 0, 0, 0, 0, 0, 0, 0, + 0, 0}; + +const short QmlJSGrammar::lhs [] = { + 102, 102, 102, 102, 102, 102, 103, 109, 109, 112, + 112, 114, 113, 113, 113, 113, 113, 113, 113, 113, + 116, 111, 110, 119, 119, 120, 120, 121, 121, 118, + 107, 107, 107, 107, 123, 123, 123, 123, 123, 123, + 123, 107, 131, 131, 131, 132, 132, 133, 133, 107, + 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, + 107, 107, 107, 107, 107, 107, 117, 117, 117, 117, + 117, 136, 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, 136, 122, + 138, 138, 138, 138, 137, 137, 140, 140, 142, 142, + 142, 142, 142, 142, 143, 143, 143, 143, 143, 143, + 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, + 143, 143, 143, 143, 143, 143, 143, 143, 143, 143, + 143, 143, 143, 143, 143, 144, 144, 115, 115, 115, + 115, 115, 147, 147, 148, 148, 148, 148, 146, 146, + 149, 149, 150, 150, 151, 151, 151, 152, 152, 152, + 152, 152, 152, 152, 152, 152, 152, 153, 153, 153, + 153, 154, 154, 154, 155, 155, 155, 155, 156, 156, + 156, 156, 156, 156, 156, 157, 157, 157, 157, 157, + 157, 158, 158, 158, 158, 158, 159, 159, 159, 159, + 159, 160, 160, 161, 161, 162, 162, 163, 163, 164, + 164, 165, 165, 166, 166, 167, 167, 168, 168, 169, + 169, 170, 170, 171, 171, 141, 141, 172, 172, 173, + 173, 173, 173, 173, 173, 173, 173, 173, 173, 173, + 173, 105, 105, 174, 174, 175, 175, 176, 176, 104, + 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, + 104, 104, 104, 104, 124, 185, 185, 184, 184, 135, + 135, 186, 186, 187, 187, 189, 189, 188, 190, 193, + 191, 191, 194, 192, 192, 125, 126, 126, 127, 127, + 177, 177, 177, 177, 177, 177, 177, 178, 178, 178, + 178, 179, 179, 179, 179, 180, 180, 128, 129, 195, + 195, 198, 198, 196, 196, 199, 197, 181, 181, 181, + 182, 182, 130, 130, 130, 200, 201, 183, 183, 134, + 145, 205, 205, 202, 202, 203, 203, 206, 108, 108, + 207, 207, 106, 106, 204, 204, 139, 139, 208}; + +const short QmlJSGrammar::rhs [] = { + 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, + 2, 1, 2, 2, 3, 3, 5, 5, 4, 4, + 2, 0, 1, 1, 2, 1, 3, 2, 3, 2, + 1, 5, 4, 4, 1, 1, 1, 1, 1, 1, + 1, 3, 1, 1, 1, 0, 1, 2, 4, 6, + 6, 3, 3, 7, 7, 4, 4, 5, 5, 5, + 6, 6, 10, 6, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 3, 3, 4, 5, 3, 4, 3, 1, + 1, 2, 3, 4, 1, 2, 3, 5, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, + 3, 5, 1, 2, 4, 4, 4, 3, 0, 1, + 1, 3, 1, 1, 1, 2, 2, 1, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 1, 3, 3, + 3, 1, 3, 3, 1, 3, 3, 3, 1, 3, + 3, 3, 3, 3, 3, 1, 3, 3, 3, 3, + 3, 1, 3, 3, 3, 3, 1, 3, 3, 3, + 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, + 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, + 3, 1, 5, 1, 5, 1, 3, 1, 3, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 3, 0, 1, 1, 3, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 3, 1, 2, 0, 1, 3, + 3, 1, 1, 1, 3, 1, 3, 2, 2, 2, + 0, 1, 2, 0, 1, 1, 2, 2, 7, 5, + 7, 7, 5, 9, 10, 7, 8, 2, 2, 3, + 3, 2, 2, 3, 3, 3, 3, 5, 5, 3, + 5, 1, 2, 0, 1, 4, 3, 3, 3, 3, + 3, 3, 3, 3, 4, 5, 2, 2, 2, 8, + 8, 1, 3, 0, 1, 0, 1, 1, 1, 1, + 1, 2, 1, 1, 0, 1, 0, 1, 2}; + +const short QmlJSGrammar::action_default [] = { + 0, 0, 22, 0, 0, 0, 22, 0, 175, 242, + 206, 214, 210, 154, 226, 202, 3, 139, 73, 155, + 218, 222, 143, 172, 153, 158, 138, 192, 179, 0, + 80, 81, 76, 345, 67, 347, 0, 0, 0, 0, + 78, 0, 0, 74, 77, 71, 0, 0, 68, 70, + 69, 79, 72, 0, 75, 0, 0, 168, 0, 0, + 155, 174, 157, 156, 0, 0, 0, 170, 171, 169, + 173, 0, 203, 0, 0, 0, 0, 193, 0, 0, + 0, 0, 0, 0, 183, 0, 0, 0, 177, 178, + 176, 181, 185, 184, 182, 180, 195, 194, 196, 0, + 211, 0, 207, 0, 0, 149, 136, 148, 137, 105, + 106, 107, 132, 108, 133, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 120, 121, 134, 122, + 123, 124, 125, 126, 127, 128, 129, 130, 131, 135, + 0, 0, 147, 243, 150, 0, 151, 0, 152, 146, + 0, 239, 232, 230, 237, 238, 236, 235, 241, 234, + 233, 231, 240, 227, 0, 215, 0, 0, 219, 0, + 0, 223, 0, 0, 149, 141, 0, 140, 0, 145, + 159, 0, 346, 334, 335, 0, 332, 0, 333, 0, + 336, 250, 257, 256, 264, 252, 0, 253, 337, 0, + 344, 254, 255, 260, 258, 341, 338, 343, 261, 0, + 272, 0, 0, 0, 0, 345, 67, 0, 347, 68, + 244, 286, 69, 0, 0, 0, 273, 0, 0, 262, + 263, 0, 251, 259, 287, 288, 331, 342, 0, 302, + 303, 304, 305, 0, 298, 299, 300, 301, 328, 329, + 0, 0, 0, 0, 0, 291, 292, 248, 246, 208, + 216, 212, 228, 204, 249, 0, 155, 220, 224, 197, + 186, 0, 0, 205, 0, 0, 0, 0, 198, 0, + 0, 0, 0, 0, 190, 188, 191, 189, 187, 200, + 199, 201, 0, 213, 0, 209, 0, 247, 155, 0, + 229, 244, 245, 0, 244, 0, 0, 294, 0, 0, + 0, 296, 0, 217, 0, 0, 221, 0, 0, 225, + 284, 0, 276, 285, 279, 0, 283, 0, 244, 277, + 0, 244, 0, 0, 295, 0, 0, 0, 297, 346, + 334, 0, 0, 336, 0, 330, 0, 320, 0, 0, + 0, 290, 0, 289, 0, 348, 0, 104, 266, 269, + 0, 105, 272, 108, 133, 110, 111, 76, 115, 116, + 67, 117, 120, 74, 77, 68, 244, 69, 79, 123, + 72, 125, 75, 127, 128, 273, 130, 131, 135, 0, + 97, 0, 0, 99, 103, 101, 88, 100, 102, 0, + 98, 87, 267, 265, 143, 144, 149, 0, 142, 0, + 319, 0, 306, 307, 0, 318, 0, 0, 0, 309, + 314, 312, 315, 0, 0, 313, 314, 0, 310, 0, + 311, 268, 317, 0, 268, 316, 0, 321, 322, 0, + 268, 323, 324, 0, 0, 325, 0, 0, 0, 326, + 327, 161, 160, 0, 0, 0, 293, 0, 0, 0, + 308, 281, 274, 0, 282, 278, 0, 280, 270, 0, + 271, 275, 91, 0, 0, 95, 82, 0, 84, 93, + 0, 85, 94, 96, 86, 92, 83, 0, 89, 165, + 163, 167, 164, 162, 166, 339, 6, 340, 4, 2, + 65, 90, 0, 0, 68, 70, 69, 31, 5, 0, + 66, 0, 45, 44, 43, 0, 0, 58, 0, 59, + 35, 36, 37, 38, 40, 41, 62, 39, 0, 45, + 0, 0, 0, 0, 0, 54, 0, 55, 0, 0, + 26, 0, 0, 63, 27, 0, 30, 28, 24, 0, + 29, 25, 0, 56, 0, 57, 143, 0, 60, 64, + 0, 0, 0, 0, 61, 0, 52, 46, 53, 47, + 0, 0, 0, 0, 49, 0, 50, 51, 48, 0, + 0, 143, 268, 0, 0, 42, 105, 272, 108, 133, + 110, 111, 76, 115, 116, 67, 117, 120, 74, 77, + 68, 244, 69, 79, 123, 72, 125, 75, 127, 128, + 273, 130, 131, 135, 0, 32, 33, 0, 34, 8, + 0, 10, 0, 9, 0, 1, 21, 12, 0, 13, + 0, 14, 0, 19, 20, 0, 15, 16, 0, 17, + 18, 11, 23, 7, 349}; + +const short QmlJSGrammar::goto_default [] = { + 7, 625, 207, 196, 205, 508, 496, 624, 643, 495, + 623, 621, 626, 22, 622, 18, 507, 549, 539, 546, + 541, 526, 191, 195, 197, 201, 233, 208, 230, 530, + 570, 569, 200, 232, 26, 474, 473, 356, 355, 9, + 354, 357, 107, 17, 145, 24, 13, 144, 19, 25, + 57, 23, 8, 28, 27, 269, 15, 263, 10, 259, + 12, 261, 11, 260, 20, 267, 21, 268, 14, 262, + 258, 299, 411, 264, 265, 202, 193, 192, 204, 203, + 229, 194, 360, 359, 231, 463, 462, 321, 322, 465, + 324, 464, 323, 419, 423, 426, 422, 421, 441, 442, + 185, 199, 181, 184, 198, 206, 0}; + +const short QmlJSGrammar::action_index [] = { + 404, 1275, 2411, 2411, 2509, 1000, 68, 92, 90, -102, + 88, 62, 60, 256, -102, 298, 86, -102, -102, 638, + 83, 134, 172, 219, -102, -102, -102, 454, 194, 1275, + -102, -102, -102, 381, -102, 2215, 1555, 1275, 1275, 1275, + -102, 790, 1275, -102, -102, -102, 1275, 1275, -102, -102, + -102, -102, -102, 1275, -102, 1275, 1275, -102, 1275, 1275, + 102, 217, -102, -102, 1275, 1275, 1275, -102, -102, -102, + 204, 1275, 304, 1275, 1275, 1275, 1275, 539, 1275, 1275, + 1275, 1275, 1275, 1275, 308, 1275, 1275, 1275, 103, 131, + 135, 308, 210, 225, 216, 308, 444, 390, 434, 1275, + 82, 1275, 100, 2117, 1275, 1275, -102, -102, -102, -102, + -102, -102, -102, -102, -102, -102, -102, -102, -102, -102, + -102, -102, -102, -102, -102, -102, -102, -102, -102, -102, + -102, -102, -102, -102, -102, -102, -102, -102, -102, -102, + 139, 1275, -102, -102, 91, 10, -102, 1275, -102, -102, + 1275, -102, -102, -102, -102, -102, -102, -102, -102, -102, + -102, -102, -102, -102, 1275, 26, 1275, 1275, 69, 66, + 1275, -102, 2117, 1275, 1275, -102, 97, -102, 44, -102, + -102, 67, -102, 297, 78, 24, -102, 291, -102, 36, + 2411, -102, -102, -102, -102, -102, 234, -102, -102, 12, + -102, -102, -102, -102, -102, -102, 2411, -102, -102, 464, + -102, 461, 115, 2509, 42, 381, 58, 46, 2705, 70, + 1275, -102, 74, 57, 1275, 65, -102, 59, 61, -102, + -102, 367, -102, -102, -102, -102, -102, -102, 106, -102, + -102, -102, -102, 87, -102, -102, -102, -102, -102, -102, + 56, 55, 1275, 99, 84, -102, -102, 1461, -102, 75, + 48, 52, -102, 306, 72, 53, 579, 77, 110, 370, + 230, 381, 1275, 286, 1275, 1275, 1275, 1275, 380, 1275, + 1275, 1275, 1275, 1275, 184, 169, 166, 190, 198, 460, + 363, 353, 1275, 50, 1275, 63, 1275, -102, 638, 1275, + -102, 1275, 64, 39, 1275, 30, 2509, -102, 1275, 173, + 2509, -102, 1275, 79, 1275, 1275, 81, 80, 1275, -102, + 71, 149, 32, -102, -102, 1275, -102, 381, 1275, -102, + 73, 1275, 76, 2509, -102, 1275, 142, 2509, -102, -16, + 381, -42, -12, 2411, -39, -102, 2509, -102, 1275, 154, + 2509, 14, 2509, -102, 20, 16, -32, -102, -102, 2509, + -51, 519, -4, 511, 136, 1275, 2509, -2, -35, 395, + -1, -27, 908, 4, 6, -102, 1370, -102, 0, -36, + 27, 1275, 47, 22, 1275, 45, 1275, 21, 17, 1275, + -102, 2313, 144, -102, -102, -102, -102, -102, -102, 1275, + -102, -102, -102, -102, 274, -102, 1275, -21, -102, 2509, + -102, 138, -102, -102, 2509, -102, 1275, 132, 5, -102, + 40, -102, 41, 101, 1275, -102, 38, 34, -102, -38, + -102, 2509, -102, 105, 2509, -102, 245, -102, -102, 96, + 2509, 11, -102, -7, -11, -102, 352, 8, 18, -102, + -102, -102, -102, 1275, 129, 2509, -102, 1275, 130, 2509, + -102, 49, -102, 226, -102, -102, 1275, -102, -102, 362, + -102, -102, -102, 107, 1837, -102, -102, 1649, -102, -102, + 1743, -102, -102, -102, -102, -102, -102, 114, -102, -102, + -102, -102, -102, -102, -102, -102, -102, 2411, -102, -102, + -102, 94, 9, 818, 189, -10, 31, -102, -102, 223, + -102, 191, -102, -102, -102, 300, 178, -102, 1928, -102, + -102, -102, -102, -102, -102, -102, -102, -102, 257, -25, + 381, 195, -22, 305, 240, -102, -6, -102, 818, 127, + -102, -18, 818, -102, -102, 1184, -102, -102, -102, 1092, + -102, -102, 237, -102, 1928, -102, 294, -8, -102, -102, + 176, 381, 19, 1928, -102, 165, -102, 174, -102, 2, + -52, 381, 183, 381, -102, 117, -102, -102, -102, 2019, + 880, 285, 2607, 1555, 3, -102, 522, 35, 453, 108, + 1275, 2509, 51, 23, 475, 54, -17, 700, 7, 43, + -102, 1370, -102, 28, -3, 33, 1275, 37, 15, 1275, + 25, 1275, 1, 13, 124, -102, -102, 29, -102, -102, + 728, -102, 250, -43, 627, -102, -102, 231, 372, -102, + 222, -102, 111, -102, -102, 381, -102, -102, 104, -102, + -102, -102, -102, -102, -102, + + -107, 9, -103, 2, 5, 266, 1, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -39, + -107, -107, -107, -107, -107, -107, -107, -107, -107, 86, + -107, -107, -107, 8, -107, -107, -22, 19, 71, 174, + -107, 186, 171, -107, -107, -107, 184, 178, -107, -107, + -107, -107, -107, 144, -107, 124, 150, -107, 165, 161, + -107, -107, -107, -107, 156, 160, 157, -107, -107, -107, + -107, 147, -107, 142, 135, 179, 166, -107, 177, 170, + 117, 72, 134, 92, -107, 75, 94, 66, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, 181, + -107, 106, -107, 143, 78, 55, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -5, -107, -107, -107, -107, -107, 54, -107, -107, + 51, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, 114, -107, 113, 38, -107, -107, + 41, -107, 231, 63, 112, -107, -107, -107, -107, -107, + -107, -107, -107, 30, -107, -107, -107, 52, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, 36, -107, -107, 45, + -107, 42, -107, 40, -107, 80, -107, -107, 77, -107, + 88, -107, -107, -107, 83, 74, -107, -107, -107, -107, + -107, -10, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, 23, -107, -107, -107, -107, 100, -107, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, 4, 223, -107, 230, 236, 222, 205, -107, 127, + 125, 115, 96, 102, -107, -107, -107, -107, -107, -107, + -107, -107, 234, -107, 215, -107, 199, -107, -107, 197, + -107, 190, -107, -107, 163, -107, 90, -107, 0, -107, + -1, -107, 203, -107, 189, 211, -107, -107, 195, -107, + -107, -107, -107, -107, -107, 191, -107, 98, 119, -107, + -107, 95, -107, 81, -107, 79, -107, 82, -107, -107, + 101, -107, -107, -16, -107, -107, 53, -107, 46, -107, + 57, -107, 59, -107, -107, -107, -107, -107, -107, 35, + -107, 33, -107, 39, -107, 89, 67, -107, -107, 58, + -107, -107, 84, -107, -107, -107, 73, -107, -107, -107, + -107, 65, -107, 43, 93, -107, 109, -107, -107, 49, + -107, 47, -107, -107, -107, -107, -107, -107, -107, 50, + -107, -107, -107, -107, -107, -107, 108, -107, -107, 61, + -107, -107, -107, -107, 62, -107, 68, -107, -107, -107, + -107, -107, -23, -107, 69, -107, -19, -107, -107, -107, + -107, 97, -107, -107, 99, -107, -107, -107, -107, -107, + 60, -61, -107, -107, 34, -107, 37, -107, 29, -107, + -107, -107, -107, 32, -107, 76, -107, 44, -107, 56, + -107, -107, -107, -107, -107, -107, 31, -107, -107, 116, + -107, -107, -107, -107, -6, -107, -107, 70, -107, -107, + 64, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -107, -107, -107, -107, -107, -107, -107, 193, -107, -107, + -107, -107, -107, 7, -107, -107, -107, -107, -107, -107, + -107, -20, -107, -107, -107, -7, -107, -107, 290, -107, + -107, -107, -107, -107, -107, -107, -107, -107, -107, -107, + -2, -25, -107, -15, -107, -107, -107, -107, 172, -107, + -107, -107, 287, -107, -107, 288, -107, -107, -107, 291, + -107, -107, -107, -107, 336, -107, -107, 20, -107, -107, + 15, 3, -107, 304, -107, -107, -107, 24, -107, -107, + -107, 28, 21, 26, -107, -107, -107, -107, -107, 320, + 104, -107, 13, 381, -3, -107, 6, -107, 10, -107, + 167, 22, -107, -107, 12, -107, -107, 87, -107, -107, + -107, 25, -107, -107, -107, -107, 11, -107, 14, 85, + -107, 121, -107, -107, -107, -107, -107, 27, -107, -107, + 17, -107, -107, 18, 91, -107, -107, -107, 16, -107, + -107, -107, -107, -107, -107, -4, -107, -107, -107, -107, + -107, -107, -107, -107, -107}; + +const short QmlJSGrammar::action_info [] = { + 416, 257, 533, -132, 403, -113, 346, -102, 575, 348, + 572, -121, 531, -103, -121, 545, 345, 430, 342, 348, + 340, 343, 440, 401, 391, 545, 563, 389, 538, 446, + 352, 444, -129, 416, -124, -102, 545, 453, 420, 408, + -124, 431, -132, 424, -126, 424, 424, 620, 440, 457, + -103, 440, -129, 457, -126, 440, 560, 453, -113, 257, + 565, 346, 545, 335, 272, 346, 466, 236, 448, 190, + 149, 164, 141, 170, 99, 511, 272, 409, 257, 312, + 296, 414, 348, 312, 189, 164, 187, 318, 325, 71, + 306, 252, 644, 416, 141, 453, 292, 457, 440, 147, + 304, 71, 443, 183, 179, 141, 0, 141, 0, 172, + 99, 427, 434, 141, 301, 477, 444, 0, 0, 0, + 0, 0, 141, 0, 0, 0, 0, 292, 173, 294, + 58, 294, 542, 251, 331, 542, 333, 141, 141, 101, + 141, 59, 0, 58, 62, 256, 255, 141, 247, 246, + 141, 399, 0, 177, 59, 63, 428, 327, 620, 254, + 314, 101, 141, 478, 315, 640, 639, 242, 241, 249, + 248, 58, 634, 633, 488, 58, 249, 248, 577, 576, + 615, 141, 59, 543, 166, 518, 59, 172, 167, 455, + 459, 85, 418, 86, 85, 142, 86, 249, 248, 413, + 412, 567, 337, 512, 87, 512, 173, 87, 174, 85, + 328, 86, 512, 0, 350, 85, 64, 86, 529, 85, + 512, 86, 87, 85, 512, 86, 568, 566, 87, 64, + 579, 64, 87, 310, 469, 85, 87, 86, 0, 519, + 517, 85, 141, 86, 554, 0, 172, 536, 87, 514, + 85, 514, 86, 141, 87, 85, 545, 86, 514, 0, + 513, 65, 513, 87, 514, 173, 514, 66, 87, 513, + 514, 103, 172, 0, 65, 513, 65, 513, 0, 0, + 66, 513, 66, 637, 636, 0, 0, 470, 468, 172, + 104, 173, 105, 406, 0, 235, 234, 630, 555, 553, + 172, 537, 535, 0, 274, 275, 438, 437, 173, 172, + 406, 631, 629, 635, 0, 580, 73, 74, -90, 173, + 34, 174, 73, 74, 274, 275, 34, -90, 173, 34, + 174, 276, 277, 85, 34, 86, 0, 0, 0, 0, + 0, 628, 0, 75, 76, 0, 87, 0, 0, 75, + 76, 276, 277, 0, 0, 0, 0, 48, 50, 49, + 0, 0, 0, 48, 50, 49, 48, 50, 49, 0, + 0, 48, 50, 49, 0, 0, 279, 280, 0, 0, + 0, 34, 0, 45, 0, 281, 279, 280, 282, 45, + 283, 34, 45, 279, 280, 281, 34, 45, 282, 0, + 283, 34, 281, 279, 280, 282, 0, 283, 0, 0, + 34, 0, 281, 78, 79, 282, 0, 283, 48, 50, + 49, 80, 81, 0, 34, 82, 0, 83, 48, 50, + 49, -345, 0, 48, 50, 49, 0, 0, 48, 50, + 49, 0, 0, 0, 45, 0, 0, 48, 50, 49, + 0, 0, 0, 0, 45, 0, 0, 78, 79, 45, + 0, 48, 50, 49, 45, 80, 81, 78, 79, 82, + 0, 83, 0, 45, 0, 80, 81, 78, 79, 82, + 0, 83, 34, 279, 280, 80, 81, 45, 0, 82, + 34, 83, 281, 34, 0, 282, 0, 283, 6, 5, + 4, 1, 3, 2, 34, 0, 0, 0, 0, 0, + 0, -345, 0, 0, 245, 244, 0, 0, 0, 48, + 50, 49, 245, 244, 0, 240, 239, 48, 50, 49, + 48, 50, 49, 0, 0, 0, 0, 0, 0, 0, + 34, 48, 50, 49, 0, 45, 0, 0, 34, 0, + 0, 34, 0, 45, 0, 0, 45, 0, 0, 0, + 0, 0, 78, 79, 0, 0, 0, 45, 0, 0, + 80, 81, 245, 244, 82, 0, 83, 48, 50, 49, + 240, 239, 151, 240, 239, 48, 50, 49, 48, 50, + 49, 0, 152, 0, 0, 0, 153, 0, 0, 0, + 0, 0, 0, 45, 0, 154, 0, 155, 0, 0, + 308, 45, 0, 0, 45, 0, 0, 0, 156, 0, + 157, 62, 0, 0, 0, 0, 0, 0, 158, 0, + 0, 159, 63, 0, 0, 0, 0, 160, 0, 30, + 31, 151, 0, 161, 0, 0, 0, 0, 0, 33, + 0, 152, 0, 0, 0, 153, 34, 0, 0, 162, + 35, 36, 0, 37, 154, 0, 155, 0, 0, 0, + 503, 0, 0, 0, 44, 0, 0, 156, 0, 157, + 62, 0, 0, 0, 0, 0, 0, 158, 0, 0, + 159, 63, 51, 48, 50, 49, 160, 52, 0, 0, + 0, 0, 161, 0, 0, 0, 0, 0, 43, 54, + 32, 0, 30, 31, 40, 0, 0, 0, 162, 45, + 0, 0, 33, 0, 0, 0, 0, 0, 0, 34, + 0, 0, 0, 35, 36, 0, 37, 0, 0, 0, + 30, 31, 0, 41, 0, 0, 0, 44, 0, 0, + 33, 0, 0, 0, 0, 0, 0, 34, 0, 0, + 0, 35, 36, 0, 37, 51, 48, 50, 49, 0, + 52, 503, 0, 0, 0, 44, 0, 0, 0, 0, + 0, 43, 54, 32, 0, 0, 0, 40, 0, 0, + 0, 0, 45, 51, 48, 50, 49, 0, 52, 0, + 0, 0, 30, 31, 0, 0, 0, 0, 0, 43, + 54, 32, 33, 0, 0, 40, 0, 0, 0, 34, + 45, 0, 0, 35, 36, 0, 37, 0, 0, 0, + 30, 31, 0, 41, 0, 0, 0, 44, 0, 0, + 33, 0, 0, 0, 0, 0, 0, 34, 0, 0, + 0, 35, 36, 0, 37, 51, 48, 50, 49, 0, + 52, 503, 0, 0, 0, 44, 0, 0, 0, 0, + 0, 43, 54, 32, 0, 0, 0, 40, 0, 0, + 0, 0, 45, 51, 48, 50, 49, 0, 52, 0, + 0, 0, 30, 31, 0, 0, 0, 0, 0, 43, + 54, 32, 33, 0, 0, 40, 0, 0, 0, 34, + 45, 0, 0, 35, 36, 0, 37, 0, 0, 0, + 30, 31, 0, 503, 0, 0, 0, 44, 0, 0, + 33, 0, 0, 0, 0, 0, 0, 34, 0, 0, + 0, 35, 36, 0, 37, 51, 48, 50, 49, 0, + 52, 41, 0, 0, 0, 44, 0, 0, 0, 0, + 0, 43, 54, 32, 0, 0, 0, 40, 0, 0, + 0, 0, 45, 51, 48, 50, 49, 0, 52, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, + 54, 32, 0, 0, 0, 40, 0, 0, 0, 0, + 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 502, 0, 30, 31, 0, 0, 0, 0, 0, 0, + 0, 0, 215, 0, 0, 0, 0, 0, 0, 34, + 0, 0, 0, 35, 36, 0, 37, 0, 0, 0, + 0, 0, 0, 503, 0, 0, 0, 44, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 51, 504, 506, 505, 0, + 52, 0, 0, 0, 0, 226, 0, 0, 0, 0, + 0, 43, 54, 32, 210, 0, 0, 40, 0, 0, + 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 502, 0, 30, 31, 0, 0, 0, 0, + 0, 0, 0, 0, 215, 0, 0, 0, 0, 0, + 0, 34, 0, 0, 0, 35, 36, 0, 37, 0, + 0, 0, 0, 0, 0, 503, 0, 0, 0, 44, + 0, 0, 0, 0, 0, 0, 0, 550, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 51, 504, 506, + 505, 0, 52, 0, 0, 0, 0, 226, 0, 0, + 0, 0, 0, 43, 54, 32, 210, 0, 0, 40, + 0, 0, 0, 0, 45, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 502, 0, 30, 31, 0, 0, + 0, 0, 0, 0, 0, 0, 215, 0, 0, 0, + 0, 0, 0, 34, 0, 0, 0, 35, 36, 0, + 37, 0, 0, 0, 0, 0, 0, 503, 0, 0, + 0, 44, 0, 0, 0, 0, 0, 0, 0, 547, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, + 504, 506, 505, 0, 52, 0, 0, 0, 0, 226, + 0, 0, 0, 0, 0, 43, 54, 32, 210, 0, + 0, 40, 0, 0, 0, 0, 45, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 29, 30, 31, 0, + 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, + 0, 0, 0, 0, 34, 0, 0, 0, 35, 36, + 0, 37, 0, 0, 0, 38, 0, 39, 41, 42, + 0, 0, 44, 0, 0, 0, 46, 0, 47, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 51, 48, 50, 49, 0, 52, 0, 53, 0, 55, + 0, 56, 0, 0, 0, 0, 43, 54, 32, 0, + 0, 0, 40, 0, 0, 0, 0, 45, 0, 0, + 0, 0, 0, 0, 0, 0, 0, -122, 0, 0, + 0, 29, 30, 31, 0, 0, 0, 0, 0, 0, + 0, 0, 33, 0, 0, 0, 0, 0, 0, 34, + 0, 0, 0, 35, 36, 0, 37, 0, 0, 0, + 38, 0, 39, 41, 42, 0, 0, 44, 0, 0, + 0, 46, 0, 47, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 51, 48, 50, 49, 0, + 52, 0, 53, 0, 55, 0, 56, 0, 0, 0, + 0, 43, 54, 32, 0, 0, 0, 40, 0, 0, + 0, 0, 45, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 29, 30, 31, 0, 0, 0, 0, 0, + 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, + 34, 0, 0, 0, 35, 36, 0, 37, 0, 0, + 0, 38, 0, 39, 41, 42, 0, 0, 44, 0, + 0, 0, 46, 0, 47, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 51, 48, 50, 49, + 0, 52, 0, 53, 0, 55, 271, 56, 0, 0, + 0, 0, 43, 54, 32, 0, 0, 0, 40, 0, + 0, 0, 0, 45, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 475, 0, 0, 29, 30, 31, 0, + 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, + 0, 0, 0, 0, 34, 0, 0, 0, 35, 36, + 0, 37, 0, 0, 0, 38, 0, 39, 41, 42, + 0, 0, 44, 0, 0, 0, 46, 0, 47, 0, + 0, 476, 0, 0, 0, 0, 0, 0, 0, 0, + 51, 48, 50, 49, 0, 52, 0, 53, 0, 55, + 0, 56, 0, 0, 0, 0, 43, 54, 32, 0, + 0, 0, 40, 0, 0, 0, 0, 45, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 475, 0, 0, + 29, 30, 31, 0, 0, 0, 0, 0, 0, 0, + 0, 33, 0, 0, 0, 0, 0, 0, 34, 0, + 0, 0, 35, 36, 0, 37, 0, 0, 0, 38, + 0, 39, 41, 42, 0, 0, 44, 0, 0, 0, + 46, 0, 47, 0, 0, 481, 0, 0, 0, 0, + 0, 0, 0, 0, 51, 48, 50, 49, 0, 52, + 0, 53, 0, 55, 0, 56, 0, 0, 0, 0, + 43, 54, 32, 0, 0, 0, 40, 0, 0, 0, + 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 483, 0, 0, 29, 30, 31, 0, 0, 0, + 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, + 0, 0, 34, 0, 0, 0, 35, 36, 0, 37, + 0, 0, 0, 38, 0, 39, 41, 42, 0, 0, + 44, 0, 0, 0, 46, 0, 47, 0, 0, 484, + 0, 0, 0, 0, 0, 0, 0, 0, 51, 48, + 50, 49, 0, 52, 0, 53, 0, 55, 0, 56, + 0, 0, 0, 0, 43, 54, 32, 0, 0, 0, + 40, 0, 0, 0, 0, 45, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 483, 0, 0, 29, 30, + 31, 0, 0, 0, 0, 0, 0, 0, 0, 33, + 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, + 35, 36, 0, 37, 0, 0, 0, 38, 0, 39, + 41, 42, 0, 0, 44, 0, 0, 0, 46, 0, + 47, 0, 0, 486, 0, 0, 0, 0, 0, 0, + 0, 0, 51, 48, 50, 49, 0, 52, 0, 53, + 0, 55, 0, 56, 0, 0, 0, 0, 43, 54, + 32, 0, 0, 0, 40, 0, 0, 0, 0, 45, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, + 30, 31, 0, 0, 0, 0, 0, 0, 0, 0, + 33, 0, 0, 0, 0, 0, 0, 34, 217, 0, + 0, 218, 36, 0, 37, 0, 0, 0, 38, 0, + 39, 41, 42, 0, 0, 44, 0, 0, 0, 46, + 0, 47, 0, 0, 0, 0, 0, 0, 0, 221, + 0, 0, 0, 51, 48, 50, 49, 223, 52, 0, + 53, 225, 55, 0, 56, 0, 228, 0, 0, 43, + 54, 32, 0, 0, 0, 40, 0, 0, 0, 0, + 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 29, 30, 31, 0, 0, 0, 0, 0, 0, 0, + 0, 33, 0, 0, 0, 0, 0, 0, 34, 217, + 0, 0, 582, 583, 0, 37, 0, 0, 0, 38, + 0, 39, 41, 42, 0, 0, 44, 0, 0, 0, + 46, 0, 47, 0, 0, 0, 0, 0, 0, 0, + 221, 0, 0, 0, 51, 48, 50, 49, 223, 52, + 0, 53, 225, 55, 0, 56, 0, 228, 0, 0, + 43, 54, 32, 0, 0, 0, 40, 0, 0, 0, + 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 109, 110, 111, 0, 0, 113, 115, 116, 0, + 0, 117, 0, 118, 0, 0, 0, 120, 121, 122, + 0, 0, 0, 0, 0, 0, 34, 123, 124, 125, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 129, 0, 0, 0, + 0, 0, 0, 48, 50, 49, 130, 131, 132, 0, + 134, 135, 136, 137, 138, 139, 0, 0, 127, 133, + 119, 112, 114, 128, 0, 0, 0, 0, 0, 45, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, + 110, 111, 0, 0, 113, 115, 116, 0, 0, 117, + 0, 118, 0, 0, 0, 120, 121, 122, 0, 0, + 0, 0, 0, 0, 393, 123, 124, 125, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 126, 0, + 0, 0, 394, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 129, 0, 0, 0, 0, 0, + 398, 395, 397, 0, 130, 131, 132, 0, 134, 135, + 136, 137, 138, 139, 0, 0, 127, 133, 119, 112, + 114, 128, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 109, 110, 111, + 0, 0, 113, 115, 116, 0, 0, 117, 0, 118, + 0, 0, 0, 120, 121, 122, 0, 0, 0, 0, + 0, 0, 393, 123, 124, 125, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 126, 0, 0, 0, + 394, 0, 0, 0, 0, 0, 0, 0, 396, 0, + 0, 0, 129, 0, 0, 0, 0, 0, 398, 395, + 397, 0, 130, 131, 132, 0, 134, 135, 136, 137, + 138, 139, 0, 0, 127, 133, 119, 112, 114, 128, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 209, 0, 0, 0, 0, + 211, 0, 29, 30, 31, 213, 0, 0, 0, 0, + 0, 0, 214, 215, 0, 0, 0, 0, 0, 0, + 216, 217, 0, 0, 218, 36, 0, 37, 0, 0, + 0, 38, 0, 39, 41, 42, 0, 0, 44, 0, + 0, 0, 46, 0, 47, 0, 0, 0, 0, 0, + 220, 0, 221, 0, 0, 0, 51, 219, 222, 49, + 223, 52, 224, 53, 225, 55, 226, 56, 227, 228, + 0, 0, 43, 54, 32, 210, 212, 0, 40, 0, + 0, 0, 0, 45, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 209, 0, 0, 0, 0, 211, 0, + 29, 30, 31, 213, 0, 0, 0, 0, 0, 0, + 214, 33, 0, 0, 0, 0, 0, 0, 216, 217, + 0, 0, 218, 36, 0, 37, 0, 0, 0, 38, + 0, 39, 41, 42, 0, 0, 44, 0, 0, 0, + 46, 0, 47, 0, 0, 0, 0, 0, 220, 0, + 221, 0, 0, 0, 51, 219, 222, 49, 223, 52, + 224, 53, 225, 55, 226, 56, 227, 228, 0, 0, + 43, 54, 32, 210, 212, 0, 40, 0, 0, 0, + 0, 45, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 586, 110, 111, 0, 0, 588, 115, 590, 30, + 31, 591, 0, 118, 0, 0, 0, 120, 593, 594, + 0, 0, 0, 0, 0, 0, 595, 596, 124, 125, + 218, 36, 0, 37, 0, 0, 0, 38, 0, 39, + 597, 42, 0, 0, 599, 0, 0, 0, 46, 0, + 47, 0, 0, 0, 0, 0, 601, 0, 221, 0, + 0, 0, 603, 600, 602, 49, 604, 605, 606, 53, + 608, 609, 610, 611, 612, 613, 0, 0, 598, 607, + 592, 587, 589, 128, 40, 0, 0, 0, 0, 45, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 361, + 110, 111, 0, 0, 363, 115, 365, 30, 31, 366, + 0, 118, 0, 0, 0, 120, 368, 369, 0, 0, + 0, 0, 0, 0, 370, 371, 124, 125, 218, 36, + 0, 37, 0, 0, 0, 38, 0, 39, 372, 42, + 0, 0, 374, 0, 0, 0, 46, 0, 47, 0, + -268, 0, 0, 0, 376, 0, 221, 0, 0, 0, + 378, 375, 377, 49, 379, 380, 381, 53, 383, 384, + 385, 386, 387, 388, 0, 0, 373, 382, 367, 362, + 364, 128, 40, 0, 0, 0, 0, 45, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + + 534, 311, 497, 309, 532, 461, 498, 499, 516, 515, + 619, 638, 16, 552, 436, 358, 616, 472, 562, 320, + 528, 238, 487, 182, 250, 243, 253, 182, 302, 641, + 627, 632, 150, 485, 143, 454, 439, 402, 445, 559, + 237, 574, 250, 578, 561, 186, 618, 458, 238, 349, + 573, 449, 447, 571, 243, 347, 450, 243, 460, 351, + 238, 353, 358, 410, 415, 439, 176, 188, 436, 250, + 467, 417, 433, 182, 425, 429, 302, 169, 456, 358, + 171, 140, 336, 334, 338, 344, 436, 392, 390, 400, + 163, 302, 307, 148, 146, 339, 439, 404, 302, 358, + 404, 358, 0, 482, 501, 480, 0, 642, 0, 479, + 0, 0, 0, 320, 60, 0, 186, 501, 90, 60, + 60, 489, 302, 60, 617, 93, 0, 88, 0, 405, + 0, 461, 405, 60, 60, 451, 180, 60, 0, 180, + 60, 60, 60, 451, 60, 95, 89, 146, 266, 287, + 60, 146, 407, 270, 60, 288, 178, 60, 106, 452, + 0, 60, 60, 60, 102, 60, 302, 332, 286, 60, + 92, 452, 60, 60, 451, 60, 165, 168, 285, 432, + 284, 435, 60, 60, 108, 501, 329, 94, 540, 96, + 60, 330, 60, 302, 494, 60, 77, 237, 60, 404, + 452, 341, 471, 72, 60, 60, 67, 69, 60, 60, + 68, 0, 70, 60, 60, 60, 61, 180, 60, 60, + 98, 491, 60, 91, 490, 60, 60, 60, 493, 60, + 84, 405, 60, 97, 492, 305, 0, 60, 0, 298, + 0, 100, 270, 298, 270, 298, 106, 298, 270, 0, + 270, 60, 270, 60, 316, 0, 270, 0, 270, 298, + 291, 326, 303, 60, 270, 319, 313, 300, 270, 297, + 60, 60, 108, 175, 295, 270, 270, 290, 60, 501, + 273, 317, 60, 270, 60, 278, 509, 270, 0, 270, + 0, 289, 0, 548, 0, 293, 551, 0, 500, 510, + 501, 501, 0, 544, 501, 0, 0, 0, 509, 0, + 0, 509, 520, 521, 522, 523, 527, 524, 525, 0, + 500, 510, 0, 500, 510, 564, 520, 521, 522, 523, + 527, 524, 525, 581, 0, 0, 0, 0, 0, 0, + 584, 585, 520, 521, 522, 523, 527, 524, 525, 556, + 0, 0, 0, 0, 0, 0, 557, 558, 520, 521, + 522, 523, 527, 524, 525, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 556, 0, 0, 540, 0, 614, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 472, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + +const short QmlJSGrammar::action_check [] = { + 36, 36, 24, 7, 55, 7, 7, 7, 60, 36, + 8, 7, 37, 7, 7, 33, 55, 55, 60, 36, + 36, 33, 33, 55, 8, 33, 7, 7, 34, 36, + 16, 20, 7, 36, 7, 7, 33, 36, 33, 60, + 7, 7, 7, 5, 7, 5, 5, 90, 33, 36, + 7, 33, 7, 36, 7, 33, 66, 36, 7, 36, + 29, 7, 33, 31, 1, 7, 17, 55, 60, 33, + 60, 2, 8, 7, 48, 66, 1, 7, 36, 2, + 8, 7, 36, 2, 60, 2, 8, 7, 17, 1, + 60, 36, 0, 36, 8, 36, 48, 36, 33, 8, + 61, 1, 6, 36, 60, 8, -1, 8, -1, 15, + 48, 10, 7, 8, 61, 8, 20, -1, -1, -1, + -1, -1, 8, -1, -1, -1, -1, 48, 34, 79, + 40, 79, 8, 77, 61, 8, 60, 8, 8, 79, + 8, 51, -1, 40, 42, 61, 62, 8, 61, 62, + 8, 7, -1, 56, 51, 53, 55, 8, 90, 60, + 50, 79, 8, 56, 54, 61, 62, 61, 62, 61, + 62, 40, 61, 62, 60, 40, 61, 62, 61, 62, + 56, 8, 51, 56, 50, 7, 51, 15, 54, 60, + 60, 25, 60, 27, 25, 56, 27, 61, 62, 61, + 62, 36, 60, 29, 38, 29, 34, 38, 36, 25, + 61, 27, 29, -1, 60, 25, 12, 27, 29, 25, + 29, 27, 38, 25, 29, 27, 61, 62, 38, 12, + 7, 12, 38, 60, 8, 25, 38, 27, -1, 61, + 62, 25, 8, 27, 7, -1, 15, 7, 38, 75, + 25, 75, 27, 8, 38, 25, 33, 27, 75, -1, + 86, 57, 86, 38, 75, 34, 75, 63, 38, 86, + 75, 15, 15, -1, 57, 86, 57, 86, -1, -1, + 63, 86, 63, 61, 62, -1, -1, 61, 62, 15, + 34, 34, 36, 36, -1, 61, 62, 47, 61, 62, + 15, 61, 62, -1, 18, 19, 61, 62, 34, 15, + 36, 61, 62, 91, -1, 92, 18, 19, 33, 34, + 29, 36, 18, 19, 18, 19, 29, 33, 34, 29, + 36, 45, 46, 25, 29, 27, -1, -1, -1, -1, + -1, 91, -1, 45, 46, -1, 38, -1, -1, 45, + 46, 45, 46, -1, -1, -1, -1, 66, 67, 68, + -1, -1, -1, 66, 67, 68, 66, 67, 68, -1, + -1, 66, 67, 68, -1, -1, 23, 24, -1, -1, + -1, 29, -1, 92, -1, 32, 23, 24, 35, 92, + 37, 29, 92, 23, 24, 32, 29, 92, 35, -1, + 37, 29, 32, 23, 24, 35, -1, 37, -1, -1, + 29, -1, 32, 23, 24, 35, -1, 37, 66, 67, + 68, 31, 32, -1, 29, 35, -1, 37, 66, 67, + 68, 36, -1, 66, 67, 68, -1, -1, 66, 67, + 68, -1, -1, -1, 92, -1, -1, 66, 67, 68, + -1, -1, -1, -1, 92, -1, -1, 23, 24, 92, + -1, 66, 67, 68, 92, 31, 32, 23, 24, 35, + -1, 37, -1, 92, -1, 31, 32, 23, 24, 35, + -1, 37, 29, 23, 24, 31, 32, 92, -1, 35, + 29, 37, 32, 29, -1, 35, -1, 37, 94, 95, + 96, 97, 98, 99, 29, -1, -1, -1, -1, -1, + -1, 36, -1, -1, 61, 62, -1, -1, -1, 66, + 67, 68, 61, 62, -1, 61, 62, 66, 67, 68, + 66, 67, 68, -1, -1, -1, -1, -1, -1, -1, + 29, 66, 67, 68, -1, 92, -1, -1, 29, -1, + -1, 29, -1, 92, -1, -1, 92, -1, -1, -1, + -1, -1, 23, 24, -1, -1, -1, 92, -1, -1, + 31, 32, 61, 62, 35, -1, 37, 66, 67, 68, + 61, 62, 3, 61, 62, 66, 67, 68, 66, 67, + 68, -1, 13, -1, -1, -1, 17, -1, -1, -1, + -1, -1, -1, 92, -1, 26, -1, 28, -1, -1, + 31, 92, -1, -1, 92, -1, -1, -1, 39, -1, + 41, 42, -1, -1, -1, -1, -1, -1, 49, -1, + -1, 52, 53, -1, -1, -1, -1, 58, -1, 12, + 13, 3, -1, 64, -1, -1, -1, -1, -1, 22, + -1, 13, -1, -1, -1, 17, 29, -1, -1, 80, + 33, 34, -1, 36, 26, -1, 28, -1, -1, -1, + 43, -1, -1, -1, 47, -1, -1, 39, -1, 41, + 42, -1, -1, -1, -1, -1, -1, 49, -1, -1, + 52, 53, 65, 66, 67, 68, 58, 70, -1, -1, + -1, -1, 64, -1, -1, -1, -1, -1, 81, 82, + 83, -1, 12, 13, 87, -1, -1, -1, 80, 92, + -1, -1, 22, -1, -1, -1, -1, -1, -1, 29, + -1, -1, -1, 33, 34, -1, 36, -1, -1, -1, + 12, 13, -1, 43, -1, -1, -1, 47, -1, -1, + 22, -1, -1, -1, -1, -1, -1, 29, -1, -1, + -1, 33, 34, -1, 36, 65, 66, 67, 68, -1, + 70, 43, -1, -1, -1, 47, -1, -1, -1, -1, + -1, 81, 82, 83, -1, -1, -1, 87, -1, -1, + -1, -1, 92, 65, 66, 67, 68, -1, 70, -1, + -1, -1, 12, 13, -1, -1, -1, -1, -1, 81, + 82, 83, 22, -1, -1, 87, -1, -1, -1, 29, + 92, -1, -1, 33, 34, -1, 36, -1, -1, -1, + 12, 13, -1, 43, -1, -1, -1, 47, -1, -1, + 22, -1, -1, -1, -1, -1, -1, 29, -1, -1, + -1, 33, 34, -1, 36, 65, 66, 67, 68, -1, + 70, 43, -1, -1, -1, 47, -1, -1, -1, -1, + -1, 81, 82, 83, -1, -1, -1, 87, -1, -1, + -1, -1, 92, 65, 66, 67, 68, -1, 70, -1, + -1, -1, 12, 13, -1, -1, -1, -1, -1, 81, + 82, 83, 22, -1, -1, 87, -1, -1, -1, 29, + 92, -1, -1, 33, 34, -1, 36, -1, -1, -1, + 12, 13, -1, 43, -1, -1, -1, 47, -1, -1, + 22, -1, -1, -1, -1, -1, -1, 29, -1, -1, + -1, 33, 34, -1, 36, 65, 66, 67, 68, -1, + 70, 43, -1, -1, -1, 47, -1, -1, -1, -1, + -1, 81, 82, 83, -1, -1, -1, 87, -1, -1, + -1, -1, 92, 65, 66, 67, 68, -1, 70, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 81, + 82, 83, -1, -1, -1, 87, -1, -1, -1, -1, + 92, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 10, -1, 12, 13, -1, -1, -1, -1, -1, -1, + -1, -1, 22, -1, -1, -1, -1, -1, -1, 29, + -1, -1, -1, 33, 34, -1, 36, -1, -1, -1, + -1, -1, -1, 43, -1, -1, -1, 47, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 65, 66, 67, 68, -1, + 70, -1, -1, -1, -1, 75, -1, -1, -1, -1, + -1, 81, 82, 83, 84, -1, -1, 87, -1, -1, + -1, -1, 92, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 10, -1, 12, 13, -1, -1, -1, -1, + -1, -1, -1, -1, 22, -1, -1, -1, -1, -1, + -1, 29, -1, -1, -1, 33, 34, -1, 36, -1, + -1, -1, -1, -1, -1, 43, -1, -1, -1, 47, + -1, -1, -1, -1, -1, -1, -1, 55, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 65, 66, 67, + 68, -1, 70, -1, -1, -1, -1, 75, -1, -1, + -1, -1, -1, 81, 82, 83, 84, -1, -1, 87, + -1, -1, -1, -1, 92, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 10, -1, 12, 13, -1, -1, + -1, -1, -1, -1, -1, -1, 22, -1, -1, -1, + -1, -1, -1, 29, -1, -1, -1, 33, 34, -1, + 36, -1, -1, -1, -1, -1, -1, 43, -1, -1, + -1, 47, -1, -1, -1, -1, -1, -1, -1, 55, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 65, + 66, 67, 68, -1, 70, -1, -1, -1, -1, 75, + -1, -1, -1, -1, -1, 81, 82, 83, 84, -1, + -1, 87, -1, -1, -1, -1, 92, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 11, 12, 13, -1, + -1, -1, -1, -1, -1, -1, -1, 22, -1, -1, + -1, -1, -1, -1, 29, -1, -1, -1, 33, 34, + -1, 36, -1, -1, -1, 40, -1, 42, 43, 44, + -1, -1, 47, -1, -1, -1, 51, -1, 53, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 65, 66, 67, 68, -1, 70, -1, 72, -1, 74, + -1, 76, -1, -1, -1, -1, 81, 82, 83, -1, + -1, -1, 87, -1, -1, -1, -1, 92, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 7, -1, -1, + -1, 11, 12, 13, -1, -1, -1, -1, -1, -1, + -1, -1, 22, -1, -1, -1, -1, -1, -1, 29, + -1, -1, -1, 33, 34, -1, 36, -1, -1, -1, + 40, -1, 42, 43, 44, -1, -1, 47, -1, -1, + -1, 51, -1, 53, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 65, 66, 67, 68, -1, + 70, -1, 72, -1, 74, -1, 76, -1, -1, -1, + -1, 81, 82, 83, -1, -1, -1, 87, -1, -1, + -1, -1, 92, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 11, 12, 13, -1, -1, -1, -1, -1, + -1, -1, -1, 22, -1, -1, -1, -1, -1, -1, + 29, -1, -1, -1, 33, 34, -1, 36, -1, -1, + -1, 40, -1, 42, 43, 44, -1, -1, 47, -1, + -1, -1, 51, -1, 53, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 65, 66, 67, 68, + -1, 70, -1, 72, -1, 74, 75, 76, -1, -1, + -1, -1, 81, 82, 83, -1, -1, -1, 87, -1, + -1, -1, -1, 92, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 8, -1, -1, 11, 12, 13, -1, + -1, -1, -1, -1, -1, -1, -1, 22, -1, -1, + -1, -1, -1, -1, 29, -1, -1, -1, 33, 34, + -1, 36, -1, -1, -1, 40, -1, 42, 43, 44, + -1, -1, 47, -1, -1, -1, 51, -1, 53, -1, + -1, 56, -1, -1, -1, -1, -1, -1, -1, -1, + 65, 66, 67, 68, -1, 70, -1, 72, -1, 74, + -1, 76, -1, -1, -1, -1, 81, 82, 83, -1, + -1, -1, 87, -1, -1, -1, -1, 92, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 8, -1, -1, + 11, 12, 13, -1, -1, -1, -1, -1, -1, -1, + -1, 22, -1, -1, -1, -1, -1, -1, 29, -1, + -1, -1, 33, 34, -1, 36, -1, -1, -1, 40, + -1, 42, 43, 44, -1, -1, 47, -1, -1, -1, + 51, -1, 53, -1, -1, 56, -1, -1, -1, -1, + -1, -1, -1, -1, 65, 66, 67, 68, -1, 70, + -1, 72, -1, 74, -1, 76, -1, -1, -1, -1, + 81, 82, 83, -1, -1, -1, 87, -1, -1, -1, + -1, 92, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 8, -1, -1, 11, 12, 13, -1, -1, -1, + -1, -1, -1, -1, -1, 22, -1, -1, -1, -1, + -1, -1, 29, -1, -1, -1, 33, 34, -1, 36, + -1, -1, -1, 40, -1, 42, 43, 44, -1, -1, + 47, -1, -1, -1, 51, -1, 53, -1, -1, 56, + -1, -1, -1, -1, -1, -1, -1, -1, 65, 66, + 67, 68, -1, 70, -1, 72, -1, 74, -1, 76, + -1, -1, -1, -1, 81, 82, 83, -1, -1, -1, + 87, -1, -1, -1, -1, 92, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 8, -1, -1, 11, 12, + 13, -1, -1, -1, -1, -1, -1, -1, -1, 22, + -1, -1, -1, -1, -1, -1, 29, -1, -1, -1, + 33, 34, -1, 36, -1, -1, -1, 40, -1, 42, + 43, 44, -1, -1, 47, -1, -1, -1, 51, -1, + 53, -1, -1, 56, -1, -1, -1, -1, -1, -1, + -1, -1, 65, 66, 67, 68, -1, 70, -1, 72, + -1, 74, -1, 76, -1, -1, -1, -1, 81, 82, + 83, -1, -1, -1, 87, -1, -1, -1, -1, 92, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 11, + 12, 13, -1, -1, -1, -1, -1, -1, -1, -1, + 22, -1, -1, -1, -1, -1, -1, 29, 30, -1, + -1, 33, 34, -1, 36, -1, -1, -1, 40, -1, + 42, 43, 44, -1, -1, 47, -1, -1, -1, 51, + -1, 53, -1, -1, -1, -1, -1, -1, -1, 61, + -1, -1, -1, 65, 66, 67, 68, 69, 70, -1, + 72, 73, 74, -1, 76, -1, 78, -1, -1, 81, + 82, 83, -1, -1, -1, 87, -1, -1, -1, -1, + 92, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 11, 12, 13, -1, -1, -1, -1, -1, -1, -1, + -1, 22, -1, -1, -1, -1, -1, -1, 29, 30, + -1, -1, 33, 34, -1, 36, -1, -1, -1, 40, + -1, 42, 43, 44, -1, -1, 47, -1, -1, -1, + 51, -1, 53, -1, -1, -1, -1, -1, -1, -1, + 61, -1, -1, -1, 65, 66, 67, 68, 69, 70, + -1, 72, 73, 74, -1, 76, -1, 78, -1, -1, + 81, 82, 83, -1, -1, -1, 87, -1, -1, -1, + -1, 92, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 4, 5, 6, -1, -1, 9, 10, 11, -1, + -1, 14, -1, 16, -1, -1, -1, 20, 21, 22, + -1, -1, -1, -1, -1, -1, 29, 30, 31, 32, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 43, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 59, -1, -1, -1, + -1, -1, -1, 66, 67, 68, 69, 70, 71, -1, + 73, 74, 75, 76, 77, 78, -1, -1, 81, 82, + 83, 84, 85, 86, -1, -1, -1, -1, -1, 92, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, + 5, 6, -1, -1, 9, 10, 11, -1, -1, 14, + -1, 16, -1, -1, -1, 20, 21, 22, -1, -1, + -1, -1, -1, -1, 29, 30, 31, 32, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, 43, -1, + -1, -1, 47, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 59, -1, -1, -1, -1, -1, + 65, 66, 67, -1, 69, 70, 71, -1, 73, 74, + 75, 76, 77, 78, -1, -1, 81, 82, 83, 84, + 85, 86, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 4, 5, 6, + -1, -1, 9, 10, 11, -1, -1, 14, -1, 16, + -1, -1, -1, 20, 21, 22, -1, -1, -1, -1, + -1, -1, 29, 30, 31, 32, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 43, -1, -1, -1, + 47, -1, -1, -1, -1, -1, -1, -1, 55, -1, + -1, -1, 59, -1, -1, -1, -1, -1, 65, 66, + 67, -1, 69, 70, 71, -1, 73, 74, 75, 76, + 77, 78, -1, -1, 81, 82, 83, 84, 85, 86, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 4, -1, -1, -1, -1, + 9, -1, 11, 12, 13, 14, -1, -1, -1, -1, + -1, -1, 21, 22, -1, -1, -1, -1, -1, -1, + 29, 30, -1, -1, 33, 34, -1, 36, -1, -1, + -1, 40, -1, 42, 43, 44, -1, -1, 47, -1, + -1, -1, 51, -1, 53, -1, -1, -1, -1, -1, + 59, -1, 61, -1, -1, -1, 65, 66, 67, 68, + 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, + -1, -1, 81, 82, 83, 84, 85, -1, 87, -1, + -1, -1, -1, 92, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 4, -1, -1, -1, -1, 9, -1, + 11, 12, 13, 14, -1, -1, -1, -1, -1, -1, + 21, 22, -1, -1, -1, -1, -1, -1, 29, 30, + -1, -1, 33, 34, -1, 36, -1, -1, -1, 40, + -1, 42, 43, 44, -1, -1, 47, -1, -1, -1, + 51, -1, 53, -1, -1, -1, -1, -1, 59, -1, + 61, -1, -1, -1, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 75, 76, 77, 78, -1, -1, + 81, 82, 83, 84, 85, -1, 87, -1, -1, -1, + -1, 92, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 4, 5, 6, -1, -1, 9, 10, 11, 12, + 13, 14, -1, 16, -1, -1, -1, 20, 21, 22, + -1, -1, -1, -1, -1, -1, 29, 30, 31, 32, + 33, 34, -1, 36, -1, -1, -1, 40, -1, 42, + 43, 44, -1, -1, 47, -1, -1, -1, 51, -1, + 53, -1, -1, -1, -1, -1, 59, -1, 61, -1, + -1, -1, 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, -1, -1, 81, 82, + 83, 84, 85, 86, 87, -1, -1, -1, -1, 92, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, + 5, 6, -1, -1, 9, 10, 11, 12, 13, 14, + -1, 16, -1, -1, -1, 20, 21, 22, -1, -1, + -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, + -1, 36, -1, -1, -1, 40, -1, 42, 43, 44, + -1, -1, 47, -1, -1, -1, 51, -1, 53, -1, + 55, -1, -1, -1, 59, -1, 61, -1, -1, -1, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 77, 78, -1, -1, 81, 82, 83, 84, + 85, 86, 87, -1, -1, -1, -1, 92, -1, -1, + -1, -1, -1, -1, -1, -1, -1, + + 15, 2, 105, 3, 29, 15, 4, 2, 15, 29, + 9, 15, 3, 15, 3, 2, 19, 39, 15, 15, + 13, 15, 3, 15, 2, 15, 3, 15, 3, 11, + 13, 15, 71, 39, 39, 3, 22, 2, 99, 19, + 4, 15, 2, 15, 29, 15, 19, 3, 15, 3, + 29, 22, 15, 29, 15, 2, 22, 15, 2, 2, + 15, 2, 2, 2, 2, 22, 3, 15, 3, 2, + 39, 3, 3, 15, 97, 94, 3, 39, 2, 2, + 39, 3, 3, 2, 2, 101, 3, 40, 39, 39, + 39, 3, 2, 39, 39, 15, 22, 13, 3, 2, + 13, 2, -1, 39, 13, 35, -1, 16, -1, 39, + -1, -1, -1, 15, 48, -1, 15, 13, 52, 48, + 48, 50, 3, 48, 20, 53, -1, 52, -1, 45, + -1, 15, 45, 48, 48, 50, 50, 48, -1, 50, + 48, 48, 48, 50, 48, 53, 52, 39, 48, 53, + 48, 39, 44, 53, 48, 53, 44, 48, 15, 50, + -1, 48, 48, 48, 58, 48, 3, 72, 53, 48, + 53, 50, 48, 48, 50, 48, 62, 64, 53, 82, + 53, 82, 48, 48, 41, 13, 88, 53, 16, 54, + 48, 72, 48, 3, 50, 48, 54, 4, 48, 13, + 50, 100, 86, 56, 48, 48, 50, 50, 48, 48, + 50, -1, 51, 48, 48, 48, 51, 50, 48, 48, + 54, 50, 48, 53, 50, 48, 48, 48, 50, 48, + 53, 45, 48, 54, 50, 72, -1, 48, -1, 48, + -1, 60, 53, 48, 53, 48, 15, 48, 53, -1, + 53, 48, 53, 48, 65, -1, 53, -1, 53, 48, + 55, 70, 72, 48, 53, 70, 63, 70, 53, 70, + 48, 48, 41, 42, 59, 53, 53, 55, 48, 13, + 57, 70, 48, 53, 48, 55, 20, 53, -1, 53, + -1, 55, -1, 5, -1, 61, 5, -1, 32, 33, + 13, 13, -1, 16, 13, -1, -1, -1, 20, -1, + -1, 20, 22, 23, 24, 25, 26, 27, 28, -1, + 32, 33, -1, 32, 33, 21, 22, 23, 24, 25, + 26, 27, 28, 13, -1, -1, -1, -1, -1, -1, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 13, + -1, -1, -1, -1, -1, -1, 20, 21, 22, 23, + 24, 25, 26, 27, 28, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 13, -1, -1, 16, -1, 18, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 39, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1}; + +} // namespace QbsQmlJS diff --git a/src/lib/corelib/parser/qmljsgrammar_p.h b/src/lib/corelib/parser/qmljsgrammar_p.h new file mode 100644 index 00000000..a3525408 --- /dev/null +++ b/src/lib/corelib/parser/qmljsgrammar_p.h @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +// This file was generated by qlalr - DO NOT EDIT! +#ifndef QMLJSGRAMMAR_P_H +#define QMLJSGRAMMAR_P_H + +#include "qmljsglobal_p.h" +#include + +namespace QbsQmlJS { + +class QML_PARSER_EXPORT QmlJSGrammar +{ +public: + enum VariousConstants { + EOF_SYMBOL = 0, + REDUCE_HERE = 101, + SHIFT_THERE = 100, + T_AND = 1, + T_AND_AND = 2, + T_AND_EQ = 3, + T_AS = 91, + T_AUTOMATIC_SEMICOLON = 62, + T_BREAK = 4, + T_CASE = 5, + T_CATCH = 6, + T_COLON = 7, + T_COMMA = 8, + T_COMMENT = 88, + T_CONST = 84, + T_CONTINUE = 9, + T_DEBUGGER = 85, + T_DEFAULT = 10, + T_DELETE = 11, + T_DIVIDE_ = 12, + T_DIVIDE_EQ = 13, + T_DO = 14, + T_DOT = 15, + T_ELSE = 16, + T_EQ = 17, + T_EQ_EQ = 18, + T_EQ_EQ_EQ = 19, + T_ERROR = 93, + T_FALSE = 83, + T_FEED_JS_EXPRESSION = 97, + T_FEED_JS_PROGRAM = 99, + T_FEED_JS_SOURCE_ELEMENT = 98, + T_FEED_JS_STATEMENT = 96, + T_FEED_UI_OBJECT_MEMBER = 95, + T_FEED_UI_PROGRAM = 94, + T_FINALLY = 20, + T_FOR = 21, + T_FUNCTION = 22, + T_GE = 23, + T_GT = 24, + T_GT_GT = 25, + T_GT_GT_EQ = 26, + T_GT_GT_GT = 27, + T_GT_GT_GT_EQ = 28, + T_IDENTIFIER = 29, + T_IF = 30, + T_IMPORT = 90, + T_IN = 31, + T_INSTANCEOF = 32, + T_LBRACE = 33, + T_LBRACKET = 34, + T_LE = 35, + T_LPAREN = 36, + T_LT = 37, + T_LT_LT = 38, + T_LT_LT_EQ = 39, + T_MINUS = 40, + T_MINUS_EQ = 41, + T_MINUS_MINUS = 42, + T_MULTILINE_STRING_LITERAL = 87, + T_NEW = 43, + T_NOT = 44, + T_NOT_EQ = 45, + T_NOT_EQ_EQ = 46, + T_NULL = 81, + T_NUMERIC_LITERAL = 47, + T_ON = 92, + T_OR = 48, + T_OR_EQ = 49, + T_OR_OR = 50, + T_PLUS = 51, + T_PLUS_EQ = 52, + T_PLUS_PLUS = 53, + T_PROPERTY = 66, + T_PUBLIC = 89, + T_QUESTION = 54, + T_RBRACE = 55, + T_RBRACKET = 56, + T_READONLY = 68, + T_REMAINDER = 57, + T_REMAINDER_EQ = 58, + T_RESERVED_WORD = 86, + T_RETURN = 59, + T_RPAREN = 60, + T_SEMICOLON = 61, + T_SIGNAL = 67, + T_STAR = 63, + T_STAR_EQ = 64, + T_STRING_LITERAL = 65, + T_SWITCH = 69, + T_THIS = 70, + T_THROW = 71, + T_TILDE = 72, + T_TRUE = 82, + T_TRY = 73, + T_TYPEOF = 74, + T_VAR = 75, + T_VOID = 76, + T_WHILE = 77, + T_WITH = 78, + T_XOR = 79, + T_XOR_EQ = 80, + + ACCEPT_STATE = 644, + RULE_COUNT = 349, + STATE_COUNT = 645, + TERMINAL_COUNT = 102, + NON_TERMINAL_COUNT = 107, + + GOTO_INDEX_OFFSET = 645, + GOTO_INFO_OFFSET = 2807, + GOTO_CHECK_OFFSET = 2807 + }; + + static const char *const spell []; + static const short lhs []; + static const short rhs []; + static const short goto_default []; + static const short action_default []; + static const short action_index []; + static const short action_info []; + static const short action_check []; + + static inline int nt_action (int state, int nt) + { + const int yyn = action_index [GOTO_INDEX_OFFSET + state] + nt; + if (yyn < 0 || action_check [GOTO_CHECK_OFFSET + yyn] != nt) + return goto_default [nt]; + + return action_info [GOTO_INFO_OFFSET + yyn]; + } + + static inline int t_action (int state, int token) + { + const int yyn = action_index [state] + token; + + if (yyn < 0 || action_check [yyn] != token) + return - action_default [state]; + + return action_info [yyn]; + } +}; + + +} // namespace QbsQmlJS + +#endif // QMLJSGRAMMAR_P_H + diff --git a/src/lib/corelib/parser/qmljskeywords_p.h b/src/lib/corelib/parser/qmljskeywords_p.h new file mode 100644 index 00000000..77abdada --- /dev/null +++ b/src/lib/corelib/parser/qmljskeywords_p.h @@ -0,0 +1,862 @@ +/**************************************************************************** +** +** 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 QMLJSKEYWORDS_P_H +#define QMLJSKEYWORDS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +namespace QbsQmlJS { + +static inline int classify2(const QChar *s, bool qmlMode) { + if (s[0].unicode() == 'a') { + if (s[1].unicode() == 's') { + return qmlMode ? Lexer::T_AS : Lexer::T_RESERVED_WORD; + } + } + else if (s[0].unicode() == 'd') { + if (s[1].unicode() == 'o') { + return Lexer::T_DO; + } + } + else if (s[0].unicode() == 'i') { + if (s[1].unicode() == 'f') { + return Lexer::T_IF; + } + else if (s[1].unicode() == 'n') { + return Lexer::T_IN; + } + } + else if (qmlMode && s[0].unicode() == 'o') { + if (s[1].unicode() == 'n') { + return Lexer::T_ON; + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify3(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 'f') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'r') { + return Lexer::T_FOR; + } + } + } + else if (s[0].unicode() == 'i') { + if (s[1].unicode() == 'n') { + if (s[2].unicode() == 't') { + return Lexer::T_INT; + } + } + } + else if (s[0].unicode() == 'n') { + if (s[1].unicode() == 'e') { + if (s[2].unicode() == 'w') { + return Lexer::T_NEW; + } + } + } + else if (s[0].unicode() == 't') { + if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'y') { + return Lexer::T_TRY; + } + } + } + else if (s[0].unicode() == 'v') { + if (s[1].unicode() == 'a') { + if (s[2].unicode() == 'r') { + return Lexer::T_VAR; + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify4(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 'b') { + if (s[1].unicode() == 'y') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'e') { + return Lexer::T_BYTE; + } + } + } + } + else if (s[0].unicode() == 'c') { + if (s[1].unicode() == 'a') { + if (s[2].unicode() == 's') { + if (s[3].unicode() == 'e') { + return Lexer::T_CASE; + } + } + } + else if (s[1].unicode() == 'h') { + if (s[2].unicode() == 'a') { + if (s[3].unicode() == 'r') { + return Lexer::T_CHAR; + } + } + } + } + else if (s[0].unicode() == 'e') { + if (s[1].unicode() == 'l') { + if (s[2].unicode() == 's') { + if (s[3].unicode() == 'e') { + return Lexer::T_ELSE; + } + } + } + else if (s[1].unicode() == 'n') { + if (s[2].unicode() == 'u') { + if (s[3].unicode() == 'm') { + return Lexer::T_ENUM; + } + } + } + } + else if (s[0].unicode() == 'g') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'o') { + return Lexer::T_GOTO; + } + } + } + } + else if (s[0].unicode() == 'l') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 'g') { + return Lexer::T_LONG; + } + } + } + } + else if (s[0].unicode() == 'n') { + if (s[1].unicode() == 'u') { + if (s[2].unicode() == 'l') { + if (s[3].unicode() == 'l') { + return Lexer::T_NULL; + } + } + } + } + else if (s[0].unicode() == 't') { + if (s[1].unicode() == 'h') { + if (s[2].unicode() == 'i') { + if (s[3].unicode() == 's') { + return Lexer::T_THIS; + } + } + } + else if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'u') { + if (s[3].unicode() == 'e') { + return Lexer::T_TRUE; + } + } + } + } + else if (s[0].unicode() == 'v') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'i') { + if (s[3].unicode() == 'd') { + return Lexer::T_VOID; + } + } + } + } + else if (s[0].unicode() == 'w') { + if (s[1].unicode() == 'i') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'h') { + return Lexer::T_WITH; + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify5(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 'b') { + if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'e') { + if (s[3].unicode() == 'a') { + if (s[4].unicode() == 'k') { + return Lexer::T_BREAK; + } + } + } + } + } + else if (s[0].unicode() == 'c') { + if (s[1].unicode() == 'a') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'c') { + if (s[4].unicode() == 'h') { + return Lexer::T_CATCH; + } + } + } + } + else if (s[1].unicode() == 'l') { + if (s[2].unicode() == 'a') { + if (s[3].unicode() == 's') { + if (s[4].unicode() == 's') { + return Lexer::T_CLASS; + } + } + } + } + else if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 's') { + if (s[4].unicode() == 't') { + return Lexer::T_CONST; + } + } + } + } + } + else if (s[0].unicode() == 'f') { + if (s[1].unicode() == 'a') { + if (s[2].unicode() == 'l') { + if (s[3].unicode() == 's') { + if (s[4].unicode() == 'e') { + return Lexer::T_FALSE; + } + } + } + } + else if (s[1].unicode() == 'i') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 'a') { + if (s[4].unicode() == 'l') { + return Lexer::T_FINAL; + } + } + } + } + else if (s[1].unicode() == 'l') { + if (s[2].unicode() == 'o') { + if (s[3].unicode() == 'a') { + if (s[4].unicode() == 't') { + return Lexer::T_FLOAT; + } + } + } + } + } + else if (s[0].unicode() == 's') { + if (s[1].unicode() == 'h') { + if (s[2].unicode() == 'o') { + if (s[3].unicode() == 'r') { + if (s[4].unicode() == 't') { + return Lexer::T_SHORT; + } + } + } + } + else if (s[1].unicode() == 'u') { + if (s[2].unicode() == 'p') { + if (s[3].unicode() == 'e') { + if (s[4].unicode() == 'r') { + return Lexer::T_SUPER; + } + } + } + } + } + else if (s[0].unicode() == 't') { + if (s[1].unicode() == 'h') { + if (s[2].unicode() == 'r') { + if (s[3].unicode() == 'o') { + if (s[4].unicode() == 'w') { + return Lexer::T_THROW; + } + } + } + } + } + else if (s[0].unicode() == 'w') { + if (s[1].unicode() == 'h') { + if (s[2].unicode() == 'i') { + if (s[3].unicode() == 'l') { + if (s[4].unicode() == 'e') { + return Lexer::T_WHILE; + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify6(const QChar *s, bool qmlMode) { + if (s[0].unicode() == 'd') { + if (s[1].unicode() == 'e') { + if (s[2].unicode() == 'l') { + if (s[3].unicode() == 'e') { + if (s[4].unicode() == 't') { + if (s[5].unicode() == 'e') { + return Lexer::T_DELETE; + } + } + } + } + } + else if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'u') { + if (s[3].unicode() == 'b') { + if (s[4].unicode() == 'l') { + if (s[5].unicode() == 'e') { + return Lexer::T_DOUBLE; + } + } + } + } + } + } + else if (s[0].unicode() == 'e') { + if (s[1].unicode() == 'x') { + if (s[2].unicode() == 'p') { + if (s[3].unicode() == 'o') { + if (s[4].unicode() == 'r') { + if (s[5].unicode() == 't') { + return Lexer::T_EXPORT; + } + } + } + } + } + } + else if (s[0].unicode() == 'i') { + if (s[1].unicode() == 'm') { + if (s[2].unicode() == 'p') { + if (s[3].unicode() == 'o') { + if (s[4].unicode() == 'r') { + if (s[5].unicode() == 't') { + return qmlMode ? Lexer::T_IMPORT : Lexer::T_RESERVED_WORD; + } + } + } + } + } + } + else if (s[0].unicode() == 'n') { + if (s[1].unicode() == 'a') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'i') { + if (s[4].unicode() == 'v') { + if (s[5].unicode() == 'e') { + return Lexer::T_NATIVE; + } + } + } + } + } + } + else if (s[0].unicode() == 'p') { + if (s[1].unicode() == 'u') { + if (s[2].unicode() == 'b') { + if (s[3].unicode() == 'l') { + if (s[4].unicode() == 'i') { + if (s[5].unicode() == 'c') { + return qmlMode ? Lexer::T_PUBLIC : Lexer::T_RESERVED_WORD; + } + } + } + } + } + } + else if (s[0].unicode() == 'r') { + if (s[1].unicode() == 'e') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'u') { + if (s[4].unicode() == 'r') { + if (s[5].unicode() == 'n') { + return Lexer::T_RETURN; + } + } + } + } + } + } + else if (s[0].unicode() == 's') { + if (qmlMode && s[1].unicode() == 'i') { + if (s[2].unicode() == 'g') { + if (s[3].unicode() == 'n') { + if (s[4].unicode() == 'a') { + if (s[5].unicode() == 'l') { + return Lexer::T_SIGNAL; + } + } + } + } + } + else if (s[1].unicode() == 't') { + if (s[2].unicode() == 'a') { + if (s[3].unicode() == 't') { + if (s[4].unicode() == 'i') { + if (s[5].unicode() == 'c') { + return Lexer::T_STATIC; + } + } + } + } + } + else if (s[1].unicode() == 'w') { + if (s[2].unicode() == 'i') { + if (s[3].unicode() == 't') { + if (s[4].unicode() == 'c') { + if (s[5].unicode() == 'h') { + return Lexer::T_SWITCH; + } + } + } + } + } + } + else if (s[0].unicode() == 't') { + if (s[1].unicode() == 'h') { + if (s[2].unicode() == 'r') { + if (s[3].unicode() == 'o') { + if (s[4].unicode() == 'w') { + if (s[5].unicode() == 's') { + return Lexer::T_THROWS; + } + } + } + } + } + else if (s[1].unicode() == 'y') { + if (s[2].unicode() == 'p') { + if (s[3].unicode() == 'e') { + if (s[4].unicode() == 'o') { + if (s[5].unicode() == 'f') { + return Lexer::T_TYPEOF; + } + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify7(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 'b') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'o') { + if (s[3].unicode() == 'l') { + if (s[4].unicode() == 'e') { + if (s[5].unicode() == 'a') { + if (s[6].unicode() == 'n') { + return Lexer::T_BOOLEAN; + } + } + } + } + } + } + } + else if (s[0].unicode() == 'd') { + if (s[1].unicode() == 'e') { + if (s[2].unicode() == 'f') { + if (s[3].unicode() == 'a') { + if (s[4].unicode() == 'u') { + if (s[5].unicode() == 'l') { + if (s[6].unicode() == 't') { + return Lexer::T_DEFAULT; + } + } + } + } + } + } + } + else if (s[0].unicode() == 'e') { + if (s[1].unicode() == 'x') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'e') { + if (s[4].unicode() == 'n') { + if (s[5].unicode() == 'd') { + if (s[6].unicode() == 's') { + return Lexer::T_EXTENDS; + } + } + } + } + } + } + } + else if (s[0].unicode() == 'f') { + if (s[1].unicode() == 'i') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 'a') { + if (s[4].unicode() == 'l') { + if (s[5].unicode() == 'l') { + if (s[6].unicode() == 'y') { + return Lexer::T_FINALLY; + } + } + } + } + } + } + } + else if (s[0].unicode() == 'p') { + if (s[1].unicode() == 'a') { + if (s[2].unicode() == 'c') { + if (s[3].unicode() == 'k') { + if (s[4].unicode() == 'a') { + if (s[5].unicode() == 'g') { + if (s[6].unicode() == 'e') { + return Lexer::T_PACKAGE; + } + } + } + } + } + } + else if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'i') { + if (s[3].unicode() == 'v') { + if (s[4].unicode() == 'a') { + if (s[5].unicode() == 't') { + if (s[6].unicode() == 'e') { + return Lexer::T_PRIVATE; + } + } + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify8(const QChar *s, bool qmlMode) { + if (s[0].unicode() == 'a') { + if (s[1].unicode() == 'b') { + if (s[2].unicode() == 's') { + if (s[3].unicode() == 't') { + if (s[4].unicode() == 'r') { + if (s[5].unicode() == 'a') { + if (s[6].unicode() == 'c') { + if (s[7].unicode() == 't') { + return Lexer::T_ABSTRACT; + } + } + } + } + } + } + } + } + else if (s[0].unicode() == 'c') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 't') { + if (s[4].unicode() == 'i') { + if (s[5].unicode() == 'n') { + if (s[6].unicode() == 'u') { + if (s[7].unicode() == 'e') { + return Lexer::T_CONTINUE; + } + } + } + } + } + } + } + } + else if (s[0].unicode() == 'd') { + if (s[1].unicode() == 'e') { + if (s[2].unicode() == 'b') { + if (s[3].unicode() == 'u') { + if (s[4].unicode() == 'g') { + if (s[5].unicode() == 'g') { + if (s[6].unicode() == 'e') { + if (s[7].unicode() == 'r') { + return Lexer::T_DEBUGGER; + } + } + } + } + } + } + } + } + else if (s[0].unicode() == 'f') { + if (s[1].unicode() == 'u') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 'c') { + if (s[4].unicode() == 't') { + if (s[5].unicode() == 'i') { + if (s[6].unicode() == 'o') { + if (s[7].unicode() == 'n') { + return Lexer::T_FUNCTION; + } + } + } + } + } + } + } + } + else if (qmlMode && s[0].unicode() == 'p') { + if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'o') { + if (s[3].unicode() == 'p') { + if (s[4].unicode() == 'e') { + if (s[5].unicode() == 'r') { + if (s[6].unicode() == 't') { + if (s[7].unicode() == 'y') { + return Lexer::T_PROPERTY; + } + } + } + } + } + } + } + } + else if (qmlMode && s[0].unicode() == 'r') { + if (s[1].unicode() == 'e') { + if (s[2].unicode() == 'a') { + if (s[3].unicode() == 'd') { + if (s[4].unicode() == 'o') { + if (s[5].unicode() == 'n') { + if (s[6].unicode() == 'l') { + if (s[7].unicode() == 'y') { + return Lexer::T_READONLY; + } + } + } + } + } + } + } + } + else if (s[0].unicode() == 'v') { + if (s[1].unicode() == 'o') { + if (s[2].unicode() == 'l') { + if (s[3].unicode() == 'a') { + if (s[4].unicode() == 't') { + if (s[5].unicode() == 'i') { + if (s[6].unicode() == 'l') { + if (s[7].unicode() == 'e') { + return Lexer::T_VOLATILE; + } + } + } + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify9(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 'i') { + if (s[1].unicode() == 'n') { + if (s[2].unicode() == 't') { + if (s[3].unicode() == 'e') { + if (s[4].unicode() == 'r') { + if (s[5].unicode() == 'f') { + if (s[6].unicode() == 'a') { + if (s[7].unicode() == 'c') { + if (s[8].unicode() == 'e') { + return Lexer::T_INTERFACE; + } + } + } + } + } + } + } + } + } + else if (s[0].unicode() == 'p') { + if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'o') { + if (s[3].unicode() == 't') { + if (s[4].unicode() == 'e') { + if (s[5].unicode() == 'c') { + if (s[6].unicode() == 't') { + if (s[7].unicode() == 'e') { + if (s[8].unicode() == 'd') { + return Lexer::T_PROTECTED; + } + } + } + } + } + } + } + } + } + else if (s[0].unicode() == 't') { + if (s[1].unicode() == 'r') { + if (s[2].unicode() == 'a') { + if (s[3].unicode() == 'n') { + if (s[4].unicode() == 's') { + if (s[5].unicode() == 'i') { + if (s[6].unicode() == 'e') { + if (s[7].unicode() == 'n') { + if (s[8].unicode() == 't') { + return Lexer::T_TRANSIENT; + } + } + } + } + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify10(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 'i') { + if (s[1].unicode() == 'm') { + if (s[2].unicode() == 'p') { + if (s[3].unicode() == 'l') { + if (s[4].unicode() == 'e') { + if (s[5].unicode() == 'm') { + if (s[6].unicode() == 'e') { + if (s[7].unicode() == 'n') { + if (s[8].unicode() == 't') { + if (s[9].unicode() == 's') { + return Lexer::T_IMPLEMENTS; + } + } + } + } + } + } + } + } + } + else if (s[1].unicode() == 'n') { + if (s[2].unicode() == 's') { + if (s[3].unicode() == 't') { + if (s[4].unicode() == 'a') { + if (s[5].unicode() == 'n') { + if (s[6].unicode() == 'c') { + if (s[7].unicode() == 'e') { + if (s[8].unicode() == 'o') { + if (s[9].unicode() == 'f') { + return Lexer::T_INSTANCEOF; + } + } + } + } + } + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +static inline int classify12(const QChar *s, bool /*qmlMode*/) { + if (s[0].unicode() == 's') { + if (s[1].unicode() == 'y') { + if (s[2].unicode() == 'n') { + if (s[3].unicode() == 'c') { + if (s[4].unicode() == 'h') { + if (s[5].unicode() == 'r') { + if (s[6].unicode() == 'o') { + if (s[7].unicode() == 'n') { + if (s[8].unicode() == 'i') { + if (s[9].unicode() == 'z') { + if (s[10].unicode() == 'e') { + if (s[11].unicode() == 'd') { + return Lexer::T_SYNCHRONIZED; + } + } + } + } + } + } + } + } + } + } + } + } + return Lexer::T_IDENTIFIER; +} + +int Lexer::classify(const QChar *s, int n, bool qmlMode) { + switch (n) { + case 2: return classify2(s, qmlMode); + case 3: return classify3(s, qmlMode); + case 4: return classify4(s, qmlMode); + case 5: return classify5(s, qmlMode); + case 6: return classify6(s, qmlMode); + case 7: return classify7(s, qmlMode); + case 8: return classify8(s, qmlMode); + case 9: return classify9(s, qmlMode); + case 10: return classify10(s, qmlMode); + case 12: return classify12(s, qmlMode); + default: return Lexer::T_IDENTIFIER; + } // switch +} + +} // namespace QbsQmlJS + +#endif // QMLJSKEYWORDS_P_H diff --git a/src/lib/corelib/parser/qmljslexer.cpp b/src/lib/corelib/parser/qmljslexer.cpp new file mode 100644 index 00000000..f8e83c33 --- /dev/null +++ b/src/lib/corelib/parser/qmljslexer.cpp @@ -0,0 +1,1155 @@ +/**************************************************************************** +** +** 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 "qmljslexer_p.h" +#include "qmljsengine_p.h" +#include "qmljsmemorypool_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE +Q_CORE_EXPORT double qstrtod(const char *s00, char const **se, bool *ok); +QT_END_NAMESPACE + +namespace QbsQmlJS { + +static int regExpFlagFromChar(const QChar &ch) +{ + switch (ch.unicode()) { + case 'g': return Lexer::RegExp_Global; + case 'i': return Lexer::RegExp_IgnoreCase; + case 'm': return Lexer::RegExp_Multiline; + } + return 0; +} + +static unsigned char convertHex(ushort c) +{ + if (c >= '0' && c <= '9') + return (c - '0'); + else if (c >= 'a' && c <= 'f') + return (c - 'a' + 10); + else + return (c - 'A' + 10); +} + +static QChar convertHex(QChar c1, QChar c2) +{ + return QChar{(convertHex(c1.unicode()) << 4) + convertHex(c2.unicode())}; +} + +static QChar convertUnicode(QChar c1, QChar c2, QChar c3, QChar c4) +{ + return {uchar((convertHex(c3.unicode()) << 4) + convertHex(c4.unicode())), + uchar((convertHex(c1.unicode()) << 4) + convertHex(c2.unicode()))}; +} + +Lexer::Lexer(Engine *engine) + : _engine(engine) + , _codePtr(nullptr) + , _lastLinePtr(nullptr) + , _tokenLinePtr(nullptr) + , _tokenStartPtr(nullptr) + , _char(QLatin1Char('\n')) + , _errorCode(NoError) + , _currentLineNumber(0) + , _tokenValue(0) + , _parenthesesState(IgnoreParentheses) + , _parenthesesCount(0) + , _stackToken(-1) + , _patternFlags(0) + , _tokenKind(0) + , _tokenLength(0) + , _tokenLine(0) + , _validTokenText(false) + , _prohibitAutomaticSemicolon(false) + , _restrictedKeyword(false) + , _terminator(false) + , _followsClosingBrace(false) + , _delimited(true) + , _qmlMode(true) +{ + if (engine) + engine->setLexer(this); +} + +bool Lexer::qmlMode() const +{ + return _qmlMode; +} + +QString Lexer::code() const +{ + return _code; +} + +void Lexer::setCode(const QString &code, int lineno, bool qmlMode) +{ + if (_engine) + _engine->setCode(code); + + _qmlMode = qmlMode; + _code = code; + _tokenText.clear(); + _tokenText.reserve(1024); + _errorMessage.clear(); + _tokenSpell = QStringRef(); + + _codePtr = code.unicode(); + _lastLinePtr = _codePtr; + _tokenLinePtr = _codePtr; + _tokenStartPtr = _codePtr; + + _char = QLatin1Char('\n'); + _errorCode = NoError; + + _currentLineNumber = lineno; + _tokenValue = 0; + + // parentheses state + _parenthesesState = IgnoreParentheses; + _parenthesesCount = 0; + + _stackToken = -1; + + _patternFlags = 0; + _tokenLength = 0; + _tokenLine = lineno; + + _validTokenText = false; + _prohibitAutomaticSemicolon = false; + _restrictedKeyword = false; + _terminator = false; + _followsClosingBrace = false; + _delimited = true; +} + +void Lexer::scanChar() +{ + _char = *_codePtr++; + + if (_char == QLatin1Char('\n')) { + _lastLinePtr = _codePtr; // points to the first character after the newline + ++_currentLineNumber; + } +} + +int Lexer::lex() +{ + const int previousTokenKind = _tokenKind; + + _tokenSpell = QStringRef(); + _tokenKind = scanToken(); + _tokenLength = int(_codePtr - _tokenStartPtr - 1); + + _delimited = false; + _restrictedKeyword = false; + _followsClosingBrace = (previousTokenKind == T_RBRACE); + + // update the flags + switch (_tokenKind) { + case T_LBRACE: + case T_SEMICOLON: + case T_COLON: + _delimited = true; + break; + + case T_IF: + case T_FOR: + case T_WHILE: + case T_WITH: + _parenthesesState = CountParentheses; + _parenthesesCount = 0; + break; + + case T_DO: + _parenthesesState = BalancedParentheses; + break; + + case T_CONTINUE: + case T_BREAK: + case T_RETURN: + case T_THROW: + _restrictedKeyword = true; + break; + } // switch + + // update the parentheses state + switch (_parenthesesState) { + case IgnoreParentheses: + break; + + case CountParentheses: + if (_tokenKind == T_RPAREN) { + --_parenthesesCount; + if (_parenthesesCount == 0) + _parenthesesState = BalancedParentheses; + } else if (_tokenKind == T_LPAREN) { + ++_parenthesesCount; + } + break; + + case BalancedParentheses: + _parenthesesState = IgnoreParentheses; + break; + } // switch + + return _tokenKind; +} + +bool Lexer::isUnicodeEscapeSequence(const QChar *chars) +{ + return isHexDigit(chars[0]) + && isHexDigit(chars[1]) + && isHexDigit(chars[2]) + && isHexDigit(chars[3]); +} + +QChar Lexer::decodeUnicodeEscapeCharacter(bool *ok) +{ + if (_char == QLatin1Char('u') && isUnicodeEscapeSequence(&_codePtr[0])) { + scanChar(); // skip u + + const QChar c1 = _char; + scanChar(); + + const QChar c2 = _char; + scanChar(); + + const QChar c3 = _char; + scanChar(); + + const QChar c4 = _char; + scanChar(); + + if (ok) + *ok = true; + + return convertUnicode(c1, c2, c3, c4); + } + + *ok = false; + return {}; +} + +int Lexer::scanToken() +{ + if (_stackToken != -1) { + int tk = _stackToken; + _stackToken = -1; + return tk; + } + + _terminator = false; + +again: + _validTokenText = false; + _tokenLinePtr = _lastLinePtr; + + while (_char.isSpace()) { + if (_char == QLatin1Char('\n')) { + _tokenLinePtr = _codePtr; + + if (_restrictedKeyword) { + // automatic semicolon insertion + _tokenLine = _currentLineNumber; + _tokenStartPtr = _codePtr - 1; // ### TODO: insert it before the optional \r sequence. + return T_SEMICOLON; + } else { + _terminator = true; + syncProhibitAutomaticSemicolon(); + } + } + + scanChar(); + } + + _tokenStartPtr = _codePtr - 1; + _tokenLine = _currentLineNumber; + + if (_char.isNull()) + return EOF_SYMBOL; + + const QChar ch = _char; + scanChar(); + + switch (ch.unicode()) { + case '~': return T_TILDE; + case '}': return T_RBRACE; + + case '|': + if (_char == QLatin1Char('|')) { + scanChar(); + return T_OR_OR; + } else if (_char == QLatin1Char('=')) { + scanChar(); + return T_OR_EQ; + } + return T_OR; + + case '{': return T_LBRACE; + + case '^': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_XOR_EQ; + } + return T_XOR; + + case ']': return T_RBRACKET; + case '[': return T_LBRACKET; + case '?': return T_QUESTION; + + case '>': + if (_char == QLatin1Char('>')) { + scanChar(); + if (_char == QLatin1Char('>')) { + scanChar(); + if (_char == QLatin1Char('=')) { + scanChar(); + return T_GT_GT_GT_EQ; + } + return T_GT_GT_GT; + } else if (_char == QLatin1Char('=')) { + scanChar(); + return T_GT_GT_EQ; + } + return T_GT_GT; + } else if (_char == QLatin1Char('=')) { + scanChar(); + return T_GE; + } + return T_GT; + + case '=': + if (_char == QLatin1Char('=')) { + scanChar(); + if (_char == QLatin1Char('=')) { + scanChar(); + return T_EQ_EQ_EQ; + } + return T_EQ_EQ; + } + return T_EQ; + + case '<': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_LE; + } else if (_char == QLatin1Char('<')) { + scanChar(); + if (_char == QLatin1Char('=')) { + scanChar(); + return T_LT_LT_EQ; + } + return T_LT_LT; + } + return T_LT; + + case ';': return T_SEMICOLON; + case ':': return T_COLON; + + case '/': + if (_char == QLatin1Char('*')) { + scanChar(); + while (!_char.isNull()) { + if (_char == QLatin1Char('*')) { + scanChar(); + if (_char == QLatin1Char('/')) { + scanChar(); + + if (_engine) { + _engine->addComment(tokenOffset() + 2, + int(_codePtr - _tokenStartPtr - 1 - 4), + tokenStartLine(), tokenStartColumn() + 2); + } + + goto again; + } + } else { + scanChar(); + } + } + } else if (_char == QLatin1Char('/')) { + while (!_char.isNull() && _char != QLatin1Char('\n')) { + scanChar(); + } + if (_engine) { + _engine->addComment(tokenOffset() + 2, int(_codePtr - _tokenStartPtr - 1 - 2), + tokenStartLine(), tokenStartColumn() + 2); + } + goto again; + } if (_char == QLatin1Char('=')) { + scanChar(); + return T_DIVIDE_EQ; + } + return T_DIVIDE_; + + case '.': + if (_char.isDigit()) { + QVarLengthArray chars; + + chars.append(ch.unicode()); // append the `.' + + while (_char.isDigit()) { + chars.append(_char.unicode()); + scanChar(); + } + + if (_char == QLatin1Char('e') || _char == QLatin1Char('E')) { + if (_codePtr[0].isDigit() || ((_codePtr[0] == QLatin1Char('+') || _codePtr[0] == QLatin1Char('-')) && + _codePtr[1].isDigit())) { + + chars.append(_char.unicode()); + scanChar(); // consume `e' + + if (_char == QLatin1Char('+') || _char == QLatin1Char('-')) { + chars.append(_char.unicode()); + scanChar(); // consume the sign + } + + while (_char.isDigit()) { + chars.append(_char.unicode()); + scanChar(); + } + } + } + + chars.append('\0'); + + const char *begin = chars.constData(); + const char *end = nullptr; + bool ok = false; + + _tokenValue = qstrtod(begin, &end, &ok); + + if (end - begin != chars.size() - 1) { + _errorCode = IllegalExponentIndicator; + _errorMessage = QCoreApplication::translate("QmlParser", "Illegal syntax for exponential number"); + return T_ERROR; + } + + return T_NUMERIC_LITERAL; + } + return T_DOT; + + case '-': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_MINUS_EQ; + } else if (_char == QLatin1Char('-')) { + scanChar(); + + if (_terminator && !_delimited && !_prohibitAutomaticSemicolon) { + _stackToken = T_MINUS_MINUS; + return T_SEMICOLON; + } + + return T_MINUS_MINUS; + } + return T_MINUS; + + case ',': return T_COMMA; + + case '+': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_PLUS_EQ; + } else if (_char == QLatin1Char('+')) { + scanChar(); + + if (_terminator && !_delimited && !_prohibitAutomaticSemicolon) { + _stackToken = T_PLUS_PLUS; + return T_SEMICOLON; + } + + return T_PLUS_PLUS; + } + return T_PLUS; + + case '*': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_STAR_EQ; + } + return T_STAR; + + case ')': return T_RPAREN; + case '(': return T_LPAREN; + + case '&': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_AND_EQ; + } else if (_char == QLatin1Char('&')) { + scanChar(); + return T_AND_AND; + } + return T_AND; + + case '%': + if (_char == QLatin1Char('=')) { + scanChar(); + return T_REMAINDER_EQ; + } + return T_REMAINDER; + + case '!': + if (_char == QLatin1Char('=')) { + scanChar(); + if (_char == QLatin1Char('=')) { + scanChar(); + return T_NOT_EQ_EQ; + } + return T_NOT_EQ; + } + return T_NOT; + + case '\'': + case '"': { + const QChar quote = ch; + bool multilineStringLiteral = false; + + const QChar *startCode = _codePtr; + + if (_engine) { + while (!_char.isNull()) { + if (_char == QLatin1Char('\n') || _char == QLatin1Char('\\')) { + break; + } else if (_char == quote) { + _tokenSpell = _engine->midRef( + int(startCode - _code.unicode() - 1), int(_codePtr - startCode)); + scanChar(); + + return T_STRING_LITERAL; + } + scanChar(); + } + } + + _validTokenText = true; + _tokenText.resize(0); + startCode--; + while (startCode != _codePtr - 1) + _tokenText += *startCode++; + + while (! _char.isNull()) { + if (_char == QLatin1Char('\n')) { + multilineStringLiteral = true; + _tokenText += _char; + scanChar(); + } else if (_char == quote) { + scanChar(); + + if (_engine) + _tokenSpell = _engine->newStringRef(_tokenText); + + return multilineStringLiteral ? T_MULTILINE_STRING_LITERAL : T_STRING_LITERAL; + } else if (_char == QLatin1Char('\\')) { + scanChar(); + + QChar u; + bool ok = false; + + switch (_char.unicode()) { + // unicode escape sequence + case 'u': + u = decodeUnicodeEscapeCharacter(&ok); + if (! ok) + u = _char; + break; + + // hex escape sequence + case 'x': + case 'X': + if (isHexDigit(_codePtr[0]) && isHexDigit(_codePtr[1])) { + scanChar(); + + const QChar c1 = _char; + scanChar(); + + const QChar c2 = _char; + scanChar(); + + u = convertHex(c1, c2); + } else { + u = _char; + } + break; + + // single character escape sequence + case '\\': u = QLatin1Char('\\'); scanChar(); break; + case '\'': u = QLatin1Char('\''); scanChar(); break; + case '\"': u = QLatin1Char('\"'); scanChar(); break; + case 'b': u = QLatin1Char('\b'); scanChar(); break; + case 'f': u = QLatin1Char('\f'); scanChar(); break; + case 'n': u = QLatin1Char('\n'); scanChar(); break; + case 'r': u = QLatin1Char('\r'); scanChar(); break; + case 't': u = QLatin1Char('\t'); scanChar(); break; + case 'v': u = QLatin1Char('\v'); scanChar(); break; + + case '0': + if (! _codePtr[1].isDigit()) { + scanChar(); + u = QLatin1Char('\0'); + } else { + // ### parse deprecated octal escape sequence ? + u = _char; + } + break; + + case '\r': + while (_char == QLatin1Char('\r')) + scanChar(); + + if (_char == QLatin1Char('\n')) { + u = _char; + scanChar(); + } else { + u = QLatin1Char('\n'); + } + + break; + + case '\n': + u = _char; + scanChar(); + break; + + default: + // non escape character + u = _char; + scanChar(); + } + + _tokenText += u; + } else { + _tokenText += _char; + scanChar(); + } + } + + _errorCode = UnclosedStringLiteral; + _errorMessage = QCoreApplication::translate("QmlParser", "Unclosed string at end of line"); + return T_ERROR; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scanNumber(ch); + + default: + if (ch.isLetter() || ch == QLatin1Char('$') || ch == QLatin1Char('_') || (ch == QLatin1Char('\\') && _char == QLatin1Char('u'))) { + bool identifierWithEscapeChars = false; + if (ch == QLatin1Char('\\')) { + identifierWithEscapeChars = true; + _tokenText.resize(0); + bool ok = false; + _tokenText += decodeUnicodeEscapeCharacter(&ok); + _validTokenText = true; + if (! ok) { + _errorCode = IllegalUnicodeEscapeSequence; + _errorMessage = QCoreApplication::translate("QmlParser", "Illegal unicode escape sequence"); + return T_ERROR; + } + } + while (true) { + if (_char.isLetterOrNumber() || _char == QLatin1Char('$') || _char == QLatin1Char('_')) { + if (identifierWithEscapeChars) + _tokenText += _char; + + scanChar(); + } else if (_char == QLatin1Char('\\') && _codePtr[0] == QLatin1Char('u')) { + if (! identifierWithEscapeChars) { + identifierWithEscapeChars = true; + _tokenText.resize(0); + _tokenText.insert(0, _tokenStartPtr, int(_codePtr - _tokenStartPtr - 1)); + _validTokenText = true; + } + + scanChar(); // skip '\\' + bool ok = false; + _tokenText += decodeUnicodeEscapeCharacter(&ok); + if (! ok) { + _errorCode = IllegalUnicodeEscapeSequence; + _errorMessage = QCoreApplication::translate("QmlParser", "Illegal unicode escape sequence"); + return T_ERROR; + } + } else { + _tokenLength = int(_codePtr - _tokenStartPtr - 1); + + int kind = T_IDENTIFIER; + + if (! identifierWithEscapeChars) + kind = classify(_tokenStartPtr, _tokenLength, _qmlMode); + + if (_engine) { + if (kind == T_IDENTIFIER && identifierWithEscapeChars) { + _tokenSpell = _engine->newStringRef(_tokenText); + } else { + _tokenSpell = _engine->midRef( + int(_tokenStartPtr - _code.unicode()), _tokenLength); + } + } + + return kind; + } + } + } + + break; + } + + return T_ERROR; +} + +int Lexer::scanNumber(QChar ch) +{ + if (ch != QLatin1Char('0')) { + double integer = ch.unicode() - '0'; + + QChar n = _char; + const QChar *code = _codePtr; + while (n.isDigit()) { + integer = integer * 10 + (n.unicode() - '0'); + n = *code++; + } + + if (n != QLatin1Char('.') && n != QLatin1Char('e') && n != QLatin1Char('E')) { + if (code != _codePtr) { + _codePtr = code - 1; + scanChar(); + } + _tokenValue = integer; + return T_NUMERIC_LITERAL; + } + } + + QVarLengthArray chars; + chars.append(ch.unicode()); + + if (ch == QLatin1Char('0') && (_char == QLatin1Char('x') || _char == QLatin1Char('X'))) { + // parse hex integer literal + + chars.append(_char.unicode()); + scanChar(); // consume `x' + + while (isHexDigit(_char)) { + chars.append(_char.unicode()); + scanChar(); + } + + _tokenValue = integerFromString(chars.constData(), chars.size(), 16); + return T_NUMERIC_LITERAL; + } + + // decimal integer literal + while (_char.isDigit()) { + chars.append(_char.unicode()); + scanChar(); // consume the digit + } + + if (_char == QLatin1Char('.')) { + chars.append(_char.unicode()); + scanChar(); // consume `.' + + while (_char.isDigit()) { + chars.append(_char.unicode()); + scanChar(); + } + + if (_char == QLatin1Char('e') || _char == QLatin1Char('E')) { + if (_codePtr[0].isDigit() || ((_codePtr[0] == QLatin1Char('+') || _codePtr[0] == QLatin1Char('-')) && + _codePtr[1].isDigit())) { + + chars.append(_char.unicode()); + scanChar(); // consume `e' + + if (_char == QLatin1Char('+') || _char == QLatin1Char('-')) { + chars.append(_char.unicode()); + scanChar(); // consume the sign + } + + while (_char.isDigit()) { + chars.append(_char.unicode()); + scanChar(); + } + } + } + } else if (_char == QLatin1Char('e') || _char == QLatin1Char('E')) { + if (_codePtr[0].isDigit() || ((_codePtr[0] == QLatin1Char('+') || _codePtr[0] == QLatin1Char('-')) && + _codePtr[1].isDigit())) { + + chars.append(_char.unicode()); + scanChar(); // consume `e' + + if (_char == QLatin1Char('+') || _char == QLatin1Char('-')) { + chars.append(_char.unicode()); + scanChar(); // consume the sign + } + + while (_char.isDigit()) { + chars.append(_char.unicode()); + scanChar(); + } + } + } + + if (chars.size() == 1) { + // if we ended up with a single digit, then it was a '0' + _tokenValue = 0; + return T_NUMERIC_LITERAL; + } + + chars.append('\0'); + + const char *begin = chars.constData(); + const char *end = nullptr; + bool ok = false; + + _tokenValue = qstrtod(begin, &end, &ok); + + if (end - begin != chars.size() - 1) { + _errorCode = IllegalExponentIndicator; + _errorMessage = QCoreApplication::translate("QmlParser", "Illegal syntax for exponential number"); + return T_ERROR; + } + + return T_NUMERIC_LITERAL; +} + +bool Lexer::scanRegExp(RegExpBodyPrefix prefix) +{ + _tokenText.resize(0); + _validTokenText = true; + _patternFlags = 0; + + if (prefix == EqualPrefix) + _tokenText += QLatin1Char('='); + + while (true) { + switch (_char.unicode()) { + case 0: // eof + case '\n': case '\r': // line terminator + _errorMessage = QCoreApplication::translate("QmlParser", "Unterminated regular expression literal"); + return false; + + case '/': + scanChar(); + + // scan the flags + _patternFlags = 0; + while (isIdentLetter(_char)) { + int flag = regExpFlagFromChar(_char); + if (flag == 0) { + _errorMessage = QCoreApplication::translate("QmlParser", "Invalid regular expression flag '%0'") + .arg(QChar(_char)); + return false; + } + _patternFlags |= flag; + scanChar(); + } + + _tokenLength = int(_codePtr - _tokenStartPtr - 1); + return true; + + case '\\': + // regular expression backslash sequence + _tokenText += _char; + scanChar(); + + if (_char.isNull() || isLineTerminator()) { + _errorMessage = QCoreApplication::translate("QmlParser", "Unterminated regular expression backslash sequence"); + return false; + } + + _tokenText += _char; + scanChar(); + break; + + case '[': + // regular expression class + _tokenText += _char; + scanChar(); + + while (! _char.isNull() && ! isLineTerminator()) { + if (_char == QLatin1Char(']')) + break; + else if (_char == QLatin1Char('\\')) { + // regular expression backslash sequence + _tokenText += _char; + scanChar(); + + if (_char.isNull() || isLineTerminator()) { + _errorMessage = QCoreApplication::translate("QmlParser", "Unterminated regular expression backslash sequence"); + return false; + } + + _tokenText += _char; + scanChar(); + } else { + _tokenText += _char; + scanChar(); + } + } + + if (_char != QLatin1Char(']')) { + _errorMessage = QCoreApplication::translate("QmlParser", "Unterminated regular expression class"); + return false; + } + + _tokenText += _char; + scanChar(); // skip ] + break; + + default: + _tokenText += _char; + scanChar(); + } // switch + } // while + + return false; +} + +bool Lexer::isLineTerminator() const +{ + return (_char == QLatin1Char('\n') || _char == QLatin1Char('\r')); +} + +bool Lexer::isIdentLetter(QChar ch) +{ + // ASCII-biased, since all reserved words are ASCII, aand hence the + // bulk of content to be parsed. + if ((ch >= QLatin1Char('a') && ch <= QLatin1Char('z')) + || (ch >= QLatin1Char('A') && ch <= QLatin1Char('Z')) + || ch == QLatin1Char('$') + || ch == QLatin1Char('_')) + return true; + if (ch.unicode() < 128) + return false; + return ch.isLetterOrNumber(); +} + +bool Lexer::isDecimalDigit(ushort c) +{ + return (c >= '0' && c <= '9'); +} + +bool Lexer::isHexDigit(QChar c) +{ + return ((c >= QLatin1Char('0') && c <= QLatin1Char('9')) + || (c >= QLatin1Char('a') && c <= QLatin1Char('f')) + || (c >= QLatin1Char('A') && c <= QLatin1Char('F'))); +} + +bool Lexer::isOctalDigit(ushort c) +{ + return (c >= '0' && c <= '7'); +} + +int Lexer::tokenEndLine() const +{ + return _currentLineNumber; +} + +int Lexer::tokenEndColumn() const +{ + return int(_codePtr - _lastLinePtr); +} + +QString Lexer::tokenText() const +{ + if (_validTokenText) + return _tokenText; + + if (_tokenKind == T_STRING_LITERAL) + return QString(_tokenStartPtr + 1, _tokenLength - 2); + + return QString(_tokenStartPtr, _tokenLength); +} + +Lexer::Error Lexer::errorCode() const +{ + return _errorCode; +} + +QString Lexer::errorMessage() const +{ + return _errorMessage; +} + +void Lexer::syncProhibitAutomaticSemicolon() +{ + if (_parenthesesState == BalancedParentheses) { + // we have seen something like "if (foo)", which means we should + // never insert an automatic semicolon at this point, since it would + // then be expanded into an empty statement (ECMA-262 7.9.1) + _prohibitAutomaticSemicolon = true; + _parenthesesState = IgnoreParentheses; + } else { + _prohibitAutomaticSemicolon = false; + } +} + +bool Lexer::prevTerminator() const +{ + return _terminator; +} + +bool Lexer::followsClosingBrace() const +{ + return _followsClosingBrace; +} + +bool Lexer::canInsertAutomaticSemicolon(int token) const +{ + return token == T_RBRACE + || token == EOF_SYMBOL + || _terminator + || _followsClosingBrace; +} + +bool Lexer::scanDirectives(Directives *directives) +{ + if (_qmlMode) { + // the directives are a Javascript-only extension. + return false; + } + + lex(); // fetch the first token + + if (_tokenKind != T_DOT) + return true; + + do { + lex(); // skip T_DOT + + const int lineNumber = tokenStartLine(); + + if (! (_tokenKind == T_IDENTIFIER || _tokenKind == T_RESERVED_WORD)) + return false; // expected a valid QML/JS directive + + const QString directiveName = tokenText(); + + if (! (directiveName == QLatin1String("pragma") || + directiveName == QLatin1String("import"))) + return false; // not a valid directive name + + // it must be a pragma or an import directive. + if (directiveName == QLatin1String("pragma")) { + // .pragma library + if (! (lex() == T_IDENTIFIER && tokenText() == QLatin1String("library"))) + return false; // expected `library + + // we found a .pragma library directive + directives->pragmaLibrary(); + + } else { + Q_ASSERT(directiveName == QLatin1String("import")); + lex(); // skip .import + + QString pathOrUri; + QString version; + bool fileImport = false; // file or uri import + + if (_tokenKind == T_STRING_LITERAL) { + // .import T_STRING_LITERAL as T_IDENTIFIER + + fileImport = true; + pathOrUri = tokenText(); + + } else if (_tokenKind == T_IDENTIFIER) { + // .import T_IDENTIFIER (. T_IDENTIFIER)* T_NUMERIC_LITERAL as T_IDENTIFIER + + pathOrUri = tokenText(); + + lex(); // skip the first T_IDENTIFIER + for (; _tokenKind == T_DOT; lex()) { + if (lex() != T_IDENTIFIER) + return false; + + pathOrUri += QLatin1Char('.'); + pathOrUri += tokenText(); + } + + if (_tokenKind != T_NUMERIC_LITERAL) + return false; // expected the module version number + + version = tokenText(); + } + + // + // recognize the mandatory `as' followed by the module name + // + if (! (lex() == T_RESERVED_WORD && tokenText() == QLatin1String("as"))) + return false; // expected `as' + + if (lex() != T_IDENTIFIER) + return false; // expected module name + + const QString module = tokenText(); + + if (fileImport) + directives->importFile(pathOrUri, module); + else + directives->importModule(pathOrUri, version, module); + } + + if (tokenStartLine() != lineNumber) + return false; // the directives cannot span over multiple lines + + // fetch the first token after the .pragma/.import directive + lex(); + } while (_tokenKind == T_DOT); + + return true; +} + +} // namespace QbsQmlJS + +#include "qmljskeywords_p.h" diff --git a/src/lib/corelib/parser/qmljslexer_p.h b/src/lib/corelib/parser/qmljslexer_p.h new file mode 100644 index 00000000..cf41fb25 --- /dev/null +++ b/src/lib/corelib/parser/qmljslexer_p.h @@ -0,0 +1,244 @@ +/**************************************************************************** +** +** 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 QMLJSLEXER_P_H +#define QMLJSLEXER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qmljsglobal_p.h" +#include "qmljsgrammar_p.h" +#include +#include + +namespace QbsQmlJS { + +class Engine; + +class QML_PARSER_EXPORT Directives { +public: + virtual ~Directives() = default; + + virtual void pragmaLibrary() + { + } + + virtual void importFile(const QString &jsfile, const QString &module) + { + Q_UNUSED(jsfile); + Q_UNUSED(module); + } + + virtual void importModule(const QString &uri, const QString &version, const QString &module) + { + Q_UNUSED(uri); + Q_UNUSED(version); + Q_UNUSED(module); + } +}; + +class QBS_AUTOTEST_EXPORT Lexer: public QmlJSGrammar +{ +public: + enum { + T_ABSTRACT = T_RESERVED_WORD, + T_BOOLEAN = T_RESERVED_WORD, + T_BYTE = T_RESERVED_WORD, + T_CHAR = T_RESERVED_WORD, + T_CLASS = T_RESERVED_WORD, + T_DOUBLE = T_RESERVED_WORD, + T_ENUM = T_RESERVED_WORD, + T_EXPORT = T_RESERVED_WORD, + T_EXTENDS = T_RESERVED_WORD, + T_FINAL = T_RESERVED_WORD, + T_FLOAT = T_RESERVED_WORD, + T_GOTO = T_RESERVED_WORD, + T_IMPLEMENTS = T_RESERVED_WORD, + T_INT = T_RESERVED_WORD, + T_INTERFACE = T_RESERVED_WORD, + T_LET = T_RESERVED_WORD, + T_LONG = T_RESERVED_WORD, + T_NATIVE = T_RESERVED_WORD, + T_PACKAGE = T_RESERVED_WORD, + T_PRIVATE = T_RESERVED_WORD, + T_PROTECTED = T_RESERVED_WORD, + T_SHORT = T_RESERVED_WORD, + T_STATIC = T_RESERVED_WORD, + T_SUPER = T_RESERVED_WORD, + T_SYNCHRONIZED = T_RESERVED_WORD, + T_THROWS = T_RESERVED_WORD, + T_TRANSIENT = T_RESERVED_WORD, + T_VOLATILE = T_RESERVED_WORD, + T_YIELD = T_RESERVED_WORD + }; + + enum Error { + NoError, + IllegalCharacter, + UnclosedStringLiteral, + IllegalEscapeSequence, + IllegalUnicodeEscapeSequence, + UnclosedComment, + IllegalExponentIndicator, + IllegalIdentifier + }; + + enum RegExpBodyPrefix { + NoPrefix, + EqualPrefix + }; + + enum RegExpFlag { + RegExp_Global = 0x01, + RegExp_IgnoreCase = 0x02, + RegExp_Multiline = 0x04 + }; + +public: + Lexer(Engine *engine); + + bool qmlMode() const; + + QString code() const; + void setCode(const QString &code, int lineno, bool qmlMode = true); + + int lex(); + + bool scanRegExp(RegExpBodyPrefix prefix = NoPrefix); + bool scanDirectives(Directives *directives); + + int regExpFlags() const { return _patternFlags; } + QString regExpPattern() const { return _tokenText; } + + int tokenKind() const { return _tokenKind; } + int tokenOffset() const { return _tokenStartPtr - _code.unicode(); } + int tokenLength() const { return _tokenLength; } + + int tokenStartLine() const { return _tokenLine; } + int tokenStartColumn() const { return _tokenStartPtr - _tokenLinePtr + 1; } + + int tokenEndLine() const; + int tokenEndColumn() const; + + inline QStringRef tokenSpell() const { return _tokenSpell; } + double tokenValue() const { return _tokenValue; } + QString tokenText() const; + + Error errorCode() const; + QString errorMessage() const; + + bool prevTerminator() const; + bool followsClosingBrace() const; + bool canInsertAutomaticSemicolon(int token) const; + + enum ParenthesesState { + IgnoreParentheses, + CountParentheses, + BalancedParentheses + }; + +protected: + int classify(const QChar *s, int n, bool qmlMode); + +private: + inline void scanChar(); + int scanToken(); + int scanNumber(QChar ch); + + bool isLineTerminator() const; + static bool isIdentLetter(QChar c); + static bool isDecimalDigit(ushort c); + static bool isHexDigit(QChar c); + static bool isOctalDigit(ushort c); + static bool isUnicodeEscapeSequence(const QChar *chars); + + void syncProhibitAutomaticSemicolon(); + QChar decodeUnicodeEscapeCharacter(bool *ok); + +private: + Engine *_engine; + + QString _code; + QString _tokenText; + QString _errorMessage; + QStringRef _tokenSpell; + + const QChar *_codePtr; + const QChar *_lastLinePtr; + const QChar *_tokenLinePtr; + const QChar *_tokenStartPtr; + + QChar _char; + Error _errorCode; + + int _currentLineNumber; + double _tokenValue; + + // parentheses state + ParenthesesState _parenthesesState; + int _parenthesesCount; + + int _stackToken; + + int _patternFlags; + int _tokenKind; + int _tokenLength; + int _tokenLine; + + bool _validTokenText; + bool _prohibitAutomaticSemicolon; + bool _restrictedKeyword; + bool _terminator; + bool _followsClosingBrace; + bool _delimited; + bool _qmlMode; +}; + +} // namespace QbsQmlJS + +#endif // LEXER_H diff --git a/src/lib/corelib/parser/qmljsmemorypool_p.h b/src/lib/corelib/parser/qmljsmemorypool_p.h new file mode 100644 index 00000000..f7de7bbf --- /dev/null +++ b/src/lib/corelib/parser/qmljsmemorypool_p.h @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** 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 QMLJSMEMORYPOOL_P_H +#define QMLJSMEMORYPOOL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qmljsglobal_p.h" + +#include +#include +#include + +#include + +namespace QbsQmlJS { + +class QML_PARSER_EXPORT MemoryPool : public QSharedData +{ + MemoryPool(const MemoryPool &other); + void operator =(const MemoryPool &other); + +public: + MemoryPool() + : _blocks(nullptr), + _allocatedBlocks(0), + _blockCount(-1), + _ptr(nullptr), + _end(nullptr) + { } + + ~MemoryPool() + { + if (_blocks) { + for (int i = 0; i < _allocatedBlocks; ++i) { + if (char *b = _blocks[i]) + free(b); + } + + free(_blocks); + } + } + + inline void *allocate(size_t size) + { + size = (size + 7) & ~7; + if (_ptr && (_ptr + size < _end)) { + void *addr = _ptr; + _ptr += size; + return addr; + } + return allocate_helper(size); + } + + void reset() + { + _blockCount = -1; + _ptr = _end = nullptr; + } + +private: + void *allocate_helper(size_t size) + { + Q_ASSERT(size < BLOCK_SIZE); + + if (++_blockCount == _allocatedBlocks) { + if (! _allocatedBlocks) + _allocatedBlocks = DEFAULT_BLOCK_COUNT; + else + _allocatedBlocks *= 2; + + _blocks = (char **) realloc(_blocks, sizeof(char *) * _allocatedBlocks); + + for (int index = _blockCount; index < _allocatedBlocks; ++index) + _blocks[index] = nullptr; + } + + char *&block = _blocks[_blockCount]; + + if (! block) + block = (char *) malloc(BLOCK_SIZE); + + _ptr = block; + _end = _ptr + BLOCK_SIZE; + + void *addr = _ptr; + _ptr += size; + return addr; + } + +private: + char **_blocks; + int _allocatedBlocks; + int _blockCount; + char *_ptr; + char *_end; + + enum + { + BLOCK_SIZE = 8 * 1024, + DEFAULT_BLOCK_COUNT = 8 + }; +}; + +class QML_PARSER_EXPORT Managed +{ + Managed(const Managed &other); + void operator = (const Managed &other); + +public: + Managed() = default; + ~Managed() = default; + + void *operator new(size_t size, MemoryPool *pool) { return pool->allocate(size); } + void operator delete(void *) {} + void operator delete(void *, MemoryPool *) {} +}; + +} // namespace QbsQmlJS + +#endif diff --git a/src/lib/corelib/parser/qmljsparser.cpp b/src/lib/corelib/parser/qmljsparser.cpp new file mode 100644 index 00000000..d2c87c7c --- /dev/null +++ b/src/lib/corelib/parser/qmljsparser.cpp @@ -0,0 +1,1819 @@ +/**************************************************************************** +** +** 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 +#include + +#include + +#include "qmljsengine_p.h" +#include "qmljslexer_p.h" +#include "qmljsast_p.h" +#include "qmljsmemorypool_p.h" + + + +#include "qmljsparser_p.h" +#include + +// +// This file is automatically generated from qmljs.g. +// Changes will be lost. +// + +namespace QbsQmlJS { + +void Parser::reallocateStack() +{ + if (! stack_size) + stack_size = 128; + else + stack_size <<= 1; + + sym_stack = reinterpret_cast (realloc(sym_stack, stack_size * sizeof(Value))); + state_stack = reinterpret_cast (realloc(state_stack, stack_size * sizeof(int))); + location_stack = reinterpret_cast (realloc(location_stack, stack_size * sizeof(AST::SourceLocation))); + string_stack = reinterpret_cast (realloc( + static_cast(string_stack), stack_size * sizeof(QStringRef))); +} + +Parser::Parser(Engine *engine): + driver(engine), + pool(engine->pool()), + tos(0), + stack_size(0), + sym_stack(nullptr), + state_stack(nullptr), + location_stack(nullptr), + string_stack(nullptr), + program(nullptr), + first_token(nullptr), + last_token(nullptr) +{ +} + +Parser::~Parser() +{ + if (stack_size) { + free(sym_stack); + free(state_stack); + free(location_stack); + free(string_stack); + } +} + +static inline AST::SourceLocation location(Lexer *lexer) +{ + AST::SourceLocation loc; + loc.offset = lexer->tokenOffset(); + loc.length = lexer->tokenLength(); + loc.startLine = lexer->tokenStartLine(); + loc.startColumn = lexer->tokenStartColumn(); + return loc; +} + +AST::UiQualifiedId *Parser::reparseAsQualifiedId(AST::ExpressionNode *expr) +{ + QVarLengthArray nameIds; + QVarLengthArray locations; + + AST::ExpressionNode *it = expr; + while (const auto m = AST::cast(it)) { + nameIds.append(m->name); + locations.append(m->identifierToken); + it = m->base; + } + + if (const auto idExpr = AST::cast(it)) { + const auto q = new (pool) AST::UiQualifiedId(idExpr->name); + q->identifierToken = idExpr->identifierToken; + + AST::UiQualifiedId *currentId = q; + for (int i = nameIds.size() - 1; i != -1; --i) { + currentId = new (pool) AST::UiQualifiedId(currentId, nameIds[i]); + currentId->identifierToken = locations[i]; + } + + return currentId->finish(); + } + + return nullptr; +} + +bool Parser::parse(int startToken) +{ + Lexer *lexer = driver->lexer(); + bool hadErrors = false; + int yytoken = -1; + int action = 0; + + token_buffer[0].token = startToken; + first_token = &token_buffer[0]; + if (startToken == T_FEED_JS_PROGRAM) { + Directives ignoreDirectives; + Directives *directives = driver->directives(); + if (!directives) + directives = &ignoreDirectives; + lexer->scanDirectives(directives); + token_buffer[1].token = lexer->tokenKind(); + token_buffer[1].dval = lexer->tokenValue(); + token_buffer[1].loc = location(lexer); + token_buffer[1].spell = lexer->tokenSpell(); + last_token = &token_buffer[2]; + } else { + last_token = &token_buffer[1]; + } + + tos = -1; + program = nullptr; + + do { + if (++tos == stack_size) + reallocateStack(); + + state_stack[tos] = action; + + _Lcheck_token: + if (yytoken == -1 && -TERMINAL_COUNT != action_index[action]) { + yyprevlloc = yylloc; + + if (first_token == last_token) { + yytoken = lexer->lex(); + yylval = lexer->tokenValue(); + yytokenspell = lexer->tokenSpell(); + yylloc = location(lexer); + } else { + yytoken = first_token->token; + yylval = first_token->dval; + yytokenspell = first_token->spell; + yylloc = first_token->loc; + ++first_token; + } + } + + action = t_action(action, yytoken); + if (action > 0) { + if (action != ACCEPT_STATE) { + yytoken = -1; + sym(1).dval = yylval; + stringRef(1) = yytokenspell; + loc(1) = yylloc; + } else { + --tos; + return ! hadErrors; + } + } else if (action < 0) { + const int r = -action - 1; + tos -= rhs[r]; + + switch (r) { + +case 0: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; + +case 1: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; + +case 2: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; + +case 3: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; + +case 4: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; + +case 5: { + sym(1).Node = sym(2).Node; + program = sym(1).Node; +} break; + +case 6: { + sym(1).UiProgram = new (pool) AST::UiProgram(sym(1).UiImportList, + sym(2).UiObjectMemberList->finish()); +} break; + +case 8: { + sym(1).Node = sym(1).UiImportList->finish(); +} break; + +case 9: { + sym(1).Node = new (pool) AST::UiImportList(sym(1).UiImport); +} break; + +case 10: { + sym(1).Node = new (pool) AST::UiImportList(sym(1).UiImportList, sym(2).UiImport); +} break; + +case 13: { + sym(1).UiImport->semicolonToken = loc(2); +} break; + +case 15: { + sym(1).UiImport->versionToken = loc(2); + sym(1).UiImport->semicolonToken = loc(3); +} break; + +case 17: { + sym(1).UiImport->versionToken = loc(2); + sym(1).UiImport->asToken = loc(3); + sym(1).UiImport->importIdToken = loc(4); + sym(1).UiImport->importId = stringRef(4); + sym(1).UiImport->semicolonToken = loc(5); +} break; + +case 19: { + sym(1).UiImport->asToken = loc(2); + sym(1).UiImport->importIdToken = loc(3); + sym(1).UiImport->importId = stringRef(3); + sym(1).UiImport->semicolonToken = loc(4); +} break; + +case 20: { + AST::UiImport *node = nullptr; + + if (const auto importIdLiteral = AST::cast(sym(2).Expression)) { + node = new (pool) AST::UiImport(importIdLiteral->value); + node->fileNameToken = loc(2); + } else if (AST::UiQualifiedId *qualifiedId = reparseAsQualifiedId(sym(2).Expression)) { + node = new (pool) AST::UiImport(qualifiedId); + node->fileNameToken = loc(2); + } + + sym(1).Node = node; + + if (node) { + node->importToken = loc(1); + } else { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, loc(1), + QStringLiteral("Expected a qualified name id or a string literal"))); + + return false; // ### remove me + } +} break; + +case 21: { + sym(1).Node = nullptr; +} break; + +case 22: { + sym(1).Node = new (pool) AST::UiObjectMemberList(sym(1).UiObjectMember); +} break; + +case 23: { + sym(1).Node = new (pool) AST::UiObjectMemberList(sym(1).UiObjectMember); +} break; + +case 24: { + const auto node = new (pool) AST:: UiObjectMemberList( + sym(1).UiObjectMemberList, sym(2).UiObjectMember); + sym(1).Node = node; +} break; + +case 25: { + sym(1).Node = new (pool) AST::UiArrayMemberList(sym(1).UiObjectMember); +} break; + +case 26: { + const auto node = new (pool) AST::UiArrayMemberList( + sym(1).UiArrayMemberList, sym(3).UiObjectMember); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 27: { + const auto node = new (pool) AST::UiObjectInitializer(nullptr); + node->lbraceToken = loc(1); + node->rbraceToken = loc(2); + sym(1).Node = node; +} break; + +case 28: { + const auto node = new (pool) AST::UiObjectInitializer(sym(2).UiObjectMemberList->finish()); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; + +case 29: { + const auto node = new (pool) AST::UiObjectDefinition(sym(1).UiQualifiedId, + sym(2).UiObjectInitializer); + sym(1).Node = node; +} break; + +case 31: { + const auto node = new (pool) AST::UiArrayBinding( + sym(1).UiQualifiedId, sym(4).UiArrayMemberList->finish()); + node->colonToken = loc(2); + node->lbracketToken = loc(3); + node->rbracketToken = loc(5); + sym(1).Node = node; +} break; + +case 32: { + const auto node = new (pool) AST::UiObjectBinding( + sym(1).UiQualifiedId, sym(3).UiQualifiedId, sym(4).UiObjectInitializer); + node->colonToken = loc(2); + sym(1).Node = node; +} break; + +case 33: { + const auto node = new (pool) AST::UiObjectBinding( + sym(3).UiQualifiedId, sym(1).UiQualifiedId, sym(4).UiObjectInitializer); + node->colonToken = loc(2); + node->hasOnToken = true; + sym(1).Node = node; +} break; + +case 41: +{ + const auto node = new (pool) AST::UiScriptBinding( + sym(1).UiQualifiedId, sym(3).Statement); + node->colonToken = loc(2); + sym(1).Node = node; +} break; + +case 45: { + sym(1).Node = nullptr; +} break; + +case 46: { + sym(1).Node = sym(1).UiParameterList->finish (); +} break; + +case 47: { + const auto node = new (pool) AST::UiParameterList(stringRef(1), stringRef(2)); + node->propertyTypeToken = loc(1); + node->identifierToken = loc(2); + sym(1).Node = node; +} break; + +case 48: { + const auto node = new (pool) AST::UiParameterList(sym(1).UiParameterList, stringRef(3), stringRef(4)); + node->commaToken = loc(2); + node->identifierToken = loc(4); + sym(1).Node = node; +} break; + +case 50: { + const auto node = new (pool) AST::UiPublicMember(QStringRef(), stringRef(2)); + node->type = AST::UiPublicMember::Signal; + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(2); + node->parameters = sym(4).UiParameterList; + node->semicolonToken = loc(6); + sym(1).Node = node; +} break; + +case 52: { + const auto node = new (pool) AST::UiPublicMember(QStringRef(), stringRef(2)); + node->type = AST::UiPublicMember::Signal; + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(2); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; + +case 54: { + const auto node = new (pool) AST::UiPublicMember(stringRef(4), stringRef(6)); + node->typeModifier = stringRef(2); + node->propertyToken = loc(1); + node->typeModifierToken = loc(2); + node->typeToken = loc(4); + node->identifierToken = loc(6); + node->semicolonToken = loc(7); + sym(1).Node = node; +} break; + +case 56: { + const auto node = new (pool) AST::UiPublicMember(stringRef(2), stringRef(3)); + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(3); + node->semicolonToken = loc(4); + sym(1).Node = node; +} break; + +case 58: { + const auto node = new (pool) AST::UiPublicMember(stringRef(3), stringRef(4)); + node->isDefaultMember = true; + node->defaultToken = loc(1); + node->propertyToken = loc(2); + node->typeToken = loc(3); + node->identifierToken = loc(4); + node->semicolonToken = loc(5); + sym(1).Node = node; +} break; + +case 59: { + const auto node = new (pool) AST::UiPublicMember(stringRef(2), stringRef(3), + sym(5).Statement); + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(3); + node->colonToken = loc(4); + sym(1).Node = node; +} break; + +case 60: { + const auto node = new (pool) AST::UiPublicMember(stringRef(3), stringRef(4), + sym(6).Statement); + node->isReadonlyMember = true; + node->readonlyToken = loc(1); + node->propertyToken = loc(2); + node->typeToken = loc(3); + node->identifierToken = loc(4); + node->colonToken = loc(5); + sym(1).Node = node; +} break; + +case 61: { + const auto node = new (pool) AST::UiPublicMember(stringRef(3), stringRef(4), + sym(6).Statement); + node->isDefaultMember = true; + node->defaultToken = loc(1); + node->propertyToken = loc(2); + node->typeToken = loc(3); + node->identifierToken = loc(4); + node->colonToken = loc(5); + sym(1).Node = node; +} break; + +case 62: { + const auto node = new (pool) AST::UiPublicMember(stringRef(4), stringRef(6)); + node->typeModifier = stringRef(2); + node->propertyToken = loc(1); + node->typeModifierToken = loc(2); + node->typeToken = loc(4); + node->identifierToken = loc(6); + node->semicolonToken = loc(7); // insert a fake ';' before ':' + + const auto propertyName = new (pool) AST::UiQualifiedId(stringRef(6)); + propertyName->identifierToken = loc(6); + propertyName->next = nullptr; + + const auto binding = new (pool) AST::UiArrayBinding( + propertyName, sym(9).UiArrayMemberList->finish()); + binding->colonToken = loc(7); + binding->lbracketToken = loc(8); + binding->rbracketToken = loc(10); + + node->binding = binding; + + sym(1).Node = node; +} break; + +case 63: { + const auto node = new (pool) AST::UiPublicMember(stringRef(2), stringRef(3)); + node->propertyToken = loc(1); + node->typeToken = loc(2); + node->identifierToken = loc(3); + node->semicolonToken = loc(4); // insert a fake ';' before ':' + + const auto propertyName = new (pool) AST::UiQualifiedId(stringRef(3)); + propertyName->identifierToken = loc(3); + propertyName->next = nullptr; + + const auto binding = new (pool) AST::UiObjectBinding( + propertyName, sym(5).UiQualifiedId, sym(6).UiObjectInitializer); + binding->colonToken = loc(4); + + node->binding = binding; + + sym(1).Node = node; +} break; + +case 64: { + sym(1).Node = new (pool) AST::UiSourceElement(sym(1).Node); +} break; + +case 65: { + sym(1).Node = new (pool) AST::UiSourceElement(sym(1).Node); +} break; + +case 71: { + const auto node = new (pool) AST::ThisExpression(); + node->thisToken = loc(1); + sym(1).Node = node; +} break; + +case 72: { + const auto node = new (pool) AST::IdentifierExpression(stringRef(1)); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; + +case 73: { + const auto node = new (pool) AST::NullExpression(); + node->nullToken = loc(1); + sym(1).Node = node; +} break; + +case 74: { + const auto node = new (pool) AST::TrueLiteral(); + node->trueToken = loc(1); + sym(1).Node = node; +} break; + +case 75: { + const auto node = new (pool) AST::FalseLiteral(); + node->falseToken = loc(1); + sym(1).Node = node; +} break; + +case 76: { + const auto node = new (pool) AST::NumericLiteral(sym(1).dval); + node->literalToken = loc(1); + sym(1).Node = node; +} break; +case 77: +case 78: { + const auto node = new (pool) AST::StringLiteral(stringRef(1)); + node->literalToken = loc(1); + sym(1).Node = node; +} break; + +case 79: { + bool rx = lexer->scanRegExp(Lexer::NoPrefix); + if (!rx) { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, location(lexer), lexer->errorMessage())); + return false; // ### remove me + } + + loc(1).length = lexer->tokenLength(); + yylloc = loc(1); // adjust the location of the current token + + const auto node = new (pool) AST::RegExpLiteral( + driver->newStringRef(lexer->regExpPattern()), lexer->regExpFlags()); + node->literalToken = loc(1); + sym(1).Node = node; +} break; + +case 80: { + bool rx = lexer->scanRegExp(Lexer::EqualPrefix); + if (!rx) { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, location(lexer), lexer->errorMessage())); + return false; + } + + loc(1).length = lexer->tokenLength(); + yylloc = loc(1); // adjust the location of the current token + + const auto node = new (pool) AST::RegExpLiteral( + driver->newStringRef(lexer->regExpPattern()), lexer->regExpFlags()); + node->literalToken = loc(1); + sym(1).Node = node; +} break; + +case 81: { + const auto node = new (pool) AST::ArrayLiteral(static_cast(nullptr)); + node->lbracketToken = loc(1); + node->rbracketToken = loc(2); + sym(1).Node = node; +} break; + +case 82: { + const auto node = new (pool) AST::ArrayLiteral(sym(2).Elision->finish()); + node->lbracketToken = loc(1); + node->rbracketToken = loc(3); + sym(1).Node = node; +} break; + +case 83: { + const auto node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish ()); + node->lbracketToken = loc(1); + node->rbracketToken = loc(3); + sym(1).Node = node; +} break; + +case 84: { + const auto node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish (), nullptr); + node->lbracketToken = loc(1); + node->commaToken = loc(3); + node->rbracketToken = loc(4); + sym(1).Node = node; +} break; + +case 85: { + const auto node = new (pool) AST::ArrayLiteral(sym(2).ElementList->finish (), + sym(4).Elision->finish()); + node->lbracketToken = loc(1); + node->commaToken = loc(3); + node->rbracketToken = loc(5); + sym(1).Node = node; +} break; + +case 86: { + AST::ObjectLiteral *node = nullptr; + if (sym(2).Node) + node = new (pool) AST::ObjectLiteral( + sym(2).PropertyNameAndValueList->finish ()); + else + node = new (pool) AST::ObjectLiteral(); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; + +case 87: { + const auto node = new (pool) AST::ObjectLiteral( + sym(2).PropertyNameAndValueList->finish ()); + node->lbraceToken = loc(1); + node->rbraceToken = loc(4); + sym(1).Node = node; +} break; + +case 88: { + const auto node = new (pool) AST::NestedExpression(sym(2).Expression); + node->lparenToken = loc(1); + node->rparenToken = loc(3); + sym(1).Node = node; +} break; + +case 89: { + if (const auto *mem = AST::cast(sym(1).Expression)) { + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Warning, mem->lbracketToken, + QStringLiteral("Ignored annotation"))); + + sym(1).Expression = mem->base; + } + + if (AST::UiQualifiedId *qualifiedId = reparseAsQualifiedId(sym(1).Expression)) { + sym(1).UiQualifiedId = qualifiedId; + } else { + sym(1).UiQualifiedId = nullptr; + + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, loc(1), + QStringLiteral("Expected a qualified name id"))); + + return false; // ### recover + } +} break; + +case 90: { + sym(1).Node = new (pool) AST::ElementList(nullptr, sym(1).Expression); +} break; + +case 91: { + sym(1).Node = new (pool) AST::ElementList(sym(1).Elision->finish(), sym(2).Expression); +} break; + +case 92: { + const auto node = new (pool) AST::ElementList(sym(1).ElementList, nullptr, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 93: { + const auto node = new (pool) AST::ElementList(sym(1).ElementList, sym(3).Elision->finish(), + sym(4).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 94: { + const auto node = new (pool) AST::Elision(); + node->commaToken = loc(1); + sym(1).Node = node; +} break; + +case 95: { + const auto node = new (pool) AST::Elision(sym(1).Elision); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 96: { + const auto node = new (pool) AST::PropertyNameAndValueList( + sym(1).PropertyName, sym(3).Expression); + node->colonToken = loc(2); + sym(1).Node = node; +} break; + +case 97: { + const auto node = new (pool) AST::PropertyNameAndValueList( + sym(1).PropertyNameAndValueList, sym(3).PropertyName, sym(5).Expression); + node->commaToken = loc(2); + node->colonToken = loc(4); + sym(1).Node = node; +} break; + +case 98: { + const auto node = new (pool) AST::IdentifierPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; +case 99: +case 100: { + const auto node = new (pool) AST::IdentifierPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; + +case 101: { + const auto node = new (pool) AST::StringLiteralPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; + +case 102: { + const auto node = new (pool) AST::NumericLiteralPropertyName(sym(1).dval); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; + +case 103: { + const auto node = new (pool) AST::IdentifierPropertyName(stringRef(1)); + node->propertyNameToken = loc(1); + sym(1).Node = node; +} break; + +case 139: { + const auto node = new (pool) AST::ArrayMemberExpression(sym(1).Expression, sym(3).Expression); + node->lbracketToken = loc(2); + node->rbracketToken = loc(4); + sym(1).Node = node; +} break; + +case 140: { + const auto node = new (pool) AST::FieldMemberExpression(sym(1).Expression, stringRef(3)); + node->dotToken = loc(2); + node->identifierToken = loc(3); + sym(1).Node = node; +} break; + +case 141: { + const auto node = new (pool) AST::NewMemberExpression(sym(2).Expression, sym(4).ArgumentList); + node->newToken = loc(1); + node->lparenToken = loc(3); + node->rparenToken = loc(5); + sym(1).Node = node; +} break; + +case 143: { + const auto node = new (pool) AST::NewExpression(sym(2).Expression); + node->newToken = loc(1); + sym(1).Node = node; +} break; + +case 144: { + const auto node = new (pool) AST::CallExpression(sym(1).Expression, sym(3).ArgumentList); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 145: { + const auto node = new (pool) AST::CallExpression(sym(1).Expression, sym(3).ArgumentList); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 146: { + const auto node = new (pool) AST::ArrayMemberExpression(sym(1).Expression, sym(3).Expression); + node->lbracketToken = loc(2); + node->rbracketToken = loc(4); + sym(1).Node = node; +} break; + +case 147: { + const auto node = new (pool) AST::FieldMemberExpression(sym(1).Expression, stringRef(3)); + node->dotToken = loc(2); + node->identifierToken = loc(3); + sym(1).Node = node; +} break; + +case 148: { + sym(1).Node = nullptr; +} break; + +case 149: { + sym(1).Node = sym(1).ArgumentList->finish(); +} break; + +case 150: { + sym(1).Node = new (pool) AST::ArgumentList(sym(1).Expression); +} break; + +case 151: { + const auto node = new (pool) AST::ArgumentList(sym(1).ArgumentList, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 155: { + const auto node = new (pool) AST::PostIncrementExpression(sym(1).Expression); + node->incrementToken = loc(2); + sym(1).Node = node; +} break; + +case 156: { + const auto node = new (pool) AST::PostDecrementExpression(sym(1).Expression); + node->decrementToken = loc(2); + sym(1).Node = node; +} break; + +case 158: { + const auto node = new (pool) AST::DeleteExpression(sym(2).Expression); + node->deleteToken = loc(1); + sym(1).Node = node; +} break; + +case 159: { + const auto node = new (pool) AST::VoidExpression(sym(2).Expression); + node->voidToken = loc(1); + sym(1).Node = node; +} break; + +case 160: { + const auto node = new (pool) AST::TypeOfExpression(sym(2).Expression); + node->typeofToken = loc(1); + sym(1).Node = node; +} break; + +case 161: { + const auto node = new (pool) AST::PreIncrementExpression(sym(2).Expression); + node->incrementToken = loc(1); + sym(1).Node = node; +} break; + +case 162: { + const auto node = new (pool) AST::PreDecrementExpression(sym(2).Expression); + node->decrementToken = loc(1); + sym(1).Node = node; +} break; + +case 163: { + const auto node = new (pool) AST::UnaryPlusExpression(sym(2).Expression); + node->plusToken = loc(1); + sym(1).Node = node; +} break; + +case 164: { + const auto node = new (pool) AST::UnaryMinusExpression(sym(2).Expression); + node->minusToken = loc(1); + sym(1).Node = node; +} break; + +case 165: { + const auto node = new (pool) AST::TildeExpression(sym(2).Expression); + node->tildeToken = loc(1); + sym(1).Node = node; +} break; + +case 166: { + const auto node = new (pool) AST::NotExpression(sym(2).Expression); + node->notToken = loc(1); + sym(1).Node = node; +} break; + +case 168: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Mul, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 169: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Div, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 170: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Mod, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 172: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Add, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 173: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Sub, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 175: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::LShift, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 176: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::RShift, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 177: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::URShift, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 179: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Lt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 180: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Gt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 181: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Le, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 182: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Ge, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 183: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::InstanceOf, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 184: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::In, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 186: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Lt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 187: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Gt, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 188: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Le, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 189: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Ge, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 190: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::InstanceOf, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 192: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Equal, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 193: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::NotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 194: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 195: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictNotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 197: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Equal, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 198: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::NotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 199: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 200: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::StrictNotEqual, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 202: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitAnd, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 204: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitAnd, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 206: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitXor, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 208: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitXor, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 210: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitOr, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 212: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::BitOr, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 214: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::And, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 216: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::And, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 218: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Or, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 220: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + QSOperator::Or, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 222: { + const auto node = new (pool) AST::ConditionalExpression(sym(1).Expression, + sym(3).Expression, sym(5).Expression); + node->questionToken = loc(2); + node->colonToken = loc(4); + sym(1).Node = node; +} break; + +case 224: { + const auto node = new (pool) AST::ConditionalExpression(sym(1).Expression, + sym(3).Expression, sym(5).Expression); + node->questionToken = loc(2); + node->colonToken = loc(4); + sym(1).Node = node; +} break; + +case 226: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + sym(2).ival, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 228: { + const auto node = new (pool) AST::BinaryExpression(sym(1).Expression, + sym(2).ival, sym(3).Expression); + node->operatorToken = loc(2); + sym(1).Node = node; +} break; + +case 229: { + sym(1).ival = QSOperator::Assign; +} break; + +case 230: { + sym(1).ival = QSOperator::InplaceMul; +} break; + +case 231: { + sym(1).ival = QSOperator::InplaceDiv; +} break; + +case 232: { + sym(1).ival = QSOperator::InplaceMod; +} break; + +case 233: { + sym(1).ival = QSOperator::InplaceAdd; +} break; + +case 234: { + sym(1).ival = QSOperator::InplaceSub; +} break; + +case 235: { + sym(1).ival = QSOperator::InplaceLeftShift; +} break; + +case 236: { + sym(1).ival = QSOperator::InplaceRightShift; +} break; + +case 237: { + sym(1).ival = QSOperator::InplaceURightShift; +} break; + +case 238: { + sym(1).ival = QSOperator::InplaceAnd; +} break; + +case 239: { + sym(1).ival = QSOperator::InplaceXor; +} break; + +case 240: { + sym(1).ival = QSOperator::InplaceOr; +} break; + +case 242: { + const auto node = new (pool) AST::Expression(sym(1).Expression, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 243: { + sym(1).Node = nullptr; +} break; + +case 246: { + const auto node = new (pool) AST::Expression(sym(1).Expression, sym(3).Expression); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 247: { + sym(1).Node = nullptr; +} break; + +case 264: { + const auto node = new (pool) AST::Block(sym(2).StatementList); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; + +case 265: { + sym(1).Node = new (pool) AST::StatementList(sym(1).Statement); +} break; + +case 266: { + sym(1).Node = new (pool) AST::StatementList(sym(1).StatementList, sym(2).Statement); +} break; + +case 267: { + sym(1).Node = nullptr; +} break; + +case 268: { + sym(1).Node = sym(1).StatementList->finish (); +} break; + +case 270: { + const auto node = new (pool) AST::VariableStatement( + sym(2).VariableDeclarationList->finish (/*readOnly=*/sym(1).ival == T_CONST)); + node->declarationKindToken = loc(1); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; + +case 271: { + sym(1).ival = T_CONST; +} break; + +case 272: { + sym(1).ival = T_VAR; +} break; + +case 273: { + sym(1).Node = new (pool) AST::VariableDeclarationList(sym(1).VariableDeclaration); +} break; + +case 274: { + const auto node = new (pool) AST::VariableDeclarationList( + sym(1).VariableDeclarationList, sym(3).VariableDeclaration); + node->commaToken = loc(2); + sym(1).Node = node; +} break; + +case 275: { + sym(1).Node = new (pool) AST::VariableDeclarationList(sym(1).VariableDeclaration); +} break; + +case 276: { + sym(1).Node = new (pool) AST::VariableDeclarationList(sym(1).VariableDeclarationList, sym(3).VariableDeclaration); +} break; + +case 277: { + const auto node = new (pool) AST::VariableDeclaration(stringRef(1), sym(2).Expression); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; + +case 278: { + const auto node = new (pool) AST::VariableDeclaration(stringRef(1), sym(2).Expression); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; + +case 279: { + // ### TODO: AST for initializer + sym(1) = sym(2); +} break; + +case 280: { + sym(1).Node = nullptr; +} break; + +case 282: { + // ### TODO: AST for initializer + sym(1) = sym(2); +} break; + +case 283: { + sym(1).Node = nullptr; +} break; + +case 285: { + const auto node = new (pool) AST::EmptyStatement(); + node->semicolonToken = loc(1); + sym(1).Node = node; +} break; + +case 287: { + const auto node = new (pool) AST::ExpressionStatement(sym(1).Expression); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; + +case 288: { + const auto node = new (pool) AST::IfStatement(sym(3).Expression, sym(5).Statement, sym(7).Statement); + node->ifToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + node->elseToken = loc(6); + sym(1).Node = node; +} break; + +case 289: { + const auto node = new (pool) AST::IfStatement(sym(3).Expression, sym(5).Statement); + node->ifToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 291: { + const auto node = new (pool) AST::DoWhileStatement(sym(2).Statement, sym(5).Expression); + node->doToken = loc(1); + node->whileToken = loc(3); + node->lparenToken = loc(4); + node->rparenToken = loc(6); + node->semicolonToken = loc(7); + sym(1).Node = node; +} break; + +case 292: { + const auto node = new (pool) AST::WhileStatement(sym(3).Expression, sym(5).Statement); + node->whileToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 293: { + const auto node = new (pool) AST::ForStatement(sym(3).Expression, + sym(5).Expression, sym(7).Expression, sym(9).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->firstSemicolonToken = loc(4); + node->secondSemicolonToken = loc(6); + node->rparenToken = loc(8); + sym(1).Node = node; +} break; + +case 294: { + const auto node = new (pool) AST::LocalForStatement( + sym(4).VariableDeclarationList->finish (/*readOnly=*/false), sym(6).Expression, + sym(8).Expression, sym(10).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->varToken = loc(3); + node->firstSemicolonToken = loc(5); + node->secondSemicolonToken = loc(7); + node->rparenToken = loc(9); + sym(1).Node = node; +} break; + +case 295: { + const auto node = new (pool) AST::ForEachStatement(sym(3).Expression, + sym(5).Expression, sym(7).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->inToken = loc(4); + node->rparenToken = loc(6); + sym(1).Node = node; +} break; + +case 296: { + const auto node = new (pool) AST::LocalForEachStatement( + sym(4).VariableDeclaration, sym(6).Expression, sym(8).Statement); + node->forToken = loc(1); + node->lparenToken = loc(2); + node->varToken = loc(3); + node->inToken = loc(5); + node->rparenToken = loc(7); + sym(1).Node = node; +} break; + +case 298: { + const auto node = new (pool) AST::ContinueStatement(); + node->continueToken = loc(1); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; + +case 300: { + const auto node = new (pool) AST::ContinueStatement(stringRef(2)); + node->continueToken = loc(1); + node->identifierToken = loc(2); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; + +case 302: { + const auto node = new (pool) AST::BreakStatement(QStringRef()); + node->breakToken = loc(1); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; + +case 304: { + const auto node = new (pool) AST::BreakStatement(stringRef(2)); + node->breakToken = loc(1); + node->identifierToken = loc(2); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; + +case 306: { + const auto node = new (pool) AST::ReturnStatement(sym(2).Expression); + node->returnToken = loc(1); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; + +case 307: { + const auto node = new (pool) AST::WithStatement(sym(3).Expression, sym(5).Statement); + node->withToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 308: { + const auto node = new (pool) AST::SwitchStatement(sym(3).Expression, sym(5).CaseBlock); + node->switchToken = loc(1); + node->lparenToken = loc(2); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 309: { + const auto node = new (pool) AST::CaseBlock(sym(2).CaseClauses); + node->lbraceToken = loc(1); + node->rbraceToken = loc(3); + sym(1).Node = node; +} break; + +case 310: { + const auto node = new (pool) AST::CaseBlock(sym(2).CaseClauses, sym(3).DefaultClause, sym(4).CaseClauses); + node->lbraceToken = loc(1); + node->rbraceToken = loc(5); + sym(1).Node = node; +} break; + +case 311: { + sym(1).Node = new (pool) AST::CaseClauses(sym(1).CaseClause); +} break; + +case 312: { + sym(1).Node = new (pool) AST::CaseClauses(sym(1).CaseClauses, sym(2).CaseClause); +} break; + +case 313: { + sym(1).Node = nullptr; +} break; + +case 314: { + sym(1).Node = sym(1).CaseClauses->finish (); +} break; + +case 315: { + const auto node = new (pool) AST::CaseClause(sym(2).Expression, sym(4).StatementList); + node->caseToken = loc(1); + node->colonToken = loc(3); + sym(1).Node = node; +} break; + +case 316: { + const auto node = new (pool) AST::DefaultClause(sym(3).StatementList); + node->defaultToken = loc(1); + node->colonToken = loc(2); + sym(1).Node = node; +} break; +case 317: +case 318: { + const auto node = new (pool) AST::LabelledStatement(stringRef(1), sym(3).Statement); + node->identifierToken = loc(1); + node->colonToken = loc(2); + sym(1).Node = node; +} break; + +case 319: { + const auto node = new (pool) AST::LabelledStatement(stringRef(1), sym(3).Statement); + node->identifierToken = loc(1); + node->colonToken = loc(2); + sym(1).Node = node; +} break; + +case 321: { + const auto node = new (pool) AST::ThrowStatement(sym(2).Expression); + node->throwToken = loc(1); + node->semicolonToken = loc(3); + sym(1).Node = node; +} break; + +case 322: { + const auto node = new (pool) AST::TryStatement(sym(2).Statement, sym(3).Catch); + node->tryToken = loc(1); + sym(1).Node = node; +} break; + +case 323: { + const auto node = new (pool) AST::TryStatement(sym(2).Statement, sym(3).Finally); + node->tryToken = loc(1); + sym(1).Node = node; +} break; + +case 324: { + const auto node = new (pool) AST::TryStatement(sym(2).Statement, sym(3).Catch, sym(4).Finally); + node->tryToken = loc(1); + sym(1).Node = node; +} break; + +case 325: { + const auto node = new (pool) AST::Catch(stringRef(3), sym(5).Block); + node->catchToken = loc(1); + node->lparenToken = loc(2); + node->identifierToken = loc(3); + node->rparenToken = loc(4); + sym(1).Node = node; +} break; + +case 326: { + const auto node = new (pool) AST::Finally(sym(2).Block); + node->finallyToken = loc(1); + sym(1).Node = node; +} break; + +case 328: { + const auto node = new (pool) AST::DebuggerStatement(); + node->debuggerToken = loc(1); + node->semicolonToken = loc(2); + sym(1).Node = node; +} break; + +case 329: { + const auto node = new (pool) AST::FunctionDeclaration(stringRef(2), sym(4).FormalParameterList, sym(7).FunctionBody); + node->functionToken = loc(1); + node->identifierToken = loc(2); + node->lparenToken = loc(3); + node->rparenToken = loc(5); + node->lbraceToken = loc(6); + node->rbraceToken = loc(8); + sym(1).Node = node; +} break; + +case 330: { + const auto node = new (pool) AST::FunctionExpression(stringRef(2), sym(4).FormalParameterList, sym(7).FunctionBody); + node->functionToken = loc(1); + if (! stringRef(2).isNull()) + node->identifierToken = loc(2); + node->lparenToken = loc(3); + node->rparenToken = loc(5); + node->lbraceToken = loc(6); + node->rbraceToken = loc(8); + sym(1).Node = node; +} break; + +case 331: { + const auto node = new (pool) AST::FormalParameterList(stringRef(1)); + node->identifierToken = loc(1); + sym(1).Node = node; +} break; + +case 332: { + const auto node = new (pool) AST::FormalParameterList(sym(1).FormalParameterList, stringRef(3)); + node->commaToken = loc(2); + node->identifierToken = loc(3); + sym(1).Node = node; +} break; + +case 333: { + sym(1).Node = nullptr; +} break; + +case 334: { + sym(1).Node = sym(1).FormalParameterList->finish (); +} break; + +case 335: { + sym(1).Node = nullptr; +} break; + +case 337: { + sym(1).Node = new (pool) AST::FunctionBody(sym(1).SourceElements->finish ()); +} break; + +case 339: { + sym(1).Node = new (pool) AST::Program(sym(1).SourceElements->finish ()); +} break; + +case 340: { + sym(1).Node = new (pool) AST::SourceElements(sym(1).SourceElement); +} break; + +case 341: { + sym(1).Node = new (pool) AST::SourceElements(sym(1).SourceElements, sym(2).SourceElement); +} break; + +case 342: { + sym(1).Node = new (pool) AST::StatementSourceElement(sym(1).Statement); +} break; + +case 343: { + sym(1).Node = new (pool) AST::FunctionSourceElement(sym(1).FunctionDeclaration); +} break; + +case 344: { + stringRef(1) = QStringRef(); +} break; + +case 346: { + sym(1).Node = nullptr; +} break; + + } // switch + action = nt_action(state_stack[tos], lhs[r] - TERMINAL_COUNT); + } // if + } while (action != 0); + + if (first_token == last_token) { + const int errorState = state_stack[tos]; + + // automatic insertion of `;' + if (yytoken != -1 && t_action(errorState, T_AUTOMATIC_SEMICOLON) && lexer->canInsertAutomaticSemicolon(yytoken)) { + SavedToken &tk = token_buffer[0]; + tk.token = yytoken; + tk.dval = yylval; + tk.spell = yytokenspell; + tk.loc = yylloc; + + yylloc = yyprevlloc; + yylloc.offset += yylloc.length; + yylloc.startColumn += yylloc.length; + yylloc.length = 0; + + //const QString msg = qApp->translate("QmlParser", "Missing `;'"); + //diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Warning, yylloc, msg)); + + first_token = &token_buffer[0]; + last_token = &token_buffer[1]; + + yytoken = T_SEMICOLON; + yylval = 0; + + action = errorState; + + goto _Lcheck_token; + } + + hadErrors = true; + + token_buffer[0].token = yytoken; + token_buffer[0].dval = yylval; + token_buffer[0].spell = yytokenspell; + token_buffer[0].loc = yylloc; + + token_buffer[1].token = yytoken = lexer->lex(); + token_buffer[1].dval = yylval = lexer->tokenValue(); + token_buffer[1].spell = yytokenspell = lexer->tokenSpell(); + token_buffer[1].loc = yylloc = location(lexer); + + if (t_action(errorState, yytoken)) { + QString msg; + int token = token_buffer[0].token; + if (token < 0 || token >= TERMINAL_COUNT) + msg = qApp->translate("QmlParser", "Syntax error"); + else + msg = qApp->translate("QmlParser", "Unexpected token `%1'").arg(QLatin1String(spell[token])); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + + action = errorState; + goto _Lcheck_token; + } + + static int tokens[] = { + T_PLUS, + T_EQ, + + T_COMMA, + T_COLON, + T_SEMICOLON, + + T_RPAREN, T_RBRACKET, T_RBRACE, + + T_NUMERIC_LITERAL, + T_IDENTIFIER, + + T_LPAREN, T_LBRACKET, T_LBRACE, + + EOF_SYMBOL + }; + + for (int *tk = tokens; *tk != EOF_SYMBOL; ++tk) { + int a = t_action(errorState, *tk); + if (a > 0 && t_action(a, yytoken)) { + const QString msg = qApp->translate("QmlParser", "Expected token `%1'").arg(QLatin1String(spell[*tk])); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + + yytoken = *tk; + yylval = 0; + yylloc = token_buffer[0].loc; + yylloc.length = 0; + + first_token = &token_buffer[0]; + last_token = &token_buffer[2]; + + action = errorState; + goto _Lcheck_token; + } + } + + for (int tk = 1; tk < TERMINAL_COUNT; ++tk) { + if (tk == T_AUTOMATIC_SEMICOLON || tk == T_FEED_UI_PROGRAM || + tk == T_FEED_JS_STATEMENT || tk == T_FEED_JS_EXPRESSION || + tk == T_FEED_JS_SOURCE_ELEMENT) + continue; + + int a = t_action(errorState, tk); + if (a > 0 && t_action(a, yytoken)) { + const QString msg = qApp->translate("QmlParser", "Expected token `%1'").arg(QLatin1String(spell[tk])); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + + yytoken = tk; + yylval = 0; + yylloc = token_buffer[0].loc; + yylloc.length = 0; + + action = errorState; + goto _Lcheck_token; + } + } + + const QString msg = qApp->translate("QmlParser", "Syntax error"); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + } + + return false; +} + +} // namespace QbsQmlJS diff --git a/src/lib/corelib/parser/qmljsparser_p.h b/src/lib/corelib/parser/qmljsparser_p.h new file mode 100644 index 00000000..c761bb25 --- /dev/null +++ b/src/lib/corelib/parser/qmljsparser_p.h @@ -0,0 +1,241 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +// +// This file is automatically generated from qmljs.g. +// Changes will be lost. +// + +#ifndef QMLJSPARSER_P_H +#define QMLJSPARSER_P_H + +#include "qmljsglobal_p.h" +#include "qmljsgrammar_p.h" +#include "qmljsast_p.h" +#include "qmljsengine_p.h" +#include + +#include +#include + +namespace QbsQmlJS { + +class Engine; + +class QBS_AUTOTEST_EXPORT Parser: protected QmlJSGrammar +{ +public: + union Value { + int ival; + double dval; + AST::ArgumentList *ArgumentList; + AST::CaseBlock *CaseBlock; + AST::CaseClause *CaseClause; + AST::CaseClauses *CaseClauses; + AST::Catch *Catch; + AST::DefaultClause *DefaultClause; + AST::ElementList *ElementList; + AST::Elision *Elision; + AST::ExpressionNode *Expression; + AST::Finally *Finally; + AST::FormalParameterList *FormalParameterList; + AST::FunctionBody *FunctionBody; + AST::FunctionDeclaration *FunctionDeclaration; + AST::Node *Node; + AST::PropertyName *PropertyName; + AST::PropertyNameAndValueList *PropertyNameAndValueList; + AST::SourceElement *SourceElement; + AST::SourceElements *SourceElements; + AST::Statement *Statement; + AST::StatementList *StatementList; + AST::Block *Block; + AST::VariableDeclaration *VariableDeclaration; + AST::VariableDeclarationList *VariableDeclarationList; + + AST::UiProgram *UiProgram; + AST::UiImportList *UiImportList; + AST::UiImport *UiImport; + AST::UiParameterList *UiParameterList; + AST::UiPublicMember *UiPublicMember; + AST::UiObjectDefinition *UiObjectDefinition; + AST::UiObjectInitializer *UiObjectInitializer; + AST::UiObjectBinding *UiObjectBinding; + AST::UiScriptBinding *UiScriptBinding; + AST::UiArrayBinding *UiArrayBinding; + AST::UiObjectMember *UiObjectMember; + AST::UiObjectMemberList *UiObjectMemberList; + AST::UiArrayMemberList *UiArrayMemberList; + AST::UiQualifiedId *UiQualifiedId; + }; + +public: + Parser(Engine *engine); + ~Parser(); + + // parse a UI program + bool parse() { return parse(T_FEED_UI_PROGRAM); } + bool parseStatement() { return parse(T_FEED_JS_STATEMENT); } + bool parseExpression() { return parse(T_FEED_JS_EXPRESSION); } + bool parseSourceElement() { return parse(T_FEED_JS_SOURCE_ELEMENT); } + bool parseUiObjectMember() { return parse(T_FEED_UI_OBJECT_MEMBER); } + bool parseProgram() { return parse(T_FEED_JS_PROGRAM); } + + AST::UiProgram *ast() const + { return AST::cast(program); } + + AST::Statement *statement() const + { + if (! program) + return nullptr; + + return program->statementCast(); + } + + AST::ExpressionNode *expression() const + { + if (! program) + return nullptr; + + return program->expressionCast(); + } + + AST::UiObjectMember *uiObjectMember() const + { + if (! program) + return nullptr; + + return program->uiObjectMemberCast(); + } + + AST::Node *rootNode() const + { return program; } + + QList diagnosticMessages() const + { return diagnostic_messages; } + + inline DiagnosticMessage diagnosticMessage() const + { + foreach (const DiagnosticMessage &d, diagnostic_messages) { + if (d.kind != DiagnosticMessage::Warning) + return d; + } + + return DiagnosticMessage(); + } + + inline QString errorMessage() const + { return diagnosticMessage().message; } + + inline int errorLineNumber() const + { return diagnosticMessage().loc.startLine; } + + inline int errorColumnNumber() const + { return diagnosticMessage().loc.startColumn; } + +protected: + bool parse(int startToken); + + void reallocateStack(); + + inline Value &sym(int index) + { return sym_stack [tos + index - 1]; } + + inline QStringRef &stringRef(int index) + { return string_stack [tos + index - 1]; } + + inline AST::SourceLocation &loc(int index) + { return location_stack [tos + index - 1]; } + + AST::UiQualifiedId *reparseAsQualifiedId(AST::ExpressionNode *expr); + +protected: + Engine *driver; + MemoryPool *pool; + int tos; + int stack_size; + Value *sym_stack; + int *state_stack; + AST::SourceLocation *location_stack; + QStringRef *string_stack; + + AST::Node *program; + + // error recovery + enum { TOKEN_BUFFER_SIZE = 3 }; + + struct SavedToken { + int token = 0; + double dval = 0.0; + AST::SourceLocation loc; + QStringRef spell; + }; + + double yylval = 0.0; + QStringRef yytokenspell; + AST::SourceLocation yylloc; + AST::SourceLocation yyprevlloc; + + SavedToken token_buffer[TOKEN_BUFFER_SIZE]; + SavedToken *first_token; + SavedToken *last_token; + + QList diagnostic_messages; +}; + +} // end of namespace QbsQmlJS + + + +#define J_SCRIPT_REGEXPLITERAL_RULE1 79 + +#define J_SCRIPT_REGEXPLITERAL_RULE2 80 + +#endif // QMLJSPARSER_P_H diff --git a/src/lib/corelib/qbs.h b/src/lib/corelib/qbs.h new file mode 100644 index 00000000..867051c4 --- /dev/null +++ b/src/lib/corelib/qbs.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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 QBS_H +#define QBS_H + +#include "api/jobs.h" +#include "api/languageinfo.h" +#include "api/project.h" +#include "api/projectdata.h" +#include "api/rulecommand.h" +#include "api/runenvironment.h" +#include "logging/ilogsink.h" +#include "tools/architectures.h" +#include "tools/buildoptions.h" +#include "tools/cleanoptions.h" +#include "tools/codelocation.h" +#include "tools/commandechomode.h" +#include "tools/error.h" +#include "tools/generateoptions.h" +#include "tools/installoptions.h" +#include "tools/preferences.h" +#include "tools/processresult.h" +#include "tools/profile.h" +#include "tools/projectgeneratormanager.h" +#include "tools/settings.h" +#include "tools/settingsmodel.h" +#include "tools/settingsrepresentation.h" +#include "tools/setupprojectparameters.h" +#include "tools/toolchains.h" +#include "tools/version.h" + +#endif // QBS_H diff --git a/src/lib/corelib/tools/applecodesignutils.cpp b/src/lib/corelib/tools/applecodesignutils.cpp new file mode 100644 index 00000000..feae266b --- /dev/null +++ b/src/lib/corelib/tools/applecodesignutils.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** 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 "applecodesignutils.h" +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +namespace qbs { +namespace Internal { + +QByteArray smimeMessageContent(const QByteArray &data) +{ + QCFType decoder = NULL; + if (CMSDecoderCreate(&decoder) != noErr) + return {}; + + if (CMSDecoderUpdateMessage(decoder, data.constData(), data.size()) != noErr) + return {}; + + if (CMSDecoderFinalizeMessage(decoder) != noErr) + return {}; + + QCFType content = NULL; + if (CMSDecoderCopyContent(decoder, &content) != noErr) + return {}; + + return QByteArray::fromCFData(content); +} + +QVariantMap certificateInfo(const QByteArray &data) +{ + const QSslCertificate cert(data, QSsl::Der); + + // Also potentially useful, but these are for signing pkgs which aren't used here + // 1.2.840.113635.100.4.9 - 3rd Party Mac Developer Installer: + // 1.2.840.113635.100.4.13 - Developer ID Installer: + const auto extensions = cert.extensions(); + for (const auto &extension : extensions) { + if (extension.name() == QStringLiteral("extendedKeyUsage")) { + if (!extension.value().toStringList().contains(QStringLiteral("Code Signing"))) + return {}; + } + } + + const auto subjectInfo = [](const QSslCertificate &cert) { + QVariantMap map; + const auto attributes = cert.subjectInfoAttributes(); + for (const auto &attr : attributes) + map.insert(QString::fromUtf8(attr), cert.subjectInfo(attr).front()); + return map; + }; + + return { + {QStringLiteral("SHA1"), cert.digest(QCryptographicHash::Sha1).toHex().toUpper()}, + {QStringLiteral("subjectInfo"), subjectInfo(cert)}, + {QStringLiteral("validBefore"), cert.effectiveDate()}, + {QStringLiteral("validAfter"), cert.expiryDate()} + }; +} + +QVariantMap identitiesProperties() +{ + // Apple documentation states that the Sec* family of functions are not thread-safe on macOS + // https://developer.apple.com/library/mac/documentation/Security/Reference/certifkeytrustservices/ + static std::mutex securityMutex; + std::lock_guard locker(securityMutex); + Q_UNUSED(locker); + + const void *keys[] = {kSecClass, kSecMatchLimit, kSecAttrCanSign}; + const void *values[] = {kSecClassIdentity, kSecMatchLimitAll, kCFBooleanTrue}; + QCFType query = CFDictionaryCreate(kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + QCFType result = NULL; + if (SecItemCopyMatching(query, &result) != errSecSuccess) + return {}; + + QVariantMap items; + const auto tryAppend = [&](SecIdentityRef identity) { + if (!identity) + return; + + QCFType certificate = NULL; + if (SecIdentityCopyCertificate(identity, &certificate) != errSecSuccess) + return; + + QCFType certificateData = SecCertificateCopyData(certificate); + if (!certificateData) + return; + + auto props = certificateInfo(QByteArray::fromRawCFData(certificateData)); + if (!props.empty()) + items.insert(props[QStringLiteral("SHA1")].toString(), props); + }; + + if (CFGetTypeID(result) == SecIdentityGetTypeID()) { + tryAppend((SecIdentityRef)result.operator const void *()); + } else if (CFGetTypeID(result) == CFArrayGetTypeID()) { + for (CFIndex i = 0; i < CFArrayGetCount((CFArrayRef)result.operator const void *()); ++i) + tryAppend((SecIdentityRef)CFArrayGetValueAtIndex(result.as(), i)); + } + + return items; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/applecodesignutils.h b/src/lib/corelib/tools/applecodesignutils.h new file mode 100644 index 00000000..4ee8e165 --- /dev/null +++ b/src/lib/corelib/tools/applecodesignutils.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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 QBS_APPLECODESIGNUTILS_H +#define QBS_APPLECODESIGNUTILS_H + +#include "qbs_export.h" +#include + +namespace qbs { +namespace Internal { + +QByteArray smimeMessageContent(const QByteArray &data); +QVariantMap certificateInfo(const QByteArray &data); +QVariantMap identitiesProperties(); + +} // namespace Internal +} // namespace qbs + +#endif // QBS_APPLECODESIGNUTILS_H diff --git a/src/lib/corelib/tools/architectures.cpp b/src/lib/corelib/tools/architectures.cpp new file mode 100644 index 00000000..cf9fec27 --- /dev/null +++ b/src/lib/corelib/tools/architectures.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Petroules Corporation. +** 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 "architectures.h" + +#include "stringconstants.h" + +#include +#include + +namespace qbs { + +using namespace Internal; + +QString canonicalTargetArchitecture(const QString &architecture, + const QString &endianness, + const QString &vendor, + const QString &system, + const QString &abi) +{ + const QString arch = canonicalArchitecture(architecture); + const bool isApple = (vendor == QStringLiteral("apple") + || system == QStringLiteral("darwin") + || system == QStringLiteral("macosx") + || system == QStringLiteral("ios") + || system == QStringLiteral("tvos") + || system == QStringLiteral("watchos") + || abi == QStringLiteral("macho")); + const bool isQnx = (system == QStringLiteral("nto") + || abi.startsWith(QStringLiteral("qnx"))); + + if (arch == QStringLiteral("armv7a")) { + if (isApple) + return StringConstants::armv7Arch(); + if (isQnx) + return StringConstants::armArch(); + } + + if (arch == StringConstants::arm64Arch() && isQnx) + return StringConstants::aarch64Arch(); + + if (arch == StringConstants::x86Arch()) { + if (isQnx) + return StringConstants::i586Arch(); + return StringConstants::i386Arch(); + } + + if (arch == StringConstants::mipsArch() || arch == StringConstants::mips64Arch()) { + if (endianness == QStringLiteral("big")) + return arch + QStringLiteral("eb"); + if (endianness == QStringLiteral("little")) + return arch + QStringLiteral("el"); + } + + if (arch == StringConstants::ppcArch()) + return StringConstants::powerPcArch(); + + if (arch == StringConstants::ppc64Arch() && endianness == QStringLiteral("little")) + return arch + QStringLiteral("le"); + + return arch; +} + +QString canonicalArchitecture(const QString &architecture) +{ + QMap archMap; + archMap.insert(StringConstants::x86Arch(), QStringList() + << StringConstants::i386Arch() + << QStringLiteral("i486") + << StringConstants::i586Arch() + << QStringLiteral("i686") + << QStringLiteral("ia32") + << QStringLiteral("ia-32") + << QStringLiteral("x86_32") + << QStringLiteral("x86-32") + << QStringLiteral("intel32") + << QStringLiteral("mingw32")); + + archMap.insert(StringConstants::x86_64Arch(), QStringList() + << QStringLiteral("x86-64") + << QStringLiteral("x64") + << StringConstants::amd64Arch() + << QStringLiteral("ia32e") + << QStringLiteral("em64t") + << QStringLiteral("intel64") + << QStringLiteral("mingw64")); + + archMap.insert(StringConstants::arm64Arch(), QStringList() + << StringConstants::aarch64Arch()); + + archMap.insert(QStringLiteral("ia64"), QStringList() + << QStringLiteral("ia-64") + << QStringLiteral("itanium")); + + archMap.insert(StringConstants::ppcArch(), QStringList() + << StringConstants::powerPcArch()); + + archMap.insert(StringConstants::ppc64Arch(), QStringList() + << QStringLiteral("ppc64le") + << QStringLiteral("powerpc64") + << QStringLiteral("powerpc64le")); + + archMap.insert(StringConstants::mipsArch(), QStringList() + << QStringLiteral("mipseb") + << QStringLiteral("mipsel")); + + archMap.insert(StringConstants::mips64Arch(), QStringList() + << QStringLiteral("mips64eb") + << QStringLiteral("mips64el")); + + QMapIterator i(archMap); + while (i.hasNext()) { + i.next(); + if (i.value().contains(architecture.toLower())) + return i.key(); + } + + return architecture; + +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/architectures.h b/src/lib/corelib/tools/architectures.h new file mode 100644 index 00000000..03852069 --- /dev/null +++ b/src/lib/corelib/tools/architectures.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** 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 QBS_ARCHITECTURES_H +#define QBS_ARCHITECTURES_H + +#include "qbs_export.h" + +#include + +namespace qbs { + +QBS_EXPORT QString canonicalTargetArchitecture(const QString &architecture, + const QString &endianness, + const QString &vendor, + const QString &system, + const QString &abi); + +QBS_EXPORT QString canonicalArchitecture(const QString &architecture); + +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/tools/buildgraphlocker.cpp b/src/lib/corelib/tools/buildgraphlocker.cpp new file mode 100644 index 00000000..28a58e3f --- /dev/null +++ b/src/lib/corelib/tools/buildgraphlocker.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** 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 "buildgraphlocker.h" + +#include "error.h" +#include "hostosinfo.h" +#include "processutils.h" +#include "progressobserver.h" +#include "stringconstants.h" + +#include + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +DirectoryManager::DirectoryManager(QString dir, Logger logger) + : m_dir(std::move(dir)), m_logger(std::move(logger)) +{ + rememberCreatedDirectories(); +} + +DirectoryManager::~DirectoryManager() +{ + removeEmptyCreatedDirectories(); +} + +void DirectoryManager::rememberCreatedDirectories() +{ + QString parentDir = m_dir; + while (!QFileInfo::exists(parentDir)) { + m_createdParentDirs.push(parentDir); + parentDir = QDir::cleanPath(parentDir + StringConstants::slashDotDot()); + } +} + +void DirectoryManager::removeEmptyCreatedDirectories() +{ + QDir root = QDir::root(); + while (!m_createdParentDirs.empty()) { + const QString parentDir = m_createdParentDirs.front(); + m_createdParentDirs.pop(); + QDirIterator it(parentDir, QDir::AllEntries | QDir::NoDotAndDotDot | QDir::System, + QDirIterator::Subdirectories); + if (it.hasNext()) + break; + if (!root.rmdir(parentDir) && m_logger.logSink()) { + m_logger.printWarning(ErrorInfo(Tr::tr("Failed to remove empty directory '%1'.") + .arg(parentDir))); + } + } +} + +static void tryCreateBuildDirectory(const QString &buildDir, const QString &buildGraphFilePath) +{ + if (!QDir::root().mkpath(buildDir)) { + throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1': Failed to create directory.") + .arg(buildGraphFilePath)); + } +} + +static bool appNamesAreEqual(const QString &app1, const QString &app2) +{ + return QString::compare(app1, app2, HostOsInfo::fileNameCaseSensitivity()) == 0; +} + +BuildGraphLocker::BuildGraphLocker(const QString &buildGraphFilePath, const Logger &logger, + bool waitIndefinitely, ProgressObserver *observer) + : m_lockFile(buildGraphFilePath + QStringLiteral(".lock")) + , m_logger(logger) + , m_dirManager(QFileInfo(buildGraphFilePath).absolutePath(), logger) +{ + if (waitIndefinitely) + m_logger.qbsDebug() << "Waiting to acquire lock file..."; + m_lockFile.setStaleLockTime(0); + int attemptsToGetInfo = 0; + do { + if (observer && observer->canceled()) + break; + tryCreateBuildDirectory(m_dirManager.dir(), buildGraphFilePath); + if (m_lockFile.tryLock(250)) + return; + switch (m_lockFile.error()) { + case QLockFile::LockFailedError: { + if (waitIndefinitely) + continue; + qint64 pid; + QString hostName; + QString appName; + if (m_lockFile.getLockInfo(&pid, &hostName, &appName)) { + if (appNamesAreEqual(appName, processNameByPid(pid))) { + throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1': " + "Already locked by '%2' (PID %3).") + .arg(buildGraphFilePath, appName).arg(pid)); + } + + // The process id was reused by some other process. + m_logger.qbsInfo() << Tr::tr("Removing stale lock file."); + m_lockFile.removeStaleLockFile(); + } + break; + } + case QLockFile::PermissionError: + throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1': Permission denied.") + .arg(buildGraphFilePath)); + case QLockFile::UnknownError: + case QLockFile::NoError: + throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1' (reason unknown).") + .arg(buildGraphFilePath)); + } + } while (++attemptsToGetInfo < 10 || waitIndefinitely); + + // This very unlikely case arises if tryLock() repeatedly returns LockFailedError + // with the subsequent getLockInfo() failing as well. + throw ErrorInfo(Tr::tr("Cannot lock build graph file '%1' (reason unknown).") + .arg(buildGraphFilePath)); +} + +BuildGraphLocker::~BuildGraphLocker() +{ + m_lockFile.unlock(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/buildgraphlocker.h b/src/lib/corelib/tools/buildgraphlocker.h new file mode 100644 index 00000000..f85fc076 --- /dev/null +++ b/src/lib/corelib/tools/buildgraphlocker.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** 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 QBS_BUILDGRAPHLOCKER_H +#define QBS_BUILDGRAPHLOCKER_H + +#include + +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class ProgressObserver; + +class DirectoryManager +{ +public: + DirectoryManager(QString dir, Logger logger); + ~DirectoryManager(); + QString dir() const { return m_dir; } + +private: + void rememberCreatedDirectories(); + void removeEmptyCreatedDirectories(); + + std::queue m_createdParentDirs; + const QString m_dir; + Logger m_logger; +}; + +class BuildGraphLocker +{ +public: + explicit BuildGraphLocker(const QString &buildGraphFilePath, const Logger &logger, + bool waitIndefinitely, ProgressObserver *observer); + ~BuildGraphLocker(); + +private: + QLockFile m_lockFile; + Logger m_logger; + DirectoryManager m_dirManager; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/tools/buildoptions.cpp b/src/lib/corelib/tools/buildoptions.cpp new file mode 100644 index 00000000..e4e9ba17 --- /dev/null +++ b/src/lib/corelib/tools/buildoptions.cpp @@ -0,0 +1,463 @@ +/**************************************************************************** +** +** 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 "buildoptions.h" + +#include "jsonhelper.h" + +#include +#include +#include + +namespace qbs { +namespace Internal { + +class BuildOptionsPrivate : public QSharedData +{ +public: + BuildOptionsPrivate() + : maxJobCount(0), dryRun(false), keepGoing(false), forceTimestampCheck(false), + forceOutputCheck(false), + logElapsedTime(false), echoMode(defaultCommandEchoMode()), install(true), + removeExistingInstallation(false), onlyExecuteRules(false) + { + } + + QStringList changedFiles; + QStringList filesToConsider; + QStringList activeFileTags; + JobLimits jobLimits; + QString settingsDir; + int maxJobCount; + bool dryRun; + bool keepGoing; + bool forceTimestampCheck; + bool forceOutputCheck; + bool logElapsedTime; + CommandEchoMode echoMode; + bool install; + bool removeExistingInstallation; + bool onlyExecuteRules; + bool jobLimitsFromProjectTakePrecedence = false; +}; + +} // namespace Internal + +/*! + * \class BuildOptions + * \brief The \c BuildOptions class comprises parameters that influence the behavior of + * build and clean operations. + */ + +/*! + * \brief Creates a \c BuildOptions object and initializes its members to sensible default values. + */ +BuildOptions::BuildOptions() : d(new Internal::BuildOptionsPrivate) +{ +} + +BuildOptions::BuildOptions(const BuildOptions &other) = default; + +BuildOptions &BuildOptions::operator=(const BuildOptions &other) = default; + +BuildOptions::~BuildOptions() = default; + +/*! + * \brief If non-empty, qbs pretends that only these files have changed. + * By default, this list is empty. + */ +QStringList BuildOptions::changedFiles() const +{ + return d->changedFiles; +} + +/*! + * \brief If the given list is empty, qbs will pretend only the listed files are changed. + * \note The list elements must be absolute file paths. + */ +void BuildOptions::setChangedFiles(const QStringList &changedFiles) +{ + d->changedFiles = changedFiles; +} + +/*! + * \brief The list of files to consider. + * \sa setFilesToConsider. + * By default, this list is empty. + */ +QStringList BuildOptions::filesToConsider() const +{ + return d->filesToConsider; +} + +/*! + * \brief If the given list is non-empty, qbs will run only commands whose rule has at least one + * of these files as an input. + * \note The list elements must be absolute file paths. + */ +void BuildOptions::setFilesToConsider(const QStringList &files) +{ + d->filesToConsider = files; +} + +/*! + * \brief The list of active file tags. + * \sa setActiveFileTags + */ +QStringList BuildOptions::activeFileTags() const +{ + return d->activeFileTags; +} + +/*! + * \brief Set the list of active file tags. + * If this list is non-empty, then every transformer with non-matching output file tags is skipped. + * E.g. call \c setFilesToConsider() with "foo.cpp" and \c setActiveFileTags() with "obj" to + * run the compiler on foo.cpp without further processing like linking. + * \sa activeFileTags + */ +void BuildOptions::setActiveFileTags(const QStringList &fileTags) +{ + d->activeFileTags = fileTags; +} + +/*! + * \brief Returns the default value for \c maxJobCount. + * This value will be used when \c maxJobCount has not been set explicitly. + */ +int BuildOptions::defaultMaxJobCount() +{ + return QThread::idealThreadCount(); +} + +/*! + * \brief Returns the maximum number of build commands to run concurrently. + * If the value is not valid (i.e. <= 0), a sensible one will be derived at build time + * from the number of available processor cores at build time. + * The default is 0. + * \sa BuildOptions::defaultMaxJobCount + */ +int BuildOptions::maxJobCount() const +{ + return d->maxJobCount; +} + +/*! + * \brief Controls how many build commands can be run in parallel. + * A value <= 0 leaves the decision to qbs. + */ +void BuildOptions::setMaxJobCount(int jobCount) +{ + d->maxJobCount = jobCount; +} + +/*! + * \brief The base directory for qbs settings. + * This value is used to locate profiles and preferences. + */ +QString BuildOptions::settingsDirectory() const +{ + return d->settingsDir; +} + +/*! + * \brief Sets the base directory for qbs settings. + * \param settingsBaseDir Will be used to locate profiles and preferences. + */ +void BuildOptions::setSettingsDirectory(const QString &settingsBaseDir) +{ + d->settingsDir = settingsBaseDir; +} + +JobLimits BuildOptions::jobLimits() const +{ + return d->jobLimits; +} + +void BuildOptions::setJobLimits(const JobLimits &jobLimits) +{ + d->jobLimits = jobLimits; +} + +bool BuildOptions::projectJobLimitsTakePrecedence() const +{ + return d->jobLimitsFromProjectTakePrecedence; +} + +void BuildOptions::setProjectJobLimitsTakePrecedence(bool toggle) +{ + d->jobLimitsFromProjectTakePrecedence = toggle; +} + +/*! + * \brief Returns true iff qbs will not actually execute any commands, but just show what + * would happen. + * The default is false. + */ +bool BuildOptions::dryRun() const +{ + return d->dryRun; +} + +/*! + * \brief Controls whether qbs will actually build something. + * If the argument is true, qbs will just emit information about what it would do. Otherwise, + * the build is actually done. + * \note After you build with this setting enabled, the next call to \c build() on the same + * \c Project object will do nothing, since the internal state needs to be updated the same way + * as if an actual build had happened. You'll need to create a new \c Project object to do + * a real build afterwards. + */ +void BuildOptions::setDryRun(bool dryRun) +{ + d->dryRun = dryRun; +} + +/*! + * \brief Returns true iff a build will continue after an error. + * E.g. a failed compile command will result in a warning message being printed, instead of + * stopping the build process right away. However, there might still be fatal errors after which the + * build process cannot continue. + * The default is \c false. + */ +bool BuildOptions::keepGoing() const +{ + return d->keepGoing; +} + +/*! + * \brief Controls whether a qbs will try to continue building after an error has occurred. + */ +void BuildOptions::setKeepGoing(bool keepGoing) +{ + d->keepGoing = keepGoing; +} + +/*! + * \brief Returns true if qbs is to use physical timestamps instead of the timestamps stored in the + * build graph. + * The default is \c false. + */ +bool BuildOptions::forceTimestampCheck() const +{ + return d->forceTimestampCheck; +} + +/*! + * \brief Controls whether qbs should use physical timestamps for up-to-date checks. + */ +void BuildOptions::setForceTimestampCheck(bool enabled) +{ + d->forceTimestampCheck = enabled; +} + +/*! + * \brief Returns true if qbs will test whether rules actually create their + * declared output artifacts. + * The default is \c false. + */ +bool BuildOptions::forceOutputCheck() const +{ + return d->forceOutputCheck; +} + +/*! + * \brief Controls whether qbs should test whether rules actually create their + * declared output artifacts. Enabling this may introduce some small I/O overhead during the build. + */ +void BuildOptions::setForceOutputCheck(bool enabled) +{ + d->forceOutputCheck = enabled; +} + +/*! + * \brief Returns true iff the time the operation takes will be logged. + * The default is \c false. + */ +bool BuildOptions::logElapsedTime() const +{ + return d->logElapsedTime; +} + +/*! + * \brief Controls whether the build time will be measured and logged. + */ +void BuildOptions::setLogElapsedTime(bool log) +{ + d->logElapsedTime = log; +} + +/*! + * \brief The kind of output that is displayed when executing commands. + */ +CommandEchoMode BuildOptions::echoMode() const +{ + return d->echoMode; +} + +/*! + * \brief Controls the kind of output that is displayed when executing commands. + */ +void BuildOptions::setEchoMode(CommandEchoMode echoMode) +{ + d->echoMode = echoMode; +} + +/*! + * \brief Returns true iff installation should happen as part of the build. + * The default is \c true. + */ +bool BuildOptions::install() const +{ + return d->install; +} + +/*! + * \brief Controls whether to install artifacts as part of the build process. + */ +void BuildOptions::setInstall(bool install) +{ + d->install = install; +} + +/*! + * \brief Returns true iff an existing installation will be removed prior to building. + * The default is false. + */ +bool BuildOptions::removeExistingInstallation() const +{ + return d->removeExistingInstallation; +} + +/*! + * Controls whether to remove an existing installation before installing. + * \note qbs may do some safety checks and refuse to remove certain directories such as + * a user's home directory. You should still be careful with this option, since it + * deletes recursively. + */ +void BuildOptions::setRemoveExistingInstallation(bool removeExisting) +{ + d->removeExistingInstallation = removeExisting; +} + +/*! + * \brief Returns true iff instead of a full build, only the rules of the project will be run. + * The default is false. + */ +bool BuildOptions::executeRulesOnly() const +{ + return d->onlyExecuteRules; +} + +/*! + * If \a onlyRules is \c true, then no artifacts are built, but only rules are being executed. + * \note If the project contains highly dynamic rules that depend on output artifacts of child + * rules being already present, then the associated build job may fail even though + * the project is perfectly valid. Callers need to take this into consideration. + */ +void BuildOptions::setExecuteRulesOnly(bool onlyRules) +{ + d->onlyExecuteRules = onlyRules; +} + + +bool operator==(const BuildOptions &bo1, const BuildOptions &bo2) +{ + return bo1.changedFiles() == bo2.changedFiles() + && bo1.dryRun() == bo2.dryRun() + && bo1.keepGoing() == bo2.keepGoing() + && bo1.logElapsedTime() == bo2.logElapsedTime() + && bo1.echoMode() == bo2.echoMode() + && bo1.maxJobCount() == bo2.maxJobCount() + && bo1.install() == bo2.install() + && bo1.removeExistingInstallation() == bo2.removeExistingInstallation(); +} + +namespace Internal { +template<> JobLimits fromJson(const QJsonValue &limitsData) +{ + JobLimits limits; + const QJsonArray &limitsArray = limitsData.toArray(); + for (const QJsonValue &v : limitsArray) { + const QJsonObject limitData = v.toObject(); + QString pool; + int limit = 0; + setValueFromJson(pool, limitData, "pool"); + setValueFromJson(limit, limitData, "limit"); + if (!pool.isEmpty() && limit > 0) + limits.setJobLimit(pool, limit); + } + return limits; +} + +template<> CommandEchoMode fromJson(const QJsonValue &modeData) +{ + const QString modeString = modeData.toString(); + if (modeString == QLatin1String("silent")) + return CommandEchoModeSilent; + if (modeString == QLatin1String("command-line")) + return CommandEchoModeCommandLine; + if (modeString == QLatin1String("command-line-with-environment")) + return CommandEchoModeCommandLineWithEnvironment; + return CommandEchoModeSummary; +} +} // namespace Internal + +qbs::BuildOptions qbs::BuildOptions::fromJson(const QJsonObject &data) +{ + using namespace Internal; + BuildOptions opt; + setValueFromJson(opt.d->changedFiles, data, "changed-files"); + setValueFromJson(opt.d->filesToConsider, data, "files-to-consider"); + setValueFromJson(opt.d->activeFileTags, data, "active-file-tags"); + setValueFromJson(opt.d->jobLimits, data, "job-limits"); + setValueFromJson(opt.d->maxJobCount, data, "max-job-count"); + setValueFromJson(opt.d->dryRun, data, "dry-run"); + setValueFromJson(opt.d->keepGoing, data, "keep-going"); + setValueFromJson(opt.d->forceTimestampCheck, data, "check-timestamps"); + setValueFromJson(opt.d->forceOutputCheck, data, "check-outputs"); + setValueFromJson(opt.d->logElapsedTime, data, "log-time"); + setValueFromJson(opt.d->echoMode, data, "command-echo-mode"); + setValueFromJson(opt.d->install, data, "install"); + setValueFromJson(opt.d->removeExistingInstallation, data, "clean-install-root"); + setValueFromJson(opt.d->onlyExecuteRules, data, "only-execute-rules"); + setValueFromJson(opt.d->jobLimitsFromProjectTakePrecedence, data, "enforce-project-job-limits"); + return opt; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/buildoptions.h b/src/lib/corelib/tools/buildoptions.h new file mode 100644 index 00000000..bd0fb22c --- /dev/null +++ b/src/lib/corelib/tools/buildoptions.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** 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 QBS_BUILDOPTIONS_H +#define QBS_BUILDOPTIONS_H + +#include "qbs_export.h" + +#include "commandechomode.h" +#include "joblimits.h" + +#include + +QT_BEGIN_NAMESPACE +class QJsonObject; +class QStringList; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { class BuildOptionsPrivate; } + +class QBS_EXPORT BuildOptions +{ +public: + BuildOptions(); + BuildOptions(const BuildOptions &other); + BuildOptions &operator=(const BuildOptions &other); + ~BuildOptions(); + + static BuildOptions fromJson(const QJsonObject &data); + + QStringList filesToConsider() const; + void setFilesToConsider(const QStringList &files); + + QStringList changedFiles() const; + void setChangedFiles(const QStringList &changedFiles); + + QStringList activeFileTags() const; + void setActiveFileTags(const QStringList &fileTags); + + static int defaultMaxJobCount(); + int maxJobCount() const; + void setMaxJobCount(int jobCount); + + QString settingsDirectory() const; + void setSettingsDirectory(const QString &settingsBaseDir); + + JobLimits jobLimits() const; + void setJobLimits(const JobLimits &jobLimits); + + bool projectJobLimitsTakePrecedence() const; + void setProjectJobLimitsTakePrecedence(bool toggle); + + bool dryRun() const; + void setDryRun(bool dryRun); + + bool keepGoing() const; + void setKeepGoing(bool keepGoing); + + bool forceTimestampCheck() const; + void setForceTimestampCheck(bool enabled); + + bool forceOutputCheck() const; + void setForceOutputCheck(bool enabled); + + bool logElapsedTime() const; + void setLogElapsedTime(bool log); + + CommandEchoMode echoMode() const; + void setEchoMode(CommandEchoMode echoMode); + + bool install() const; + void setInstall(bool install); + + bool removeExistingInstallation() const; + void setRemoveExistingInstallation(bool removeExisting); + + bool executeRulesOnly() const; + void setExecuteRulesOnly(bool onlyRules); + +private: + QSharedDataPointer d; +}; + +bool operator==(const BuildOptions &bo1, const BuildOptions &bo2); +inline bool operator!=(const BuildOptions &bo1, const BuildOptions &bo2) { return !(bo1 == bo2); } + +} // namespace qbs + +#endif // QBS_BUILDOPTIONS_H diff --git a/src/lib/corelib/tools/clangclinfo.cpp b/src/lib/corelib/tools/clangclinfo.cpp new file mode 100644 index 00000000..4e1022c2 --- /dev/null +++ b/src/lib/corelib/tools/clangclinfo.cpp @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** 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. +** +****************************************************************************/ + +#include "clangclinfo.h" + +#include "hostosinfo.h" +#include "msvcinfo.h" +#include "stlutils.h" + +namespace qbs { +namespace Internal { + +static std::vector compatibleMsvcs(Logger &logger) +{ + auto msvcs = MSVCInstallInfo::installedMSVCs(logger); + auto filter = [](const MSVCInstallInfo &info) + { + const auto versions = info.version.split(QLatin1Char('.')); + if (versions.empty()) + return true; + bool ok = false; + const int major = versions.at(0).toInt(&ok); + return !(ok && major >= 15); // support MSVC2017 and above + }; + const auto it = std::remove_if(msvcs.begin(), msvcs.end(), filter); + msvcs.erase(it, msvcs.end()); + for (const auto &msvc: msvcs) { + auto vcvarsallPath = msvc.findVcvarsallBat(); + if (vcvarsallPath.isEmpty()) + continue; + } + return msvcs; +} + +static QString findCompatibleVcsarsallBat(const std::vector &msvcs) +{ + for (const auto &msvc: msvcs) { + const auto vcvarsallPath = msvc.findVcvarsallBat(); + if (!vcvarsallPath.isEmpty()) + return vcvarsallPath; + } + return {}; +} + +static QString wow6432Key() +{ +#ifdef Q_OS_WIN64 + return QStringLiteral("\\Wow6432Node"); +#else + return {}; +#endif +} + +static QString getToolchainInstallPath(const QFileInfo &compiler) +{ + return compiler.path(); // 1 level up +} + +QVariantMap ClangClInfo::toVariantMap() const +{ + return { + {QStringLiteral("toolchainInstallPath"), toolchainInstallPath}, + {QStringLiteral("vcvarsallPath"), vcvarsallPath}, + }; +} + +ClangClInfo ClangClInfo::fromCompilerFilePath(const QString &path, Logger &logger) +{ + const auto compilerName = QStringLiteral("clang-cl"); + const auto vcvarsallPath = findCompatibleVcsarsallBat(compatibleMsvcs(logger)); + if (vcvarsallPath.isEmpty()) { + logger.qbsWarning() + << Tr::tr("%1 requires installed Visual Studio 2017 or newer, but none was found.") + .arg(compilerName); + return {}; + } + + const auto toolchainInstallPath = getToolchainInstallPath(path); + return {toolchainInstallPath, vcvarsallPath}; +} + +std::vector ClangClInfo::installedCompilers( + const std::vector &extraPaths, Logger &logger) +{ + std::vector compilerPaths; + compilerPaths.reserve(extraPaths.size()); + std::copy_if(extraPaths.begin(), extraPaths.end(), + std::back_inserter(compilerPaths), + [](const QString &path){ return !path.isEmpty(); }); + const auto compilerName = HostOsInfo::appendExecutableSuffix(QStringLiteral("clang-cl")); + + const QSettings registry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE%1\\LLVM\\LLVM").arg(wow6432Key()), + QSettings::NativeFormat); + const auto key = QStringLiteral("."); + if (registry.contains(key)) { + const auto compilerPath = QDir::fromNativeSeparators(registry.value(key).toString()) + + QStringLiteral("/bin/") + compilerName; + if (QFileInfo(compilerPath).exists()) + compilerPaths.push_back(compilerPath); + } + + // this branch can be useful in case user had two LLVM installations (e.g. 32bit & 64bit) + // but uninstalled one - in that case, registry will be empty + static const char * const envVarCandidates[] = {"ProgramFiles", "ProgramFiles(x86)"}; + for (const auto &envVar : envVarCandidates) { + const auto value + = QDir::fromNativeSeparators(QString::fromLocal8Bit(qgetenv(envVar))); + const auto compilerPath = value + QStringLiteral("/LLVM/bin/") + compilerName; + if (QFileInfo(compilerPath).exists() && !contains(compilerPaths, compilerPath)) + compilerPaths.push_back(compilerPath); + } + + const auto msvcs = compatibleMsvcs(logger); + const auto vcvarsallPath = findCompatibleVcsarsallBat(msvcs); + if (vcvarsallPath.isEmpty()) { + logger.qbsWarning() + << Tr::tr("%1 requires installed Visual Studio 2017 or newer, but none was found.") + .arg(compilerName); + return {}; + } + + std::vector result; + result.reserve(compilerPaths.size() + msvcs.size()); + + for (const auto &path: compilerPaths) + result.push_back({getToolchainInstallPath(path), vcvarsallPath}); + + // If we didn't find custom LLVM installation, try to find if it's installed with Visual Studio + for (const auto &msvc : msvcs) { + const auto compilerPath = QStringLiteral("%1/VC/Tools/Llvm/bin/%2") + .arg(msvc.installDir, compilerName); + if (QFileInfo(compilerPath).exists()) { + const auto vcvarsallPath = msvc.findVcvarsallBat(); + if (vcvarsallPath.isEmpty()) { + logger.qbsWarning() + << Tr::tr("Found LLVM in %1, but vcvarsall.bat is missing.") + .arg(msvc.installDir); + } + + result.push_back({getToolchainInstallPath(compilerPath), vcvarsallPath}); + } + } + + return result; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/clangclinfo.h b/src/lib/corelib/tools/clangclinfo.h new file mode 100644 index 00000000..76ae169f --- /dev/null +++ b/src/lib/corelib/tools/clangclinfo.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** 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. +** +****************************************************************************/ + +#ifndef QBS_CLANGCLINFO_H +#define QBS_CLANGCLINFO_H + +#include "logging/logger.h" + +#include + +#include + +namespace qbs { +namespace Internal { + +class ClangClInfo +{ +public: + QString toolchainInstallPath; + QString vcvarsallPath; + + bool isEmpty() const { return toolchainInstallPath.isEmpty() && vcvarsallPath.isEmpty(); } + + QBS_EXPORT QVariantMap toVariantMap() const; + + QBS_EXPORT static ClangClInfo fromCompilerFilePath(const QString &path, Logger &logger); + QBS_EXPORT static std::vector installedCompilers( + const std::vector &extraPaths, qbs::Internal::Logger &logger); +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_CLANGCLINFO_H diff --git a/src/lib/corelib/tools/cleanoptions.cpp b/src/lib/corelib/tools/cleanoptions.cpp new file mode 100644 index 00000000..affc9b3f --- /dev/null +++ b/src/lib/corelib/tools/cleanoptions.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** 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 "cleanoptions.h" + +#include "jsonhelper.h" + +#include + +namespace qbs { +namespace Internal { + +class CleanOptionsPrivate : public QSharedData +{ +public: + CleanOptionsPrivate() + : dryRun(false), + keepGoing(false), logElapsedTime(false) + { } + + bool dryRun; + bool keepGoing; + bool logElapsedTime; +}; + +} // namespace Internal + +/*! + * \class CleanOptions + * \brief The \c CleanOptions class comprises parameters that influence the behavior of + * cleaning operations. + */ + +/*! + * \enum CleanOptions::CleanType + * This enum type specifies which kind of build artifacts to remove. + * \value CleanupAll Indicates that all files created by the build process should be removed. + * \value CleanupTemporaries Indicates that only intermediate build artifacts should be removed. + * If, for example, the product to clean up for is a Linux shared library, the .so file + * would be left on the disk, but the .o files would be removed. + */ + +CleanOptions::CleanOptions() : d(new Internal::CleanOptionsPrivate) +{ +} + +CleanOptions::CleanOptions(const CleanOptions &other) = default; + +CleanOptions::CleanOptions(CleanOptions &&other) Q_DECL_NOEXCEPT = default; + +CleanOptions &CleanOptions::operator=(const CleanOptions &other) = default; + +CleanOptions &CleanOptions::operator=(CleanOptions &&other) Q_DECL_NOEXCEPT = default; + +CleanOptions::~CleanOptions() = default; + +/*! + * \brief Returns true iff qbs will not actually remove any files, but just show what would happen. + * The default is false. + */ +bool CleanOptions::dryRun() const +{ + return d->dryRun; +} + +/*! + * \brief Controls whether clean-up will actually take place. + * If the argument is true, then qbs will emit information about which files would be removed + * instead of actually doing it. + */ +void CleanOptions::setDryRun(bool dryRun) +{ + d->dryRun = dryRun; +} + +/*! + * Returns true iff clean-up will continue if an error occurs. + * The default is false. + */ +bool CleanOptions::keepGoing() const +{ + return d->keepGoing; +} + +/*! + * \brief Controls whether to abort on errors. + * If the argument is true, then if a file cannot be removed e.g. due to a permission problem, + * a warning will be printed and the clean-up will continue. If the argument is false, + * then the clean-up will abort immediately in case of an error. + */ +void CleanOptions::setKeepGoing(bool keepGoing) +{ + d->keepGoing = keepGoing; +} + +/*! + * \brief Returns true iff the time the operation takes will be logged. + * The default is false. + */ +bool CleanOptions::logElapsedTime() const +{ + return d->logElapsedTime; +} + +/*! + * \brief Controls whether the clean-up time will be measured and logged. + */ +void CleanOptions::setLogElapsedTime(bool log) +{ + d->logElapsedTime = log; +} + +qbs::CleanOptions qbs::CleanOptions::fromJson(const QJsonObject &data) +{ + CleanOptions opt; + using namespace Internal; + setValueFromJson(opt.d->dryRun, data, "dry-run"); + setValueFromJson(opt.d->keepGoing, data, "keep-going"); + setValueFromJson(opt.d->logElapsedTime, data, "log-time"); + return opt; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/cleanoptions.h b/src/lib/corelib/tools/cleanoptions.h new file mode 100644 index 00000000..7827697b --- /dev/null +++ b/src/lib/corelib/tools/cleanoptions.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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 QBS_CLEANOPTIONS_H +#define QBS_CLEANOPTIONS_H + +#include "qbs_export.h" + +#include + +QT_BEGIN_NAMESPACE +class QJsonObject; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { class CleanOptionsPrivate; } + +class QBS_EXPORT CleanOptions +{ +public: + CleanOptions(); + CleanOptions(const CleanOptions &other); + CleanOptions(CleanOptions &&other) Q_DECL_NOEXCEPT; + CleanOptions &operator=(const CleanOptions &other); + CleanOptions &operator=(CleanOptions &&other) Q_DECL_NOEXCEPT; + ~CleanOptions(); + + static CleanOptions fromJson(const QJsonObject &data); + + bool dryRun() const; + void setDryRun(bool dryRun); + + bool keepGoing() const; + void setKeepGoing(bool keepGoing); + + bool logElapsedTime() const; + void setLogElapsedTime(bool log); + +private: + QSharedDataPointer d; +}; + +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/tools/codelocation.cpp b/src/lib/corelib/tools/codelocation.cpp new file mode 100644 index 00000000..54240879 --- /dev/null +++ b/src/lib/corelib/tools/codelocation.cpp @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** 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 "codelocation.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace qbs { + +class CodeLocation::CodeLocationPrivate : public QSharedData +{ +public: + void load(Internal::PersistentPool &pool) + { + pool.load(filePath); + pool.load(line); + pool.load(column); + } + + void store(Internal::PersistentPool &pool) const + { + pool.store(filePath); + pool.store(line); + pool.store(column); + } + + QString filePath; + int line = 0; + int column = 0; +}; + +CodeLocation::CodeLocation() = default; + +CodeLocation::CodeLocation(const QString &aFilePath, int aLine, int aColumn, bool checkPath) + : d(new CodeLocationPrivate) +{ + QBS_ASSERT(!checkPath || Internal::FileInfo::isAbsolute(aFilePath), qDebug() << aFilePath); + d->filePath = aFilePath; + d->line = aLine; + d->column = aColumn; +} + +CodeLocation::CodeLocation(const CodeLocation &other) = default; + +CodeLocation &CodeLocation::operator=(const CodeLocation &other) = default; + +CodeLocation::~CodeLocation() = default; + +QString CodeLocation::filePath() const +{ + return d ? d->filePath : QString(); +} + +int CodeLocation::line() const +{ + return d ? d->line : -1; +} + +int CodeLocation::column() const +{ + return d ? d->column : -1; +} + +bool CodeLocation::isValid() const +{ + return !filePath().isEmpty(); +} + +QString CodeLocation::toString() const +{ + QString str; + if (isValid()) { + str = QDir::toNativeSeparators(filePath()); + QString lineAndColumn; + if (line() > 0 && !str.contains(QRegExp(QStringLiteral(":[0-9]+$")))) + lineAndColumn += QLatin1Char(':') + QString::number(line()); + if (column() > 0 && !str.contains(QRegExp(QStringLiteral(":[0-9]+:[0-9]+$")))) + lineAndColumn += QLatin1Char(':') + QString::number(column()); + str += lineAndColumn; + } + return str; +} + +QJsonObject CodeLocation::toJson() const +{ + QJsonObject obj; + if (!filePath().isEmpty()) + obj.insert(Internal::StringConstants::filePathKey(), filePath()); + if (line() != -1) + obj.insert(QStringLiteral("line"), line()); + if (column() != -1) + obj.insert(QStringLiteral("column"), column()); + return obj; +} + +void CodeLocation::load(Internal::PersistentPool &pool) +{ + const bool isValid = pool.load(); + if (!isValid) + return; + d = new CodeLocationPrivate; + pool.load(*d); +} + +void CodeLocation::store(Internal::PersistentPool &pool) const +{ + if (d) { + pool.store(true); + pool.store(*d); + } else { + pool.store(false); + } +} + +bool operator==(const CodeLocation &cl1, const CodeLocation &cl2) +{ + if (cl1.d == cl2.d) + return true; + return cl1.filePath() == cl2.filePath() && cl1.line() == cl2.line() + && cl1.column() == cl2.column(); +} + +bool operator!=(const CodeLocation &cl1, const CodeLocation &cl2) +{ + return !(cl1 == cl2); +} + +QDebug operator<<(QDebug debug, const CodeLocation &location) +{ + return debug << location.toString(); +} + +bool operator<(const CodeLocation &cl1, const CodeLocation &cl2) +{ + return cl1.toString() < cl2.toString(); +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/codelocation.h b/src/lib/corelib/tools/codelocation.h new file mode 100644 index 00000000..3e84ce2d --- /dev/null +++ b/src/lib/corelib/tools/codelocation.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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 QBS_SOURCELOCATION_H +#define QBS_SOURCELOCATION_H + +#include "qbs_export.h" + +#include +#include + +QT_BEGIN_NAMESPACE +class QDataStream; +class QJsonObject; +class QString; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { class PersistentPool; } + +class QBS_EXPORT CodeLocation +{ + friend QBS_EXPORT bool operator==(const CodeLocation &cl1, const CodeLocation &cl2); +public: + CodeLocation(); + explicit CodeLocation(const QString &aFilePath, int aLine = -1, int aColumn = -1, + bool checkPath = true); + CodeLocation(const CodeLocation &other); + CodeLocation &operator=(const CodeLocation &other); + ~CodeLocation(); + + QString filePath() const; + int line() const; + int column() const; + + bool isValid() const; + QString toString() const; + QJsonObject toJson() const; + + void load(Internal::PersistentPool &pool); + void store(Internal::PersistentPool &pool) const; + +private: + class CodeLocationPrivate; + QExplicitlySharedDataPointer d; +}; + +QBS_EXPORT bool operator==(const CodeLocation &cl1, const CodeLocation &cl2); +QBS_EXPORT bool operator!=(const CodeLocation &cl1, const CodeLocation &cl2); +QBS_EXPORT bool operator<(const CodeLocation &cl1, const CodeLocation &cl2); + +inline uint qHash(const CodeLocation &cl) { return qHash(cl.toString()); } + +QDebug operator<<(QDebug debug, const CodeLocation &location); + +} // namespace qbs + +#endif // QBS_SOURCELOCATION_H diff --git a/src/lib/corelib/tools/commandechomode.cpp b/src/lib/corelib/tools/commandechomode.cpp new file mode 100644 index 00000000..c47ddb5b --- /dev/null +++ b/src/lib/corelib/tools/commandechomode.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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 "commandechomode.h" + +#include +#include + +/*! + * \enum CommandEchoMode + * This enum type specifies the kind of output to display when executing commands. + * \value CommandEchoModeSilent Indicates that no output will be printed. + * \value CommandEchoModeSummary Indicates that descriptions will be printed. + * \value CommandEchoModeCommandLine Indidcates that full command line invocations will be printed. + * \value CommandEchoModeCommandLineWithEnvironment Indidcates that full command line invocations, + * including environment variables, will be printed. + */ + +namespace qbs { + +CommandEchoMode defaultCommandEchoMode() +{ + return CommandEchoModeSummary; +} + +QString commandEchoModeName(CommandEchoMode mode) +{ + switch (mode) { + case CommandEchoModeSilent: + return QStringLiteral("silent"); + case CommandEchoModeSummary: + return QStringLiteral("summary"); + case CommandEchoModeCommandLine: + return QStringLiteral("command-line"); + case CommandEchoModeCommandLineWithEnvironment: + return QStringLiteral("command-line-with-environment"); + default: + break; + } + return {}; +} + +CommandEchoMode commandEchoModeFromName(const QString &name) +{ + CommandEchoMode mode = defaultCommandEchoMode(); + for (int i = 0; i < CommandEchoModeInvalid; ++i) { + if (commandEchoModeName(static_cast(i)) == name) { + mode = static_cast(i); + break; + } + } + + return mode; +} + +QStringList allCommandEchoModeStrings() +{ + QStringList result; + for (int i = 0; i < CommandEchoModeInvalid; ++i) + result << commandEchoModeName(static_cast(i)); + return result; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/commandechomode.h b/src/lib/corelib/tools/commandechomode.h new file mode 100644 index 00000000..88d8377a --- /dev/null +++ b/src/lib/corelib/tools/commandechomode.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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_COMMANDECHOMODE_H +#define QBS_COMMANDECHOMODE_H + +#include "qbs_export.h" +#include + +QT_BEGIN_NAMESPACE +class QString; +class QStringList; +QT_END_NAMESPACE + +namespace qbs { + +enum CommandEchoMode { + CommandEchoModeSilent, + CommandEchoModeSummary, + CommandEchoModeCommandLine, + CommandEchoModeCommandLineWithEnvironment, + CommandEchoModeInvalid, +}; + +QBS_EXPORT CommandEchoMode defaultCommandEchoMode(); +QBS_EXPORT QString commandEchoModeName(CommandEchoMode mode); +QBS_EXPORT CommandEchoMode commandEchoModeFromName(const QString &name); +QBS_EXPORT QStringList allCommandEchoModeStrings(); + +} // namespace qbs + +#endif // QBS_COMMANDECHOMODE_H + diff --git a/src/lib/corelib/tools/dynamictypecheck.h b/src/lib/corelib/tools/dynamictypecheck.h new file mode 100644 index 00000000..b8756c57 --- /dev/null +++ b/src/lib/corelib/tools/dynamictypecheck.h @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ +#ifndef QBS_DYNAMICTYPECHECK_H +#define QBS_DYNAMICTYPECHECK_H + +namespace qbs { +namespace Internal { + +// Generic implementation would use dynamic_cast. Don't implement unless it becomes necessary. +template inline bool hasDynamicType(const StaticType *); + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/tools/error.cpp b/src/lib/corelib/tools/error.cpp new file mode 100644 index 00000000..ff7ce46c --- /dev/null +++ b/src/lib/corelib/tools/error.cpp @@ -0,0 +1,328 @@ +/**************************************************************************** +** +** 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 "error.h" + +#include "persistence.h" +#include "qttools.h" +#include "stringconstants.h" + +#include +#include +#include +#include + +#include +#include +#include + +namespace qbs { + +class ErrorItem::ErrorItemPrivate : public QSharedData +{ +public: + template + void serializationOp(Internal::PersistentPool &pool) + { + pool.serializationOp(description, codeLocation, isBacktraceItem); + } + + void load(Internal::PersistentPool &pool) + { + serializationOp(pool); + } + + void store(Internal::PersistentPool &pool) + { + serializationOp(pool); + } + + QString description; + CodeLocation codeLocation; + bool isBacktraceItem = false; +}; + +/*! + * \class ErrorData + * \brief The \c ErrorData class describes (part of) an error resulting from a qbs operation. + * It is always delivered as part of an \c Error. + * \sa Error + */ + +ErrorItem::ErrorItem() : d(new ErrorItemPrivate) +{ +} + +ErrorItem::ErrorItem(ErrorItem &&rhs) noexcept = default; + +ErrorItem::ErrorItem(const QString &description, const CodeLocation &codeLocation, + bool isBacktraceItem) + : d(new ErrorItemPrivate) +{ + d->description = description; + d->codeLocation = codeLocation; + d->isBacktraceItem = isBacktraceItem; +} + +ErrorItem::ErrorItem(const ErrorItem &rhs) = default; + +ErrorItem &ErrorItem::operator=(const ErrorItem &other) = default; +ErrorItem &ErrorItem::operator=(ErrorItem &&other) noexcept = default; + +ErrorItem::~ErrorItem() = default; + +QString ErrorItem::description() const +{ + return d->description; +} + +CodeLocation ErrorItem::codeLocation() const +{ + return d->codeLocation; +} + +bool ErrorItem::isBacktraceItem() const +{ + return d->isBacktraceItem; +} + +void ErrorItem::load(Internal::PersistentPool &pool) +{ + pool.load(*d); +} + +void ErrorItem::store(Internal::PersistentPool &pool) const +{ + pool.store(*d); +} + +/*! + * \fn const QString &ErrorData::description() const + * \brief A general description of the error. + */ + + /*! + * \fn const QString &ErrorData::codeLocation() const + * \brief The location at which file in which the error occurred. + * \note This information might not be applicable, in which case location().isValid() returns false + */ + +/*! + * \brief A full textual description of the error using all available information. + */ +QString ErrorItem::toString() const +{ + QString str = codeLocation().toString(); + if (!str.isEmpty()) + str += QLatin1Char(' '); + return str += description(); +} + +QJsonObject ErrorItem::toJson() const +{ + QJsonObject data; + data.insert(Internal::StringConstants::descriptionProperty(), description()); + data.insert(Internal::StringConstants::locationKey(), codeLocation().toJson()); + return data; +} + + +class ErrorInfo::ErrorInfoPrivate : public QSharedData +{ +public: + ErrorInfoPrivate() : internalError(false) { } + + template + void completeSerializationOp(Internal::PersistentPool &pool) + { + pool.serializationOp(items, internalError); + } + + QList items; + bool internalError; +}; + +/*! + * \class Error + * \brief Represents an error resulting from a qbs operation. + * It is made up of one or more \c ErrorData objects. + * \sa ErrorData + */ + +ErrorInfo::ErrorInfo() : d(new ErrorInfoPrivate) +{ +} + +ErrorInfo::ErrorInfo(const ErrorInfo &rhs) = default; + +ErrorInfo::ErrorInfo(ErrorInfo &&rhs) noexcept = default; + +ErrorInfo::ErrorInfo(const QString &description, const CodeLocation &location, bool internalError) + : d(new ErrorInfoPrivate) +{ + append(description, location); + d->internalError = internalError; +} + +ErrorInfo::ErrorInfo(const QString &description, const QStringList &backtrace) + : d(new ErrorInfoPrivate) +{ + append(description); + for (const QString &traceLine : backtrace) { + static const std::regex regexp("^(.+) at (.+):(\\-?[0-9]+)$"); + std::smatch match; + const std::string tl = traceLine.toStdString(); + if (std::regex_match(tl, match, regexp)) { + const QString message = QString::fromStdString(match[1]), + file = QString::fromStdString(match[2]), + line = QString::fromStdString(match[3]); + const CodeLocation location(file, line.toInt()); + appendBacktrace(message, location); + } + } +} + +ErrorInfo &ErrorInfo::operator=(ErrorInfo &&other) noexcept = default; + +ErrorInfo &ErrorInfo::operator =(const ErrorInfo &other) = default; + +ErrorInfo::~ErrorInfo() = default; + +void ErrorInfo::appendBacktrace(const QString &description, const CodeLocation &location) +{ + d->items.push_back(ErrorItem(description, location, true)); +} + +void ErrorInfo::append(const ErrorItem &item) +{ + d->items.push_back(item); +} + +void ErrorInfo::append(const QString &description, const CodeLocation &location) +{ + d->items.push_back(ErrorItem(description, location)); +} + +void ErrorInfo::prepend(const QString &description, const CodeLocation &location) +{ + d->items.prepend(ErrorItem(description, location)); +} + +/*! + * \brief A list of concrete error description. + * Most often, there will be one element in this list, but there can be more e.g. to illustrate + * how an error condition propagates through several source files. + */ +const QList ErrorInfo::items() const +{ + return d->items; +} + +void ErrorInfo::clear() +{ + d->items.clear(); +} + +/*! + * \brief A complete textual description of the error. + * All "sub-errors" will be represented. + * \sa Error::entries() + */ +QString ErrorInfo::toString() const +{ + QStringList lines; + for (const ErrorItem &e : qAsConst(d->items)) { + if (e.isBacktraceItem()) { + QString line; + if (!e.description().isEmpty()) + line.append(QStringLiteral(" at %1").arg(e.description())); + if (e.codeLocation().isValid()) + line.append(QStringLiteral(" in %1").arg(e.codeLocation().toString())); + if (!line.isEmpty()) + lines.push_back(QLatin1Char('\t') + line); + } else { + lines.push_back(e.toString()); + } + } + return lines.join(QLatin1Char('\n')); +} + +QJsonObject ErrorInfo::toJson() const +{ + QJsonObject data; + data.insert(QLatin1String("is-internal"), isInternalError()); + QJsonArray itemsArray; + for (const ErrorItem &item : items()) + itemsArray.append(item.toJson()); + data.insert(QLatin1String("items"), itemsArray); + return data; +} + +/*! + * \brief Returns true if this error represents a bug in qbs, false otherwise. + */ +bool ErrorInfo::isInternalError() const +{ + return d->internalError; +} + +bool ErrorInfo::hasLocation() const +{ + return std::any_of(d->items.cbegin(), d->items.cend(), + [](const ErrorItem &ei) { return ei.codeLocation().isValid(); }); +} + +void ErrorInfo::load(Internal::PersistentPool &pool) +{ + pool.load(*d); +} + +void ErrorInfo::store(Internal::PersistentPool &pool) const +{ + pool.store(*d); +} + +void appendError(ErrorInfo &dst, const ErrorInfo &src) +{ + const QList &sourceItems = src.items(); + for (const ErrorItem &item : sourceItems) + dst.append(item); +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/error.h b/src/lib/corelib/tools/error.h new file mode 100644 index 00000000..36cf5e0e --- /dev/null +++ b/src/lib/corelib/tools/error.h @@ -0,0 +1,132 @@ +/**************************************************************************** +** +** 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 QBS_ERROR +#define QBS_ERROR + +#include "codelocation.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QJsonObject; +template class QList; +class QString; +class QStringList; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { class PersistentPool; } +class CodeLocation; + +class QBS_EXPORT ErrorItem +{ + friend class ErrorInfo; +public: + ErrorItem(); + ErrorItem(const ErrorItem &rhs); + ErrorItem(ErrorItem &&rhs) noexcept; + ErrorItem &operator=(const ErrorItem &other); + ErrorItem &operator=(ErrorItem &&other) noexcept; + ~ErrorItem(); + + QString description() const; + CodeLocation codeLocation() const; + QString toString() const; + QJsonObject toJson() const; + + bool isBacktraceItem() const; + + void load(Internal::PersistentPool &pool); + void store(Internal::PersistentPool &pool) const; + +private: + ErrorItem(const QString &description, const CodeLocation &codeLocation, + bool isBacktraceItem = false); + + class ErrorItemPrivate; + QExplicitlySharedDataPointer d; +}; + +class QBS_EXPORT ErrorInfo +{ +public: + ErrorInfo(); + ErrorInfo(const ErrorInfo &rhs); + ErrorInfo(ErrorInfo &&rhs) noexcept; + ErrorInfo(const QString &description, const CodeLocation &location = CodeLocation(), + bool internalError = false); + ErrorInfo(const QString &description, const QStringList &backtrace); + ErrorInfo &operator=(const ErrorInfo &other); + ErrorInfo &operator=(ErrorInfo &&other) noexcept; + ~ErrorInfo(); + + void appendBacktrace(const QString &description, const CodeLocation &location = CodeLocation()); + void append(const ErrorItem &item); + void append(const QString &description, const CodeLocation &location = CodeLocation()); + void prepend(const QString &description, const CodeLocation &location = CodeLocation()); + const QList items() const; + bool hasError() const { return !items().empty(); } + void clear(); + QString toString() const; + QJsonObject toJson() const; + bool isInternalError() const; + bool hasLocation() const; + + void load(Internal::PersistentPool &pool); + void store(Internal::PersistentPool &pool) const; + +private: + class ErrorInfoPrivate; + QSharedDataPointer d; +}; + +void appendError(ErrorInfo &dst, const ErrorInfo &src); +inline uint qHash(const ErrorInfo &e) { return qHash(e.toString()); } +inline bool operator==(const ErrorInfo &e1, const ErrorInfo &e2) { + return e1.toString() == e2.toString(); +} + +} // namespace qbs + +Q_DECLARE_METATYPE(qbs::ErrorInfo) + +#endif // QBS_ERROR diff --git a/src/lib/corelib/tools/executablefinder.cpp b/src/lib/corelib/tools/executablefinder.cpp new file mode 100644 index 00000000..a02fbdc9 --- /dev/null +++ b/src/lib/corelib/tools/executablefinder.cpp @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** 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 "executablefinder.h" + +#include "fileinfo.h" +#include "hostosinfo.h" +#include "qttools.h" +#include "stringconstants.h" + +#include + +#include + +namespace qbs { +namespace Internal { + +static QStringList populateExecutableSuffixes() +{ + QStringList result; + result << QString(); + if (HostOsInfo::isWindowsHost()) { + result << QStringLiteral(".com") << QStringLiteral(".exe") + << QStringLiteral(".bat") << QStringLiteral(".cmd"); + } + return result; +} + +QStringList ExecutableFinder::m_executableSuffixes = populateExecutableSuffixes(); + +ExecutableFinder::ExecutableFinder(ResolvedProductPtr product, const QProcessEnvironment &env) + : m_product(std::move(product)) + , m_environment(env) // QProcessEnvironment doesn't have move-ctor, copy here +{ +} + +QString ExecutableFinder::findExecutable(const QString &path, const QString &workingDirPath) +{ + QString filePath = QDir::fromNativeSeparators(path); + //if (FileInfo::fileName(filePath) == filePath) + if (!FileInfo::isAbsolute(filePath)) + return findInPath(filePath, workingDirPath); + else if (HostOsInfo::isWindowsHost()) + return findBySuffix(filePath); + return filePath; +} + +QString ExecutableFinder::findBySuffix(const QString &filePath) const +{ + QString fullProgramPath = cachedFilePath(filePath); + if (!fullProgramPath.isEmpty()) + return fullProgramPath; + + fullProgramPath = filePath; + qCDebug(lcExec) << "looking for executable by suffix" << fullProgramPath; + const QString emptyDirectory; + candidateCheck(emptyDirectory, fullProgramPath, fullProgramPath); + cacheFilePath(filePath, fullProgramPath); + return fullProgramPath; + +} + +bool ExecutableFinder::candidateCheck(const QString &directory, const QString &program, + QString &fullProgramPath) const +{ + for (const QString &suffix : qAsConst(m_executableSuffixes)) { + QString candidate = directory + program + suffix; + qCDebug(lcExec) << "candidate:" << candidate; + QFileInfo fi(candidate); + if (fi.isFile() && fi.isExecutable()) { + fullProgramPath = candidate; + return true; + } + } + return false; +} + +QString ExecutableFinder::findInPath(const QString &filePath, const QString &workingDirPath) const +{ + QString fullProgramPath = cachedFilePath(filePath); + if (!fullProgramPath.isEmpty()) + return fullProgramPath; + + fullProgramPath = filePath; + qCDebug(lcExec) << "looking for executable in PATH" << fullProgramPath; + QStringList pathEnv = m_environment.value(StringConstants::pathEnvVar()) + .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS); + if (HostOsInfo::isWindowsHost()) + pathEnv.prepend(StringConstants::dot()); + for (QString directory : qAsConst(pathEnv)) { + if (directory == StringConstants::dot()) + directory = workingDirPath; + if (!directory.isEmpty()) { + const QChar lastChar = directory.at(directory.size() - 1); + if (lastChar != QLatin1Char('/') && lastChar != QLatin1Char('\\')) + directory.append(QLatin1Char('/')); + } + if (candidateCheck(directory, fullProgramPath, fullProgramPath)) + break; + } + cacheFilePath(filePath, fullProgramPath); + return fullProgramPath; +} + +QString ExecutableFinder::cachedFilePath(const QString &filePath) const +{ + return m_product ? m_product->cachedExecutablePath(filePath) : QString(); +} + +void ExecutableFinder::cacheFilePath(const QString &filePath, const QString &fullFilePath) const +{ + if (m_product) + m_product->cacheExecutablePath(filePath, fullFilePath); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/executablefinder.h b/src/lib/corelib/tools/executablefinder.h new file mode 100644 index 00000000..ae834d91 --- /dev/null +++ b/src/lib/corelib/tools/executablefinder.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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 QBS_EXECUTABLEFINDER_H +#define QBS_EXECUTABLEFINDER_H + +#include + +#include + +namespace qbs { +namespace Internal { + +/*! + * \brief Helper class for finding an executable in the PATH of the build environment. + */ +class ExecutableFinder +{ +public: + ExecutableFinder(ResolvedProductPtr product, const QProcessEnvironment &env); + + QString findExecutable(const QString &path, const QString &workingDirPath); + +private: + static QStringList m_executableSuffixes; + QString findBySuffix(const QString &filePath) const; + bool candidateCheck(const QString &directory, const QString &program, + QString &fullProgramPath) const; + QString findInPath(const QString &filePath, const QString &workingDirPath) const; + + QString cachedFilePath(const QString &filePath) const; + void cacheFilePath(const QString &filePaht, const QString &filePath) const; + + ResolvedProductPtr m_product; + const QProcessEnvironment m_environment; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_EXECUTABLEFINDER_H diff --git a/src/lib/corelib/tools/fileinfo.cpp b/src/lib/corelib/tools/fileinfo.cpp new file mode 100644 index 00000000..8f6b285d --- /dev/null +++ b/src/lib/corelib/tools/fileinfo.cpp @@ -0,0 +1,572 @@ +/**************************************************************************** +** +** 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 "fileinfo.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#if defined(Q_OS_UNIX) +#include +#include +#include +#elif defined(Q_OS_WIN) +#include +#endif + +namespace qbs { +namespace Internal { + +QString FileInfo::fileName(const QString &fp) +{ + int last = fp.lastIndexOf(QLatin1Char('/')); + if (last < 0) + return fp; + return fp.mid(last + 1); +} + +QString FileInfo::baseName(const QString &fp) +{ + QString fn = fileName(fp); + int dot = fn.indexOf(QLatin1Char('.')); + if (dot < 0) + return fn; + return fn.mid(0, dot); +} + +QString FileInfo::completeBaseName(const QString &fp) +{ + QString fn = fileName(fp); + int dot = fn.lastIndexOf(QLatin1Char('.')); + if (dot < 0) + return fn; + return fn.mid(0, dot); +} + +QString FileInfo::suffix(const QString &fp) +{ + QString fn = fileName(fp); + int dot = fn.lastIndexOf(QLatin1Char('.')); + if (dot < 0) + return fn; + return fn.mid(dot + 1); +} + +QString FileInfo::completeSuffix(const QString &fp) +{ + QString fn = fileName(fp); + int dot = fn.indexOf(QLatin1Char('.')); + if (dot < 0) + return fn; + return fn.mid(dot + 1); +} + +QString FileInfo::path(const QString &fp, HostOsInfo::HostOs hostOs) +{ + if (fp.isEmpty()) + return {}; + int last = fp.lastIndexOf(QLatin1Char('/')); + if (last < 0) + return StringConstants::dot(); + QString p = QDir::cleanPath(fp.mid(0, last)); + if (p.isEmpty() || (hostOs == HostOsInfo::HostOsWindows && p.length() == 2 && p.at(0).isLetter() + && p.at(1) == QLatin1Char(':'))) { + // Make sure we don't return Windows drive roots without an ending slash. + // Those paths are considered relative. + p.append(QLatin1Char('/')); + } + return p; +} + +void FileInfo::splitIntoDirectoryAndFileName(const QString &filePath, QString *dirPath, QString *fileName) +{ + int idx = filePath.lastIndexOf(QLatin1Char('/')); + if (idx < 0) { + dirPath->clear(); + *fileName = filePath; + return; + } + *dirPath = filePath.left(idx); + *fileName = filePath.mid(idx + 1); +} + +void FileInfo::splitIntoDirectoryAndFileName(const QString &filePath, QStringRef *dirPath, QStringRef *fileName) +{ + int idx = filePath.lastIndexOf(QLatin1Char('/')); + if (idx < 0) { + dirPath->clear(); + *fileName = QStringRef(&filePath); + return; + } + *dirPath = filePath.leftRef(idx); + *fileName = filePath.midRef(idx + 1); +} + +bool FileInfo::exists(const QString &fp) +{ + return FileInfo(fp).exists(); +} + +// Whether a path is the special "current drive path" path type, +// which is neither truly relative nor absolute +static bool isCurrentDrivePath(const QString &path, HostOsInfo::HostOs hostOs) +{ + return hostOs == HostOsInfo::HostOsWindows + ? path.size() == 2 && path.at(1) == QLatin1Char(':') && path.at(0).isLetter() + : false; +} + +// from creator/src/shared/proparser/ioutils.cpp +bool FileInfo::isAbsolute(const QString &path, HostOsInfo::HostOs hostOs) +{ + const int n = path.size(); + if (n == 0) + return false; + const QChar at0 = path.at(0); + if (at0 == QLatin1Char('/')) + return true; + if (hostOs == HostOsInfo::HostOsWindows) { + if (at0 == QLatin1Char('\\')) + return true; + // Unlike QFileInfo, this won't accept a relative path with a drive letter. + // Such paths result in a royal mess anyway ... + if (n >= 3 && path.at(1) == QLatin1Char(':') && at0.isLetter() + && (path.at(2) == QLatin1Char('/') || path.at(2) == QLatin1Char('\\'))) + return true; + } + return false; +} + +bool FileInfo::isPattern(const QString &str) +{ + return isPattern(QStringRef(&str)); +} + +bool FileInfo::isPattern(const QStringRef &str) +{ + for (const QChar &ch : str) { + if (ch == QLatin1Char('*') || ch == QLatin1Char('?') + || ch == QLatin1Char(']') || ch == QLatin1Char('[')) { + return true; + } + } + return false; +} + +/** + * Concatenates the paths \a base and \a rel. + * Base must be an absolute path. + * Double dots at the start of \a rel are handled. + * This function assumes that both paths are clean, that is they don't contain + * double slashes or redundant dot parts. + */ +QString FileInfo::resolvePath(const QString &base, const QString &rel, HostOsInfo::HostOs hostOs) +{ + QBS_ASSERT(isAbsolute(base, hostOs) && !isCurrentDrivePath(rel, hostOs), + qDebug("base: %s, rel: %s", qPrintable(base), qPrintable(rel)); + return {}); + if (isAbsolute(rel, hostOs)) + return rel; + if (rel.size() == 1 && rel.at(0) == QLatin1Char('.')) + return base; + if (rel.size() == 1 && rel.at(0) == QLatin1Char('~')) + return QDir::homePath(); + if (rel.startsWith(StringConstants::tildeSlash())) + return QDir::homePath() + rel.mid(1); + + QString r = base; + if (r.endsWith(QLatin1Char('/'))) + r.chop(1); + + QString s = rel; + while (s.startsWith(QStringLiteral("../"))) { + s.remove(0, 3); + int idx = r.lastIndexOf(QLatin1Char('/')); + if (idx >= 0) + r.truncate(idx); + } + if (s == StringConstants::dotDot()) { + int idx = r.lastIndexOf(QLatin1Char('/')); + if (idx >= 0) + r.truncate(idx); + s.clear(); + } + if (!s.isEmpty() || isCurrentDrivePath(r, hostOs)) { + r.reserve(r.length() + 1 + s.length()); + r += QLatin1Char('/'); + r += s; + } + return r; +} + +bool FileInfo::globMatches(const QRegExp ®exp, const QString &fileName) +{ + const QString pattern = regexp.pattern(); + // May be it's simple wildcard, i.e. "*.cpp"? + if (pattern.startsWith(QLatin1Char('*')) && !isPattern(pattern.midRef(1))) { + // Yes, it's rather simple to just check the extension + return fileName.endsWith(pattern.midRef(1)); + } + return regexp.exactMatch(fileName); +} + +#ifdef Q_OS_WIN +static QString prependLongPathPrefix(const QString &absolutePath) +{ + QString nativePath = QDir::toNativeSeparators(absolutePath); + if (nativePath.startsWith(QStringLiteral("\\\\"))) + nativePath.remove(0, 1).prepend(QLatin1String("UNC")); + nativePath.prepend(QLatin1String("\\\\?\\")); + return nativePath; +} +#endif + +bool FileInfo::isFileCaseCorrect(const QString &filePath) +{ +#if defined(Q_OS_WIN) + // QFileInfo::canonicalFilePath() does not return the real case of the file path on Windows. + QFileInfo fi(filePath); + const QString absolute = prependLongPathPrefix(fi.absoluteFilePath()); + WIN32_FIND_DATA fd; + HANDLE hFindFile = ::FindFirstFile((wchar_t*)absolute.utf16(), &fd); + if (hFindFile == INVALID_HANDLE_VALUE) + return false; + const QString actualFileName = QString::fromWCharArray(fd.cFileName); + FindClose(hFindFile); + return actualFileName == fi.fileName(); +#elif defined(Q_OS_DARWIN) + QFileInfo fi(filePath); + return fi.fileName() == fileName(fi.canonicalFilePath()); +#else + Q_UNUSED(filePath) + return true; +#endif +} + +bool FileInfo::fileExists(const QFileInfo &fi) +{ + return fi.isSymLink() || fi.exists(); +} + +#if defined(Q_OS_WIN) + +#define z(x) reinterpret_cast(const_cast(&x)) + +FileInfo::FileInfo(const QString &fileName) +{ + static_assert(sizeof(FileInfo::InternalStatType) == sizeof(WIN32_FILE_ATTRIBUTE_DATA), + "FileInfo::InternalStatType has wrong size."); + + QString filePath = fileName; + + // The extended-length path prefix cannot be used with a relative path, so make it absolute + if (!isAbsolute(filePath)) + filePath = QDir::currentPath() + QDir::separator() + filePath; + + filePath = prependLongPathPrefix(QDir::cleanPath(filePath)); + if (!GetFileAttributesEx(reinterpret_cast(filePath.utf16()), + GetFileExInfoStandard, &m_stat)) + { + ZeroMemory(z(m_stat), sizeof(WIN32_FILE_ATTRIBUTE_DATA)); + z(m_stat)->dwFileAttributes = INVALID_FILE_ATTRIBUTES; + } +} + +bool FileInfo::exists() const +{ + return z(m_stat)->dwFileAttributes != INVALID_FILE_ATTRIBUTES; +} + +FileTime FileInfo::lastModified() const +{ + const FileTime::InternalType* ft_it; + ft_it = reinterpret_cast(&z(m_stat)->ftLastWriteTime); + return {*ft_it}; +} + +FileTime FileInfo::lastStatusChange() const +{ + return lastModified(); +} + +bool FileInfo::isDir() const +{ + return exists() && z(m_stat)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; +} + +static QString resolveSymlinks(const QString &fileName) +{ + QFileInfo fi(fileName); + while (fi.isSymLink()) + fi.setFile(fi.dir(), fi.symLinkTarget()); + return fi.absoluteFilePath(); +} + +QString applicationDirPath() +{ + static const QString appDirPath = FileInfo::path(resolveSymlinks(QCoreApplication::applicationFilePath())); + return appDirPath; +} + +#elif defined(Q_OS_UNIX) + +FileInfo::FileInfo(const QString &fileName) +{ + if (stat(fileName.toLocal8Bit().constData(), &m_stat) == -1) { + m_stat.st_mtime = 0; + m_stat.st_mode = 0; + } +} + +bool FileInfo::exists() const +{ + return m_stat.st_mode != 0; +} + +FileTime FileInfo::lastModified() const +{ +#if APPLE_STAT_TIMESPEC + return m_stat.st_mtimespec; +#elif HAS_CLOCK_GETTIME + return m_stat.st_mtim; +#else + return m_stat.st_mtime; +#endif +} + +FileTime FileInfo::lastStatusChange() const +{ +#if APPLE_STAT_TIMESPEC + return m_stat.st_ctimespec; +#elif HAS_CLOCK_GETTIME + return m_stat.st_ctim; +#else + return m_stat.st_ctime; +#endif +} + +bool FileInfo::isDir() const +{ + return S_ISDIR(m_stat.st_mode); +} + +#endif + +// adapted from qtc/plugins/vcsbase/cleandialog.cpp +bool removeFileRecursion(const QFileInfo &f, QString *errorMessage) +{ + if (!FileInfo::fileExists(f)) + return true; + if (f.isDir() && !f.isSymLink()) { + const QDir dir(f.absoluteFilePath()); + + // QDir::System is needed for broken symlinks. + const auto fileInfos = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot + | QDir::Hidden | QDir::System); + for (const QFileInfo &fi : fileInfos) + removeFileRecursion(fi, errorMessage); + QDir parent = f.absoluteDir(); + if (!parent.rmdir(f.fileName())) { + errorMessage->append(Tr::tr("The directory %1 could not be deleted."). + arg(QDir::toNativeSeparators(f.absoluteFilePath()))); + return false; + } + } else { + QFile file(f.absoluteFilePath()); + file.setPermissions(f.permissions() | QFile::WriteUser); + if (!file.remove()) { + if (!errorMessage->isEmpty()) + errorMessage->append(QLatin1Char('\n')); + errorMessage->append(Tr::tr("The file %1 could not be deleted."). + arg(QDir::toNativeSeparators(f.absoluteFilePath()))); + return false; + } + } + return true; +} + +bool removeDirectoryWithContents(const QString &path, QString *errorMessage) +{ + QFileInfo f(path); + if (f.exists() && !f.isDir()) { + *errorMessage = Tr::tr("%1 is not a directory.").arg(QDir::toNativeSeparators(path)); + return false; + } + return removeFileRecursion(f, errorMessage); +} + +/*! + * Returns the stored link target of the symbolic link \a{filePath}. + * Unlike QFileInfo::symLinkTarget, this will not make the link target an absolute path. + */ +static QByteArray storedLinkTarget(const QString &filePath) +{ + QByteArray result; + +#ifdef Q_OS_UNIX + const QByteArray nativeFilePath = QFile::encodeName(filePath); + ssize_t len; + while (true) { + struct stat sb{}; + if (lstat(nativeFilePath.constData(), &sb)) { + qWarning("storedLinkTarget: lstat for %s failed with error code %d", + nativeFilePath.constData(), errno); + return {}; + } + + result.resize(sb.st_size); + len = readlink(nativeFilePath.constData(), result.data(), sb.st_size + 1); + if (len < 0) { + qWarning("storedLinkTarget: readlink for %s failed with error code %d", + nativeFilePath.constData(), errno); + return {}; + } + + if (len < sb.st_size) { + result.resize(len); + break; + } + if (len == sb.st_size) + break; + } +#else + Q_UNUSED(filePath); +#endif // Q_OS_UNIX + + return result; +} + +static bool createSymLink(const QByteArray &path1, const QString &path2) +{ +#ifdef Q_OS_UNIX + const QByteArray newPath = QFile::encodeName(path2); + unlink(newPath.constData()); + return symlink(path1.constData(), newPath.constData()) == 0; +#else + Q_UNUSED(path1); + Q_UNUSED(path2); + return false; +#endif // Q_OS_UNIX +} + +/*! + Copies the directory specified by \a srcFilePath recursively to \a tgtFilePath. + \a tgtFilePath will contain the target directory, which will be created. Example usage: + + \code + QString error; + book ok = Utils::FileUtils::copyRecursively("/foo/bar", "/foo/baz", &error); + if (!ok) + qDebug() << error; + \endcode + + This will copy the contents of /foo/bar into to the baz directory under /foo, + which will be created in the process. + + \return Whether the operation succeeded. + \note Function was adapted from qtc/src/libs/fileutils.cpp +*/ + +bool copyFileRecursion(const QString &srcFilePath, const QString &tgtFilePath, + bool preserveSymLinks, bool copyDirectoryContents, QString *errorMessage) +{ + QFileInfo srcFileInfo(srcFilePath); + QFileInfo tgtFileInfo(tgtFilePath); + const QString targetDirPath = tgtFileInfo.absoluteDir().path(); + if (!QDir::root().mkpath(targetDirPath)) { + *errorMessage = Tr::tr("The directory '%1' could not be created.") + .arg(QDir::toNativeSeparators(targetDirPath)); + return false; + } + if (HostOsInfo::isAnyUnixHost() && preserveSymLinks && srcFileInfo.isSymLink()) { + // For now, disable symlink preserving copying on Windows. + // MS did a good job to prevent people from using symlinks - even if they are supported. + if (!createSymLink(storedLinkTarget(srcFilePath), tgtFilePath)) { + *errorMessage = Tr::tr("The symlink '%1' could not be created.") + .arg(tgtFilePath); + return false; + } + } else if (srcFileInfo.isDir()) { + if (copyDirectoryContents) { + QDir sourceDir(srcFilePath); + const QStringList fileNames = sourceDir.entryList(QDir::Files | QDir::Dirs + | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); + for (const QString &fileName : fileNames) { + const QString newSrcFilePath = srcFilePath + QLatin1Char('/') + fileName; + const QString newTgtFilePath = tgtFilePath + QLatin1Char('/') + fileName; + if (!copyFileRecursion(newSrcFilePath, newTgtFilePath, preserveSymLinks, + copyDirectoryContents, errorMessage)) + return false; + } + } else { + if (tgtFileInfo.exists() && srcFileInfo.lastModified() <= tgtFileInfo.lastModified()) + return true; + return QDir::root().mkpath(tgtFilePath); + } + } else { + if (tgtFileInfo.exists() && srcFileInfo.lastModified() <= tgtFileInfo.lastModified()) + return true; + QFile file(srcFilePath); + QFile targetFile(tgtFilePath); + if (targetFile.exists()) { + targetFile.setPermissions(targetFile.permissions() | QFile::WriteUser); + if (!targetFile.remove()) { + *errorMessage = Tr::tr("Could not remove file '%1'. %2") + .arg(QDir::toNativeSeparators(tgtFilePath), targetFile.errorString()); + } + } + if (!file.copy(tgtFilePath)) { + *errorMessage = Tr::tr("Could not copy file '%1' to '%2'. %3") + .arg(QDir::toNativeSeparators(srcFilePath), QDir::toNativeSeparators(tgtFilePath), + file.errorString()); + return false; + } + } + return true; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/fileinfo.h b/src/lib/corelib/tools/fileinfo.h new file mode 100644 index 00000000..9813b69a --- /dev/null +++ b/src/lib/corelib/tools/fileinfo.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** 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 QBS_FILEINFO_H +#define QBS_FILEINFO_H + +#include "filetime.h" +#include "hostosinfo.h" +#include "qbs_export.h" + +#if defined(Q_OS_UNIX) +#include +#endif + +#include + +QT_FORWARD_DECLARE_CLASS(QFileInfo) + +namespace qbs { +namespace Internal { + +class QBS_AUTOTEST_EXPORT FileInfo +{ +public: + FileInfo(const QString &fileName); + + bool exists() const; + FileTime lastModified() const; + FileTime lastStatusChange() const; + bool isDir() const; + + static QString fileName(const QString &fp); + static QString baseName(const QString &fp); + static QString completeBaseName(const QString &fp); + static QString suffix(const QString &fp); + static QString completeSuffix(const QString &fp); + static QString path(const QString &fp, HostOsInfo::HostOs hostOs = HostOsInfo::hostOs()); + static void splitIntoDirectoryAndFileName(const QString &filePath, QString *dirPath, QString *fileName); + static void splitIntoDirectoryAndFileName(const QString &filePath, QStringRef *dirPath, QStringRef *fileName); + static bool exists(const QString &fp); + static bool isAbsolute(const QString &fp, HostOsInfo::HostOs hostOs = HostOsInfo::hostOs()); + static bool isPattern(const QStringRef &str); + static bool isPattern(const QString &str); + static QString resolvePath(const QString &base, const QString &rel, + HostOsInfo::HostOs hostOs = HostOsInfo::hostOs()); + static bool globMatches(const QRegExp &pattern, const QString &subject); + static bool isFileCaseCorrect(const QString &filePath); + + // Symlink-correct check. + static bool fileExists(const QFileInfo &fi); + +private: +#if defined(Q_OS_WIN) + struct InternalStatType + { + quint8 z[36]; + }; +#elif defined(Q_OS_UNIX) + using InternalStatType = struct stat; +#else +# error unknown platform +#endif + InternalStatType m_stat{}; +}; + +bool removeFileRecursion(const QFileInfo &f, QString *errorMessage); + +// FIXME: Used by tests. +bool QBS_EXPORT removeDirectoryWithContents(const QString &path, QString *errorMessage); +bool QBS_EXPORT copyFileRecursion(const QString &sourcePath, const QString &targetPath, + bool preserveSymLinks, bool copyDirectoryContents, QString *errorMessage); + +} // namespace Internal +} // namespace qbs + +#endif diff --git a/src/lib/corelib/tools/filesaver.cpp b/src/lib/corelib/tools/filesaver.cpp new file mode 100644 index 00000000..5a0a68c1 --- /dev/null +++ b/src/lib/corelib/tools/filesaver.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** 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 "filesaver.h" +#include "stlutils.h" + +#include + +#include +#include + +namespace qbs { +namespace Internal { + +FileSaver::FileSaver(std::string filePath, bool overwriteIfUnchanged) + : m_filePath(std::move(filePath)), m_overwriteIfUnchanged(overwriteIfUnchanged) +{ +} + +std::ostream *FileSaver::device() +{ + return m_memoryDevice.get(); +} + +bool FileSaver::open() +{ + if (!m_overwriteIfUnchanged) { + std::ifstream file(utf8_to_native_path(m_filePath)); + if (file.is_open()) + m_oldFileContents.assign(std::istreambuf_iterator(file), + std::istreambuf_iterator()); + else + m_oldFileContents.clear(); + } + + m_memoryDevice = std::make_shared(); + return true; +} + +bool FileSaver::commit() +{ + if (!device()) + return false; + + device()->flush(); + if (!device()->good()) + return false; + + const std::string newFileContents = m_memoryDevice->str(); + if (!m_overwriteIfUnchanged && m_oldFileContents == newFileContents) + return true; // no need to write unchanged data + + const std::string tempFilePath = m_filePath + "~"; + std::ofstream tempFile(utf8_to_native_path(tempFilePath)); + if (!tempFile.is_open()) + return false; + + tempFile.write(newFileContents.data(), newFileContents.size()); + tempFile.close(); + if (!tempFile.good()) + return false; + + if (Internal::rename(tempFilePath, m_filePath) != 0) { + if (errno != EEXIST) + return false; + if (Internal::unlink(m_filePath) != 0) + return false; + return Internal::rename(tempFilePath, m_filePath) == 0; + } + + return true; +} + +bool FileSaver::write(const std::vector &data) +{ + return fwrite(data, device()); +} + +bool FileSaver::write(const std::string &data) +{ + return fwrite(data, device()); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/filesaver.h b/src/lib/corelib/tools/filesaver.h new file mode 100644 index 00000000..8b4c0166 --- /dev/null +++ b/src/lib/corelib/tools/filesaver.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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 FILESAVER_H +#define FILESAVER_H + +#include "qbs_export.h" + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +/*! + * QSaveFile wrapper which doesn't update the target file if the contents are unchanged. + */ +class QBS_EXPORT FileSaver { +public: + FileSaver(std::string filePath, bool overwriteIfUnchanged = false); + + std::ostream *device(); + bool open(); + bool commit(); + bool write(const std::vector &data); + bool write(const std::string &data); + +private: + std::string m_oldFileContents; + std::shared_ptr m_memoryDevice; + const std::string m_filePath; + const bool m_overwriteIfUnchanged; +}; + +} // namespace Internal +} // namespace qbs + +#endif // FILESAVER_H diff --git a/src/lib/corelib/tools/filetime.cpp b/src/lib/corelib/tools/filetime.cpp new file mode 100644 index 00000000..d115075a --- /dev/null +++ b/src/lib/corelib/tools/filetime.cpp @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** 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 "filetime.h" + +#include + +#ifdef Q_OS_WIN +#include +#else +#include +#endif + +namespace qbs { +namespace Internal { + +#ifdef APPLE_CUSTOM_CLOCK_GETTIME +#include + +#ifndef CLOCK_REALTIME +#define CLOCK_REALTIME 0 +#endif + +// clk_id isn't used, only the CLOCK_REALTIME case is implemented. +int clock_gettime(int /*clk_id*/, struct timespec *t) +{ + struct timeval tv; + // Resolution of gettimeofday is 1000nsecs = 1 microsecond. + int ret = gettimeofday(&tv, NULL); + t->tv_sec = tv.tv_sec; + t->tv_nsec = tv.tv_usec * 1000; + return ret; +} +#endif + +FileTime::FileTime() +{ +#ifdef Q_OS_WIN + static_assert(sizeof(FileTime::InternalType) == sizeof(FILETIME), + "FileTime::InternalType has wrong size."); + m_fileTime = 0; +#elif HAS_CLOCK_GETTIME + m_fileTime = {0, 0}; +#else + m_fileTime = 0; +#endif +} + +FileTime::FileTime(const FileTime::InternalType &ft) : m_fileTime(ft) +{ +#if HAS_CLOCK_GETTIME + if (m_fileTime.tv_sec == 0) + m_fileTime.tv_nsec = 0; // stat() sets only the first member to 0 for non-existing files. +#endif +} + +int FileTime::compare(const FileTime &other) const +{ +#ifdef Q_OS_WIN + auto const t1 = reinterpret_cast(&m_fileTime); + auto const t2 = reinterpret_cast(&other.m_fileTime); + return CompareFileTime(t1, t2); +#elif HAS_CLOCK_GETTIME + if (m_fileTime.tv_sec < other.m_fileTime.tv_sec) + return -1; + if (m_fileTime.tv_sec > other.m_fileTime.tv_sec) + return 1; + if (m_fileTime.tv_nsec < other.m_fileTime.tv_nsec) + return -1; + if (m_fileTime.tv_nsec > other.m_fileTime.tv_nsec) + return 1; + return 0; +#else + if (m_fileTime < other.m_fileTime) + return -1; + if (m_fileTime > other.m_fileTime) + return 1; + return 0; +#endif +} + +void FileTime::clear() +{ +#if HAS_CLOCK_GETTIME + m_fileTime = { 0, 0 }; +#else + m_fileTime = 0; +#endif +} + +bool FileTime::isValid() const +{ + return *this != FileTime(); +} + +FileTime FileTime::currentTime() +{ +#ifdef Q_OS_WIN + FileTime result; + SYSTEMTIME st; + GetSystemTime(&st); + auto const ft = reinterpret_cast(&result.m_fileTime); + SystemTimeToFileTime(&st, ft); + return result; +#elif defined APPLE_CUSTOM_CLOCK_GETTIME + InternalType t; + // Explicitly use our custom version, so that we don't get an additional unresolved symbol on a + // system that actually provides one, but isn't used due to the minimium deployment target + // being lower. + qbs::Internal::clock_gettime(CLOCK_REALTIME, &t); + return t; +#elif HAS_CLOCK_GETTIME + InternalType t; + clock_gettime(CLOCK_REALTIME, &t); + return t; +#else + return time(nullptr); +#endif +} + +FileTime FileTime::oldestTime() +{ +#ifdef Q_OS_WIN + SYSTEMTIME st = { + 1601, + 1, + 5, + 2, + 0, + 0, + 0, + 0 + }; + FileTime result; + auto const ft = reinterpret_cast(&result.m_fileTime); + SystemTimeToFileTime(&st, ft); + return result; +#elif HAS_CLOCK_GETTIME + return FileTime({1, 0}); +#else + return 1; +#endif +} + +double FileTime::asDouble() const +{ +#if HAS_CLOCK_GETTIME + return static_cast(m_fileTime.tv_sec); +#else + return static_cast(m_fileTime); +#endif +} + +QString FileTime::toString() const +{ +#ifdef Q_OS_WIN + auto const ft = reinterpret_cast(&m_fileTime); + SYSTEMTIME stUTC, stLocal; + FileTimeToSystemTime(ft, &stUTC); + SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &stLocal); + const QString result = QStringLiteral("%1.%2.%3 %4:%5:%6") + .arg(stLocal.wDay, 2, 10, QLatin1Char('0')).arg(stLocal.wMonth, 2, 10, QLatin1Char('0')).arg(stLocal.wYear) + .arg(stLocal.wHour, 2, 10, QLatin1Char('0')).arg(stLocal.wMinute, 2, 10, QLatin1Char('0')).arg(stLocal.wSecond, 2, 10, QLatin1Char('0')); + return result; +#else + QDateTime dt; +#if HAS_CLOCK_GETTIME + dt.setMSecsSinceEpoch(m_fileTime.tv_sec * 1000 + m_fileTime.tv_nsec / 1000000); +#else + dt.setTime_t(m_fileTime); +#endif + return dt.toString(Qt::ISODateWithMs); +#endif +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/filetime.h b/src/lib/corelib/tools/filetime.h new file mode 100644 index 00000000..f9a15f79 --- /dev/null +++ b/src/lib/corelib/tools/filetime.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** 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 QBS_FILETIME_H +#define QBS_FILETIME_H + +#include "persistence.h" + +#include +#include + +#if defined(Q_OS_UNIX) && !defined(__APPLE__) +#include +#define HAS_CLOCK_GETTIME (_POSIX_C_SOURCE >= 199309L) +#endif // Q_OS_UNIX + +#ifdef __APPLE__ + +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 +// macOS 10.12+ ships clock_gettime. +#else +// We implement our own clock_gettime. +#define APPLE_CUSTOM_CLOCK_GETTIME 1 +#endif // __MAC_OS_X_VERSION_MIN_REQUIRED + +// Either way we have a clock_gettime in the end. +#define HAS_CLOCK_GETTIME 1 + +// Apple stat struct has slightly different names for time fields. +#define APPLE_STAT_TIMESPEC 1 + +#endif // __APPLE__ + +namespace qbs { +namespace Internal { + +class QBS_AUTOTEST_EXPORT FileTime +{ +public: +#if defined(Q_OS_UNIX) +#if HAS_CLOCK_GETTIME + using InternalType = timespec; +#else + using InternalType = time_t; +#endif // HAS_CLOCK_GETTIME +#elif defined(Q_OS_WIN) + using InternalType = quint64; +#else +# error unknown platform +#endif + + FileTime(); + FileTime(const InternalType &ft); + + bool operator<(const FileTime &rhs) const { return compare(rhs) < 0; } + bool operator>(const FileTime &rhs) const { return compare(rhs) > 0; } + bool operator<=(const FileTime &rhs) const { return !operator>(rhs); } + bool operator>=(const FileTime &rhs) const { return !operator<(rhs); } + bool operator==(const FileTime &rhs) const { return compare(rhs) == 0; } + bool operator!= (const FileTime &rhs) const { return !operator==(rhs); } + int compare(const FileTime &other) const; + + void clear(); + bool isValid() const; + QString toString() const; + + static FileTime currentTime(); + static FileTime oldestTime(); + + double asDouble() const; + + template void completeSerializationOp(PersistentPool &pool) + { +#if HAS_CLOCK_GETTIME + pool.serializationOp(m_fileTime.tv_sec, m_fileTime.tv_nsec); +#else + pool.serializationOp(m_fileTime); +#endif + } + +private: + InternalType m_fileTime{}; +}; + +} // namespace Internal +} // namespace qbs + +QT_BEGIN_NAMESPACE + +inline QDebug operator<<(QDebug dbg, const qbs::Internal::FileTime &t) +{ + dbg.nospace() << t.toString(); + return dbg.space(); +} + +QT_END_NAMESPACE + +#endif // QBS_FILETIME_H diff --git a/src/lib/corelib/tools/generateoptions.cpp b/src/lib/corelib/tools/generateoptions.cpp new file mode 100644 index 00000000..596e0acb --- /dev/null +++ b/src/lib/corelib/tools/generateoptions.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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 "generateoptions.h" + +#include +#include + +namespace qbs { +namespace Internal { + +class GenerateOptionsPrivate : public QSharedData +{ +public: + GenerateOptionsPrivate() + : generatorName() + {} + + QString generatorName; +}; + +} // namespace Internal + +/*! + * \class GenerateOptions + * \brief The \c GenerateOptions class comprises parameters that influence the behavior of + * generate operations. + */ + +GenerateOptions::GenerateOptions() : d(new Internal::GenerateOptionsPrivate) +{ +} + +GenerateOptions::GenerateOptions(const GenerateOptions &other) = default; + +GenerateOptions &GenerateOptions::operator=(const GenerateOptions &other) = default; + +GenerateOptions::~GenerateOptions() = default; + +/*! + * Returns the name of the generator used to create the external build system files. + * The default is empty. + */ +QString GenerateOptions::generatorName() const +{ + return d->generatorName; +} + +/*! + * \brief Sets the name of the generator used to create the external build system files. + */ +void GenerateOptions::setGeneratorName(const QString &generatorName) +{ + d->generatorName = generatorName; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/generateoptions.h b/src/lib/corelib/tools/generateoptions.h new file mode 100644 index 00000000..869b9a57 --- /dev/null +++ b/src/lib/corelib/tools/generateoptions.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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_GENERATEOPTIONS_H +#define QBS_GENERATEOPTIONS_H + +#include "qbs_export.h" + +#include + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { class GenerateOptionsPrivate; } + +class QBS_EXPORT GenerateOptions +{ +public: + GenerateOptions(); + GenerateOptions(const GenerateOptions &other); + GenerateOptions &operator=(const GenerateOptions &other); + ~GenerateOptions(); + + QString generatorName() const; + void setGeneratorName(const QString &generatorName); + +private: + QSharedDataPointer d; +}; + +} // namespace qbs + +#endif // QBS_GENERATEOPTIONS_H diff --git a/src/lib/corelib/tools/hostosinfo.h b/src/lib/corelib/tools/hostosinfo.h new file mode 100644 index 00000000..4bfc3e00 --- /dev/null +++ b/src/lib/corelib/tools/hostosinfo.h @@ -0,0 +1,265 @@ +/**************************************************************************** +** +** 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 QBS_HOSTOSINFO_H +#define QBS_HOSTOSINFO_H + +#include "qbs_export.h" +#include "stlutils.h" +#include "version.h" + +#include +#include +#include +#include +#include + +#if defined(Q_OS_WIN) +#define QBS_HOST_EXE_SUFFIX ".exe" +#define QBS_HOST_DYNAMICLIB_PREFIX "" +#define QBS_HOST_DYNAMICLIB_SUFFIX ".dll" +#elif defined(Q_OS_DARWIN) +#define QBS_HOST_EXE_SUFFIX "" +#define QBS_HOST_DYNAMICLIB_PREFIX "lib" +#define QBS_HOST_DYNAMICLIB_SUFFIX ".dylib" +#else +#define QBS_HOST_EXE_SUFFIX "" +#define QBS_HOST_DYNAMICLIB_PREFIX "lib" +#define QBS_HOST_DYNAMICLIB_SUFFIX ".so" +#endif // Q_OS_WIN + +namespace qbs { +namespace Internal { + +class HostOsInfo +{ +public: + // Add more as needed. + enum HostOs { HostOsWindows, HostOsLinux, HostOsMacos, HostOsOtherUnix, HostOsOther }; + + static inline std::string hostOSIdentifier(); + static inline std::string hostOSArchitecture(); + static inline std::vector hostOSIdentifiers(); + static inline std::vector canonicalOSIdentifiers(const std::string &os); + static inline HostOs hostOs(); + + static inline Version hostOsVersion() { + Version v; + if (HostOsInfo::isWindowsHost()) { + QSettings settings(QStringLiteral("HKEY_LOCAL_MACHINE\\Software\\" + "Microsoft\\Windows NT\\CurrentVersion"), + QSettings::NativeFormat); + v = v.fromString(settings.value(QStringLiteral("CurrentVersion")).toString() + + QLatin1Char('.') + + settings.value(QStringLiteral("CurrentBuildNumber")).toString()); + Q_ASSERT(v.isValid()); + } else if (HostOsInfo::isMacosHost()) { + QSettings settings(QStringLiteral("/System/Library/CoreServices/SystemVersion.plist"), + QSettings::NativeFormat); + v = v.fromString(settings.value(QStringLiteral("ProductVersion")).toString()); + Q_ASSERT(v.isValid()); + } + return v; + } + + static bool isWindowsHost() { return hostOs() == HostOsWindows; } + static bool isLinuxHost() { return hostOs() == HostOsLinux; } + static bool isMacosHost() { return hostOs() == HostOsMacos; } + static inline bool isAnyUnixHost(); + static inline QString rfc1034Identifier(const QString &str); + + static QString appendExecutableSuffix(const QString &executable) + { + QString finalName = executable; + if (isWindowsHost()) + finalName += QLatin1String(QBS_HOST_EXE_SUFFIX); + return finalName; + } + + static QString stripExecutableSuffix(const QString &executable) + { + constexpr QLatin1String suffix(QBS_HOST_EXE_SUFFIX, sizeof(QBS_HOST_EXE_SUFFIX) - 1); + return !suffix.isEmpty() && executable.endsWith(suffix) + ? executable.chopped(suffix.size()) : executable; + } + + static QString dynamicLibraryName(const QString &libraryBaseName) + { + return QLatin1String(QBS_HOST_DYNAMICLIB_PREFIX) + libraryBaseName + + QLatin1String(QBS_HOST_DYNAMICLIB_SUFFIX); + } + + static Qt::CaseSensitivity fileNameCaseSensitivity() + { + return isWindowsHost() ? Qt::CaseInsensitive: Qt::CaseSensitive; + } + + static QString libraryPathEnvironmentVariable() + { + if (isWindowsHost()) + return QStringLiteral("PATH"); + if (isMacosHost()) + return QStringLiteral("DYLD_LIBRARY_PATH"); + return QStringLiteral("LD_LIBRARY_PATH"); + } + + static QChar pathListSeparator(HostOsInfo::HostOs hostOs = HostOsInfo::hostOs()) + { + return hostOs == HostOsWindows ? QLatin1Char(';') : QLatin1Char(':'); + } + + static QChar pathSeparator(HostOsInfo::HostOs hostOs = HostOsInfo::hostOs()) + { + return hostOs == HostOsWindows ? QLatin1Char('\\') : QLatin1Char('/'); + } + + static Qt::KeyboardModifier controlModifier() + { + return isMacosHost() ? Qt::MetaModifier : Qt::ControlModifier; + } +}; + +std::string HostOsInfo::hostOSIdentifier() +{ +#if defined(__APPLE__) + return "macos"; +#elif defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + return "windows"; +#elif defined(_AIX) + return "aix"; +#elif defined(hpux) || defined(__hpux) + return "hpux"; +#elif defined(__sun) || defined(sun) + return "solaris"; +#elif defined(__linux__) || defined(__linux) + return "linux"; +#elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) + return "freebsd"; +#elif defined(__NetBSD__) + return "netbsd"; +#elif defined(__OpenBSD__) + return "openbsd"; +#elif defined(__GNU__) + return "hurd"; +#elif defined(__HAIKU__) + return "haiku"; +#else + #warning "Qbs has not been ported to this OS - see http://qbs.io/" + return ""; +#endif +} + +std::string HostOsInfo::hostOSArchitecture() +{ + const auto cpuArch = QSysInfo::currentCpuArchitecture(); + if (cpuArch == QLatin1String("i386")) + return "x86"; + return cpuArch.toStdString(); +} + +std::vector HostOsInfo::hostOSIdentifiers() +{ + return canonicalOSIdentifiers(hostOSIdentifier()); +} + +std::vector HostOsInfo::canonicalOSIdentifiers(const std::string &name) +{ + std::vector list { name }; + if (contains({"ios-simulator"}, name)) + list << canonicalOSIdentifiers("ios"); + if (contains({"tvos-simulator"}, name)) + list << canonicalOSIdentifiers("tvos"); + if (contains({"watchos-simulator"}, name)) + list << canonicalOSIdentifiers("watchos"); + if (contains({"macos", "ios", "tvos", "watchos"}, name)) + list << canonicalOSIdentifiers("darwin"); + if (contains({"darwin", "freebsd", "netbsd", "openbsd"}, name)) + list << canonicalOSIdentifiers("bsd"); + if (contains({"android"}, name)) + list << canonicalOSIdentifiers("linux"); + + // Note: recognized non-Unix platforms include: windows, haiku, vxworks + if (contains({"bsd", "aix", "hpux", "solaris", "linux", "hurd", "qnx", "integrity"}, name)) + list << canonicalOSIdentifiers("unix"); + + return list; +} + +HostOsInfo::HostOs HostOsInfo::hostOs() +{ +#if defined(Q_OS_WIN) + return HostOsWindows; +#elif defined(Q_OS_LINUX) + return HostOsLinux; +#elif defined(Q_OS_DARWIN) + return HostOsMacos; +#elif defined(Q_OS_UNIX) + return HostOsOtherUnix; +#else + return HostOsOther; +#endif +} + +bool HostOsInfo::isAnyUnixHost() +{ +#ifdef Q_OS_UNIX + return true; +#else + return false; +#endif +} + +QString HostOsInfo::rfc1034Identifier(const QString &str) +{ + QString s = str; + for (QChar &ch : s) { + const char c = ch.toLatin1(); + + const bool okChar = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') || c == '-' || c == '.'; + if (!okChar) + ch = QChar::fromLatin1('-'); + } + return s; +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_HOSTOSINFO_H diff --git a/src/lib/corelib/tools/id.cpp b/src/lib/corelib/tools/id.cpp new file mode 100644 index 00000000..33cfd60f --- /dev/null +++ b/src/lib/corelib/tools/id.cpp @@ -0,0 +1,285 @@ +/**************************************************************************** +** +** 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 "id.h" +#include "qbsassert.h" + +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { + +/*! + \class qbs::Internal::Id + + \brief The class Id encapsulates an identifier that is unique + within a specific running process, using the qbs library. + + \c{Id} is used as facility to identify objects of interest + in a more typesafe and faster manner than a plain \c QString or + \c QByteArray would provide. + + An id is internally represented as a 32 bit integer (its \c UID) + and associated with a plain 7-bit-clean ASCII name used + for display and persistency. + + This class is copied from Qt Creator. +*/ + +class StringHolder +{ +public: + StringHolder() = default; + + StringHolder(const char *s, int length) + : n(length), str(s) + { + if (!n) + length = n = qstrlen(s); + h = 0; + while (length--) { + h = (h << 4) + *s++; + h ^= (h & 0xf0000000) >> 23; + h &= 0x0fffffff; + } + } + int n = 0; + const char *str = nullptr; + uint h = 0; +}; + +static bool operator==(const StringHolder &sh1, const StringHolder &sh2) +{ + // sh.n is unlikely to discriminate better than the hash. + return sh1.h == sh2.h && sh1.str && sh2.str && strcmp(sh1.str, sh2.str) == 0; +} + + +static uint qHash(const StringHolder &sh) +{ + return sh.h; +} + +struct IdCache : public QHash +{ +#ifndef QBS_ALLOW_STATIC_LEAKS + ~IdCache() + { + for (IdCache::iterator it = begin(); it != end(); ++it) + delete[](const_cast(it.key().str)); + } +#endif +}; + + +static int firstUnusedId = Id::IdsPerPlugin * Id::ReservedPlugins; + +static QHash stringFromId; +static IdCache idFromString; +static std::mutex mutex; + +static QByteArray getStringFromId(int id) +{ + std::lock_guard lock(mutex); + return stringFromId.value(id).str; +} + +static int theId(const char *str, int n = 0) +{ + QBS_ASSERT(str && *str, return 0); + StringHolder sh(str, n); + std::lock_guard lock(mutex); + int res = idFromString.value(sh, 0); + if (res == 0) { + res = firstUnusedId++; + sh.str = qstrdup(sh.str); + idFromString[sh] = res; + stringFromId[res] = sh; + } + return res; +} + +static int theId(const QByteArray &ba) +{ + return theId(ba.constData(), ba.size()); +} + +/*! + \fn qbs::Internal::Id(int uid) + + \brief Constructs an id given a UID. + + The UID is an integer value that is unique within the running + process. + + It is the callers responsibility to ensure the uniqueness of + the passed integer. The recommended approach is to use + \c{registerId()} with an value taken from the plugin's + private range. + + \sa registerId() + +*/ + +/*! + Constructs an id given its associated name. The internal + representation will be unspecified, but consistent within a + process. + +*/ +Id::Id(const char *name) + : m_id(theId(name, 0)) +{} + +/*! + \overload + +*/ +Id::Id(const QByteArray &name) + : m_id(theId(name)) +{} + +/*! + Returns an internal representation of the id. +*/ + +QByteArray Id::name() const +{ + return getStringFromId(m_id); +} + +/*! + Returns a string representation of the id suitable + for UI display. + + This should not be used to create a persistent version + of the Id, use \c{toSetting()} instead. + + \sa fromString(), toSetting() +*/ + +QString Id::toString() const +{ + return QString::fromUtf8(getStringFromId(m_id)); +} + +/*! + Returns a persistent value representing the id which is + suitable to be stored in QSettings. + + \sa fromSetting() +*/ + +QVariant Id::toSetting() const +{ + return QString::fromUtf8(getStringFromId(m_id)); +} + +/*! + Reconstructs an id from a persistent value. + + \sa toSetting() +*/ + +Id Id::fromSetting(const QVariant &variant) +{ + const QByteArray ba = variant.toString().toUtf8(); + if (ba.isEmpty()) + return {}; + return {theId(ba)}; +} + +/*! + Constructs a derived id. + + This can be used to construct groups of ids logically + belonging together. The associated internal name + will be generated by appending \c{suffix}. +*/ + +Id Id::withSuffix(int suffix) const +{ + const QByteArray ba = name() + QByteArray::number(suffix); + return {ba.constData()}; +} + +/*! + \overload +*/ + +Id Id::withSuffix(const char *suffix) const +{ + const QByteArray ba = name() + suffix; + return {ba.constData()}; +} + +/*! + Constructs a derived id. + + This can be used to construct groups of ids logically + belonging together. The associated internal name + will be generated by prepending \c{prefix}. +*/ + +Id Id::withPrefix(const char *prefix) const +{ + const QByteArray ba = prefix + name(); + return {ba.constData()}; +} + +bool Id::operator==(const char *name) const +{ + const auto string = getStringFromId(m_id); + if (!string.isNull() && name) + return strcmp(string.data(), name) == 0; + else + return false; +} + +bool Id::alphabeticallyBefore(Id other) const +{ + return toString().compare(other.toString(), Qt::CaseInsensitive) < 0; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/id.h b/src/lib/corelib/tools/id.h new file mode 100644 index 00000000..aa327833 --- /dev/null +++ b/src/lib/corelib/tools/id.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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 QBS_TOOLS_ID_H +#define QBS_TOOLS_ID_H + +#include "qbs_export.h" + +#include +#include +#include + +namespace qbs { +namespace Internal { + +class QBS_AUTOTEST_EXPORT Id +{ +public: + enum { IdsPerPlugin = 10000, ReservedPlugins = 1000 }; + + Id() : m_id(0) {} + Id(int uid) : m_id(uid) {} + Id(const char *name); + explicit Id(const QByteArray &name); + + Id withSuffix(int suffix) const; + Id withSuffix(const char *name) const; + Id withPrefix(const char *name) const; + + QByteArray name() const; + QString toString() const; // Avoid. + QVariant toSetting() const; // Good to use. + bool isValid() const { return m_id; } + bool operator==(Id id) const { return m_id == id.m_id; } + bool operator==(const char *name) const; + bool operator!=(Id id) const { return m_id != id.m_id; } + bool operator!=(const char *name) const { return !operator==(name); } + bool operator<(Id id) const { return m_id < id.m_id; } + bool operator>(Id id) const { return m_id > id.m_id; } + bool alphabeticallyBefore(Id other) const; + int uniqueIdentifier() const { return m_id; } + static Id fromUniqueIdentifier(int uid) { return {uid}; } + static Id fromSetting(const QVariant &variant); // Good to use. + +private: + // Intentionally unimplemented + Id(const QLatin1String &); + int m_id; +}; + +inline uint qHash(const Id &id) { return id.uniqueIdentifier(); } + +} // namespace Internal +} // namespace qbs + +Q_DECLARE_METATYPE(qbs::Internal::Id) +Q_DECLARE_METATYPE(QList) + +#endif // QBS_TOOLS_ID_H diff --git a/src/lib/corelib/tools/installoptions.cpp b/src/lib/corelib/tools/installoptions.cpp new file mode 100644 index 00000000..5e112e6d --- /dev/null +++ b/src/lib/corelib/tools/installoptions.cpp @@ -0,0 +1,242 @@ +/**************************************************************************** +** +** 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 "installoptions.h" + +#include "jsonhelper.h" +#include "stringconstants.h" + +#include + +#include +#include + +namespace qbs { +namespace Internal { + +class InstallOptionsPrivate : public QSharedData +{ +public: + InstallOptionsPrivate() + : useSysroot(false), removeExisting(false), dryRun(false), + keepGoing(false), logElapsedTime(false) + {} + + QString installRoot; + bool useSysroot; + bool removeExisting; + bool dryRun; + bool keepGoing; + bool logElapsedTime; +}; + +QString effectiveInstallRoot(const InstallOptions &options, const TopLevelProject *project) +{ + const QString installRoot = options.installRoot(); + if (!installRoot.isEmpty()) + return installRoot; + + if (options.installIntoSysroot()) { + return project->buildConfiguration().value(StringConstants::qbsModule()).toMap() + .value(QStringLiteral("sysroot")).toString(); + } + return project->buildConfiguration().value(StringConstants::qbsModule()).toMap() + .value(StringConstants::installRootProperty()).toString(); +} + +} // namespace Internal + +/*! + * \class InstallOptions + * \brief The \c InstallOptions class comprises parameters that influence the behavior of + * install operations. + */ + +InstallOptions::InstallOptions() : d(new Internal::InstallOptionsPrivate) +{ +} + +InstallOptions::InstallOptions(const InstallOptions &other) = default; + +InstallOptions::InstallOptions(InstallOptions &&other) Q_DECL_NOEXCEPT = default; + +InstallOptions &InstallOptions::operator=(const InstallOptions &other) = default; + +InstallOptions &InstallOptions::operator=(InstallOptions &&other) Q_DECL_NOEXCEPT = default; + +InstallOptions::~InstallOptions() = default; + +/*! + * \brief The default install root, relative to the build directory. + */ +QString InstallOptions::defaultInstallRoot() +{ + return QStringLiteral("install-root"); +} + +/*! + * Returns the base directory for the installation. + * The \c qbs.installPrefix path is relative to this root. If the string is empty, either the value of + * qbs.sysroot or "/install-root" will be used, depending on what \c installIntoSysroot() + * returns. + * The default is empty. + */ +QString InstallOptions::installRoot() const +{ + return d->installRoot; +} + +/*! + * \brief Sets the base directory for the installation. + * \note The argument must either be an empty string or an absolute path to a directory + * (which might not yet exist, in which case it will be created). + */ +void InstallOptions::setInstallRoot(const QString &installRoot) +{ + d->installRoot = QDir::cleanPath(installRoot); + if (!QDir(installRoot).isRoot()) { + while (d->installRoot.endsWith(QLatin1Char('/'))) + d->installRoot.chop(1); + } +} + +/*! + * Returns whether to use the sysroot as the default install root. + * The default is false. + */ +bool InstallOptions::installIntoSysroot() const +{ + return d->useSysroot; +} + +void InstallOptions::setInstallIntoSysroot(bool useSysroot) +{ + d->useSysroot = useSysroot; +} + +/*! + * \brief Returns true iff an existing installation will be removed prior to installing. + * The default is false. + */ +bool InstallOptions::removeExistingInstallation() const +{ + return d->removeExisting; +} + +/*! + * Controls whether to remove an existing installation before installing. + * \note qbs may do some safety checks and refuse to remove certain directories such as + * a user's home directory. You should still be careful with this option, since it + * deletes recursively. + */ +void InstallOptions::setRemoveExistingInstallation(bool removeExisting) +{ + d->removeExisting = removeExisting; +} + +/*! + * \brief Returns true iff qbs will not actually copy any files, but just show what would happen. + * The default is false. + */ +bool InstallOptions::dryRun() const +{ + return d->dryRun; +} + +/*! + * \brief Controls whether installation will actually take place. + * If the argument is true, then qbs will emit information about which files would be copied + * instead of actually doing it. + */ +void InstallOptions::setDryRun(bool dryRun) +{ + d->dryRun = dryRun; +} + +/*! + * Returns true iff installation will continue if an error occurs. + * The default is false. + */ +bool InstallOptions::keepGoing() const +{ + return d->keepGoing; +} + +/*! + * \brief Controls whether to abort on errors. + * If the argument is true, then if a file cannot be copied e.g. due to a permission problem, + * a warning will be printed and the installation will continue. If the argument is false, + * then the installation will abort immediately in case of an error. + */ +void InstallOptions::setKeepGoing(bool keepGoing) +{ + d->keepGoing = keepGoing; +} + +/*! + * \brief Returns true iff the time the operation takes will be logged. + * The default is false. + */ +bool InstallOptions::logElapsedTime() const +{ + return d->logElapsedTime; +} + +/*! + * \brief Controls whether the installation time will be measured and logged. + */ +void InstallOptions::setLogElapsedTime(bool logElapsedTime) +{ + d->logElapsedTime = logElapsedTime; +} + +qbs::InstallOptions qbs::InstallOptions::fromJson(const QJsonObject &data) +{ + using namespace Internal; + InstallOptions opt; + setValueFromJson(opt.d->installRoot, data, "install-root"); + setValueFromJson(opt.d->useSysroot, data, "use-sysroot"); + setValueFromJson(opt.d->removeExisting, data, "clean-install-root"); + setValueFromJson(opt.d->dryRun, data, "dry-run"); + setValueFromJson(opt.d->keepGoing, data, "keep-going"); + setValueFromJson(opt.d->logElapsedTime, data, "log-time"); + return opt; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/installoptions.h b/src/lib/corelib/tools/installoptions.h new file mode 100644 index 00000000..16511aa3 --- /dev/null +++ b/src/lib/corelib/tools/installoptions.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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 QBS_INSTALLOPTIONS_H +#define QBS_INSTALLOPTIONS_H + +#include "qbs_export.h" + +#include + +QT_BEGIN_NAMESPACE +class QJsonObject; +class QString; +QT_END_NAMESPACE + +namespace qbs { +class InstallOptions; +namespace Internal { +class InstallOptionsPrivate; +class TopLevelProject; +QString effectiveInstallRoot(const InstallOptions &options, const TopLevelProject *project); +} + +class QBS_EXPORT InstallOptions +{ +public: + InstallOptions(); + InstallOptions(const InstallOptions &other); + InstallOptions(InstallOptions &&other) Q_DECL_NOEXCEPT; + InstallOptions &operator=(const InstallOptions &other); + InstallOptions &operator=(InstallOptions &&other) Q_DECL_NOEXCEPT; + ~InstallOptions(); + + static InstallOptions fromJson(const QJsonObject &data); + + static QString defaultInstallRoot(); + QString installRoot() const; + void setInstallRoot(const QString &installRoot); + + bool installIntoSysroot() const; + void setInstallIntoSysroot(bool useSysroot); + + bool removeExistingInstallation() const; + void setRemoveExistingInstallation(bool removeExisting); + + bool dryRun() const; + void setDryRun(bool dryRun); + + bool keepGoing() const; + void setKeepGoing(bool keepGoing); + + bool logElapsedTime() const; + void setLogElapsedTime(bool logElapsedTime); + +private: + QSharedDataPointer d; +}; + +} // namespace qbs + +#endif // QBS_INSTALLOPTIONS_H diff --git a/src/lib/corelib/tools/iosutils.h b/src/lib/corelib/tools/iosutils.h new file mode 100644 index 00000000..1a5faf3c --- /dev/null +++ b/src/lib/corelib/tools/iosutils.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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 +#include +#include + +#if defined(_WIN32) && defined(_MSC_VER) +#include +#include +#define QBS_RENAME_IMPL ::_wrename +#define QBS_UNLINK_IMPL ::_wunlink +using qbs_filesystem_path_string_type = std::wstring; +#else +#include +#define QBS_RENAME_IMPL ::rename +#define QBS_UNLINK_IMPL ::unlink +using qbs_filesystem_path_string_type = std::string; +#endif + +namespace qbs { +namespace Internal { + +static inline bool fwrite(const char *values, size_t nitems, std::ostream *stream) +{ + if (!stream) + return false; + stream->write(values, nitems); + return stream->good(); +} + +template +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) && defined(_MSC_VER) + std::wstring_convert> converter; + return converter.from_bytes(str); +#else + return str; +#endif +} + +static inline 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()); +} + +static inline int unlink(const std::string &name) +{ + const auto wName = utf8_to_native_path(name); + return QBS_UNLINK_IMPL(wName.c_str()); +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_IOSUTILS_H diff --git a/src/lib/corelib/tools/joblimits.cpp b/src/lib/corelib/tools/joblimits.cpp new file mode 100644 index 00000000..54c8c98b --- /dev/null +++ b/src/lib/corelib/tools/joblimits.cpp @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "joblimits.h" + +#include + +#include +#include + +namespace qbs { +namespace Internal { + +static int transformLimit(int limitFromUser) +{ + return limitFromUser == 0 + ? std::numeric_limits::max() + : limitFromUser < -1 ? -1 + : limitFromUser; +} + +class JobLimitPrivate : public QSharedData +{ +public: + JobLimitPrivate(const QString &pool, int limit) + : jobLimit(std::make_pair(pool, transformLimit(limit))) + { + } + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(jobLimit); + } + std::pair jobLimit; +}; + +class JobLimitsPrivate : public QSharedData +{ +public: + template void serializationOp(PersistentPool &pool) + { + pool.serializationOp(jobLimits); + } + std::vector jobLimits; +}; + +} // namespace Internal + +JobLimit::JobLimit() : JobLimit(QString(), -1) +{ +} +JobLimit::JobLimit(const QString &pool, int limit) : d(new Internal::JobLimitPrivate(pool, limit)) +{ +} +JobLimit::JobLimit(const JobLimit &other) = default; +JobLimit &JobLimit::operator=(const JobLimit &other) = default; +JobLimit::~JobLimit() = default; +QString JobLimit::pool() const { return d->jobLimit.first; } +int JobLimit::limit() const { return d->jobLimit.second; } + +void JobLimit::load(Internal::PersistentPool &pool) +{ + d->serializationOp(pool); +} + +void JobLimit::store(Internal::PersistentPool &pool) +{ + d->serializationOp(pool); +} + +JobLimits::JobLimits() : d(new Internal::JobLimitsPrivate) { } +JobLimits::JobLimits(const JobLimits &other) = default; +JobLimits &JobLimits::operator=(const JobLimits &other) = default; +JobLimits::~JobLimits() = default; + +void JobLimits::setJobLimit(const JobLimit &limit) +{ + for (auto ¤tLimit : d->jobLimits) { + if (currentLimit.pool() == limit.pool()) { + if (currentLimit.limit() != limit.limit()) + currentLimit = limit; + return; + } + } + d->jobLimits.push_back(limit); +} + +void JobLimits::setJobLimit(const QString &pool, int limit) +{ + setJobLimit(JobLimit(pool, limit)); +} + +int JobLimits::getLimit(const QString &pool) const +{ + for (const JobLimit &l : d->jobLimits) { + if (l.pool() == pool) + return l.limit(); + } + return -1; +} + +bool JobLimits::isEmpty() const +{ + return d->jobLimits.empty(); +} + +int JobLimits::count() const +{ + return int(d->jobLimits.size()); +} + +JobLimit JobLimits::jobLimitAt(int i) const +{ + return d->jobLimits.at(i); +} + +JobLimits &JobLimits::update(const JobLimits &other) +{ + if (isEmpty()) { + *this = other; + } else { + for (int i = 0; i < other.count(); ++i) { + const JobLimit &l = other.jobLimitAt(i); + if (l.limit() != -1) + setJobLimit(l); + } + } + return *this; +} + +void JobLimits::load(Internal::PersistentPool &pool) +{ + d->serializationOp(pool); +} + +void JobLimits::store(Internal::PersistentPool &pool) +{ + d->serializationOp(pool); +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/joblimits.h b/src/lib/corelib/tools/joblimits.h new file mode 100644 index 00000000..de95f551 --- /dev/null +++ b/src/lib/corelib/tools/joblimits.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_JOB_LIMITS_H +#define QBS_JOB_LIMITS_H + +#include "qbs_export.h" + +#include + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class JobLimitPrivate; +class JobLimitsPrivate; +class PersistentPool; +} + +class QBS_EXPORT JobLimit +{ +public: + JobLimit(); + JobLimit(const QString &pool, int limit); + JobLimit(const JobLimit &other); + JobLimit &operator=(const JobLimit &other); + ~JobLimit(); + + QString pool() const; + int limit() const; + + void load(Internal::PersistentPool &pool); + void store(Internal::PersistentPool &pool); +private: + QSharedDataPointer d; +}; + +class QBS_EXPORT JobLimits +{ +public: + JobLimits(); + JobLimits(const JobLimits &other); + JobLimits &operator=(const JobLimits &other); + ~JobLimits(); + + void setJobLimit(const JobLimit &limit); + void setJobLimit(const QString &pool, int limit); + int getLimit(const QString &pool) const; + bool hasLimit(const QString &pool) const { return getLimit(pool) != -1; } + bool isEmpty() const; + + int count() const; + JobLimit jobLimitAt(int i) const; + + JobLimits &update(const JobLimits &other); + + void load(Internal::PersistentPool &pool); + void store(Internal::PersistentPool &pool); +private: + QSharedDataPointer d; +}; + +} // namespace qbs + +#endif // include guard diff --git a/src/lib/corelib/tools/jsliterals.cpp b/src/lib/corelib/tools/jsliterals.cpp new file mode 100644 index 00000000..74328006 --- /dev/null +++ b/src/lib/corelib/tools/jsliterals.cpp @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** 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 "jsliterals.h" + +#include + +#include + +namespace qbs { + +QString toJSLiteral(const bool b) +{ + return b ? Internal::StringConstants::trueValue() : Internal::StringConstants::falseValue(); +} + +QString toJSLiteral(const QString &str) +{ + QString js = str; + js.replace(QRegExp(QLatin1String("([\\\\\"])")), QLatin1String("\\\\1")); + js.prepend(QLatin1Char('"')); + js.append(QLatin1Char('"')); + return js; +} + +QString toJSLiteral(const QStringList &strs) +{ + QString js = QStringLiteral("["); + for (int i = 0; i < strs.size(); ++i) { + if (i != 0) + js.append(QLatin1String(", ")); + js.append(toJSLiteral(strs.at(i))); + } + js.append(QLatin1Char(']')); + return js; +} + +QString toJSLiteral(const QVariant &val) +{ + if (!val.isValid()) + return Internal::StringConstants::undefinedValue(); + if (val.type() == QVariant::List || val.type() == QVariant::StringList) { + QString res; + const auto list = val.toList(); + for (const QVariant &child : list) { + if (res.length()) res.append(QLatin1String(", ")); + res.append(toJSLiteral(child)); + } + res.prepend(QLatin1Char('[')); + res.append(QLatin1Char(']')); + return res; + } + if (val.type() == QVariant::Map) { + const QVariantMap &vm = val.toMap(); + QString str = QStringLiteral("{"); + for (QVariantMap::const_iterator it = vm.begin(); it != vm.end(); ++it) { + if (it != vm.begin()) + str += QLatin1Char(','); + str += toJSLiteral(it.key()) + QLatin1Char(':') + toJSLiteral(it.value()); + } + str += QLatin1Char('}'); + return str; + } + if (val.type() == QVariant::Bool) + return toJSLiteral(val.toBool()); + if (val.canConvert(QVariant::String)) + return toJSLiteral(val.toString()); + return QStringLiteral("Unconvertible type %1").arg(QLatin1String(val.typeName())); +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/jsliterals.h b/src/lib/corelib/tools/jsliterals.h new file mode 100644 index 00000000..7286ca5d --- /dev/null +++ b/src/lib/corelib/tools/jsliterals.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** 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 QBS_JSLITERALS_H +#define QBS_JSLITERALS_H + +#include "qbs_export.h" + +#include +#include +#include + +namespace qbs { + +QBS_EXPORT QString toJSLiteral(const bool b); +QBS_EXPORT QString toJSLiteral(const QString &str); +QBS_EXPORT QString toJSLiteral(const QStringList &strs); +QBS_EXPORT QString toJSLiteral(const QVariant &val); + +} // namespace qbs + +#endif // QBS_JSLITERALS_H diff --git a/src/lib/corelib/tools/jsonhelper.h b/src/lib/corelib/tools/jsonhelper.h new file mode 100644 index 00000000..991d6bd6 --- /dev/null +++ b/src/lib/corelib/tools/jsonhelper.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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_JSON_HELPER_H +#define QBS_JSON_HELPER_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { + +template inline T fromJson(const QJsonValue &v); +template<> inline bool fromJson(const QJsonValue &v) { return v.toBool(); } +template<> inline int fromJson(const QJsonValue &v) { return v.toInt(); } +template<> inline QString fromJson(const QJsonValue &v) { return v.toString(); } +template<> inline QStringList fromJson(const QJsonValue &v) +{ + const QJsonArray &jsonList = v.toArray(); + QStringList stringList; + std::transform(jsonList.begin(), jsonList.end(), std::back_inserter(stringList), + [](const QVariant &v) { return v.toString(); }); + return stringList; +} +template<> inline QVariantMap fromJson(const QJsonValue &v) { return v.toObject().toVariantMap(); } +template<> inline QProcessEnvironment fromJson(const QJsonValue &v) +{ + const QJsonObject obj = v.toObject(); + QProcessEnvironment env; + for (auto it = obj.begin(); it != obj.end(); ++it) + env.insert(it.key(), it.value().toString()); + return env; +} + +template inline void setValueFromJson(T &targetValue, const QJsonObject &data, + const char *jsonProperty) +{ + const auto it = data.find(QLatin1String(jsonProperty)); + if (it != data.end()) + targetValue = fromJson(*it); +} + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/tools/launcherinterface.cpp b/src/lib/corelib/tools/launcherinterface.cpp new file mode 100644 index 00000000..d2cdf44d --- /dev/null +++ b/src/lib/corelib/tools/launcherinterface.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** 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 "launcherinterface.h" + +#include "launcherpackets.h" +#include "launchersocket.h" +#include "qbsassert.h" +#include +#include + +#include +#include +#include +#include +#include + +#ifdef Q_OS_UNIX +#include +#endif + +namespace qbs { +namespace Internal { + +class LauncherProcess : public QProcess +{ +public: + LauncherProcess(QObject *parent) : QProcess(parent) { } + +private: + void setupChildProcess() override + { +#ifdef Q_OS_UNIX + const auto pid = static_cast(processId()); + setpgid(pid, pid); +#endif + } +}; + +static QString launcherSocketName() +{ + return QStringLiteral("qbs_processlauncher-%1") + .arg(QString::number(qApp->applicationPid())); +} + +LauncherInterface::LauncherInterface() + : m_server(new QLocalServer(this)), m_socket(new LauncherSocket(this)) +{ + QObject::connect(m_server, &QLocalServer::newConnection, + this, &LauncherInterface::handleNewConnection); +} + +LauncherInterface &LauncherInterface::instance() +{ + static LauncherInterface p; + return p; +} + +LauncherInterface::~LauncherInterface() +{ + m_server->disconnect(); +} + +void LauncherInterface::doStart() +{ + if (++m_startRequests > 1) + return; + const QString &socketName = launcherSocketName(); + QLocalServer::removeServer(socketName); + if (!m_server->listen(socketName)) { + emit errorOccurred(ErrorInfo(m_server->errorString())); + return; + } + m_process = new LauncherProcess(this); + connect(m_process, &QProcess::errorOccurred, this, &LauncherInterface::handleProcessError); + connect(m_process, + static_cast(&QProcess::finished), + this, &LauncherInterface::handleProcessFinished); + connect(m_process, &QProcess::readyReadStandardError, + this, &LauncherInterface::handleProcessStderr); + m_process->start(qApp->applicationDirPath() + QLatin1Char('/') + + QLatin1String(QBS_RELATIVE_LIBEXEC_PATH) + + QLatin1String("/qbs_processlauncher"), + QStringList(m_server->fullServerName())); +} + +void LauncherInterface::doStop() +{ + if (--m_startRequests > 0) + return; + m_server->close(); + if (!m_process) + return; + m_process->disconnect(); + m_socket->shutdown(); + m_process->waitForFinished(3000); + m_process->deleteLater(); + m_process = nullptr; +} + +void LauncherInterface::handleNewConnection() +{ + QLocalSocket * const socket = m_server->nextPendingConnection(); + if (!socket) + return; + m_server->close(); + m_socket->setSocket(socket); +} + +void LauncherInterface::handleProcessError() +{ + if (m_process->error() == QProcess::FailedToStart) { + const QString launcherPathForUser + = QDir::toNativeSeparators(QDir::cleanPath(m_process->program())); + emit errorOccurred(ErrorInfo(Tr::tr("Failed to start process launcher at '%1': %2") + .arg(launcherPathForUser, m_process->errorString()))); + } +} + +void LauncherInterface::handleProcessFinished() +{ + emit errorOccurred(ErrorInfo(Tr::tr("Process launcher closed unexpectedly: %1") + .arg(m_process->errorString()))); +} + +void LauncherInterface::handleProcessStderr() +{ + qDebug() << "[launcher]" << m_process->readAllStandardError(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/launcherinterface.h b/src/lib/corelib/tools/launcherinterface.h new file mode 100644 index 00000000..f3aca9bf --- /dev/null +++ b/src/lib/corelib/tools/launcherinterface.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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 QBS_LAUNCHERINTERFACE_H +#define QBS_LAUNCHERINTERFACE_H + +#include + +QT_BEGIN_NAMESPACE +class QLocalServer; +QT_END_NAMESPACE + +namespace qbs { +class ErrorInfo; +namespace Internal { +class LauncherProcess; +class LauncherSocket; + +class LauncherInterface : public QObject +{ + Q_OBJECT +public: + static LauncherInterface &instance(); + ~LauncherInterface() override; + + static void startLauncher() { instance().doStart(); } + static void stopLauncher() { instance().doStop(); } + static LauncherSocket *socket() { return instance().m_socket; } + +signals: + void errorOccurred(const ErrorInfo &error); + +private: + LauncherInterface(); + + void doStart(); + void doStop(); + void handleNewConnection(); + void handleProcessError(); + void handleProcessFinished(); + void handleProcessStderr(); + + QLocalServer * const m_server; + LauncherSocket * const m_socket; + LauncherProcess * m_process = nullptr; + int m_startRequests = 0; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/tools/launcherpackets.cpp b/src/lib/corelib/tools/launcherpackets.cpp new file mode 100644 index 00000000..9c7946d7 --- /dev/null +++ b/src/lib/corelib/tools/launcherpackets.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** 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 "launcherpackets.h" + +#include +#include + +namespace qbs { +namespace Internal { + +LauncherPacket::~LauncherPacket() = default; + +QByteArray LauncherPacket::serialize() const +{ + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << static_cast(0) << static_cast(type) << token; + doSerialize(stream); + stream.device()->reset(); + stream << static_cast(data.size() - sizeof(int)); + return data; +} + +void LauncherPacket::deserialize(const QByteArray &data) +{ + QDataStream stream(data); + doDeserialize(stream); +} + + +StartProcessPacket::StartProcessPacket(quintptr token) + : LauncherPacket(LauncherPacketType::StartProcess, token) +{ +} + +void StartProcessPacket::doSerialize(QDataStream &stream) const +{ + stream << command << arguments << workingDir << env; +} + +void StartProcessPacket::doDeserialize(QDataStream &stream) +{ + stream >> command >> arguments >> workingDir >> env; +} + + +StopProcessPacket::StopProcessPacket(quintptr token) + : LauncherPacket(LauncherPacketType::StopProcess, token) +{ +} + +void StopProcessPacket::doSerialize(QDataStream &stream) const +{ + Q_UNUSED(stream); +} + +void StopProcessPacket::doDeserialize(QDataStream &stream) +{ + Q_UNUSED(stream); +} + + +ProcessErrorPacket::ProcessErrorPacket(quintptr token) + : LauncherPacket(LauncherPacketType::ProcessError, token) +{ +} + +void ProcessErrorPacket::doSerialize(QDataStream &stream) const +{ + stream << static_cast(error) << errorString; +} + +void ProcessErrorPacket::doDeserialize(QDataStream &stream) +{ + quint8 e; + stream >> e; + error = static_cast(e); + stream >> errorString; +} + + +ProcessFinishedPacket::ProcessFinishedPacket(quintptr token) + : LauncherPacket(LauncherPacketType::ProcessFinished, token) +{ +} + +void ProcessFinishedPacket::doSerialize(QDataStream &stream) const +{ + stream << errorString << stdOut << stdErr + << static_cast(exitStatus) << static_cast(error) + << exitCode; +} + +void ProcessFinishedPacket::doDeserialize(QDataStream &stream) +{ + stream >> errorString >> stdOut >> stdErr; + quint8 val; + stream >> val; + exitStatus = static_cast(val); + stream >> val; + error = static_cast(val); + stream >> exitCode; +} + +ShutdownPacket::ShutdownPacket() : LauncherPacket(LauncherPacketType::Shutdown, 0) { } +void ShutdownPacket::doSerialize(QDataStream &stream) const { Q_UNUSED(stream); } +void ShutdownPacket::doDeserialize(QDataStream &stream) { Q_UNUSED(stream); } + +void PacketParser::setDevice(QIODevice *device) +{ + m_stream.setDevice(device); + m_sizeOfNextPacket = -1; +} + +bool PacketParser::parse() +{ + static const int commonPayloadSize = static_cast(1 + sizeof(quintptr)); + if (m_sizeOfNextPacket == -1) { + if (m_stream.device()->bytesAvailable() < static_cast(sizeof m_sizeOfNextPacket)) + return false; + m_stream >> m_sizeOfNextPacket; + if (m_sizeOfNextPacket < commonPayloadSize) + throw InvalidPacketSizeException(m_sizeOfNextPacket); + } + if (m_stream.device()->bytesAvailable() < m_sizeOfNextPacket) + return false; + quint8 type; + m_stream >> type; + m_type = static_cast(type); + m_stream >> m_token; + m_packetData = m_stream.device()->read(m_sizeOfNextPacket - commonPayloadSize); + m_sizeOfNextPacket = -1; + return true; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/launcherpackets.h b/src/lib/corelib/tools/launcherpackets.h new file mode 100644 index 00000000..b3eac432 --- /dev/null +++ b/src/lib/corelib/tools/launcherpackets.h @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** 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_LAUNCHERPACKETS_H +#define QBS_LAUNCHERPACKETS_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QByteArray; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +enum class LauncherPacketType { + Shutdown, StartProcess, StopProcess, ProcessError, ProcessFinished +}; + +class PacketParser +{ +public: + class InvalidPacketSizeException + { + public: + InvalidPacketSizeException(int size) : size(size) { } + const int size; + }; + + void setDevice(QIODevice *device); + bool parse(); + LauncherPacketType type() const { return m_type; } + quintptr token() const { return m_token; } + const QByteArray &packetData() const { return m_packetData; } + +private: + QDataStream m_stream; + LauncherPacketType m_type = LauncherPacketType::Shutdown; + quintptr m_token = 0; + QByteArray m_packetData; + int m_sizeOfNextPacket = -1; +}; + +class LauncherPacket +{ +public: + virtual ~LauncherPacket(); + + template static Packet extractPacket(quintptr token, const QByteArray &data) + { + Packet p(token); + p.deserialize(data); + return p; + } + + QByteArray serialize() const; + void deserialize(const QByteArray &data); + + const LauncherPacketType type; + const quintptr token = 0; + +protected: + LauncherPacket(LauncherPacketType type, quintptr token) : type(type), token(token) { } + +private: + virtual void doSerialize(QDataStream &stream) const = 0; + virtual void doDeserialize(QDataStream &stream) = 0; +}; + +class StartProcessPacket : public LauncherPacket +{ +public: + StartProcessPacket(quintptr token); + + QString command; + QStringList arguments; + QString workingDir; + QStringList env; + +private: + void doSerialize(QDataStream &stream) const override; + void doDeserialize(QDataStream &stream) override; +}; + +class StopProcessPacket : public LauncherPacket +{ +public: + StopProcessPacket(quintptr token); + +private: + void doSerialize(QDataStream &stream) const override; + void doDeserialize(QDataStream &stream) override; +}; + +class ShutdownPacket : public LauncherPacket +{ +public: + ShutdownPacket(); + +private: + void doSerialize(QDataStream &stream) const override; + void doDeserialize(QDataStream &stream) override; +}; + +class ProcessErrorPacket : public LauncherPacket +{ +public: + ProcessErrorPacket(quintptr token); + + QProcess::ProcessError error = QProcess::UnknownError; + QString errorString; + +private: + void doSerialize(QDataStream &stream) const override; + void doDeserialize(QDataStream &stream) override; +}; + +class ProcessFinishedPacket : public LauncherPacket +{ +public: + ProcessFinishedPacket(quintptr token); + + QString errorString; + QByteArray stdOut; + QByteArray stdErr; + QProcess::ExitStatus exitStatus = QProcess::ExitStatus::NormalExit; + QProcess::ProcessError error = QProcess::ProcessError::UnknownError; + int exitCode = 0; + +private: + void doSerialize(QDataStream &stream) const override; + void doDeserialize(QDataStream &stream) override; +}; + +} // namespace Internal +} // namespace qbs + +Q_DECLARE_METATYPE(qbs::Internal::LauncherPacketType); + +#endif // Include guard diff --git a/src/lib/corelib/tools/launchersocket.cpp b/src/lib/corelib/tools/launchersocket.cpp new file mode 100644 index 00000000..1489af1e --- /dev/null +++ b/src/lib/corelib/tools/launchersocket.cpp @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** 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 "launchersocket.h" + +#include "qbsassert.h" +#include "qttools.h" + +#include + +#include +#include + +namespace qbs { +namespace Internal { + +LauncherSocket::LauncherSocket(QObject *parent) : QObject(parent) +{ + qRegisterMetaType(); + qRegisterMetaType("quintptr"); +} + +void LauncherSocket::sendData(const QByteArray &data) +{ + if (!isReady()) + return; + std::lock_guard locker(m_requestsMutex); + m_requests.push_back(data); + if (m_requests.size() == 1) + QTimer::singleShot(0, this, &LauncherSocket::handleRequests); +} + +void LauncherSocket::shutdown() +{ + const auto socket = m_socket.exchange(nullptr); + if (!socket) + return; + socket->disconnect(); + socket->write(ShutdownPacket().serialize()); + socket->waitForBytesWritten(1000); + socket->deleteLater(); +} + +void LauncherSocket::setSocket(QLocalSocket *socket) +{ + QBS_ASSERT(!m_socket, return); + m_socket.store(socket); + m_packetParser.setDevice(m_socket); + connect(m_socket, +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) + static_cast(&QLocalSocket::error), +#else + &QLocalSocket::errorOccurred, +#endif + this, &LauncherSocket::handleSocketError); + connect(m_socket, &QLocalSocket::readyRead, + this, &LauncherSocket::handleSocketDataAvailable); + connect(m_socket, &QLocalSocket::disconnected, + this, &LauncherSocket::handleSocketDisconnected); + emit ready(); +} + +void LauncherSocket::handleSocketError() +{ + auto socket = m_socket.load(); + if (socket->error() != QLocalSocket::PeerClosedError) + handleError(Tr::tr("Socket error: %1").arg(socket->errorString())); +} + +void LauncherSocket::handleSocketDataAvailable() +{ + try { + if (!m_packetParser.parse()) + return; + } catch (const PacketParser::InvalidPacketSizeException &e) { + handleError(Tr::tr("Internal protocol error: invalid packet size %1.").arg(e.size)); + return; + } + switch (m_packetParser.type()) { + case LauncherPacketType::ProcessError: + case LauncherPacketType::ProcessFinished: + emit packetArrived(m_packetParser.type(), m_packetParser.token(), + m_packetParser.packetData()); + break; + default: + handleError(Tr::tr("Internal protocol error: invalid packet type %1.") + .arg(static_cast(m_packetParser.type()))); + return; + } + handleSocketDataAvailable(); +} + +void LauncherSocket::handleSocketDisconnected() +{ + handleError(Tr::tr("Launcher socket closed unexpectedly")); +} + +void LauncherSocket::handleError(const QString &error) +{ + const auto socket = m_socket.exchange(nullptr); + socket->disconnect(); + socket->deleteLater(); + emit errorOccurred(error); +} + +void LauncherSocket::handleRequests() +{ + const auto socket = m_socket.load(); + QBS_ASSERT(socket, return); + std::lock_guard locker(m_requestsMutex); + for (const QByteArray &request : qAsConst(m_requests)) + socket->write(request); + m_requests.clear(); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/launchersocket.h b/src/lib/corelib/tools/launchersocket.h new file mode 100644 index 00000000..2eb2c3f6 --- /dev/null +++ b/src/lib/corelib/tools/launchersocket.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** 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_LAUNCHERSOCKET_H +#define QBS_LAUNCHERSOCKET_H + +#include "launcherpackets.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE +class QLocalSocket; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class LauncherInterface; + +class LauncherSocket : public QObject +{ + Q_OBJECT + friend class LauncherInterface; +public: + bool isReady() const { return m_socket.load(); } + void sendData(const QByteArray &data); + +signals: + void ready(); + void errorOccurred(const QString &error); + void packetArrived(qbs::Internal::LauncherPacketType type, quintptr token, + const QByteArray &payload); + +private: + LauncherSocket(QObject *parent); + + void setSocket(QLocalSocket *socket); + void shutdown(); + + void handleSocketError(); + void handleSocketDataAvailable(); + void handleSocketDisconnected(); + void handleError(const QString &error); + void handleRequests(); + + std::atomic m_socket{nullptr}; + PacketParser m_packetParser; + std::vector m_requests; + std::mutex m_requestsMutex; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/tools/msvcinfo.cpp b/src/lib/corelib/tools/msvcinfo.cpp new file mode 100644 index 00000000..42cfefe7 --- /dev/null +++ b/src/lib/corelib/tools/msvcinfo.cpp @@ -0,0 +1,699 @@ +/**************************************************************************** +** +** 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 "msvcinfo.h" +#include "visualstudioversioninfo.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#endif + +#include +#include +#include + +using namespace qbs; +using namespace qbs::Internal; + +static std::recursive_mutex envMutex; + +static QString mkStr(const char *s) { return QString::fromLocal8Bit(s); } +static QString mkStr(const QByteArray &ba) { return mkStr(ba.constData()); } + +class TemporaryEnvChanger +{ +public: + TemporaryEnvChanger(const QProcessEnvironment &envChanges) : m_locker(envMutex) + { + QProcessEnvironment currentEnv = QProcessEnvironment::systemEnvironment(); + const auto keys = envChanges.keys(); + for (const QString &key : keys) { + m_changesToRestore.insert(key, currentEnv.value(key)); + qputenv(qPrintable(key), qPrintable(envChanges.value(key))); + } + } + + ~TemporaryEnvChanger() + { + const auto keys = m_changesToRestore.keys(); + for (const QString &key : keys) + qputenv(qPrintable(key), qPrintable(m_changesToRestore.value(key))); + } + +private: + QProcessEnvironment m_changesToRestore; + std::lock_guard m_locker; +}; + +static QByteArray runProcess(const QString &exeFilePath, const QStringList &args, + const QProcessEnvironment &env = QProcessEnvironment(), + bool allowFailure = false, + const QByteArray &pipeData = QByteArray()) +{ + TemporaryEnvChanger envChanger(env); + QProcess process; + process.start(exeFilePath, args); + if (!process.waitForStarted()) + throw ErrorInfo(mkStr("Could not start %1 (%2)").arg(exeFilePath, process.errorString())); + if (!pipeData.isEmpty()) { + process.write(pipeData); + process.closeWriteChannel(); + } + if (!process.waitForFinished(-1) || process.exitStatus() != QProcess::NormalExit) + throw ErrorInfo(mkStr("Could not run %1 (%2)").arg(exeFilePath, process.errorString())); + if (process.exitCode() != 0 && !allowFailure) { + ErrorInfo e(mkStr("Process '%1' failed with exit code %2.") + .arg(exeFilePath).arg(process.exitCode())); + const QByteArray stdErr = process.readAllStandardError(); + if (!stdErr.isEmpty()) + e.append(mkStr("stderr was: %1").arg(mkStr(stdErr))); + const QByteArray stdOut = process.readAllStandardOutput(); + if (!stdOut.isEmpty()) + e.append(mkStr("stdout was: %1").arg(mkStr(stdOut))); + throw e; + } + return process.readAllStandardOutput().trimmed(); +} + +class DummyFile { +public: + DummyFile(QString fp) : filePath(std::move(fp)) { } + ~DummyFile() { QFile::remove(filePath); } + const QString filePath; +}; + +#ifdef Q_OS_WIN +static QStringList parseCommandLine(const QString &commandLine) +{ + const auto buf = std::make_unique(size_t(commandLine.size()) + 1); + buf[size_t(commandLine.toWCharArray(buf.get()))] = 0; + int argCount = 0; + const auto argsDeleter = [](LPWSTR *p){ LocalFree(p); }; + const auto args = std::unique_ptr( + CommandLineToArgvW(buf.get(), &argCount), argsDeleter); + if (!args) + throw ErrorInfo(mkStr("Could not parse command line arguments: ") + commandLine); + QStringList list; + list.reserve(argCount); + for (int i = 0; i < argCount; ++i) + list.push_back(QString::fromWCharArray(args[size_t(i)])); + return list; +} +#endif + +static QVariantMap getMsvcDefines(const QString &compilerFilePath, + 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") + << backendSwitch + << qEnvironmentVariable("COMSPEC") + << QStringLiteral("/c") + << languageSwitch + << QStringLiteral("NUL"), + compilerEnv, true, commands)).split(QLatin1Char('\n')); + + auto findResult = std::find_if(out.cbegin(), out.cend(), [] (const QString &line) { + return line.startsWith(QLatin1String("MSC_CMD_FLAGS=")); + }); + if (findResult == out.cend()) { + throw ErrorInfo(QStringLiteral("Unexpected compiler frontend output: ") + + out.join(QLatin1Char('\n'))); + } + + QVariantMap map; + const QStringList args = parseCommandLine(findResult->trimmed()); + for (const QString &arg : args) { + if (!arg.startsWith(QStringLiteral("-D"))) + continue; + int idx = arg.indexOf(QLatin1Char('='), 2); + if (idx > 2) + map.insert(arg.mid(2, idx - 2), arg.mid(idx + 1)); + else + map.insert(arg.mid(2), QVariant()); + } + + return map; +#else + Q_UNUSED(compilerFilePath); + Q_UNUSED(compilerEnv); + Q_UNUSED(language); + return {}; +#endif +} + +/*! + \internal + clang-cl does not support gcc and msvc ways to dump a macros, so we have to use original + clang.exe directly +*/ +static QVariantMap getClangClDefines( + const QString &compilerFilePath, + const QProcessEnvironment &compilerEnv, + MSVC::CompilerLanguage language, + const QString &arch) +{ +#ifdef Q_OS_WIN + QFileInfo clInfo(compilerFilePath); + QFileInfo clangInfo(clInfo.absolutePath() + QLatin1String("/clang-cl.exe")); + if (!clangInfo.exists()) + throw ErrorInfo(QStringLiteral("%1 does not exist").arg(clangInfo.absoluteFilePath())); + + QStringList args = { + QStringLiteral("/d1PP"), // dump macros + QStringLiteral("/E") // preprocess to stdout + }; + + if (language == MSVC::CLanguage) + args.append(QStringLiteral("/TC")); + else if (language == MSVC::CPlusPlusLanguage) + args.append(QStringLiteral("/TP")); + + if (arch == QLatin1String("x86")) + args.append(QStringLiteral("-m32")); + else if (arch == QLatin1String("x86_64")) + args.append(QStringLiteral("-m64")); + + args.append(QStringLiteral("NUL")); // filename + + const auto lines = QString::fromLocal8Bit( + runProcess( + clangInfo.absoluteFilePath(), + args, + compilerEnv, + true)).split(QLatin1Char('\n')); + QVariantMap result; + for (const auto &line: lines) { + static const auto defineString = QLatin1String("#define "); + if (!line.startsWith(defineString)) + continue; + QStringView view(line.data() + defineString.size()); + const auto it = std::find(view.begin(), view.end(), QLatin1Char(' ')); + if (it == view.end()) { + throw ErrorInfo(QStringLiteral("Unexpected compiler frontend output: ") + + lines.join(QLatin1Char('\n'))); + } + QStringView key(view.begin(), it); + QStringView value(it + 1, view.end()); + result.insert(key.toString(), value.isEmpty() ? QVariant() : QVariant(value.toString())); + } + if (result.isEmpty()) { + throw ErrorInfo(QStringLiteral("Cannot determine macroses from compiler frontend output: ") + + lines.join(QLatin1Char('\n'))); + } + return result; +#else + Q_UNUSED(compilerFilePath); + Q_UNUSED(compilerEnv); + Q_UNUSED(language); + Q_UNUSED(arch); + return {}; +#endif +} + +static QString formatVswhereOutput(const QString &out, const QString &err) +{ + QString ret; + if (!out.isEmpty()) { + ret.append(Tr::tr("stdout")).append(QLatin1String(":\n")); + const auto lines = out.split(QLatin1Char('\n')); + for (const QString &line : lines) + ret.append(QLatin1Char('\t')).append(line).append(QLatin1Char('\n')); + } + if (!err.isEmpty()) { + ret.append(Tr::tr("stderr")).append(QLatin1String(":\n")); + const auto lines = err.split(QLatin1Char('\n')); + for (const QString &line : lines) + ret.append(QLatin1Char('\t')).append(line).append(QLatin1Char('\n')); + } + return ret; +} + +static QString wow6432Key() +{ +#ifdef Q_OS_WIN64 + return QStringLiteral("\\Wow6432Node"); +#else + return {}; +#endif +} + +static QString vswhereFilePath() +{ + static const std::vector envVarCandidates{"ProgramFiles", "ProgramFiles(x86)"}; + for (const char * const envVar : envVarCandidates) { + const QString value = QDir::fromNativeSeparators(QString::fromLocal8Bit(qgetenv(envVar))); + const QString cmd = value + + QStringLiteral("/Microsoft Visual Studio/Installer/vswhere.exe"); + if (QFileInfo(cmd).exists()) + return cmd; + } + return {}; +} + +enum class ProductType { VisualStudio, BuildTools }; +static std::vector retrieveInstancesFromVSWhere( + ProductType productType, Logger &logger) +{ + std::vector result; + const QString cmd = vswhereFilePath(); + if (cmd.isEmpty()) + return result; + QProcess vsWhere; + QStringList args = productType == ProductType::VisualStudio + ? QStringList({QStringLiteral("-all"), QStringLiteral("-legacy"), + QStringLiteral("-prerelease")}) + : QStringList({QStringLiteral("-products"), + QStringLiteral("Microsoft.VisualStudio.Product.BuildTools")}); + args << QStringLiteral("-format") << QStringLiteral("json") << QStringLiteral("-utf8"); + vsWhere.start(cmd, args); + if (!vsWhere.waitForStarted(-1)) + return result; + if (!vsWhere.waitForFinished(-1)) { + logger.qbsWarning() << Tr::tr("The vswhere tool failed to run").append(QLatin1String(": ")) + .append(vsWhere.errorString()); + return result; + } + if (vsWhere.exitCode() != 0) { + const QString stdOut = QString::fromLocal8Bit(vsWhere.readAllStandardOutput()); + const QString stdErr = QString::fromLocal8Bit(vsWhere.readAllStandardError()); + logger.qbsWarning() << Tr::tr("The vswhere tool failed to run").append(QLatin1String(".\n")) + .append(formatVswhereOutput(stdOut, stdErr)); + return result; + } + QJsonParseError parseError{}; + QJsonDocument jsonOutput = QJsonDocument::fromJson(vsWhere.readAllStandardOutput(), + &parseError); + if (parseError.error != QJsonParseError::NoError) { + logger.qbsWarning() << Tr::tr("The vswhere tool produced invalid JSON output: %1") + .arg(parseError.errorString()); + return result; + } + const auto jsonArray = jsonOutput.array(); + for (const QJsonValue &v : jsonArray) { + const QJsonObject o = v.toObject(); + MSVCInstallInfo info; + info.version = o.value(QStringLiteral("installationVersion")).toString(); + if (productType == ProductType::BuildTools) { + // For build tools, the version is e.g. "15.8.28010.2036", rather than "15.0". + const int dotIndex = info.version.indexOf(QLatin1Char('.')); + if (dotIndex != -1) + info.version = info.version.left(dotIndex); + } + info.installDir = o.value(QStringLiteral("installationPath")).toString(); + if (!info.version.isEmpty() && !info.installDir.isEmpty()) + result.push_back(info); + } + return result; +} + +static std::vector installedMSVCsFromVsWhere(Logger &logger) +{ + const std::vector vsInstallations + = retrieveInstancesFromVSWhere(ProductType::VisualStudio, logger); + const std::vector buildToolInstallations + = retrieveInstancesFromVSWhere(ProductType::BuildTools, logger); + std::vector all; + std::copy(vsInstallations.begin(), vsInstallations.end(), std::back_inserter(all)); + std::copy(buildToolInstallations.begin(), buildToolInstallations.end(), + std::back_inserter(all)); + return all; +} + +static std::vector installedMSVCsFromRegistry() +{ + std::vector result; + + // Detect Visual Studio + const QSettings vsRegistry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key() + + QStringLiteral("\\Microsoft\\VisualStudio\\SxS\\VS7"), + QSettings::NativeFormat); + const auto vsNames = vsRegistry.childKeys(); + for (const QString &vsName : vsNames) { + MSVCInstallInfo entry; + entry.version = vsName; + entry.installDir = vsRegistry.value(vsName).toString(); + result.push_back(entry); + } + + // Detect Visual C++ Build Tools + QSettings vcbtRegistry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key() + + QStringLiteral("\\Microsoft\\VisualCppBuildTools"), + QSettings::NativeFormat); + const QStringList &vcbtRegistryChildGroups = vcbtRegistry.childGroups(); + for (const QString &childGroup : vcbtRegistryChildGroups) { + vcbtRegistry.beginGroup(childGroup); + bool ok; + int installed = vcbtRegistry.value(QStringLiteral("Installed")).toInt(&ok); + if (ok && installed) { + MSVCInstallInfo entry; + entry.version = childGroup; + const QSettings vsRegistry( + QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE") + wow6432Key() + + QStringLiteral("\\Microsoft\\VisualStudio\\") + childGroup + + QStringLiteral("\\Setup\\VC"), + QSettings::NativeFormat); + entry.installDir = vsRegistry.value(QStringLiteral("ProductDir")).toString(); + result.push_back(entry); + } + vcbtRegistry.endGroup(); + } + + return result; +} + +/* + Returns the list of compilers present in all MSVC installations + (Visual Studios or Build Tools) without the architecture, e.g. + [VC\Tools\MSVC\14.16.27023, VC\Tools\MSVC\14.14.26428, ...] +*/ +static std::vector installedCompilersHelper(Logger &logger) +{ + std::vector msvcs; + std::vector installInfos = installedMSVCsFromVsWhere(logger); + if (installInfos.empty()) + installInfos = installedMSVCsFromRegistry(); + for (const MSVCInstallInfo &installInfo : installInfos) { + MSVC msvc; + msvc.internalVsVersion = Version::fromString(installInfo.version, true); + if (!msvc.internalVsVersion.isValid()) + continue; + + QDir vsInstallDir(installInfo.installDir); + msvc.vsInstallPath = vsInstallDir.absolutePath(); + if (vsInstallDir.dirName() != QStringLiteral("VC") + && !vsInstallDir.cd(QStringLiteral("VC"))) { + continue; + } + + msvc.version = QString::number(Internal::VisualStudioVersionInfo( + msvc.internalVsVersion).marketingVersion()); + if (msvc.version.isEmpty()) { + logger.qbsWarning() + << Tr::tr("Unknown MSVC version %1 found.").arg(installInfo.version); + continue; + } + + if (msvc.internalVsVersion.majorVersion() < 15) { + QDir vcInstallDir = vsInstallDir; + if (!vcInstallDir.cd(QStringLiteral("bin"))) + continue; + msvc.vcInstallPath = vcInstallDir.absolutePath(); + msvcs.push_back(msvc); + } else { + QDir vcInstallDir = vsInstallDir; + vcInstallDir.cd(QStringLiteral("Tools/MSVC")); + const auto vcVersionStrs = vcInstallDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &vcVersionStr : vcVersionStrs) { + const Version vcVersion = Version::fromString(vcVersionStr); + if (!vcVersion.isValid()) + continue; + QDir specificVcInstallDir = vcInstallDir; + if (!specificVcInstallDir.cd(vcVersionStr) + || !specificVcInstallDir.cd(QStringLiteral("bin"))) { + continue; + } + msvc.vcInstallPath = specificVcInstallDir.absolutePath(); + msvcs.push_back(msvc); + } + } + } + return msvcs; +} + +void MSVC::init() +{ + determineCompilerVersion(); +} + +/*! + \internal + Returns the architecture detected from the compiler path. +*/ +QString MSVC::architectureFromClPath(const QString &clPath) +{ + const auto parentDir = QFileInfo(clPath).absolutePath(); + const auto parentDirName = QFileInfo(parentDir).fileName().toLower(); + if (parentDirName == QLatin1String("bin")) + return QStringLiteral("x86"); + return parentDirName; +} + +QString MSVC::canonicalArchitecture(const QString &arch) +{ + if (arch == QLatin1String("x64") || arch == QLatin1String("amd64")) + return QStringLiteral("x86_64"); + return arch; +} + +std::pair MSVC::getHostTargetArchPair(const QString &arch) +{ + QString hostArch; + QString targetArch; + const int index = arch.indexOf(QLatin1Char('_')); + if (index != -1) { + hostArch = arch.mid(0, index); + targetArch = arch.mid(index); + } else { + hostArch = arch; + targetArch = arch; + } + return {canonicalArchitecture(hostArch), canonicalArchitecture(targetArch)}; +} + +QString MSVC::binPathForArchitecture(const QString &arch) const +{ + QString archSubDir; + if (arch != StringConstants::x86Arch()) + archSubDir = arch; + return QDir::cleanPath(vcInstallPath + QLatin1Char('/') + pathPrefix + QLatin1Char('/') + + archSubDir); +} + +static QString clExeSuffix() { return QStringLiteral("/cl.exe"); } + +QString MSVC::clPathForArchitecture(const QString &arch) const +{ + return binPathForArchitecture(arch) + clExeSuffix(); +} + +QVariantMap MSVC::compilerDefines(const QString &compilerFilePath, + MSVC::CompilerLanguage language) const +{ + const auto compilerName = QFileInfo(compilerFilePath).fileName().toLower(); + if (compilerName == QLatin1String("clang-cl.exe")) + return getClangClDefines(compilerFilePath, environment, language, architecture); + return getMsvcDefines(compilerFilePath, environment, language); +} + +std::vector MSVC::findSupportedArchitectures(const MSVC &msvc) +{ + std::vector result; + auto addResult = [&result](const MSVCArchInfo &ai) { + if (QFile::exists(ai.binPath + QLatin1String("/cl.exe"))) + result.push_back(ai); + }; + if (msvc.internalVsVersion.majorVersion() < 15) { + static const QStringList knownArchitectures = QStringList() + << QStringLiteral("x86") + << QStringLiteral("amd64_x86") + << QStringLiteral("amd64") + << QStringLiteral("x86_amd64") + << QStringLiteral("ia64") + << QStringLiteral("x86_ia64") + << QStringLiteral("x86_arm") + << QStringLiteral("amd64_arm"); + for (const QString &knownArchitecture : knownArchitectures) { + MSVCArchInfo ai; + ai.arch = knownArchitecture; + ai.binPath = msvc.binPathForArchitecture(knownArchitecture); + addResult(ai); + } + } else { + QDir vcInstallDir(msvc.vcInstallPath); + const auto hostArchs = vcInstallDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &hostArch : hostArchs) { + QDir subdir = vcInstallDir; + if (!subdir.cd(hostArch)) + continue; + const QString shortHostArch = hostArch.mid(4).toLower(); + const auto archs = subdir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const QString &arch : archs) { + MSVCArchInfo ai; + ai.binPath = subdir.absoluteFilePath(arch); + if (shortHostArch == arch) + ai.arch = arch; + else + ai.arch = shortHostArch + QLatin1Char('_') + arch; + addResult(ai); + } + } + } + return result; +} + +QVariantMap MSVC::toVariantMap() const +{ + return { + {QStringLiteral("version"), version}, + {QStringLiteral("internalVsVersion"), internalVsVersion.toString()}, + {QStringLiteral("vsInstallPath"), vsInstallPath}, + {QStringLiteral("vcInstallPath"), vcInstallPath}, + {QStringLiteral("binPath"), binPath}, + {QStringLiteral("architecture"), architecture}, + }; +} + +/*! + \internal + Returns the list of all compilers present in all MSVC installations + separated by host/target arch, e.g. + [ + VC\Tools\MSVC\14.16.27023\bin\Hostx64\x64, + VC\Tools\MSVC\14.16.27023\bin\Hostx64\x86, + VC\Tools\MSVC\14.16.27023\bin\Hostx64\arm, + VC\Tools\MSVC\14.16.27023\bin\Hostx64\arm64, + VC\Tools\MSVC\14.16.27023\bin\Hostx86\x64, + ... + ] + \note that MSVC.architecture can be either "x64" or "amd64" (depending on the MSVC version) + in case of 64-bit platform (but we use the "x86_64" name...) +*/ +std::vector MSVC::installedCompilers(Logger &logger) +{ + std::vector msvcs; + const auto instMsvcs = installedCompilersHelper(logger); + for (const MSVC &msvc : instMsvcs) { + if (msvc.internalVsVersion.majorVersion() < 15) { + // Check existence of various install scripts + const QString vcvars32bat = msvc.vcInstallPath + QLatin1String("/vcvars32.bat"); + if (!QFileInfo(vcvars32bat).isFile()) + continue; + } + + const auto ais = findSupportedArchitectures(msvc); + for (const MSVCArchInfo &ai : ais) { + MSVC specificMSVC = msvc; + specificMSVC.architecture = ai.arch; + specificMSVC.binPath = ai.binPath; + msvcs.push_back(specificMSVC); + } + } + return msvcs; +} + +void MSVC::determineCompilerVersion() +{ + QString cppFilePath; + { + QTemporaryFile cppFile(QDir::tempPath() + QLatin1String("/qbsXXXXXX.cpp")); + cppFile.setAutoRemove(false); + if (!cppFile.open()) { + throw ErrorInfo(mkStr("Could not create temporary file (%1)") + .arg(cppFile.errorString())); + } + cppFilePath = cppFile.fileName(); + cppFile.write("_MSC_FULL_VER"); + cppFile.close(); + } + DummyFile fileDeleter(cppFilePath); + + std::lock_guard locker(envMutex); + const QByteArray origPath = qgetenv("PATH"); + qputenv("PATH", environment.value(StringConstants::pathEnvVar()).toLatin1() + ';' + origPath); + QByteArray versionStr = runProcess( + binPath + clExeSuffix(), + QStringList() << QStringLiteral("/nologo") << QStringLiteral("/EP") + << QDir::toNativeSeparators(cppFilePath)); + qputenv("PATH", origPath); + compilerVersion = Version(versionStr.mid(0, 2).toInt(), versionStr.mid(2, 2).toInt(), + versionStr.mid(4).toInt()); +} + +QString MSVCInstallInfo::findVcvarsallBat() const +{ + static const auto vcvarsall2017 = QStringLiteral("VC/Auxiliary/Build/vcvarsall.bat"); + // 2015, 2013 and 2012 + static const auto vcvarsallOld = QStringLiteral("VC/vcvarsall.bat"); + QDir dir(installDir); + if (dir.exists(vcvarsall2017)) + return dir.absoluteFilePath(vcvarsall2017); + if (dir.exists(vcvarsallOld)) + return dir.absoluteFilePath(vcvarsallOld); + return {}; +} + +std::vector MSVCInstallInfo::installedMSVCs(Logger &logger) +{ + const auto installInfos = installedMSVCsFromVsWhere(logger); + if (installInfos.empty()) + return installedMSVCsFromRegistry(); + return installInfos; +} diff --git a/src/lib/corelib/tools/msvcinfo.h b/src/lib/corelib/tools/msvcinfo.h new file mode 100644 index 00000000..de4470bf --- /dev/null +++ b/src/lib/corelib/tools/msvcinfo.h @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** 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 QBS_MSVCINFO_H +#define QBS_MSVCINFO_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +class Logger; + +struct MSVCArchInfo +{ + QString arch; + QString binPath; +}; + +/** + * Represents one MSVC installation for one specific target architecture. + * There are potentially multiple MSVCs in one Visual Studio installation. + */ +class MSVC +{ +public: + enum CompilerLanguage { + CLanguage = 0, + CPlusPlusLanguage = 1 + }; + + QString version; + Version internalVsVersion; + Version compilerVersion; + QString vsInstallPath; + QString vcInstallPath; + QString binPath; + QString pathPrefix; + QString architecture; + QProcessEnvironment environment; + + MSVC() = default; + + MSVC(const QString &clPath, QString arch): + architecture(std::move(arch)) + { + QDir parentDir = QFileInfo(clPath).dir(); + binPath = parentDir.absolutePath(); + QString parentDirName = parentDir.dirName().toLower(); + if (parentDirName != QLatin1String("bin")) + parentDir.cdUp(); + vcInstallPath = parentDir.path(); + } + + QBS_EXPORT void init(); + QBS_EXPORT static QString architectureFromClPath(const QString &clPath); + QBS_EXPORT static QString canonicalArchitecture(const QString &arch); + QBS_EXPORT static std::pair getHostTargetArchPair(const QString &arch); + QBS_EXPORT QString binPathForArchitecture(const QString &arch) const; + QBS_EXPORT QString clPathForArchitecture(const QString &arch) const; + QBS_EXPORT QVariantMap compilerDefines(const QString &compilerFilePath, + CompilerLanguage language) const; + + QBS_EXPORT static std::vector findSupportedArchitectures(const MSVC &msvc); + + QBS_EXPORT QVariantMap toVariantMap() const; + + QBS_EXPORT static std::vector installedCompilers(Logger &logger); + +private: + void determineCompilerVersion(); +}; + +class WinSDK : public MSVC +{ +public: + bool isDefault = false; + + WinSDK() + { + pathPrefix = QStringLiteral("bin"); + } +}; + +struct QBS_EXPORT MSVCInstallInfo +{ + QString version; + QString installDir; + + QString findVcvarsallBat() const; + + static std::vector installedMSVCs(Logger &logger); +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_MSVCINFO_H diff --git a/src/lib/corelib/tools/pathutils.h b/src/lib/corelib/tools/pathutils.h new file mode 100644 index 00000000..55dbc1c6 --- /dev/null +++ b/src/lib/corelib/tools/pathutils.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** 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 QBS_PATHUTILS_H +#define QBS_PATHUTILS_H + +#include "hostosinfo.h" +#include "qbs_export.h" + +namespace qbs { +namespace Internal { + +class PathUtils +{ +public: + static QString toNativeSeparators(const QString &s, + HostOsInfo::HostOs os = HostOsInfo::hostOs()) + { + QString value = s; + if (os == HostOsInfo::HostOsWindows) + value.replace(QLatin1Char('/'), HostOsInfo::pathSeparator(os)); + return value; + } +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PATHUTILS_H diff --git a/src/lib/corelib/tools/persistence.cpp b/src/lib/corelib/tools/persistence.cpp new file mode 100644 index 00000000..3f2b57e0 --- /dev/null +++ b/src/lib/corelib/tools/persistence.cpp @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** 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 "persistence.h" + +#include "fileinfo.h" +#include +#include + +#include + +namespace qbs { +namespace Internal { + +static const char QBS_PERSISTENCE_MAGIC[] = "QBSPERSISTENCE-128"; + +NoBuildGraphError::NoBuildGraphError(const QString &filePath) + : ErrorInfo(Tr::tr("Build graph not found for configuration '%1'. Expected location was '%2'.") + .arg(FileInfo::completeBaseName(filePath), QDir::toNativeSeparators(filePath))) +{ +} + +PersistentPool::PersistentPool(Logger &logger) : m_logger(logger) +{ + Q_UNUSED(m_logger); + m_stream.setVersion(QDataStream::Qt_4_8); +} + +PersistentPool::~PersistentPool() = default; + +void PersistentPool::load(const QString &filePath) +{ + std::unique_ptr file(new QFile(filePath)); + if (!file->exists()) + throw NoBuildGraphError(filePath); + if (!file->open(QFile::ReadOnly)) { + throw ErrorInfo(Tr::tr("Could not open open build graph file '%1': %2") + .arg(filePath, file->errorString())); + } + + m_stream.setDevice(file.get()); + QByteArray magic; + m_stream >> magic; + if (magic != QBS_PERSISTENCE_MAGIC) { + m_stream.setDevice(nullptr); + throw ErrorInfo(Tr::tr("Cannot use stored build graph at '%1': Incompatible file format. " + "Expected magic token '%2', got '%3'.") + .arg(filePath, QLatin1String(QBS_PERSISTENCE_MAGIC), + QString::fromLatin1(magic))); + } + + m_stream >> m_headData.projectConfig; + m_file = std::move(file); + m_loadedRaw.clear(); + m_loaded.clear(); + m_storageIndices.clear(); + m_stringStorage.clear(); + m_inverseStringStorage.clear(); +} + +void PersistentPool::setupWriteStream(const QString &filePath) +{ + QString dirPath = FileInfo::path(filePath); + if (!FileInfo::exists(dirPath) && !QDir().mkpath(dirPath)) { + throw ErrorInfo(Tr::tr("Failure storing build graph: Cannot create directory '%1'.") + .arg(dirPath)); + } + + if (QFile::exists(filePath) && !QFile::remove(filePath)) { + throw ErrorInfo(Tr::tr("Failure storing build graph: Cannot remove old file '%1'") + .arg(filePath)); + } + QBS_CHECK(!QFile::exists(filePath)); + std::unique_ptr file(new QFile(filePath)); + if (!file->open(QFile::WriteOnly)) { + throw ErrorInfo(Tr::tr("Failure storing build graph: " + "Cannot open file '%1' for writing: %2").arg(filePath, file->errorString())); + } + + m_stream.setDevice(file.get()); + m_file = std::move(file); + m_stream << QByteArray(qstrlen(QBS_PERSISTENCE_MAGIC), 0) << m_headData.projectConfig; + m_lastStoredObjectId = 0; + m_lastStoredStringId = 0; + m_lastStoredEnvId = 0; + m_lastStoredStringListId = 0; +} + +void PersistentPool::finalizeWriteStream() +{ + if (m_stream.status() != QDataStream::Ok) + throw ErrorInfo(Tr::tr("Failure serializing build graph.")); + m_stream.device()->seek(0); + m_stream << QByteArray(QBS_PERSISTENCE_MAGIC); + if (m_stream.status() != QDataStream::Ok) + throw ErrorInfo(Tr::tr("Failure serializing build graph.")); + const auto file = static_cast(m_stream.device()); + if (!file->flush()) { + file->close(); + file->remove(); + throw ErrorInfo(Tr::tr("Failure serializing build graph: %1").arg(file->errorString())); + } +} + +void PersistentPool::storeVariant(const QVariant &variant) +{ + const auto type = static_cast(variant.type()); + m_stream << type; + switch (type) { + case QMetaType::QString: + store(variant.toString()); + break; + case QMetaType::QStringList: + store(variant.toStringList()); + break; + case QMetaType::QVariantList: + store(variant.toList()); + break; + case QMetaType::QVariantMap: + store(variant.toMap()); + break; + default: + m_stream << variant; + } +} + +QVariant PersistentPool::loadVariant() +{ + const auto type = load(); + QVariant value; + switch (type) { + case QMetaType::QString: + value = load(); + break; + case QMetaType::QStringList: + value = load(); + break; + case QMetaType::QVariantList: + value = load(); + break; + case QMetaType::QVariantMap: + value = load(); + break; + default: + m_stream >> value; + } + return value; +} + +void PersistentPool::clear() +{ + m_loaded.clear(); + m_storageIndices.clear(); + m_stringStorage.clear(); + m_inverseStringStorage.clear(); +} + +void PersistentPool::doLoadValue(QString &s) +{ + m_stream >> s; +} + +void PersistentPool::doLoadValue(QStringList &l) +{ + int size; + m_stream >> size; + for (int i = 0; i < size; ++i) + l << load(); +} + +void PersistentPool::doLoadValue(QProcessEnvironment &env) +{ + const QStringList keys = load(); + for (const QString &key : keys) + env.insert(key, load()); +} + +void PersistentPool::doStoreValue(const QString &s) +{ + m_stream << s; +} + +void PersistentPool::doStoreValue(const QStringList &l) +{ + m_stream << l.size(); + for (const QString &s : l) + store(s); +} + +void PersistentPool::doStoreValue(const QProcessEnvironment &env) +{ + const QStringList &keys = env.keys(); + store(keys); + for (const QString &key : keys) + store(env.value(key)); +} + +const PersistentPool::PersistentObjectId PersistentPool::ValueNotFoundId; +const PersistentPool::PersistentObjectId PersistentPool::EmptyValueId; + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/persistence.h b/src/lib/corelib/tools/persistence.h new file mode 100644 index 00000000..e0031032 --- /dev/null +++ b/src/lib/corelib/tools/persistence.h @@ -0,0 +1,532 @@ +/**************************************************************************** +** +** 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 QBS_PERSISTENCE +#define QBS_PERSISTENCE + +#include "error.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +class NoBuildGraphError : public ErrorInfo +{ +public: + NoBuildGraphError(const QString &filePath); +}; + +template +struct PPHelper; + +class PersistentPool +{ +public: + PersistentPool(Logger &logger); + ~PersistentPool(); + + class HeadData + { + public: + QVariantMap projectConfig; + }; + + template void store(const T &value, const Types &...args) + { + PPHelper::store(value, this); + store(args...); + } + + template void load(T &value, Types &...args) + { + PPHelper::load(value, this); + load(args...); + } + template T load() { + T tmp; + PPHelper::load(tmp, this); + return tmp; + } + + enum OpType { Store, Load }; + template struct OpTypeHelper { }; + template struct OpTypeHelper + { + static void serializationOp(PersistentPool *pool, const T &value, const Types &...args) + { + pool->store(value, args...); + } + }; + template struct OpTypeHelper + { + static void serializationOp(PersistentPool *pool, T &value, Types &...args) + { + pool->load(value, args...); + } + }; + template void serializationOp(const T &value, + const Types &...args) + { + OpTypeHelper::serializationOp(this, value, args...); + } + template void serializationOp(T &value, + Types &...args) + { + OpTypeHelper::serializationOp(this, value, args...); + } + + void load(const QString &filePath); + void setupWriteStream(const QString &filePath); + void finalizeWriteStream(); + void clear(); + + const HeadData &headData() const { return m_headData; } + void setHeadData(const HeadData &hd) { m_headData = hd; } + +private: + using PersistentObjectId = int; + + template T *idLoad(); + template std::shared_ptr idLoadS(); + template T idLoadValue(); + + void doLoadValue(QString &s); + void doLoadValue(QStringList &l); + void doLoadValue(QProcessEnvironment &env); + + template void storeSharedObject(const T *object); + + void storeVariant(const QVariant &variant); + QVariant loadVariant(); + + template void idStoreValue(const T &value); + + void doStoreValue(const QString &s); + void doStoreValue(const QStringList &l); + void doStoreValue(const QProcessEnvironment &env); + + template std::vector &idStorage(); + template QHash &idMap(); + template PersistentObjectId &lastStoredId(); + + // Recursion termination + void store() {} + void load() {} + + static const PersistentObjectId ValueNotFoundId = -1; + static const PersistentObjectId EmptyValueId = -2; + + std::unique_ptr m_file; + QDataStream m_stream; + HeadData m_headData; + std::vector m_loadedRaw; + std::vector> m_loaded; + std::unordered_map m_storageIndices; + PersistentObjectId m_lastStoredObjectId = 0; + + std::vector m_stringStorage; + QHash m_inverseStringStorage; + PersistentObjectId m_lastStoredStringId = 0; + std::vector m_envStorage; + QHash m_inverseEnvStorage; + PersistentObjectId m_lastStoredEnvId = 0; + std::vector m_stringListStorage; + QHash m_inverseStringListStorage; + PersistentObjectId m_lastStoredStringListId = 0; + Logger &m_logger; + + template + friend struct PPHelper; +}; + +template inline const void *uniqueAddress(const T *t) { return t; } + +template inline void PersistentPool::storeSharedObject(const T *object) +{ + if (!object) { + m_stream << -1; + return; + } + const void * const addr = uniqueAddress(object); + const auto found = m_storageIndices.find(addr); + if (found == m_storageIndices.end()) { + PersistentObjectId id = m_lastStoredObjectId++; + m_storageIndices[addr] = id; + m_stream << id; + store(*object); + } else { + m_stream << found->second; + } +} + +template inline T *PersistentPool::idLoad() +{ + PersistentObjectId id; + m_stream >> id; + + if (id < 0) + return nullptr; + + if (id < static_cast(m_loadedRaw.size())) + return static_cast(m_loadedRaw.at(id)); + + auto i = m_loadedRaw.size(); + m_loadedRaw.resize(id + 1); + for (; i < m_loadedRaw.size(); ++i) + m_loadedRaw[i] = nullptr; + + const auto t = new T; + m_loadedRaw[id] = t; + load(*t); + return t; +} + +template<> inline std::vector &PersistentPool::idStorage() { return m_stringStorage; } +template<> inline QHash &PersistentPool::idMap() +{ + return m_inverseStringStorage; +} +template<> inline PersistentPool::PersistentObjectId &PersistentPool::lastStoredId() +{ + return m_lastStoredStringId; +} +template<> inline std::vector &PersistentPool::idStorage() +{ + return m_stringListStorage; +} +template<> inline QHash &PersistentPool::idMap() +{ + return m_inverseStringListStorage; +} +template<> inline PersistentPool::PersistentObjectId &PersistentPool::lastStoredId() +{ + return m_lastStoredStringListId; +} +template<> inline std::vector &PersistentPool::idStorage() +{ + return m_envStorage; +} +template<> inline QHash +&PersistentPool::idMap() +{ + return m_inverseEnvStorage; +} +template<> inline PersistentPool::PersistentObjectId +&PersistentPool::lastStoredId() +{ + return m_lastStoredEnvId; +} + +template inline std::shared_ptr PersistentPool::idLoadS() +{ + PersistentObjectId id; + m_stream >> id; + + if (id < 0) + return std::shared_ptr(); + + if (id < static_cast(m_loaded.size())) + return std::static_pointer_cast(m_loaded.at(id)); + + m_loaded.resize(id + 1); + const std::shared_ptr t = T::create(); + m_loaded[id] = t; + load(*t); + return t; +} + +template inline T PersistentPool::idLoadValue() +{ + int id; + m_stream >> id; + if (id == EmptyValueId) + return T(); + QBS_CHECK(id >= 0); + if (id >= static_cast(idStorage().size())) { + T value; + doLoadValue(value); + idStorage().resize(id + 1); + idStorage()[id] = value; + return value; + } + return idStorage().at(id); +} + +template +void PersistentPool::idStoreValue(const T &value) +{ + if (value.isEmpty()) { + m_stream << EmptyValueId; + return; + } + int id = idMap().value(value, ValueNotFoundId); + if (id < 0) { + id = lastStoredId()++; + idMap().insert(value, id); + m_stream << id; + doStoreValue(value); + } else { + m_stream << id; + } +} + +// We need a helper class template, because we require partial specialization for some of +// the aggregate types, which is not possible with function templates. +// The generic implementation assumes that T is of class type and has load() and store() +// member functions. +template +struct PPHelper +{ + static void store(const T &object, PersistentPool *pool) + { + const_cast(object).store(*pool); + } + static void load(T &object, PersistentPool *pool) + { + object.load(*pool); + } +}; + +/***** Specializations of Helper class *****/ + +template +struct PPHelper)>::value>> +{ + static void store(const T &value, PersistentPool *pool) + { + const_cast(value).template completeSerializationOp(*pool); + } + static void load(T &value, PersistentPool *pool) + { + value.template completeSerializationOp(*pool); + } +}; + +template struct PPHelper::value>> +{ + static void store(const T &value, PersistentPool *pool) { pool->m_stream << value; } + static void load(T &value, PersistentPool *pool) { pool->m_stream >> value; } +}; + +template<> struct PPHelper +{ + static void store(long value, PersistentPool *pool) { pool->m_stream << qint64(value); } + static void load(long &value, PersistentPool *pool) + { + qint64 v; + pool->m_stream >> v; + value = long(v); + } +}; + +template struct PPHelper::value>> +{ + using U = std::underlying_type_t; + static void store(const T &value, PersistentPool *pool) + { + pool->m_stream << static_cast(value); + } + static void load(T &value, PersistentPool *pool) + { + pool->m_stream >> reinterpret_cast(value); + } +}; + +template struct PPHelper> +{ + static void store(const std::shared_ptr &value, PersistentPool *pool) + { + pool->store(value.get()); + } + static void load(std::shared_ptr &value, PersistentPool *pool) + { + value = pool->idLoadS>(); + } +}; + +template struct PPHelper> +{ + static void store(const std::unique_ptr &value, PersistentPool *pool) + { + pool->store(value.get()); + } + static void load(std::unique_ptr &ptr, PersistentPool *pool) + { + ptr.reset(pool->idLoad>()); + } +}; + +template struct PPHelper +{ + static void store(const T *value, PersistentPool *pool) { pool->storeSharedObject(value); } + static void load(T* &value, PersistentPool *pool) { value = pool->idLoad(); } +}; + +template struct PPHelper::value + || std::is_same::value || std::is_same::value>> +{ + static void store(const T &v, PersistentPool *pool) { pool->idStoreValue(v); } + static void load(T &v, PersistentPool *pool) { v = pool->idLoadValue(); } +}; + +template<> struct PPHelper +{ + static void store(const QVariant &v, PersistentPool *pool) { pool->storeVariant(v); } + static void load(QVariant &v, PersistentPool *pool) { v = pool->loadVariant(); } +}; + +template<> struct PPHelper +{ + static void store(const QRegExp &re, PersistentPool *pool) { pool->store(re.pattern()); } + static void load(QRegExp &re, PersistentPool *pool) { re.setPattern(pool->load()); } +}; + +template struct PPHelper> +{ + static void store(const std::pair &pair, PersistentPool *pool) + { + pool->store(pair.first); + pool->store(pair.second); + } + static void load(std::pair &pair, PersistentPool *pool) + { + pool->load(pair.first); + pool->load(pair.second); + } +}; + +template struct PPHelper> +{ + using Int = typename QFlags::Int; + static void store(const QFlags &flags, PersistentPool *pool) + { + pool->store(flags); + } + static void load(QFlags &flags, PersistentPool *pool) + { + flags = QFlags(pool->load()); + } +}; + +template struct IsSimpleContainer : std::false_type { }; +template struct IsSimpleContainer> : std::true_type { }; +template struct IsSimpleContainer> : std::true_type { }; + +template struct PPHelper::value>> +{ + static void store(const T &container, PersistentPool *pool) + { + pool->store(int(container.size())); + for (auto it = container.cbegin(); it != container.cend(); ++it) + pool->store(*it); + } + static void load(T &container, PersistentPool *pool) + { + const int count = pool->load(); + container.clear(); + container.reserve(count); + for (int i = count; --i >= 0;) + container.push_back(pool->load()); + } +}; + +template struct IsKeyValueContainer : std::false_type { }; +template struct IsKeyValueContainer> : std::true_type { }; +template struct IsKeyValueContainer> : std::true_type { }; + +template +struct PPHelper::value>> +{ + static void store(const T &container, PersistentPool *pool) + { + pool->store(container.size()); + for (auto it = container.cbegin(); it != container.cend(); ++it) { + pool->store(it.key()); + pool->store(it.value()); + } + } + static void load(T &container, PersistentPool *pool) + { + container.clear(); + const int count = pool->load(); + for (int i = 0; i < count; ++i) { + const auto &key = pool->load(); + const auto &value = pool->load(); + container.insert(key, value); + } + } +}; + +template +struct PPHelper> +{ + static void store(const std::unordered_map &map, PersistentPool *pool) + { + pool->store(quint32(map.size())); + for (auto it = map.cbegin(); it != map.cend(); ++it) + pool->store(*it); + } + static void load(std::unordered_map &map, PersistentPool *pool) + { + map.clear(); + const auto count = pool->load(); + for (std::size_t i = 0; i < count; ++i) + map.insert(pool->load>()); + } +}; + +} // namespace Internal +} // namespace qbs + +#endif diff --git a/src/lib/corelib/tools/preferences.cpp b/src/lib/corelib/tools/preferences.cpp new file mode 100644 index 00000000..4db27175 --- /dev/null +++ b/src/lib/corelib/tools/preferences.cpp @@ -0,0 +1,204 @@ +#include + +/**************************************************************************** +** +** 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 "preferences.h" + +#include "buildoptions.h" +#include "hostosinfo.h" +#include "profile.h" +#include "stringconstants.h" + +namespace qbs { + +/*! + * \class Preferences + * \brief The \c Preferences class gives access to all general qbs preferences. + * If a non-empty \c profileName is given, the profile's preferences take precedence over global + * ones. Otherwise, the global preferences are used. + */ +Preferences::Preferences(Settings *settings, QString profileName) + : m_settings(settings), m_profile(std::move(profileName)) +{ +} + +Preferences::Preferences(Settings *settings, QVariantMap profileContents) + : m_settings(settings), m_profileContents(std::move(profileContents)) +{ +} + + +/*! + * \brief Returns true <=> colored output should be used for printing messages. + * This is only relevant for command-line frontends. + */ +bool Preferences::useColoredOutput() const +{ + return getPreference(QStringLiteral("useColoredOutput"), true).toBool(); +} + +/*! + * \brief Returns the number of parallel jobs to use for building. + * Uses a sensible default value if there is no such setting. + */ +int Preferences::jobs() const +{ + return getPreference(QStringLiteral("jobs"), BuildOptions::defaultMaxJobCount()).toInt(); +} + +/*! + * \brief Returns the shell to use for the "qbs shell" command. + * This is only relevant for command-line frontends. + */ +QString Preferences::shell() const +{ + return getPreference(QStringLiteral("shell")).toString(); +} + +/*! + * \brief Returns the default build directory used by Qbs if none is specified. + */ +QString Preferences::defaultBuildDirectory() const +{ + return getPreference(QStringLiteral("defaultBuildDirectory")).toString(); +} + +/*! + * \brief Returns the default echo mode used by Qbs if none is specified. + */ +CommandEchoMode Preferences::defaultEchoMode() const +{ + return commandEchoModeFromName(getPreference(QStringLiteral("defaultEchoMode")).toString()); +} + +/*! + * \brief Returns the list of paths where qbs looks for modules and imports. + * In addition to user-supplied locations, they will also be looked up at \c{baseDir}/share/qbs. + */ +QStringList Preferences::searchPaths(const QString &baseDir) const +{ + return pathList(Internal::StringConstants::qbsSearchPathsProperty(), + baseDir + QLatin1String("/share/qbs")); +} + +/*! + * \brief Returns the list of paths where qbs looks for plugins. + * In addition to user-supplied locations, they will be looked up at \c{baseDir}/qbs/plugins. + */ +QStringList Preferences::pluginPaths(const QString &baseDir) const +{ + return pathList(QStringLiteral("pluginsPath"), baseDir + QStringLiteral("/qbs/plugins")); +} + +/*! + * \brief Returns the per-pool job limits. + */ +JobLimits Preferences::jobLimits() const +{ + const QString prefix = QStringLiteral("preferences.jobLimit"); + JobLimits limits; + const auto keys = m_settings->allKeysWithPrefix(prefix, Settings::allScopes()); + for (const QString &key : keys) { + limits.setJobLimit(key, m_settings->value(prefix + QLatin1Char('.') + key, + Settings::allScopes()).toInt()); + } + const QString fullPrefix = prefix + QLatin1Char('.'); + if (!m_profile.isEmpty()) { + Profile p(m_profile, m_settings, m_profileContents); + const auto keys = p.allKeys(Profile::KeySelectionRecursive); + for (const QString &key : keys) { + if (!key.startsWith(fullPrefix)) + continue; + const QString jobPool = key.mid(fullPrefix.size()); + const int limit = p.value(key).toInt(); + if (limit >= 0) + limits.setJobLimit(jobPool, limit); + } + } + return limits; +} + +QVariant Preferences::getPreference(const QString &key, const QVariant &defaultValue) const +{ + static const QString keyPrefix = QStringLiteral("preferences"); + const QString fullKey = keyPrefix + QLatin1Char('.') + key; + const bool isSearchPaths = key == Internal::StringConstants::qbsSearchPathsProperty(); + if (!m_profile.isEmpty()) { + QVariant value = Profile(m_profile, m_settings).value(fullKey); + if (value.isValid()) { + if (isSearchPaths) { // Merge with top-level value. + value = value.toStringList() + m_settings->value( + fullKey, scopesForSearchPaths()).toStringList(); + } + return value; + } + } + + QVariant value = m_profileContents.value(keyPrefix).toMap().value(key); + if (value.isValid()) { + if (isSearchPaths) {// Merge with top-level value + value = value.toStringList() + m_settings->value( + fullKey, scopesForSearchPaths()).toStringList(); + } + return value; + } + + return m_settings->value(fullKey, + isSearchPaths ? scopesForSearchPaths() : Settings::allScopes(), + defaultValue); +} + +QStringList Preferences::pathList(const QString &key, const QString &defaultValue) const +{ + QStringList paths = getPreference(key).toStringList(); + paths << defaultValue; + return paths; +} + +bool Preferences::ignoreSystemSearchPaths() const +{ + return getPreference(QStringLiteral("ignoreSystemSearchPaths")).toBool(); +} + +Settings::Scopes Preferences::scopesForSearchPaths() const +{ + return ignoreSystemSearchPaths() ? Settings::UserScope : Settings::allScopes(); +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/preferences.h b/src/lib/corelib/tools/preferences.h new file mode 100644 index 00000000..2824ebf2 --- /dev/null +++ b/src/lib/corelib/tools/preferences.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** 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 QBS_PREFERENCES_H +#define QBS_PREFERENCES_H + +#include "qbs_export.h" + +#include "commandechomode.h" +#include "joblimits.h" +#include "settings.h" + +#include +#include + +namespace qbs { +class Settings; + +class QBS_EXPORT Preferences +{ +public: + explicit Preferences(Settings *settings, QString profileName = QString()); + Preferences(Settings *settings, QVariantMap profileContents); + + bool useColoredOutput() const; + int jobs() const; + QString shell() const; + QString defaultBuildDirectory() const; + CommandEchoMode defaultEchoMode() const; + QStringList searchPaths(const QString &baseDir = QString()) const; + QStringList pluginPaths(const QString &baseDir = QString()) const; + JobLimits jobLimits() const; + +private: + QVariant getPreference(const QString &key, const QVariant &defaultValue = QVariant()) const; + QStringList pathList(const QString &key, const QString &defaultValue) const; + + bool ignoreSystemSearchPaths() const; + Settings::Scopes scopesForSearchPaths() const; + + Settings *m_settings; + QString m_profile; + QVariantMap m_profileContents; +}; + +} // namespace qbs + + +#endif // Header guard diff --git a/src/lib/corelib/tools/processresult.cpp b/src/lib/corelib/tools/processresult.cpp new file mode 100644 index 00000000..d5591a7e --- /dev/null +++ b/src/lib/corelib/tools/processresult.cpp @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** 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 "processresult.h" +#include "processresult_p.h" + +#include +#include + +/*! + * \class SetupProjectParameters + * \brief The \c ProcessResult class describes a finished qbs process command. + */ + +namespace qbs { + +ProcessResult::ProcessResult() : d(new Internal::ProcessResultPrivate) +{ +} + +ProcessResult::ProcessResult(const ProcessResult &other) = default; + +ProcessResult &ProcessResult::operator=(const ProcessResult &other) = default; + +ProcessResult::~ProcessResult() = default; + +/*! + * \brief Returns true iff the command finished successfully. + */ +bool ProcessResult::success() const +{ + return d->success; +} + +/*! + * \brief Returns the file path of the executable that was run. + */ +QString ProcessResult::executableFilePath() const +{ + return d->executableFilePath; +} + +/*! + * \brief Returns the command-line arguments with which the command was invoked. + */ +QStringList ProcessResult::arguments() const +{ + return d->arguments; +} + +/*! + * \brief Returns the working directory of the invoked command. + */ +QString ProcessResult::workingDirectory() const +{ + return d->workingDirectory; +} + +/*! + * \brief Returns the error status of the process. If no error occurred, the value + * is \c Process::UnknownError. + */ +QProcess::ProcessError ProcessResult::error() const +{ + return d->error; +} + +/*! + * \brief Returns the exit code of the command. + */ +int ProcessResult::exitCode() const +{ + return d->exitCode; +} + +/*! + * \brief Returns the data the command wrote to the standard output channel. + */ +QStringList ProcessResult::stdOut() const +{ + return d->stdOut; +} + +/*! + * \brief Returns the data the command wrote to the standard error channel. + */ +QStringList ProcessResult::stdErr() const +{ + return d->stdErr; +} + +static QJsonValue processErrorToJson(QProcess::ProcessError error) +{ + switch (error) { + case QProcess::FailedToStart: return QLatin1String("failed-to-start"); + case QProcess::Crashed: return QLatin1String("crashed"); + case QProcess::Timedout: return QLatin1String("timed-out"); + case QProcess::WriteError: return QLatin1String("write-error"); + case QProcess::ReadError: return QLatin1String("read-error"); + case QProcess::UnknownError: return QStringLiteral("unknown-error"); + } + return {}; // For dumb compilers. +} + +QJsonObject qbs::ProcessResult::toJson() const +{ + return QJsonObject{ + {QStringLiteral("success"), success()}, + {QStringLiteral("executable-file-path"), executableFilePath()}, + {QStringLiteral("arguments"), QJsonArray::fromStringList(arguments())}, + {QStringLiteral("working-directory"), workingDirectory()}, + {QStringLiteral("error"), processErrorToJson(error())}, + {QStringLiteral("exit-code"), exitCode()}, + {QStringLiteral("stdout"), QJsonArray::fromStringList(stdOut())}, + {QStringLiteral("stderr"), QJsonArray::fromStringList(stdErr())} + }; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/processresult.h b/src/lib/corelib/tools/processresult.h new file mode 100644 index 00000000..92408aa3 --- /dev/null +++ b/src/lib/corelib/tools/processresult.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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 QBS_PROCESSRESULT_H +#define QBS_PROCESSRESULT_H + +#include "qbs_export.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QJsonObject; +class QString; +class QStringList; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class ProcessCommandExecutor; +class ProcessResultPrivate; +} + +class QBS_EXPORT ProcessResult +{ + friend class qbs::Internal::ProcessCommandExecutor; +public: + ProcessResult(); + ProcessResult(const ProcessResult &other); + ProcessResult &operator=(const ProcessResult &other); + ~ProcessResult(); + + QJsonObject toJson() const; + + bool success() const; + QString executableFilePath() const; + QStringList arguments() const; + QString workingDirectory() const; + QProcess::ProcessError error() const; + int exitCode() const; + QStringList stdOut() const; + QStringList stdErr() const; + +private: + QExplicitlySharedDataPointer d; +}; + +} // namespace qbs + +Q_DECLARE_METATYPE(qbs::ProcessResult) + +#endif // QBS_PROCESSRESULT_H diff --git a/src/lib/corelib/tools/processresult_p.h b/src/lib/corelib/tools/processresult_p.h new file mode 100644 index 00000000..69ccb859 --- /dev/null +++ b/src/lib/corelib/tools/processresult_p.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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 QBS_PROCESSRESULT_P_H +#define QBS_PROCESSRESULT_P_H + +#include +#include +#include + +namespace qbs { +namespace Internal { +class ProcessResultPrivate : public QSharedData +{ +public: + bool success = false; + + QString executableFilePath; + QStringList arguments; + QString workingDirectory; + + QProcess::ProcessError error = QProcess::ProcessError::UnknownError; + int exitCode = 0; + QStringList stdOut; + QStringList stdErr; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/tools/processutils.cpp b/src/lib/corelib/tools/processutils.cpp new file mode 100644 index 00000000..b27592f8 --- /dev/null +++ b/src/lib/corelib/tools/processutils.cpp @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** 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 "processutils.h" + +#if defined(Q_OS_WIN) +# define PSAPI_VERSION 1 // To use GetModuleFileNameEx from Psapi.lib on all Win versions. +# include +# include +#elif defined(Q_OS_DARWIN) +# include +#elif defined(Q_OS_LINUX) +# include "fileinfo.h" +# include +# include +#elif defined(Q_OS_BSD4) +# include +# include +# include +# include +# if !defined(Q_OS_NETBSD) +# include +# endif +#else +# error Missing implementation of processNameByPid for this platform. +#endif + +namespace qbs { +namespace Internal { + +QString processNameByPid(qint64 pid) +{ +#if defined(Q_OS_WIN) + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, DWORD(pid)); + if (!hProcess) + return {}; + wchar_t buf[UNICODE_STRING_MAX_CHARS]; + const DWORD length = GetModuleFileNameEx(hProcess, NULL, buf, sizeof(buf) / sizeof(wchar_t)); + CloseHandle(hProcess); + if (!length) + return {}; + QString name = QString::fromWCharArray(buf, length); + int i = name.lastIndexOf(QLatin1Char('\\')); + if (i >= 0) + name.remove(0, i + 1); + i = name.lastIndexOf(QLatin1Char('.')); + if (i >= 0) + name.truncate(i); + return name; +#elif defined(Q_OS_DARWIN) + char name[1024]; + proc_name(pid, name, sizeof(name) / sizeof(char)); + return QString::fromUtf8(name); +#elif defined(Q_OS_LINUX) + char exePath[64]; + char buf[PATH_MAX]; + memset(buf, 0, sizeof(buf)); + sprintf(exePath, "/proc/%lld/exe", pid); + if (readlink(exePath, buf, sizeof(buf)) < 0) + return {}; + return FileInfo::fileName(QString::fromUtf8(buf)); +#elif defined(Q_OS_BSD4) +# if defined(Q_OS_NETBSD) + struct kinfo_proc2 kp; + int mib[6] = { CTL_KERN, KERN_PROC2, KERN_PROC_PID, (int)pid, sizeof(struct kinfo_proc2), 1 }; +# elif defined(Q_OS_OPENBSD) + struct kinfo_proc kp; + int mib[6] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, (int)pid, sizeof(struct kinfo_proc), 1 }; +# else + struct kinfo_proc kp; + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, (int)pid }; +# endif + size_t len = sizeof(kp); + u_int mib_len = sizeof(mib)/sizeof(u_int); + + if (sysctl(mib, mib_len, &kp, &len, NULL, 0) < 0) + return {}; + +# if defined(Q_OS_OPENBSD) || defined(Q_OS_NETBSD) + if (kp.p_pid != pid) + return {}; + QString name = QFile::decodeName(kp.p_comm); +# else + if (kp.ki_pid != pid) + return {}; + QString name = QFile::decodeName(kp.ki_comm); +# endif + return name; + +#else + Q_UNUSED(pid); + return {}; +#endif +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/processutils.h b/src/lib/corelib/tools/processutils.h new file mode 100644 index 00000000..14ab1381 --- /dev/null +++ b/src/lib/corelib/tools/processutils.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** 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 QBS_PROCESSUTILS_H +#define QBS_PROCESSUTILS_H + +#include + +#include +#include + +namespace qbs { +namespace Internal { + +QString QBS_AUTOTEST_EXPORT processNameByPid(qint64 pid); + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROCESSUTILS_H + diff --git a/src/lib/corelib/tools/profile.cpp b/src/lib/corelib/tools/profile.cpp new file mode 100644 index 00000000..2eac2509 --- /dev/null +++ b/src/lib/corelib/tools/profile.cpp @@ -0,0 +1,257 @@ +/**************************************************************************** +** +** 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 "profile.h" +#include "qbsassert.h" +#include "settings.h" +#include "stringconstants.h" + +#include +#include + +namespace qbs { + +/*! + * \class Profile + * \brief The \c Profile class gives access to the settings of a given profile. + */ + + /*! + * \enum Profile::KeySelection + * This enum type specifies whether to enumerate keys recursively. + * \value KeySelectionRecursive Indicates that key enumeration should happen recursively, i.e. + * it should go up the base profile chain. + * \value KeySelectionNonRecursive Indicates that only keys directly attached to a profile + * should be listed. + */ + +/*! + * \brief Creates an object giving access to the settings for profile \c name. + */ +Profile::Profile(QString name, Settings *settings, QVariantMap profiles) + : m_name(std::move(name)), + m_settings(settings), + m_values(profiles.value(m_name).toMap()), + m_profiles(std::move(profiles)) +{ + QBS_ASSERT(m_name == cleanName(m_name), return); +} + +bool Profile::exists() const +{ + return m_name == fallbackName() || !m_values.empty() + || !m_settings->allKeysWithPrefix(profileKey(), Settings::allScopes()).empty(); +} + +/*! + * \brief Returns the value for property \c key in this profile. + */ +QVariant Profile::value(const QString &key, const QVariant &defaultValue, ErrorInfo *error) const +{ + try { + return possiblyInheritedValue(key, defaultValue, QStringList()); + } catch (const ErrorInfo &e) { + if (error) + *error = e; + return {}; + } +} + +/*! + * \brief Gives value \c value to the property \c key in this profile. + */ +void Profile::setValue(const QString &key, const QVariant &value) +{ + m_settings->setValue(fullyQualifiedKey(key), value); + + if (key == baseProfileKey()) { + QBS_ASSERT(value.toString() == cleanName(value.toString()), return); + } +} + +/*! + * \brief Removes a key and the associated value from this profile. + */ +void Profile::remove(const QString &key) +{ + m_settings->remove(fullyQualifiedKey(key)); +} + +/*! + * \brief Returns the name of this profile. + */ +QString Profile::name() const +{ + return m_name; +} + +/*! + * \brief Returns all property keys in this profile. + * If and only if selection is Profile::KeySelectionRecursive, this will also list keys defined + * in base profiles. + */ +QStringList Profile::allKeys(KeySelection selection, ErrorInfo *error) const +{ + try { + return allKeysInternal(selection, QStringList()); + } catch (const ErrorInfo &e) { + if (error) + *error = e; + return {}; + } +} + +/*! + * \brief Returns the name of this profile's base profile. + * The returned value is empty if the profile does not have a base profile. + */ +QString Profile::baseProfile() const +{ + return localValue(baseProfileKey()).toString(); +} + +/*! + * \brief Sets a new base profile for this profile. + */ +void Profile::setBaseProfile(const QString &baseProfile) +{ + setValue(baseProfileKey(), baseProfile); +} + +/*! + * \brief Removes this profile's base profile setting. + */ +void Profile::removeBaseProfile() +{ + remove(baseProfileKey()); +} + +/*! + * \brief Removes this profile from the settings. + */ +void Profile::removeProfile() +{ + m_settings->remove(profileKey()); +} + +/*! + * \brief Returns a string suitiable as a profile name. + * Removes all dots and replaces them with hyphens. + */ +QString Profile::cleanName(const QString &name) +{ + QString newName = name; + return newName.replace(QLatin1Char('.'), QLatin1Char('-')); +} + +QString Profile::profileKey() const +{ + return Internal::StringConstants::profilesSettingsPrefix() + m_name; +} + +QString Profile::baseProfileKey() +{ + return Internal::StringConstants::baseProfileProperty(); +} + +void Profile::checkBaseProfileExistence(const Profile &baseProfile) const +{ + if (!baseProfile.exists()) + throw ErrorInfo(Internal::Tr::tr("Profile \"%1\" has a non-existent base profile \"%2\".").arg( + name(), baseProfile.name())); +} + +QVariant Profile::localValue(const QString &key) const +{ + QVariant val = m_values.value(key); + if (!val.isValid()) + val = m_settings->value(fullyQualifiedKey(key), Settings::allScopes()); + return val; +} + +QString Profile::fullyQualifiedKey(const QString &key) const +{ + return profileKey() + QLatin1Char('.') + key; +} + +QVariant Profile::possiblyInheritedValue(const QString &key, const QVariant &defaultValue, + QStringList profileChain) const +{ + extendAndCheckProfileChain(profileChain); + const QVariant v = localValue(key); + if (v.isValid()) + return v; + const QString baseProfileName = baseProfile(); + if (baseProfileName.isEmpty()) + return defaultValue; + Profile parentProfile(baseProfileName, m_settings, m_profiles); + checkBaseProfileExistence(parentProfile); + return parentProfile.possiblyInheritedValue(key, defaultValue, profileChain); +} + +QStringList Profile::allKeysInternal(Profile::KeySelection selection, + QStringList profileChain) const +{ + extendAndCheckProfileChain(profileChain); + QStringList keys = m_values.keys(); + if (keys.empty()) + keys = m_settings->allKeysWithPrefix(profileKey(), Settings::allScopes()); + if (selection == KeySelectionNonRecursive) + return keys; + const QString baseProfileName = baseProfile(); + if (baseProfileName.isEmpty()) + return keys; + Profile parentProfile(baseProfileName, m_settings, m_profiles); + checkBaseProfileExistence(parentProfile); + keys += parentProfile.allKeysInternal(KeySelectionRecursive, profileChain); + keys.removeDuplicates(); + keys.removeOne(baseProfileKey()); + keys.sort(); + return keys; +} + +void Profile::extendAndCheckProfileChain(QStringList &chain) const +{ + chain << m_name; + if (Q_UNLIKELY(chain.count(m_name) > 1)) { + throw ErrorInfo(Internal::Tr::tr("Circular profile inheritance. Cycle is '%1'.") + .arg(chain.join(QLatin1String(" -> ")))); + } +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/profile.h b/src/lib/corelib/tools/profile.h new file mode 100644 index 00000000..0eee23ae --- /dev/null +++ b/src/lib/corelib/tools/profile.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** 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 QBS_PROFILE_H +#define QBS_PROFILE_H + +#include "qbs_export.h" + +#include +#include + +QT_BEGIN_NAMESPACE +class QStringList; +QT_END_NAMESPACE + +namespace qbs { +class ErrorInfo; +class Settings; + +class QBS_EXPORT Profile +{ +public: + Profile(QString name, Settings *settings, QVariantMap profiles = QVariantMap()); + + bool exists() const; + QVariant value(const QString &key, const QVariant &defaultValue = QVariant(), + ErrorInfo *error = nullptr) const; + void setValue(const QString &key, const QVariant &value); + void remove(const QString &key); + + QString name() const; + + QString baseProfile() const; + void setBaseProfile(const QString &baseProfile); + void removeBaseProfile(); + + void removeProfile(); + + enum KeySelection { KeySelectionRecursive, KeySelectionNonRecursive }; + QStringList allKeys(KeySelection selection, ErrorInfo *error = nullptr) const; + + static QString cleanName(const QString &name); + + static QString fallbackName() { return QStringLiteral("none"); } + +private: + static QString baseProfileKey(); + void checkBaseProfileExistence(const Profile &baseProfile) const; + QString profileKey() const; + QVariant localValue(const QString &key) const; + QString fullyQualifiedKey(const QString &key) const; + QVariant possiblyInheritedValue(const QString &key, const QVariant &defaultValue, + QStringList profileChain) const; + QStringList allKeysInternal(KeySelection selection, QStringList profileChain) const; + void extendAndCheckProfileChain(QStringList &chain) const; + + QString m_name; + Settings *m_settings; + QVariantMap m_values; + QVariantMap m_profiles; +}; + +namespace Internal { +// Exported for autotests. +class QBS_EXPORT TemporaryProfile { +public: + TemporaryProfile(const QString &name, Settings *settings) : p(name, settings) {} + ~TemporaryProfile() { p.removeProfile(); } + + Profile p; +}; +} // namespace Internal + +} // namespace qbs + +#endif // Header guard diff --git a/src/lib/corelib/tools/profiling.cpp b/src/lib/corelib/tools/profiling.cpp new file mode 100644 index 00000000..7e3559b5 --- /dev/null +++ b/src/lib/corelib/tools/profiling.cpp @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** 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 "profiling.h" + +#include +#include + +#include + +namespace qbs { +namespace Internal { + +class TimedActivityLogger::TimedActivityLoggerPrivate +{ +public: + Logger logger; + QString activity; + QElapsedTimer timer; +}; + +TimedActivityLogger::TimedActivityLogger(const Logger &logger, const QString &activity, + bool enabled) + : d(nullptr) +{ + if (!enabled) + return; + d = new TimedActivityLoggerPrivate; + d->logger = logger; + d->activity = activity; + d->logger.qbsLog(LoggerInfo, true) << Tr::tr("Starting activity '%2'.").arg(activity); + d->timer.start(); +} + +void TimedActivityLogger::finishActivity() +{ + if (!d) + return; + const QString timeString = elapsedTimeString(d->timer.elapsed()); + d->logger.qbsLog(LoggerInfo, true) + << Tr::tr("Activity '%2' took %3.").arg(d->activity, timeString); + delete d; + d = nullptr; +} + +TimedActivityLogger::~TimedActivityLogger() +{ + finishActivity(); +} + +AccumulatingTimer::AccumulatingTimer(qint64 *elapsedTime) : m_elapsedTime(elapsedTime) +{ + if (elapsedTime) + m_timer.start(); +} + +AccumulatingTimer::~AccumulatingTimer() +{ + stop(); +} + +void AccumulatingTimer::stop() +{ + if (!m_timer.isValid()) + return; + *m_elapsedTime += m_timer.elapsed(); + m_timer.invalidate(); +} + +QString elapsedTimeString(qint64 elapsedTimeInMs) +{ + qint64 ms = elapsedTimeInMs; + qint64 s = ms/1000; + ms -= s*1000; + qint64 m = s/60; + s -= m*60; + const qint64 h = m/60; + m -= h*60; + QString timeString = QStringLiteral("%1ms").arg(ms); + if (h || m || s) + timeString.prepend(QStringLiteral("%1s, ").arg(s)); + if (h || m) + timeString.prepend(QStringLiteral("%1m, ").arg(m)); + if (h) + timeString.prepend(QStringLiteral("%1h, ").arg(h)); + return timeString; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/profiling.h b/src/lib/corelib/tools/profiling.h new file mode 100644 index 00000000..89f862ff --- /dev/null +++ b/src/lib/corelib/tools/profiling.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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 QBS_PROFILING_H +#define QBS_PROFILING_H + +#include + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class Logger; + +QString elapsedTimeString(qint64 elapsedTimeInMs); + +class TimedActivityLogger +{ +public: + TimedActivityLogger(const Logger &logger, const QString &activity, bool enabled); + void finishActivity(); + ~TimedActivityLogger(); + +private: + class TimedActivityLoggerPrivate; + TimedActivityLoggerPrivate *d; +}; + +class AccumulatingTimer +{ +public: + AccumulatingTimer(qint64 *elapsedTime); + ~AccumulatingTimer(); + void stop(); + +private: + QElapsedTimer m_timer; + qint64 * const m_elapsedTime; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Header guard diff --git a/src/lib/corelib/tools/progressobserver.cpp b/src/lib/corelib/tools/progressobserver.cpp new file mode 100644 index 00000000..49787fd2 --- /dev/null +++ b/src/lib/corelib/tools/progressobserver.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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 "progressobserver.h" + +namespace qbs { +namespace Internal { + +/*! + * \class ProgressObserver + * The \c ProgressObserver class is used in long running qbs operations. It serves two purposes: + * Firstly, it allows operations to indicate progress to a client. Secondly, a client can + * signal to an operation that is should exit prematurely. + * Clients of the qbs library are supposed to subclass this class and implement the virtual + * functions in a way that lets users know about the current operation and its progress. + */ + +/*! + * \fn virtual void initialize(const QString &task, int maximum) = 0 + * \brief Indicates that a new operation is starting. + * Library code calls this function to indicate that it is starting a new task. + * The \a task parameter is a textual description of that task suitable for presentation to a user. + * The \a maximum parameter is an estimate of the maximum effort the operation is going to take. + * This is helpful if the client wants to set up some sort of progress bar showing the + * percentage of the work already done. + */ + +/*! + * \fn virtual void setProgressValue(int value) = 0 + * \brief Sets the new progress value. + * Library code calls this function to indicate that the current operation has progressed. + * It will try hard to ensure that \a value will not exceed \c maximum(). + * \sa ProgressObserver::maximum(). + */ + +/*! + * \fn virtual int progressValue() = 0 + * \brief The current progress value. + * Will typically reflect the \a value from the last call to \c setProgressValue() and should not + * exceed \c maximum(). + * \sa setProgressvalue() + * \sa maximum() + */ + +void ProgressObserver::incrementProgressValue(int increment) +{ + setProgressValue(progressValue() + increment); +} + +/*! + * \fn virtual bool canceled() const = 0 + * \brief Indicates whether the current operation should be canceled. + * Library code will periodically call this function and abort the current operation + * if it returns true. + */ + +/*! + * \fn virtual int maximum() const = 0 + * \brief The expected maximum progress value. + * This will typically be the value of \c maximum passed to \c initialize(). + * \sa ProgressObserver::initialize() + */ + +void ProgressObserver::setFinished() +{ + setProgressValue(maximum()); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/progressobserver.h b/src/lib/corelib/tools/progressobserver.h new file mode 100644 index 00000000..fc49d9ee --- /dev/null +++ b/src/lib/corelib/tools/progressobserver.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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 QBS_PROGRESSOBSERVER_H +#define QBS_PROGRESSOBSERVER_H + +#include + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +class ProgressObserver +{ +public: + virtual ~ProgressObserver() = default; + + virtual void initialize(const QString &task, int maximum) = 0; + virtual void setProgressValue(int value) = 0; + virtual int progressValue() = 0; + virtual bool canceled() const = 0; + virtual void setMaximum(int maximum) = 0; + virtual int maximum() const = 0; + + void incrementProgressValue(int increment = 1); + + // Call this to ensure that the progress bar always goes to 100%. + void setFinished(); +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PROGRESSOBSERVER_H diff --git a/src/lib/corelib/tools/projectgeneratormanager.cpp b/src/lib/corelib/tools/projectgeneratormanager.cpp new file mode 100644 index 00000000..53d72bcd --- /dev/null +++ b/src/lib/corelib/tools/projectgeneratormanager.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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 "projectgeneratormanager.h" + +#include +#include +#include +#include + +#include +#include + +namespace qbs { + +using namespace Internal; + +ProjectGeneratorManager::~ProjectGeneratorManager() = default; + +ProjectGeneratorManager *ProjectGeneratorManager::instance() +{ + static ProjectGeneratorManager generatorPlugin; + return &generatorPlugin; +} + +ProjectGeneratorManager::ProjectGeneratorManager() = default; + +QStringList ProjectGeneratorManager::loadedGeneratorNames() +{ + return instance()->m_generators.keys(); +} + +std::shared_ptr ProjectGeneratorManager::findGenerator(const QString &generatorName) +{ + return instance()->m_generators.value(generatorName); +} + +void ProjectGeneratorManager::registerGenerator(const std::shared_ptr &generator) +{ + if (!findGenerator(generator->generatorName())) + instance()->m_generators.insert(generator->generatorName(), generator); +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/projectgeneratormanager.h b/src/lib/corelib/tools/projectgeneratormanager.h new file mode 100644 index 00000000..469d650b --- /dev/null +++ b/src/lib/corelib/tools/projectgeneratormanager.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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_PROJECTGENERATORMANAGER_H +#define QBS_PROJECTGENERATORMANAGER_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE +class QStringList; +QT_END_NAMESPACE + +namespace qbs { +class ProjectGenerator; + +class QBS_EXPORT ProjectGeneratorManager +{ +public: + ~ProjectGeneratorManager(); + static ProjectGeneratorManager *instance(); + static QStringList loadedGeneratorNames(); + static std::shared_ptr findGenerator(const QString &generatorName); + static void registerGenerator(const std::shared_ptr &generator); + +private: + ProjectGeneratorManager(); + +private: + QMap > m_generators; +}; + +} // namespace qbs + +#endif diff --git a/src/lib/corelib/tools/qbs_export.h b/src/lib/corelib/tools/qbs_export.h new file mode 100644 index 00000000..164aa418 --- /dev/null +++ b/src/lib/corelib/tools/qbs_export.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** 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 QBS_EXPORT_H +#define QBS_EXPORT_H + +#if defined(_WIN32) || defined(WIN32) +# define QBS_DECL_EXPORT __declspec(dllexport) +# define QBS_DECL_IMPORT __declspec(dllimport) +#else +# define QBS_DECL_EXPORT __attribute__((visibility("default"))) +# define QBS_DECL_IMPORT __attribute__((visibility("default"))) +# endif + +#ifdef QBS_STATIC_LIB +# define QBS_EXPORT +# define QBS_AUTOTEST_EXPORT +#else +# ifdef QBS_LIBRARY +# define QBS_EXPORT QBS_DECL_EXPORT +# ifdef QBS_ENABLE_UNIT_TESTS +# define QBS_AUTOTEST_EXPORT QBS_DECL_EXPORT +# else +# define QBS_AUTOTEST_EXPORT +# endif +# else +# define QBS_EXPORT QBS_DECL_IMPORT +# ifdef QBS_ENABLE_UNIT_TESTS +# define QBS_AUTOTEST_EXPORT QBS_DECL_IMPORT +# else +# define QBS_AUTOTEST_EXPORT +# endif +# endif +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +#pragma warning(disable: 4251) +#endif + +#endif // Include guard. diff --git a/src/lib/corelib/tools/qbsassert.cpp b/src/lib/corelib/tools/qbsassert.cpp new file mode 100644 index 00000000..a6cfd610 --- /dev/null +++ b/src/lib/corelib/tools/qbsassert.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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 "qbsassert.h" +#include "error.h" + +#include + +namespace qbs { +namespace Internal { + +void writeAssertLocation(const char *condition, const char *file, int line) +{ + qDebug("SOFT ASSERT: %s in %s:%d", condition, file, line); +} + +void throwAssertLocation(const char *condition, const char *file, int line) +{ + throw ErrorInfo(QStringLiteral("ASSERT: %1").arg(QLatin1String(condition)), + CodeLocation(QString::fromLocal8Bit(file), line, -1, false), true); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/qbsassert.h b/src/lib/corelib/tools/qbsassert.h new file mode 100644 index 00000000..e8dfcacf --- /dev/null +++ b/src/lib/corelib/tools/qbsassert.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** 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 QBS_QBSASSERT_H +#define QBS_QBSASSERT_H + +#include "qbs_export.h" + +namespace qbs { +namespace Internal { + +QBS_EXPORT void writeAssertLocation(const char *condition, const char *file, int line); +[[noreturn]] QBS_EXPORT void throwAssertLocation(const char *condition, const char *file, int line); + +} // namespace Internal +} // namespace qbs + +#define QBS_ASSERT(cond, action)\ + if (Q_LIKELY(cond)) {} else {\ + ::qbs::Internal::writeAssertLocation(#cond, __FILE__, __LINE__); action;\ + } do {} while (0) + +// The do {} while (0) is here to enforce the use of a semicolon after QBS_ASSERT. +// action can also be continue or break. Copied from qtcassert.h in Qt Creator. + +#define QBS_CHECK(cond)\ + do {\ + if (Q_LIKELY(cond)) {} else {\ + ::qbs::Internal::throwAssertLocation(#cond, __FILE__, __LINE__);\ + }\ + } while (0) + +#endif // QBS_QBSASSERT_H diff --git a/src/lib/corelib/tools/qbspluginmanager.cpp b/src/lib/corelib/tools/qbspluginmanager.cpp new file mode 100644 index 00000000..d4e92e22 --- /dev/null +++ b/src/lib/corelib/tools/qbspluginmanager.cpp @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** 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 "qbspluginmanager.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace qbs { +namespace Internal { + +struct QbsPlugin { + QbsPluginLoadFunction load; + QbsPluginUnloadFunction unload; + bool loaded; +}; + +class QbsPluginManagerPrivate { +public: + std::vector staticPlugins; + std::vector libs; +}; + +QbsPluginManager::QbsPluginManager() + : d(new QbsPluginManagerPrivate) +{ +} + +QbsPluginManager::~QbsPluginManager() +{ + unloadStaticPlugins(); + + for (QLibrary * const lib : qAsConst(d->libs)) { + auto unload = reinterpret_cast(lib->resolve("QbsPluginUnload")); + if (unload) + unload(); + lib->unload(); + delete lib; + } +} + +QbsPluginManager *QbsPluginManager::instance() +{ + static QbsPluginManager instance; + return &instance; +} + +void QbsPluginManager::registerStaticPlugin(QbsPluginLoadFunction load, + QbsPluginUnloadFunction unload) +{ + if (none_of(d->staticPlugins, [&load](const QbsPlugin &p) { return p.load == load; })) + d->staticPlugins.push_back(QbsPlugin { load, unload, false }); +} + +void QbsPluginManager::loadStaticPlugins() +{ + for (const auto &plugin : d->staticPlugins) { + if (!plugin.loaded && plugin.load) + plugin.load(); + } +} + +void QbsPluginManager::unloadStaticPlugins() +{ + for (auto &plugin : d->staticPlugins) { + if (plugin.loaded && plugin.unload) + plugin.unload(); + } + + d->staticPlugins.clear(); +} + +void QbsPluginManager::loadPlugins(const std::vector &pluginPaths, + const Logger &logger) +{ + QStringList filters; + + if (HostOsInfo::isWindowsHost()) + filters << QStringLiteral("*.dll"); + else if (HostOsInfo::isMacosHost()) + filters << QStringLiteral("*.bundle") << QStringLiteral("*.dylib"); + else + filters << QStringLiteral("*.so"); + + for (const std::string &pluginPath : pluginPaths) { + const QString qtPluginPath = QString::fromStdString(pluginPath); + qCDebug(lcPluginManager) << "loading plugins from" + << QDir::toNativeSeparators(qtPluginPath); + QDirIterator it(qtPluginPath, filters, QDir::Files); + while (it.hasNext()) { + const QString fileName = it.next(); + std::unique_ptr lib(new QLibrary(fileName)); + if (!lib->load()) { + logger.qbsWarning() << Tr::tr("plugin manager: Cannot load plugin '%1': %2") + .arg(QDir::toNativeSeparators(fileName), lib->errorString()); + continue; + } + + auto load = reinterpret_cast(lib->resolve("QbsPluginLoad")); + if (load) { + load(); + qCDebug(lcPluginManager) << "plugin" << QDir::toNativeSeparators(fileName) + << "loaded."; + d->libs.push_back(lib.release()); + } else { + logger.qbsWarning() << Tr::tr("plugin manager: not a qbs plugin"); + } + } + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/qbspluginmanager.h b/src/lib/corelib/tools/qbspluginmanager.h new file mode 100644 index 00000000..1113c413 --- /dev/null +++ b/src/lib/corelib/tools/qbspluginmanager.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** 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_PLUGINS_H +#define QBS_PLUGINS_H + +#include "qbs_export.h" + +#include +#include +#include + +namespace qbs { +namespace Internal { +class Logger; + +typedef void (*QbsPluginLoadFunction)(); +typedef void (*QbsPluginUnloadFunction)(); + +class QbsPluginManagerPrivate; + +class QBS_EXPORT QbsPluginManager +{ +public: + ~QbsPluginManager(); + static QbsPluginManager *instance(); + void registerStaticPlugin(QbsPluginLoadFunction, QbsPluginUnloadFunction); + void loadStaticPlugins(); + void unloadStaticPlugins(); + void loadPlugins(const std::vector &paths, const Logger &logger); + +protected: + QbsPluginManager(); + +private: + std::unique_ptr d; +}; + +} // namespace Internal +} // namespace qbs + +#ifdef QBS_STATIC_LIB +#define QBS_REGISTER_STATIC_PLUGIN(exportmacro, name, load, unload) \ + extern "C" bool qbs_static_plugin_register_##name = [] { \ + qbs::Internal::QbsPluginManager::instance()->registerStaticPlugin(load, unload); \ + return true; \ + }(); +#else +#define QBS_REGISTER_STATIC_PLUGIN(exportmacro, name, load, unload) \ + exportmacro void QbsPluginLoad() { load(); } \ + exportmacro void QbsPluginUnload() { unload(); } +#endif + +#endif diff --git a/src/lib/corelib/tools/qbsprocess.cpp b/src/lib/corelib/tools/qbsprocess.cpp new file mode 100644 index 00000000..52ce3f25 --- /dev/null +++ b/src/lib/corelib/tools/qbsprocess.cpp @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** 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 "qbsprocess.h" + +#include "launcherinterface.h" +#include "launchersocket.h" +#include "qbsassert.h" + +#include + +#include +#include + +namespace qbs { +namespace Internal { + +QbsProcess::QbsProcess(QObject *parent) : QObject(parent) +{ + connect(LauncherInterface::socket(), &LauncherSocket::ready, + this, &QbsProcess::handleSocketReady); + connect(LauncherInterface::socket(), &LauncherSocket::errorOccurred, + this, &QbsProcess::handleSocketError); + connect(LauncherInterface::socket(), &LauncherSocket::packetArrived, + this, &QbsProcess::handlePacket); +} + +void QbsProcess::start(const QString &command, const QStringList &arguments) +{ + if (m_socketError) { + m_error = QProcess::FailedToStart; + emit error(m_error); + return; + } + m_command = command; + m_arguments = arguments; + m_state = QProcess::Starting; + if (LauncherInterface::socket()->isReady()) + doStart(); +} + +void QbsProcess::doStart() +{ + m_state = QProcess::Running; + StartProcessPacket p(token()); + p.command = m_command; + p.arguments = m_arguments; + p.env = m_environment.toStringList(); + p.workingDir = m_workingDirectory; + sendPacket(p); +} + +void QbsProcess::cancel() +{ + switch (m_state) { + case QProcess::NotRunning: + break; + case QProcess::Starting: + m_errorString = Tr::tr("Process canceled before it was started."); + m_error = QProcess::FailedToStart; + m_state = QProcess::NotRunning; + emit error(m_error); + break; + case QProcess::Running: + sendPacket(StopProcessPacket(token())); + break; + } +} + +QByteArray QbsProcess::readAllStandardOutput() +{ + return readAndClear(m_stdout); +} + +QByteArray QbsProcess::readAllStandardError() +{ + return readAndClear(m_stderr); +} + +void QbsProcess::sendPacket(const LauncherPacket &packet) +{ + LauncherInterface::socket()->sendData(packet.serialize()); +} + +QByteArray QbsProcess::readAndClear(QByteArray &data) +{ + const QByteArray tmp = data; + data.clear(); + return tmp; +} + +void QbsProcess::handlePacket(LauncherPacketType type, quintptr token, const QByteArray &payload) +{ + if (token != this->token()) + return; + switch (type) { + case LauncherPacketType::ProcessError: + handleErrorPacket(payload); + break; + case LauncherPacketType::ProcessFinished: + handleFinishedPacket(payload); + break; + default: + QBS_ASSERT(false, break); + } +} + +void QbsProcess::handleSocketReady() +{ + m_socketError = false; + if (m_state == QProcess::Starting) + doStart(); +} + +void QbsProcess::handleSocketError(const QString &message) +{ + m_socketError = true; + m_errorString = Tr::tr("Internal socket error: %1").arg(message); + if (m_state != QProcess::NotRunning) { + m_state = QProcess::NotRunning; + m_error = QProcess::FailedToStart; + emit error(m_error); + } +} + +void QbsProcess::handleErrorPacket(const QByteArray &packetData) +{ + QBS_ASSERT(m_state != QProcess::NotRunning, return); + const auto packet = LauncherPacket::extractPacket(token(), packetData); + m_error = packet.error; + m_errorString = packet.errorString; + m_state = QProcess::NotRunning; + emit error(m_error); +} + +void QbsProcess::handleFinishedPacket(const QByteArray &packetData) +{ + QBS_ASSERT(m_state == QProcess::Running, return); + m_state = QProcess::NotRunning; + const auto packet = LauncherPacket::extractPacket(token(), packetData); + m_exitCode = packet.exitCode; + m_stdout = packet.stdOut; + m_stderr = packet.stdErr; + m_errorString = packet.errorString; + emit finished(m_exitCode); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/qbsprocess.h b/src/lib/corelib/tools/qbsprocess.h new file mode 100644 index 00000000..2181818f --- /dev/null +++ b/src/lib/corelib/tools/qbsprocess.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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 QBS_QBSPROCESS_H +#define QBS_QBSPROCESS_H + +#include "launcherpackets.h" + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +class QbsProcess : public QObject +{ + Q_OBJECT +public: + explicit QbsProcess(QObject *parent = nullptr); + + QProcess::ProcessState state() const { return m_state; } + void setProcessEnvironment(const QProcessEnvironment &env) { m_environment = env; } + void setWorkingDirectory(const QString &workingDir) { m_workingDirectory = workingDir; } + QString workingDirectory() const { return m_workingDirectory; } + void start(const QString &command, const QStringList &arguments); + void cancel(); + QByteArray readAllStandardOutput(); + QByteArray readAllStandardError(); + int exitCode() const { return m_exitCode; } + QProcess::ProcessError error() const { return m_error; } + QString errorString() const { return m_errorString; } + +signals: + void error(QProcess::ProcessError error); + void finished(int exitCode); + +private: + void doStart(); + void sendPacket(const LauncherPacket &packet); + QByteArray readAndClear(QByteArray &data); + + void handleSocketError(const QString &message); + void handlePacket(qbs::Internal::LauncherPacketType type, quintptr token, + const QByteArray &payload); + void handleErrorPacket(const QByteArray &packetData); + void handleFinishedPacket(const QByteArray &packetData); + void handleSocketReady(); + + quintptr token() const { return reinterpret_cast(this); } + + QString m_command; + QStringList m_arguments; + QProcessEnvironment m_environment; + QString m_workingDirectory; + QByteArray m_stdout; + QByteArray m_stderr; + QString m_errorString; + QProcess::ProcessError m_error = QProcess::UnknownError; + QProcess::ProcessState m_state = QProcess::NotRunning; + int m_exitCode = 0; + int m_connectionAttempts = 0; + bool m_socketError = false; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBSPROCESS_H diff --git a/src/lib/corelib/tools/qttools.cpp b/src/lib/corelib/tools/qttools.cpp new file mode 100644 index 00000000..ffd336d5 --- /dev/null +++ b/src/lib/corelib/tools/qttools.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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 "qttools.h" + +#include + +QT_BEGIN_NAMESPACE + +uint qHash(const QStringList &list) +{ + uint s = 0; + for (const QString &n : list) + s ^= qHash(n) + 0x9e3779b9 + (s << 6) + (s >> 2); + return s; +} + +uint qHash(const QProcessEnvironment &env) +{ + return qHash(env.toStringList()); +} + +QT_END_NAMESPACE diff --git a/src/lib/corelib/tools/qttools.h b/src/lib/corelib/tools/qttools.h new file mode 100644 index 00000000..2d4a3854 --- /dev/null +++ b/src/lib/corelib/tools/qttools.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** 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 QBSQTTOOLS_H +#define QBSQTTOOLS_H + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE +class QProcessEnvironment; +QT_END_NAMESPACE + +#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) +#define QBS_SKIP_EMPTY_PARTS QString::SkipEmptyParts +#else +#define QBS_SKIP_EMPTY_PARTS Qt::SkipEmptyParts +#endif + +namespace std { +#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) +template<> struct hash { + std::size_t operator()(const QString &s) const { return qHash(s); } +}; +#endif + +template struct hash> +{ + size_t operator()(const pair &x) const + { + return std::hash()(x.first) ^ std::hash()(x.second); + } +}; +} // namespace std + +QT_BEGIN_NAMESPACE + +uint qHash(const QStringList &list); +uint qHash(const QProcessEnvironment &env); + +#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) +namespace Qt { +inline QTextStream &endl(QTextStream &stream) { return stream << QT_PREPEND_NAMESPACE(endl); } +} // namespace Qt +#endif + +QT_END_NAMESPACE + +namespace qbs { + +template +QSet toSet(const QList &list) +{ +#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) + return list.toSet(); +#else + return QSet(list.begin(), list.end()); +#endif +} + +template +QList toList(const QSet &set) +{ +#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) + return set.toList(); +#else + return QList(set.begin(), set.end()); +#endif +} + +template +QHash &unite(QHash &h, const QHash &other) +{ +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) + return h.unite(other); +#else + h.insert(other); + return h; +#endif +} + +} // namespace qbs + +#endif // QBSQTTOOLS_H diff --git a/src/lib/corelib/tools/scannerpluginmanager.cpp b/src/lib/corelib/tools/scannerpluginmanager.cpp new file mode 100644 index 00000000..378f0e11 --- /dev/null +++ b/src/lib/corelib/tools/scannerpluginmanager.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 "scannerpluginmanager.h" + +#include + +#include + +namespace qbs { +namespace Internal { + +class ScannerPluginManagerPrivate { +public: + std::map> scannerPlugins; +}; + +ScannerPluginManager::~ScannerPluginManager() = default; + +ScannerPluginManager *ScannerPluginManager::instance() +{ + static ScannerPluginManager scannerPlugin; + return &scannerPlugin; +} + +ScannerPluginManager::ScannerPluginManager() + : d(new ScannerPluginManagerPrivate) +{ +} + +std::vector ScannerPluginManager::scannersForFileTag(const FileTag &fileTag) +{ + auto it = instance()->d->scannerPlugins.find(fileTag); + if (it != instance()->d->scannerPlugins.cend()) + return it->second; + return {}; +} + +void ScannerPluginManager::registerPlugins(ScannerPlugin **plugins) +{ + for (int i = 0; plugins[i] != nullptr; ++i) { + const FileTags &fileTags = FileTags::fromStringList( + QString::fromLatin1(plugins[i]->fileTags).split(QLatin1Char(','))); + for (const FileTag &tag : fileTags) + d->scannerPlugins[tag].push_back(plugins[i]); + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/scannerpluginmanager.h b/src/lib/corelib/tools/scannerpluginmanager.h new file mode 100644 index 00000000..d6dfce86 --- /dev/null +++ b/src/lib/corelib/tools/scannerpluginmanager.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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 QBS_PLUGINS_SCANNER_H +#define QBS_PLUGINS_SCANNER_H + +#include "qbs_export.h" + +#include +#include + +class ScannerPlugin; + +namespace qbs { +namespace Internal { + +class FileTag; +class ScannerPluginManagerPrivate; + +class QBS_EXPORT ScannerPluginManager +{ +public: + ~ScannerPluginManager(); + static ScannerPluginManager *instance(); + static std::vector scannersForFileTag(const FileTag &fileTag); + void registerPlugins(ScannerPlugin **plugins); + +private: + ScannerPluginManager(); + std::unique_ptr d; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_PLUGINS_SCANNER_H diff --git a/src/lib/corelib/tools/scripttools.cpp b/src/lib/corelib/tools/scripttools.cpp new file mode 100644 index 00000000..adf930cf --- /dev/null +++ b/src/lib/corelib/tools/scripttools.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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 "scripttools.h" + +#include + +#include + +namespace qbs { +namespace Internal { + +void setConfigProperty(QVariantMap &cfg, const QStringList &name, const QVariant &value) +{ + if (name.length() == 1) { + cfg.insert(name.front(), value); + } else { + QVariant &subCfg = cfg[name.front()]; + QVariantMap subCfgMap = subCfg.toMap(); + setConfigProperty(subCfgMap, name.mid(1), value); + subCfg = subCfgMap; + } +} + +QVariant getConfigProperty(const QVariantMap &cfg, const QStringList &name) +{ + if (name.length() == 1) + return cfg.value(name.front()); + else + return getConfigProperty(cfg.value(name.front()).toMap(), name.mid(1)); +} + +TemporaryGlobalObjectSetter::TemporaryGlobalObjectSetter(const QScriptValue &object) +{ + QScriptEngine *engine = object.engine(); + m_oldGlobalObject = engine->globalObject(); + engine->setGlobalObject(object); +} + +TemporaryGlobalObjectSetter::~TemporaryGlobalObjectSetter() +{ + m_oldGlobalObject.engine()->setGlobalObject(m_oldGlobalObject); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/scripttools.h b/src/lib/corelib/tools/scripttools.h new file mode 100644 index 00000000..4a258b98 --- /dev/null +++ b/src/lib/corelib/tools/scripttools.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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 QBS_SCRIPTTOOLS_H +#define QBS_SCRIPTTOOLS_H + +#include + +#include +#include + +#include +#include +#include + +namespace qbs { +namespace Internal { + +template +QScriptValue toScriptValue(QScriptEngine *scriptEngine, const C &container) +{ + QScriptValue v = scriptEngine->newArray(container.size()); + int i = 0; + for (const typename C::value_type &item : container) + v.setProperty(i++, scriptEngine->toScriptValue(item)); + return v; +} + +void setConfigProperty(QVariantMap &cfg, const QStringList &name, const QVariant &value); +QVariant QBS_AUTOTEST_EXPORT getConfigProperty(const QVariantMap &cfg, const QStringList &name); + +template +void attachPointerTo(QScriptValue &scriptValue, T *ptr) +{ + QVariant v; + v.setValue(reinterpret_cast(ptr)); + scriptValue.setData(scriptValue.engine()->newVariant(v)); +} + +template +T *attachedPointer(const QScriptValue &scriptValue) +{ + const auto ptr = scriptValue.data().toVariant().value(); + return reinterpret_cast(ptr); +} + +class TemporaryGlobalObjectSetter +{ +public: + TemporaryGlobalObjectSetter(const QScriptValue &object); + ~TemporaryGlobalObjectSetter(); + +private: + QScriptValue m_oldGlobalObject; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_SCRIPTTOOLS_H diff --git a/src/lib/corelib/tools/set.h b/src/lib/corelib/tools/set.h new file mode 100644 index 00000000..d2af77d7 --- /dev/null +++ b/src/lib/corelib/tools/set.h @@ -0,0 +1,436 @@ +/**************************************************************************** +** +** 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 QBS_SET_H +#define QBS_SET_H + +#include +#include + +#ifdef QT_CORE_LIB +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +template class Set; +template Set operator&(const Set &set1, const Set &set2); +template Set operator-(const Set &set1, const Set &set2); + +namespace helper { +template struct SortAfterLoad { static const bool required = false; }; +template struct SortAfterLoad { static const bool required = true; }; +template struct SortAfterLoad> { static const bool required = true; }; +} + +template class Set +{ +public: + using const_iterator = typename std::vector::const_iterator; + using iterator = typename std::vector::iterator; + using reverse_iterator = typename std::vector::reverse_iterator; + using const_reverse_iterator = typename std::vector::const_reverse_iterator; + using size_type = typename std::vector::size_type; + using value_type = T; + using difference_type = typename std::vector::difference_type; + using pointer = typename std::vector::pointer; + using const_pointer = typename std::vector::const_pointer; + using reference = typename std::vector::reference; + using const_reference = typename std::vector::const_reference; + + iterator begin() { return m_data.begin(); } + iterator end() { return m_data.end(); } + reverse_iterator rbegin() { return m_data.rbegin(); } + reverse_iterator rend() { return m_data.rend(); } + const_reverse_iterator rbegin() const { return m_data.rbegin(); } + const_reverse_iterator rend() const { return m_data.rend(); } + const_reverse_iterator crbegin() const { return m_data.crbegin(); } + const_reverse_iterator crend() const { return m_data.crend(); } + const_iterator begin() const { return m_data.begin(); } + const_iterator end() const { return m_data.end(); } + const_iterator cbegin() const { return m_data.cbegin(); } + const_iterator cend() const { return m_data.cend(); } + const_iterator constBegin() const { return m_data.cbegin(); } + const_iterator constEnd() const { return m_data.cend(); } + + Set() = default; + Set(const std::initializer_list &list); + + Set &unite(const Set &other); + Set &operator+=(const Set &other) { return unite(other); } + Set &operator|=(const Set &other) { return unite(other); } + + Set &subtract(const Set &other); + Set &operator-=(const Set &other) { return subtract(other); } + + Set &intersect(const Set &other); + Set &operator&=(const Set &other) { return intersect(other); } + Set &operator&=(const T &v) { return intersect(Set{ v }); } + + iterator find(const T &v) { return std::find(m_data.begin(), m_data.end(), v); } + const_iterator find(const T &v) const { return std::find(m_data.cbegin(), m_data.cend(), v); } + std::pair insert(const T &v); + Set &operator+=(const T &v) { insert(v); return *this; } + Set &operator|=(const T &v) { return operator+=(v); } + Set &operator<<(const T &v) { return operator+=(v); } + + bool contains(const T &v) const { return std::binary_search(cbegin(), cend(), v); } + bool contains(const Set &other) const; + bool empty() const { return m_data.empty(); } + size_type size() const { return m_data.size(); } + size_type capacity() const { return m_data.capacity(); } + bool intersects(const Set &other) const; + + bool remove(const T &v); + void operator-=(const T &v) { remove(v); } + iterator erase(iterator it) { return m_data.erase(it); } + iterator erase(iterator first, iterator last) { return m_data.erase(first, last); } + + void clear() { m_data.clear(); } + void reserve(size_type size) { m_data.reserve(size); } + + void swap(Set &other) { m_data.swap(other.m_data); } + + void load(PersistentPool &pool); + void store(PersistentPool &pool) const; + +#ifdef QT_CORE_LIB + QStringList toStringList() const; + QString toString(const T& value) const { return value.toString(); } + QString toString() const; + + static Set fromList(const QList &list); + QList toList() const; +#endif + + static Set fromStdVector(const std::vector &vector); + static Set fromStdSet(const std::set &set); + std::set toStdSet() const; + + template static Set filtered(const Set &s); + + bool operator==(const Set &other) const { return m_data == other.m_data; } + bool operator!=(const Set &other) const { return m_data != other.m_data; } + +private: + friend Set operator&<>(const Set &set1, const Set &set2); + friend Set operator-<>(const Set &set1, const Set &set2); + + void sort() { std::sort(m_data.begin(), m_data.end()); } + T loadElem(PersistentPool &pool) { return pool.load(); } + void storeElem(PersistentPool &pool, const T &v) const { pool.store(v); } + bool sortAfterLoadRequired() const { return helper::SortAfterLoad::required; } + iterator asMutableIterator(const_iterator cit); + + std::vector m_data; +}; + +template Set::Set(const std::initializer_list &list) : m_data(list) +{ + sort(); + const auto last = std::unique(m_data.begin(), m_data.end()); + m_data.erase(last, m_data.end()); +} + +template Set &Set::intersect(const Set &other) +{ + auto it = begin(); + auto otherIt = other.cbegin(); + while (it != end()) { + if (otherIt == other.cend()) { + m_data.erase(it, end()); + break; + } + if (*it < *otherIt) { + it = erase(it); + continue; + } + if (!(*otherIt < *it)) + ++it; + ++otherIt; + } + return *this; +} + +template std::pair::iterator, bool> Set::insert(const T &v) +{ + const auto it = std::lower_bound(m_data.begin(), m_data.end(), v); + if (it == m_data.end() || v < *it) + return std::make_pair(m_data.insert(it, v), true); + return std::make_pair(it, false); +} + +template bool Set::contains(const Set &other) const +{ + auto it = cbegin(); + auto otherIt = other.cbegin(); + while (otherIt != other.cend()) { + if (it == cend() || *otherIt < *it) + return false; + if (!(*it < *otherIt)) + ++otherIt; + ++it; + } + return true; +} + +template bool Set::intersects(const Set &other) const +{ + auto it = cbegin(); + auto itOther = other.cbegin(); + while (it != cend() && itOther != other.cend()) { + if (*it < *itOther) + ++it; + else if (*itOther < *it) + ++itOther; + else + return true; + } + return false; +} + +template Set &Set::unite(const Set &other) +{ + if (other.empty()) + return *this; + if (empty()) { + m_data = other.m_data; + return *this; + } + auto lowerBound = m_data.begin(); + for (auto otherIt = other.cbegin(); otherIt != other.cend(); ++otherIt) { + lowerBound = std::lower_bound(lowerBound, m_data.end(), *otherIt); + if (lowerBound == m_data.end()) { + m_data.reserve(size() + std::distance(otherIt, other.cend())); + std::copy(otherIt, other.cend(), std::back_inserter(m_data)); + return *this; + } + if (*otherIt < *lowerBound) + lowerBound = m_data.insert(lowerBound, *otherIt); + } + return *this; +} + +template bool Set::remove(const T &v) +{ + const auto it = std::lower_bound(m_data.cbegin(), m_data.cend(), v); + if (it != m_data.cend() && !(v < *it)) { + m_data.erase(asMutableIterator(it)); + return true; + } + return false; +} + +template void Set::load(PersistentPool &pool) +{ + clear(); + int i = pool.load(); + reserve(i); + for (; --i >= 0;) + m_data.push_back(loadElem(pool)); + if (sortAfterLoadRequired()) + sort(); +} + +template void Set::store(PersistentPool &pool) const +{ + pool.store(static_cast(size())); + std::for_each(m_data.cbegin(), m_data.cend(), + std::bind(&Set::storeElem, this, std::ref(pool), std::placeholders::_1)); +} + +#ifdef QT_CORE_LIB +template QStringList Set::toStringList() const +{ + QStringList sl; + sl.reserve(int(size())); + std::transform(cbegin(), cend(), std::back_inserter(sl), + [this](const T &e) { return toString(e); }); + return sl; +} + +template QString Set::toString() const +{ + return QLatin1Char('[') + toStringList().join(QLatin1String(", ")) + QLatin1Char(']'); +} + +template<> inline QString Set::toString(const QString &value) const { return value; } + +template Set Set::fromList(const QList &list) +{ + Set s; + std::copy(list.cbegin(), list.cend(), std::back_inserter(s.m_data)); + s.sort(); + return s; +} + +template QList Set::toList() const +{ + QList list; + std::copy(m_data.cbegin(), m_data.cend(), std::back_inserter(list)); + return list; +} +#endif + +template Set Set::fromStdVector(const std::vector &vector) +{ + Set s; + std::copy(vector.cbegin(), vector.cend(), std::back_inserter(s.m_data)); + s.sort(); + return s; +} + +template Set Set::fromStdSet(const std::set &set) +{ + Set s; + std::copy(set.cbegin(), set.cend(), std::back_inserter(s.m_data)); + return s; +} + +template std::set Set::toStdSet() const +{ + std::set set; + for (auto it = cbegin(); it != cend(); ++it) + set.insert(*it); + return set; +} + +template +typename Set::iterator Set::asMutableIterator(typename Set::const_iterator cit) +{ + const auto offset = std::distance(cbegin(), cit); + return begin() + offset; +} + +template template Set Set::filtered(const Set &s) +{ + static_assert(std::is_pointer::value, "Set::filtered() assumes pointer types"); + static_assert(std::is_pointer::value, "Set::filtered() assumes pointer types"); + Set filteredSet; + for (auto &u : s) { + if (hasDynamicType>(u)) + filteredSet.m_data.push_back(static_cast(u)); + } + return filteredSet; +} + +template Set &Set::subtract(const Set &other) +{ + if (empty() || other.empty()) + return *this; + auto lowerBound = m_data.begin(); + for (auto otherIt = other.cbegin(); otherIt != other.cend(); ++otherIt) { + lowerBound = std::lower_bound(lowerBound, m_data.end(), *otherIt); + if (lowerBound == m_data.end()) + return *this; + if (!(*otherIt < *lowerBound)) + lowerBound = m_data.erase(lowerBound); + } + return *this; +} + +template Set operator+(const Set &set1, const Set &set2) +{ + Set result = set1; + return result += set2; +} + +template Set operator|(const Set &set1, const Set &set2) +{ + return set1 + set2; +} + +template Set operator-(const Set &set1, const Set &set2) +{ + if (set1.empty() || set2.empty()) + return set1; + Set result; + auto it1 = set1.cbegin(); + auto it2 = set2.cbegin(); + while (it1 != set1.cend()) { + if (it2 == set2.cend()) { + std::copy(it1, set1.cend(), std::back_inserter(result.m_data)); + break; + } + if (*it1 < *it2) { + result.m_data.push_back(*it1++); + } else if (*it2 < *it1) { + ++it2; + } else { + ++it1; + ++it2; + } + } + return result; +} + +template Set operator&(const Set &set1, const Set &set2) +{ + Set result; + auto it1 = set1.cbegin(); + auto it2 = set2.cbegin(); + while (it1 != set1.cend() && it2 != set2.cend()) { + if (*it1 < *it2) { + ++it1; + continue; + } + if (*it2 < *it1) { + ++it2; + continue; + } + result.m_data.push_back(*it1); + ++it1; + ++it2; + } + return result; +} + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/tools/settings.cpp b/src/lib/corelib/tools/settings.cpp new file mode 100644 index 00000000..fccb9c9b --- /dev/null +++ b/src/lib/corelib/tools/settings.cpp @@ -0,0 +1,259 @@ +/**************************************************************************** +** +** 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 "settings.h" + +#include "error.h" +#include "profile.h" +#include "settingscreator.h" + +#include +#include +#include + +#include + +#include + +namespace qbs { +using namespace Internal; + +QString Settings::defaultSystemSettingsBaseDir() +{ + switch (HostOsInfo::hostOs()) { + case HostOsInfo::HostOsWindows: { + const char key[] = "ALLUSERSAPPDATA"; + if (qEnvironmentVariableIsSet(key)) + return QLatin1String(key); + return QStringLiteral("C:/ProgramData"); + } + case HostOsInfo::HostOsMacos: + return QStringLiteral("/Library/Application Support"); + case HostOsInfo::HostOsLinux: + case HostOsInfo::HostOsOtherUnix: + return QStringLiteral("/etc/xdg"); + default: + return {}; + } +} + +static QString systemSettingsBaseDir() +{ +#ifdef QBS_ENABLE_UNIT_TESTS + const char key[] = "QBS_AUTOTEST_SYSTEM_SETTINGS_DIR"; + if (qEnvironmentVariableIsSet(key)) + return QLatin1String(qgetenv(key)); +#endif +#ifdef QBS_SYSTEM_SETTINGS_DIR + return QLatin1String(QBS_SYSTEM_SETTINGS_DIR); +#else + return Settings::defaultSystemSettingsBaseDir() + QStringLiteral("/qbs"); +#endif +} + +Settings::Settings(const QString &baseDir) : Settings(baseDir, systemSettingsBaseDir()) { } + +Settings::Settings(const QString &baseDir, const QString &systemBaseDir) + : m_settings(SettingsCreator(baseDir).getQSettings()), + m_systemSettings(std::make_unique(systemBaseDir + QStringLiteral("/qbs.conf"), + QSettings::IniFormat)), + m_baseDir(baseDir) +{ + // Actual qbs settings are stored transparently within a group, because QSettings + // can see non-qbs fallback settings e.g. from QtProject that we're not interested in. + m_settings->beginGroup(QStringLiteral("org/qt-project/qbs")); +} + +Settings::~Settings() = default; + +QVariant Settings::value(const QString &key, Scopes scopes, const QVariant &defaultValue) const +{ + QVariant userValue; + if (scopes & UserScope) + userValue = m_settings->value(internalRepresentation(key)); + QVariant systemValue; + if (scopes & SystemScope) + systemValue = m_systemSettings->value(internalRepresentation(key)); + if (!userValue.isValid()) { + if (systemValue.isValid()) + return systemValue; + return defaultValue; + } + if (!systemValue.isValid()) + return userValue; + if (static_cast(userValue.type()) == QMetaType::QStringList) + return userValue.toStringList() + systemValue.toStringList(); + if (static_cast(userValue.type()) == QMetaType::QVariantList) + return userValue.toList() + systemValue.toList(); + return userValue; +} + +QStringList Settings::allKeys(Scopes scopes) const +{ + QStringList keys; + if (scopes & UserScope) + keys = m_settings->allKeys(); + if (scopes & SystemScope) + keys += m_systemSettings->allKeys(); + fixupKeys(keys); + return keys; +} + +QStringList Settings::directChildren(const QString &parentGroup, Scope scope) const +{ + QSettings * const settings = settingsForScope(scope); + settings->beginGroup(internalRepresentation(parentGroup)); + QStringList children = settings->childGroups(); + children << settings->childKeys(); + settings->endGroup(); + fixupKeys(children); + return children; +} + +QStringList Settings::allKeysWithPrefix(const QString &group, Scopes scopes) const +{ + QStringList keys; + if (scopes & UserScope) { + m_settings->beginGroup(internalRepresentation(group)); + keys = m_settings->allKeys(); + m_settings->endGroup(); + } + if (scopes & SystemScope) { + m_systemSettings->beginGroup(internalRepresentation(group)); + keys += m_systemSettings->allKeys(); + m_systemSettings->endGroup(); + } + fixupKeys(keys); + return keys; +} + +void Settings::setValue(const QString &key, const QVariant &value) +{ + if (key.startsWith(StringConstants::profilesSettingsPrefix() + Profile::fallbackName())) { + throw ErrorInfo(Tr::tr("Invalid use of special profile name '%1'.") + .arg(Profile::fallbackName())); + } + targetForWriting()->setValue(internalRepresentation(key), value); + checkForWriteError(); +} + +void Settings::remove(const QString &key) +{ + targetForWriting()->remove(internalRepresentation(key)); + checkForWriteError(); +} + +void Settings::clear() +{ + targetForWriting()->clear(); + checkForWriteError(); +} + +void Settings::sync() +{ + targetForWriting()->sync(); +} + +QString Settings::defaultProfile() const +{ + return value(QStringLiteral("defaultProfile"), allScopes()).toString(); +} + +QStringList Settings::profiles() const +{ + QStringList result; + if (m_scopeForWriting == UserScope) { + m_settings->beginGroup(StringConstants::profilesSettingsKey()); + result = m_settings->childGroups(); + m_settings->endGroup(); + } + m_systemSettings->beginGroup(StringConstants::profilesSettingsKey()); + result += m_systemSettings->childGroups(); + m_systemSettings->endGroup(); + result.removeDuplicates(); + return result; +} + +QString Settings::fileName() const +{ + return targetForWriting()->fileName(); +} + +QString Settings::internalRepresentation(const QString &externalKey) const +{ + QString internalKey = externalKey; + return internalKey.replace(QLatin1Char('.'), QLatin1Char('/')); +} + +QString Settings::externalRepresentation(const QString &internalKey) const +{ + QString externalKey = internalKey; + return externalKey.replace(QLatin1Char('/'), QLatin1Char('.')); +} + +void Settings::fixupKeys(QStringList &keys) const +{ + keys.sort(); + keys.removeDuplicates(); + for (auto &key : keys) + key = externalRepresentation(key); +} + +QSettings *Settings::settingsForScope(Settings::Scope scope) const +{ + return scope == UserScope ? m_settings.get() : m_systemSettings.get(); +} + +QSettings *Settings::targetForWriting() const +{ + return settingsForScope(m_scopeForWriting); +} + +void Settings::checkForWriteError() +{ + if (m_scopeForWriting == SystemScope && m_systemSettings->status() == QSettings::NoError) { + sync(); + if (m_systemSettings->status() == QSettings::AccessError) + throw ErrorInfo(Tr::tr("Failure writing system settings file '%1': " + "You do not have permission to write to that location.") + .arg(fileName())); + } +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/settings.h b/src/lib/corelib/tools/settings.h new file mode 100644 index 00000000..63821666 --- /dev/null +++ b/src/lib/corelib/tools/settings.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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 QBS_SETTINGS_H +#define QBS_SETTINGS_H + +#include "qbs_export.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE +class QSettings; +class QStringList; +QT_END_NAMESPACE + +namespace qbs { + +class QBS_EXPORT Settings +{ +public: + // The "pure" base directory without any version scope. Empty string means "system default". + Settings(const QString &baseDir); + Settings(const QString &baseDir, const QString &systemBaseDir); + + ~Settings(); + + enum Scope { UserScope = 0x1, SystemScope = 0x2 }; + Q_DECLARE_FLAGS(Scopes, Scope) + static Scopes allScopes() { return {UserScope, SystemScope}; } + + QVariant value(const QString &key, Scopes scopes, + const QVariant &defaultValue = QVariant()) const; + QStringList allKeys(Scopes scopes) const; + QStringList directChildren(const QString &parentGroup, Scope scope) const; // Keys and groups. + QStringList allKeysWithPrefix(const QString &group, Scopes scopes) const; + void setValue(const QString &key, const QVariant &value); + void remove(const QString &key); + void clear(); + void sync(); + + void setScopeForWriting(Scope scope) { m_scopeForWriting = scope; } + Scope scopeForWriting() const { return m_scopeForWriting; } + + QString defaultProfile() const; + QStringList profiles() const; + + QString fileName() const; + QString baseDirectory() const { return m_baseDir; } // As passed into the constructor. + + static QString defaultSystemSettingsBaseDir(); + +private: + QString internalRepresentation(const QString &externalKey) const; + QString externalRepresentation(const QString &internalKey) const; + void fixupKeys(QStringList &keys) const; + QSettings *settingsForScope(Scope scope) const; + QSettings *targetForWriting() const; + void checkForWriteError(); + + const std::unique_ptr m_settings; + const std::unique_ptr m_systemSettings; + const QString m_baseDir; + Scope m_scopeForWriting = UserScope; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Settings::Scopes) + +} // namespace qbs + +#endif // QBS_SETTINGS_H diff --git a/src/lib/corelib/tools/settingscreator.cpp b/src/lib/corelib/tools/settingscreator.cpp new file mode 100644 index 00000000..36e67440 --- /dev/null +++ b/src/lib/corelib/tools/settingscreator.cpp @@ -0,0 +1,157 @@ +#include +#include + +/**************************************************************************** +** +** 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 "settingscreator.h" + +#include "fileinfo.h" +#include "hostosinfo.h" + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +static QSettings::Format format() +{ + return HostOsInfo::isWindowsHost() ? QSettings::IniFormat : QSettings::NativeFormat; +} + + +SettingsCreator::SettingsCreator(QString baseDir) + : m_settingsBaseDir(std::move(baseDir)) + , m_qbsVersion(Version::fromString(QLatin1String(QBS_VERSION))) +{ +} + +std::unique_ptr SettingsCreator::getQSettings() +{ + createQSettings(); + migrate(); + return std::move(m_settings); +} + +void SettingsCreator::migrate() +{ + if (!m_settings->allKeys().empty()) // We already have settings for this qbs version. + return; + + m_settings.reset(); + + // Find settings from highest qbs version lower than this one and copy all settings data. + const Version thePredecessor = predecessor(); + QString oldSettingsDir = m_settingsBaseDir; + if (thePredecessor.isValid()) + oldSettingsDir.append(QLatin1String("/qbs/")).append(thePredecessor.toString()); + QString oldProfilesDir = oldSettingsDir; + if (!thePredecessor.isValid()) + oldProfilesDir += QLatin1String("/qbs"); + oldProfilesDir += QLatin1String("/profiles"); + const QString newProfilesDir = m_newSettingsDir + QLatin1String("/profiles"); + QString errorMessage; + if (QFileInfo(oldProfilesDir).exists() + && !copyFileRecursion(oldProfilesDir, newProfilesDir, false, true, &errorMessage)) { + qWarning() << "Error in settings migration: " << errorMessage; + } + const QString oldSettingsFilePath = oldSettingsDir + QLatin1Char('/') + m_settingsFileName; + if (QFileInfo(oldSettingsFilePath).exists() + && (!QDir::root().mkpath(m_newSettingsDir) + || !QFile::copy(oldSettingsFilePath, m_newSettingsFilePath))) { + qWarning() << "Error in settings migration: Could not copy" << oldSettingsFilePath + << "to" << m_newSettingsFilePath; + } + + // Adapt all paths in settings that point to the old location. At the time of this writing, + // that's only preferences.qbsSearchPaths as written by libqtprofilesetup, but we don't want + // to hardcode that here. + m_settings = std::make_unique(m_newSettingsFilePath, format()); + const auto allKeys = m_settings->allKeys(); + for (const QString &key : allKeys) { + QVariant v = m_settings->value(key); + if (v.type() == QVariant::String) { + QString s = v.toString(); + if (s.contains(oldProfilesDir)) + m_settings->setValue(key, s.replace(oldProfilesDir, newProfilesDir)); + } else if (v.type() == QVariant::StringList) { + const QStringList oldList = v.toStringList(); + QStringList newList; + for (const QString &oldString : oldList) { + QString newString = oldString; + newList << newString.replace(oldProfilesDir, newProfilesDir); + } + if (newList != oldList) + m_settings->setValue(key, newList); + } + } +} + +void SettingsCreator::createQSettings() +{ + std::unique_ptr tmp(m_settingsBaseDir.isEmpty() + ? new QSettings(format(), QSettings::UserScope, QStringLiteral("QtProject"), + QStringLiteral("qbs")) + : new QSettings(m_settingsBaseDir + QLatin1String("/qbs.conf"), format())); + const QFileInfo fi(tmp->fileName()); + m_settingsBaseDir = fi.path(); + m_newSettingsDir = m_settingsBaseDir + QLatin1String("/qbs/") + m_qbsVersion.toString(); + m_settingsFileName = fi.fileName(); + m_newSettingsFilePath = m_newSettingsDir + QLatin1Char('/') + m_settingsFileName; + m_settings = std::make_unique(m_newSettingsFilePath, tmp->format()); +} + +Version SettingsCreator::predecessor() const +{ + QDirIterator dit(m_settingsBaseDir + QLatin1String("/qbs")); + Version thePredecessor; + while (dit.hasNext()) { + dit.next(); + const auto currentVersion = Version::fromString(dit.fileName()); + if (currentVersion > thePredecessor && currentVersion < m_qbsVersion) + thePredecessor = currentVersion; + } + return thePredecessor; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/settingscreator.h b/src/lib/corelib/tools/settingscreator.h new file mode 100644 index 00000000..ab491105 --- /dev/null +++ b/src/lib/corelib/tools/settingscreator.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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 QBS_SETTINGSCREATOR_H +#define QBS_SETTINGSCREATOR_H + +#include "version.h" + +#include + +#include + +QT_BEGIN_NAMESPACE +class QSettings; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +class SettingsCreator +{ +public: + SettingsCreator(QString baseDir); + + std::unique_ptr getQSettings(); + +private: + void migrate(); + void createQSettings(); + Version predecessor() const; + + QString m_settingsBaseDir; + QString m_newSettingsDir; + QString m_settingsFileName; + QString m_newSettingsFilePath; + std::unique_ptr m_settings; + const Version m_qbsVersion; +}; + + +} // namespace Internal +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/tools/settingsmodel.cpp b/src/lib/corelib/tools/settingsmodel.cpp new file mode 100644 index 00000000..7192ef45 --- /dev/null +++ b/src/lib/corelib/tools/settingsmodel.cpp @@ -0,0 +1,401 @@ +/**************************************************************************** +** +** 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 "settingsmodel.h" +#include "settingsrepresentation.h" + +#include +#include +#include +#include + +#include +#include + +#ifdef QT_GUI_LIB +#include +#endif + +#include + +namespace qbs { +namespace Internal { + +struct Node +{ + Node() : parent(nullptr), isFromSettings(true) {} + ~Node() { qDeleteAll(children); } + + QString uniqueChildName() const; + bool hasDirectChildWithName(const QString &name) const; + + QString name; + QString value; + Node *parent; + QList children; + bool isFromSettings; +}; + +QString Node::uniqueChildName() const +{ + QString newName = QStringLiteral("newkey"); + bool unique; + do { + unique = true; + for (const Node *childNode : qAsConst(children)) { + if (childNode->name == newName) { + unique = false; + newName += QLatin1Char('_'); + break; + } + } + } while (!unique); + return newName; +} + +bool Node::hasDirectChildWithName(const QString &name) const +{ + for (const Node * const child : qAsConst(children)) { + if (child->name == name) + return true; + } + return false; +} + +} // namespace Internal + +using Internal::Node; + +class SettingsModel::SettingsModelPrivate +{ +public: + SettingsModelPrivate() : dirty(false), editable(true) {} + + void readSettings(); + void addNodeFromSettings(Node *parentNode, const QString &fullyQualifiedName); + void addNode(Node *parentNode, const QString ¤tNamePart, + const QStringList &restOfName, const QVariant &value); + void doSave(const Node *node, const QString &prefix); + Node *indexToNode(const QModelIndex &index); + + Settings::Scope scope() const { return settings->scopeForWriting(); } + + Node rootNode; + std::unique_ptr settings; + QVariantMap additionalProperties; + bool dirty; + bool editable; +}; + +SettingsModel::SettingsModel(const QString &settingsDir, Settings::Scope scope, QObject *parent) + : QAbstractItemModel(parent), d(new SettingsModelPrivate) +{ + d->settings = std::make_unique(settingsDir); + d->settings->setScopeForWriting(scope); + d->readSettings(); +} + +SettingsModel::~SettingsModel() +{ + delete d; +} + +void SettingsModel::reload() +{ + beginResetModel(); + d->readSettings(); + endResetModel(); +} + +void SettingsModel::save() +{ + if (!d->dirty) + return; + d->settings->clear(); + d->doSave(&d->rootNode, QString()); + d->dirty = false; +} + +void SettingsModel::updateSettingsDir(const QString &settingsDir) +{ + const Settings::Scope scope = d->scope(); + beginResetModel(); + d->settings = std::make_unique(settingsDir); + d->settings->setScopeForWriting(scope); + d->readSettings(); + endResetModel(); +} + +void SettingsModel::addNewKey(const QModelIndex &parent) +{ + Node *parentNode = d->indexToNode(parent); + if (!parentNode) + return; + const auto newNode = new Node; + newNode->parent = parentNode; + newNode->name = parentNode->uniqueChildName(); + beginInsertRows(parent, parentNode->children.size(), parentNode->children.size()); + parentNode->children << newNode; + endInsertRows(); + d->dirty = true; +} + +void SettingsModel::removeKey(const QModelIndex &index) +{ + Node * const node = d->indexToNode(index); + if (!node || node == &d->rootNode) + return; + const int positionInParent = node->parent->children.indexOf(node); + beginRemoveRows(parent(index), positionInParent, positionInParent); + node->parent->children.removeAt(positionInParent); + delete node; + endRemoveRows(); + d->dirty = true; +} + +bool SettingsModel::hasUnsavedChanges() const +{ + return d->dirty; +} + +void SettingsModel::setEditable(bool isEditable) +{ + d->editable = isEditable; +} + +void SettingsModel::setAdditionalProperties(const QVariantMap &properties) +{ + d->additionalProperties = properties; + reload(); +} + +Qt::ItemFlags SettingsModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + const Qt::ItemFlags flags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + if (index.column() == keyColumn()) { + if (d->editable) + return flags | Qt::ItemIsEditable; + return flags; + } + if (index.column() == valueColumn()) { + const Node * const node = d->indexToNode(index); + if (!node) + return Qt::NoItemFlags; + + // Only leaf nodes have values. + return d->editable && node->children.empty() ? flags | Qt::ItemIsEditable : flags; + } + return {}; +} + +QVariant SettingsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal) + return {}; + if (role != Qt::DisplayRole) + return {}; + if (section == keyColumn()) + return tr("Key"); + if (section == valueColumn()) + return tr("Value"); + return {}; +} + +int SettingsModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 2; +} + +int SettingsModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return 0; + const Node * const node = d->indexToNode(parent); + Q_ASSERT(node); + return node->children.size(); +} + +QVariant SettingsModel::data(const QModelIndex &index, int role) const +{ + if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::ForegroundRole) + return {}; + const Node * const node = d->indexToNode(index); + if (!node) + return {}; + if (role == Qt::ForegroundRole) { +#ifdef QT_GUI_LIB + if (index.column() == valueColumn() && !node->isFromSettings) + return QBrush(Qt::red); +#endif + return {}; + } + if (index.column() == keyColumn()) + return node->name; + if (index.column() == valueColumn() && node->children.empty()) + return node->value; + return {}; +} + +bool SettingsModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || role != Qt::EditRole) + return false; + Node * const node = d->indexToNode(index); + if (!node) + return false; + const QString valueString = value.toString(); + QString *toChange = nullptr; + if (index.column() == keyColumn() && !valueString.isEmpty() + && !node->parent->hasDirectChildWithName(valueString) + && !(node->parent->parent == &d->rootNode + && node->parent->name == Internal::StringConstants::profilesSettingsKey() + && valueString == Profile::fallbackName())) { + toChange = &node->name; + } else if (index.column() == valueColumn() && valueString != node->value) { + toChange = &node->value; + } + + if (toChange) { + *toChange = valueString; + emit dataChanged(index, index); + d->dirty = true; + } + return toChange; +} + +QModelIndex SettingsModel::index(int row, int column, const QModelIndex &parent) const +{ + const Node * const parentNode = d->indexToNode(parent); + Q_ASSERT(parentNode); + if (parentNode->children.size() <= row) + return {}; + return createIndex(row, column, parentNode->children.at(row)); +} + +QModelIndex SettingsModel::parent(const QModelIndex &child) const +{ + const auto childNode = static_cast(child.internalPointer()); + Q_ASSERT(childNode); + Node * const parentNode = childNode->parent; + if (parentNode == &d->rootNode) + return {}; + const Node * const grandParentNode = parentNode->parent; + Q_ASSERT(grandParentNode); + return createIndex(grandParentNode->children.indexOf(parentNode), 0, parentNode); +} + + +void SettingsModel::SettingsModelPrivate::readSettings() +{ + qDeleteAll(rootNode.children); + rootNode.children.clear(); + const auto topLevelKeys = settings->directChildren(QString(), scope()); + for (const QString &topLevelKey : topLevelKeys) + addNodeFromSettings(&rootNode, topLevelKey); + for (QVariantMap::ConstIterator it = additionalProperties.constBegin(); + it != additionalProperties.constEnd(); ++it) { + const QStringList nameAsList = it.key().split(QLatin1Char('.'), QBS_SKIP_EMPTY_PARTS); + addNode(&rootNode, nameAsList.front(), nameAsList.mid(1), it.value()); + } + dirty = false; +} + +static Node *createNode(Node *parentNode, const QString &name) +{ + const auto node = new Node; + node->name = name; + node->parent = parentNode; + parentNode->children.push_back(node); + return node; +} + +void SettingsModel::SettingsModelPrivate::addNodeFromSettings(Node *parentNode, + const QString &fullyQualifiedName) +{ + const QString &nodeName + = fullyQualifiedName.mid(fullyQualifiedName.lastIndexOf(QLatin1Char('.')) + 1); + Node * const node = createNode(parentNode, nodeName); + node->value = settingsValueToRepresentation(settings->value(fullyQualifiedName, scope())); + const auto childKeys = settings->directChildren(fullyQualifiedName, scope()); + for (const QString &childKey : childKeys) + addNodeFromSettings(node, fullyQualifiedName + QLatin1Char('.') + childKey); + dirty = true; +} + +void SettingsModel::SettingsModelPrivate::addNode(qbs::Internal::Node *parentNode, + const QString ¤tNamePart, const QStringList &restOfName, const QVariant &value) +{ + Node *currentNode = nullptr; + for (Node * const n : qAsConst(parentNode->children)) { + if (n->name == currentNamePart) { + currentNode = n; + break; + } + } + if (!currentNode) + currentNode = createNode(parentNode, currentNamePart); + if (restOfName.empty()) { + currentNode->value = settingsValueToRepresentation(value); + currentNode->isFromSettings = false; + } else { + addNode(currentNode, restOfName.front(), restOfName.mid(1), value); + } +} + +void SettingsModel::SettingsModelPrivate::doSave(const Node *node, const QString &prefix) +{ + if (node->children.empty()) { + settings->setValue(prefix + node->name, representationToSettingsValue(node->value)); + return; + } + + const QString newPrefix = prefix + node->name + QLatin1Char('.'); + for (const Node * const child : qAsConst(node->children)) + doSave(child, newPrefix); +} + +Node *SettingsModel::SettingsModelPrivate::indexToNode(const QModelIndex &index) +{ + return index.isValid() ? static_cast(index.internalPointer()) : &rootNode; +} + + +} // namespace qbs diff --git a/src/lib/corelib/tools/settingsmodel.h b/src/lib/corelib/tools/settingsmodel.h new file mode 100644 index 00000000..1bd59737 --- /dev/null +++ b/src/lib/corelib/tools/settingsmodel.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 QBS_SETTINGSMODEL_H +#define QBS_SETTINGSMODEL_H + +#include +#include + +#include +#include + +namespace qbs { + +class QBS_EXPORT SettingsModel : public QAbstractItemModel +{ + Q_OBJECT +public: + SettingsModel(const QString &settingsDir, Settings::Scope scope, QObject *parent = nullptr); + ~SettingsModel() override; + + int keyColumn() const { return 0; } + int valueColumn() const { return 1; } + bool hasUnsavedChanges() const; + + void setEditable(bool isEditable); + void setAdditionalProperties(const QVariantMap &properties); // Flat map. + void reload(); + void save(); + void updateSettingsDir(const QString &settingsDir); + + void addNewKey(const QModelIndex &parent); + void removeKey(const QModelIndex &index); + + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &child) const override; + +private: + class SettingsModelPrivate; + SettingsModelPrivate * const d; +}; + +} // namespace qbs + +#endif // QBS_SETTINGSMODEL_H diff --git a/src/lib/corelib/tools/settingsrepresentation.cpp b/src/lib/corelib/tools/settingsrepresentation.cpp new file mode 100644 index 00000000..256c60c0 --- /dev/null +++ b/src/lib/corelib/tools/settingsrepresentation.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "settingsrepresentation.h" + +#include "jsliterals.h" + +#include +#include + +namespace qbs { + +QString settingsValueToRepresentation(const QVariant &value) +{ + return toJSLiteral(value); +} + +static QVariant variantFromString(const QString &str, bool &ok) +{ + // ### use Qt5's JSON reader at some point. + QScriptEngine engine; + QScriptValue sv = engine.evaluate(QLatin1String("(function(){return ") + + str + QLatin1String(";})()")); + ok = !sv.isError(); + return sv.toVariant(); +} + +QVariant representationToSettingsValue(const QString &representation) +{ + bool ok; + QVariant variant = variantFromString(representation, ok); + + // We have no floating-point properties, so this is most likely intended to be a string. + if (static_cast(variant.type()) == QMetaType::Float + || static_cast(variant.type()) == QMetaType::Double) { + variant = variantFromString(QLatin1Char('"') + representation + QLatin1Char('"'), ok); + } + + if (ok) + return variant; + + // If it's not valid JavaScript, interpret the value as a string. + return representation; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/settingsrepresentation.h b/src/lib/corelib/tools/settingsrepresentation.h new file mode 100644 index 00000000..9ebd64d3 --- /dev/null +++ b/src/lib/corelib/tools/settingsrepresentation.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_SETTINGSREPRESENTATION_H +#define QBS_SETTINGSREPRESENTATION_H + +#include + +#include +#include + +namespace qbs { + +QBS_EXPORT QString settingsValueToRepresentation(const QVariant &value); +QBS_EXPORT QVariant representationToSettingsValue(const QString &representation); + +} // namespace qbs + +#endif // QBS_SETTINGSREPRESENTATION_H diff --git a/src/lib/corelib/tools/setupprojectparameters.cpp b/src/lib/corelib/tools/setupprojectparameters.cpp new file mode 100644 index 00000000..a06ffc4b --- /dev/null +++ b/src/lib/corelib/tools/setupprojectparameters.cpp @@ -0,0 +1,691 @@ +/**************************************************************************** +** +** 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 "setupprojectparameters.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +/*! + * \class SetupProjectParameters + * \brief The \c SetupProjectParameters class comprises data required to set up a qbs project. + */ + +class SetupProjectParametersPrivate : public QSharedData +{ +public: + SetupProjectParametersPrivate() + : overrideBuildGraphData(false) + , dryRun(false) + , logElapsedTime(false) + , forceProbeExecution(false) + , waitLockBuildGraph(false) + , restoreBehavior(SetupProjectParameters::RestoreAndTrackChanges) + , propertyCheckingMode(ErrorHandlingMode::Strict) + , productErrorMode(ErrorHandlingMode::Strict) + { + } + + QString projectFilePath; + QString topLevelProfile; + QString configurationName = QLatin1String("default"); + QString buildRoot; + QStringList searchPaths; + QStringList pluginPaths; + QString libexecPath; + QString settingsBaseDir; + QVariantMap overriddenValues; + QVariantMap buildConfiguration; + mutable QVariantMap buildConfigurationTree; + mutable QVariantMap overriddenValuesTree; + mutable QVariantMap finalBuildConfigTree; + bool overrideBuildGraphData; + bool dryRun; + bool logElapsedTime; + bool forceProbeExecution; + bool waitLockBuildGraph; + bool fallbackProviderEnabled = true; + SetupProjectParameters::RestoreBehavior restoreBehavior; + ErrorHandlingMode propertyCheckingMode; + ErrorHandlingMode productErrorMode; + QProcessEnvironment environment; +}; + +} // namespace Internal + +SetupProjectParameters::SetupProjectParameters() : d(new Internal::SetupProjectParametersPrivate) +{ +} + +SetupProjectParameters::SetupProjectParameters(const SetupProjectParameters &other) = default; + +SetupProjectParameters::SetupProjectParameters( + SetupProjectParameters &&other) Q_DECL_NOEXCEPT = default; + +SetupProjectParameters::~SetupProjectParameters() = default; + +SetupProjectParameters &SetupProjectParameters::operator=( + const SetupProjectParameters &other) = default; + +namespace Internal { +template<> ErrorHandlingMode fromJson(const QJsonValue &v) +{ + if (v.toString() == QLatin1String("relaxed")) + return ErrorHandlingMode::Relaxed; + return ErrorHandlingMode::Strict; +} + +template<> SetupProjectParameters::RestoreBehavior fromJson(const QJsonValue &v) +{ + const QString value = v.toString(); + if (value == QLatin1String("restore-only")) + return SetupProjectParameters::RestoreOnly; + if (value == QLatin1String("resolve-only")) + return SetupProjectParameters::ResolveOnly; + return SetupProjectParameters::RestoreAndTrackChanges; +} +} // namespace Internal + +SetupProjectParameters SetupProjectParameters::fromJson(const QJsonObject &data) +{ + using namespace Internal; + SetupProjectParameters params; + setValueFromJson(params.d->topLevelProfile, data, "top-level-profile"); + setValueFromJson(params.d->configurationName, data, "configuration-name"); + setValueFromJson(params.d->projectFilePath, data, "project-file-path"); + setValueFromJson(params.d->buildRoot, data, "build-root"); + setValueFromJson(params.d->settingsBaseDir, data, "settings-directory"); + setValueFromJson(params.d->overriddenValues, data, "overridden-properties"); + setValueFromJson(params.d->dryRun, data, "dry-run"); + setValueFromJson(params.d->logElapsedTime, data, "log-time"); + setValueFromJson(params.d->forceProbeExecution, data, "force-probe-execution"); + setValueFromJson(params.d->waitLockBuildGraph, data, "wait-lock-build-graph"); + setValueFromJson(params.d->fallbackProviderEnabled, data, "fallback-provider-enabled"); + setValueFromJson(params.d->environment, data, "environment"); + setValueFromJson(params.d->restoreBehavior, data, "restore-behavior"); + setValueFromJson(params.d->propertyCheckingMode, data, "error-handling-mode"); + params.d->productErrorMode = params.d->propertyCheckingMode; + return params; +} + +SetupProjectParameters &SetupProjectParameters::operator=(SetupProjectParameters &&other) Q_DECL_NOEXCEPT = default; + +/*! + * \brief Returns the name of the top-level profile for building the project. + */ +QString SetupProjectParameters::topLevelProfile() const +{ + return d->topLevelProfile; +} + +/*! + * \brief Sets the top-level profile for building the project. + */ +void SetupProjectParameters::setTopLevelProfile(const QString &profile) +{ + d->buildConfigurationTree.clear(); + d->finalBuildConfigTree.clear(); + d->topLevelProfile = profile; +} + +/*! + * Returns the name of the current project build configuration. + */ +QString SetupProjectParameters::configurationName() const +{ + return d->configurationName; +} + +/*! + * Sets the name of the current project build configuration to an arbitrary user-specified name, + * \a configurationName. + */ +void SetupProjectParameters::setConfigurationName(const QString &configurationName) +{ + d->buildConfigurationTree.clear(); + d->finalBuildConfigTree.clear(); + d->configurationName = configurationName; +} + +/*! + * \brief Returns the absolute path to the qbs project file. + * This file typically has a ".qbs" suffix. + */ +QString SetupProjectParameters::projectFilePath() const +{ + return d->projectFilePath; +} + +/*! + * \brief Sets the path to the main project file. + * \note The argument must be an absolute file path. + */ +void SetupProjectParameters::setProjectFilePath(const QString &projectFilePath) +{ + d->projectFilePath = projectFilePath; + + const QString canonicalProjectFilePath = QFileInfo(d->projectFilePath).canonicalFilePath(); + if (!canonicalProjectFilePath.isEmpty()) + d->projectFilePath = canonicalProjectFilePath; +} + +/*! + * \brief Returns the base path of where to put the build artifacts and store the build graph. + */ +QString SetupProjectParameters::buildRoot() const +{ + return d->buildRoot; +} + +/*! + * \brief Sets the base path of where to put the build artifacts and store the build graph. + * The same base path can be used for several build profiles of the same project without them + * interfering with each other. + * It might look as if this parameter would not be needed at the time of setting up the project, + * but keep in mind that the project information could already exist on disk, in which case + * loading it will be much faster than setting up the project from scratch. + * \note The argument must be an absolute path to a directory. + */ +void SetupProjectParameters::setBuildRoot(const QString &buildRoot) +{ + d->buildRoot = buildRoot; + + // Calling mkpath() may be necessary to get the canonical build root, but if we do it, + // it must be reverted immediately afterwards as not to create directories needlessly, + // e.g in the case of a dry run build. + Internal::DirectoryManager dirManager(buildRoot, Internal::Logger()); + + // We don't do error checking here, as this is not a convenient place to report an error. + // If creation of the build directory is not possible, we will get sensible error messages + // later, e.g. from the code that attempts to store the build graph. + QDir::root().mkpath(buildRoot); + + const QString canonicalBuildRoot = QFileInfo(d->buildRoot).canonicalFilePath(); + if (!canonicalBuildRoot.isEmpty()) + d->buildRoot = canonicalBuildRoot; +} + +/*! + * \brief Where to look for modules and items to import. + */ +QStringList SetupProjectParameters::searchPaths() const +{ + return d->searchPaths; +} + +/*! + * \brief Sets the information about where to look for modules and items to import. + * \note The elements of the list must be absolute paths to directories. + */ +void SetupProjectParameters::setSearchPaths(const QStringList &searchPaths) +{ + d->searchPaths = searchPaths; +} + +/*! + * \brief Where to look for plugins. + */ +QStringList SetupProjectParameters::pluginPaths() const +{ + return d->pluginPaths; +} + +/*! + * \brief Sets the information about where to look for plugins. + * \note The elements of the list must be absolute paths to directories. + */ +void SetupProjectParameters::setPluginPaths(const QStringList &pluginPaths) +{ + d->pluginPaths = pluginPaths; +} + +/*! + * \brief Where to look for internal binaries. + */ +QString SetupProjectParameters::libexecPath() const +{ + return d->libexecPath; +} + +/*! + * \brief Sets the information about where to look for internal binaries. + * \note \p libexecPath must be an absolute path. + */ +void SetupProjectParameters::setLibexecPath(const QString &libexecPath) +{ + d->libexecPath = libexecPath; +} + +/*! + * \brief The base directory for qbs settings. + * This value is used to locate profiles and preferences. + */ +QString SetupProjectParameters::settingsDirectory() const +{ + return d->settingsBaseDir; +} + +/*! + * \brief Sets the base directory for qbs settings. + * \param settingsBaseDir Will be used to locate profiles and preferences. + */ +void SetupProjectParameters::setSettingsDirectory(const QString &settingsBaseDir) +{ + d->settingsBaseDir = settingsBaseDir; +} + +/*! + * Returns the overridden values of the build configuration. + */ +QVariantMap SetupProjectParameters::overriddenValues() const +{ + return d->overriddenValues; +} + +/*! + * Set the overridden values of the build configuration. + */ +void SetupProjectParameters::setOverriddenValues(const QVariantMap &values) +{ + d->overriddenValues = values; + d->overriddenValuesTree.clear(); + d->finalBuildConfigTree.clear(); +} + +static void provideValuesTree(const QVariantMap &values, QVariantMap *valueTree) +{ + if (!valueTree->empty() || values.empty()) + return; + + valueTree->clear(); + for (QVariantMap::const_iterator it = values.constBegin(); it != values.constEnd(); ++it) { + const QString &name = it.key(); + int idx = name.lastIndexOf(QLatin1Char('.')); + const QStringList nameElements = (idx == -1) + ? QStringList() << name + : QStringList() << name.left(idx) << name.mid(idx + 1); + Internal::setConfigProperty(*valueTree, nameElements, it.value()); + } +} + +QVariantMap SetupProjectParameters::overriddenValuesTree() const +{ + provideValuesTree(d->overriddenValues, &d->overriddenValuesTree); + return d->overriddenValuesTree; +} + +/*! + * \brief Returns the build configuration. + * Overridden values are not taken into account. + */ +QVariantMap SetupProjectParameters::buildConfiguration() const +{ + return d->buildConfiguration; +} + +/*! + * \brief Returns the build configuration in tree form. + * Overridden values are not taken into account. + */ +QVariantMap SetupProjectParameters::buildConfigurationTree() const +{ + provideValuesTree(d->buildConfiguration, &d->buildConfigurationTree); + return d->buildConfigurationTree; +} + +static QVariantMap expandedBuildConfigurationInternal(const Profile &profile, + const QString &configurationName) +{ + QVariantMap buildConfig; + + // (1) Values from profile, if given. + if (profile.exists() && profile.name() != Profile::fallbackName()) { + ErrorInfo err; + const QStringList profileKeys = profile.allKeys(Profile::KeySelectionRecursive, &err); + if (err.hasError()) + throw err; + if (profileKeys.empty()) + 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()) + throw err; + } + } + + // (2) Build configuration name. + if (configurationName.isEmpty()) + throw ErrorInfo(Internal::Tr::tr("No build configuration name set.")); + buildConfig.insert(QStringLiteral("qbs.configurationName"), configurationName); + return buildConfig; +} + + +QVariantMap SetupProjectParameters::expandedBuildConfiguration(const Profile &profile, + const QString &configurationName, ErrorInfo *errorInfo) +{ + try { + return expandedBuildConfigurationInternal(profile, configurationName); + } catch (const ErrorInfo &err) { + if (errorInfo) + *errorInfo = err; + return {}; + } +} + + +/*! + * \brief Expands the build configuration. + * + * Expansion is the process by which the build configuration is completed based on the settings + * in \c settingsDirectory(). E.g. the information configured in a profile is filled into the build + * configuration by this step. + * + * This method returns an Error. The list of entries in this error will be empty is the + * expansion was successful. + */ +ErrorInfo SetupProjectParameters::expandBuildConfiguration() +{ + ErrorInfo err; + Settings settings(d->settingsBaseDir); + Profile profile(topLevelProfile(), &settings); + QVariantMap expandedConfig = expandedBuildConfiguration(profile, configurationName(), &err); + if (err.hasError()) + return err; + if (d->buildConfiguration != expandedConfig) { + d->buildConfigurationTree.clear(); + d->buildConfiguration = expandedConfig; + } + return err; +} + +QVariantMap SetupProjectParameters::finalBuildConfigurationTree(const QVariantMap &buildConfig, + const QVariantMap &overriddenValues) +{ + QVariantMap flatBuildConfig = buildConfig; + for (QVariantMap::ConstIterator it = overriddenValues.constBegin(); + it != overriddenValues.constEnd(); ++it) { + flatBuildConfig.insert(it.key(), it.value()); + } + + QVariantMap buildConfigTree; + provideValuesTree(flatBuildConfig, &buildConfigTree); + return buildConfigTree; +} + +/*! + * \brief Returns the build configuration in tree form, with overridden values taken into account. + */ +QVariantMap SetupProjectParameters::finalBuildConfigurationTree() const +{ + if (d->finalBuildConfigTree.empty()) { + d->finalBuildConfigTree = finalBuildConfigurationTree(buildConfiguration(), + overriddenValues()); + } + return d->finalBuildConfigTree; +} + + /*! + * \brief if true, qbs will not store the build graph of the resolved project. + */ +bool SetupProjectParameters::dryRun() const +{ + return d->dryRun; +} + + /*! + * \brief Controls whether the build graph will be stored. + * If the argument is true, qbs will not store the build graph after resolving the project. + * The default is false. + */ +void SetupProjectParameters::setDryRun(bool dryRun) +{ + d->dryRun = dryRun; +} + + /*! + * \brief Returns true iff the time the operation takes should be logged + */ +bool SetupProjectParameters::logElapsedTime() const +{ + return d->logElapsedTime; +} + +/*! + * Controls whether to log the time taken up for resolving the project. + * The default is false. + */ +void SetupProjectParameters::setLogElapsedTime(bool logElapsedTime) +{ + d->logElapsedTime = logElapsedTime; +} + + +/*! + * \brief Returns true iff probes should be re-run. + */ +bool SetupProjectParameters::forceProbeExecution() const +{ + return d->forceProbeExecution; +} + +/*! + * Controls whether to re-run probes even if they do not appear to be outdated. + * This option only has an effect if \c restoreBehavior() is \c RestoreAndTrackChanges. + */ +void SetupProjectParameters::setForceProbeExecution(bool force) +{ + d->forceProbeExecution = force; +} + +/*! + * \brief Returns true if qbs should wait for the build graph lock to become available, + * otherwise qbs will exit immediately if the lock cannot be acquired. + */ +bool SetupProjectParameters::waitLockBuildGraph() const +{ + return d->waitLockBuildGraph; +} + +/*! + * Controls whether to wait indefinitely for the build graph lock to be released. + * This allows multiple conflicting qbs processes to be spawned simultaneously. + */ +void SetupProjectParameters::setWaitLockBuildGraph(bool wait) +{ + d->waitLockBuildGraph = wait; +} + +/*! + * \brief Returns true if qbs should fall back to pkg-config if a dependency is not found. + */ +bool SetupProjectParameters::fallbackProviderEnabled() const +{ + return d->fallbackProviderEnabled; +} + +/*! + * Controls whether to fall back to pkg-config if a dependency is not found. + */ +void SetupProjectParameters::setFallbackProviderEnabled(bool enable) +{ + d->fallbackProviderEnabled = enable; +} + +/*! + * \brief Gets the environment used while resolving the project. + */ +QProcessEnvironment SetupProjectParameters::environment() const +{ + return d->environment; +} + +/*! + * \brief Sets the environment used while resolving the project. + */ +void SetupProjectParameters::setEnvironment(const QProcessEnvironment &env) +{ + d->environment = env; +} + +QProcessEnvironment SetupProjectParameters::adjustedEnvironment() const +{ + QProcessEnvironment result = environment(); + const QVariantMap environmentFromProfile + = buildConfigurationTree().value(QStringLiteral("buildEnvironment")).toMap(); + for (QVariantMap::const_iterator it = environmentFromProfile.begin(); + it != environmentFromProfile.end(); ++it) { + result.insert(it.key(), it.value().toString()); + } + return result; +} + + +/*! + * \enum SetupProjectParamaters::RestoreBehavior + * This enum type specifies how to deal with existing on-disk build information. + * \value RestoreOnly Indicates that a stored build graph is to be loaded and the information + * therein assumed to be up to date. It is then considered an error if no + * such build graph exists. + * \value ResolveOnly Indicates that no attempt should be made to restore an existing build graph. + * Instead, the project is to be resolved from scratch. + * \value RestoreAndTrackChanges Indicates that the build graph should be restored from disk + * if possible and otherwise set up from scratch. In the first case, + * (parts of) the project might still be re-resolved if certain + * parameters have changed (e.g. environment variables used in the + * project files). + */ + + +/*! + * Returns information about how restored build data will be handled. + */ +SetupProjectParameters::RestoreBehavior SetupProjectParameters::restoreBehavior() const +{ + return d->restoreBehavior; +} + +/*! + * Controls how restored build data will be handled. + */ +void SetupProjectParameters::setRestoreBehavior(SetupProjectParameters::RestoreBehavior behavior) +{ + d->restoreBehavior = behavior; +} + +/*! + * Returns true if and only if environment, project file path and overridden property values + * should be taken from this object even if a build graph already exists. + * If this function returns \c false and a build graph exists, then it is an error to provide a + * project file path or overridden property values that differ from the respective values + * in the build graph. + */ +bool SetupProjectParameters::overrideBuildGraphData() const +{ + return d->overrideBuildGraphData; +} + +/*! + * If \c doOverride is true, then environment, project file path and overridden property values + * are taken from this object rather than from the build graph. + * The default is \c false. + */ +void SetupProjectParameters::setOverrideBuildGraphData(bool doOverride) +{ + d->overrideBuildGraphData = doOverride; +} + +/*! + * \enum ErrorHandlingMode + * This enum type specifies how \QBS should behave if errors occur during project resolving. + * \value ErrorHandlingMode::Strict Project resolving will stop with an error message. + * \value ErrorHandlingMode::Relaxed Project resolving will continue (if possible), and a warning + * will be printed. + */ + +/*! + * Indicates how to handle unknown properties. + */ +ErrorHandlingMode SetupProjectParameters::propertyCheckingMode() const +{ + return d->propertyCheckingMode; +} + +/*! + * Controls how to handle unknown properties. + * The default is \c PropertyCheckingRelaxed. + */ +void SetupProjectParameters::setPropertyCheckingMode(ErrorHandlingMode mode) +{ + d->propertyCheckingMode = mode; +} + +/*! + * \brief Indicates how errors occurring during product resolving are handled. + */ +ErrorHandlingMode SetupProjectParameters::productErrorMode() const +{ + return d->productErrorMode; +} + +/*! + * \brief Specifies whether an error occurring during product resolving should be fatal or not. + * \note Not all errors can be ignored; this setting is mainly intended for things such as + * missing dependencies or references to non-existing source files. + */ +void SetupProjectParameters::setProductErrorMode(ErrorHandlingMode mode) +{ + d->productErrorMode = mode; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/setupprojectparameters.h b/src/lib/corelib/tools/setupprojectparameters.h new file mode 100644 index 00000000..a4d090ec --- /dev/null +++ b/src/lib/corelib/tools/setupprojectparameters.h @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** 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 QBS_SETUPPROJECTPARAMETERS_H +#define QBS_SETUPPROJECTPARAMETERS_H + +#include "qbs_export.h" + +#include + +#include + +QT_BEGIN_NAMESPACE +class QProcessEnvironment; +class QStringList; +using QVariantMap = QMap; +QT_END_NAMESPACE + +namespace qbs { + +class Profile; +class Settings; + +namespace Internal { class SetupProjectParametersPrivate; } + +enum class ErrorHandlingMode { Strict, Relaxed }; + +class QBS_EXPORT SetupProjectParameters +{ +public: + SetupProjectParameters(); + SetupProjectParameters(const SetupProjectParameters &other); + SetupProjectParameters(SetupProjectParameters &&other) Q_DECL_NOEXCEPT; + ~SetupProjectParameters(); + + SetupProjectParameters &operator=(const SetupProjectParameters &other); + SetupProjectParameters &operator=(SetupProjectParameters &&other) Q_DECL_NOEXCEPT; + + static SetupProjectParameters fromJson(const QJsonObject &data); + + QString topLevelProfile() const; + void setTopLevelProfile(const QString &profile); + + QString configurationName() const; + void setConfigurationName(const QString &configurationName); + + QString projectFilePath() const; + void setProjectFilePath(const QString &projectFilePath); + + QString buildRoot() const; + void setBuildRoot(const QString &buildRoot); + + QStringList searchPaths() const; + void setSearchPaths(const QStringList &searchPaths); + + QStringList pluginPaths() const; + void setPluginPaths(const QStringList &pluginPaths); + + QString libexecPath() const; + void setLibexecPath(const QString &libexecPath); + + QString settingsDirectory() const; + void setSettingsDirectory(const QString &settingsBaseDir); + + QVariantMap overriddenValues() const; + void setOverriddenValues(const QVariantMap &values); + QVariantMap overriddenValuesTree() const; + + static QVariantMap expandedBuildConfiguration(const Profile &profile, + const QString &configurationName, + ErrorInfo *errorInfo = nullptr); + ErrorInfo expandBuildConfiguration(); + QVariantMap buildConfiguration() const; + QVariantMap buildConfigurationTree() const; + + static QVariantMap finalBuildConfigurationTree(const QVariantMap &buildConfig, + const QVariantMap &overriddenValues); + QVariantMap finalBuildConfigurationTree() const; + + bool overrideBuildGraphData() const; + void setOverrideBuildGraphData(bool doOverride); + + bool dryRun() const; + void setDryRun(bool dryRun); + + bool logElapsedTime() const; + void setLogElapsedTime(bool logElapsedTime); + + bool forceProbeExecution() const; + void setForceProbeExecution(bool force); + + bool waitLockBuildGraph() const; + void setWaitLockBuildGraph(bool wait); + + bool fallbackProviderEnabled() const; + void setFallbackProviderEnabled(bool enable); + + QProcessEnvironment environment() const; + void setEnvironment(const QProcessEnvironment &env); + QProcessEnvironment adjustedEnvironment() const; + + enum RestoreBehavior { RestoreOnly, ResolveOnly, RestoreAndTrackChanges }; + RestoreBehavior restoreBehavior() const; + void setRestoreBehavior(RestoreBehavior behavior); + + ErrorHandlingMode propertyCheckingMode() const; + void setPropertyCheckingMode(ErrorHandlingMode mode); + + ErrorHandlingMode productErrorMode() const; + void setProductErrorMode(ErrorHandlingMode mode); + +private: + QSharedDataPointer d; +}; + +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/tools/shellutils.cpp b/src/lib/corelib/tools/shellutils.cpp new file mode 100644 index 00000000..33ab2c76 --- /dev/null +++ b/src/lib/corelib/tools/shellutils.cpp @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Petroules Corporation. +** 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 "shellutils.h" + +#include "pathutils.h" +#include "qttools.h" + +#include +#include +#include + +namespace qbs { +namespace Internal { + +QString shellInterpreter(const QString &filePath) { + QFile file(filePath); + if (file.open(QIODevice::ReadOnly)) { + QTextStream ts(&file); + const QString shebang = ts.readLine(); + if (shebang.startsWith(QLatin1String("#!"))) { + return (shebang.mid(2).split(QRegExp(QStringLiteral("\\s")), + QBS_SKIP_EMPTY_PARTS) << QString()).front(); + } + } + + return {}; +} + +// isSpecialChar, hasSpecialChars, shellQuoteUnix, shellQuoteWin: +// all from qtbase/qmake/library/ioutils.cpp + +inline static bool isSpecialChar(ushort c, const uchar (&iqm)[16]) +{ + return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); +} + +inline static bool hasSpecialChars(const QString &arg, const uchar (&iqm)[16]) +{ + for (auto it = arg.crbegin(), end = arg.crend(); it != end; ++it) { + if (isSpecialChar(it->unicode(), iqm)) + return true; + } + return false; +} + +static QString shellQuoteUnix(const QString &arg) +{ + // Chars that should be quoted (TM). This includes: + static const uchar iqm[] = { + 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, + 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78 + }; // 0-32 \'"$`<>|;&(){}*?#!~[] + + if (!arg.length()) + return QStringLiteral("''"); + + QString ret(arg); + if (hasSpecialChars(ret, iqm)) { + ret.replace(QLatin1Char('\''), QLatin1String("'\\''")); + ret.prepend(QLatin1Char('\'')); + ret.append(QLatin1Char('\'')); + } + return ret; +} + +static QString shellQuoteWin(const QString &arg) +{ + // Chars that should be quoted (TM). This includes: + // - control chars & space + // - the shell meta chars "&()<>^| + // - the potential separators ,;= + static const uchar iqm[] = { + 0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10 + }; + // Shell meta chars that need escaping. + static const uchar ism[] = { + 0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, 0x50, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10 + }; // &()<>^| + + if (!arg.length()) + return QStringLiteral("\"\""); + + QString ret(arg); + if (hasSpecialChars(ret, iqm)) { + // The process-level standard quoting allows escaping quotes with backslashes (note + // that backslashes don't escape themselves, unless they are followed by a quote). + // Consequently, quotes are escaped and their preceding backslashes are doubled. + ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\\"")); + // Trailing backslashes must be doubled as well, as they are followed by a quote. + ret.replace(QRegExp(QLatin1String("(\\\\+)$")), QLatin1String("\\1\\1")); + // However, the shell also interprets the command, and no backslash-escaping exists + // there - a quote always toggles the quoting state, but is nonetheless passed down + // to the called process verbatim. In the unquoted state, the circumflex escapes + // meta chars (including itself and quotes), and is removed from the command. + bool quoted = true; + for (int i = 0; i < ret.length(); i++) { + QChar c = ret.unicode()[i]; + if (c.unicode() == '"') + quoted = !quoted; + else if (!quoted && isSpecialChar(c.unicode(), ism)) + ret.insert(i++, QLatin1Char('^')); + } + if (!quoted) + ret.append(QLatin1Char('^')); + ret.append(QLatin1Char('"')); + ret.prepend(QLatin1Char('"')); + } + return ret; +} + +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; + if (!args.empty()) { + result += shellQuote(args.at(0), os); + for (int i = 1; i < args.size(); ++i) + result += QLatin1Char(' ') + shellQuote(args.at(i), os); + } + return result; +} + +std::string shellQuote(const std::vector &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); + if (!args.empty()) + result += QLatin1Char(' ') + shellQuote(args, os); + return result; +} + +void CommandLine::setProgram(const QString &program, bool raw) +{ + m_program = program; + 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.emplace_back(value); +} + +void CommandLine::appendArgument(const std::string &value) +{ + m_arguments.emplace_back(QString::fromStdString(value)); +} + +void CommandLine::appendArguments(const QList &args) +{ + for (const QString &arg : args) + appendArgument(arg); +} + +void CommandLine::appendRawArgument(const QString &value) +{ + Argument arg(value); + arg.shouldQuote = false; + 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); + arg.isFilePath = true; + m_arguments.push_back(arg); +} + +void CommandLine::clearArguments() +{ + m_arguments.clear(); +} + +QString CommandLine::toCommandLine(HostOsInfo::HostOs os) const +{ + QString result = PathUtils::toNativeSeparators(m_program, os); + if (!m_isRawProgram) + result = shellQuote(result, os); + for (const Argument &arg : m_arguments) { + const QString value = arg.isFilePath + ? PathUtils::toNativeSeparators(arg.value, os) + : arg.value; + result += QLatin1Char(' ') + (arg.shouldQuote ? shellQuote(value, os) : value); + } + return result; +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/shellutils.h b/src/lib/corelib/tools/shellutils.h new file mode 100644 index 00000000..f4ad3504 --- /dev/null +++ b/src/lib/corelib/tools/shellutils.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Petroules Corporation. +** 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_SHELLUTILS_H +#define QBS_SHELLUTILS_H + +#include "qbs_export.h" +#include "hostosinfo.h" +#include + +#include + +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 &args, + HostOsInfo::HostOs os = HostOsInfo::hostOs()); +QBS_EXPORT QString shellQuote(const QString &program, const QStringList &args, + HostOsInfo::HostOs os = HostOsInfo::hostOs()); + +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 &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; + +private: + struct Argument + { + Argument(QString value = QString()) : value(std::move(value)) { } + QString value; + bool isFilePath = false; + bool shouldQuote = true; + }; + + bool m_isRawProgram = false; + QString m_program; + std::vector m_arguments; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_SHELLUTILS_H diff --git a/src/lib/corelib/tools/stlutils.h b/src/lib/corelib/tools/stlutils.h new file mode 100644 index 00000000..5c21c067 --- /dev/null +++ b/src/lib/corelib/tools/stlutils.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** 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_STLUTILS_H +#define QBS_STLUTILS_H + +#include +#include + +namespace qbs { +namespace Internal { + +template +C sorted(const C &container) +{ + C result = container; + std::sort(std::begin(result), std::end(result)); + return result; +} + +template +bool contains(const C &container, const T &v) +{ + const auto &end = std::cend(container); + return std::find(std::cbegin(container), end, v) != end; +} + +template +bool contains(const T (&container)[N], const U &v) +{ + const auto &end = std::cend(container); + return std::find(std::cbegin(container), end, v) != end; +} + +template +bool containsKey(const C &container, const typename C::key_type &v) +{ + const auto &end = container.cend(); + return container.find(v) != end; +} + +template +bool removeOne(C &container, const typename C::value_type &v) +{ + auto end = std::end(container); + auto it = std::find(std::begin(container), end, v); + if (it == end) + return false; + container.erase(it); + return true; +} + +template +void removeAll(C &container, const typename C::value_type &v) +{ + container.erase(std::remove(std::begin(container), std::end(container), v), + std::end(container)); +} + +template +bool any_of(const Container &container, const UnaryPredicate &predicate) +{ + return std::any_of(std::begin(container), std::end(container), predicate); +} + +template +bool none_of(const Container &container, const UnaryPredicate &predicate) +{ + return std::none_of(std::begin(container), std::end(container), predicate); +} + +template +C &operator<<(C &container, const typename C::value_type &v) +{ + container.push_back(v); + return container; +} + +template +C &operator<<(C &container, const C &other) +{ + container.insert(container.end(), other.cbegin(), other.cend()); + return container; +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_STLUTILS_H diff --git a/src/lib/corelib/tools/stringconstants.h b/src/lib/corelib/tools/stringconstants.h new file mode 100644 index 00000000..f2bc7844 --- /dev/null +++ b/src/lib/corelib/tools/stringconstants.h @@ -0,0 +1,261 @@ +/**************************************************************************** +** +** 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_STRINGCONSTANTS_H +#define QBS_STRINGCONSTANTS_H + +#include + +#define QBS_CONSTANT(type, name, value) \ + static const type &name() { \ + static const type var{QLatin1String(value)}; \ + return var; \ + } +#define QBS_STRING_CONSTANT(name, value) QBS_CONSTANT(QString, name, value) +#define QBS_STRINGLIST_CONSTANT(name, value) QBS_CONSTANT(QStringList, name, value) + +namespace qbs { +namespace Internal { + +class StringConstants +{ +public: + static const QString &cppModule() { return cpp(); } + static const QString &qbsModule() { return qbs(); } + + QBS_STRING_CONSTANT(aggregateProperty, "aggregate") + QBS_STRING_CONSTANT(additionalProductTypesProperty, "additionalProductTypes") + QBS_STRING_CONSTANT(allowedValuesProperty, "allowedValues") + QBS_STRING_CONSTANT(alwaysUpdatedProperty, "alwaysUpdated") + QBS_STRING_CONSTANT(alwaysRunProperty, "alwaysRun") + QBS_STRING_CONSTANT(artifactsProperty, "artifacts") + QBS_STRING_CONSTANT(auxiliaryInputsProperty, "auxiliaryInputs") + QBS_STRING_CONSTANT(baseNameProperty, "baseName") + QBS_STRING_CONSTANT(baseProfileProperty, "baseProfile") + QBS_STRING_CONSTANT(buildDirectoryProperty, "buildDirectory") + QBS_STRING_CONSTANT(buildDirectoryKey, "build-directory") + QBS_STRING_CONSTANT(builtByDefaultProperty, "builtByDefault") + QBS_STRING_CONSTANT(classNameProperty, "className") + QBS_STRING_CONSTANT(completeBaseNameProperty, "completeBaseName") + QBS_STRING_CONSTANT(conditionProperty, "condition") + QBS_STRING_CONSTANT(configurationNameProperty, "configurationName") + QBS_STRING_CONSTANT(configureProperty, "configure") + QBS_STRING_CONSTANT(consoleApplicationProperty, "consoleApplication") + QBS_STRING_CONSTANT(dependenciesProperty, "dependencies") + QBS_STRING_CONSTANT(descriptionProperty, "description") + QBS_STRING_CONSTANT(destinationDirProperty, "destinationDirectory") + QBS_STRING_CONSTANT(excludeFilesProperty, "excludeFiles") + QBS_STRING_CONSTANT(excludedAuxiliaryInputsProperty, "excludedAuxiliaryInputs") + QBS_STRING_CONSTANT(excludedInputsProperty, "excludedInputs") + static const QString &explicitlyDependsOnProperty() { return explicitlyDependsOn(); } + static const QString &explicitlyDependsOnFromDependenciesProperty() { + return explicitlyDependsOnFromDependencies(); + } + QBS_STRING_CONSTANT(enableFallbackProperty, "enableFallback") + static const QString &fileNameProperty() { return fileName(); } + static const QString &filePathProperty() { return filePath(); } + static const QString &filePathVar() { return filePath(); } + QBS_STRING_CONSTANT(filePathKey, "file-path") + QBS_STRING_CONSTANT(fileTagsFilterProperty, "fileTagsFilter") + QBS_STRING_CONSTANT(fileTagsProperty, "fileTags") + QBS_STRING_CONSTANT(filesProperty, "files") + QBS_STRING_CONSTANT(filesAreTargetsProperty, "filesAreTargets") + QBS_STRING_CONSTANT(foundProperty, "found") + QBS_STRING_CONSTANT(fullDisplayNameKey, "full-display-name") + QBS_STRING_CONSTANT(imports, "imports") + static const QString &importsDir() { return imports(); } + static const QString &importsProperty() { return imports(); } + QBS_STRING_CONSTANT(inheritPropertiesProperty, "inheritProperties") + static const QString &inputsProperty() { return inputs(); } + QBS_STRING_CONSTANT(inputsFromDependenciesProperty, "inputsFromDependencies") + static const QString &installProperty() { return install(); } + QBS_STRING_CONSTANT(installRootProperty, "installRoot") + QBS_STRING_CONSTANT(installPrefixProperty, "installPrefix") + QBS_STRING_CONSTANT(installDirProperty, "installDir") + QBS_STRING_CONSTANT(installSourceBaseProperty, "installSourceBase") + QBS_STRING_CONSTANT(isEnabledKey, "is-enabled") + QBS_STRING_CONSTANT(jobCountProperty, "jobCount") + QBS_STRING_CONSTANT(jobPoolProperty, "jobPool") + QBS_STRING_CONSTANT(lengthProperty, "length") + QBS_STRING_CONSTANT(limitToSubProjectProperty, "limitToSubProject") + QBS_STRING_CONSTANT(locationKey, "location") + QBS_STRING_CONSTANT(messageKey, "message") + QBS_STRING_CONSTANT(minimumQbsVersionProperty, "minimumQbsVersion") + QBS_STRING_CONSTANT(moduleNameProperty, "moduleName") + QBS_STRING_CONSTANT(modulePropertiesKey, "module-properties") + QBS_STRING_CONSTANT(moduleProviders, "moduleProviders") + QBS_STRING_CONSTANT(multiplexByQbsPropertiesProperty, "multiplexByQbsProperties") + QBS_STRING_CONSTANT(multiplexConfigurationIdProperty, "multiplexConfigurationId") + QBS_STRING_CONSTANT(multiplexConfigurationIdsProperty, "multiplexConfigurationIds") + QBS_STRING_CONSTANT(multiplexProperty, "multiplex") + QBS_STRING_CONSTANT(multiplexedProperty, "multiplexed") + QBS_STRING_CONSTANT(multiplexedTypeProperty, "multiplexedType") + QBS_STRING_CONSTANT(nameProperty, "name") + QBS_STRING_CONSTANT(outputArtifactsProperty, "outputArtifacts") + QBS_STRING_CONSTANT(outputFileTagsProperty, "outputFileTags") + QBS_STRING_CONSTANT(overrideTagsProperty, "overrideTags") + QBS_STRING_CONSTANT(overrideListPropertiesProperty, "overrideListProperties") + QBS_STRING_CONSTANT(parametersProperty, "parameters") + static const QString &pathProperty() { return path(); } + QBS_STRING_CONSTANT(patternsProperty, "patterns") + QBS_STRING_CONSTANT(prefixMappingProperty, "prefixMapping") + QBS_STRING_CONSTANT(prefixProperty, "prefix") + QBS_STRING_CONSTANT(prepareProperty, "prepare") + QBS_STRING_CONSTANT(presentProperty, "present") + QBS_STRING_CONSTANT(priorityProperty, "priority") + QBS_STRING_CONSTANT(profileProperty, "profile") + static const QString &profilesProperty() { return profiles(); } + QBS_STRING_CONSTANT(productTypesProperty, "productTypes") + QBS_STRING_CONSTANT(productsKey, "products") + QBS_STRING_CONSTANT(qbsSearchPathsProperty, "qbsSearchPaths") + QBS_STRING_CONSTANT(referencesProperty, "references") + QBS_STRING_CONSTANT(recursiveProperty, "recursive") + QBS_STRING_CONSTANT(requiredProperty, "required") + QBS_STRING_CONSTANT(requiresInputsProperty, "requiresInputs") + QBS_STRING_CONSTANT(removalVersionProperty, "removalVersion") + QBS_STRING_CONSTANT(scanProperty, "scan") + QBS_STRING_CONSTANT(searchPathsProperty, "searchPaths") + QBS_STRING_CONSTANT(setupBuildEnvironmentProperty, "setupBuildEnvironment") + QBS_STRING_CONSTANT(setupRunEnvironmentProperty, "setupRunEnvironment") + QBS_STRING_CONSTANT(shadowProductPrefix, "__shadow__") + QBS_STRING_CONSTANT(sourceCodeProperty, "sourceCode") + QBS_STRING_CONSTANT(sourceDirectoryProperty, "sourceDirectory") + QBS_STRING_CONSTANT(submodulesProperty, "submodules") + QBS_STRING_CONSTANT(targetNameProperty, "targetName") + static const QString &typeProperty() { return type(); } + QBS_STRING_CONSTANT(type, "type") + QBS_STRING_CONSTANT(validateProperty, "validate") + QBS_STRING_CONSTANT(versionProperty, "version") + QBS_STRING_CONSTANT(versionAtLeastProperty, "versionAtLeast") + QBS_STRING_CONSTANT(versionBelowProperty, "versionBelow") + + QBS_STRING_CONSTANT(importScopeNamePropertyInternal, "_qbs_importScopeName") + QBS_STRING_CONSTANT(modulePropertyInternal, "__module") + QBS_STRING_CONSTANT(qbsSourceDirPropertyInternal, "_qbs_sourceDir") + static const char *qbsProcEnvVarInternal() { return "_qbs_procenv"; } + + static const QString &projectPrefix() { return project(); } + static const QString &productValue() { return product(); } + + QBS_STRING_CONSTANT(projectsOverridePrefix, "projects.") + QBS_STRING_CONSTANT(productsOverridePrefix, "products.") + + QBS_STRING_CONSTANT(baseVar, "base") + static const QString &explicitlyDependsOnVar() { return explicitlyDependsOn(); } + QBS_STRING_CONSTANT(inputVar, "input") + static const QString &inputsVar() { return inputs(); } + QBS_STRING_CONSTANT(originalVar, "original") + QBS_STRING_CONSTANT(outerVar, "outer") + QBS_STRING_CONSTANT(outputVar, "output") + QBS_STRING_CONSTANT(outputsVar, "outputs") + static const QString &productVar() { return product(); } + static const QString &projectVar() { return project(); } + + static const QString &filePathGlobalVar() { return filePath(); } + static const QString &pathGlobalVar() { return path(); } + + static const QString &pathType() { return path(); } + + static const QString &fileInfoFileName() { return fileName(); } + static const QString &fileInfoPath() { return path(); } + + static const QString &androidInstallCommand() { return install(); } + static const QString &simctlInstallCommand() { return install(); } + + static const QString &profilesSettingsKey() { return profiles(); } + + QBS_STRING_CONSTANT(emptyArrayValue, "[]") + QBS_STRING_CONSTANT(falseValue, "false") + QBS_STRING_CONSTANT(trueValue, "true") + QBS_STRING_CONSTANT(undefinedValue, "undefined") + + QBS_STRING_CONSTANT(javaScriptCommandType, "JavaScriptCommand") + QBS_STRING_CONSTANT(commandType, "Command") + + QBS_STRING_CONSTANT(pathEnvVar, "PATH") + + QBS_STRING_CONSTANT(dot, ".") + QBS_STRING_CONSTANT(dotDot, "..") + QBS_STRING_CONSTANT(slashDotDot, "/..") + QBS_STRING_CONSTANT(star, "*") + QBS_STRING_CONSTANT(tildeSlash, "~/") + + QBS_STRINGLIST_CONSTANT(qbsFileWildcards, "*.qbs") + QBS_STRINGLIST_CONSTANT(jsFileWildcards, "*.js") + + static const QString &cppLang() { return cpp(); } + + QBS_STRING_CONSTANT(xcode, "xcode") + + QBS_STRING_CONSTANT(aarch64Arch, "aarch64") + QBS_STRING_CONSTANT(amd64Arch, "amd64") + QBS_STRING_CONSTANT(armArch, "arm") + QBS_STRING_CONSTANT(arm64Arch, "arm64") + QBS_STRING_CONSTANT(armv7Arch, "armv7") + QBS_STRING_CONSTANT(i386Arch, "i386") + QBS_STRING_CONSTANT(i586Arch, "i586") + QBS_STRING_CONSTANT(mipsArch, "mips") + QBS_STRING_CONSTANT(mips64Arch, "mips64") + QBS_STRING_CONSTANT(powerPcArch, "powerpc") + QBS_STRING_CONSTANT(ppcArch, "ppc") + QBS_STRING_CONSTANT(ppc64Arch, "ppc64") + QBS_STRING_CONSTANT(x86Arch, "x86") + QBS_STRING_CONSTANT(x86_64Arch, "x86_64") + + QBS_STRING_CONSTANT(profilesSettingsPrefix, "profiles.") + +private: + QBS_STRING_CONSTANT(cpp, "cpp") + QBS_STRING_CONSTANT(explicitlyDependsOn, "explicitlyDependsOn") + QBS_STRING_CONSTANT(explicitlyDependsOnFromDependencies, "explicitlyDependsOnFromDependencies") + QBS_STRING_CONSTANT(fileName, "fileName") + QBS_STRING_CONSTANT(filePath, "filePath") + QBS_STRING_CONSTANT(inputs, "inputs") + QBS_STRING_CONSTANT(install, "install") + QBS_STRING_CONSTANT(path, "path") + QBS_STRING_CONSTANT(product, "product") + QBS_STRING_CONSTANT(profiles, "profiles") + QBS_STRING_CONSTANT(project, "project") + QBS_STRING_CONSTANT(qbs, "qbs") +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/lib/corelib/tools/stringutils.h b/src/lib/corelib/tools/stringutils.h new file mode 100644 index 00000000..59acdccb --- /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 +#include +#include +#include + +namespace qbs { +namespace Internal { + +template +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 +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(), + [](char c){ return !std::isspace(c); })); + return s; + }; + + // trim from end + static const auto rtrim = [](std::string &s) -> std::string & { + s.erase(std::find_if(s.rbegin(), s.rend(), + [](char c){ return !std::isspace(c); }).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 Q_DECLARE_METATYPE +Q_DECLARE_METATYPE(std::string) +#endif + +#endif // QBS_STRINGUTILS_H diff --git a/src/lib/corelib/tools/toolchains.cpp b/src/lib/corelib/tools/toolchains.cpp new file mode 100644 index 00000000..6263fb19 --- /dev/null +++ b/src/lib/corelib/tools/toolchains.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** 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 "toolchains.h" + +#include "stringconstants.h" + +#include + +#include + +namespace qbs { + +namespace Internal { +static const QString clangToolchain() { return QStringLiteral("clang"); } +static const QString clangClToolchain() { return QStringLiteral("clang-cl"); } +static const QString gccToolchain() { return QStringLiteral("gcc"); } +static const QString llvmToolchain() { return QStringLiteral("llvm"); } +static const QString mingwToolchain() { return QStringLiteral("mingw"); } +static const QString msvcToolchain() { return QStringLiteral("msvc"); } +} + +using namespace Internal; + +QStringList canonicalToolchain(const QStringList &toolchain) +{ + static const QStringList knownToolchains { + StringConstants::xcode(), + clangToolchain(), + llvmToolchain(), + mingwToolchain(), + gccToolchain(), + clangClToolchain(), + msvcToolchain() + }; + + // Canonicalize each toolchain in the toolchain list, + // which gets us the aggregate canonicalized (unsorted) list + QStringList toolchains; + for (const QString &toolchainName : toolchain) + toolchains << canonicalToolchain(toolchainName); + toolchains.removeDuplicates(); + + // Find all known toolchains in the canonicalized list, + // removing them from the main list as we go. + QStringList usedKnownToolchains; + for (int i = 0; i < toolchains.size(); ++i) { + if (knownToolchains.contains(toolchains[i])) { + usedKnownToolchains << toolchains[i]; + toolchains.removeAt(i--); + } + } + + // Sort the list of known toolchains into their canonical order. + std::sort(usedKnownToolchains.begin(), usedKnownToolchains.end(), []( + const QString &a, + const QString &b) { + return knownToolchains.indexOf(a) < knownToolchains.indexOf(b); + }); + + // Re-add the known toolchains to the main list (the custom ones go first). + toolchains << usedKnownToolchains; + + // The toolchain list still needs further validation as it may contain mututally exclusive + // toolchain types (for example, llvm and msvc). + return toolchains; +} + +QStringList canonicalToolchain(const QString &name) +{ + const QString &toolchainName = name.toLower(); + QStringList toolchains(toolchainName); + if (toolchainName == StringConstants::xcode()) + toolchains << canonicalToolchain(clangToolchain()); + else if (toolchainName == clangToolchain()) + toolchains << canonicalToolchain(llvmToolchain()); + else if (toolchainName == llvmToolchain() || + toolchainName == mingwToolchain()) { + toolchains << canonicalToolchain(QStringLiteral("gcc")); + } else if (toolchainName == clangClToolchain()) { + toolchains << canonicalToolchain(msvcToolchain()); + } + return toolchains; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/toolchains.h b/src/lib/corelib/tools/toolchains.h new file mode 100644 index 00000000..b8d2c8ee --- /dev/null +++ b/src/lib/corelib/tools/toolchains.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** 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 QBS_TOOLCHAINS_H +#define QBS_TOOLCHAINS_H + +#include "qbs_export.h" +#include + +namespace qbs { + +QBS_EXPORT QStringList canonicalToolchain(const QStringList &toolchain); +QBS_EXPORT QStringList canonicalToolchain(const QString &toolchainName); + +} // namespace qbs + +#endif // Include guard. diff --git a/src/lib/corelib/tools/tools.pri b/src/lib/corelib/tools/tools.pri new file mode 100644 index 00000000..00d87ecc --- /dev/null +++ b/src/lib/corelib/tools/tools.pri @@ -0,0 +1,146 @@ +include(../../../install_prefix.pri) + +INCLUDEPATH += $$PWD/../.. # for plugins + +QBS_SYSTEM_SETTINGS_DIR = $$(QBS_SYSTEM_SETTINGS_DIR) +!isEmpty(QBS_SYSTEM_SETTINGS_DIR) { + DEFINES += QBS_SYSTEM_SETTINGS_DIR=\\\"$$QBS_SYSTEM_SETTINGS_DIR\\\" +} + +HEADERS += \ + $$PWD/architectures.h \ + $$PWD/buildgraphlocker.h \ + $$PWD/clangclinfo.h \ + $$PWD/codelocation.h \ + $$PWD/commandechomode.h \ + $$PWD/dynamictypecheck.h \ + $$PWD/error.h \ + $$PWD/executablefinder.h \ + $$PWD/fileinfo.h \ + $$PWD/filesaver.h \ + $$PWD/filetime.h \ + $$PWD/generateoptions.h \ + $$PWD/id.h \ + $$PWD/iosutils.h \ + $$PWD/joblimits.h \ + $$PWD/jsliterals.h \ + $$PWD/jsonhelper.h \ + $$PWD/launcherinterface.h \ + $$PWD/launcherpackets.h \ + $$PWD/launchersocket.h \ + $$PWD/msvcinfo.h \ + $$PWD/persistence.h \ + $$PWD/scannerpluginmanager.h \ + $$PWD/scripttools.h \ + $$PWD/set.h \ + $$PWD/settings.h \ + $$PWD/settingsmodel.h \ + $$PWD/settingsrepresentation.h \ + $$PWD/pathutils.h \ + $$PWD/preferences.h \ + $$PWD/profile.h \ + $$PWD/profiling.h \ + $$PWD/processresult.h \ + $$PWD/processresult_p.h \ + $$PWD/processutils.h \ + $$PWD/progressobserver.h \ + $$PWD/projectgeneratormanager.h \ + $$PWD/qbspluginmanager.h \ + $$PWD/qbsprocess.h \ + $$PWD/shellutils.h \ + $$PWD/stlutils.h \ + $$PWD/stringutils.h \ + $$PWD/toolchains.h \ + $$PWD/hostosinfo.h \ + $$PWD/buildoptions.h \ + $$PWD/installoptions.h \ + $$PWD/cleanoptions.h \ + $$PWD/setupprojectparameters.h \ + $$PWD/weakpointer.h \ + $$PWD/qbs_export.h \ + $$PWD/qbsassert.h \ + $$PWD/qttools.h \ + $$PWD/settingscreator.h \ + $$PWD/stringconstants.h \ + $$PWD/version.h \ + $$PWD/visualstudioversioninfo.h \ + $$PWD/vsenvironmentdetector.h + +SOURCES += \ + $$PWD/architectures.cpp \ + $$PWD/buildgraphlocker.cpp \ + $$PWD/clangclinfo.cpp \ + $$PWD/codelocation.cpp \ + $$PWD/commandechomode.cpp \ + $$PWD/error.cpp \ + $$PWD/executablefinder.cpp \ + $$PWD/fileinfo.cpp \ + $$PWD/filesaver.cpp \ + $$PWD/filetime.cpp \ + $$PWD/generateoptions.cpp \ + $$PWD/id.cpp \ + $$PWD/joblimits.cpp \ + $$PWD/jsliterals.cpp \ + $$PWD/launcherinterface.cpp \ + $$PWD/launcherpackets.cpp \ + $$PWD/launchersocket.cpp \ + $$PWD/msvcinfo.cpp \ + $$PWD/persistence.cpp \ + $$PWD/scannerpluginmanager.cpp \ + $$PWD/scripttools.cpp \ + $$PWD/settings.cpp \ + $$PWD/settingsmodel.cpp \ + $$PWD/settingsrepresentation.cpp \ + $$PWD/preferences.cpp \ + $$PWD/processresult.cpp \ + $$PWD/processutils.cpp \ + $$PWD/profile.cpp \ + $$PWD/profiling.cpp \ + $$PWD/progressobserver.cpp \ + $$PWD/projectgeneratormanager.cpp \ + $$PWD/qbspluginmanager.cpp \ + $$PWD/qbsprocess.cpp \ + $$PWD/shellutils.cpp \ + $$PWD/buildoptions.cpp \ + $$PWD/installoptions.cpp \ + $$PWD/cleanoptions.cpp \ + $$PWD/setupprojectparameters.cpp \ + $$PWD/qbsassert.cpp \ + $$PWD/qttools.cpp \ + $$PWD/settingscreator.cpp \ + $$PWD/toolchains.cpp \ + $$PWD/version.cpp \ + $$PWD/visualstudioversioninfo.cpp \ + $$PWD/vsenvironmentdetector.cpp + +osx { + HEADERS += $$PWD/applecodesignutils.h + SOURCES += $$PWD/applecodesignutils.cpp + LIBS += -framework Security +} + +!qbs_no_dev_install { + tools_headers.files = \ + $$PWD/architectures.h \ + $$PWD/buildoptions.h \ + $$PWD/cleanoptions.h \ + $$PWD/codelocation.h \ + $$PWD/commandechomode.h \ + $$PWD/error.h \ + $$PWD/generateoptions.h \ + $$PWD/installoptions.h \ + $$PWD/joblimits.h \ + $$PWD/preferences.h \ + $$PWD/processresult.h \ + $$PWD/profile.h \ + $$PWD/projectgeneratormanager.h \ + $$PWD/qbs_export.h \ + $$PWD/settings.h \ + $$PWD/settingsmodel.h \ + $$PWD/settingsrepresentation.h \ + $$PWD/setupprojectparameters.h \ + $$PWD/toolchains.h \ + $$PWD/version.h + tools_headers.path = $${QBS_INSTALL_PREFIX}/include/qbs/tools + INSTALLS += tools_headers +} diff --git a/src/lib/corelib/tools/version.cpp b/src/lib/corelib/tools/version.cpp new file mode 100644 index 00000000..f653256b --- /dev/null +++ b/src/lib/corelib/tools/version.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 "version.h" + +#include +#include + +namespace qbs { + +Version Version::fromString(const QString &versionString, bool buildNumberAllowed) +{ + QString pattern = QStringLiteral("(\\d+)"); // At least one number. + for (int i = 0; i < 2; ++i) + pattern += QStringLiteral("(?:\\.(\\d+))?"); // Followed by a dot and a number up to two times. + if (buildNumberAllowed) + pattern += QStringLiteral("(?:[-.](\\d+))?"); // And possibly a dash or dot followed by the build number. + QRegExp rex(pattern); + if (!rex.exactMatch(versionString)) + return Version{}; + const int majorNr = rex.cap(1).toInt(); + const int minorNr = rex.captureCount() >= 2 ? rex.cap(2).toInt() : 0; + const int patchNr = rex.captureCount() >= 3 ? rex.cap(3).toInt() : 0; + const int buildNr = rex.captureCount() >= 4 ? rex.cap(4).toInt() : 0; + return Version{majorNr, minorNr, patchNr, buildNr}; +} + +QString Version::toString(const QChar &separator, const QChar &buildSeparator) const +{ + if (m_build) { + return QStringLiteral("%1%5%2%5%3%6%4") + .arg(QString::number(m_major), QString::number(m_minor), + QString::number(m_patch), QString::number(m_build), + separator, buildSeparator); + } + return QStringLiteral("%1%4%2%4%3") + .arg(QString::number(m_major), QString::number(m_minor), + QString::number(m_patch), separator); +} + +VersionRange &VersionRange::narrowDown(const VersionRange &other) +{ + if (other.minimum > minimum) + minimum = other.minimum; + if (other.maximum.isValid() && other.maximum < maximum) + maximum = other.maximum; + return *this; +} + +} // namespace qbs diff --git a/src/lib/corelib/tools/version.h b/src/lib/corelib/tools/version.h new file mode 100644 index 00000000..7b2d23eb --- /dev/null +++ b/src/lib/corelib/tools/version.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** 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 QBS_VERSION_H +#define QBS_VERSION_H + +#include "qbs_export.h" + +#include +#include + +QT_BEGIN_NAMESPACE +class QString; +QT_END_NAMESPACE + +namespace qbs { + +class Version +{ +public: + constexpr explicit Version(int majorVersion = 0, int minorVersion = 0, int patchLevel = 0, + int buildNr = 0) + : m_major(majorVersion), m_minor(minorVersion), m_patch(patchLevel), m_build(buildNr) + { } + + constexpr bool isValid() const { return m_major || m_minor || m_patch || m_build; } + + constexpr int majorVersion() const { return m_major; } + constexpr void setMajorVersion(int majorVersion) { m_major = majorVersion; } + + constexpr int minorVersion() const { return m_minor; } + constexpr void setMinorVersion(int minorVersion) { m_minor = minorVersion;} + + constexpr int patchLevel() const { return m_patch; } + constexpr void setPatchLevel(int patchLevel) { m_patch = patchLevel; } + + constexpr int buildNumber() const { return m_build; } + constexpr void setBuildNumber(int nr) { m_build = nr; } + + static QBS_EXPORT Version fromString(const QString &versionString, bool buildNumberAllowed = false); + QString QBS_EXPORT toString(const QChar &separator = QLatin1Char('.'), + const QChar &buildSeparator = QLatin1Char('-')) const; + +private: + int m_major; + int m_minor; + int m_patch; + int m_build; +}; + +class VersionRange +{ +public: + constexpr VersionRange() = default; + constexpr VersionRange(const Version &minVersion, const Version &maxVersion) + : minimum(minVersion), maximum(maxVersion) + { } + + Version minimum; + Version maximum; // exclusive + + VersionRange &narrowDown(const VersionRange &other); +}; + +constexpr inline int compare(const Version &lhs, const Version &rhs) +{ + if (lhs.majorVersion() < rhs.majorVersion()) + return -1; + if (lhs.majorVersion() > rhs.majorVersion()) + return 1; + if (lhs.minorVersion() < rhs.minorVersion()) + return -1; + if (lhs.minorVersion() > rhs.minorVersion()) + return 1; + if (lhs.patchLevel() < rhs.patchLevel()) + return -1; + if (lhs.patchLevel() > rhs.patchLevel()) + return 1; + if (lhs.buildNumber() < rhs.buildNumber()) + return -1; + if (lhs.buildNumber() > rhs.buildNumber()) + return 1; + return 0; +} + +constexpr inline bool operator==(const Version &lhs, const Version &rhs) +{ return compare(lhs, rhs) == 0; } +constexpr inline bool operator!=(const Version &lhs, const Version &rhs) +{ return !operator==(lhs, rhs); } +constexpr inline bool operator<(const Version &lhs, const Version &rhs) +{ return compare(lhs, rhs) < 0; } +constexpr inline bool operator>(const Version &lhs, const Version &rhs) +{ return compare(lhs, rhs) > 0; } +constexpr inline bool operator<=(const Version &lhs, const Version &rhs) +{ return !operator>(lhs, rhs); } +constexpr inline bool operator>=(const Version &lhs, const Version &rhs) +{ return !operator<(lhs, rhs); } + +} // namespace qbs + +#endif // QBS_VERSION_H diff --git a/src/lib/corelib/tools/visualstudioversioninfo.cpp b/src/lib/corelib/tools/visualstudioversioninfo.cpp new file mode 100644 index 00000000..b5ee3e71 --- /dev/null +++ b/src/lib/corelib/tools/visualstudioversioninfo.cpp @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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 "visualstudioversioninfo.h" +#include +#include +#include + +namespace qbs { +namespace Internal { + +VisualStudioVersionInfo::VisualStudioVersionInfo() = default; + +VisualStudioVersionInfo::VisualStudioVersionInfo(const Version &version) + : m_version(version) +{ + QBS_CHECK(version.minorVersion() == 0 || version == Version(7, 1) + || version.majorVersion() >= 15); +} + +std::set VisualStudioVersionInfo::knownVersions() +{ + static const std::set known = { + Version(16), Version(15), Version(14), Version(12), Version(11), Version(10), Version(9), + Version(8), Version(7, 1), Version(7), Version(6) + }; + return known; +} + +bool VisualStudioVersionInfo::operator<(const VisualStudioVersionInfo &other) const +{ + return m_version < other.m_version; +} + +bool VisualStudioVersionInfo::operator==(const VisualStudioVersionInfo &other) const +{ + return m_version == other.m_version; +} + +bool VisualStudioVersionInfo::usesMsBuild() const +{ + return m_version.majorVersion() >= 10; +} + +bool VisualStudioVersionInfo::usesVcBuild() const +{ + return m_version.majorVersion() <= 9; +} + +bool VisualStudioVersionInfo::usesSolutions() const +{ + return m_version.majorVersion() >= 7; +} + +Version VisualStudioVersionInfo::version() const +{ + return m_version; +} + +int VisualStudioVersionInfo::marketingVersion() const +{ + switch (m_version.majorVersion()) { + case 6: + return 6; + case 7: + switch (m_version.minorVersion()) { + case 0: + return 2002; + case 1: + return 2003; + default: + Q_UNREACHABLE(); + } + break; + case 8: + return 2005; + case 9: + return 2008; + case 10: + return 2010; + case 11: + return 2012; + case 12: + return 2013; + case 14: + return 2015; + case 15: + return 2017; + case 16: + return 2019; + default: + qWarning() << QStringLiteral("unrecognized Visual Studio version: ") + << m_version.toString(); + return 0; + } +} + +QString VisualStudioVersionInfo::solutionVersion() const +{ + // Visual Studio 2012 finally stabilized the solution version + if (m_version >= Version(11)) + return QStringLiteral("12.00"); + + if (m_version >= Version(8)) + return QStringLiteral("%1.00").arg(m_version.majorVersion() + 1); + + if (m_version >= Version(7, 1)) + return QStringLiteral("8.00"); + + if (m_version >= Version(7)) + return QStringLiteral("7.00"); + + // these versions do not use solution files + // Visual Studio 6 uses .dsw files which are format version 6.00 but these are different + Q_ASSERT(!usesSolutions()); + Q_UNREACHABLE(); +} + +QString VisualStudioVersionInfo::toolsVersion() const +{ + // "https://msdn.microsoft.com/en-us/library/bb383796.aspx" + // Starting in Visual Studio 2013, the MSBuild Toolset version is the same as the Visual Studio + // version number"... again + if (m_version >= Version(12)) + return QStringLiteral("%1.0").arg(m_version.majorVersion()); + + if (m_version >= Version(10)) + return QStringLiteral("4.0"); + + // pre-MSBuild + return QStringLiteral("%1,00").arg(m_version.majorVersion()); +} + +QString VisualStudioVersionInfo::platformToolsetVersion() const +{ + static std::pair table[] = { + {16, QStringLiteral("v142")}, // VS 2019 + {15, QStringLiteral("v141")} // VS 2017 + }; + for (const auto &p : table) { + if (p.first == m_version.majorVersion()) + return p.second; + } + return QStringLiteral("v%1").arg(m_version.majorVersion() * 10); +} + +quint32 qHash(const VisualStudioVersionInfo &info) +{ + return qHash(info.version().toString()); +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/visualstudioversioninfo.h b/src/lib/corelib/tools/visualstudioversioninfo.h new file mode 100644 index 00000000..d4b22662 --- /dev/null +++ b/src/lib/corelib/tools/visualstudioversioninfo.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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_VISUALSTUDIOVERSIONINFO_H +#define QBS_VISUALSTUDIOVERSIONINFO_H + +#include "qbs_export.h" + +#include + +#include + +#include + +namespace qbs { +namespace Internal { + +class QBS_EXPORT VisualStudioVersionInfo +{ +public: + VisualStudioVersionInfo(); + VisualStudioVersionInfo(const Version &version); + + static std::set knownVersions(); + + bool operator<(const VisualStudioVersionInfo &other) const; + bool operator==(const VisualStudioVersionInfo &other) const; + + bool usesMsBuild() const; + bool usesVcBuild() const; + bool usesSolutions() const; + + Version version() const; + int marketingVersion() const; + + QString solutionVersion() const; + QString toolsVersion() const; + QString platformToolsetVersion() const; + +private: + Version m_version; +}; + +quint32 qHash(const VisualStudioVersionInfo &info); + +} // namespace Internal +} // namespace qbs + +#endif // QBS_VISUALSTUDIOVERSIONINFO_H diff --git a/src/lib/corelib/tools/vsenvironmentdetector.cpp b/src/lib/corelib/tools/vsenvironmentdetector.cpp new file mode 100644 index 00000000..b0788823 --- /dev/null +++ b/src/lib/corelib/tools/vsenvironmentdetector.cpp @@ -0,0 +1,284 @@ +/**************************************************************************** +** +** 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 "vsenvironmentdetector.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#include +#endif + +namespace qbs { +namespace Internal { + +static QString windowsSystem32Path() +{ +#ifdef Q_OS_WIN + wchar_t str[UNICODE_STRING_MAX_CHARS]; + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, 0, str))) + return QString::fromUtf16(reinterpret_cast(str)); +#endif + return {}; +} + +VsEnvironmentDetector::VsEnvironmentDetector(QString vcvarsallPath) + : m_windowsSystemDirPath(windowsSystem32Path()) + , m_vcvarsallPath(std::move(vcvarsallPath)) +{ +} + +bool VsEnvironmentDetector::start(MSVC *msvc) +{ + return start(std::vector{ msvc }); +} + +bool VsEnvironmentDetector::start(std::vector msvcs) +{ + std::sort(msvcs.begin(), msvcs.end(), [] (const MSVC *a, const MSVC *b) -> bool { + return a->vcInstallPath < b->vcInstallPath; + }); + + std::vector compatibleMSVCs; + QString lastVcInstallPath; + bool someMSVCDetected = false; + for (MSVC * const msvc : msvcs) { + if (lastVcInstallPath != msvc->vcInstallPath) { + lastVcInstallPath = msvc->vcInstallPath; + if (!compatibleMSVCs.empty()) { + if (startDetection(compatibleMSVCs)) + someMSVCDetected = true; + compatibleMSVCs.clear(); + } + } + compatibleMSVCs.push_back(msvc); + } + if (startDetection(compatibleMSVCs)) + someMSVCDetected = true; + return someMSVCDetected; +} + +QString VsEnvironmentDetector::findVcVarsAllBat(const MSVC &msvc, + std::vector &searchedPaths) const +{ + // ### We can only rely on MSVC.vcInstallPath being set + // when this is called from utilitiesextension.cpp :-( + // If we knew the vsInstallPath at this point we could just use that + // instead of searching for vcvarsall.bat candidates. + QDir dir(msvc.vcInstallPath); + for (;;) { + if (!dir.cdUp()) + return {}; + if (dir.dirName() == QLatin1String("VC")) + break; + } + const QString vcvarsallbat = QStringLiteral("vcvarsall.bat"); + QString path = vcvarsallbat; + QString fullPath = dir.absoluteFilePath(path); + if (dir.exists(path)) + return fullPath; + else + searchedPaths.push_back(fullPath); + path = QStringLiteral("Auxiliary/Build/") + vcvarsallbat; + fullPath = dir.absoluteFilePath(path); + if (dir.exists(path)) + return fullPath; + else + searchedPaths.push_back(fullPath); + return {}; +} + +bool VsEnvironmentDetector::startDetection(const std::vector &compatibleMSVCs) +{ + std::vector searchedPaths; + + if (!m_vcvarsallPath.isEmpty() && !QFileInfo::exists(m_vcvarsallPath)) { + m_errorString = Tr::tr("%1 does not exist.").arg(m_vcvarsallPath); + return false; + } + + const auto vcvarsallbat = !m_vcvarsallPath.isEmpty() + ? m_vcvarsallPath + : findVcVarsAllBat(**compatibleMSVCs.begin(), searchedPaths); + if (vcvarsallbat.isEmpty()) { + if (!searchedPaths.empty()) { + m_errorString = Tr::tr( + "Cannot find 'vcvarsall.bat' at any of the following locations:\n\t") + + join(searchedPaths, QStringLiteral("\n\t")); + } else { + m_errorString = Tr::tr("Cannot find 'vcvarsall.bat'."); + } + return false; + } + + QTemporaryFile tmpFile(QDir::tempPath() + QLatin1Char('/') + QStringLiteral("XXXXXX.bat")); + if (!tmpFile.open()) { + m_errorString = Tr::tr("Cannot open temporary file '%1' for writing.").arg( + tmpFile.fileName()); + return false; + } + + writeBatchFile(&tmpFile, vcvarsallbat, compatibleMSVCs); + tmpFile.flush(); + + QProcess process; + static const QString shellFilePath = QStringLiteral("cmd.exe"); + process.start(shellFilePath, QStringList() + << QStringLiteral("/C") << tmpFile.fileName()); + if (!process.waitForStarted()) { + m_errorString = Tr::tr("Failed to start '%1'.").arg(shellFilePath); + return false; + } + process.waitForFinished(-1); + if (process.exitStatus() != QProcess::NormalExit) { + m_errorString = Tr::tr("Process '%1' did not exit normally.").arg(shellFilePath); + return false; + } + if (process.exitCode() != 0) { + m_errorString = Tr::tr("Failed to detect Visual Studio environment."); + return false; + } + parseBatOutput(process.readAllStandardOutput(), compatibleMSVCs); + return true; + +} + +static void batClearVars(QTextStream &s, const QStringList &varnames) +{ + for (const QString &varname : varnames) + s << "set " << varname << '=' << Qt::endl; +} + +static void batPrintVars(QTextStream &s, const QStringList &varnames) +{ + for (const QString &varname : varnames) + s << "echo " << varname << "=%" << varname << '%' << Qt::endl; +} + +static QString vcArchitecture(const MSVC *msvc) +{ + QString vcArch = msvc->architecture; + if (msvc->architecture == StringConstants::armv7Arch()) + vcArch = StringConstants::armArch(); + if (msvc->architecture == StringConstants::x86_64Arch()) + vcArch = StringConstants::amd64Arch(); + + const QString hostPrefixes[] = { + StringConstants::x86Arch(), + QStringLiteral("amd64_"), + QStringLiteral("x86_") + }; + for (const QString &hostPrefix : hostPrefixes) { + if (QFile::exists(msvc->clPathForArchitecture(hostPrefix + vcArch))) { + vcArch.prepend(hostPrefix); + break; + } + } + + return vcArch; +} + +void VsEnvironmentDetector::writeBatchFile(QIODevice *device, const QString &vcvarsallbat, + const std::vector &msvcs) const +{ + const QStringList varnames = QStringList() << StringConstants::pathEnvVar() + << QStringLiteral("INCLUDE") << QStringLiteral("LIB") << QStringLiteral("WindowsSdkDir") + << QStringLiteral("WindowsSDKVersion") << QStringLiteral("VSINSTALLDIR"); + QTextStream s(device); + using Qt::endl; + s << "@echo off" << endl; + // Avoid execution of powershell (in vsdevcmd.bat), which is not in the cleared PATH + s << "set VSCMD_SKIP_SENDTELEMETRY=1" << endl; + for (const MSVC *msvc : msvcs) { + s << "echo --" << msvc->architecture << "--" << endl + << "setlocal" << endl; + batClearVars(s, varnames); + s << "set PATH=" << m_windowsSystemDirPath << endl; // vcvarsall.bat needs tools from here + s << "call \"" << vcvarsallbat << "\" " << vcArchitecture(msvc) + << " || exit /b 1" << endl; + batPrintVars(s, varnames); + s << "endlocal" << endl; + } +} + +void VsEnvironmentDetector::parseBatOutput(const QByteArray &output, std::vector msvcs) +{ + QString arch; + QProcessEnvironment *targetEnv = nullptr; + const auto lines = output.split('\n'); + for (QByteArray line : lines) { + line = line.trimmed(); + if (line.isEmpty()) + continue; + + if (line.startsWith("--") && line.endsWith("--")) { + line.remove(0, 2); + line.chop(2); + arch = QString::fromLocal8Bit(line); + targetEnv = &msvcs.front()->environment; + msvcs.erase(msvcs.begin()); + } else { + int idx = line.indexOf('='); + if (idx < 0) + continue; + QBS_CHECK(targetEnv); + const QString name = QString::fromLocal8Bit(line.left(idx)); + QString value = QString::fromLocal8Bit(line.mid(idx + 1)); + if (name.compare(StringConstants::pathEnvVar(), Qt::CaseInsensitive) == 0) + value.remove(m_windowsSystemDirPath); + if (value.endsWith(QLatin1Char(';'))) + value.chop(1); + targetEnv->insert(name, value); + } + } +} + +} // namespace Internal +} // namespace qbs diff --git a/src/lib/corelib/tools/vsenvironmentdetector.h b/src/lib/corelib/tools/vsenvironmentdetector.h new file mode 100644 index 00000000..7fa152cb --- /dev/null +++ b/src/lib/corelib/tools/vsenvironmentdetector.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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 QBS_VSENVIRONMENTDETECTOR_H +#define QBS_VSENVIRONMENTDETECTOR_H + +#include "qbs_export.h" +#include "msvcinfo.h" + +#include + +QT_BEGIN_NAMESPACE +class QIODevice; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { + +class MSVC; + +class QBS_EXPORT VsEnvironmentDetector +{ +public: + explicit VsEnvironmentDetector(QString vcvarsallPath = QString()); + + bool start(MSVC *msvc); + bool start(std::vector msvcs); + QString errorString() const { return m_errorString; } + +private: + QString findVcVarsAllBat(const MSVC &msvc, std::vector &searchedPaths) const; + bool startDetection(const std::vector &compatibleMSVCs); + void writeBatchFile(QIODevice *device, const QString &vcvarsallbat, const std::vector &msvcs) const; + void parseBatOutput(const QByteArray &output, std::vector msvcs); + + const QString m_windowsSystemDirPath; + const QString m_vcvarsallPath; + QString m_errorString; +}; + +} // namespace Internal +} // namespace qbs + +#endif // QBS_VSENVIRONMENTDETECTOR_H diff --git a/src/lib/corelib/tools/weakpointer.h b/src/lib/corelib/tools/weakpointer.h new file mode 100644 index 00000000..fecae682 --- /dev/null +++ b/src/lib/corelib/tools/weakpointer.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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 QBS_WEAKPOINTER_H +#define QBS_WEAKPOINTER_H + +#include + +namespace qbs { +namespace Internal { + +template class WeakPointer : public std::weak_ptr +{ +public: + WeakPointer() : std::weak_ptr() {} + WeakPointer(const std::shared_ptr &sharedPointer) : std::weak_ptr(sharedPointer) {} + template WeakPointer(const std::shared_ptr &sp) : std::weak_ptr(sp) { } + + T *get() const { auto p = std::weak_ptr::lock(); return p.get(); } + operator bool() const { return !std::weak_ptr::expired(); } + bool operator!() const { return std::weak_ptr::expired(); } + operator T*() const { return checkedData(); } + T *operator->() const { return checkedData(); } + T operator*() const { return *checkedData(); } + +private: + T *checkedData() const { + T * const d = get(); + Q_ASSERT(d); // Calling code is not expecting this situation. + return d; + } +}; + +template bool operator==(const WeakPointer &a, const WeakPointer &b) +{ + return a.get() == b.get(); +} + +template bool operator!=(const WeakPointer &a, const WeakPointer &b) +{ + return a.get() != b.get(); +} + +template bool operator==(const WeakPointer &a, + const std::shared_ptr &b) +{ + return a.lock() == b; +} + +template bool operator!=(const WeakPointer &a, + const std::shared_ptr &b) +{ + return a.lock() != b; +} + +} // namespace Internal +} // namespace qbs + +#endif // QBS_WEAKPOINTER_H diff --git a/src/lib/corelib/use_corelib.pri b/src/lib/corelib/use_corelib.pri new file mode 100644 index 00000000..c674ee66 --- /dev/null +++ b/src/lib/corelib/use_corelib.pri @@ -0,0 +1,47 @@ +include(../../../qbs_version.pri) +include(../../library_dirname.pri) + +isEmpty(QBSLIBDIR) { + QBSLIBDIR = $$OUT_PWD/../../../$${QBS_LIBRARY_DIRNAME} +} + +unix { + LIBS += -L$$QBSLIBDIR -lqbscore +} + +isEmpty(QBS_RPATH): QBS_RPATH = ../$$QBS_LIBRARY_DIRNAME +!qbs_disable_rpath { + linux-*: QMAKE_LFLAGS += -Wl,-z,origin \'-Wl,-rpath,\$\$ORIGIN/$${QBS_RPATH}\' + macx: QMAKE_LFLAGS += -Wl,-rpath,@loader_path/$${QBS_RPATH} +} + +!CONFIG(static, static|shared) { + QBSCORELIBSUFFIX = $$QBS_VERSION_MAJ +} + +win32 { + CONFIG(debug, debug|release) { + QBSCORELIB = qbscored$$QBSCORELIBSUFFIX + } + CONFIG(release, debug|release) { + QBSCORELIB = qbscore$$QBSCORELIBSUFFIX + } + msvc { + LIBS += /LIBPATH:$$QBSLIBDIR + QBSCORELIB = $${QBSCORELIB}.lib + LIBS += Shell32.lib + } else { + LIBS += -L$${QBSLIBDIR} + QBSCORELIB = lib$${QBSCORELIB} + } + LIBS += $$QBSCORELIB +} + +INCLUDEPATH += \ + $$PWD + +CONFIG(static, static|shared) { + DEFINES += QBS_STATIC_LIB +} +qbs_enable_project_file_updates:DEFINES += QBS_ENABLE_PROJECT_FILE_UPDATES +qbs_enable_unit_tests:DEFINES += QBS_ENABLE_UNIT_TESTS diff --git a/src/lib/corelib/use_installed_corelib.pri b/src/lib/corelib/use_installed_corelib.pri new file mode 100644 index 00000000..4ff72414 --- /dev/null +++ b/src/lib/corelib/use_installed_corelib.pri @@ -0,0 +1,38 @@ +include(qbs_version.pri) + +QBSLIBDIR=$${PWD}/../../lib +unix { + LIBS += -L$$QBSLIBDIR -lqbscore +} + +!qbs_disable_rpath:unix:QMAKE_LFLAGS += -Wl,-rpath,$${QBSLIBDIR} + +!CONFIG(static, static|shared) { + QBSCORELIBSUFFIX = $$QBS_VERSION_MAJ +} + +win32 { + CONFIG(debug, debug|release) { + QBSCORELIB = qbscored$$QBSCORELIBSUFFIX + } + CONFIG(release, debug|release) { + QBSCORELIB = qbscore$$QBSCORELIBSUFFIX + } + msvc { + LIBS += /LIBPATH:$$QBSLIBDIR + QBSCORELIB = $${QBSCORELIB}.lib + LIBS += Shell32.lib + } else { + LIBS += -L$${QBSLIBDIR} + QBSCORELIB = lib$${QBSCORELIB} + } + LIBS += $$QBSCORELIB +} + +INCLUDEPATH += $${PWD} + +CONFIG(static, static|shared) { + DEFINES += QBS_STATIC_LIB +} +qbs_enable_project_file_updates:DEFINES += QBS_ENABLE_PROJECT_FILE_UPDATES +qbs_enable_unit_tests:DEFINES += QBS_ENABLE_UNIT_TESTS diff --git a/src/lib/library.pri b/src/lib/library.pri new file mode 100644 index 00000000..7d12e107 --- /dev/null +++ b/src/lib/library.pri @@ -0,0 +1,32 @@ +include(library_base.pri) + +CONFIG(static, static|shared) { + DEFINES += QBS_STATIC_LIB +} else { + DEFINES += QBS_LIBRARY +} + +qbs_disable_rpath { + osx:QMAKE_LFLAGS_SONAME = -Wl,-install_name,$$QBS_INSTALL_PREFIX/$$QBS_LIBRARY_DIRNAME/ +} else { + osx:QMAKE_LFLAGS_SONAME = -Wl,-install_name,@rpath/ +} + +linux { + # Turn off absurd qmake's soname "logic" and directly add the linker flag. + QMAKE_LFLAGS_SONAME = + QMAKE_LFLAGS += -Wl,-soname=lib$${TARGET}.so.$${QBS_VERSION_MAJ}.$${QBS_VERSION_MIN} +} + +win32 { + dlltarget.path = $${QBS_INSTALL_PREFIX}/bin + INSTALLS += dlltarget +} + +!win32|!qbs_no_dev_install { + !isEmpty(QBS_LIB_INSTALL_DIR): \ + target.path = $${QBS_LIB_INSTALL_DIR} + else: \ + target.path = $${QBS_INSTALL_PREFIX}/$${QBS_LIBRARY_DIRNAME} + INSTALLS += target +} diff --git a/src/lib/library_base.pri b/src/lib/library_base.pri new file mode 100644 index 00000000..093dcd39 --- /dev/null +++ b/src/lib/library_base.pri @@ -0,0 +1,21 @@ +include(../library_dirname.pri) +include(../install_prefix.pri) + +TEMPLATE = lib +QT = core +!isEmpty(QBS_DLLDESTDIR):DLLDESTDIR = $${QBS_DLLDESTDIR} +else:DLLDESTDIR = ../../../bin +!isEmpty(QBS_DESTDIR):DESTDIR = $${QBS_DESTDIR} +else:DESTDIR = ../../../$${QBS_LIBRARY_DIRNAME} + +DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_PROCESS_COMBINED_ARGUMENT_START +qbs_enable_unit_tests:DEFINES += QBS_ENABLE_UNIT_TESTS +INCLUDEPATH += $${PWD}/../ +contains(QT_CONFIG, reduce_exports):CONFIG += hide_symbols +win32:CONFIG(debug, debug|release):TARGET = $${TARGET}d +CONFIG(debug, debug|release):DEFINES += QT_STRICT_ITERATORS +CONFIG += c++14 +CONFIG += create_prl + +include(../../qbs_version.pri) +VERSION = $${QBS_VERSION} diff --git a/src/lib/libs.qbs b/src/lib/libs.qbs new file mode 100644 index 00000000..1cea47c5 --- /dev/null +++ b/src/lib/libs.qbs @@ -0,0 +1,9 @@ +import qbs + +Project { + references: [ + "corelib/corelib.qbs", + "msbuild/msbuild.qbs", + "scriptengine/scriptengine.qbs", + ] +} diff --git a/src/lib/msbuild/io/msbuildprojectwriter.cpp b/src/lib/msbuild/io/msbuildprojectwriter.cpp new file mode 100644 index 00000000..12fbe2da --- /dev/null +++ b/src/lib/msbuild/io/msbuildprojectwriter.cpp @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildprojectwriter.h" + +#include "../msbuild/imsbuildnodevisitor.h" +#include "../msbuild/msbuildimport.h" +#include "../msbuild/msbuildimportgroup.h" +#include "../msbuild/msbuilditem.h" +#include "../msbuild/msbuilditemdefinitiongroup.h" +#include "../msbuild/msbuilditemgroup.h" +#include "../msbuild/msbuilditemmetadata.h" +#include "../msbuild/msbuildproject.h" +#include "../msbuild/msbuildproperty.h" +#include "../msbuild/msbuildpropertygroup.h" + +#include + +#include + +namespace qbs { + +static const QString kMSBuildSchemaURI = + QStringLiteral("http://schemas.microsoft.com/developer/msbuild/2003"); + +class MSBuildProjectWriterPrivate : public IMSBuildNodeVisitor +{ +public: + std::ostream *device = nullptr; + QByteArray buffer; + std::unique_ptr writer; + + void visitStart(const MSBuildImport *import) override; + void visitEnd(const MSBuildImport *import) override; + + void visitStart(const MSBuildImportGroup *importGroup) override; + void visitEnd(const MSBuildImportGroup *importGroup) override; + + void visitStart(const MSBuildItem *item) override; + void visitEnd(const MSBuildItem *item) override; + + void visitStart(const MSBuildItemDefinitionGroup *itemDefinitionGroup) override; + void visitEnd(const MSBuildItemDefinitionGroup *itemDefinitionGroup) override; + + void visitStart(const MSBuildItemGroup *itemGroup) override; + void visitEnd(const MSBuildItemGroup *itemGroup) override; + + void visitStart(const MSBuildItemMetadata *itemMetadata) override; + void visitEnd(const MSBuildItemMetadata *itemMetadata) override; + + void visitStart(const MSBuildProject *project) override; + void visitEnd(const MSBuildProject *project) override; + + void visitStart(const MSBuildProperty *property) override; + void visitEnd(const MSBuildProperty *property) override; + + void visitStart(const MSBuildPropertyGroup *propertyGroup) override; + void visitEnd(const MSBuildPropertyGroup *propertyGroup) override; +}; + +MSBuildProjectWriter::MSBuildProjectWriter(std::ostream *device) + : d(new MSBuildProjectWriterPrivate) +{ + d->device = device; + d->writer = std::make_unique(&d->buffer); + d->writer->setAutoFormatting(true); +} + +MSBuildProjectWriter::~MSBuildProjectWriter() +{ + delete d; +} + +bool MSBuildProjectWriter::write(const MSBuildProject *project) +{ + d->buffer.clear(); + d->writer->writeStartDocument(); + project->accept(d); + d->writer->writeEndDocument(); + 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) +{ + writer->writeStartElement(QStringLiteral("Import")); + writer->writeAttribute(QStringLiteral("Project"), import->project()); + if (!import->condition().isEmpty()) + writer->writeAttribute(QStringLiteral("Condition"), import->condition()); +} + +void MSBuildProjectWriterPrivate::visitEnd(const MSBuildImport *) +{ + writer->writeEndElement(); +} + +void MSBuildProjectWriterPrivate::visitStart(const MSBuildImportGroup *importGroup) +{ + writer->writeStartElement(QStringLiteral("ImportGroup")); + if (!importGroup->condition().isEmpty()) + writer->writeAttribute(QStringLiteral("Condition"), importGroup->condition()); + if (!importGroup->label().isEmpty()) + writer->writeAttribute(QStringLiteral("Label"), importGroup->label()); +} + +void MSBuildProjectWriterPrivate::visitEnd(const MSBuildImportGroup *) +{ + writer->writeEndElement(); +} + +void MSBuildProjectWriterPrivate::visitStart(const MSBuildItem *item) +{ + writer->writeStartElement(item->name()); + if (!item->include().isEmpty()) + writer->writeAttribute(QStringLiteral("Include"), item->include()); +} + +void MSBuildProjectWriterPrivate::visitEnd(const MSBuildItem *) +{ + writer->writeEndElement(); +} + +void MSBuildProjectWriterPrivate::visitStart(const MSBuildItemDefinitionGroup *itemDefinitionGroup) +{ + writer->writeStartElement(QStringLiteral("ItemDefinitionGroup")); + if (!itemDefinitionGroup->condition().isEmpty()) + writer->writeAttribute(QStringLiteral("Condition"), itemDefinitionGroup->condition()); +} + +void MSBuildProjectWriterPrivate::visitEnd(const MSBuildItemDefinitionGroup *) +{ + writer->writeEndElement(); +} + +void MSBuildProjectWriterPrivate::visitStart(const MSBuildItemGroup *itemGroup) +{ + writer->writeStartElement(QStringLiteral("ItemGroup")); + if (!itemGroup->condition().isEmpty()) + writer->writeAttribute(QStringLiteral("Condition"), itemGroup->condition()); + if (!itemGroup->label().isEmpty()) + writer->writeAttribute(QStringLiteral("Label"), itemGroup->label()); +} + +void MSBuildProjectWriterPrivate::visitEnd(const MSBuildItemGroup *) +{ + writer->writeEndElement(); +} + +void MSBuildProjectWriterPrivate::visitStart(const MSBuildItemMetadata *itemMetadata) +{ + QString stringValue; + if (itemMetadata->value().type() == QVariant::Bool) { + stringValue = itemMetadata->value().toBool() + ? QStringLiteral("True") + : QStringLiteral("False"); + } else { + stringValue = itemMetadata->value().toString(); + } + writer->writeTextElement(itemMetadata->name(), stringValue); +} + +void MSBuildProjectWriterPrivate::visitEnd(const MSBuildItemMetadata *) +{ +} + +void MSBuildProjectWriterPrivate::visitStart(const MSBuildProject *project) +{ + writer->writeStartElement(QStringLiteral("Project")); + if (!project->defaultTargets().isEmpty()) + writer->writeAttribute(QStringLiteral("DefaultTargets"), project->defaultTargets()); + if (!project->toolsVersion().isEmpty()) + writer->writeAttribute(QStringLiteral("ToolsVersion"), project->toolsVersion()); + writer->writeAttribute(QStringLiteral("xmlns"), kMSBuildSchemaURI); +} + +void MSBuildProjectWriterPrivate::visitEnd(const MSBuildProject *) +{ + writer->writeEndElement(); +} + +void MSBuildProjectWriterPrivate::visitStart(const MSBuildProperty *property) +{ + QString stringValue; + if (property->value().type() == QVariant::Bool) + stringValue = property->value().toBool() ? QStringLiteral("True") : QStringLiteral("False"); + else + stringValue = property->value().toString(); + writer->writeTextElement(property->name(), stringValue); +} + +void MSBuildProjectWriterPrivate::visitEnd(const MSBuildProperty *) +{ +} + +void MSBuildProjectWriterPrivate::visitStart(const MSBuildPropertyGroup *propertyGroup) +{ + writer->writeStartElement(QStringLiteral("PropertyGroup")); + if (!propertyGroup->condition().isEmpty()) + writer->writeAttribute(QStringLiteral("Condition"), propertyGroup->condition()); + if (!propertyGroup->label().isEmpty()) + writer->writeAttribute(QStringLiteral("Label"), propertyGroup->label()); +} + +void MSBuildProjectWriterPrivate::visitEnd(const MSBuildPropertyGroup *) +{ + writer->writeEndElement(); +} + +} // namespace qbs diff --git a/src/lib/msbuild/io/msbuildprojectwriter.h b/src/lib/msbuild/io/msbuildprojectwriter.h new file mode 100644 index 00000000..cfe6beb7 --- /dev/null +++ b/src/lib/msbuild/io/msbuildprojectwriter.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDPROJECTWRITER_H +#define MSBUILDPROJECTWRITER_H + +#include + +#include + +namespace qbs { + +class MSBuildProject; +class MSBuildProjectWriterPrivate; + +class MSBuildProjectWriter +{ + Q_DISABLE_COPY(MSBuildProjectWriter) +public: + explicit MSBuildProjectWriter(std::ostream *device); + ~MSBuildProjectWriter(); + + bool write(const MSBuildProject *project); + +private: + MSBuildProjectWriterPrivate *d; +}; + +} // namespace qbs + +#endif // MSBUILDPROJECTWRITER_H diff --git a/src/lib/msbuild/io/visualstudiosolutionwriter.cpp b/src/lib/msbuild/io/visualstudiosolutionwriter.cpp new file mode 100644 index 00000000..625489ac --- /dev/null +++ b/src/lib/msbuild/io/visualstudiosolutionwriter.cpp @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "visualstudiosolutionwriter.h" + +#include "../solution/visualstudiosolutionfileproject.h" +#include "../solution/visualstudiosolutionfolderproject.h" +#include "../solution/visualstudiosolutionglobalsection.h" +#include "../solution/visualstudiosolution.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace qbs { + +using namespace Internal; + +class VisualStudioSolutionWriterPrivate +{ +public: + std::ostream *device = nullptr; + std::string baseDir; +}; + +VisualStudioSolutionWriter::VisualStudioSolutionWriter(std::ostream *device) + : d(new VisualStudioSolutionWriterPrivate) +{ + d->device = device; +} + +VisualStudioSolutionWriter::~VisualStudioSolutionWriter() = default; + +std::string VisualStudioSolutionWriter::projectBaseDirectory() const +{ + return d->baseDir; +} + +void VisualStudioSolutionWriter::setProjectBaseDirectory(const std::string &dir) +{ + d->baseDir = dir; +} + +bool VisualStudioSolutionWriter::write(const VisualStudioSolution *solution) +{ + 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"; + + const auto fileProjects = solution->fileProjects(); + for (const auto &project : fileProjects) { + 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.empty()) { + const QDir solutionDir(QString::fromStdString(d->baseDir)); + projectFilePath = Internal::PathUtils::toNativeSeparators( + solutionDir.relativeFilePath(QString::fromStdString(projectFilePath)), + Internal::HostOsInfo::HostOsWindows).toStdString(); + } + + 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.empty()) { + out << u8"\tProjectSection(ProjectDependencies) = postProject\n"; + + for (const auto &dependency : dependencies) + out << u8"\t\t" + << dependency->guid().toString().toStdString() + << u8" = " + << dependency->guid().toString().toStdString() + << u8"\n"; + + out << u8"\tEndProjectSection\n"; + } + + out << u8"EndProject\n"; + } + + const auto folderProjects = solution->folderProjects(); + for (const auto &project : folderProjects) { + 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 << u8"Global\n"; + + const auto globalSections = solution->globalSections(); + for (const auto &globalSection : globalSections) { + out << u8"\tGlobalSection(" + << globalSection->name().toStdString() + << u8") = " + << (globalSection->isPost() ? u8"postSolution" : u8"preSolution") + << u8"\n"; + for (const auto &property : globalSection->properties()) + out << u8"\t\t" + << property.first.toStdString() + << u8" = " + << property.second.toStdString() + << u8"\n"; + + out << u8"\tEndGlobalSection\n"; + } + + out << u8"EndGlobal\n"; + + return out.good(); +} + +} // namespace qbs diff --git a/src/lib/msbuild/io/visualstudiosolutionwriter.h b/src/lib/msbuild/io/visualstudiosolutionwriter.h new file mode 100644 index 00000000..62ab45ea --- /dev/null +++ b/src/lib/msbuild/io/visualstudiosolutionwriter.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef VISUALSTUDIOSOLUTIONWRITER_H +#define VISUALSTUDIOSOLUTIONWRITER_H + +#include +#include +#include + +#include + +namespace qbs { + +namespace Internal { class VisualStudioVersionInfo; } + +class VisualStudioSolution; +class VisualStudioSolutionWriterPrivate; + +class VisualStudioSolutionWriter +{ + Q_DISABLE_COPY(VisualStudioSolutionWriter) +public: + explicit VisualStudioSolutionWriter(std::ostream *device); + ~VisualStudioSolutionWriter(); + + std::string projectBaseDirectory() const; + void setProjectBaseDirectory(const std::string &dir); + + bool write(const VisualStudioSolution *solution); + +private: + void addDefaultGlobalSections(); + + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // VISUALSTUDIOSOLUTIONWRITER_H diff --git a/src/lib/msbuild/msbuild.pro b/src/lib/msbuild/msbuild.pro new file mode 100644 index 00000000..43216912 --- /dev/null +++ b/src/lib/msbuild/msbuild.pro @@ -0,0 +1,58 @@ +TARGET = qbsmsbuild +include(../staticlibrary.pri) +include(../corelib/use_corelib.pri) + +HEADERS += \ + io/msbuildprojectwriter.h \ + io/visualstudiosolutionwriter.h \ + msbuild/imsbuildgroup.h \ + msbuild/imsbuildnode.h \ + msbuild/imsbuildnodevisitor.h \ + msbuild/imsbuildproperty.h \ + msbuild/items/msbuildclcompile.h \ + msbuild/items/msbuildclinclude.h \ + msbuild/items/msbuildfileitem.h \ + msbuild/items/msbuildfilter.h \ + msbuild/items/msbuildlink.h \ + msbuild/items/msbuildnone.h \ + msbuild/msbuildimport.h \ + msbuild/msbuildimportgroup.h \ + msbuild/msbuilditem.h \ + msbuild/msbuilditemdefinitiongroup.h \ + msbuild/msbuilditemgroup.h \ + msbuild/msbuilditemmetadata.h \ + msbuild/msbuildproject.h \ + msbuild/msbuildproperty.h \ + msbuild/msbuildpropertygroup.h \ + solution/ivisualstudiosolutionproject.h \ + solution/visualstudiosolution.h \ + solution/visualstudiosolutionfileproject.h \ + solution/visualstudiosolutionfolderproject.h \ + solution/visualstudiosolutionglobalsection.h + +SOURCES += \ + io/msbuildprojectwriter.cpp \ + io/visualstudiosolutionwriter.cpp \ + msbuild/imsbuildgroup.cpp \ + msbuild/imsbuildnode.cpp \ + msbuild/imsbuildproperty.cpp \ + msbuild/items/msbuildclcompile.cpp \ + msbuild/items/msbuildclinclude.cpp \ + msbuild/items/msbuildfileitem.cpp \ + msbuild/items/msbuildfilter.cpp \ + msbuild/items/msbuildlink.cpp \ + msbuild/items/msbuildnone.cpp \ + msbuild/msbuildimport.cpp \ + msbuild/msbuildimportgroup.cpp \ + msbuild/msbuilditem.cpp \ + msbuild/msbuilditemdefinitiongroup.cpp \ + msbuild/msbuilditemgroup.cpp \ + msbuild/msbuilditemmetadata.cpp \ + msbuild/msbuildproject.cpp \ + msbuild/msbuildproperty.cpp \ + msbuild/msbuildpropertygroup.cpp \ + solution/ivisualstudiosolutionproject.cpp \ + solution/visualstudiosolution.cpp \ + solution/visualstudiosolutionfileproject.cpp \ + solution/visualstudiosolutionfolderproject.cpp \ + solution/visualstudiosolutionglobalsection.cpp diff --git a/src/lib/msbuild/msbuild.qbs b/src/lib/msbuild/msbuild.qbs new file mode 100644 index 00000000..5e69c28e --- /dev/null +++ b/src/lib/msbuild/msbuild.qbs @@ -0,0 +1,88 @@ +import qbs + +QbsStaticLibrary { + Depends { name: "cpp" } + Depends { name: "qbscore" } + name: "qbsmsbuild" + cpp.visibility: "default" + cpp.includePaths: base.concat([ + ".", + "../corelib", // for some header-only functions in tools + ]) + + Group { + name: "Solution Object Model" + prefix: "solution/" + files: [ + "ivisualstudiosolutionproject.cpp", + "ivisualstudiosolutionproject.h", + "visualstudiosolutionfileproject.cpp", + "visualstudiosolutionfileproject.h", + "visualstudiosolutionfolderproject.cpp", + "visualstudiosolutionfolderproject.h", + "visualstudiosolution.cpp", + "visualstudiosolution.h", + "visualstudiosolutionglobalsection.cpp", + "visualstudiosolutionglobalsection.h", + ] + } + Group { + name: "MSBuild Object Model" + prefix: "msbuild/" + files: [ + "imsbuildgroup.cpp", + "imsbuildgroup.h", + "imsbuildnode.cpp", + "imsbuildnode.h", + "imsbuildnodevisitor.h", + "imsbuildproperty.cpp", + "imsbuildproperty.h", + "msbuildimport.cpp", + "msbuildimport.h", + "msbuildimportgroup.cpp", + "msbuildimportgroup.h", + "msbuilditem.cpp", + "msbuilditem.h", + "msbuilditemdefinitiongroup.cpp", + "msbuilditemdefinitiongroup.h", + "msbuilditemgroup.cpp", + "msbuilditemgroup.h", + "msbuilditemmetadata.cpp", + "msbuilditemmetadata.h", + "msbuildproject.cpp", + "msbuildproject.h", + "msbuildproperty.cpp", + "msbuildproperty.h", + "msbuildpropertygroup.cpp", + "msbuildpropertygroup.h", + ] + } + Group { + name: "MSBuild Object Model Items" + prefix: "msbuild/items/" + files: [ + "msbuildclcompile.cpp", + "msbuildclcompile.h", + "msbuildclinclude.cpp", + "msbuildclinclude.h", + "msbuildfileitem.cpp", + "msbuildfileitem.h", + "msbuildfilter.cpp", + "msbuildfilter.h", + "msbuildlink.cpp", + "msbuildlink.h", + "msbuildnone.cpp", + "msbuildnone.h", + ] + } + Group { + name: "Visual Studio Object Model I/O" + prefix: "io/" + files: [ + "msbuildprojectwriter.cpp", + "msbuildprojectwriter.h", + "visualstudiosolutionwriter.cpp", + "visualstudiosolutionwriter.h", + ] + } +} diff --git a/src/lib/msbuild/msbuild/imsbuildgroup.cpp b/src/lib/msbuild/msbuild/imsbuildgroup.cpp new file mode 100644 index 00000000..81078ecb --- /dev/null +++ b/src/lib/msbuild/msbuild/imsbuildgroup.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "imsbuildgroup.h" +#include "msbuildproject.h" + +namespace qbs { + +class IMSBuildGroupPrivate +{ +public: + QString condition; +}; + +IMSBuildGroup::IMSBuildGroup(MSBuildProject *parent) + : QObject(parent) + , d(new IMSBuildGroupPrivate) +{ +} + +IMSBuildGroup::~IMSBuildGroup() = default; + +QString IMSBuildGroup::condition() const +{ + return d->condition; +} + +void IMSBuildGroup::setCondition(const QString &condition) +{ + d->condition = condition; +} + +IMSBuildItemGroup::IMSBuildItemGroup(MSBuildProject *parent) + : IMSBuildGroup(parent) +{ +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/imsbuildgroup.h b/src/lib/msbuild/msbuild/imsbuildgroup.h new file mode 100644 index 00000000..3d911564 --- /dev/null +++ b/src/lib/msbuild/msbuild/imsbuildgroup.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef IMSBUILDGROUP_H +#define IMSBUILDGROUP_H + +#include + +#include + +namespace qbs { + +class MSBuildProject; +class IMSBuildGroupPrivate; + +class IMSBuildGroup : public QObject +{ + Q_OBJECT +public: + explicit IMSBuildGroup(MSBuildProject *parent = nullptr); + ~IMSBuildGroup() override; + + QString condition() const; + void setCondition(const QString &condition); + +private: + std::unique_ptr d; +}; + +class IMSBuildItemGroup : public IMSBuildGroup +{ + Q_OBJECT +public: + explicit IMSBuildItemGroup(MSBuildProject *parent = nullptr); +}; + +} // namespace qbs + +#endif // IMSBUILDGROUP_H diff --git a/src/lib/msbuild/msbuild/imsbuildnode.cpp b/src/lib/msbuild/msbuild/imsbuildnode.cpp new file mode 100644 index 00000000..f563f7b0 --- /dev/null +++ b/src/lib/msbuild/msbuild/imsbuildnode.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "imsbuildnode.h" + +namespace qbs { + +IMSBuildNode::~IMSBuildNode() = default; + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/imsbuildnode.h b/src/lib/msbuild/msbuild/imsbuildnode.h new file mode 100644 index 00000000..67fb1008 --- /dev/null +++ b/src/lib/msbuild/msbuild/imsbuildnode.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef IMSBUILDNODE_H +#define IMSBUILDNODE_H + +namespace qbs { + +class IMSBuildNodeVisitor; + +class IMSBuildNode +{ +public: + virtual ~IMSBuildNode(); + virtual void accept(IMSBuildNodeVisitor *visitor) const = 0; +}; + +} // namespace qbs + +#endif // IMSBUILDNODE_H diff --git a/src/lib/msbuild/msbuild/imsbuildnodevisitor.h b/src/lib/msbuild/msbuild/imsbuildnodevisitor.h new file mode 100644 index 00000000..84239d64 --- /dev/null +++ b/src/lib/msbuild/msbuild/imsbuildnodevisitor.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef IMSBUILDNODEVISITOR_H +#define IMSBUILDNODEVISITOR_H + +namespace qbs { + +class MSBuildImport; +class MSBuildImportGroup; +class MSBuildItem; +class MSBuildItemDefinitionGroup; +class MSBuildItemGroup; +class MSBuildItemMetadata; +class MSBuildProject; +class MSBuildProperty; +class MSBuildPropertyGroup; + +class IMSBuildNodeVisitor +{ +public: + virtual ~IMSBuildNodeVisitor() = default; + + virtual void visitStart(const MSBuildImport *import) = 0; + virtual void visitEnd(const MSBuildImport *import) = 0; + + virtual void visitStart(const MSBuildImportGroup *importGroup) = 0; + virtual void visitEnd(const MSBuildImportGroup *importGroup) = 0; + + virtual void visitStart(const MSBuildItem *item) = 0; + virtual void visitEnd(const MSBuildItem *item) = 0; + + virtual void visitStart(const MSBuildItemDefinitionGroup *itemDefinitionGroup) = 0; + virtual void visitEnd(const MSBuildItemDefinitionGroup *itemDefinitionGroup) = 0; + + virtual void visitStart(const MSBuildItemGroup *itemGroup) = 0; + virtual void visitEnd(const MSBuildItemGroup *itemGroup) = 0; + + virtual void visitStart(const MSBuildItemMetadata *itemMetadata) = 0; + virtual void visitEnd(const MSBuildItemMetadata *itemMetadata) = 0; + + virtual void visitStart(const MSBuildProject *project) = 0; + virtual void visitEnd(const MSBuildProject *project) = 0; + + virtual void visitStart(const MSBuildProperty *property) = 0; + virtual void visitEnd(const MSBuildProperty *property) = 0; + + virtual void visitStart(const MSBuildPropertyGroup *propertyGroup) = 0; + virtual void visitEnd(const MSBuildPropertyGroup *propertyGroup) = 0; +}; + +} // namespace qbs + +#endif // IMSBUILDNODEVISITOR_H diff --git a/src/lib/msbuild/msbuild/imsbuildproperty.cpp b/src/lib/msbuild/msbuild/imsbuildproperty.cpp new file mode 100644 index 00000000..be18f1a5 --- /dev/null +++ b/src/lib/msbuild/msbuild/imsbuildproperty.cpp @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "imsbuildproperty.h" + +namespace qbs { + +class IMSBuildPropertyPrivate +{ +public: + QString condition; + QString name; + QVariant value; +}; + +IMSBuildProperty::IMSBuildProperty(QObject *parent) + : QObject(parent) + , d(new IMSBuildPropertyPrivate) +{ +} + +IMSBuildProperty::~IMSBuildProperty() = default; + +QString IMSBuildProperty::condition() const +{ + return d->condition; +} + +void IMSBuildProperty::setCondition(const QString &condition) +{ + d->condition = condition; +} + +QString IMSBuildProperty::name() const +{ + return d->name; +} + +void IMSBuildProperty::setName(const QString &name) +{ + d->name = name; +} + +QVariant IMSBuildProperty::value() const +{ + return d->value; +} + +void IMSBuildProperty::setValue(const QVariant &value) +{ + d->value = value; +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/imsbuildproperty.h b/src/lib/msbuild/msbuild/imsbuildproperty.h new file mode 100644 index 00000000..13f26f07 --- /dev/null +++ b/src/lib/msbuild/msbuild/imsbuildproperty.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef IMSBUILDPROPERTY_H +#define IMSBUILDPROPERTY_H + +#include +#include + +#include + +namespace qbs { + +class IMSBuildPropertyPrivate; + +class IMSBuildProperty : public QObject +{ + Q_OBJECT +protected: + explicit IMSBuildProperty(QObject *parent = nullptr); + +public: + ~IMSBuildProperty() override; + + QString condition() const; + void setCondition(const QString &condition); + + QString name() const; + void setName(const QString &name); + + QVariant value() const; + void setValue(const QVariant &value); + +private: + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // IMSBUILDPROPERTY_H diff --git a/src/lib/msbuild/msbuild/items/msbuildclcompile.cpp b/src/lib/msbuild/msbuild/items/msbuildclcompile.cpp new file mode 100644 index 00000000..d8a58745 --- /dev/null +++ b/src/lib/msbuild/msbuild/items/msbuildclcompile.cpp @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildclcompile.h" + +namespace qbs { + +static const QString MSBuildClCompileItemName = QStringLiteral("ClCompile"); + +MSBuildClCompile::MSBuildClCompile(IMSBuildItemGroup *parent) + : MSBuildFileItem(MSBuildClCompileItemName, parent) +{ +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/items/msbuildclcompile.h b/src/lib/msbuild/msbuild/items/msbuildclcompile.h new file mode 100644 index 00000000..8c326a03 --- /dev/null +++ b/src/lib/msbuild/msbuild/items/msbuildclcompile.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDCLCOMPILE_H +#define MSBUILDCLCOMPILE_H + +#include "msbuildfileitem.h" + +namespace qbs { + +class MSBuildClCompile : public MSBuildFileItem +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildClCompile) +public: + explicit MSBuildClCompile(IMSBuildItemGroup *parent = nullptr); +}; + +} // namespace qbs + +#endif // MSBUILDCLCOMPILE_H diff --git a/src/lib/msbuild/msbuild/items/msbuildclinclude.cpp b/src/lib/msbuild/msbuild/items/msbuildclinclude.cpp new file mode 100644 index 00000000..d9c61bec --- /dev/null +++ b/src/lib/msbuild/msbuild/items/msbuildclinclude.cpp @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildclinclude.h" + +namespace qbs { + +static const QString MSBuildClIncludeItemName = QStringLiteral("ClInclude"); + +MSBuildClInclude::MSBuildClInclude(IMSBuildItemGroup *parent) + : MSBuildFileItem(MSBuildClIncludeItemName, parent) +{ +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/items/msbuildclinclude.h b/src/lib/msbuild/msbuild/items/msbuildclinclude.h new file mode 100644 index 00000000..77d8e0ff --- /dev/null +++ b/src/lib/msbuild/msbuild/items/msbuildclinclude.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDCLINCLUDE_H +#define MSBUILDCLINCLUDE_H + +#include "msbuildfileitem.h" + +namespace qbs { + +class MSBuildClInclude : public MSBuildFileItem +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildClInclude) +public: + explicit MSBuildClInclude(IMSBuildItemGroup *parent = nullptr); +}; + +} // namespace qbs + +#endif // MSBUILDCLINCLUDE_H diff --git a/src/lib/msbuild/msbuild/items/msbuildfileitem.cpp b/src/lib/msbuild/msbuild/items/msbuildfileitem.cpp new file mode 100644 index 00000000..4cbb01d0 --- /dev/null +++ b/src/lib/msbuild/msbuild/items/msbuildfileitem.cpp @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildfileitem.h" + +#include +#include "../msbuilditemmetadata.h" + +namespace qbs { + +class MSBuildFileItemPrivate +{ +public: + std::unique_ptr filter; +}; + +MSBuildFileItem::MSBuildFileItem(const QString &name, IMSBuildItemGroup *parent) + : MSBuildItem(name, parent) + , d(new MSBuildFileItemPrivate) +{ + d->filter = std::make_unique(QStringLiteral("Filter"), QVariant()); +} + +MSBuildFileItem::~MSBuildFileItem() = default; + +QString MSBuildFileItem::filePath() const +{ + return include(); +} + +void MSBuildFileItem::setFilePath(const QString &filePath) +{ + setInclude(filePath); +} + +QString MSBuildFileItem::filterName() const +{ + return d->filter->value().toString(); +} + +void MSBuildFileItem::setFilterName(const QString &filterName) +{ + d->filter->setValue(filterName); + d->filter->setParent(!filterName.isEmpty() ? this : nullptr); +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/items/msbuildfileitem.h b/src/lib/msbuild/msbuild/items/msbuildfileitem.h new file mode 100644 index 00000000..f34ac119 --- /dev/null +++ b/src/lib/msbuild/msbuild/items/msbuildfileitem.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDFILEITEM_H +#define MSBUILDFILEITEM_H + +#include "../msbuilditem.h" + +namespace qbs { + +class MSBuildFileItemPrivate; + +class MSBuildFileItem : public MSBuildItem +{ +public: + ~MSBuildFileItem() override; + + QString filePath() const; + void setFilePath(const QString &filePath); + + QString filterName() const; + void setFilterName(const QString &filterName); + +protected: + explicit MSBuildFileItem(const QString &name, IMSBuildItemGroup *parent = nullptr); + +private: + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // MSBUILDFILEITEM_H diff --git a/src/lib/msbuild/msbuild/items/msbuildfilter.cpp b/src/lib/msbuild/msbuild/items/msbuildfilter.cpp new file mode 100644 index 00000000..0b9c2c86 --- /dev/null +++ b/src/lib/msbuild/msbuild/items/msbuildfilter.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildfilter.h" +#include "../msbuilditemmetadata.h" +#include +#include + +namespace qbs { + +static const QString MSBuildFilterItemName = QStringLiteral("Filter"); + +class MSBuildFilterPrivate +{ +public: + QUuid identifier; + QList extensions; + bool parseFiles = true; + bool sourceControlFiles = true; + MSBuildItemMetadata *identifierMetadata = nullptr; + MSBuildItemMetadata *extensionsMetadata = nullptr; +}; + +MSBuildFilter::MSBuildFilter(IMSBuildItemGroup *parent) + : MSBuildItem(MSBuildFilterItemName, parent) + , d(new MSBuildFilterPrivate) +{ + d->identifierMetadata = new MSBuildItemMetadata(QStringLiteral("UniqueIdentifier"), + QVariant(), this); + d->extensionsMetadata = new MSBuildItemMetadata(QStringLiteral("Extensions"), + QVariant(), this); + setIdentifier(QUuid::createUuid()); +} + +MSBuildFilter::MSBuildFilter(const QString &name, + const QList &extensions, + IMSBuildItemGroup *parent) + : MSBuildFilter(parent) +{ + setInclude(name); + setExtensions(extensions); +} + +MSBuildFilter::~MSBuildFilter() = default; + +QUuid MSBuildFilter::identifier() const +{ + return d->identifier; +} + +void MSBuildFilter::setIdentifier(const QUuid &identifier) +{ + d->identifier = identifier; + d->identifierMetadata->setValue(identifier.toString()); +} + +QList MSBuildFilter::extensions() const +{ + return d->extensions; +} + +void MSBuildFilter::setExtensions(const QList &extensions) +{ + d->extensions = extensions; + d->extensionsMetadata->setValue(QStringList(extensions).join( + Internal::HostOsInfo::pathListSeparator( + Internal::HostOsInfo::HostOsWindows))); +} + +bool MSBuildFilter::parseFiles() const +{ + return d->parseFiles; +} + +void MSBuildFilter::setParseFiles(bool parseFiles) +{ + d->parseFiles = parseFiles; +} + +bool MSBuildFilter::sourceControlFiles() const +{ + return d->sourceControlFiles; +} + +void MSBuildFilter::setSourceControlFiles(bool sourceControlFiles) +{ + d->sourceControlFiles = sourceControlFiles; +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/items/msbuildfilter.h b/src/lib/msbuild/msbuild/items/msbuildfilter.h new file mode 100644 index 00000000..fb977f1c --- /dev/null +++ b/src/lib/msbuild/msbuild/items/msbuildfilter.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDFILTER_H +#define MSBUILDFILTER_H + +#include "../msbuilditem.h" + +namespace qbs { + +class IMSBuildItemGroup; +class MSBuildFilterPrivate; + +class MSBuildFilter : public MSBuildItem +{ + Q_OBJECT +public: + explicit MSBuildFilter(IMSBuildItemGroup *parent = nullptr); + MSBuildFilter(const QString &name, const QList &extensions, + IMSBuildItemGroup *parent = nullptr); + ~MSBuildFilter() override; + + QUuid identifier() const; + void setIdentifier(const QUuid &identifier); + + QList extensions() const; + void setExtensions(const QList &extensions); + + bool parseFiles() const; + void setParseFiles(bool parseFiles); + + bool sourceControlFiles() const; + void setSourceControlFiles(bool sourceControlFiles); + +private: + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // MSBUILDFILTER_H diff --git a/src/lib/msbuild/msbuild/items/msbuildlink.cpp b/src/lib/msbuild/msbuild/items/msbuildlink.cpp new file mode 100644 index 00000000..cae1a63a --- /dev/null +++ b/src/lib/msbuild/msbuild/items/msbuildlink.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildlink.h" + +#include "../imsbuildgroup.h" + +namespace qbs { + +static const QString MSBuildLinkItemName = QStringLiteral("Link"); + +MSBuildLink::MSBuildLink(IMSBuildItemGroup *parent) + : MSBuildItem(MSBuildLinkItemName, parent) +{ +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/items/msbuildlink.h b/src/lib/msbuild/msbuild/items/msbuildlink.h new file mode 100644 index 00000000..3d7a3ab4 --- /dev/null +++ b/src/lib/msbuild/msbuild/items/msbuildlink.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDLINK_H +#define MSBUILDLINK_H + +#include "../msbuilditem.h" + +namespace qbs { + +class IMSBuildItemGroup; + +class MSBuildLink : public MSBuildItem +{ + Q_OBJECT +public: + explicit MSBuildLink(IMSBuildItemGroup *parent = nullptr); +}; + +} // namespace qbs + +#endif // MSBUILDLINK_H diff --git a/src/lib/msbuild/msbuild/items/msbuildnone.cpp b/src/lib/msbuild/msbuild/items/msbuildnone.cpp new file mode 100644 index 00000000..a590c6e9 --- /dev/null +++ b/src/lib/msbuild/msbuild/items/msbuildnone.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildnone.h" + +namespace qbs { + +MSBuildNone::MSBuildNone(IMSBuildItemGroup *parent) + : MSBuildFileItem(QStringLiteral("None"), parent) +{ +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/items/msbuildnone.h b/src/lib/msbuild/msbuild/items/msbuildnone.h new file mode 100644 index 00000000..3779da04 --- /dev/null +++ b/src/lib/msbuild/msbuild/items/msbuildnone.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDNONE_H +#define MSBUILDNONE_H + +#include "msbuildfileitem.h" + +namespace qbs { + +class MSBuildNone : public MSBuildFileItem +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildNone) +public: + explicit MSBuildNone(IMSBuildItemGroup *parent = nullptr); +}; + +} // namespace qbs + +#endif // MSBUILDNONE_H diff --git a/src/lib/msbuild/msbuild/msbuildimport.cpp b/src/lib/msbuild/msbuild/msbuildimport.cpp new file mode 100644 index 00000000..000af449 --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuildimport.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildimport.h" + +#include "imsbuildnodevisitor.h" +#include "msbuildimportgroup.h" +#include "msbuildproject.h" + +namespace qbs { + +class MSBuildImportPrivate +{ +public: + QString project; + QString condition; +}; + +MSBuildImport::MSBuildImport(MSBuildProject *parent) + : QObject(parent) + , d(new MSBuildImportPrivate) +{ +} + +MSBuildImport::MSBuildImport(MSBuildImportGroup *parent) + : QObject(parent) + , d(new MSBuildImportPrivate) +{ +} + +MSBuildImport::~MSBuildImport() = default; + +QString MSBuildImport::project() const +{ + return d->project; +} + +void MSBuildImport::setProject(const QString &project) +{ + d->project = project; +} + +QString MSBuildImport::condition() const +{ + return d->condition; +} + +void MSBuildImport::setCondition(const QString &condition) +{ + d->condition = condition; +} + +void MSBuildImport::accept(IMSBuildNodeVisitor *visitor) const +{ + visitor->visitStart(this); + visitor->visitEnd(this); +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/msbuildimport.h b/src/lib/msbuild/msbuild/msbuildimport.h new file mode 100644 index 00000000..65f9bc05 --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuildimport.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ +#ifndef MSBUILDIMPORT_H +#define MSBUILDIMPORT_H + +#include +#include "imsbuildnode.h" + +#include + +namespace qbs { + +class MSBuildImportGroup; +class MSBuildImportPrivate; +class MSBuildProject; + +/*! + * \brief The MSBuildImport class represents an MSBuild Import element. + * + * https://msdn.microsoft.com/en-us/library/92x05xfs.aspx + */ +class MSBuildImport : public QObject, public IMSBuildNode +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildImport) +public: + explicit MSBuildImport(MSBuildProject *parent); + explicit MSBuildImport(MSBuildImportGroup *parent); + ~MSBuildImport() override; + + QString project() const; + void setProject(const QString &project); + + QString condition() const; + void setCondition(const QString &condition); + + void accept(IMSBuildNodeVisitor *visitor) const override; + +private: + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // MSBUILDIMPORT_H diff --git a/src/lib/msbuild/msbuild/msbuildimportgroup.cpp b/src/lib/msbuild/msbuild/msbuildimportgroup.cpp new file mode 100644 index 00000000..d84d8178 --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuildimportgroup.cpp @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildimportgroup.h" + +#include "imsbuildnodevisitor.h" +#include "msbuildimport.h" + +namespace qbs { + +class MSBuildImportGroupPrivate +{ +public: + QString label; +}; + +MSBuildImportGroup::MSBuildImportGroup(MSBuildProject *parent) + : IMSBuildGroup(parent) + , d(new MSBuildImportGroupPrivate) +{ +} + +MSBuildImportGroup::~MSBuildImportGroup() = default; + +QString MSBuildImportGroup::label() const +{ + return d->label; +} + +void MSBuildImportGroup::setLabel(const QString &label) +{ + d->label = label; +} + +void MSBuildImportGroup::accept(IMSBuildNodeVisitor *visitor) const +{ + visitor->visitStart(this); + + for (const auto &child : children()) { + if (const auto import = qobject_cast(child)) + import->accept(visitor); + } + + visitor->visitEnd(this); +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/msbuildimportgroup.h b/src/lib/msbuild/msbuild/msbuildimportgroup.h new file mode 100644 index 00000000..a21cb5a5 --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuildimportgroup.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDIMPORTGROUP_H +#define MSBUILDIMPORTGROUP_H + +#include "imsbuildgroup.h" +#include "imsbuildnode.h" + +namespace qbs { + +class MSBuildProject; +class MSBuildImportGroupPrivate; + +/*! + * \brief The MSBuildImportGroup class represents an MSBuild ImportGroup element. + * + * https://msdn.microsoft.com/en-us/library/ff606262.aspx + */ +class MSBuildImportGroup : public IMSBuildGroup, public IMSBuildNode +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildImportGroup) +public: + explicit MSBuildImportGroup(MSBuildProject *parent = nullptr); + ~MSBuildImportGroup() override; + + QString label() const; + void setLabel(const QString &label); + + void accept(IMSBuildNodeVisitor *visitor) const override; + +private: + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // MSBUILDIMPORTGROUP_H diff --git a/src/lib/msbuild/msbuild/msbuilditem.cpp b/src/lib/msbuild/msbuild/msbuilditem.cpp new file mode 100644 index 00000000..ed181a45 --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuilditem.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuilditem.h" + +#include "imsbuildnodevisitor.h" +#include "msbuilditemdefinitiongroup.h" +#include "msbuilditemgroup.h" +#include "msbuilditemmetadata.h" + +namespace qbs { + +class MSBuildItemPrivate +{ +public: + QString name = QStringLiteral("Item"); + QString include; +}; + +MSBuildItem::MSBuildItem(const QString &name, IMSBuildItemGroup *parent) + : QObject(parent) + , d(new MSBuildItemPrivate) +{ + setName(name); +} + +MSBuildItem::~MSBuildItem() = default; + +QString MSBuildItem::name() const +{ + return d->name; +} + +void MSBuildItem::setName(const QString &name) +{ + d->name = name; +} + +QString MSBuildItem::include() const +{ + return d->include; +} + +void MSBuildItem::setInclude(const QString &include) +{ + d->include = include; +} + +void MSBuildItem::appendProperty(const QString &name, const QVariant &value) +{ + new MSBuildItemMetadata(name, value, this); +} + +void MSBuildItem::accept(IMSBuildNodeVisitor *visitor) const +{ + visitor->visitStart(this); + + for (const auto &child : children()) { + if (const auto itemMetadata = qobject_cast(child)) + itemMetadata->accept(visitor); + } + + visitor->visitEnd(this); +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/msbuilditem.h b/src/lib/msbuild/msbuild/msbuilditem.h new file mode 100644 index 00000000..d01a357a --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuilditem.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDITEM_H +#define MSBUILDITEM_H + +#include +#include "imsbuildnode.h" + +#include + +namespace qbs { + +class IMSBuildItemGroup; +class MSBuildItemDefinitionGroup; +class MSBuildItemGroup; +class MSBuildItemPrivate; + +/*! + * \brief The MSBuildItem class represents an MSBuild Item element. + * + * https://msdn.microsoft.com/en-us/library/ms164283.aspx + */ +class MSBuildItem : public QObject, public IMSBuildNode +{ + Q_OBJECT +public: + explicit MSBuildItem(const QString &name, IMSBuildItemGroup *parent = nullptr); + ~MSBuildItem() override; + + QString name() const; + void setName(const QString &name); + + QString include() const; + void setInclude(const QString &include); + + void appendProperty(const QString &name, const QVariant &value); + + void accept(IMSBuildNodeVisitor *visitor) const override; + +private: + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // MSBUILDITEM_H diff --git a/src/lib/msbuild/msbuild/msbuilditemdefinitiongroup.cpp b/src/lib/msbuild/msbuild/msbuilditemdefinitiongroup.cpp new file mode 100644 index 00000000..1479577c --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuilditemdefinitiongroup.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuilditemdefinitiongroup.h" + +#include "imsbuildnodevisitor.h" +#include "msbuilditem.h" + +namespace qbs { + +MSBuildItemDefinitionGroup::MSBuildItemDefinitionGroup(MSBuildProject *parent) + : IMSBuildItemGroup(parent) +{ +} + +MSBuildItemDefinitionGroup::~MSBuildItemDefinitionGroup() = default; + +void MSBuildItemDefinitionGroup::accept(IMSBuildNodeVisitor *visitor) const +{ + visitor->visitStart(this); + + for (const auto &child : children()) { + if (const auto item = qobject_cast(child)) + item->accept(visitor); + } + + visitor->visitEnd(this); +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/msbuilditemdefinitiongroup.h b/src/lib/msbuild/msbuild/msbuilditemdefinitiongroup.h new file mode 100644 index 00000000..3c324699 --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuilditemdefinitiongroup.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDITEMDEFINITIONGROUP_H +#define MSBUILDITEMDEFINITIONGROUP_H + +#include "imsbuildgroup.h" +#include "imsbuildnode.h" + +namespace qbs { + +class MSBuildProject; +class MSBuildItemDefinitionGroupPrivate; + +/*! + * \brief The MSBuildItemDefinitionGroup class represents an MSBuild ItemDefinitionGroup element. + * + * https://msdn.microsoft.com/en-us/library/bb629392.aspx + */ +class MSBuildItemDefinitionGroup : public IMSBuildItemGroup, public IMSBuildNode +{ + Q_OBJECT +public: + explicit MSBuildItemDefinitionGroup(MSBuildProject *parent = nullptr); + ~MSBuildItemDefinitionGroup() override; + + void accept(IMSBuildNodeVisitor *visitor) const override; +}; + +} // namespace qbs + +#endif // MSBUILDITEMDEFINITIONGROUP_H diff --git a/src/lib/msbuild/msbuild/msbuilditemgroup.cpp b/src/lib/msbuild/msbuild/msbuilditemgroup.cpp new file mode 100644 index 00000000..48f91970 --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuilditemgroup.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuilditemgroup.h" + +#include "imsbuildnodevisitor.h" +#include "msbuilditem.h" + +#include + +namespace qbs { + +class MSBuildItemGroupPrivate +{ +public: + QString label; +}; + +MSBuildItemGroup::MSBuildItemGroup(MSBuildProject *parent) + : IMSBuildItemGroup(parent) + , d(new MSBuildItemGroupPrivate) +{ +} + +MSBuildItemGroup::~MSBuildItemGroup() = default; + +QString MSBuildItemGroup::label() const +{ + return d->label; +} + +void MSBuildItemGroup::setLabel(const QString &label) +{ + d->label = label; +} + +void MSBuildItemGroup::accept(IMSBuildNodeVisitor *visitor) const +{ + visitor->visitStart(this); + + for (const auto &child : children()) { + if (const MSBuildItem *item = qobject_cast(child)) + item->accept(visitor); + } + + visitor->visitEnd(this); +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/msbuilditemgroup.h b/src/lib/msbuild/msbuild/msbuilditemgroup.h new file mode 100644 index 00000000..bbbdb0e6 --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuilditemgroup.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDITEMGROUP_H +#define MSBUILDITEMGROUP_H + +#include "imsbuildgroup.h" +#include "imsbuildnode.h" + +#include + +namespace qbs { + +class MSBuildProject; +class MSBuildItemGroupPrivate; + +/*! + * \brief The MSBuildItemGroup class represents an MSBuild ItemGroup element. + * + * https://msdn.microsoft.com/en-us/library/646dk05y.aspx + */ +class MSBuildItemGroup : public IMSBuildItemGroup, public IMSBuildNode +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildItemGroup) +public: + explicit MSBuildItemGroup(MSBuildProject *parent = nullptr); + ~MSBuildItemGroup() override; + + QString label() const; + void setLabel(const QString &label); + + void accept(IMSBuildNodeVisitor *visitor) const override; + +private: + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // MSBUILDITEMGROUP_H diff --git a/src/lib/msbuild/msbuild/msbuilditemmetadata.cpp b/src/lib/msbuild/msbuild/msbuilditemmetadata.cpp new file mode 100644 index 00000000..daaa4c6b --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuilditemmetadata.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuilditemmetadata.h" + +#include "imsbuildnodevisitor.h" +#include "msbuilditem.h" + +namespace qbs { + +MSBuildItemMetadata::MSBuildItemMetadata(MSBuildItem *parent) + : IMSBuildProperty(parent) +{ +} + +MSBuildItemMetadata::MSBuildItemMetadata(const QString &name, const QVariant &value, + MSBuildItem *parent) + : MSBuildItemMetadata(parent) +{ + setName(name); + setValue(value); +} + +void MSBuildItemMetadata::accept(IMSBuildNodeVisitor *visitor) const +{ + visitor->visitStart(this); + visitor->visitEnd(this); +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/msbuilditemmetadata.h b/src/lib/msbuild/msbuild/msbuilditemmetadata.h new file mode 100644 index 00000000..09da2649 --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuilditemmetadata.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDITEMMETADATA_H +#define MSBUILDITEMMETADATA_H + +#include "imsbuildproperty.h" +#include "imsbuildnode.h" + +namespace qbs { + +class MSBuildItem; + +/*! + * \brief The MSBuildItemMetadata class represents an MSBuild ItemMetadata element. + * + * https://msdn.microsoft.com/en-us/library/ms164284.aspx + */ +class MSBuildItemMetadata : public IMSBuildProperty, public IMSBuildNode +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildItemMetadata) +public: + explicit MSBuildItemMetadata(MSBuildItem *parent = nullptr); + MSBuildItemMetadata(const QString &name, const QVariant &value = QVariant(), + MSBuildItem *parent = nullptr); + + void accept(IMSBuildNodeVisitor *visitor) const override; +}; + +} // namespace qbs + +#endif // MSBUILDITEMMETADATA_H diff --git a/src/lib/msbuild/msbuild/msbuildproject.cpp b/src/lib/msbuild/msbuild/msbuildproject.cpp new file mode 100644 index 00000000..11b5b81e --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuildproject.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildproject.h" + +#include "imsbuildnodevisitor.h" +#include "msbuildimport.h" +#include "msbuildimportgroup.h" +#include "msbuilditemdefinitiongroup.h" +#include "msbuilditemgroup.h" +#include "msbuildpropertygroup.h" + +namespace qbs { + +class MSBuildProjectPrivate +{ +public: + QString defaultTargets; + QString toolsVersion; +}; + +MSBuildProject::MSBuildProject(QObject *parent) + : QObject(parent) + , d(new MSBuildProjectPrivate) +{ +} + +MSBuildProject::~MSBuildProject() = default; + +QString MSBuildProject::defaultTargets() const +{ + return d->defaultTargets; +} + +void MSBuildProject::setDefaultTargets(const QString &defaultTargets) +{ + d->defaultTargets = defaultTargets; +} + +QString MSBuildProject::toolsVersion() const +{ + return d->toolsVersion; +} + +void MSBuildProject::setToolsVersion(const QString &toolsVersion) +{ + d->toolsVersion = toolsVersion; +} + +void MSBuildProject::accept(IMSBuildNodeVisitor *visitor) const +{ + visitor->visitStart(this); + + for (const auto &child : children()) { + if (const auto node = qobject_cast(child)) + node->accept(visitor); + else if (const auto node = qobject_cast(child)) + node->accept(visitor); + else if (const auto node = qobject_cast(child)) + node->accept(visitor); + else if (const auto node = qobject_cast(child)) + node->accept(visitor); + else if (const auto node = qobject_cast(child)) + node->accept(visitor); + } + + visitor->visitEnd(this); +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/msbuildproject.h b/src/lib/msbuild/msbuild/msbuildproject.h new file mode 100644 index 00000000..5281f615 --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuildproject.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDPROJECT_H +#define MSBUILDPROJECT_H + +#include +#include "imsbuildnode.h" + +#include + +namespace qbs { + +class MSBuildProjectPrivate; + +/*! + * \brief The MSBuildProject class represents an MSBuild Project element. + * + * https://msdn.microsoft.com/en-us/library/bcxfsh87.aspx + */ +class MSBuildProject : public QObject, public IMSBuildNode +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildProject) +public: + explicit MSBuildProject(QObject *parent = nullptr); + ~MSBuildProject() override; + + QString defaultTargets() const; + void setDefaultTargets(const QString &defaultTargets); + + QString toolsVersion() const; + void setToolsVersion(const QString &toolsVersion); + + void accept(IMSBuildNodeVisitor *visitor) const override; + +private: + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // MSBUILDPROJECT_H diff --git a/src/lib/msbuild/msbuild/msbuildproperty.cpp b/src/lib/msbuild/msbuild/msbuildproperty.cpp new file mode 100644 index 00000000..410e2a69 --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuildproperty.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildproperty.h" + +#include "imsbuildnodevisitor.h" +#include "msbuildpropertygroup.h" + +namespace qbs { + +MSBuildProperty::MSBuildProperty(MSBuildPropertyGroup *parent) + : IMSBuildProperty(parent) +{ +} + +MSBuildProperty::MSBuildProperty(const QString &name, const QVariant &value, + MSBuildPropertyGroup *parent) + : MSBuildProperty(parent) +{ + setName(name); + setValue(value); +} + +void MSBuildProperty::accept(IMSBuildNodeVisitor *visitor) const +{ + visitor->visitStart(this); + visitor->visitEnd(this); +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/msbuildproperty.h b/src/lib/msbuild/msbuild/msbuildproperty.h new file mode 100644 index 00000000..de2c5239 --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuildproperty.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDPROPERTY_H +#define MSBUILDPROPERTY_H + +#include "imsbuildproperty.h" +#include "imsbuildnode.h" + +namespace qbs { + +class MSBuildPropertyGroup; + +/*! + * \brief The MSBuildProperty class represents an MSBuild Property element. + * + * https://msdn.microsoft.com/en-us/library/ms164288.aspx + */ +class MSBuildProperty : public IMSBuildProperty, public IMSBuildNode +{ + Q_OBJECT +public: + explicit MSBuildProperty(MSBuildPropertyGroup *parent = nullptr); + MSBuildProperty(const QString &name, const QVariant &value = QVariant(), + MSBuildPropertyGroup *parent = nullptr); + + void accept(IMSBuildNodeVisitor *visitor) const override; +}; + +} // namespace qbs + +#endif // MSBUILDPROPERTY_H diff --git a/src/lib/msbuild/msbuild/msbuildpropertygroup.cpp b/src/lib/msbuild/msbuild/msbuildpropertygroup.cpp new file mode 100644 index 00000000..5acb73da --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuildpropertygroup.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildpropertygroup.h" + +#include "imsbuildnodevisitor.h" +#include "msbuildproperty.h" + +namespace qbs { + +class MSBuildPropertyGroupPrivate +{ +public: + QString condition; + QString label; +}; + +MSBuildPropertyGroup::MSBuildPropertyGroup(MSBuildProject *parent) + : IMSBuildGroup(parent) + , d(new MSBuildPropertyGroupPrivate) +{ +} + +MSBuildPropertyGroup::~MSBuildPropertyGroup() = default; + +QString MSBuildPropertyGroup::label() const +{ + return d->label; +} + +void MSBuildPropertyGroup::setLabel(const QString &label) +{ + d->label = label; +} + +void MSBuildPropertyGroup::appendProperty(const QString &name, const QVariant &value) +{ + new MSBuildProperty(name, value, this); +} + +void MSBuildPropertyGroup::accept(IMSBuildNodeVisitor *visitor) const +{ + visitor->visitStart(this); + + for (const auto &child : children()) { + if (const MSBuildProperty *property = qobject_cast(child)) + property->accept(visitor); + } + + visitor->visitEnd(this); +} + +} // namespace qbs diff --git a/src/lib/msbuild/msbuild/msbuildpropertygroup.h b/src/lib/msbuild/msbuild/msbuildpropertygroup.h new file mode 100644 index 00000000..60bdb008 --- /dev/null +++ b/src/lib/msbuild/msbuild/msbuildpropertygroup.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDPROPERTYGROUP_H +#define MSBUILDPROPERTYGROUP_H + +#include "imsbuildgroup.h" +#include "imsbuildnode.h" + +namespace qbs { + +class MSBuildProject; +class MSBuildPropertyGroupPrivate; + +/*! + * \brief The MSBuildPropertyGroup class represents an MSBuild PropertyGroup element. + * + * https://msdn.microsoft.com/en-us/library/t4w159bs.aspx + */ +class MSBuildPropertyGroup : public IMSBuildGroup, public IMSBuildNode +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildPropertyGroup) +public: + explicit MSBuildPropertyGroup(MSBuildProject *parent = nullptr); + ~MSBuildPropertyGroup() override; + + QString label() const; + void setLabel(const QString &label); + + void appendProperty(const QString &name, const QVariant &value); + + void accept(IMSBuildNodeVisitor *visitor) const override; + +private: + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // MSBUILDPROPERTYGROUP_H diff --git a/src/lib/msbuild/solution/ivisualstudiosolutionproject.cpp b/src/lib/msbuild/solution/ivisualstudiosolutionproject.cpp new file mode 100644 index 00000000..4623b5d4 --- /dev/null +++ b/src/lib/msbuild/solution/ivisualstudiosolutionproject.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "ivisualstudiosolutionproject.h" + +namespace qbs { + +class IVisualStudioSolutionProjectPrivate +{ +public: + QUuid guid = QUuid::createUuid(); + QString name; + QString filePath; +}; + +IVisualStudioSolutionProject::IVisualStudioSolutionProject(QObject *parent) + : QObject(parent) + , d(new IVisualStudioSolutionProjectPrivate) +{ +} + +IVisualStudioSolutionProject::~IVisualStudioSolutionProject() = default; + +QUuid IVisualStudioSolutionProject::guid() const +{ + return d->guid; +} + +void IVisualStudioSolutionProject::setGuid(const QUuid &guid) +{ + d->guid = guid; +} + +QString IVisualStudioSolutionProject::name() const +{ + return d->name; +} + +void IVisualStudioSolutionProject::setName(const QString &name) +{ + d->name = name; +} + +} // namespace qbs diff --git a/src/lib/msbuild/solution/ivisualstudiosolutionproject.h b/src/lib/msbuild/solution/ivisualstudiosolutionproject.h new file mode 100644 index 00000000..6f8574ae --- /dev/null +++ b/src/lib/msbuild/solution/ivisualstudiosolutionproject.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef IVISUALSTUDIOSOLUTIONPROJECT_H +#define IVISUALSTUDIOSOLUTIONPROJECT_H + +#include +#include +#include + +#include + +namespace qbs { + +class IVisualStudioSolutionProjectPrivate; + +class IVisualStudioSolutionProject : public QObject +{ + Q_OBJECT +protected: + explicit IVisualStudioSolutionProject(QObject *parent = nullptr); + +public: + ~IVisualStudioSolutionProject() override; + + virtual QUuid projectTypeGuid() const = 0; + + QUuid guid() const; + void setGuid(const QUuid &guid); + + virtual QString name() const; + void setName(const QString &name); + +private: + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // IVISUALSTUDIOSOLUTIONPROJECT_H diff --git a/src/lib/msbuild/solution/visualstudiosolution.cpp b/src/lib/msbuild/solution/visualstudiosolution.cpp new file mode 100644 index 00000000..89b26664 --- /dev/null +++ b/src/lib/msbuild/solution/visualstudiosolution.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "visualstudiosolution.h" + +#include "visualstudiosolutionfileproject.h" +#include "visualstudiosolutionfolderproject.h" + +#include + +#include + +namespace qbs { + +class VisualStudioSolutionPrivate +{ +public: + VisualStudioSolutionPrivate(const Internal::VisualStudioVersionInfo &versionInfo) + : versionInfo(versionInfo) { } + const Internal::VisualStudioVersionInfo versionInfo; + QList projects; + QMap> dependencies; + QList globalSections; +}; + +VisualStudioSolution::VisualStudioSolution(const Internal::VisualStudioVersionInfo &versionInfo, + QObject *parent) + : QObject(parent) + , d(new VisualStudioSolutionPrivate(versionInfo)) +{ +} + +VisualStudioSolution::~VisualStudioSolution() = default; + +Internal::VisualStudioVersionInfo VisualStudioSolution::versionInfo() const +{ + return d->versionInfo; +} + +QList VisualStudioSolution::projects() const +{ + return d->projects; +} + +QList VisualStudioSolution::fileProjects() const +{ + QList list; + for (const auto &project : qAsConst(d->projects)) + if (auto fileProject = qobject_cast(project)) + list.push_back(fileProject); + return list; +} + +QList VisualStudioSolution::folderProjects() const +{ + QList list; + for (const auto &project : qAsConst(d->projects)) + if (auto folderProject = qobject_cast(project)) + list.push_back(folderProject); + return list; +} + +void VisualStudioSolution::appendProject(IVisualStudioSolutionProject *project) +{ + d->projects.push_back(project); +} + +QList VisualStudioSolution::dependencies( + VisualStudioSolutionFileProject *project) const +{ + return d->dependencies.value(project); +} + +void VisualStudioSolution::addDependency(VisualStudioSolutionFileProject *project, + VisualStudioSolutionFileProject *dependency) +{ + d->dependencies[project].push_back(dependency); +} + +QList VisualStudioSolution::globalSections() const +{ + return d->globalSections; +} + +void VisualStudioSolution::appendGlobalSection(VisualStudioSolutionGlobalSection *globalSection) +{ + d->globalSections.push_back(globalSection); +} + +} // namespace qbs diff --git a/src/lib/msbuild/solution/visualstudiosolution.h b/src/lib/msbuild/solution/visualstudiosolution.h new file mode 100644 index 00000000..ba304c00 --- /dev/null +++ b/src/lib/msbuild/solution/visualstudiosolution.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef VISUALSTUDIOSOLUTION_H +#define VISUALSTUDIOSOLUTION_H + +#include + +#include + +namespace qbs { + +namespace Internal { class VisualStudioVersionInfo; } + +class MSBuildProject; + +class IVisualStudioSolutionProject; +class VisualStudioSolutionFileProject; +class VisualStudioSolutionFolderProject; +class VisualStudioSolutionGlobalSection; + +class VisualStudioSolutionPrivate; + +class VisualStudioSolution : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(VisualStudioSolution) +public: + explicit VisualStudioSolution(const Internal::VisualStudioVersionInfo &versionInfo, + QObject *parent = nullptr); + ~VisualStudioSolution() override; + + Internal::VisualStudioVersionInfo versionInfo() const; + + QList projects() const; + QList fileProjects() const; + QList folderProjects() const; + void appendProject(IVisualStudioSolutionProject *project); + void removeProject(const IVisualStudioSolutionProject *project); + void clearProjects(); + + QList dependencies( + VisualStudioSolutionFileProject *project) const; + void addDependency(VisualStudioSolutionFileProject *project, + VisualStudioSolutionFileProject *dependency); + + QList globalSections() const; + void appendGlobalSection(VisualStudioSolutionGlobalSection *globalSection); + +private: + void addDefaultGlobalSections(); + + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // VISUALSTUDIOSOLUTION_H diff --git a/src/lib/msbuild/solution/visualstudiosolutionfileproject.cpp b/src/lib/msbuild/solution/visualstudiosolutionfileproject.cpp new file mode 100644 index 00000000..ab5db088 --- /dev/null +++ b/src/lib/msbuild/solution/visualstudiosolutionfileproject.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "visualstudiosolutionfileproject.h" + +#include + +namespace qbs { + +class VisualStudioSolutionFileProjectPrivate +{ +public: + QString filePath; +}; + +VisualStudioSolutionFileProject::VisualStudioSolutionFileProject(const QString &filePath, + QObject *parent) + : IVisualStudioSolutionProject(parent) + , d(new VisualStudioSolutionFileProjectPrivate) +{ + setFilePath(filePath); +} + +VisualStudioSolutionFileProject::~VisualStudioSolutionFileProject() = default; + +QString VisualStudioSolutionFileProject::name() const +{ + const auto projectName = IVisualStudioSolutionProject::name(); + if (projectName.isEmpty()) + return QFileInfo(filePath()).baseName(); + return projectName; +} + +QUuid VisualStudioSolutionFileProject::projectTypeGuid() const +{ + return QStringLiteral("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}"); // C++ +} + +QString VisualStudioSolutionFileProject::filePath() const +{ + return d->filePath; +} + +void VisualStudioSolutionFileProject::setFilePath(const QString &filePath) +{ + d->filePath = filePath; +} + +} // namespace qbs diff --git a/src/lib/msbuild/solution/visualstudiosolutionfileproject.h b/src/lib/msbuild/solution/visualstudiosolutionfileproject.h new file mode 100644 index 00000000..2039146b --- /dev/null +++ b/src/lib/msbuild/solution/visualstudiosolutionfileproject.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef VISUALSTUDIOSOLUTIONFILEPROJECT_H +#define VISUALSTUDIOSOLUTIONFILEPROJECT_H + +#include +#include "ivisualstudiosolutionproject.h" + +#include + +namespace qbs { + +class VisualStudioSolutionFileProjectPrivate; + +class VisualStudioSolutionFileProject : public IVisualStudioSolutionProject +{ + Q_OBJECT +public: + explicit VisualStudioSolutionFileProject(const QString &filePath, QObject *parent = nullptr); + ~VisualStudioSolutionFileProject() override; + + QString name() const override; + + QString filePath() const; + void setFilePath(const QString &filePath); + + QUuid projectTypeGuid() const override; + +private: + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // VISUALSTUDIOSOLUTIONFILEPROJECT_H diff --git a/src/lib/msbuild/solution/visualstudiosolutionfolderproject.cpp b/src/lib/msbuild/solution/visualstudiosolutionfolderproject.cpp new file mode 100644 index 00000000..d59d1e1e --- /dev/null +++ b/src/lib/msbuild/solution/visualstudiosolutionfolderproject.cpp @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "visualstudiosolutionfolderproject.h" + +#include + +namespace qbs { + +VisualStudioSolutionFolderProject::VisualStudioSolutionFolderProject(QObject *parent) + : IVisualStudioSolutionProject(parent) +{ +} + +QUuid VisualStudioSolutionFolderProject::projectTypeGuid() const +{ + return QStringLiteral("{2150E333-8FDC-42A3-9474-1A3956D46DE8}"); +} + +} // namespace qbs diff --git a/src/lib/msbuild/solution/visualstudiosolutionfolderproject.h b/src/lib/msbuild/solution/visualstudiosolutionfolderproject.h new file mode 100644 index 00000000..a7fd180c --- /dev/null +++ b/src/lib/msbuild/solution/visualstudiosolutionfolderproject.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef VISUALSTUDIOSOLUTIONFOLDERPROJECT_H +#define VISUALSTUDIOSOLUTIONFOLDERPROJECT_H + +#include +#include "ivisualstudiosolutionproject.h" + +namespace qbs { + +class VisualStudioSolutionFolderProject : public IVisualStudioSolutionProject +{ + Q_OBJECT +public: + explicit VisualStudioSolutionFolderProject(QObject *parent); + + QUuid projectTypeGuid() const override; +}; + +} // namespace qbs + +#endif // VISUALSTUDIOSOLUTIONFOLDERPROJECT_H diff --git a/src/lib/msbuild/solution/visualstudiosolutionglobalsection.cpp b/src/lib/msbuild/solution/visualstudiosolutionglobalsection.cpp new file mode 100644 index 00000000..5cbb0cd6 --- /dev/null +++ b/src/lib/msbuild/solution/visualstudiosolutionglobalsection.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "visualstudiosolutionglobalsection.h" + +#include +#include + +namespace qbs { + +class VisualStudioSolutionGlobalSectionPrivate +{ +public: + QString name; + std::vector> properties; + bool post = false; +}; + +VisualStudioSolutionGlobalSection::VisualStudioSolutionGlobalSection(const QString &name, + QObject *parent) + : QObject(parent) + , d(new VisualStudioSolutionGlobalSectionPrivate) +{ + setName(name); +} + +VisualStudioSolutionGlobalSection::~VisualStudioSolutionGlobalSection() = default; + +QString VisualStudioSolutionGlobalSection::name() const +{ + return d->name; +} + +void VisualStudioSolutionGlobalSection::setName(const QString &name) +{ + d->name = name; +} + +bool VisualStudioSolutionGlobalSection::isPost() const +{ + return d->post; +} + +void VisualStudioSolutionGlobalSection::setPost(bool post) +{ + d->post = post; +} + +std::vector > VisualStudioSolutionGlobalSection::properties() const +{ + return d->properties; +} + +void VisualStudioSolutionGlobalSection::appendProperty(const QString &key, const QString &value) +{ + d->properties.emplace_back(key, value); +} + +} // namespace qbs diff --git a/src/lib/msbuild/solution/visualstudiosolutionglobalsection.h b/src/lib/msbuild/solution/visualstudiosolutionglobalsection.h new file mode 100644 index 00000000..6d383e7d --- /dev/null +++ b/src/lib/msbuild/solution/visualstudiosolutionglobalsection.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef VISUALSTUDIOSOLUTIONGLOBALSECTION_H +#define VISUALSTUDIOSOLUTIONGLOBALSECTION_H + +#include + +#include + +namespace qbs { + +class VisualStudioSolutionGlobalSectionPrivate; + +class VisualStudioSolutionGlobalSection : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(VisualStudioSolutionGlobalSection) +public: + explicit VisualStudioSolutionGlobalSection(const QString &name, QObject *parent = nullptr); + ~VisualStudioSolutionGlobalSection() override; + + QString name() const; + void setName(const QString &name); + + bool isPost() const; + void setPost(bool post); + + std::vector > properties() const; + void appendProperty(const QString &key, const QString &value); + +private: + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // VISUALSTUDIOSOLUTIONGLOBALSECTION_H diff --git a/src/lib/msbuild/use_installed_msbuild.pri b/src/lib/msbuild/use_installed_msbuild.pri new file mode 100644 index 00000000..af89b255 --- /dev/null +++ b/src/lib/msbuild/use_installed_msbuild.pri @@ -0,0 +1,20 @@ +include(use_installed_corelib.pri) + +LIBNAME=qbsmsbuild + +unix:LIBS += -l$${LIBNAME} + +win32 { + CONFIG(debug, debug|release) { + QBSMSBUILDLIB = $${LIBNAME}d$${QBSCORELIBSUFFIX} + } + CONFIG(release, debug|release) { + QBSMSBUILDLIB = $${LIBNAME}$${QBSCORELIBSUFFIX} + } + msvc { + QBSMSBUILDLIB = $${QBSMSBUILDLIB}.lib + } else { + QBSMSBUILDLIB = lib$${QBSMSBUILDLIB} + } + LIBS += $${QBSMSBUILDLIB} +} diff --git a/src/lib/msbuild/use_msbuild.pri b/src/lib/msbuild/use_msbuild.pri new file mode 100644 index 00000000..bb69968d --- /dev/null +++ b/src/lib/msbuild/use_msbuild.pri @@ -0,0 +1,38 @@ +include(../../library_dirname.pri) + +isEmpty(QBSLIBDIR) { + QBSLIBDIR = $${OUT_PWD}/../../../$${QBS_LIBRARY_DIRNAME} +} + +LIBNAME=qbsmsbuild + +unix { + LIBS += -L$${QBSLIBDIR} -l$${LIBNAME} +} + +win32 { + CONFIG(debug, debug|release) { + QBSMSBUILDLIB = $${LIBNAME}d + } + CONFIG(release, debug|release) { + QBSMSBUILDLIB = $${LIBNAME} + } + msvc { + LIBS += /LIBPATH:$$QBSLIBDIR + QBSMSBUILDLIB = $${QBSMSBUILDLIB}.lib + LIBS += Shell32.lib + } else { + LIBS += -L$${QBSLIBDIR} + QBSMSBUILDLIB = lib$${QBSMSBUILDLIB} + } + LIBS += $${QBSMSBUILDLIB} +} + +INCLUDEPATH += \ + $$PWD + +CONFIG += depend_includepath + +CONFIG(static, static|shared) { + DEFINES += QBS_STATIC_LIB +} diff --git a/src/lib/scriptengine/include/QtScript/qtscriptglobal.h b/src/lib/scriptengine/include/QtScript/qtscriptglobal.h new file mode 100644 index 00000000..8b1e09bb --- /dev/null +++ b/src/lib/scriptengine/include/QtScript/qtscriptglobal.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_QTSCRIPTGLOBAL_H +#define QBS_QTSCRIPTGLOBAL_H + +#define Q_SCRIPT_EXPORT +#define Q_SCRIPTTOOLS_EXPORT + +#endif // include guard diff --git a/src/lib/scriptengine/scriptengine.pro b/src/lib/scriptengine/scriptengine.pro new file mode 100644 index 00000000..c5ad5f07 --- /dev/null +++ b/src/lib/scriptengine/scriptengine.pro @@ -0,0 +1,92 @@ +TARGET = qbsscriptengine +include(../library.pri) +INSTALLS = + +QT = core-private + +DEFINES += QT_BUILD_SCRIPT_LIB + +DEFINES += JSC=QTJSC jscyyparse=qtjscyyparse jscyylex=qtjscyylex jscyyerror=qtjscyyerror WTF=QTWTF +DEFINES += QT_NO_USING_NAMESPACE + +CONFIG += building-libs +CONFIG += staticlib + +GENERATED_SOURCES_DIR = generated + +CONFIG += QTDIR_build +include(../../shared/qtscript/src/3rdparty/javascriptcore/WebKit.pri) + +# Disable a few warnings on Windows. +# These are in addition to the ones disabled in WebKit.pri +msvc: QMAKE_CXXFLAGS += -wd4396 -wd4099 +else: QMAKE_CXXFLAGS += -Wno-deprecated + +# We cannot afford -O0 for QtScript even in debug builds. +QMAKE_CXXFLAGS_DEBUG += -O2 + +darwin { + DEFINES += ENABLE_JSC_MULTIPLE_THREADS=0 + contains(QT_CONFIG, coreservices) { + LIBS_PRIVATE += -framework CoreServices + } else { + LIBS_PRIVATE += -framework CoreFoundation + } +} +win32 { + LIBS += -lwinmm +} + +# Suppress 'LEAK' messages (see QTBUG-18201) +DEFINES += LOG_DISABLED=1 + +JAVASCRIPTCORE_JIT = no +include(../../shared/qtscript/src/3rdparty/javascriptcore/JavaScriptCore/JavaScriptCore.pri) + +# This line copied from WebCore.pro +DEFINES += WTF_USE_JAVASCRIPTCORE_BINDINGS=1 WTF_CHANGES=1 + +CONFIG(release, debug|release): DEFINES += NDEBUG + +# Avoid JSC C API functions being exported. +DEFINES += JS_NO_EXPORT + +!build_pass { + qtPrepareTool(QMAKE_SYNCQT, syncqt, , system) + QMAKE_SYNCQT += \ + -minimal -version $$[QT_VERSION] \ + -outdir $$system_quote($$system_path($$OUT_PWD)) \ + $$system_quote($$system_path($$clean_path($$PWD/../../shared/qtscript))) + !system($$QMAKE_SYNCQT): error("Failed to execute syncqt for the bundled QtScript module.") +} + +INCLUDEPATH += \ + $$PWD/include \ + $$OUT_PWD/include \ + $$OUT_PWD/include/QtScript/$$[QT_VERSION]/QtScript \ + $$PWD/../../shared/qtscript/src/script \ + $$PWD/../../shared/qtscript/src/script/api + +include(../../shared/qtscript/src/script/api/api.pri) +include(../../shared/qtscript/src/script/parser/parser.pri) + +BRIDGESRCDIR = ../../shared/qtscript/src/script/bridge +SOURCES += \ + $$BRIDGESRCDIR/qscriptactivationobject.cpp \ + $$BRIDGESRCDIR/qscriptclassobject.cpp \ + $$BRIDGESRCDIR/qscriptfunction.cpp \ + $$BRIDGESRCDIR/qscriptglobalobject.cpp \ + $$BRIDGESRCDIR/qscriptobject.cpp \ + $$BRIDGESRCDIR/qscriptqobject.cpp \ + $$BRIDGESRCDIR/qscriptstaticscopeobject.cpp \ + $$BRIDGESRCDIR/qscriptvariant.cpp + +HEADERS += \ + $$BRIDGESRCDIR/qscriptactivationobject_p.h \ + $$BRIDGESRCDIR/qscriptclassobject_p.h \ + $$BRIDGESRCDIR/qscriptfunction_p.h \ + $$BRIDGESRCDIR/qscriptglobalobject_p.h \ + $$BRIDGESRCDIR/qscriptobject_p.h \ + $$BRIDGESRCDIR/qscriptqobject_p.h \ + $$BRIDGESRCDIR/qscriptstaticscopeobject_p.h \ + $$BRIDGESRCDIR/qscriptvariant_p.h diff --git a/src/lib/scriptengine/scriptengine.qbs b/src/lib/scriptengine/scriptengine.qbs new file mode 100644 index 00000000..88263845 --- /dev/null +++ b/src/lib/scriptengine/scriptengine.qbs @@ -0,0 +1,439 @@ +import qbs +import qbs.File +import qbs.FileInfo +import qbs.Process + +Project { + QbsLibrary { + condition: qbsbuildconfig.useBundledQtScript || !Qt.script.present + Depends { + name: "Qt.script" + condition: !qbsbuildconfig.useBundledQtScript + required: false + } + Depends { name: "QtScriptFwdHeaders" } + Depends { name: "cpp" } + Depends { name: "Qt"; submodules: ["core-private"] } + type: ["staticlibrary"] + name: "qbsscriptengine" + + generatePkgConfigFile: false + generateQbsModule: false + + property bool useSystemMalloc: !qbs.targetOS.contains("macos") + && !qbs.targetOS.contains("unix") + property string qtscriptPath: "../../shared/qtscript/src/" + + cpp.includePaths: { + var result = base.concat( + ".", + "include" + ); + + var jscBaseDir = qtscriptPath + "3rdparty/javascriptcore"; + result.push(jscBaseDir); + jscBaseDir += "/JavaScriptCore"; + result.push(jscBaseDir); + + var jscSubDirs = [ + "assembler", + "bytecode", + "bytecompiler", + "debugger", + "interpreter", + "jit", + "parser", + "pcre", + "profiler", + "runtime", + "wrec", + "wtf", + "wtf/unicode", + "yarr", + "API", + "ForwardingHeaders", + "generated"]; + result = result.concat(jscSubDirs.map(function(s) { return jscBaseDir + '/' + s; })); + + result.push(qtscriptPath + "script"); + result.push(qtscriptPath + "script/api"); + result.push(qtscriptPath + "script/parser"); + result = result.concat(QtScriptFwdHeaders.publicIncludePaths, + QtScriptFwdHeaders.privateIncludePaths); + return result; + } + cpp.defines: { + var result = base.concat([ + "QT_BUILD_SCRIPT_LIB", "QT_NO_USING_NAMESPACE", + "JSC=QTJSC", "jscyyparse=qtjscyyparse", "jscyylex=qtjscyylex", + "jscyyerror=qtjscyyerror", + "WTF=QTWTF", + "LOG_DISABLED=1", + "WTF_USE_JAVASCRIPTCORE_BINDINGS=1", "WTF_CHANGES=1", + "JS_NO_EXPORT"]); + if (qbs.buildVariant != "debug") + result.push("NDEBUG"); + if (qbs.targetOS.contains("macos")) + result.push("ENABLE_JSC_MULTIPLE_THREADS=0"); + + // JavaScriptCore + result.push("BUILDING_QT__", "BUILDING_JavaScriptCore", "BUILDING_WTF", + "ENABLE_JIT=0", "ENABLE_YARR_JIT=0", "ENABLE_YARR=0"); + if (qbs.targetOS.contains("windows")) { + // Prevent definition of min, max macros in windows.h + result.push("NOMINMAX"); + // Enables rand_s + result.push("_CRT_RAND_S"); + } + + // WebKit + result.push("WTF_USE_ACCELERATED_COMPOSITING"); + if (useSystemMalloc) + result.push("USE_SYSTEM_MALLOC"); + + result = result.filter(function(value) { + return value !== "QT_RESTRICTED_CAST_FROM_ASCII" + && value !== "QT_NO_CAST_FROM_BYTEARRAY" + && value !== "QT_NO_CAST_FROM_ASCII"; + }) + return result; + } + cpp.cxxFlags: { + var result = base; + if (qbs.toolchain.contains("gcc")) { + result.push("-fno-strict-aliasing", + "-Wall", "-Wreturn-type", "-Wcast-align", "-Wchar-subscripts", + "-Wformat-security", "-Wreturn-type", "-Wno-unused-parameter", + "-Wno-sign-compare", "-Wno-switch", "-Wno-switch-enum", "-Wundef", + "-Wmissing-noreturn", "-Winit-self", "-Wno-deprecated", + "-Wno-suggest-attribute=noreturn", "-Wno-nonnull-compare"); + } else if (qbs.toolchain.contains("msvc")) { + result.push("-wd4291", "-wd4344", "-wd4396", "-wd4503", "-wd4800", "-wd4819", + "-wd4996"); + } + if (qbs.targetOS.contains("unix")) + result.push("-pthread"); + + return result; + } + cpp.warningLevel: "none" + cpp.optimization: "fast" // We cannot afford -O0 for QtScript even in debug builds. + Properties { + condition: qbs.targetOS.contains("unix") + cpp.dynamicLibraries: base.concat(["pthread"]) + } + + Group { + name: "pcre" + prefix: qtscriptPath + "3rdparty/javascriptcore/JavaScriptCore/pcre/" + files: [ + "pcre_compile.cpp", + "pcre_exec.cpp", + "pcre_tables.cpp", + "pcre_ucp_searchfuncs.cpp", + "pcre_xclass.cpp", + ] + } + + Group { + name: "system malloc replacement" + prefix: qtscriptPath + "3rdparty/javascriptcore/JavaScriptCore/" + condition: !useSystemMalloc + files: [ + "wtf/TCSystemAlloc.cpp", + ] + } + + Group { + name: "JavaScriptCore" + prefix: qtscriptPath + "3rdparty/javascriptcore/JavaScriptCore/" + files: [ + "API/JSBase.cpp", + "API/JSCallbackConstructor.cpp", + "API/JSCallbackFunction.cpp", + "API/JSCallbackObject.cpp", + "API/JSClassRef.cpp", + "API/JSContextRef.cpp", + "API/JSObjectRef.cpp", + "API/JSStringRef.cpp", + "API/JSValueRef.cpp", + "API/OpaqueJSString.cpp", + "assembler/ARMAssembler.cpp", + "assembler/MacroAssemblerARM.cpp", + "bytecode/CodeBlock.cpp", + "bytecode/JumpTable.cpp", + "bytecode/Opcode.cpp", + "bytecode/SamplingTool.cpp", + "bytecode/StructureStubInfo.cpp", + "bytecompiler/BytecodeGenerator.cpp", + "bytecompiler/NodesCodegen.cpp", + "debugger/DebuggerActivation.cpp", + "debugger/DebuggerCallFrame.cpp", + "debugger/Debugger.cpp", + "generated/Grammar.cpp", + "interpreter/CallFrame.cpp", + "interpreter/Interpreter.cpp", + "interpreter/RegisterFile.cpp", + "parser/Lexer.cpp", + "parser/Nodes.cpp", + "parser/ParserArena.cpp", + "parser/Parser.cpp", + "profiler/Profile.cpp", + "profiler/ProfileGenerator.cpp", + "profiler/ProfileNode.cpp", + "profiler/Profiler.cpp", + "runtime/ArgList.cpp", + "runtime/Arguments.cpp", + "runtime/ArrayConstructor.cpp", + "runtime/ArrayPrototype.cpp", + "runtime/BooleanConstructor.cpp", + "runtime/BooleanObject.cpp", + "runtime/BooleanPrototype.cpp", + "runtime/CallData.cpp", + "runtime/Collector.cpp", + "runtime/CommonIdentifiers.cpp", + "runtime/Completion.cpp", + "runtime/ConstructData.cpp", + "runtime/DateConstructor.cpp", + "runtime/DateConversion.cpp", + "runtime/DateInstance.cpp", + "runtime/DatePrototype.cpp", + "runtime/ErrorConstructor.cpp", + "runtime/Error.cpp", + "runtime/ErrorInstance.cpp", + "runtime/ErrorPrototype.cpp", + "runtime/ExceptionHelpers.cpp", + "runtime/Executable.cpp", + "runtime/FunctionConstructor.cpp", + "runtime/FunctionPrototype.cpp", + "runtime/GetterSetter.cpp", + "runtime/GlobalEvalFunction.cpp", + "runtime/Identifier.cpp", + "runtime/InitializeThreading.cpp", + "runtime/InternalFunction.cpp", + "runtime/JSActivation.cpp", + "runtime/JSAPIValueWrapper.cpp", + "runtime/JSArray.cpp", + "runtime/JSByteArray.cpp", + "runtime/JSCell.cpp", + "runtime/JSFunction.cpp", + "runtime/JSGlobalData.cpp", + "runtime/JSGlobalObject.cpp", + "runtime/JSGlobalObjectFunctions.cpp", + "runtime/JSImmediate.cpp", + "runtime/JSLock.cpp", + "runtime/JSNotAnObject.cpp", + "runtime/JSNumberCell.cpp", + "runtime/JSObject.cpp", + "runtime/JSONObject.cpp", + "runtime/JSPropertyNameIterator.cpp", + "runtime/JSStaticScopeObject.cpp", + "runtime/JSString.cpp", + "runtime/JSValue.cpp", + "runtime/JSVariableObject.cpp", + "runtime/JSWrapperObject.cpp", + "runtime/LiteralParser.cpp", + "runtime/Lookup.cpp", + "runtime/MarkStackPosix.cpp", + "runtime/MarkStackSymbian.cpp", + "runtime/MarkStackWin.cpp", + "runtime/MarkStack.cpp", + "runtime/MathObject.cpp", + "runtime/NativeErrorConstructor.cpp", + "runtime/NativeErrorPrototype.cpp", + "runtime/NumberConstructor.cpp", + "runtime/NumberObject.cpp", + "runtime/NumberPrototype.cpp", + "runtime/ObjectConstructor.cpp", + "runtime/ObjectPrototype.cpp", + "runtime/Operations.cpp", + "runtime/PropertyDescriptor.cpp", + "runtime/PropertyNameArray.cpp", + "runtime/PropertySlot.cpp", + "runtime/PrototypeFunction.cpp", + "runtime/RegExpConstructor.cpp", + "runtime/RegExp.cpp", + "runtime/RegExpObject.cpp", + "runtime/RegExpPrototype.cpp", + "runtime/ScopeChain.cpp", + "runtime/SmallStrings.cpp", + "runtime/StringConstructor.cpp", + "runtime/StringObject.cpp", + "runtime/StringPrototype.cpp", + "runtime/StructureChain.cpp", + "runtime/Structure.cpp", + "runtime/TimeoutChecker.cpp", + "runtime/UString.cpp", + "runtime/UStringImpl.cpp", + "wtf/Assertions.cpp", + "wtf/ByteArray.cpp", + "wtf/CurrentTime.cpp", + "wtf/DateMath.cpp", + "wtf/dtoa.cpp", + "wtf/FastMalloc.cpp", + "wtf/HashTable.cpp", + "wtf/MainThread.cpp", + "wtf/qt/MainThreadQt.cpp", + "wtf/qt/ThreadingQt.cpp", + "wtf/RandomNumber.cpp", + "wtf/RefCountedLeakCounter.cpp", + "wtf/ThreadingNone.cpp", + "wtf/Threading.cpp", + "wtf/TypeTraits.cpp", + "wtf/unicode/CollatorDefault.cpp", + "wtf/unicode/icu/CollatorICU.cpp", + "wtf/unicode/UTF8.cpp", + ] + } + Group { + name: "api" + prefix: qtscriptPath + "script/api/" + files: [ + "qscriptable.cpp", + "qscriptable.h", + "qscriptable_p.h", + "qscriptclass.cpp", + "qscriptclass.h", + "qscriptclasspropertyiterator.cpp", + "qscriptclasspropertyiterator.h", + "qscriptcontext.cpp", + "qscriptcontext.h", + "qscriptcontextinfo.cpp", + "qscriptcontextinfo.h", + "qscriptcontext_p.h", + "qscriptengineagent.cpp", + "qscriptengineagent.h", + "qscriptengineagent_p.h", + "qscriptengine.cpp", + "qscriptengine.h", + "qscriptengine_p.h", + "qscriptextensioninterface.h", + "qscriptextensionplugin.cpp", + "qscriptextensionplugin.h", + "qscriptprogram.cpp", + "qscriptprogram.h", + "qscriptprogram_p.h", + "qscriptstring.cpp", + "qscriptstring.h", + "qscriptstring_p.h", + "qscriptvalue.cpp", + "qscriptvalue.h", + "qscriptvalueiterator.cpp", + "qscriptvalueiterator.h", + "qscriptvalue_p.h", + "qtscriptglobal.h", + ] + } + Group { + name: "bridge" + prefix: qtscriptPath + "script/bridge/" + files: [ + "qscriptactivationobject.cpp", + "qscriptactivationobject_p.h", + "qscriptclassobject.cpp", + "qscriptclassobject_p.h", + "qscriptfunction.cpp", + "qscriptfunction_p.h", + "qscriptglobalobject.cpp", + "qscriptglobalobject_p.h", + "qscriptobject.cpp", + "qscriptobject_p.h", + "qscriptqobject.cpp", + "qscriptqobject_p.h", + "qscriptstaticscopeobject.cpp", + "qscriptstaticscopeobject_p.h", + "qscriptvariant.cpp", + "qscriptvariant_p.h", + ] + } + Group { + name: "parser" + prefix: qtscriptPath + "script/parser/" + files: [ + "qscriptast.cpp", + "qscriptastfwd_p.h", + "qscriptast_p.h", + "qscriptastvisitor.cpp", + "qscriptastvisitor_p.h", + "qscriptgrammar.cpp", + "qscriptgrammar_p.h", + "qscriptlexer.cpp", + "qscriptlexer_p.h", + "qscriptsyntaxchecker.cpp", + "qscriptsyntaxchecker_p.h", + ] + } + + Export { + Depends { name: "QtScriptFwdHeaders" } + Depends { name: "cpp" } + property stringList includePaths: [product.sourceDirectory + "/include"] + .concat(QtScriptFwdHeaders.publicIncludePaths) + Properties { + condition: qbs.targetOS.contains("unix") + cpp.dynamicLibraries: base.concat(["pthread"]) + } + Properties { + condition: qbs.targetOS.contains("windows") + cpp.dynamicLibraries: base.concat(["winmm"]) + } + } + } + Product { + type: ["hpp"] + name: "QtScriptFwdHeaders" + Depends { name: "Qt.core" } + Group { + files: [ + "../../shared/qtscript/src/script/api/*.h" + ] + fileTags: ["qtscriptheader"] + } + Rule { + multiplex: true + inputs: ["qtscriptheader"] + Artifact { + filePath: "include/QtScript/qscriptengine.h" + fileTags: ["hpp"] + } + prepare: { + var syncQtPath = FileInfo.joinPaths(product.Qt.core.binPath, "syncqt.pl"); + if (!File.exists(syncQtPath)) { + // syncqt.pl is not in Qt's bin path. We might have a developer build. + // As we don't provide QT_HOST_BINS/src in our Qt modules we must + // kindly ask qmake. + var qmake = FileInfo.joinPaths(product.Qt.core.binPath, + "qmake" + product.cpp.executableSuffix); + var p = new Process(); + if (p.exec(qmake, ["-query", "QT_HOST_BINS/src"]) !== 0) + throw new Error("Error while querying qmake."); + syncQtPath = FileInfo.joinPaths(p.readStdOut().replace(/\r?\n/, ''), + "syncqt.pl"); + } + var qtScriptSrcPath = FileInfo.cleanPath( + FileInfo.path(inputs["qtscriptheader"][0].filePath) + "/../../.."); + console.info("qtScriptSrcPath: " + qtScriptSrcPath); + var cmd = new Command("perl", [ + syncQtPath, + "-minimal", + "-version", product.Qt.core.version, + "-outdir", FileInfo.cleanPath( + FileInfo.path(output.filePath) + "/../.."), + qtScriptSrcPath + ]); + cmd.description = "Create forwarding headers for the bundled QtScript module."; + return cmd; + } + } + Export { + Depends { name: "Qt.core" } + property stringList publicIncludePaths: [ + FileInfo.joinPaths(product.buildDirectory, "include") + ] + property stringList privateIncludePaths: [ + FileInfo.joinPaths(product.buildDirectory, "include", + "QtScript", Qt.core.version, "QtScript") + ] + } + } +} diff --git a/src/lib/scriptengine/use_scriptengine.pri b/src/lib/scriptengine/use_scriptengine.pri new file mode 100644 index 00000000..e8f82a94 --- /dev/null +++ b/src/lib/scriptengine/use_scriptengine.pri @@ -0,0 +1,19 @@ +!qbs_do_not_link_bundled_qtscript { + include(../../library_dirname.pri) + isEmpty(QBSLIBDIR) { + QBSLIBDIR = $$shadowed($$PWD/../../../$${QBS_LIBRARY_DIRNAME}) + } + + LIBS += -L$$QBSLIBDIR + macos { + LIBS += -lqbsscriptengine + } + else { + LIBS += -lqbsscriptengine$$qtPlatformTargetSuffix() + } + +} + +INCLUDEPATH += \ + $$PWD/include \ + $$shadowed($$PWD/include) diff --git a/src/lib/staticlibrary.pri b/src/lib/staticlibrary.pri new file mode 100644 index 00000000..57f780b7 --- /dev/null +++ b/src/lib/staticlibrary.pri @@ -0,0 +1,4 @@ +include(library_base.pri) + +CONFIG += staticlib +DEFINES += QBS_STATIC_LIB diff --git a/src/libexec/libexec.pri b/src/libexec/libexec.pri new file mode 100644 index 00000000..a5cd2c59 --- /dev/null +++ b/src/libexec/libexec.pri @@ -0,0 +1,11 @@ +include(../install_prefix.pri) + +win32:LIBEXEC_BASE_DIR=bin +else:LIBEXEC_BASE_DIR=libexec/qbs + +!isEmpty(QBS_LIBEXEC_DESTDIR):DESTDIR=$${QBS_LIBEXEC_DESTDIR} +else:DESTDIR=../../../$$LIBEXEC_BASE_DIR + +!isEmpty(QBS_LIBEXEC_INSTALL_DIR):target.path = $${QBS_LIBEXEC_INSTALL_DIR} +else:target.path = $${QBS_INSTALL_PREFIX}/$$LIBEXEC_BASE_DIR +INSTALLS += target diff --git a/src/libexec/libexec.pro b/src/libexec/libexec.pro new file mode 100644 index 00000000..75b1d084 --- /dev/null +++ b/src/libexec/libexec.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs + +SUBDIRS += qbs_processlauncher diff --git a/src/libexec/libexec.qbs b/src/libexec/libexec.qbs new file mode 100644 index 00000000..a43e2615 --- /dev/null +++ b/src/libexec/libexec.qbs @@ -0,0 +1,7 @@ +import qbs + +Project { + references: [ + "qbs_processlauncher/qbs_processlauncher.qbs", + ] +} diff --git a/src/libexec/qbs_processlauncher/launcherlogging.cpp b/src/libexec/qbs_processlauncher/launcherlogging.cpp new file mode 100644 index 00000000..81a4a4a0 --- /dev/null +++ b/src/libexec/qbs_processlauncher/launcherlogging.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** 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 "launcherlogging.h" + +namespace qbs { +namespace Internal { +Q_LOGGING_CATEGORY(launcherLog, "qbs.launcher", QtWarningMsg) +} +} diff --git a/src/libexec/qbs_processlauncher/launcherlogging.h b/src/libexec/qbs_processlauncher/launcherlogging.h new file mode 100644 index 00000000..356433a9 --- /dev/null +++ b/src/libexec/qbs_processlauncher/launcherlogging.h @@ -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: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_LAUCHERLOGGING_H +#define QBS_LAUCHERLOGGING_H + +#include +#include + +namespace qbs { +namespace Internal { +Q_DECLARE_LOGGING_CATEGORY(launcherLog) +template void logDebug(const T &msg) { qCDebug(launcherLog) << msg; } +template void logWarn(const T &msg) { qCWarning(launcherLog) << msg; } +template void logError(const T &msg) { qCCritical(launcherLog) << msg; } +} +} + +#endif // Include guard diff --git a/src/libexec/qbs_processlauncher/launchersockethandler.cpp b/src/libexec/qbs_processlauncher/launchersockethandler.cpp new file mode 100644 index 00000000..637362b8 --- /dev/null +++ b/src/libexec/qbs_processlauncher/launchersockethandler.cpp @@ -0,0 +1,295 @@ +/**************************************************************************** +** +** 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 "launchersockethandler.h" + +#include "launcherlogging.h" + +#include +#include +#include +#include + +namespace qbs { +namespace Internal { + +class Process : public QProcess +{ + Q_OBJECT +public: + Process(quintptr token, QObject *parent = nullptr) : + QProcess(parent), m_token(token), m_stopTimer(new QTimer(this)) + { + m_stopTimer->setSingleShot(true); + connect(m_stopTimer, &QTimer::timeout, this, &Process::cancel); + } + + void cancel() + { + switch (m_stopState) { + case StopState::Inactive: + m_stopState = StopState::Terminating; + m_stopTimer->start(3000); + terminate(); + break; + case StopState::Terminating: + m_stopState = StopState::Killing; + m_stopTimer->start(3000); + kill(); + break; + case StopState::Killing: + m_stopState = StopState::Inactive; + emit failedToStop(); + break; + } + } + + void stopStopProcedure() + { + m_stopState = StopState::Inactive; + m_stopTimer->stop(); + } + + quintptr token() const { return m_token; } + +signals: + void failedToStop(); + +private: + const quintptr m_token; + QTimer * const m_stopTimer; + enum class StopState { Inactive, Terminating, Killing } m_stopState = StopState::Inactive; +}; + +LauncherSocketHandler::LauncherSocketHandler(QString serverPath, QObject *parent) + : QObject(parent), + m_serverPath(std::move(serverPath)), + m_socket(new QLocalSocket(this)) +{ + m_packetParser.setDevice(m_socket); +} + +LauncherSocketHandler::~LauncherSocketHandler() +{ + m_socket->disconnect(); + if (m_socket->state() != QLocalSocket::UnconnectedState) { + logWarn("socket handler destroyed while connection was active"); + m_socket->close(); + } + for (auto it = m_processes.cbegin(); it != m_processes.cend(); ++it) + it.value()->disconnect(); +} + +void LauncherSocketHandler::start() +{ + connect(m_socket, &QLocalSocket::disconnected, + this, &LauncherSocketHandler::handleSocketClosed); + connect(m_socket, &QLocalSocket::readyRead, this, &LauncherSocketHandler::handleSocketData); + connect(m_socket, +#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) + static_cast(&QLocalSocket::error), +#else + &QLocalSocket::errorOccurred, +#endif + this, &LauncherSocketHandler::handleSocketError); + m_socket->connectToServer(m_serverPath); +} + +void LauncherSocketHandler::handleSocketData() +{ + try { + if (!m_packetParser.parse()) + return; + } catch (const PacketParser::InvalidPacketSizeException &e) { + logWarn(QStringLiteral("Internal protocol error: invalid packet size %1.") + .arg(e.size)); + return; + } + switch (m_packetParser.type()) { + case LauncherPacketType::StartProcess: + handleStartPacket(); + break; + case LauncherPacketType::StopProcess: + handleStopPacket(); + break; + case LauncherPacketType::Shutdown: + handleShutdownPacket(); + return; + default: + logWarn(QStringLiteral("Internal protocol error: invalid packet type %1.") + .arg(static_cast(m_packetParser.type()))); + return; + } + handleSocketData(); +} + +void LauncherSocketHandler::handleSocketError() +{ + if (m_socket->error() != QLocalSocket::PeerClosedError) { + logError(QStringLiteral("socket error: %1").arg(m_socket->errorString())); + m_socket->disconnect(); + qApp->quit(); + } +} + +void LauncherSocketHandler::handleSocketClosed() +{ + for (auto it = m_processes.cbegin(); it != m_processes.cend(); ++it) { + if (it.value()->state() != QProcess::NotRunning) { + logWarn("client closed connection while process still running"); + break; + } + } + m_socket->disconnect(); + qApp->quit(); +} + +void LauncherSocketHandler::handleProcessError() +{ + Process * proc = senderProcess(); + if (proc->error() != QProcess::FailedToStart) + return; + proc->stopStopProcedure(); + ProcessErrorPacket packet(proc->token()); + packet.error = proc->error(); + packet.errorString = proc->errorString(); + sendPacket(packet); +} + +void LauncherSocketHandler::handleProcessFinished() +{ + Process * proc = senderProcess(); + proc->stopStopProcedure(); + ProcessFinishedPacket packet(proc->token()); + packet.error = proc->error(); + packet.errorString = proc->errorString(); + packet.exitCode = proc->exitCode(); + packet.exitStatus = proc->exitStatus(); + packet.stdErr = proc->readAllStandardError(); + packet.stdOut = proc->readAllStandardOutput(); + sendPacket(packet); +} + +void LauncherSocketHandler::handleStopFailure() +{ + // Process did not react to a kill signal. Rare, but not unheard of. + // Forget about the associated Process object and report process exit to the client. + Process * proc = senderProcess(); + proc->disconnect(); + m_processes.remove(proc->token()); + ProcessFinishedPacket packet(proc->token()); + packet.error = QProcess::Crashed; + packet.exitCode = -1; + packet.exitStatus = QProcess::CrashExit; + packet.stdErr = proc->readAllStandardError(); + packet.stdOut = proc->readAllStandardOutput(); + sendPacket(packet); +} + +void LauncherSocketHandler::handleStartPacket() +{ + Process *& process = m_processes[m_packetParser.token()]; + if (!process) + process = setupProcess(m_packetParser.token()); + if (process->state() != QProcess::NotRunning) { + logWarn("got start request while process was running"); + return; + } + const auto packet = LauncherPacket::extractPacket( + m_packetParser.token(), + m_packetParser.packetData()); + process->setEnvironment(packet.env); + process->setWorkingDirectory(packet.workingDir); + process->start(packet.command, packet.arguments); +} + +void LauncherSocketHandler::handleStopPacket() +{ + Process * const process = m_processes.value(m_packetParser.token()); + if (!process) { + logWarn("got stop request for unknown process"); + return; + } + if (process->state() == QProcess::NotRunning) { + // This can happen if the process finishes on its own at about the same time the client + // sends the request. + logDebug("got stop request when process was not running"); + return; + } + process->cancel(); +} + +void LauncherSocketHandler::handleShutdownPacket() +{ + logDebug("got shutdown request, closing down"); + for (auto it = m_processes.cbegin(); it != m_processes.cend(); ++it) { + it.value()->disconnect(); + if (it.value()->state() != QProcess::NotRunning) { + logWarn("got shutdown request while process was running"); + it.value()->terminate(); + } + } + m_socket->disconnect(); + qApp->quit(); +} + +void LauncherSocketHandler::sendPacket(const LauncherPacket &packet) +{ + m_socket->write(packet.serialize()); +} + +Process *LauncherSocketHandler::setupProcess(quintptr token) +{ + const auto p = new Process(token, this); + connect(p, &QProcess::errorOccurred, this, &LauncherSocketHandler::handleProcessError); + connect(p, static_cast(&QProcess::finished), + this, &LauncherSocketHandler::handleProcessFinished); + connect(p, &Process::failedToStop, this, &LauncherSocketHandler::handleStopFailure); + return p; +} + +Process *LauncherSocketHandler::senderProcess() const +{ + return static_cast(sender()); +} + +} // namespace Internal +} // namespace qbs + +#include diff --git a/src/libexec/qbs_processlauncher/launchersockethandler.h b/src/libexec/qbs_processlauncher/launchersockethandler.h new file mode 100644 index 00000000..b2d57187 --- /dev/null +++ b/src/libexec/qbs_processlauncher/launchersockethandler.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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 QBS_LAUNCHERSOCKETHANDLER_H +#define QBS_LAUNCHERSOCKETHANDLER_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QLocalSocket; +QT_END_NAMESPACE + +namespace qbs { +namespace Internal { +class Process; + +class LauncherSocketHandler : public QObject +{ + Q_OBJECT +public: + explicit LauncherSocketHandler(QString socketPath, QObject *parent = nullptr); + ~LauncherSocketHandler() override; + + void start(); + +private: + void handleSocketData(); + void handleSocketError(); + void handleSocketClosed(); + void handleProcessError(); + void handleProcessFinished(); + void handleStopFailure(); + + void handleStartPacket(); + void handleStopPacket(); + void handleShutdownPacket(); + + void sendPacket(const LauncherPacket &packet); + + Process *setupProcess(quintptr token); + Process *senderProcess() const; + + const QString m_serverPath; + QLocalSocket * const m_socket; + PacketParser m_packetParser; + QHash m_processes; +}; + +} // namespace Internal +} // namespace qbs + +#endif // Include guard diff --git a/src/libexec/qbs_processlauncher/processlauncher-main.cpp b/src/libexec/qbs_processlauncher/processlauncher-main.cpp new file mode 100644 index 00000000..61bbfbc3 --- /dev/null +++ b/src/libexec/qbs_processlauncher/processlauncher-main.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 "launcherlogging.h" +#include "launchersockethandler.h" + +#include +#include + +#ifdef Q_OS_WIN +#include + +BOOL WINAPI consoleCtrlHandler(DWORD) +{ + // Ignore Ctrl-C / Ctrl-Break. Qbs will tell us to exit gracefully. + return TRUE; +} +#endif + +int main(int argc, char *argv[]) +{ +#ifdef Q_OS_WIN + SetConsoleCtrlHandler(consoleCtrlHandler, TRUE); +#endif + + QCoreApplication app(argc, argv); + if (app.arguments().size() != 2) { + qbs::Internal::logError("Need exactly one argument (path to socket)"); + return 1; + } + + qbs::Internal::LauncherSocketHandler launcher(app.arguments().constLast()); + QTimer::singleShot(0, &launcher, &qbs::Internal::LauncherSocketHandler::start); + return app.exec(); +} diff --git a/src/libexec/qbs_processlauncher/qbs_processlauncher.pro b/src/libexec/qbs_processlauncher/qbs_processlauncher.pro new file mode 100644 index 00000000..1c98d97a --- /dev/null +++ b/src/libexec/qbs_processlauncher/qbs_processlauncher.pro @@ -0,0 +1,21 @@ +include(../libexec.pri) + +TARGET = qbs_processlauncher +CONFIG += console c++14 +CONFIG -= app_bundle +QT = core network + +TOOLS_DIR = $$PWD/../../lib/corelib/tools + +INCLUDEPATH += $$TOOLS_DIR + +HEADERS += \ + launcherlogging.h \ + launchersockethandler.h \ + $$TOOLS_DIR/launcherpackets.h + +SOURCES += \ + launcherlogging.cpp \ + launchersockethandler.cpp \ + processlauncher-main.cpp \ + $$TOOLS_DIR/launcherpackets.cpp diff --git a/src/libexec/qbs_processlauncher/qbs_processlauncher.qbs b/src/libexec/qbs_processlauncher/qbs_processlauncher.qbs new file mode 100644 index 00000000..7aa7d3fc --- /dev/null +++ b/src/libexec/qbs_processlauncher/qbs_processlauncher.qbs @@ -0,0 +1,39 @@ +import qbs +import qbs.FileInfo + +QbsProduct { + type: "application" + name: "qbs_processlauncher" + consoleApplication: true + + Depends { name: "Qt.network" } + + cpp.includePaths: base.concat(pathToProtocolSources) + + files: [ + "launcherlogging.cpp", + "launcherlogging.h", + "launchersockethandler.cpp", + "launchersockethandler.h", + "processlauncher-main.cpp", + ] + + property string pathToProtocolSources: sourceDirectory + "/../../lib/corelib/tools" + Group { + name: "protocol sources" + prefix: pathToProtocolSources + '/' + files: [ + "launcherpackets.cpp", + "launcherpackets.h", + ] + } + + Group { + fileTagsFilter: product.type + .concat(qbs.buildVariant === "debug" ? ["debuginfo_app"] : []) + qbs.install: true + qbs.installDir: targetInstallDir + qbs.installSourceBase: buildDirectory + } + targetInstallDir: qbsbuildconfig.libexecInstallDir +} diff --git a/src/library_dirname.pri b/src/library_dirname.pri new file mode 100644 index 00000000..5d0712d7 --- /dev/null +++ b/src/library_dirname.pri @@ -0,0 +1 @@ +isEmpty(QBS_LIBRARY_DIRNAME):QBS_LIBRARY_DIRNAME = lib diff --git a/src/packages/archive/archive.qbs b/src/packages/archive/archive.qbs new file mode 100644 index 00000000..9a9b09ba --- /dev/null +++ b/src/packages/archive/archive.qbs @@ -0,0 +1,86 @@ +import qbs +import qbs.FileInfo +import qbs.ModUtils +import qbs.Process +import qbs.TextFile + +QbsProduct { + Depends { name: "qbs_processlauncher" } + Depends { name: "qbscore" } + Depends { name: "bundledqt"; required: false } + Depends { name: "qbs documentation"; condition: project.withDocumentation } + Depends { name: "qbs resources" } + Depends { + name: "qbs man page" + condition: qbs.targetOS.contains("unix") && project.withDocumentation + } + Depends { productTypes: ["qbsapplication", "qbsplugin"] } + + Depends { name: "archiver" } + + property bool includeTopLevelDir: false + + builtByDefault: false + name: "qbs archive" + type: ["archiver.archive"] + targetName: "qbs-" + qbs.targetOS[0] + "-" + qbs.architecture + "-" + qbsversion.version + destinationDirectory: project.buildDirectory + + archiver.type: qbs.targetOS.contains("windows") ? "zip" : "tar" + Properties { + condition: includeTopLevelDir + archiver.workingDirectory: qbs.installRoot + "/.." + } + archiver.workingDirectory: qbs.installRoot + + Group { + name: "Licenses" + prefix: "../../../" + files: [ + "LGPL_EXCEPTION.txt", + "LICENSE.LGPLv3", + "LICENSE.LGPLv21", + "LICENSE.GPL3-EXCEPT", + ] + qbs.install: true + qbs.installDir: "share/doc/qbs" + } + + Rule { + multiplex: true + inputs: ["installable"] + inputsFromDependencies: ["installable"] + + Artifact { + filePath: "list.txt" + fileTags: ["archiver.input-list"] + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.excludedPathPrefixes = product.excludedPathPrefixes; + cmd.inputFilePaths = inputs.installable.map(function(a) { + return ModUtils.artifactInstalledFilePath(a); + }); + cmd.outputFilePath = output.filePath; + cmd.baseDirectory = product.moduleProperty("archiver", "workingDirectory"); + cmd.sourceCode = function() { + inputFilePaths.sort(); + var tf; + try { + tf = new TextFile(outputFilePath, TextFile.WriteOnly); + for (var i = 0; i < inputFilePaths.length; ++i) { + var relativePath = FileInfo.relativePath(baseDirectory, inputFilePaths[i]); + tf.writeLine(relativePath); + } + } finally { + if (tf) + tf.close(); + } + }; + + return [cmd]; + } + } +} diff --git a/src/packages/chocolatey/chocolatey.qbs b/src/packages/chocolatey/chocolatey.qbs new file mode 100644 index 00000000..e878f8e0 --- /dev/null +++ b/src/packages/chocolatey/chocolatey.qbs @@ -0,0 +1,130 @@ +import qbs +import qbs.Environment +import qbs.File +import qbs.FileInfo +import qbs.Probes +import qbs.TextFile +import qbs.Utilities +import qbs.Xml + +Product { + Depends { name: "qbsversion" } + + Probes.BinaryProbe { + id: choco + condition: qbs.targetOS.contains("windows") + names: ["choco"] + platformSearchPaths: { + var chocolateyInstall = Environment.getEnv("ChocolateyInstall"); + if (chocolateyInstall) + return [FileInfo.joinPaths(chocolateyInstall, "bin")]; + else + return [FileInfo.joinPaths(Environment.getEnv("PROGRAMDATA"), + "chocolatey", "bin")]; + } + } + + condition: choco.found + builtByDefault: false + name: "qbs chocolatey" + type: ["chocolatey.nupkg"] + targetName: "qbs." + qbsversion.version + destinationDirectory: project.buildDirectory + + property string chocoFilePath: choco.filePath + + Group { + files: ["qbs.nuspec"] + fileTags: ["chocolatey.nuspec"] + } + + Group { + files: ["chocolateyinstall.ps1"] + fileTags: ["powershell.source"] + } + + Group { + files: ["../../../changelogs/*"] + fileTags: ["changelog"] + } + + Rule { + inputs: ["chocolatey.nuspec", "powershell.source", "changelog"] + multiplex: true + + Artifact { + filePath: FileInfo.joinPaths(product.destinationDirectory, + product.targetName + ".nupkg") + fileTags: ["chocolatey.nupkg"] + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.qbsVersion = product.qbsversion.version; + cmd.powershellFilePath = inputs["powershell.source"][0].filePath; + cmd.nuspecFileName = inputs["chocolatey.nuspec"][0].fileName; + cmd.nuspecFilePath = inputs["chocolatey.nuspec"][0].filePath; + cmd.chocoBuildDirectory = FileInfo.joinPaths(product.buildDirectory, "choco"); + cmd.chocoOutDirectory = FileInfo.path(outputs["chocolatey.nupkg"][0].filePath); + cmd.changelogs = (inputs["changelog"] || []).map(function (a) { + return { + filePath: a.filePath, + version: a.fileName.replace(/^changes-([0-9](\.[0-9]+)*)(\.md)?$/, "$1") + }; + }).sort(function(a, b) { + return Utilities.versionCompare(b.version, a.version); + }); + cmd.sourceCode = function () { + File.makePath(chocoBuildDirectory); + File.makePath(FileInfo.joinPaths(chocoBuildDirectory, "tools")); + + var tf = new TextFile(FileInfo.joinPaths( + chocoBuildDirectory, "tools", "chocolateyinstall.ps1"), + TextFile.WriteOnly); + try { + tf.writeLine("$qbsVersion = '" + qbsVersion + "'"); + tf.writeLine(""); + var tf2 = new TextFile(powershellFilePath, TextFile.ReadOnly); + try { + tf.write(tf2.readAll()); + } finally { + tf2.close(); + } + } finally { + tf.close(); + } + + var doc = new Xml.DomDocument(); + doc.load(nuspecFilePath); + var versionNode = doc.createElement("version"); + versionNode.appendChild(doc.createTextNode(qbsVersion)); + var releaseNotesNode = doc.createElement("releaseNotes"); + + var releaseNotesText = ""; + changelogs.map(function (changelog) { + releaseNotesText += "qbs " + changelog.version + "\n\n"; + var tf = new TextFile(changelog.filePath, TextFile.ReadOnly); + try { + releaseNotesText += tf.readAll() + "\n"; + } finally { + tf.close(); + } + }); + releaseNotesNode.appendChild(doc.createTextNode(releaseNotesText.trim())); + + var metadataNode = doc.documentElement().firstChild("metadata"); + metadataNode.appendChild(versionNode); + metadataNode.appendChild(releaseNotesNode); + doc.save(FileInfo.joinPaths(chocoBuildDirectory, nuspecFileName)); + }; + var cmd2 = new Command(product.chocoFilePath, + ["pack", FileInfo.joinPaths(cmd.chocoBuildDirectory, + cmd.nuspecFileName), + "--limitoutput", + "--outputdirectory", cmd.chocoOutDirectory]); + cmd2.description = "choco pack " + inputs["chocolatey.nuspec"][0].fileName; + return [cmd, cmd2]; + } + } +} diff --git a/src/packages/chocolatey/chocolateyinstall.ps1 b/src/packages/chocolatey/chocolateyinstall.ps1 new file mode 100644 index 00000000..f55d87eb --- /dev/null +++ b/src/packages/chocolatey/chocolateyinstall.ps1 @@ -0,0 +1,23 @@ +$ErrorActionPreference = 'Stop' + +$qbsBaseUrl = "https://download.qt.io/official_releases/qbs/$qbsVersion" +$checksumType = 'md5' +$checksums = @{} +ForEach ($line in (New-Object Net.WebClient).DownloadString("$qbsBaseUrl/${checksumType}sums.txt").Split(` + "`n", [System.StringSplitOptions]::RemoveEmptyEntries)) { + $items = $line.Split(" ", [System.StringSplitOptions]::RemoveEmptyEntries) + $checksums.Add($items[1], $items[0]) +} + +$qbs32 = "qbs-windows-x86-$qbsVersion.zip" +$qbs64 = "qbs-windows-x86_64-$qbsVersion.zip" + +Install-ChocolateyZipPackage ` + -PackageName 'qbs' ` + -Url "$qbsBaseUrl/$qbs32" ` + -UnzipLocation "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" ` + -Url64bit "$qbsBaseUrl/$qbs64" ` + -Checksum $checksums[$qbs32] ` + -ChecksumType $checksumType ` + -Checksum64 $checksums[$qbs64] ` + -ChecksumType64 $checksumType diff --git a/src/packages/chocolatey/icon.png b/src/packages/chocolatey/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fb08b22f5f2e0aee3034f38c53966996ef3dc06d GIT binary patch literal 6915 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 literal 0 HcmV?d00001 diff --git a/src/packages/chocolatey/qbs.nuspec b/src/packages/chocolatey/qbs.nuspec new file mode 100644 index 00000000..a0d650fc --- /dev/null +++ b/src/packages/chocolatey/qbs.nuspec @@ -0,0 +1,23 @@ + + + + + qbs + Qbs + Qt Project + qbs + Build tool that helps simplify the build process for developing projects across multiple platforms. + Qbs is a tool that helps simplify the build process for developing projects across multiple platforms. + https://wiki.qt.io/Qbs + https://code.qt.io/cgit/qbs/qbs.git + https://code.qt.io/cgit/qbs/qbs.git + https://doc.qt.io/qbs/ + http://lists.qt-project.org/mailman/listinfo/qbs + https://bugreports.qt.io/browse/QBS + qbs qt build + © 2017 The Qt Company Ltd. + http://doc.qt.io/qt-5/licensing.html + false + https://rawcdn.githack.com/qbs/qbs/v1.17.0/src/packages/chocolatey/icon.png + + diff --git a/src/packages/packages.qbs b/src/packages/packages.qbs new file mode 100644 index 00000000..12ca8b04 --- /dev/null +++ b/src/packages/packages.qbs @@ -0,0 +1,16 @@ +import qbs + +Project { + references: [ + "archive/archive.qbs", + "chocolatey/chocolatey.qbs", + ] + + // Virtual product for building all possible packagings + Product { + Depends { name: "qbs archive"; required: false } + Depends { name: "qbs chocolatey"; required: false } + name: "dist" + builtByDefault: false + } +} diff --git a/src/plugins/generator/clangcompilationdb/clangcompilationdb.pri b/src/plugins/generator/clangcompilationdb/clangcompilationdb.pri new file mode 100644 index 00000000..7c9a7112 --- /dev/null +++ b/src/plugins/generator/clangcompilationdb/clangcompilationdb.pri @@ -0,0 +1 @@ +qbsPluginTarget = clangcompilationdbgenerator diff --git a/src/plugins/generator/clangcompilationdb/clangcompilationdb.pro b/src/plugins/generator/clangcompilationdb/clangcompilationdb.pro new file mode 100644 index 00000000..1b534259 --- /dev/null +++ b/src/plugins/generator/clangcompilationdb/clangcompilationdb.pro @@ -0,0 +1,11 @@ +include(clangcompilationdb.pri) +include(../../plugins.pri) + +QT = core + +HEADERS += \ + $$PWD/clangcompilationdbgenerator.h + +SOURCES += \ + $$PWD/clangcompilationdbgenerator.cpp \ + $$PWD/clangcompilationdbgeneratorplugin.cpp diff --git a/src/plugins/generator/clangcompilationdb/clangcompilationdb.qbs b/src/plugins/generator/clangcompilationdb/clangcompilationdb.qbs new file mode 100644 index 00000000..741d6dbf --- /dev/null +++ b/src/plugins/generator/clangcompilationdb/clangcompilationdb.qbs @@ -0,0 +1,11 @@ +import qbs +import "../../qbsplugin.qbs" as QbsPlugin + +QbsPlugin { + name: "clangcompilationdbgenerator" + files: [ + "clangcompilationdbgenerator.cpp", + "clangcompilationdbgenerator.h", + "clangcompilationdbgeneratorplugin.cpp" + ] +} diff --git a/src/plugins/generator/clangcompilationdb/clangcompilationdbgenerator.cpp b/src/plugins/generator/clangcompilationdb/clangcompilationdbgenerator.cpp new file mode 100644 index 00000000..3676d079 --- /dev/null +++ b/src/plugins/generator/clangcompilationdb/clangcompilationdbgenerator.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** 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 "clangcompilationdbgenerator.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace qbs { +using namespace Internal; + +const QString ClangCompilationDatabaseGenerator::DefaultDatabaseFileName = + QStringLiteral("compile_commands.json"); + +ClangCompilationDatabaseGenerator::ClangCompilationDatabaseGenerator() = default; + +QString ClangCompilationDatabaseGenerator::generatorName() const +{ + return QStringLiteral("clangdb"); +} + +void ClangCompilationDatabaseGenerator::generate() +{ + const auto projects = project().projects.values(); + for (const Project &theProject : projects) { + QJsonArray database; + const ProjectData projectData = theProject.projectData(); + const QString &buildDir = projectData.buildDirectory(); + + for (const ProductData &productData : projectData.allProducts()) { + for (const GroupData &groupData : productData.groups()) { + const auto sourceArtifacts = groupData.allSourceArtifacts(); + for (const ArtifactData &sourceArtifact : sourceArtifacts) { + if (!hasValidInputFileTag(sourceArtifact.fileTags())) + continue; + + const QString filePath = sourceArtifact.filePath(); + ErrorInfo errorInfo; + const RuleCommandList rules = theProject.ruleCommands(productData, filePath, + QStringLiteral("obj"), + &errorInfo); + + if (errorInfo.hasError()) + throw errorInfo; + + for (const RuleCommand &rule : rules) { + if (rule.type() != RuleCommand::ProcessCommandType) + continue; + database.push_back(createEntry(filePath, buildDir, rule)); + } + } + } + } + + writeProjectDatabase(QDir(buildDir).filePath(DefaultDatabaseFileName), database); + } +} + +// See http://clang.llvm.org/docs/JSONCompilationDatabase.html +QJsonObject ClangCompilationDatabaseGenerator::createEntry(const QString &filePath, + const QString &buildDir, + const RuleCommand &ruleCommand) +{ + QString workDir = ruleCommand.workingDirectory(); + if (workDir.isEmpty()) + workDir = buildDir; + + const QStringList arguments = QStringList() << ruleCommand.executable() + << ruleCommand.arguments(); + + const QJsonObject object = { + { QStringLiteral("directory"), QJsonValue(workDir) }, + { QStringLiteral("arguments"), QJsonArray::fromStringList(arguments) }, + { QStringLiteral("file"), QJsonValue(filePath) } + }; + return object; +} + +void ClangCompilationDatabaseGenerator::writeProjectDatabase(const QString &filePath, + const QJsonArray &entries) +{ + const QJsonDocument database(entries); + QFile databaseFile(filePath); + + if (!databaseFile.open(QFile::WriteOnly)) + throw ErrorInfo(Tr::tr("Cannot open '%1' for writing: %2") + .arg(filePath) + .arg(databaseFile.errorString())); + + if (databaseFile.write(database.toJson()) == -1) + throw ErrorInfo(Tr::tr("Error while writing '%1': %2") + .arg(filePath) + .arg(databaseFile.errorString())); +} + +bool ClangCompilationDatabaseGenerator::hasValidInputFileTag(const QStringList &fileTags) const +{ + static const QStringList validFileTags = { + QStringLiteral("c"), + QStringLiteral("cpp"), + QStringLiteral("objc"), + QStringLiteral("objcpp") + }; + + for (const QString &tag : fileTags) { + if (validFileTags.contains(tag)) + return true; + } + return false; +} + +} // namespace qbs diff --git a/src/plugins/generator/clangcompilationdb/clangcompilationdbgenerator.h b/src/plugins/generator/clangcompilationdb/clangcompilationdbgenerator.h new file mode 100644 index 00000000..5a850507 --- /dev/null +++ b/src/plugins/generator/clangcompilationdb/clangcompilationdbgenerator.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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 QBS_CLANGCOMPILATIONDATABASEGENERATOR_H +#define QBS_CLANGCOMPILATIONDATABASEGENERATOR_H + +#include + +namespace qbs { + +class SourceArtifact; +class ProjectData; + +class ClangCompilationDatabaseGenerator : public ProjectGenerator +{ +public: + ClangCompilationDatabaseGenerator(); + +private: + QString generatorName() const override; + void generate() override; + static const QString DefaultDatabaseFileName; + QJsonObject createEntry(const QString &filePath, const QString &buildDir, + const RuleCommand &ruleCommand); + void writeProjectDatabase(const QString &filePath, const QJsonArray &entries); + bool hasValidInputFileTag(const QStringList &fileTags) const; +}; + +} // namespace qbs + +#endif // QBS_VISUALSTUDIOGENERATOR_H diff --git a/src/plugins/generator/clangcompilationdb/clangcompilationdbgeneratorplugin.cpp b/src/plugins/generator/clangcompilationdb/clangcompilationdbgeneratorplugin.cpp new file mode 100644 index 00000000..c6dec555 --- /dev/null +++ b/src/plugins/generator/clangcompilationdb/clangcompilationdbgeneratorplugin.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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 "clangcompilationdbgenerator.h" + +#include +#include + +static void QbsClangDbGeneratorPluginLoad() +{ + qbs::ProjectGeneratorManager::registerGenerator( + std::make_shared()); +} + +static void QbsClangDbGeneratorPluginUnload() +{ +} + +#ifndef GENERATOR_EXPORT +#if defined(WIN32) || defined(_WIN32) +#define GENERATOR_EXPORT __declspec(dllexport) +#else +#define GENERATOR_EXPORT __attribute__((visibility("default"))) +#endif +#endif + +QBS_REGISTER_STATIC_PLUGIN(extern "C" GENERATOR_EXPORT, clangcompilationdbgenerator, + QbsClangDbGeneratorPluginLoad, QbsClangDbGeneratorPluginUnload) diff --git a/src/plugins/generator/generator.pro b/src/plugins/generator/generator.pro new file mode 100644 index 00000000..4a386132 --- /dev/null +++ b/src/plugins/generator/generator.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs +SUBDIRS += clangcompilationdb +SUBDIRS += makefilegenerator +SUBDIRS += visualstudio +SUBDIRS += iarew +SUBDIRS += keiluv diff --git a/src/plugins/generator/iarew/archs/arm/armarchiversettingsgroup_v8.cpp b/src/plugins/generator/iarew/archs/arm/armarchiversettingsgroup_v8.cpp new file mode 100644 index 00000000..cc075062 --- /dev/null +++ b/src/plugins/generator/iarew/archs/arm/armarchiversettingsgroup_v8.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armarchiversettingsgroup_v8.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace arm { +namespace v8 { + +constexpr int kArchiverArchiveVersion = 0; +constexpr int kArchiverDataVersion = 0; + +namespace { + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + outputFile = QLatin1String("$PROJ_DIR$/") + + gen::utils::targetBinaryPath(baseDirectory, qbsProduct); + } + + QString outputFile; +}; + +} // namespace + +// ArmArchiverSettingsGroup + +ArmArchiverSettingsGroup::ArmArchiverSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("IARCHIVE")); + setArchiveVersion(kArchiverArchiveVersion); + setDataVersion(kArchiverDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + buildOutputPage(buildRootDirectory, qbsProduct); +} + +void ArmArchiverSettingsGroup::buildOutputPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(baseDirectory, qbsProduct); + // Add 'IarchiveOverride' item (Override default). + addOptionsGroup(QByteArrayLiteral("IarchiveOverride"), + {1}); + // Add 'IarchiveOutput' item (Output filename). + addOptionsGroup(QByteArrayLiteral("IarchiveOutput"), + {opts.outputFile}); +} + +} // namespace v8 +} // namespace arm +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/arm/armarchiversettingsgroup_v8.h b/src/plugins/generator/iarew/archs/arm/armarchiversettingsgroup_v8.h new file mode 100644 index 00000000..c99c2eeb --- /dev/null +++ b/src/plugins/generator/iarew/archs/arm/armarchiversettingsgroup_v8.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWARMAARCHIVERSETTINGSGROUP_V8_H +#define QBS_IAREWARMAARCHIVERSETTINGSGROUP_V8_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace arm { +namespace v8 { + +class ArmArchiverSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit ArmArchiverSettingsGroup(const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildOutputPage(const QString &baseDirectory, + const ProductData &qbsProduct); +}; + +} // namespace v8 +} // namespace arm +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWARMAARCHIVERSETTINGSGROUP_V8_H diff --git a/src/plugins/generator/iarew/archs/arm/armassemblersettingsgroup_v8.cpp b/src/plugins/generator/iarew/archs/arm/armassemblersettingsgroup_v8.cpp new file mode 100644 index 00000000..66c4c9ee --- /dev/null +++ b/src/plugins/generator/iarew/archs/arm/armassemblersettingsgroup_v8.cpp @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armassemblersettingsgroup_v8.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace arm { +namespace v8 { + +constexpr int kAssemblerArchiveVersion = 2; +constexpr int kAssemblerDataVersion = 10; + +namespace { + +// Language page options. + +struct LanguagePageOptions final +{ + enum MacroQuoteCharacter { + AngleBracketsQuote, + RoundBracketsQuote, + SquareBracketsQuote, + FigureBracketsQuote + }; + + explicit LanguagePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleAssemblerFlags(qbsProps); + enableSymbolsCaseSensitive = flags.contains(QLatin1String("-s+")); + allowAlternativeRegister = flags.contains(QLatin1String("-j")); + disableCodeMemoryDataReads = flags.contains( + QLatin1String("--no_literal_pool")); + if (flags.contains(QLatin1String("-M<>"))) + macroQuoteCharacter = LanguagePageOptions::AngleBracketsQuote; + else if (flags.contains(QLatin1String("-M()"))) + macroQuoteCharacter = LanguagePageOptions::RoundBracketsQuote; + else if (flags.contains(QLatin1String("-M[]"))) + macroQuoteCharacter = LanguagePageOptions::SquareBracketsQuote; + else if (flags.contains(QLatin1String("-M{}"))) + macroQuoteCharacter = LanguagePageOptions::FigureBracketsQuote; + } + + MacroQuoteCharacter macroQuoteCharacter = AngleBracketsQuote; + int enableSymbolsCaseSensitive = 0; + int allowAlternativeRegister = 0; + int disableCodeMemoryDataReads = 0; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + debugInfo = gen::utils::debugInformation(qbsProduct); + } + + int debugInfo = 0; +}; + +// Preprocessor page options. + +struct PreprocessorPageOptions final +{ + explicit PreprocessorPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + defineSymbols = gen::utils::cppVariantModuleProperties( + qbsProps, {QStringLiteral("defines")}); + + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + const QStringList fullIncludePaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("includePaths"), + QStringLiteral("systemIncludePaths")}); + for (const QString &fullIncludePath : fullIncludePaths) { + const QFileInfo includeFileInfo(fullIncludePath); + const QString includeFilePath = includeFileInfo.absoluteFilePath(); + if (includeFilePath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, includeFilePath); + includePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, includeFilePath); + includePaths.push_back(path); + } + } + } + + QVariantList defineSymbols; + QVariantList includePaths; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString warningLevel = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("warningLevel")); + if (warningLevel == QLatin1String("all")) { + enableWarnings = 0; + enableAllWarnings = 0; + } else if (warningLevel == QLatin1String("none")) { + enableWarnings = 1; + enableAllWarnings = 0; + } else { + enableWarnings = 0; + enableAllWarnings = 1; + } + } + + int enableWarnings = 0; + int enableAllWarnings = 0; +}; + +} // namespace + +// ArmAssemblerSettingsGroup + +ArmAssemblerSettingsGroup::ArmAssemblerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("AARM")); + setArchiveVersion(kAssemblerArchiveVersion); + setDataVersion(kAssemblerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildLanguagePage(qbsProduct); + buildOutputPage(qbsProduct); + buildPreprocessorPage(buildRootDirectory, qbsProduct); + buildDiagnosticsPage(qbsProduct); +} + +void ArmAssemblerSettingsGroup::buildLanguagePage( + const ProductData &qbsProduct) +{ + const LanguagePageOptions opts(qbsProduct); + // Add 'ACaseSensitivity' item (User symbols are case sensitive). + addOptionsGroup(QByteArrayLiteral("ACaseSensitivity"), + {opts.enableSymbolsCaseSensitive}); + // Add 'AltRegisterNames' item (Allow alternative register names, + // mnemonics and operands). + addOptionsGroup(QByteArrayLiteral("AltRegisterNames"), + {opts.allowAlternativeRegister}); + // Add 'AsmNoLiteralPool' item (No data reads in code memory). + addOptionsGroup(QByteArrayLiteral("AsmNoLiteralPool"), + {opts.disableCodeMemoryDataReads}); + // Add 'MacroChars' item (Macro quote characters: ()/[]/{}/<>). + addOptionsGroup(QByteArrayLiteral("MacroChars"), + {opts.macroQuoteCharacter}, 0); +} +void ArmAssemblerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + // Add 'ADebug' item (Generate debug information). + addOptionsGroup(QByteArrayLiteral("ADebug"), + {opts.debugInfo}); +} + +void ArmAssemblerSettingsGroup::buildPreprocessorPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const PreprocessorPageOptions opts(baseDirectory, qbsProduct); + // Add 'ADefines' item (Defined symbols). + addOptionsGroup(QByteArrayLiteral("ADefines"), + opts.defineSymbols); + // Add 'AUserIncludes' item (Additional include directories). + addOptionsGroup(QByteArrayLiteral("AUserIncludes"), + opts.includePaths); +} + +void ArmAssemblerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'AWarnEnable' item (Enable/disable warnings). + addOptionsGroup(QByteArrayLiteral("AWarnEnable"), + {opts.enableWarnings}); + // Add 'AWarnWhat' item (Enable/disable all warnings). + addOptionsGroup(QByteArrayLiteral("AWarnWhat"), + {opts.enableAllWarnings}); +} + +} // namespace v8 +} // namespace arm +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/arm/armassemblersettingsgroup_v8.h b/src/plugins/generator/iarew/archs/arm/armassemblersettingsgroup_v8.h new file mode 100644 index 00000000..620dcf1f --- /dev/null +++ b/src/plugins/generator/iarew/archs/arm/armassemblersettingsgroup_v8.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWARMASSEMBLERSETTINGSGROUP_V8_H +#define QBS_IAREWARMASSEMBLERSETTINGSGROUP_V8_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace arm { +namespace v8 { + +class ArmAssemblerSettingsGroup final + : public IarewSettingsPropertyGroup +{ +public: + explicit ArmAssemblerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildLanguagePage(const ProductData &qbsProduct); + void buildOutputPage(const ProductData &qbsProduct); + void buildPreprocessorPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); +}; + +} // namespace v8 +} // namespace arm +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWARMASSEMBLERSETTINGSGROUP_V8_H diff --git a/src/plugins/generator/iarew/archs/arm/armbuildconfigurationgroup_v8.cpp b/src/plugins/generator/iarew/archs/arm/armbuildconfigurationgroup_v8.cpp new file mode 100644 index 00000000..a16e57a4 --- /dev/null +++ b/src/plugins/generator/iarew/archs/arm/armbuildconfigurationgroup_v8.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armarchiversettingsgroup_v8.h" +#include "armassemblersettingsgroup_v8.h" +#include "armbuildconfigurationgroup_v8.h" +#include "armcompilersettingsgroup_v8.h" +#include "armgeneralsettingsgroup_v8.h" +#include "armlinkersettingsgroup_v8.h" + +#include "../../iarewtoolchainpropertygroup.h" +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace arm { +namespace v8 { + +ArmBuildConfigurationGroup::ArmBuildConfigurationGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps) + : gen::xml::PropertyGroup("configuration") +{ + // Append configuration name item. + const QString cfgName = gen::utils::buildConfigurationName(qbsProject); + appendProperty("name", cfgName); + + // Apend toolchain name group item. + appendChild("ARM"); + + // Append debug info item. + const int debugBuild = gen::utils::debugInformation(qbsProduct); + appendProperty("debug", debugBuild); + + // Append settings group items. + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); +} + +bool ArmBuildConfigurationGroupFactory::canCreate( + gen::utils::Architecture arch, + const Version &version) const +{ + return arch == gen::utils::Architecture::Arm + && version.majorVersion() == 8; +} + +std::unique_ptr +ArmBuildConfigurationGroupFactory::create( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps) const +{ + const auto group = new ArmBuildConfigurationGroup( + qbsProject, qbsProduct, qbsProductDeps); + return std::unique_ptr(group); +} + +} // namespace v8 +} // namespace arm +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/arm/armbuildconfigurationgroup_v8.h b/src/plugins/generator/iarew/archs/arm/armbuildconfigurationgroup_v8.h new file mode 100644 index 00000000..26370c9b --- /dev/null +++ b/src/plugins/generator/iarew/archs/arm/armbuildconfigurationgroup_v8.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWARMBUILDCONFIGURATIONGROUP_V8_H +#define QBS_IAREWARMBUILDCONFIGURATIONGROUP_V8_H + +#include + +namespace qbs { +namespace iarew { +namespace arm { +namespace v8 { + +class ArmBuildConfigurationGroup final + : public gen::xml::PropertyGroup +{ +private: + explicit ArmBuildConfigurationGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps); + + friend class ArmBuildConfigurationGroupFactory; +}; + +class ArmBuildConfigurationGroupFactory final + : public gen::xml::PropertyGroupFactory +{ +public: + bool canCreate(gen::utils::Architecture arch, + const Version &version) const final; + + std::unique_ptr create( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps) const final; +}; + +} // namespace v8 +} // namespace arm +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWARMBUILDCONFIGURATIONGROUP_V8_H diff --git a/src/plugins/generator/iarew/archs/arm/armcompilersettingsgroup_v8.cpp b/src/plugins/generator/iarew/archs/arm/armcompilersettingsgroup_v8.cpp new file mode 100644 index 00000000..0981fade --- /dev/null +++ b/src/plugins/generator/iarew/archs/arm/armcompilersettingsgroup_v8.cpp @@ -0,0 +1,478 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armcompilersettingsgroup_v8.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace arm { +namespace v8 { + +constexpr int kCompilerArchiveVersion = 2; +constexpr int kCompilerDataVersion = 34; + +namespace { + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + debugInfo = gen::utils::debugInformation(qbsProduct); + } + + int debugInfo = 0; +}; + +// Language 1 page options. + +struct LanguageOnePageOptions final +{ + enum LanguageExtension { + CLanguageExtension, + CxxLanguageExtension, + AutoLanguageExtension + }; + + enum CLanguageDialect { + C89LanguageDialect, + C11LanguageDialect + }; + + enum LanguageConformance { + AllowIarExtension, + RelaxedStandard, + StrictStandard + }; + + explicit LanguageOnePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + // File extension based by default. + languageExtension = LanguageOnePageOptions::AutoLanguageExtension; + // Language dialect. + const QStringList cLanguageVersion = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("cLanguageVersion")}); + cLanguageDialect = cLanguageVersion.contains(QLatin1String("c89")) + ? LanguageOnePageOptions::C89LanguageDialect + : LanguageOnePageOptions::C11LanguageDialect; + + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + // Language conformance. + if (flags.contains(QLatin1String("-e"))) + languageConformance = LanguageOnePageOptions::AllowIarExtension; + else if (flags.contains(QLatin1String("--strict"))) + languageConformance = LanguageOnePageOptions::StrictStandard; + else + languageConformance = LanguageOnePageOptions::RelaxedStandard; + // Exceptions, rtti, static desrtuction. + enableExceptions = !flags.contains(QLatin1String("--no_exceptions")); + enableRtti = !flags.contains(QLatin1String("--no_rtti")); + destroyStaticObjects = !flags.contains( + QLatin1String("--no_static_destruction")); + allowVla = flags.contains(QLatin1String("--vla")); + enableInlineSemantics = flags.contains(QLatin1String("--use_c++_inline")); + requirePrototypes = flags.contains(QLatin1String("--require_prototypes")); + } + + LanguageExtension languageExtension = AutoLanguageExtension; + CLanguageDialect cLanguageDialect = C89LanguageDialect; + LanguageConformance languageConformance = AllowIarExtension; + // C++ options. + int enableExceptions = 0; + int enableRtti = 0; + int destroyStaticObjects = 0; + int allowVla = 0; + int enableInlineSemantics = 0; + int requirePrototypes = 0; +}; + +// Language 2 page options. + +struct LanguageTwoPageOptions final +{ + enum PlainCharacter { + SignedCharacter, + UnsignedCharacter + }; + + enum FloatingPointSemantic { + StrictSemantic, + RelaxedSemantic + }; + + explicit LanguageTwoPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + plainCharacter = flags.contains(QLatin1String("--char_is_signed")) + ? LanguageTwoPageOptions::SignedCharacter + : LanguageTwoPageOptions::UnsignedCharacter; + floatingPointSemantic = flags.contains(QLatin1String("--relaxed_fp")) + ? LanguageTwoPageOptions::RelaxedSemantic + : LanguageTwoPageOptions::StrictSemantic; + } + + PlainCharacter plainCharacter = SignedCharacter; + FloatingPointSemantic floatingPointSemantic = StrictSemantic; +}; + +// Optimizations page options. + +struct OptimizationsPageOptions final +{ + // Optimizations level radio-buttons with + // combo-box on "level" widget. + + enum Strategy { + StrategyBalanced, + StrategySize, + StrategySpeed + }; + + enum Level { + LevelNone, + LevelLow, + LevelMedium, + LevelHigh + }; + + enum LevelSlave { + LevelSlave0, + LevelSlave1, + LevelSlave2, + LevelSlave3 + }; + + explicit OptimizationsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString optimization = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("optimization")); + if (optimization == QLatin1String("none")) { + optimizationStrategy = OptimizationsPageOptions::StrategyBalanced; + optimizationLevel = OptimizationsPageOptions::LevelNone; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave0; + } else if (optimization == QLatin1String("fast")) { + optimizationStrategy = OptimizationsPageOptions::StrategySpeed; + optimizationLevel = OptimizationsPageOptions::LevelHigh; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave3; + } else if (optimization == QLatin1String("small")) { + optimizationStrategy = OptimizationsPageOptions::StrategySize; + optimizationLevel = OptimizationsPageOptions::LevelHigh; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave3; + } + + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + enableCommonSubexpressionElimination = !flags.contains( + QLatin1String("--no_cse")); + enableLoopUnroll = !flags.contains( + QLatin1String("--no_unroll")); + enableFunctionInlining = !flags.contains( + QLatin1String("--no_inline")); + enableCodeMotion = !flags.contains( + QLatin1String("--no_code_motion")); + enableTypeBasedAliasAnalysis = !flags.contains( + QLatin1String("--no_tbaa")); + enableStaticClustering = !flags.contains( + QLatin1String("--no_clustering")); + enableInstructionScheduling = !flags.contains( + QLatin1String("--no_scheduling")); + enableVectorization = flags.contains( + QLatin1String("--vectorize")); + disableSizeConstraints = flags.contains( + QLatin1String("--no_size_constraints")); + } + + Strategy optimizationStrategy = StrategyBalanced; + Level optimizationLevel = LevelNone; + LevelSlave optimizationLevelSlave = LevelSlave0; + // Eight bit-field flags on "enabled optimizations" widget. + int enableCommonSubexpressionElimination = 0; // Common sub-expression elimination. + int enableLoopUnroll = 0; // Loop unrolling. + int enableFunctionInlining = 0; // Function inlining. + int enableCodeMotion = 0; // Code motion. + int enableTypeBasedAliasAnalysis = 0; // Type-based alias analysis. + int enableStaticClustering = 0; // Static clustering. + int enableInstructionScheduling = 0; // Instruction scheduling. + int enableVectorization = 0; // Vectorization. + + // Separate "no size constraints" checkbox. + int disableSizeConstraints = 0; +}; + +// Preprocessor page options. + +struct PreprocessorPageOptions final +{ + explicit PreprocessorPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + defineSymbols = gen::utils::cppVariantModuleProperties( + qbsProps, {QStringLiteral("defines")}); + + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + const QStringList fullIncludePaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("includePaths"), + QStringLiteral("systemIncludePaths")}); + for (const QString &fullIncludePath : fullIncludePaths) { + const QFileInfo includeFileInfo(fullIncludePath); + const QString includeFilePath = includeFileInfo.absoluteFilePath(); + if (includeFilePath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, includeFilePath); + includePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, includeFilePath); + includePaths.push_back(path); + } + } + } + + QVariantList defineSymbols; + QVariantList includePaths; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + treatWarningsAsErrors = gen::utils::cppIntegerModuleProperty( + qbsProps, QStringLiteral("treatWarningsAsErrors")); + } + + int treatWarningsAsErrors = 0; +}; + +// Code page options. + +struct CodePageOptions final +{ + enum ProcessorMode { + CpuArmMode, + CpuThumbMode + }; + + explicit CodePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + const QString cpuModeValue = IarewUtils::flagValue( + flags, QStringLiteral("--cpu_mode")); + if (cpuModeValue == QLatin1String("thumb")) + cpuMode = CodePageOptions::CpuThumbMode; + else if (cpuModeValue == QLatin1String("arm")) + cpuMode = CodePageOptions::CpuArmMode; + + generateReadOnlyPosIndependentCode = flags.contains( + QLatin1String("--ropi")); + generateReadWritePosIndependentCode = flags.contains( + QLatin1String("--rwpi")); + disableDynamicReadWriteInitialization = flags.contains( + QLatin1String("--no_rw_dynamic_init")); + disableCodeMemoryDataReads = flags.contains( + QLatin1String("--no_literal_pool")); + } + + ProcessorMode cpuMode = CpuThumbMode; + int generateReadOnlyPosIndependentCode = 0; + int generateReadWritePosIndependentCode = 0; + int disableDynamicReadWriteInitialization = 0; + int disableCodeMemoryDataReads = 0; +}; + +} // namespace + +// ArmCompilerSettingsGroup + +ArmCompilerSettingsGroup::ArmCompilerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProject) + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("ICCARM")); + setArchiveVersion(kCompilerArchiveVersion); + setDataVersion(kCompilerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildOutputPage(qbsProduct); + buildLanguageOnePage(qbsProduct); + buildLanguageTwoPage(qbsProduct); + buildOptimizationsPage(qbsProduct); + buildPreprocessorPage(buildRootDirectory, qbsProduct); + buildDiagnosticsPage(qbsProduct); + buildCodePage(qbsProduct); +} + +void ArmCompilerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + // Add 'CCDebugInfo' item (Generate debug info). + addOptionsGroup(QByteArrayLiteral("CCDebugInfo"), + {opts.debugInfo}); +} + +void ArmCompilerSettingsGroup::buildLanguageOnePage( + const ProductData &qbsProduct) +{ + const LanguageOnePageOptions opts(qbsProduct); + // Add 'IccLang' item with 'auto-extension based' + // value (Language: C/C++/Auto). + addOptionsGroup(QByteArrayLiteral("IccLang"), + {opts.languageExtension}); + // Add 'IccCDialect' item (C dialect: c89/99/11). + addOptionsGroup(QByteArrayLiteral("IccCDialect"), + {opts.cLanguageDialect}); + // Add 'CCExt' item (Language conformance: IAR/relaxed/strict). + addOptionsGroup(QByteArrayLiteral("CCLangConformance"), + {opts.languageConformance}); + // Add 'IccExceptions2' item (Enable exceptions). + addOptionsGroup(QByteArrayLiteral("IccExceptions2"), + {opts.enableExceptions}); + // Add 'IccRTTI2' item (Enable RTTI). + addOptionsGroup(QByteArrayLiteral("IccRTTI2"), + {opts.enableRtti}); + // Add 'IccStaticDestr' item (Destroy static objects). + addOptionsGroup(QByteArrayLiteral("IccStaticDestr"), + {opts.destroyStaticObjects}); + // Add 'IccAllowVLA' item (Allow VLA). + addOptionsGroup(QByteArrayLiteral("IccAllowVLA"), + {opts.allowVla}); + // Add 'IccCppInlineSemantics' item (C++ inline semantics). + addOptionsGroup(QByteArrayLiteral("IccCppInlineSemantics"), + {opts.enableInlineSemantics}); + // Add 'CCRequirePrototypes' item (Require prototypes). + addOptionsGroup(QByteArrayLiteral("CCRequirePrototypes"), + {opts.requirePrototypes}); +} + +void ArmCompilerSettingsGroup::buildLanguageTwoPage( + const ProductData &qbsProduct) +{ + const LanguageTwoPageOptions opts(qbsProduct); + // Add 'CCSignedPlainChar' item (Plain char is: signed/unsigned). + addOptionsGroup(QByteArrayLiteral("CCSignedPlainChar"), + {opts.plainCharacter}); + // Add 'IccFloatSemantics' item + // (Floating-point semantic: strict/relaxed). + addOptionsGroup(QByteArrayLiteral("IccFloatSemantics"), + {opts.floatingPointSemantic}); +} + +void ArmCompilerSettingsGroup::buildOptimizationsPage( + const ProductData &qbsProduct) +{ + const OptimizationsPageOptions opts(qbsProduct); + // Add 'CCOptStrategy', 'CCOptLevel' + // and 'CCOptLevelSlave' items (Level). + addOptionsGroup(QByteArrayLiteral("CCOptStrategy"), + {opts.optimizationStrategy}); + addOptionsGroup(QByteArrayLiteral("CCOptLevel"), + {opts.optimizationLevel}); + addOptionsGroup(QByteArrayLiteral("CCOptLevelSlave"), + {opts.optimizationLevelSlave}); + // Add 'CCAllowList' item (Enabled optimizations: 6 check boxes). + const QString transformations = QStringLiteral("%1%2%3%4%5%6%7%8") + .arg(opts.enableCommonSubexpressionElimination) + .arg(opts.enableLoopUnroll) + .arg(opts.enableFunctionInlining) + .arg(opts.enableCodeMotion) + .arg(opts.enableTypeBasedAliasAnalysis) + .arg(opts.enableStaticClustering) + .arg(opts.enableInstructionScheduling) + .arg(opts.enableVectorization); + addOptionsGroup(QByteArrayLiteral("CCAllowList"), + {transformations}); + // Add 'CCOptimizationNoSizeConstraints' item (No size constraints). + addOptionsGroup(QByteArrayLiteral("CCOptimizationNoSizeConstraints"), + {opts.disableSizeConstraints}); +} + +void ArmCompilerSettingsGroup::buildPreprocessorPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const PreprocessorPageOptions opts(baseDirectory, qbsProduct); + // Add 'CCDefines' item (Defined symbols). + addOptionsGroup(QByteArrayLiteral("CCDefines"), + opts.defineSymbols); + // Add 'CCIncludePath2' item (Additional include directories). + addOptionsGroup(QByteArrayLiteral("CCIncludePath2"), + opts.includePaths); +} + +void ArmCompilerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'CCDiagWarnAreErr' item (Treat all warnings as errors). + addOptionsGroup(QByteArrayLiteral("CCDiagWarnAreErr"), + {opts.treatWarningsAsErrors}); +} + +void ArmCompilerSettingsGroup::buildCodePage( + const ProductData &qbsProduct) +{ + const CodePageOptions opts(qbsProduct); + // Add 'IProcessorMode2' item (Processor mode: arm/thumb). + addOptionsGroup(QByteArrayLiteral("IProcessorMode2"), + {opts.cpuMode}); + // Add 'CCPosIndRopi' item (Code and read-only data "ropi"). + addOptionsGroup(QByteArrayLiteral("CCPosIndRopi"), + {opts.generateReadOnlyPosIndependentCode}); + // Add 'CCPosIndRwpi' item (Read/write data "rwpi"). + addOptionsGroup(QByteArrayLiteral("CCPosIndRwpi"), + {opts.generateReadWritePosIndependentCode}); + // Add 'CCPosIndNoDynInit' item (No dynamic read/write initialization). + addOptionsGroup(QByteArrayLiteral("CCPosIndNoDynInit"), + {opts.disableDynamicReadWriteInitialization}); + // Add 'CCNoLiteralPool' item (No data reads in code memory). + addOptionsGroup(QByteArrayLiteral("CCNoLiteralPool"), + {opts.disableCodeMemoryDataReads}); +} + +} // namespace v8 +} // namespace arm +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/arm/armcompilersettingsgroup_v8.h b/src/plugins/generator/iarew/archs/arm/armcompilersettingsgroup_v8.h new file mode 100644 index 00000000..6137bc97 --- /dev/null +++ b/src/plugins/generator/iarew/archs/arm/armcompilersettingsgroup_v8.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWARMCOMPILERSETTINGSGROUP_V8_H +#define QBS_IAREWARMCOMPILERSETTINGSGROUP_V8_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace arm { +namespace v8 { + +class ArmCompilerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit ArmCompilerSettingsGroup(const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildOutputPage(const ProductData &qbsProduct); + void buildLanguageOnePage(const ProductData &qbsProduct); + void buildLanguageTwoPage(const ProductData &qbsProduct); + void buildOptimizationsPage(const ProductData &qbsProduct); + void buildPreprocessorPage(const QString &baseDirectory, const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); + void buildCodePage(const ProductData &qbsProduct); +}; + +} // namespace v8 +} // namespace arm +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWARMCOMPILERSETTINGSGROUP_V8_H diff --git a/src/plugins/generator/iarew/archs/arm/armgeneralsettingsgroup_v8.cpp b/src/plugins/generator/iarew/archs/arm/armgeneralsettingsgroup_v8.cpp new file mode 100644 index 00000000..a2395e3d --- /dev/null +++ b/src/plugins/generator/iarew/archs/arm/armgeneralsettingsgroup_v8.cpp @@ -0,0 +1,551 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armgeneralsettingsgroup_v8.h" + +#include "../../iarewutils.h" + +#include + +namespace qbs { +namespace iarew { +namespace arm { +namespace v8 { + +constexpr int kGeneralArchiveVersion = 3; +constexpr int kGeneralDataVersion = 30; + +namespace { + +struct CpuCoreEntry final +{ + enum CpuCoreCode { + Arm7tdmi = 0, + Arm7tdmis = 1, + Arm710t = 2, + Arm720t = 3, + Arm740t = 4, + Arm7ejs = 5, + Arm9tdmi = 6, + Arm920t = 7, + Arm922t = 8, + Arm940t = 9, + Arm9e = 10, + Arm9es = 11, + Arm926ejs = 12, + Arm946es = 13, + Arm966es = 14, + Arm968es = 15, + Arm10e = 16, + Arm1020e = 17, + Arm1022e = 18, + Arm1026ejs = 19, + Arm1136j = 20, + Arm1136js = 21, + Arm1176j = 24, + Arm1176js = 25, + Xscale = 32, + XscaleIr7 = 33, + CortexM0 = 34, + CortexM0Plus = 35, + CortexM1 = 36, + CortexMs1 = 37, + CortexM3 = 38, + CortexM4 = 39, + CortexM7 = 41, + CortexR4 = 42, + CortexR5 = 44, + CortexR7 = 46, + CortexR8 = 48, + CortexR52 = 49, + CortexA5 = 50, + CortexA7 = 52, + CortexA8 = 53, + CortexA9 = 54, + CortexA15 = 55, + CortexA17 = 56, + CortexM23 = 58, + CortexM33 = 59 + }; + + // Required to compile in MSVC2015. + CpuCoreEntry(CpuCoreCode cc, QByteArray tf) + : coreCode(cc), targetFlag(std::move(tf)) + {} + + CpuCoreCode coreCode = Arm7tdmi; + QByteArray targetFlag; +}; + +// Dictionary of known ARM CPU cores and its compiler options. +const CpuCoreEntry cpusDict[] = { + {CpuCoreEntry::Arm7tdmi, "arm7tdmi"}, // same as 'sc100' + {CpuCoreEntry::Arm7tdmis, "arm7tdmi-s"}, + {CpuCoreEntry::Arm710t, "arm710t"}, + {CpuCoreEntry::Arm720t, "arm720t"}, + {CpuCoreEntry::Arm740t, "arm740t"}, + {CpuCoreEntry::Arm7ejs, "arm7ej-s"}, + {CpuCoreEntry::Arm9tdmi, "arm9tdmi"}, + {CpuCoreEntry::Arm920t, "arm920t"}, + {CpuCoreEntry::Arm922t, "arm922t"}, + {CpuCoreEntry::Arm940t, "arm940t"}, + {CpuCoreEntry::Arm9e, "arm9e"}, + {CpuCoreEntry::Arm9es, "arm9e-s"}, + {CpuCoreEntry::Arm926ejs, "arm926ej-s"}, + {CpuCoreEntry::Arm946es, "arm946e-s"}, + {CpuCoreEntry::Arm966es, "arm966e-s"}, + {CpuCoreEntry::Arm968es, "arm968e-s"}, + {CpuCoreEntry::Arm10e, "arm10e"}, + {CpuCoreEntry::Arm1020e, "arm1020e"}, + {CpuCoreEntry::Arm1022e, "arm1022e"}, + {CpuCoreEntry::Arm1026ejs, "arm1026ej-s"}, + {CpuCoreEntry::Arm1136j, "arm1136j"}, + {CpuCoreEntry::Arm1136js, "arm1136j-s"}, + {CpuCoreEntry::Arm1176j, "arm1176j"}, + {CpuCoreEntry::Arm1176js, "arm1176j-s"}, + {CpuCoreEntry::Xscale, "xscale"}, + {CpuCoreEntry::XscaleIr7, "xscale-ir7"}, + {CpuCoreEntry::CortexM0, "cortex-m0"}, + {CpuCoreEntry::CortexM0Plus, "cortex-m0+"}, // same as 'sc000' + {CpuCoreEntry::CortexM1, "cortex-m1"}, + {CpuCoreEntry::CortexMs1, "cortex-ms1"}, + {CpuCoreEntry::CortexM3, "cortex-m3"}, // same as 'sc300' + {CpuCoreEntry::CortexM4, "cortex-m4"}, + {CpuCoreEntry::CortexM7, "cortex-m7"}, + {CpuCoreEntry::CortexR4, "cortex-r4"}, + {CpuCoreEntry::CortexR5, "cortex-r5"}, + {CpuCoreEntry::CortexR7, "cortex-r7"}, + {CpuCoreEntry::CortexR8, "cortex-r8"}, + {CpuCoreEntry::CortexR52, "cortex-r52"}, + {CpuCoreEntry::CortexA5, "cortex-a5"}, + {CpuCoreEntry::CortexA7, "cortex-a7"}, + {CpuCoreEntry::CortexA8, "cortex-a8"}, + {CpuCoreEntry::CortexA9, "cortex-a9"}, + {CpuCoreEntry::CortexA15, "cortex-a15"}, + {CpuCoreEntry::CortexA17, "cortex-a17"}, + {CpuCoreEntry::CortexM23, "cortex-m23"}, + {CpuCoreEntry::CortexM33, "cortex-m33"}, +}; + +struct FpuCoreEntry final +{ + enum FpuRegistersCount { + NoFpuRegisters, + Fpu16Registers, + Fpu32Registers + }; + + enum FpuCoreCode { + NoVfp = 0, + Vfp2 = 2, + Vfp3d16 = 3, + Vfp3 = 3, + Vfp4sp = 4, + Vfp4d16 = 5, + Vfp4 = 5, + Vfp5sp = 6, + Vfp5d16 = 7, + Vfp9s = 8 + }; + + // Required to compile in MSVC2015. + FpuCoreEntry(FpuCoreCode cc, FpuRegistersCount rc, + QByteArray tf) + : coreCode(cc), regsCount(rc), targetFlag(std::move(tf)) + {} + + FpuCoreCode coreCode = NoVfp; + FpuRegistersCount regsCount = NoFpuRegisters; + QByteArray targetFlag; +}; + +// Dictionary of known ARM FPU cores and its compiler options. +const FpuCoreEntry fpusDict[] = { + {FpuCoreEntry::Vfp2, FpuCoreEntry::NoFpuRegisters, "vfpv2"}, + {FpuCoreEntry::Vfp3d16, FpuCoreEntry::Fpu16Registers, "vfpv3_d16"}, + {FpuCoreEntry::Vfp3, FpuCoreEntry::Fpu32Registers, "vfpv3"}, + {FpuCoreEntry::Vfp4sp, FpuCoreEntry::Fpu16Registers, "vfpv4_sp"}, + {FpuCoreEntry::Vfp4d16, FpuCoreEntry::Fpu16Registers, "vfpv4_d16"}, + {FpuCoreEntry::Vfp4, FpuCoreEntry::Fpu32Registers, "vfpv4"}, + {FpuCoreEntry::Vfp5sp, FpuCoreEntry::Fpu16Registers, "vfpv5_sp"}, + {FpuCoreEntry::Vfp5d16, FpuCoreEntry::Fpu16Registers, "vfpv5_d16"}, + {FpuCoreEntry::Vfp9s, FpuCoreEntry::NoFpuRegisters, "vfp9-s"}, +}; + +// Target page options. + +struct TargetPageOptions final +{ + enum Endianness { + LittleEndian, + BigEndian + }; + + explicit TargetPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("driverFlags")}); + // Detect target CPU code. + const QString cpuValue = IarewUtils::flagValue( + flags, QStringLiteral("--cpu")) + .toLower(); + const auto cpuEnd = std::cend(cpusDict); + const auto cpuIt = std::find_if(std::cbegin(cpusDict), cpuEnd, + [cpuValue]( + const CpuCoreEntry &entry) { + return entry.targetFlag == cpuValue.toLatin1(); + }); + if (cpuIt != cpuEnd) + targetCpu = cpuIt->coreCode; + // Detect target FPU code. + const QString fpuValue = IarewUtils::flagValue( + flags, QStringLiteral("--fpu")) + .toLower(); + const auto fpuEnd = std::cend(fpusDict); + const auto fpuIt = std::find_if(std::cbegin(fpusDict), fpuEnd, + [fpuValue]( + const FpuCoreEntry &entry) { + return entry.targetFlag == fpuValue.toLatin1(); + }); + if (fpuIt != fpuEnd) { + targetFpu = fpuIt->coreCode; + targetFpuRegs = fpuIt->regsCount; + } + // Detect endian. + const QString prop = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("endianness")); + if (prop == QLatin1String("big")) + endianness = TargetPageOptions::BigEndian; + else if (prop == QLatin1String("little")) + endianness = TargetPageOptions::LittleEndian; + } + + CpuCoreEntry::CpuCoreCode targetCpu = CpuCoreEntry::Arm7tdmi; + FpuCoreEntry::FpuCoreCode targetFpu = FpuCoreEntry::NoVfp; + FpuCoreEntry::FpuRegistersCount targetFpuRegs = FpuCoreEntry::NoFpuRegisters; + Endianness endianness = LittleEndian; +}; + +// Library 1 page options. + +struct LibraryOnePageOptions final +{ + enum PrintfFormatter { + PrintfAutoFormatter, + PrintfFullFormatter, + PrintfLargeFormatter, + PrintfSmallFormatter, + PrintfTinyFormatter + }; + + enum ScanfFormatter { + ScanfAutoFormatter, + ScanfFullFormatter, + ScanfLargeFormatter, + ScanfSmallFormatter + }; + + explicit LibraryOnePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + for (auto flagIt = flags.cbegin(); flagIt < flags.cend(); ++flagIt) { + if (*flagIt != QLatin1String("--redirect")) + continue; + ++flagIt; + if (flagIt->startsWith(QLatin1String("_printf="), + Qt::CaseInsensitive)) { + const QString prop = flagIt->split( + QLatin1Char('=')).at(1).toLower(); + if (prop == QLatin1String("_printffullnomb")) + printfFormatter = LibraryOnePageOptions::PrintfFullFormatter; + else if (prop == QLatin1String("_printflargenomb")) + printfFormatter = LibraryOnePageOptions::PrintfLargeFormatter; + else if (prop == QLatin1String("_printfsmallnomb")) + printfFormatter = LibraryOnePageOptions::PrintfSmallFormatter; + else if (prop == QLatin1String("_printftiny")) + printfFormatter = LibraryOnePageOptions::PrintfTinyFormatter; + } else if (flagIt->startsWith(QLatin1String("_scanf="), + Qt::CaseInsensitive)) { + const QString prop = flagIt->split( + QLatin1Char('=')).at(1).toLower();; + if (prop == QLatin1String("_scanffullnomb")) + scanfFormatter = LibraryOnePageOptions::ScanfFullFormatter; + else if (prop == QLatin1String("_scanflargenomb")) + scanfFormatter = LibraryOnePageOptions::ScanfLargeFormatter; + else if (prop == QLatin1String("_scanfsmallnomb")) + scanfFormatter = LibraryOnePageOptions::ScanfSmallFormatter; + } + } + } + + PrintfFormatter printfFormatter = PrintfAutoFormatter; + ScanfFormatter scanfFormatter = ScanfAutoFormatter; +}; + +// Library 2 page options. + +struct LibraryTwoPageOptions final +{ + enum HeapType { + AutomaticHeap, + AdvancedHeap, + BasicHeap, + NoFreeHeap + }; + + explicit LibraryTwoPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + if (flags.contains(QLatin1String("--advanced_heap"))) + heapType = LibraryTwoPageOptions::AdvancedHeap; + else if (flags.contains(QLatin1String("--basic_heap"))) + heapType = LibraryTwoPageOptions::BasicHeap; + else if (flags.contains(QLatin1String("--no_free_heap"))) + heapType = LibraryTwoPageOptions::NoFreeHeap; + else + heapType = LibraryTwoPageOptions::AutomaticHeap; + } + + HeapType heapType = AutomaticHeap; +}; + +// Library configuration page options. + +struct LibraryConfigPageOptions final +{ + enum RuntimeLibrary { + NoLibrary, + NormalLibrary, + FullLibrary, + CustomLibrary + }; + + enum ThreadSupport { + NoThread, + EnableThread + }; + + enum LowLevelInterface { + NoInterface, + SemihostedInterface + }; + + explicit LibraryConfigPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + const QFileInfo dlibFileInfo(IarewUtils::flagValue( + flags, QStringLiteral("--dlib_config"))); + if (!dlibFileInfo.exists()) { + dlibType = LibraryConfigPageOptions::NoLibrary; + } else { + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + const QString dlibFilePath = dlibFileInfo.absoluteFilePath(); + if (dlibFilePath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + if (dlibFilePath.endsWith(QLatin1String("dlib_config_normal.h"), + Qt::CaseInsensitive)) { + dlibType = LibraryConfigPageOptions::NormalLibrary; + } else if (dlibFilePath.endsWith( + QLatin1String("dlib_config_full.h"), + Qt::CaseInsensitive)) { + dlibType = LibraryConfigPageOptions::FullLibrary; + } else { + dlibType = LibraryConfigPageOptions::CustomLibrary; + } + + dlibConfigPath = IarewUtils::toolkitRelativeFilePath( + toolkitPath, dlibFilePath); + } else { + dlibType = LibraryConfigPageOptions::CustomLibrary; + dlibConfigPath = IarewUtils::projectRelativeFilePath( + baseDirectory, dlibFilePath); + } + } + + threadSupport = flags.contains(QLatin1String("--threaded_lib")) + ? LibraryConfigPageOptions::EnableThread + : LibraryConfigPageOptions::NoThread; + lowLevelInterface = flags.contains(QLatin1String("--semihosting")) + ? LibraryConfigPageOptions::SemihostedInterface + : LibraryConfigPageOptions::NoInterface; + } + + RuntimeLibrary dlibType = NoLibrary; + QString dlibConfigPath; + ThreadSupport threadSupport = NoThread; + LowLevelInterface lowLevelInterface = NoInterface; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + binaryType = IarewUtils::outputBinaryType(qbsProduct); + binaryDirectory = gen::utils::binaryOutputDirectory( + baseDirectory, qbsProduct); + objectDirectory = gen::utils::objectsOutputDirectory( + baseDirectory, qbsProduct); + listingDirectory = gen::utils::listingOutputDirectory( + baseDirectory, qbsProduct); + } + + IarewUtils::OutputBinaryType binaryType = IarewUtils::ApplicationOutputType; + QString binaryDirectory; + QString objectDirectory; + QString listingDirectory; +}; + +} // namespace + +// ArmGeneralSettingsGroup + +ArmGeneralSettingsGroup::ArmGeneralSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("General")); + setArchiveVersion(kGeneralArchiveVersion); + setDataVersion(kGeneralDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildTargetPage(qbsProduct); + buildLibraryOptionsOnePage(qbsProduct); + buildLibraryOptionsTwoPage(qbsProduct); + buildLibraryConfigPage(buildRootDirectory, qbsProduct); + buildOutputPage(buildRootDirectory, qbsProduct); +} + +void ArmGeneralSettingsGroup::buildTargetPage( + const ProductData &qbsProduct) +{ + const TargetPageOptions opts(qbsProduct); + // Add 'GBECoreSlave', 'CoreVariant', 'GFPUCoreSlave2' items + // (Processor variant chooser). + addOptionsGroup(QByteArrayLiteral("GBECoreSlave"), + {opts.targetCpu}, 26); + addOptionsGroup(QByteArrayLiteral("CoreVariant"), + {opts.targetCpu}, 26); + addOptionsGroup(QByteArrayLiteral("GFPUCoreSlave2"), + {opts.targetCpu}, 26); + // Add 'FPU2', 'NrRegs' item (Floating point settings chooser). + addOptionsGroup(QByteArrayLiteral("FPU2"), + {opts.targetFpu}, 0); + addOptionsGroup(QByteArrayLiteral("NrRegs"), + {opts.targetFpuRegs}, 0); + // Add 'GEndianMode' item (Endian mode chooser). + addOptionsGroup(QByteArrayLiteral("GEndianMode"), + {opts.endianness}); +} + +void ArmGeneralSettingsGroup::buildLibraryOptionsOnePage( + const ProductData &qbsProduct) +{ + const LibraryOnePageOptions opts(qbsProduct); + // Add 'OGPrintfVariant' item (Printf formatter). + addOptionsGroup(QByteArrayLiteral("OGPrintfVariant"), + {opts.printfFormatter}); + // Add 'OGScanfVariant' item (Printf formatter). + addOptionsGroup(QByteArrayLiteral("OGScanfVariant"), + {opts.scanfFormatter}); +} + +void ArmGeneralSettingsGroup::buildLibraryOptionsTwoPage( + const ProductData &qbsProduct) +{ + const LibraryTwoPageOptions opts(qbsProduct); + // Add 'OgLibHeap' item (Heap selection: + // auto/advanced/basic/nofree). + addOptionsGroup(QByteArrayLiteral("OgLibHeap"), + {opts.heapType}); +} + +void ArmGeneralSettingsGroup::buildLibraryConfigPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const LibraryConfigPageOptions opts(baseDirectory, qbsProduct); + // Add 'GRuntimeLibSelect', 'GRuntimeLibSelectSlave' + // and 'RTConfigPath2' items + // (Link with runtime: none/normal/full/custom). + addOptionsGroup(QByteArrayLiteral("GRuntimeLibSelect"), + {opts.dlibType}); + addOptionsGroup(QByteArrayLiteral("GRuntimeLibSelectSlave"), + {opts.dlibType}); + addOptionsGroup(QByteArrayLiteral("RTConfigPath2"), + {opts.dlibConfigPath}); + // Add 'GRuntimeLibThreads'item + // (Enable thread support in library). + addOptionsGroup(QByteArrayLiteral("GRuntimeLibThreads"), + {opts.threadSupport}); + // Add 'GenLowLevelInterface' item (Library low-level + // interface: none/semihosted/breakpoint). + addOptionsGroup(QByteArrayLiteral("GenLowLevelInterface"), + {opts.lowLevelInterface}); +} + +void ArmGeneralSettingsGroup::buildOutputPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(baseDirectory, qbsProduct); + // Add 'GOutputBinary' item + // (Output file: executable/library). + addOptionsGroup(QByteArrayLiteral("GOutputBinary"), + {opts.binaryType}); + // Add 'ExePath' item + // (Executable/binaries output directory). + addOptionsGroup(QByteArrayLiteral("ExePath"), + {opts.binaryDirectory}); + // Add 'ObjPath' item + // (Object files output directory). + addOptionsGroup(QByteArrayLiteral("ObjPath"), + {opts.objectDirectory}); + // Add 'ListPath' item + // (List files output directory). + addOptionsGroup(QByteArrayLiteral("ListPath"), + {opts.listingDirectory}); +} + +} // namespace v8 +} // namespace arm +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/arm/armgeneralsettingsgroup_v8.h b/src/plugins/generator/iarew/archs/arm/armgeneralsettingsgroup_v8.h new file mode 100644 index 00000000..0b1564b3 --- /dev/null +++ b/src/plugins/generator/iarew/archs/arm/armgeneralsettingsgroup_v8.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWARMGENERALSETTINGSGROUP_V8_H +#define QBS_IAREWARMGENERALSETTINGSGROUP_V8_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace arm { +namespace v8 { + +class ArmGeneralSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit ArmGeneralSettingsGroup(const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildTargetPage(const ProductData &qbsProduct); + void buildLibraryOptionsOnePage(const ProductData &qbsProduct); + void buildLibraryOptionsTwoPage(const ProductData &qbsProduct); + void buildLibraryConfigPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildOutputPage(const QString &baseDirectory, + const ProductData &qbsProduct); +}; + +} // namespace v8 +} // namespace arm +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWARMGENERALSETTINGSGROUP_V8_H diff --git a/src/plugins/generator/iarew/archs/arm/armlinkersettingsgroup_v8.cpp b/src/plugins/generator/iarew/archs/arm/armlinkersettingsgroup_v8.cpp new file mode 100644 index 00000000..2cabac58 --- /dev/null +++ b/src/plugins/generator/iarew/archs/arm/armlinkersettingsgroup_v8.cpp @@ -0,0 +1,488 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armlinkersettingsgroup_v8.h" + +#include "../../iarewutils.h" + +#include + +namespace qbs { +namespace iarew { +namespace arm { +namespace v8 { + +constexpr int kLinkerArchiveVersion = 0; +constexpr int kLinkerDataVersion = 20; + +namespace { + +// Config page options. + +struct ConfigPageOptions final +{ + explicit ConfigPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + // Accumulate config definitions (if exists). + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + configDefines = IarewUtils::flagValues( + flags, QStringLiteral("--config_def")); + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + + // Enumerate all product linker config files + // (which are set trough 'linkerscript' tag). + for (const auto &qbsGroup : qbsProduct.groups()) { + if (!qbsGroup.isEnabled()) + continue; + const auto qbsArtifacts = qbsGroup.sourceArtifacts(); + for (const auto &qbsArtifact : qbsArtifacts) { + const auto qbsTags = qbsArtifact.fileTags(); + if (!qbsTags.contains(QLatin1String("linkerscript"))) + continue; + const QString fullConfigPath = qbsArtifact.filePath(); + if (fullConfigPath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullConfigPath); + configFilePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullConfigPath); + configFilePaths.push_back(path); + } + } + } + + // Enumerate all product linker config files + // (which are set trough '--config' option). + const QVariantList configPathValues = IarewUtils::flagValues( + flags, QStringLiteral("--config")); + for (const auto &configPathValue : configPathValues) { + const QString fullConfigPath = configPathValue.toString(); + if (fullConfigPath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullConfigPath); + if (!configFilePaths.contains(path)) + configFilePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullConfigPath); + if (!configFilePaths.contains(path)) + configFilePaths.push_back(path); + } + } + } + + QVariantList configFilePaths; + QVariantList configDefines; +}; + +// Library page options. + +struct LibraryPageOptions final +{ + explicit LibraryPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + + entryPoint = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("entryPoint")); + + // Add libraries search paths. + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + const QStringList libraryPaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("libraryPaths")}); + for (const QString &libraryPath : libraryPaths) { + const QFileInfo libraryPathInfo(libraryPath); + const QString fullLibrarySearchPath = + libraryPathInfo.absoluteFilePath(); + if (fullLibrarySearchPath.startsWith( + toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullLibrarySearchPath); + librarySearchPaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory,fullLibrarySearchPath); + librarySearchPaths.push_back(path); + } + } + + // Add static libraries paths. + const QStringList staticLibrariesProps = + gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("staticLibraries")}); + for (const QString &staticLibrary : staticLibrariesProps) { + const QFileInfo staticLibraryInfo(staticLibrary); + if (staticLibraryInfo.isAbsolute()) { + const QString fullStaticLibraryPath = + staticLibraryInfo.absoluteFilePath(); + if (fullStaticLibraryPath.startsWith( + toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullStaticLibraryPath); + staticLibraries.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullStaticLibraryPath); + staticLibraries.push_back(path); + } + } else { + staticLibraries.push_back(staticLibrary); + } + } + + // Add static libraries from product dependencies. + for (const ProductData &qbsProductDep : qbsProductDeps) { + const QString depBinaryPath = QLatin1String("$PROJ_DIR$/") + + gen::utils::targetBinaryPath(baseDirectory, + qbsProductDep); + staticLibraries.push_back(depBinaryPath); + } + + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + enableRuntimeLibsSearch = !flags.contains( + QLatin1String("--no_library_search")); + } + + QString entryPoint; + QVariantList staticLibraries; + QVariantList librarySearchPaths; + int enableRuntimeLibsSearch = 0; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + debugInfo = !flags.contains(QLatin1String("--strip")); + outputFile = gen::utils::targetBinary(qbsProduct); + } + + int debugInfo = 0; + QString outputFile; +}; + +// Input page options. + +struct InputPageOptions final +{ + explicit InputPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + keepSymbols = IarewUtils::flagValues(flags, QStringLiteral("--keep")); + } + + QVariantList keepSymbols; +}; + +// List page options. + +struct ListPageOptions final +{ + enum ListingAction { + NoListing, + GenerateListing + }; + + explicit ListPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + generateMap = gen::utils::cppBooleanModuleProperty( + qbsProps, QStringLiteral("generateLinkerMapFile")) + ? ListPageOptions::GenerateListing + : ListPageOptions::NoListing; + } + + ListingAction generateMap = GenerateListing; +}; + +// Optimizations page options. + +struct OptimizationsPageOptions final +{ + explicit OptimizationsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + inlineSmallRoutines = flags.contains(QLatin1String("--inline")); + mergeDuplicateSections = flags.contains( + QLatin1String("--merge_duplicate_sections")); + virtualFuncElimination = flags.contains(QLatin1String("--vfe")); + } + + int inlineSmallRoutines = 0; + int mergeDuplicateSections = 0; + int virtualFuncElimination = 0; +}; + +// Advanced page options. + +struct AdvancedPageOptions final +{ + explicit AdvancedPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + allowExceptions = !flags.contains(QLatin1String("--no_exceptions")); + } + + int allowExceptions = 0; +}; + +// Defines page options. + +struct DefinesPageOptions final +{ + explicit DefinesPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + defineSymbols = IarewUtils::flagValues( + flags, QStringLiteral("--define_symbol")); + } + + QVariantList defineSymbols; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + treatWarningsAsErrors = gen::utils::cppIntegerModuleProperty( + qbsProps, QStringLiteral("treatWarningsAsErrors")); + } + + int treatWarningsAsErrors = 0; +}; + +} // namespace + +// ArmLinkerSettingsGroup + +ArmLinkerSettingsGroup::ArmLinkerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + setName(QByteArrayLiteral("ILINK")); + setArchiveVersion(kLinkerArchiveVersion); + setDataVersion(kLinkerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildConfigPage(buildRootDirectory, qbsProduct); + buildLibraryPage(buildRootDirectory, qbsProduct, qbsProductDeps); + buildOutputPage(qbsProduct); + buildInputPage(qbsProduct); + buildListPage(qbsProduct); + buildOptimizationsPage(qbsProduct); + buildAdvancedPage(qbsProduct); + buildDefinesPage(qbsProduct); + + // Should be called as latest stage! + buildExtraOptionsPage(qbsProduct); +} + +void ArmLinkerSettingsGroup::buildConfigPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + ConfigPageOptions opts(baseDirectory, qbsProduct); + // Add 'IlinkConfigDefines' item + // (Configuration file symbol definitions). + addOptionsGroup(QByteArrayLiteral("IlinkConfigDefines"), + opts.configDefines); + + if (opts.configFilePaths.count() > 0) { + // Note: IAR IDE does not allow to specify a multiple config files, + // although the IAR linker support it. So, we use followig 'trick': + // we take a first config file and to add it as usual to required items; + // and then an other remainders we forward to the "Extra options page". + const QVariant configPath = opts.configFilePaths.takeFirst(); + // Add 'IlinkIcfOverride' item (Override default). + addOptionsGroup(QByteArrayLiteral("IlinkIcfOverride"), + {1}); + // Add 'IlinkIcfFile' item (Linker configuration file). + addOptionsGroup(QByteArrayLiteral("IlinkIcfFile"), + {configPath}); + + // Add remainder configuration files to the "Extra options page". + if (!opts.configFilePaths.isEmpty()) { + for (QVariant &configPath : opts.configFilePaths) + configPath = QLatin1String("--config ") + + configPath.toString(); + + m_extraOptions << opts.configFilePaths; + } + } +} + +void ArmLinkerSettingsGroup::buildLibraryPage( + const QString &baseDirectory, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + LibraryPageOptions opts(baseDirectory, qbsProduct, qbsProductDeps); + // Add 'IlinkOverrideProgramEntryLabel' item + // (Override default program entry). + addOptionsGroup(QByteArrayLiteral("IlinkOverrideProgramEntryLabel"), + {1}); + const int select = opts.entryPoint.isEmpty() ? 1 : 0; + addOptionsGroup(QByteArrayLiteral("IlinkProgramEntryLabelSelect"), + {select}); + // Add 'IlinkProgramEntryLabel' item (Entry point name). + addOptionsGroup(QByteArrayLiteral("IlinkProgramEntryLabel"), + {opts.entryPoint}); + + if (!opts.staticLibraries.isEmpty()) { + // Add 'IlinkAdditionalLibs' item (Additional libraries). + addOptionsGroup(QByteArrayLiteral("IlinkAdditionalLibs"), + opts.staticLibraries); + } + + // Add 'IlinkAutoLibEnable' item + // (Automatic runtime library selection). + addOptionsGroup(QByteArrayLiteral("IlinkAutoLibEnable"), + {opts.enableRuntimeLibsSearch}); + + // Add library searh directories to the + // "Extra options page", because IAR IDE + // has not other options to add this paths. + for (QVariant &libraryPath : opts.librarySearchPaths) + libraryPath = QLatin1String("-L ") + libraryPath.toString(); + + m_extraOptions << opts.librarySearchPaths; +} + +void ArmLinkerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + // Add 'IlinkDebugInfoEnable' item + // (Include debug information in output). + addOptionsGroup(QByteArrayLiteral("IlinkDebugInfoEnable"), + {opts.debugInfo}); + // Add 'IlinkOutputFile' item (Output filename). + addOptionsGroup(QByteArrayLiteral("IlinkOutputFile"), + {opts.outputFile}); +} + +void ArmLinkerSettingsGroup::buildInputPage( + const ProductData &qbsProduct) +{ + const InputPageOptions opts(qbsProduct); + // Add 'IlinkKeepSymbols' item (). + addOptionsGroup(QByteArrayLiteral("IlinkKeepSymbols"), + opts.keepSymbols); +} + +void ArmLinkerSettingsGroup::buildListPage( + const ProductData &qbsProduct) +{ + const ListPageOptions opts(qbsProduct); + // Add 'IlinkMapFile' item (Generate linker map file). + addOptionsGroup(QByteArrayLiteral("IlinkMapFile"), + {opts.generateMap}); +} + +void ArmLinkerSettingsGroup::buildOptimizationsPage( + const ProductData &qbsProduct) +{ + const OptimizationsPageOptions opts(qbsProduct); + // Add 'IlinkOptInline' item (Inline small routines). + addOptionsGroup(QByteArrayLiteral("IlinkOptInline"), + {opts.inlineSmallRoutines}); + // Add 'IlinkOptMergeDuplSections'item + // (Merge duplicate sections). + addOptionsGroup(QByteArrayLiteral("IlinkOptMergeDuplSections"), + {opts.mergeDuplicateSections}); + // Add 'IlinkOptUseVfe' item + // (Perform C++ virtual functions elimination). + addOptionsGroup(QByteArrayLiteral("IlinkOptUseVfe"), + {opts.virtualFuncElimination}); +} + +void ArmLinkerSettingsGroup::buildAdvancedPage( + const ProductData &qbsProduct) +{ + const AdvancedPageOptions opts(qbsProduct); + // Add 'IlinkOptExceptionsAllow' item (Allow C++ exceptions). + addOptionsGroup(QByteArrayLiteral("IlinkOptExceptionsAllow"), + {opts.allowExceptions}); +} + +void ArmLinkerSettingsGroup::buildDefinesPage( + const ProductData &qbsProduct) +{ + const DefinesPageOptions opts(qbsProduct); + // Add 'IlinkDefines' item (Defined symbols). + addOptionsGroup(QByteArrayLiteral("IlinkDefines"), + {opts.defineSymbols}); +} + +void ArmLinkerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'IlinkWarningsAreErrors' item + // (Treat all warnings as errors). + addOptionsGroup(QByteArrayLiteral("IlinkWarningsAreErrors"), + {opts.treatWarningsAsErrors}); +} + +void ArmLinkerSettingsGroup::buildExtraOptionsPage( + const ProductData &qbsProduct) +{ + Q_UNUSED(qbsProduct) + + // Add 'IlinkUseExtraOptions' and 'IlinkExtraOptions' items. + addOptionsGroup(QByteArrayLiteral("IlinkUseExtraOptions"), + {1}); + addOptionsGroup(QByteArrayLiteral("IlinkExtraOptions"), + m_extraOptions); +} + +} // namespace v8 +} // namespace arm +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/arm/armlinkersettingsgroup_v8.h b/src/plugins/generator/iarew/archs/arm/armlinkersettingsgroup_v8.h new file mode 100644 index 00000000..e86297e6 --- /dev/null +++ b/src/plugins/generator/iarew/archs/arm/armlinkersettingsgroup_v8.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWARMLINKERSETTINGSGROUP_V8_H +#define QBS_IAREWARMLINKERSETTINGSGROUP_V8_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace arm { +namespace v8 { + +class ArmLinkerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit ArmLinkerSettingsGroup(const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildConfigPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildLibraryPage(const QString &baseDirectory, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + void buildOutputPage(const ProductData &qbsProduct); + void buildInputPage(const ProductData &qbsProduct); + void buildListPage(const ProductData &qbsProduct); + void buildOptimizationsPage(const ProductData &qbsProduct); + void buildAdvancedPage(const ProductData &qbsProduct); + void buildDefinesPage(const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); + void buildExtraOptionsPage(const ProductData &qbsProduct); + + QVariantList m_extraOptions; +}; + +} // namespace v8 +} // namespace arm +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWARMLINKERSETTINGSGROUP_V8_H diff --git a/src/plugins/generator/iarew/archs/avr/avrarchiversettingsgroup_v7.cpp b/src/plugins/generator/iarew/archs/avr/avrarchiversettingsgroup_v7.cpp new file mode 100644 index 00000000..4e002d13 --- /dev/null +++ b/src/plugins/generator/iarew/archs/avr/avrarchiversettingsgroup_v7.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "avrarchiversettingsgroup_v7.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace avr { +namespace v7 { + +constexpr int kArchiverArchiveVersion = 2; +constexpr int kArchiverDataVersion = 0; + +namespace { + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + outputFile = QLatin1String("$PROJ_DIR$/") + + gen::utils::targetBinaryPath(baseDirectory, qbsProduct); + } + + QString outputFile; +}; + +} // namespace + +// AvrArchiverSettingsGroup + +AvrArchiverSettingsGroup::AvrArchiverSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("XAR")); + setArchiveVersion(kArchiverArchiveVersion); + setDataVersion(kArchiverDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + buildOutputPage(buildRootDirectory, qbsProduct); +} + +void AvrArchiverSettingsGroup::buildOutputPage(const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(baseDirectory, qbsProduct); + // Add 'XAROutOverride' item (Override default). + addOptionsGroup(QByteArrayLiteral("XAROutOverride"), + {1}); + // Add 'OutputFile' item (Output filename). + addOptionsGroup(QByteArrayLiteral("OutputFile"), + {opts.outputFile}); +} + +} // namespace v7 +} // namespace avr +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/avr/avrarchiversettingsgroup_v7.h b/src/plugins/generator/iarew/archs/avr/avrarchiversettingsgroup_v7.h new file mode 100644 index 00000000..2ff667e1 --- /dev/null +++ b/src/plugins/generator/iarew/archs/avr/avrarchiversettingsgroup_v7.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWAVRARCHIVERSETTINGSGROUP_V7_H +#define QBS_IAREWAVRARCHIVERSETTINGSGROUP_V7_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace avr { +namespace v7 { + +class AvrArchiverSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit AvrArchiverSettingsGroup(const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildOutputPage(const QString &baseDirectory, + const ProductData &qbsProduct); +}; + +} // namespace v7 +} // namespace avr +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWAVRARCHIVERSETTINGSGROUP_V7_H diff --git a/src/plugins/generator/iarew/archs/avr/avrassemblersettingsgroup_v7.cpp b/src/plugins/generator/iarew/archs/avr/avrassemblersettingsgroup_v7.cpp new file mode 100644 index 00000000..08e86be5 --- /dev/null +++ b/src/plugins/generator/iarew/archs/avr/avrassemblersettingsgroup_v7.cpp @@ -0,0 +1,226 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "avrassemblersettingsgroup_v7.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace avr { +namespace v7 { + +constexpr int kAssemblerArchiveVersion = 5; +constexpr int kAssemblerDataVersion = 11; + +namespace { + +// Language page options. + +struct LanguagePageOptions final +{ + enum MacroQuoteCharacter { + AngleBracketsQuote, + RoundBracketsQuote, + SquareBracketsQuote, + FigureBracketsQuote + }; + + explicit LanguagePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("assemblerFlags")}); + enableSymbolsCaseSensitive = flags.contains(QLatin1String("-s+")); + enableMultibyteSupport = flags.contains(QLatin1String("-n")); + + if (flags.contains(QLatin1String("-M<>"))) + macroQuoteCharacter = LanguagePageOptions::AngleBracketsQuote; + else if (flags.contains(QLatin1String("-M()"))) + macroQuoteCharacter = LanguagePageOptions::RoundBracketsQuote; + else if (flags.contains(QLatin1String("-M[]"))) + macroQuoteCharacter = LanguagePageOptions::SquareBracketsQuote; + else if (flags.contains(QLatin1String("-M{}"))) + macroQuoteCharacter = LanguagePageOptions::FigureBracketsQuote; + } + + MacroQuoteCharacter macroQuoteCharacter = AngleBracketsQuote; + int enableSymbolsCaseSensitive = 0; + int enableMultibyteSupport = 0; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + debugInfo = gen::utils::debugInformation(qbsProduct); + } + + int debugInfo = 0; +}; + +// Preprocessor page options. + +struct PreprocessorPageOptions final +{ + explicit PreprocessorPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + defineSymbols = gen::utils::cppVariantModuleProperties( + qbsProps, {QStringLiteral("defines")}); + + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + const QStringList fullIncludePaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("includePaths"), + QStringLiteral("systemIncludePaths")}); + for (const auto &fullIncludePath : fullIncludePaths) { + const QFileInfo includeFileInfo(fullIncludePath); + const QString includeFilePath = includeFileInfo.absoluteFilePath(); + if (includeFilePath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, includeFilePath); + includePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, includeFilePath); + includePaths.push_back(path); + } + } + } + + QVariantList defineSymbols; + QVariantList includePaths; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString warningLevel = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("warningLevel")); + if (warningLevel == QLatin1String("all")) { + enableWarnings = 0; + enableAllWarnings = 0; + } else if (warningLevel == QLatin1String("none")) { + enableWarnings = 1; + enableAllWarnings = 0; + } else { + enableWarnings = 0; + enableAllWarnings = 1; + } + } + + int enableWarnings = 0; + int enableAllWarnings = 0; +}; + +} // namespace + +// AvrAssemblerSettingsGroup + +AvrAssemblerSettingsGroup::AvrAssemblerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("AAVR")); + setArchiveVersion(kAssemblerArchiveVersion); + setDataVersion(kAssemblerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildLanguagePage(qbsProduct); + buildOutputPage(qbsProduct); + buildPreprocessorPage(buildRootDirectory, qbsProduct); + buildDiagnosticsPage(qbsProduct); +} + +void AvrAssemblerSettingsGroup::buildLanguagePage( + const ProductData &qbsProduct) +{ + const LanguagePageOptions opts(qbsProduct); + // Add 'ACaseSensitivity' item (User symbols are case sensitive). + addOptionsGroup(QByteArrayLiteral("ACaseSensitivity"), + {opts.enableSymbolsCaseSensitive}); + // Add 'AsmMultiByteSupport' item (Enable multibyte support). + addOptionsGroup(QByteArrayLiteral("AsmMultiByteSupport"), + {opts.enableMultibyteSupport}); + // Add 'MacroChars' item (Macro quote characters: ()/[]/{}/<>). + addOptionsGroup(QByteArrayLiteral("MacroChars"), + {opts.macroQuoteCharacter}, 0); +} + +void AvrAssemblerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + // Add 'CDebug' item (Generate debug information). + addOptionsGroup(QByteArrayLiteral("CDebug"), + {opts.debugInfo}); +} + +void AvrAssemblerSettingsGroup::buildPreprocessorPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const PreprocessorPageOptions opts(baseDirectory, qbsProduct); + // Add 'ADefines' item (Defined symbols). + addOptionsGroup(QByteArrayLiteral("ADefines"), + opts.defineSymbols); + // Add 'AUserIncludes' item (Additional include directories). + addOptionsGroup(QByteArrayLiteral("ANewIncludes"), + opts.includePaths); +} + +void AvrAssemblerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'AWarnEnable' item (Enable/disable warnings). + addOptionsGroup(QByteArrayLiteral("AWarnEnable"), + {opts.enableWarnings}); + // Add 'AWarnWhat' item (Enable/disable all warnings). + addOptionsGroup(QByteArrayLiteral("AWarnWhat"), + {opts.enableAllWarnings}); +} + +} // namespace v7 +} // namespace avr +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/avr/avrassemblersettingsgroup_v7.h b/src/plugins/generator/iarew/archs/avr/avrassemblersettingsgroup_v7.h new file mode 100644 index 00000000..608a4265 --- /dev/null +++ b/src/plugins/generator/iarew/archs/avr/avrassemblersettingsgroup_v7.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWAVRASSEMBLERSETTINGSGROUP_V7_H +#define QBS_IAREWAVRASSEMBLERSETTINGSGROUP_V7_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace avr { +namespace v7 { + +class AvrAssemblerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit AvrAssemblerSettingsGroup(const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildLanguagePage(const ProductData &qbsProduct); + void buildOutputPage(const ProductData &qbsProduct); + void buildPreprocessorPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); +}; + +} // namespace v7 +} // namespace avr +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWAVRASSEMBLERSETTINGSGROUP_V7_H diff --git a/src/plugins/generator/iarew/archs/avr/avrbuildconfigurationgroup_v7.cpp b/src/plugins/generator/iarew/archs/avr/avrbuildconfigurationgroup_v7.cpp new file mode 100644 index 00000000..dbb5c662 --- /dev/null +++ b/src/plugins/generator/iarew/archs/avr/avrbuildconfigurationgroup_v7.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "avrarchiversettingsgroup_v7.h" +#include "avrassemblersettingsgroup_v7.h" +#include "avrbuildconfigurationgroup_v7.h" +#include "avrcompilersettingsgroup_v7.h" +#include "avrgeneralsettingsgroup_v7.h" +#include "avrlinkersettingsgroup_v7.h" + +#include "../../iarewtoolchainpropertygroup.h" +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace avr { +namespace v7 { + +AvrBuildConfigurationGroup::AvrBuildConfigurationGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) + : gen::xml::PropertyGroup("configuration") +{ + // Append configuration name item. + const QString cfgName = gen::utils::buildConfigurationName(qbsProject); + appendProperty("name", cfgName); + + // Apend toolchain name group item. + appendChild("AVR"); + + // Append debug info item. + const int debugBuild = gen::utils::debugInformation(qbsProduct); + appendProperty("debug", debugBuild); + + // Append settings group items. + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); +} + +bool AvrBuildConfigurationGroupFactory::canCreate( + gen::utils::Architecture arch, + const Version &version) const +{ + return arch == gen::utils::Architecture::Avr + && version.majorVersion() == 7; +} + +std::unique_ptr +AvrBuildConfigurationGroupFactory::create( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) const +{ + const auto group = new AvrBuildConfigurationGroup( + qbsProject, qbsProduct, qbsProductDeps); + return std::unique_ptr(group); +} + +} // namespace v7 +} // namespace avr +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/avr/avrbuildconfigurationgroup_v7.h b/src/plugins/generator/iarew/archs/avr/avrbuildconfigurationgroup_v7.h new file mode 100644 index 00000000..618cef4c --- /dev/null +++ b/src/plugins/generator/iarew/archs/avr/avrbuildconfigurationgroup_v7.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWAVRBUILDCONFIGURATIONGROUP_V7_H +#define QBS_IAREWAVRBUILDCONFIGURATIONGROUP_V7_H + +#include +#include + +namespace qbs { +namespace iarew { +namespace avr { +namespace v7 { + +class AvrBuildConfigurationGroup final + : public gen::xml::PropertyGroup +{ +private: + explicit AvrBuildConfigurationGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + + friend class AvrBuildConfigurationGroupFactory; +}; + +class AvrBuildConfigurationGroupFactory final + : public gen::xml::PropertyGroupFactory +{ +public: + bool canCreate(gen::utils::Architecture arch, + const Version &version) const final; + + std::unique_ptr create( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) const final; +}; + +} // namespace v7 +} // namespace avr +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWAVRBUILDCONFIGURATIONGROUP_V7_H diff --git a/src/plugins/generator/iarew/archs/avr/avrcompilersettingsgroup_v7.cpp b/src/plugins/generator/iarew/archs/avr/avrcompilersettingsgroup_v7.cpp new file mode 100644 index 00000000..26b6858f --- /dev/null +++ b/src/plugins/generator/iarew/archs/avr/avrcompilersettingsgroup_v7.cpp @@ -0,0 +1,492 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "avrcompilersettingsgroup_v7.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace avr { +namespace v7 { + +constexpr int kCompilerArchiveVersion = 6; +constexpr int kCompilerDataVersion = 17; + +namespace { + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + moduleType = flags.contains(QLatin1String("--library_module")) + ? OutputPageOptions::LibraryModule + : OutputPageOptions::ProgramModule; + debugInfo = gen::utils::debugInformation(qbsProduct); + disableErrorMessages = flags.contains( + QLatin1String("--no_ubrof_messages")); + } + + int debugInfo = 0; + int disableErrorMessages = 0; + enum ModuleType { ProgramModule, LibraryModule}; + ModuleType moduleType = ProgramModule; +}; + +// Language one page options. + +struct LanguageOnePageOptions final +{ + enum LanguageExtension { + CLanguageExtension, + CxxLanguageExtension, + AutoLanguageExtension + }; + + enum CLanguageDialect { + C89LanguageDialect, + C99LanguageDialect + }; + + enum CxxLanguageDialect { + EmbeddedCPlusPlus, + ExtendedEmbeddedCPlusPlus + }; + + enum LanguageConformance { + AllowIarExtension, + RelaxedStandard, + StrictStandard + }; + + explicit LanguageOnePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + // File extension based by default. + languageExtension = LanguageOnePageOptions::AutoLanguageExtension; + // C language dialect. + const QStringList cLanguageVersion = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("cLanguageVersion")}); + if (cLanguageVersion.contains(QLatin1String("c89"))) + cLanguageDialect = LanguageOnePageOptions::C89LanguageDialect; + else if (cLanguageVersion.contains(QLatin1String("c99"))) + cLanguageDialect = LanguageOnePageOptions::C99LanguageDialect; + // C++ language dialect. + if (flags.contains(QLatin1String("--ec++"))) + cxxLanguageDialect = LanguageOnePageOptions::EmbeddedCPlusPlus; + else if (flags.contains(QLatin1String("--eec++"))) + cxxLanguageDialect = LanguageOnePageOptions::ExtendedEmbeddedCPlusPlus; + // Language conformance. + if (flags.contains(QLatin1String("-e"))) + languageConformance = LanguageOnePageOptions::AllowIarExtension; + else if (flags.contains(QLatin1String("--strict"))) + languageConformance = LanguageOnePageOptions::StrictStandard; + else + languageConformance = LanguageOnePageOptions::RelaxedStandard; + + allowVla = flags.contains(QLatin1String("--vla")); + useCppInlineSemantics = flags.contains( + QLatin1String("--use_c++_inline")); + requirePrototypes = flags.contains( + QLatin1String("--require_prototypes")); + destroyStaticObjects = !flags.contains( + QLatin1String("--no_static_destruction")); + } + + LanguageExtension languageExtension = AutoLanguageExtension; + CLanguageDialect cLanguageDialect = C89LanguageDialect; + CxxLanguageDialect cxxLanguageDialect = EmbeddedCPlusPlus; + LanguageConformance languageConformance = AllowIarExtension; + int allowVla = 0; + int useCppInlineSemantics = 0; + int requirePrototypes = 0; + int destroyStaticObjects = 0; +}; + +// Language two page options. + +struct LanguageTwoPageOptions final +{ + enum PlainCharacter { + SignedCharacter, + UnsignedCharacter + }; + + enum FloatingPointSemantic { + StrictSemantic, + RelaxedSemantic + }; + + explicit LanguageTwoPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + plainCharacter = flags.contains(QLatin1String("--char_is_signed")) + ? LanguageTwoPageOptions::SignedCharacter + : LanguageTwoPageOptions::UnsignedCharacter; + floatingPointSemantic = flags.contains(QLatin1String("--relaxed_fp")) + ? LanguageTwoPageOptions::RelaxedSemantic + : LanguageTwoPageOptions::StrictSemantic; + enableMultibyteSupport = flags.contains( + QLatin1String("--enable_multibytes")); + } + + PlainCharacter plainCharacter = SignedCharacter; + FloatingPointSemantic floatingPointSemantic = StrictSemantic; + int enableMultibyteSupport = 0; +}; + +// Optimizations page options. + +struct OptimizationsPageOptions final +{ + // Optimizations level radio-buttons with + // combo-box on "level" widget. + enum Strategy { + StrategyBalanced, + StrategySize, + StrategySpeed + }; + + enum Level { + LevelNone, + LevelLow, + LevelMedium, + LevelHigh + }; + + enum LevelSlave { + LevelSlave0, + LevelSlave1, + LevelSlave2, + LevelSlave3 + }; + + explicit OptimizationsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString optimization = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("optimization")); + if (optimization == QLatin1String("none")) { + optimizationStrategy = OptimizationsPageOptions::StrategyBalanced; + optimizationLevel = OptimizationsPageOptions::LevelNone; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave0; + } else if (optimization == QLatin1String("fast")) { + optimizationStrategy = OptimizationsPageOptions::StrategySpeed; + optimizationLevel = OptimizationsPageOptions::LevelHigh; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave3; + } else if (optimization == QLatin1String("small")) { + optimizationStrategy = OptimizationsPageOptions::StrategySize; + optimizationLevel = OptimizationsPageOptions::LevelHigh; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave3; + } + + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + enableCommonSubexpressionElimination = !flags.contains( + QLatin1String("--no_cse")); + enableFunctionInlining = !flags.contains(QLatin1String("--no_inline")); + enableCodeMotion = !flags.contains(QLatin1String("--no_code_motion")); + enableCrossCall = !flags.contains(QLatin1String("--no_cross_call")); + enableVariableClustering = !flags.contains( + QLatin1String("--no_clustering")); + enableTypeBasedAliasAnalysis = !flags.contains( + QLatin1String("--no_tbaa")); + enableForceCrossCall = flags.contains( + QLatin1String("--do_cross_call")); + } + + Strategy optimizationStrategy = StrategyBalanced; + Level optimizationLevel = LevelNone; + LevelSlave optimizationLevelSlave = LevelSlave0; + // Six bit-field flags on "enabled optimizations" widget. + int enableCommonSubexpressionElimination = 0; // Common sub-expression elimination. + int enableFunctionInlining = 0; // Function inlining. + int enableCodeMotion = 0; // Code motion. + int enableCrossCall = 0; // Cross call optimization. + int enableVariableClustering = 0; // Variable clustering. + int enableTypeBasedAliasAnalysis = 0; // Type based alias analysis. + // Force cross-call optimization. + int enableForceCrossCall = 0; +}; + +// Preprocessor page options. + +struct PreprocessorPageOptions final +{ + explicit PreprocessorPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + defineSymbols = gen::utils::cppVariantModuleProperties( + qbsProps, {QStringLiteral("defines")}); + + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + const QStringList fullIncludePaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("includePaths"), + QStringLiteral("systemIncludePaths")}); + for (const QString &fullIncludePath : fullIncludePaths) { + const QFileInfo includeFileInfo(fullIncludePath); + const QString includeFilePath = includeFileInfo.absoluteFilePath(); + if (includeFilePath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, includeFilePath); + includePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, includeFilePath); + includePaths.push_back(path); + } + } + } + + QVariantList defineSymbols; + QVariantList includePaths; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + warningsAsErrors = gen::utils::cppIntegerModuleProperty( + qbsProps, QStringLiteral("treatWarningsAsErrors")); + } + + int warningsAsErrors = 0; +}; + +// Code page options. + +struct CodePageOptions final +{ + explicit CodePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + placeConstantsInRam = flags.contains(QLatin1String("-y")); + placeInitializiersInFlash = flags.contains( + QLatin1String("--initializiers_in_flash")); + forceVariablesGeneration = flags.contains( + QLatin1String("--root_variables")); + useIccA90CallingConvention = flags.contains( + QLatin1String("--version1_calls")); + lockRegistersCount = IarewUtils::flagValue( + flags, QStringLiteral("--lock_regs")).toInt(); + } + + int placeConstantsInRam = 0; + int placeInitializiersInFlash = 0; + int forceVariablesGeneration = 0; + int useIccA90CallingConvention = 0; + int lockRegistersCount = 0; +}; + +} // namespace + +// AvrCompilerSettingsGroup + +AvrCompilerSettingsGroup::AvrCompilerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("ICCAVR")); + setArchiveVersion(kCompilerArchiveVersion); + setDataVersion(kCompilerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildOutputPage(qbsProduct); + buildLanguageOnePage(qbsProduct); + buildLanguageTwoPage(qbsProduct); + buildOptimizationsPage(qbsProduct); + buildPreprocessorPage(buildRootDirectory, qbsProduct); + buildDiagnosticsPage(qbsProduct); + buildCodePage(qbsProduct); +} + +void AvrCompilerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + // Add 'CCDebugInfo' item (Generate debug info). + addOptionsGroup(QByteArrayLiteral("CCDebugInfo"), + {opts.debugInfo}); + // Add 'CCNoErrorMsg' item (No error messages in output files). + addOptionsGroup(QByteArrayLiteral("CCNoErrorMsg"), + {opts.disableErrorMessages}); + // Add 'CCOverrideModuleTypeDefault' item + // (Override default module type). + addOptionsGroup(QByteArrayLiteral("CCOverrideModuleTypeDefault"), + {1}); + // Add 'CCRadioModuleType' item (Module type: program/library). + addOptionsGroup(QByteArrayLiteral("CCRadioModuleType"), + {opts.moduleType}); +} + +void AvrCompilerSettingsGroup::buildLanguageOnePage( + const ProductData &qbsProduct) +{ + const LanguageOnePageOptions opts(qbsProduct); + // Add 'IccLang' item with 'auto-extension based' + // value (Language: C/C++/Auto). + addOptionsGroup(QByteArrayLiteral("IccLang"), + {opts.languageExtension}); + // Add 'IccCDialect' item (C dialect: c89/99/11). + addOptionsGroup(QByteArrayLiteral("IccCDialect"), + {opts.cLanguageDialect}); + // Add 'IccCppDialect' item (C++ dialect: embedded/extended). + addOptionsGroup(QByteArrayLiteral("IccCppDialect"), + {opts.cxxLanguageDialect}); + // Add 'CCExt' item (Language conformance: IAR/relaxed/strict). + addOptionsGroup(QByteArrayLiteral("CCExt"), + {opts.languageConformance}); + // Add 'IccAllowVLA' item (Allow VLA). + addOptionsGroup(QByteArrayLiteral("IccAllowVLA"), + {opts.allowVla}); + // Add 'IccCppInlineSemantics' item (C++ inline semantics). + addOptionsGroup(QByteArrayLiteral("IccCppInlineSemantics"), + {opts.useCppInlineSemantics}); + // Add 'CCRequirePrototypes' item (Require prototypes). + addOptionsGroup(QByteArrayLiteral("CCRequirePrototypes"), + {opts.requirePrototypes}); + // Add 'IccStaticDestr' item (Destroy static objects). + addOptionsGroup(QByteArrayLiteral("IccStaticDestr"), + {opts.destroyStaticObjects}); +} + +void AvrCompilerSettingsGroup::buildLanguageTwoPage( + const ProductData &qbsProduct) +{ + const LanguageTwoPageOptions opts(qbsProduct); + // Add 'CCCharIs' item (Plain char is: signed/unsigned). + addOptionsGroup(QByteArrayLiteral("CCCharIs"), + {opts.plainCharacter}); + // Add 'IccFloatSemantics' item (Floatic-point + // semantics: strict/relaxed conformance). + addOptionsGroup(QByteArrayLiteral("IccFloatSemantics"), + {opts.floatingPointSemantic}); + // Add 'CCMultibyteSupport' item (Enable multibyte support). + addOptionsGroup(QByteArrayLiteral("CCMultibyteSupport"), + {opts.enableMultibyteSupport}); +} + +void AvrCompilerSettingsGroup::buildOptimizationsPage( + const ProductData &qbsProduct) +{ + const OptimizationsPageOptions opts(qbsProduct); + // Add 'CCOptStrategy', 'CCOptLevel' and + // 'CCOptLevelSlave' items (Level). + addOptionsGroup(QByteArrayLiteral("CCOptStrategy"), + {opts.optimizationStrategy}); + addOptionsGroup(QByteArrayLiteral("CCOptLevel"), + {opts.optimizationLevel}); + addOptionsGroup(QByteArrayLiteral("CCOptLevelSlave"), + {opts.optimizationLevelSlave}); + // Add 'CCAllowList' item + // (Enabled optimizations: 6 check boxes). + const QString bitflags = QStringLiteral("%1%2%3%4%5%6") + .arg(opts.enableCommonSubexpressionElimination) + .arg(opts.enableFunctionInlining) + .arg(opts.enableCodeMotion) + .arg(opts.enableCrossCall) + .arg(opts.enableVariableClustering) + .arg(opts.enableTypeBasedAliasAnalysis); + addOptionsGroup(QByteArrayLiteral("CCAllowList"), + {bitflags}); + // Add 'CCOptForceCrossCall' item + // (Always do cross call optimization). + addOptionsGroup(QByteArrayLiteral("CCOptForceCrossCall"), + {opts.enableForceCrossCall}); +} + +void AvrCompilerSettingsGroup::buildPreprocessorPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const PreprocessorPageOptions opts(baseDirectory, qbsProduct); + // Add 'CCDefines' item (Defines symbols). + addOptionsGroup(QByteArrayLiteral("CCDefines"), + opts.defineSymbols); + // Add 'newCCIncludePaths' item + // (Additional include directories). + addOptionsGroup(QByteArrayLiteral("newCCIncludePaths"), + opts.includePaths); +} + +void AvrCompilerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'CCWarnAsError' item (Treat all warnings as errors). + addOptionsGroup(QByteArrayLiteral("CCWarnAsError"), + {opts.warningsAsErrors}); +} + +void AvrCompilerSettingsGroup::buildCodePage( + const ProductData &qbsProduct) +{ + const CodePageOptions opts(qbsProduct); + // Add 'CCConstInRAM' item (Place string literals + // and constants in initialized RAM). + addOptionsGroup(QByteArrayLiteral("CCConstInRAM"), + {opts.placeConstantsInRam}); + // Add 'CCInitInFlash' item (Place aggregate + // initializiers in flash memory). + addOptionsGroup(QByteArrayLiteral("CCInitInFlash"), + {opts.placeInitializiersInFlash}); + // Add 'CCForceVariables' item (Force generation of + // all global and static variables). + addOptionsGroup(QByteArrayLiteral("CCForceVariables"), + {opts.forceVariablesGeneration}); + // Add 'CCOldCallConv' item (Use ICCA90 1.x + // calling convention). + addOptionsGroup(QByteArrayLiteral("CCOldCallConv"), + {opts.useIccA90CallingConvention}); + // Add 'CCLockRegs' item (Number of registers to + // lock for global variables). + addOptionsGroup(QByteArrayLiteral("CCLockRegs"), + {opts.lockRegistersCount}); +} + +} // namespace v7 +} // namespace avr +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/avr/avrcompilersettingsgroup_v7.h b/src/plugins/generator/iarew/archs/avr/avrcompilersettingsgroup_v7.h new file mode 100644 index 00000000..2d8c53b8 --- /dev/null +++ b/src/plugins/generator/iarew/archs/avr/avrcompilersettingsgroup_v7.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWAVRCOMPILERSETTINGSGROUP_V7_H +#define QBS_IAREWAVRCOMPILERSETTINGSGROUP_V7_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace avr { +namespace v7 { + +class AvrCompilerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit AvrCompilerSettingsGroup(const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildOutputPage(const ProductData &qbsProduct); + void buildLanguageOnePage(const ProductData &qbsProduct); + void buildLanguageTwoPage(const ProductData &qbsProduct); + void buildOptimizationsPage(const ProductData &qbsProduct); + void buildPreprocessorPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); + void buildCodePage(const ProductData &qbsProduct); +}; + +} // namespace v7 +} // namespace avr +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWAVRCOMPILERSETTINGSGROUP_V7_H diff --git a/src/plugins/generator/iarew/archs/avr/avrgeneralsettingsgroup_v7.cpp b/src/plugins/generator/iarew/archs/avr/avrgeneralsettingsgroup_v7.cpp new file mode 100644 index 00000000..f3fb8647 --- /dev/null +++ b/src/plugins/generator/iarew/archs/avr/avrgeneralsettingsgroup_v7.cpp @@ -0,0 +1,774 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "avrgeneralsettingsgroup_v7.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace avr { +namespace v7 { + +constexpr int kGeneralArchiveVersion = 12; +constexpr int kGeneralDataVersion = 10; + +namespace { + +struct TargetMcuEntry final +{ + QByteArray targetName; + QByteArray targetFlag; +}; + +// Dictionary of known AVR MCU's and its compiler options. +const TargetMcuEntry mcusDict[] = { + {"AT43USB320A", "at43usb320a"}, + {"AT43USB325", "at43usb325"}, + {"AT43USB326", "at43usb326"}, + {"AT43USB351M", "at43usb351m"}, + {"AT43USB353M", "at43usb353m"}, + {"AT43USB355", "at43usb355"}, + {"AT76C712", "at76c712"}, + {"AT76C713", "at76c713"}, + {"AT86RF401", "at86rf401"}, + {"AT90CAN128", "can128"}, + {"AT90CAN32", "can32"}, + {"AT90CAN64", "can64"}, + {"AT90PWM1", "pwm1"}, + {"AT90PWM161", "pwm161"}, + {"AT90PWM2", "pwm2"}, + {"AT90PWM216", "pwm216"}, + {"AT90PWM2B", "pwm2b"}, + {"AT90PWM3", "pwm3"}, + {"AT90PWM316", "pwm316"}, + {"AT90PWM3B", "pwm3b"}, + {"AT90PWM81", "pwm81"}, + {"AT90S1200", "1200"}, + {"AT90S2313", "2313"}, + {"AT90S2323", "2323"}, + {"AT90S2333", "2333"}, + {"AT90S2343", "2343"}, + {"AT90S4414", "4414"}, + {"AT90S4433", "4433"}, + {"AT90S4434", "4434"}, + {"AT90S8515", "8515"}, + {"AT90S8534", "8534"}, + {"AT90S8535", "8535"}, + {"AT90SCR050", "scr050"}, + {"AT90SCR075", "scr075"}, + {"AT90SCR100", "scr100"}, + {"AT90SCR200", "scr200"}, + {"AT90SCR400", "scr400"}, + {"AT90USB128", "usb128"}, + {"AT90USB1286", "usb1286"}, + {"AT90USB1287", "usb1287"}, + {"AT90USB162", "usb162"}, + {"AT90USB64", "usb64"}, + {"AT90USB646", "usb646"}, + {"AT90USB647", "usb647"}, + {"AT90USB82", "usb82"}, + {"AT94Kxx", "at94k"}, + {"ATA5272", "ata5272"}, + {"ATA5505", "ata5505"}, + {"ATA5700M322", "ata5700m322"}, + {"ATA5702M322", "ata5702m322"}, + {"ATA5781", "ata5781"}, + {"ATA5782", "ata5782"}, + {"ATA5783", "ata5783"}, + {"ATA5785", "ata5785"}, + {"ATA5787", "ata5787"}, + {"ATA5790", "ata5790"}, + {"ATA5790N", "ata5790n"}, + {"ATA5795", "ata5795"}, + {"ATA5830", "ata5830"}, + {"ATA5831", "ata5831"}, + {"ATA5832", "ata5832"}, + {"ATA5833", "ata5833"}, + {"ATA5835", "ata5835"}, + {"ATA6285", "ata6285"}, + {"ATA6286", "ata6286"}, + {"ATA6289", "ata6289"}, + {"ATA8210", "ata8210"}, + {"ATA8215", "ata8215"}, + {"ATA8510", "ata8510"}, + {"ATA8515", "ata8515"}, + {"ATmX224E", "mx224e"}, + {"ATmXT112SL", "mxt112sl"}, + {"ATmXT224", "mxt224"}, + {"ATmXT224E", "mxt224e"}, + {"ATmXT336S", "mxt336s"}, + {"ATmXT540S", "mxt540s"}, + {"ATmXT540S_RevA", "mxt540s_reva"}, + {"ATmXTS200", "mxts200"}, + {"ATmXTS220", "mxts220"}, + {"ATmXTS220E", "mxts220e"}, + {"ATmega007", "m007"}, + {"ATmega103", "m103"}, + {"ATmega128", "m128"}, + {"ATmega1280", "m1280"}, + {"ATmega1281", "m1281"}, + {"ATmega1284", "m1284"}, + {"ATmega1284P", "m1284p"}, + {"ATmega1284RFR2", "m1284rfr2"}, + {"ATmega128A", "m128a"}, + {"ATmega128RFA1", "m128rfa1"}, + {"ATmega128RFA2", "m128rfa2"}, + {"ATmega128RFR2", "m128rfr2"}, + {"ATmega16", "m16"}, + {"ATmega1608", "m1608"}, + {"ATmega1609", "m1609"}, + {"ATmega161", "m161"}, + {"ATmega162", "m162"}, + {"ATmega163", "m163"}, + {"ATmega164", "m164"}, + {"ATmega164A", "m164a"}, + {"ATmega164P", "m164p"}, + {"ATmega164PA", "m164pa"}, + {"ATmega165", "m165"}, + {"ATmega165A", "m165a"}, + {"ATmega165P", "m165p"}, + {"ATmega165PA", "m165pa"}, + {"ATmega168", "m168"}, + {"ATmega168A", "m168a"}, + {"ATmega168P", "m168p"}, + {"ATmega168PA", "m168pa"}, + {"ATmega168PB", "m168pb"}, + {"ATmega169", "m169"}, + {"ATmega169A", "m169a"}, + {"ATmega169P", "m169p"}, + {"ATmega169PA", "m169pa"}, + {"ATmega16A", "m16a"}, + {"ATmega16HVA", "m16hva"}, + {"ATmega16HVA2", "m16hva2"}, + {"ATmega16HVB", "m16hvb"}, + {"ATmega16M1", "m16m1"}, + {"ATmega16U2", "m16u2"}, + {"ATmega16U4", "m16u4"}, + {"ATmega2560", "m2560"}, + {"ATmega2561", "m2561"}, + {"ATmega2564RFR2", "m2564rfr2"}, + {"ATmega256RFA2", "m256rfa2"}, + {"ATmega256RFR2", "m256rfr2"}, + {"ATmega26HVG", "m26hvg"}, + {"ATmega32", "m32"}, + {"ATmega3208", "m3208"}, + {"ATmega3209", "m3209"}, + {"ATmega323", "m323"}, + {"ATmega324", "m324"}, + {"ATmega324A", "m324a"}, + {"ATmega324P", "m324p"}, + {"ATmega324PA", "m324pa"}, + {"ATmega324PB", "m324pb"}, + {"ATmega325", "m325"}, + {"ATmega3250", "m3250"}, + {"ATmega3250A", "m3250a"}, + {"ATmega3250P", "m3250p"}, + {"ATmega3250PA", "m3250pa"}, + {"ATmega325A", "m325a"}, + {"ATmega325P", "m325p"}, + {"ATmega325PA", "m325pa"}, + {"ATmega328", "m328"}, + {"ATmega328P", "m328p"}, + {"ATmega328PB", "m328pb"}, + {"ATmega329", "m329"}, + {"ATmega3290", "m3290"}, + {"ATmega3290A", "m3290a"}, + {"ATmega3290P", "m3290p"}, + {"ATmega3290PA", "m3290pa"}, + {"ATmega329A", "m329a"}, + {"ATmega329P", "m329p"}, + {"ATmega329PA", "m329pa"}, + {"ATmega32A", "m32a"}, + {"ATmega32C1", "m32c1"}, + {"ATmega32HVB", "m32hvb"}, + {"ATmega32M1", "m32m1"}, + {"ATmega32U2", "m32u2"}, + {"ATmega32U4", "m32u4"}, + {"ATmega32U6", "m32u6"}, + {"ATmega406", "m406"}, + {"ATmega48", "m48"}, + {"ATmega4808", "m4808"}, + {"ATmega4809", "m4809"}, + {"ATmega48A", "m48a"}, + {"ATmega48HVF", "m48hvf"}, + {"ATmega48P", "m48p"}, + {"ATmega48PA", "m48pa"}, + {"ATmega48PB", "m48pb"}, + {"ATmega4HVD", "m4hvd"}, + {"ATmega603", "m603"}, + {"ATmega64", "m64"}, + {"ATmega640", "m640"}, + {"ATmega644", "m644"}, + {"ATmega644A", "m644a"}, + {"ATmega644P", "m644p"}, + {"ATmega644PA", "m644pa"}, + {"ATmega644RFR2", "m644rfr2"}, + {"ATmega645", "m645"}, + {"ATmega6450", "m6450"}, + {"ATmega6450A", "m6450a"}, + {"ATmega6450P", "m6450p"}, + {"ATmega645A", "m645a"}, + {"ATmega645P", "m645p"}, + {"ATmega649", "m649"}, + {"ATmega6490", "m6490"}, + {"ATmega6490A", "m6490a"}, + {"ATmega6490P", "m6490p"}, + {"ATmega649A", "m649a"}, + {"ATmega649P", "m649p"}, + {"ATmega64A", "m64a"}, + {"ATmega64C1", "m64c1"}, + {"ATmega64HVE", "m256rfa2"}, + {"ATmega64HVE", "m64hve"}, + {"ATmega64HVE2", "m64hve2"}, + {"ATmega64M1", "m64m1"}, + {"ATmega64RFA2", "m64rfa2"}, + {"ATmega64RFR2", "m64rfr2"}, + {"ATmega8", "m8"}, + {"ATmega808", "m808"}, + {"ATmega809", "m809"}, + {"ATmega83", "m83"}, + {"ATmega8515", "m8515"}, + {"ATmega8535", "m8535"}, + {"ATmega88", "m88"}, + {"ATmega88A", "m88a"}, + {"ATmega88P", "m88p"}, + {"ATmega88PA", "m88pa"}, + {"ATmega88PB", "m88pb"}, + {"ATmega8A", "m8a"}, + {"ATmega8HVA", "m8hva"}, + {"ATmega8HVD", "m8hvd"}, + {"ATmega8U2", "m8u2"}, + {"ATtiny10", "tiny10"}, + {"ATtiny102", "tiny102"}, + {"ATtiny104", "tiny104"}, + {"ATtiny11", "tiny11"}, + {"ATtiny12", "tiny12"}, + {"ATtiny13", "tiny13"}, + {"ATtiny13A", "tiny13a"}, + {"ATtiny15", "tiny15"}, + {"ATtiny1604", "tiny1604"}, + {"ATtiny1606", "tiny1606"}, + {"ATtiny1607", "tiny1607"}, + {"ATtiny1614", "tiny1614"}, + {"ATtiny1616", "tiny1616"}, + {"ATtiny1617", "tiny1617"}, + {"ATtiny1634", "tiny1634"}, + {"ATtiny167", "tiny167"}, + {"ATtiny20", "tiny20"}, + {"ATtiny202", "tiny202"}, + {"ATtiny204", "tiny204"}, + {"ATtiny212", "tiny212"}, + {"ATtiny214", "tiny214"}, + {"ATtiny22", "tiny22"}, + {"ATtiny2313", "tiny2313"}, + {"ATtiny2313A", "tiny2313a"}, + {"ATtiny23U", "tiny23u"}, + {"ATtiny24", "tiny24"}, + {"ATtiny24A", "tiny24a"}, + {"ATtiny25", "tiny25"}, + {"ATtiny26", "tiny26"}, + {"ATtiny261", "tiny261"}, + {"ATtiny261A", "tiny261a"}, + {"ATtiny28", "tiny28"}, + {"ATtiny3214", "tiny3214"}, + {"ATtiny3216", "tiny3216"}, + {"ATtiny3217", "tiny3217"}, + {"ATtiny4", "tiny4"}, + {"ATtiny40", "tiny40"}, + {"ATtiny402", "tiny402"}, + {"ATtiny404", "tiny404"}, + {"ATtiny406", "tiny406"}, + {"ATtiny412", "tiny412"}, + {"ATtiny414", "tiny414"}, + {"ATtiny416", "tiny416"}, + {"ATtiny417", "tiny417"}, + {"ATtiny4313", "tiny4313"}, + {"ATtiny43U", "tiny43u"}, + {"ATtiny44", "tiny44"}, + {"ATtiny441", "tiny441"}, + {"ATtiny44A", "tiny44a"}, + {"ATtiny45", "tiny45"}, + {"ATtiny461", "tiny461"}, + {"ATtiny461A", "tiny461a"}, + {"ATtiny474", "tiny474"}, + {"ATtiny48", "tiny48"}, + {"ATtiny5", "tiny5"}, + {"ATtiny80", "tiny80"}, + {"ATtiny804", "tiny804"}, + {"ATtiny806", "tiny806"}, + {"ATtiny807", "tiny807"}, + {"ATtiny80_pre_2015", "tiny80_pre_2015"}, + {"ATtiny814", "tiny814"}, + {"ATtiny816", "tiny816"}, + {"ATtiny817", "tiny817"}, + {"ATtiny828", "tiny828"}, + {"ATtiny84", "tiny84"}, + {"ATtiny840", "tiny840"}, + {"ATtiny841", "tiny841"}, + {"ATtiny84A", "tiny84a"}, + {"ATtiny85", "tiny85"}, + {"ATtiny861", "tiny861"}, + {"ATtiny861A", "tiny861a"}, + {"ATtiny87", "tiny87"}, + {"ATtiny88", "tiny88"}, + {"ATtiny9", "tiny9"}, + {"ATxmega128A1", "xm128a1"}, + {"ATxmega128A1U", "xm128a1u"}, + {"ATxmega128A3", "xm128a3"}, + {"ATxmega128A3U", "xm128a3u"}, + {"ATxmega128A4", "xm128a4"}, + {"ATxmega128A4U", "xm128a4u"}, + {"ATxmega128B1", "xm128b1"}, + {"ATxmega128B3", "xm128b3"}, + {"ATxmega128C3", "xm128c3"}, + {"ATxmega128D3", "xm128d3"}, + {"ATxmega128D4", "xm128d4"}, + {"ATxmega16A4", "xm16a4"}, + {"ATxmega16A4U", "xm16a4u"}, + {"ATxmega16C4", "xm16c4"}, + {"ATxmega16D4", "xm16d4"}, + {"ATxmega16E5", "xm16e5"}, + {"ATxmega192A1", "xm192a1"}, + {"ATxmega192A3", "xm192a3"}, + {"ATxmega192A3U", "xm192a3u"}, + {"ATxmega192C3", "xm192c3"}, + {"ATxmega192D3", "xm192d3"}, + {"ATxmega256A1", "xm256a1"}, + {"ATxmega256A3", "xm256a3"}, + {"ATxmega256A3B", "xm256a3b"}, + {"ATxmega256A3BU", "xm256a3bu"}, + {"ATxmega256A3U", "xm256a3u"}, + {"ATxmega256B1", "xm256b1"}, + {"ATxmega256C3", "xm256c3"}, + {"ATxmega256D3", "xm256d3"}, + {"ATxmega32A4", "xm32a4"}, + {"ATxmega32A4U", "xm32a4u"}, + {"ATxmega32C3", "xm32c3"}, + {"ATxmega32C4", "xm32c4"}, + {"ATxmega32D3", "xm32d3"}, + {"ATxmega32D4", "xm32d4"}, + {"ATxmega32D4P", "xm32d4p"}, + {"ATxmega32E5", "xm32e5"}, + {"ATxmega32X1", "xm32x1"}, + {"ATxmega384A1", "xm384a1"}, + {"ATxmega384C3", "xm384c3"}, + {"ATxmega384D3", "xm384d3"}, + {"ATxmega64A1", "xm64a1"}, + {"ATxmega64A1U", "xm64a1u"}, + {"ATxmega64A3", "xm64a3"}, + {"ATxmega64A3U", "xm64a3u"}, + {"ATxmega64A4", "xm64a4"}, + {"ATxmega64A4U", "xm64a4u"}, + {"ATxmega64B1", "xm64b1"}, + {"ATxmega64B3", "xm64b3"}, + {"ATxmega64C3", "xm64c3"}, + {"ATxmega64D3", "xm64d3"}, + {"ATxmega64D4", "xm64d4"}, + {"ATxmega8E5", "xm8e5"}, + {"M3000", "m3000"}, + {"MaxBSE", "maxbse"}, +}; + +// Target page options. + +struct TargetPageOptions final +{ + enum MemoryModel { + TinyModel, + SmallModel, + LargeModel, + HugeModel + }; + + explicit TargetPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("driverFlags")}); + // Detect target MCU record. + const QString mcuValue = IarewUtils::flagValue( + flags, QStringLiteral("--cpu")).toLower(); + targetMcu = mcuStringFromFlagValue(mcuValue); + // Detect target memory model. + const QString modelValue = IarewUtils::flagValue( + flags, QStringLiteral("-m")); + if (modelValue == QLatin1Char('t')) + memoryModel = TargetPageOptions::TinyModel; + else if (modelValue == QLatin1Char('s')) + memoryModel = TargetPageOptions::SmallModel; + else if (modelValue == QLatin1Char('l')) + memoryModel = TargetPageOptions::LargeModel; + else if (modelValue == QLatin1Char('h')) + memoryModel = TargetPageOptions::HugeModel; + // Detect target EEPROM util size. + eepromUtilSize = IarewUtils::flagValue( + flags, QStringLiteral("--eeprom_size")).toInt(); + } + + static QString mcuStringFromFlagValue(const QString &mcuValue) + { + const auto targetBegin = std::cbegin(mcusDict); + const auto targetEnd = std::cend(mcusDict); + const auto targetIt = std::find_if(targetBegin, targetEnd, + [mcuValue]( + const TargetMcuEntry &entry) { + return entry.targetFlag == mcuValue.toLatin1(); + }); + if (targetIt != targetEnd) { + return QStringLiteral("%1\t%2") + .arg(QString::fromLatin1(targetIt->targetFlag), + QString::fromLatin1(targetIt->targetName)); + } + return {}; + } + + QString targetMcu; + MemoryModel memoryModel = TinyModel; + int eepromUtilSize = 0; +}; + +// System page options. + +struct SystemPageOptions final +{ + explicit SystemPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("driverLinkerFlags"), + QStringLiteral("defines")}); + cstackSize = IarewUtils::flagValue( + flags, QStringLiteral("_..X_CSTACK_SIZE")).toInt(); + rstackSize = IarewUtils::flagValue( + flags, QStringLiteral("_..X_RSTACK_SIZE")).toInt(); + } + + int cstackSize = 0; + int rstackSize = 0; +}; + +// Library options page options. + +struct LibraryOptionsPageOptions final +{ + enum PrintfFormatter { + PrintfAutoFormatter = 0, + PrintfFullFormatter = 1, + PrintfFullNoMultibytesFormatter = 2, + PrintfLargeFormatter = 3, + PrintfLargeNoMultibytesFormatter = 4, + PrintfSmallFormatter = 6, + PrintfSmallNoMultibytesFormatter = 7, + PrintfTinyFormatter = 8 + }; + + enum ScanfFormatter { + ScanfAutoFormatter = 0, + ScanfFullFormatter = 1, + ScanfFullNoMultibytesFormatter = 2, + ScanfLargeFormatter = 3, + ScanfLargeNoMultibytesFormatter = 4, + ScanfSmallFormatter = 6, + ScanfSmallNoMultibytesFormatter = 7 + }; + + explicit LibraryOptionsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + for (const QString &flag : flags) { + if (flag.endsWith(QLatin1String("_printf"), Qt::CaseInsensitive)) { + const QString prop = flag.split(QLatin1Char('=')).at(0).toLower(); + if (prop == QLatin1String("-e_printffull")) + printfFormatter = LibraryOptionsPageOptions::PrintfFullFormatter; + else if (prop == QLatin1String("-e_printffullnomb")) + printfFormatter = LibraryOptionsPageOptions::PrintfFullNoMultibytesFormatter; + else if (prop == QLatin1String("-e_printflarge")) + printfFormatter = LibraryOptionsPageOptions::PrintfLargeFormatter; + else if (prop == QLatin1String("-e_printflargenomb")) + printfFormatter = LibraryOptionsPageOptions::PrintfLargeNoMultibytesFormatter; + else if (prop == QLatin1String("-e_printfsmall")) + printfFormatter = LibraryOptionsPageOptions::PrintfSmallFormatter; + else if (prop == QLatin1String("-e_printfsmallnomb")) + printfFormatter = LibraryOptionsPageOptions::PrintfSmallNoMultibytesFormatter; + else if (prop == QLatin1String("-printftiny")) + printfFormatter = LibraryOptionsPageOptions::PrintfTinyFormatter; + } else if (flag.endsWith(QLatin1String("_scanf"), Qt::CaseInsensitive)) { + const QString prop = flag.split(QLatin1Char('=')).at(0).toLower(); + if (prop == QLatin1String("-e_scanffull")) + scanfFormatter = LibraryOptionsPageOptions::ScanfFullFormatter; + else if (prop == QLatin1String("-e_scanffullnomb")) + scanfFormatter = LibraryOptionsPageOptions::ScanfFullNoMultibytesFormatter; + else if (prop == QLatin1String("-e_scanflarge")) + scanfFormatter = LibraryOptionsPageOptions::ScanfLargeFormatter; + else if (prop == QLatin1String("-e_scanflargenomb")) + scanfFormatter = LibraryOptionsPageOptions::ScanfLargeNoMultibytesFormatter; + else if (prop == QLatin1String("-e_scanfsmall")) + scanfFormatter = LibraryOptionsPageOptions::ScanfSmallFormatter; + else if (prop == QLatin1String("-e_scanfsmallnomb")) + scanfFormatter = LibraryOptionsPageOptions::ScanfSmallNoMultibytesFormatter; + } + } + } + + PrintfFormatter printfFormatter = PrintfAutoFormatter; + ScanfFormatter scanfFormatter = ScanfAutoFormatter; +}; + +// Library configuration page options. + +struct LibraryConfigPageOptions final +{ + enum RuntimeLibrary { + NoLibrary, + NormalDlibLibrary, + FullDlibLibrary, + CustomDlibLibrary, + ClibLibrary, + CustomClibLibrary, + ThirdPartyLibrary + }; + + explicit LibraryConfigPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + + const QStringList libraryPaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("staticLibraries")}); + const auto libraryBegin = libraryPaths.cbegin(); + const auto libraryEnd = libraryPaths.cend(); + + if (flags.contains(QLatin1String("--dlib"))) { + const QString dlibToolkitPath = + IarewUtils::dlibToolkitRootPath(qbsProduct); + const QFileInfo configInfo(IarewUtils::flagValue( + flags, + QStringLiteral("--dlib_config"))); + const QString configFilePath = configInfo.absoluteFilePath(); + if (configFilePath.startsWith(dlibToolkitPath, + Qt::CaseInsensitive)) { + if (configFilePath.endsWith(QLatin1String("-n.h"), + Qt::CaseInsensitive)) { + libraryType = LibraryConfigPageOptions::NormalDlibLibrary; + } else if (configFilePath.endsWith(QLatin1String("-f.h"), + Qt::CaseInsensitive)) { + libraryType = LibraryConfigPageOptions::FullDlibLibrary; + } else { + libraryType = LibraryConfigPageOptions::CustomDlibLibrary; + } + + configPath = IarewUtils::toolkitRelativeFilePath( + baseDirectory, configFilePath); + + // Find dlib library inside of IAR toolkit directory. + const auto libraryIt = std::find_if(libraryBegin, libraryEnd, + [dlibToolkitPath]( + const QString &libraryPath) { + return libraryPath.startsWith(dlibToolkitPath); + }); + if (libraryIt != libraryEnd) { + // This means that dlib library is 'standard' (placed inside + // of IAR toolkit directory). + libraryPath = IarewUtils::toolkitRelativeFilePath( + baseDirectory, *libraryIt); + } + } else { + // This means that dlib library is 'custom' + // (but we don't know its path). + libraryType = LibraryConfigPageOptions::CustomDlibLibrary; + configPath = IarewUtils::projectRelativeFilePath( + baseDirectory, configFilePath); + } + } else if (flags.contains(QLatin1String("--clib"))) { + const QString clibToolkitPath = + IarewUtils::clibToolkitRootPath(qbsProduct); + // Find clib library inside of IAR toolkit directory. + const auto libraryIt = std::find_if(libraryBegin, libraryEnd, + [clibToolkitPath]( + const QString &libraryPath) { + return libraryPath.startsWith(clibToolkitPath); + }); + if (libraryIt != libraryEnd) { + // This means that clib library is 'standard' (placed inside + // of IAR toolkit directory). + libraryType = LibraryConfigPageOptions::ClibLibrary; + libraryPath = IarewUtils::toolkitRelativeFilePath( + baseDirectory, *libraryIt); + } else { + // This means that clib library is 'custom' + // (but we don't know its path). + libraryType = LibraryConfigPageOptions::CustomClibLibrary; + } + } else { + libraryType = LibraryConfigPageOptions::NoLibrary; + } + } + + RuntimeLibrary libraryType = NoLibrary; + QString configPath; + QString libraryPath; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + binaryType = IarewUtils::outputBinaryType(qbsProduct); + binaryDirectory = gen::utils::binaryOutputDirectory( + baseDirectory, qbsProduct); + objectDirectory = gen::utils::objectsOutputDirectory( + baseDirectory, qbsProduct); + listingDirectory = gen::utils::listingOutputDirectory( + baseDirectory, qbsProduct); + } + + IarewUtils::OutputBinaryType binaryType = IarewUtils::ApplicationOutputType; + QString binaryDirectory; + QString objectDirectory; + QString listingDirectory; +}; + +} // namespace + +// AvrGeneralSettingsGroup + +AvrGeneralSettingsGroup::AvrGeneralSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("General")); + setArchiveVersion(kGeneralArchiveVersion); + setDataVersion(kGeneralDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildTargetPage(qbsProduct); + buildSystemPage(qbsProduct); + buildLibraryOptionsPage(qbsProduct); + buildLibraryConfigPage(buildRootDirectory, qbsProduct); + buildOutputPage(buildRootDirectory, qbsProduct); +} + +void AvrGeneralSettingsGroup::buildTargetPage( + const ProductData &qbsProduct) +{ + const TargetPageOptions opts(qbsProduct); + // Add 'GenDeviceSelectMenu' item + // (Processor configuration chooser). + addOptionsGroup(QByteArrayLiteral("GenDeviceSelectMenu"), + {opts.targetMcu}); + // Add 'Variant Memory' item + // (Memory model: tiny/small/large/huge). + addOptionsGroup(QByteArrayLiteral("Variant Memory"), + {opts.memoryModel}); + // Add 'GGEepromUtilSize' item + // (Utilize inbuilt EEPROM size, in bytes). + addOptionsGroup(QByteArrayLiteral("GGEepromUtilSize"), + {opts.eepromUtilSize}); +} + +void AvrGeneralSettingsGroup::buildSystemPage( + const ProductData &qbsProduct) +{ + const SystemPageOptions opts (qbsProduct); + // Add 'SCCStackSize' item (Data stack + // - CSTACK size in bytes). + addOptionsGroup(QByteArrayLiteral("SCCStackSize"), + {opts.cstackSize}); + // Add 'SCRStackSize' item (Return address stack + // - RSTACK depth in bytes). + addOptionsGroup(QByteArrayLiteral("SCRStackSize"), + {opts.rstackSize}); +} + +void AvrGeneralSettingsGroup::buildLibraryOptionsPage( + const ProductData &qbsProduct) +{ + const LibraryOptionsPageOptions opts(qbsProduct); + // Add 'Output variant' item (Printf formatter). + addOptionsGroup(QByteArrayLiteral("Output variant"), + {opts.printfFormatter}); + // Add 'Input variant' item (Printf formatter). + addOptionsGroup(QByteArrayLiteral("Input variant"), + {opts.scanfFormatter}); +} + +void AvrGeneralSettingsGroup::buildLibraryConfigPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const LibraryConfigPageOptions opts(baseDirectory, qbsProduct); + // Add 'GRuntimeLibSelect' and 'GRuntimeLibSelectSlave' items + // (Link with runtime: none/dlib/clib/etc). + addOptionsGroup(QByteArrayLiteral("GRuntimeLibSelect"), + {opts.libraryType}); + addOptionsGroup(QByteArrayLiteral("GRuntimeLibSelectSlave"), + {opts.libraryType}); + // Add 'RTConfigPath' item (Runtime configuration file). + addOptionsGroup(QByteArrayLiteral("RTConfigPath"), + {opts.configPath}); + // Add 'RTLibraryPath' item (Runtime library file). + addOptionsGroup(QByteArrayLiteral("RTLibraryPath"), + {opts.libraryPath}); +} + +void AvrGeneralSettingsGroup::buildOutputPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(baseDirectory, qbsProduct); + // Add 'GOutputBinary' item (Output file: executable/library). + addOptionsGroup(QByteArrayLiteral("GOutputBinary"), + {opts.binaryType}); + // Add 'ExePath' item (Executable/binaries output directory). + addOptionsGroup(QByteArrayLiteral("ExePath"), + {opts.binaryDirectory}); + // Add 'ObjPath' item (Object files output directory). + addOptionsGroup(QByteArrayLiteral("ObjPath"), + {opts.objectDirectory}); + // Add 'ListPath' item (List files output directory). + addOptionsGroup(QByteArrayLiteral("ListPath"), + {opts.listingDirectory}); +} + +} // namespace v7 +} // namespace avr +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/avr/avrgeneralsettingsgroup_v7.h b/src/plugins/generator/iarew/archs/avr/avrgeneralsettingsgroup_v7.h new file mode 100644 index 00000000..5411eeae --- /dev/null +++ b/src/plugins/generator/iarew/archs/avr/avrgeneralsettingsgroup_v7.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWAVRGENERALSETTINGSGROUP_V7_H +#define QBS_IAREWAVRGENERALSETTINGSGROUP_V7_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace avr { +namespace v7 { + +class AvrGeneralSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit AvrGeneralSettingsGroup(const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildTargetPage(const ProductData &qbsProduct); + void buildSystemPage(const ProductData &qbsProduct); + void buildLibraryOptionsPage(const ProductData &qbsProduct); + void buildLibraryConfigPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildOutputPage(const QString &baseDirectory, + const ProductData &qbsProduct); +}; + +} // namespace v7 +} // namespace avr +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWAVRGENERALSETTINGSGROUP_V7_H diff --git a/src/plugins/generator/iarew/archs/avr/avrlinkersettingsgroup_v7.cpp b/src/plugins/generator/iarew/archs/avr/avrlinkersettingsgroup_v7.cpp new file mode 100644 index 00000000..0af4f088 --- /dev/null +++ b/src/plugins/generator/iarew/archs/avr/avrlinkersettingsgroup_v7.cpp @@ -0,0 +1,387 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "avrlinkersettingsgroup_v7.h" + +#include "../../iarewutils.h" + +#include + +namespace qbs { +namespace iarew { +namespace avr { +namespace v7 { + +constexpr int kLinkerArchiveVersion = 3; +constexpr int kLinkerDataVersion = 16; + +namespace { + +// Config page options. + +struct ConfigPageOptions final +{ + explicit ConfigPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + + entryPoint = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("entryPoint")); + + // Enumerate all product linker config files + // (which are set trough 'linkerscript' tag). + for (const auto &qbsGroup : qbsProduct.groups()) { + const auto qbsArtifacts = qbsGroup.sourceArtifacts(); + for (const auto &qbsArtifact : qbsArtifacts) { + const auto qbsTags = qbsArtifact.fileTags(); + if (!qbsTags.contains(QLatin1String("linkerscript"))) + continue; + const QString fullConfigPath = qbsArtifact.filePath(); + if (fullConfigPath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullConfigPath); + configFilePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullConfigPath); + configFilePaths.push_back(path); + } + } + } + + // Enumerate all product linker config files + // (which are set trough '-f' option). + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + const QVariantList configPathValues = IarewUtils::flagValues( + flags, QStringLiteral("-f")); + for (const QVariant &configPathValue : configPathValues) { + const QString fullConfigPath = configPathValue.toString(); + if (fullConfigPath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullConfigPath); + if (!configFilePaths.contains(path)) + configFilePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullConfigPath); + if (!configFilePaths.contains(path)) + configFilePaths.push_back(path); + } + } + + // Add libraries search paths. + const QStringList libraryPaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("libraryPaths")}); + for (const QString &libraryPath : libraryPaths) { + const QFileInfo libraryPathInfo(libraryPath); + const QString fullLibrarySearchPath = + libraryPathInfo.absoluteFilePath(); + if (fullLibrarySearchPath.startsWith(toolkitPath, + Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullLibrarySearchPath); + librarySearchPaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullLibrarySearchPath); + librarySearchPaths.push_back(path); + } + } + + // Add static libraries paths. + const QStringList staticLibrariesProps = + gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("staticLibraries")}); + for (const QString &staticLibrary : staticLibrariesProps) { + const QFileInfo staticLibraryInfo(staticLibrary); + if (staticLibraryInfo.isAbsolute()) { + const QString fullStaticLibraryPath = + staticLibraryInfo.absoluteFilePath(); + if (fullStaticLibraryPath.startsWith(toolkitPath, + Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullStaticLibraryPath); + staticLibraries.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullStaticLibraryPath); + staticLibraries.push_back(path); + } + } else { + staticLibraries.push_back(staticLibrary); + } + } + + // Add static libraries from product dependencies. + for (const ProductData &qbsProductDep : qbsProductDeps) { + const QString depBinaryPath = QLatin1String("$PROJ_DIR$/") + + gen::utils::targetBinaryPath(baseDirectory, + qbsProductDep); + staticLibraries.push_back(depBinaryPath); + } + } + + QVariantList configFilePaths; + QVariantList librarySearchPaths; + QVariantList staticLibraries; + QString entryPoint; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + outputFile = gen::utils::targetBinary(qbsProduct); + } + + QString outputFile; +}; + +// List page options. + +struct ListPageOptions final +{ + enum ListingAction { + NoListing, + GenerateListing + }; + + explicit ListPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + generateMap = gen::utils::cppBooleanModuleProperty( + qbsProps, QStringLiteral("generateLinkerMapFile")) + ? ListPageOptions::GenerateListing + : ListPageOptions::NoListing; + } + + ListingAction generateMap = NoListing; +}; + +// Define page options. + +struct DefinePageOptions final +{ + explicit DefinePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + // Enumerate all linker defines. + for (const QString &flag : flags) { + if (!flag.startsWith(QLatin1String("-D"))) + continue; + const auto symbol = flag.mid(2); + // Ignore system-defined macroses. + if (symbol.startsWith(QLatin1String("_..X_HEAP_SIZE")) + || symbol.startsWith(QLatin1String("_..X_TINY_HEAP_SIZE")) + || symbol.startsWith(QLatin1String("_..X_NEAR_HEAP_SIZE")) + || symbol.startsWith(QLatin1String("_..X_FAR_HEAP_SIZE")) + || symbol.startsWith(QLatin1String("_..X_HUGE_HEAP_SIZE")) + || symbol.startsWith(QLatin1String("_..X_CSTACK_SIZE")) + || symbol.startsWith(QLatin1String("_..X_RSTACK_SIZE")) + || symbol.startsWith(QLatin1String("_..X_FLASH_CODE_END")) + || symbol.startsWith(QLatin1String("_..X_FLASH_BASE")) + || symbol.startsWith(QLatin1String("_..X_CSTACK_BASE")) + || symbol.startsWith(QLatin1String("_..X_CSTACK_END")) + || symbol.startsWith(QLatin1String("_..X_RSTACK_BASE")) + || symbol.startsWith(QLatin1String("_..X_RSTACK_END")) + || symbol.startsWith(QLatin1String("_..X_EXT_SRAM_BASE")) + || symbol.startsWith(QLatin1String("_..X_EXT_SRAM_SIZE")) + || symbol.startsWith(QLatin1String("_..X_EXT_ROM_BASE")) + || symbol.startsWith(QLatin1String("_..X_EXT_ROM_SIZE")) + || symbol.startsWith(QLatin1String("_..X_EXT_NV_BASE")) + || symbol.startsWith(QLatin1String("_..X_EXT_NV_SIZE")) + || symbol.startsWith(QLatin1String("_..X_SRAM_BASE")) + || symbol.startsWith(QLatin1String("_..X_SRAM_SIZE")) + || symbol.startsWith(QLatin1String("_..X_RSTACK_BASE")) + || symbol.startsWith(QLatin1String("_..X_RSTACK_SIZE")) + ) { + continue; + } + defineSymbols.push_back(symbol); + } + } + + QVariantList defineSymbols; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString warningLevel = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("warningLevel")); + suppressAllWarnings = (warningLevel == QLatin1String("none")); + } + + int suppressAllWarnings = 0; +}; + +} // namespace + +// AvrLinkerSettingsGroup + +AvrLinkerSettingsGroup::AvrLinkerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + setName(QByteArrayLiteral("XLINK")); + setArchiveVersion(kLinkerArchiveVersion); + setDataVersion(kLinkerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildConfigPage(buildRootDirectory, qbsProduct, qbsProductDeps); + buildOutputPage(qbsProduct); + buildListPage(qbsProduct); + buildDefinePage(qbsProduct); + buildDiagnosticsPage(qbsProduct); + + // Should be called as latest stage! + buildExtraOptionsPage(qbsProduct); +} + +void AvrLinkerSettingsGroup::buildConfigPage( + const QString &baseDirectory, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + ConfigPageOptions opts(baseDirectory, qbsProduct, qbsProductDeps); + + if (opts.configFilePaths.count() > 0) { + // Note: IAR IDE does not allow to specify a multiple config files, + // although the IAR linker support it. So, we use followig 'trick': + // we take a first config file and to add it as usual to required items; + // and then an other remainders we forward to the "Extra options page". + const QVariant configPath = opts.configFilePaths.takeFirst(); + // Add 'XclOverride' item (Override default). + addOptionsGroup(QByteArrayLiteral("XclOverride"), + {1}); + // Add 'XclFile' item (Linke configuration file). + addOptionsGroup(QByteArrayLiteral("XclFile"), + {configPath}); + + // Add remainder configuration files to the "Extra options page". + if (!opts.configFilePaths.isEmpty()) { + for (QVariant &configPath : opts.configFilePaths) + configPath = QLatin1String("-f ") + configPath.toString(); + + m_extraOptions << opts.configFilePaths; + } + } + + if (opts.staticLibraries.count() > 0) + m_extraOptions << opts.staticLibraries; + + if (!opts.entryPoint.isEmpty()) { + // Add 'xcProgramEntryLabel' item (Entry symbol). + addOptionsGroup(QByteArrayLiteral("xcProgramEntryLabel"), + {opts.entryPoint}); + // Add 'xcOverrideProgramEntryLabel' item + // (Override default program entry). + addOptionsGroup(QByteArrayLiteral("xcOverrideProgramEntryLabel"), + {1}); + // Add 'xcProgramEntryLabelSelect' item. + addOptionsGroup(QByteArrayLiteral("xcProgramEntryLabelSelect"), + {0}); + } + + // Add 'XIncludes' item (Libraries search paths). + addOptionsGroup(QByteArrayLiteral("XIncludes"), + opts.librarySearchPaths); +} + +void AvrLinkerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + // Add 'XOutOverride' item (Override default output file). + addOptionsGroup(QByteArrayLiteral("XOutOverride"), + {1}); + // Add 'OutputFile' item (Output file name). + addOptionsGroup(QByteArrayLiteral("OutputFile"), + {opts.outputFile}); +} + +void AvrLinkerSettingsGroup::buildListPage( + const ProductData &qbsProduct) +{ + const ListPageOptions opts(qbsProduct); + // Add 'XList' item (Generate linker listing). + addOptionsGroup(QByteArrayLiteral("XList"), + {opts.generateMap}); +} + +void AvrLinkerSettingsGroup::buildDefinePage( + const ProductData &qbsProduct) +{ + const DefinePageOptions opts(qbsProduct); + // Add 'XDefines' item (Defined symbols). + addOptionsGroup(QByteArrayLiteral("XDefines"), + opts.defineSymbols); +} + +void AvrLinkerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'SuppressAllWarn' item (Suppress all warnings). + addOptionsGroup(QByteArrayLiteral("SuppressAllWarn"), + {opts.suppressAllWarnings}); +} + +void AvrLinkerSettingsGroup::buildExtraOptionsPage(const ProductData &qbsProduct) +{ + Q_UNUSED(qbsProduct) + + if (!m_extraOptions.isEmpty()) { + // Add 'XExtraOptionsCheck' (Use command line options). + addOptionsGroup(QByteArrayLiteral("XExtraOptionsCheck"), + {1}); + // Add 'XExtraOptions' item (Command line options). + addOptionsGroup(QByteArrayLiteral("XExtraOptions"), + m_extraOptions); + } +} + +} // namespace v7 +} // namespace avr +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/avr/avrlinkersettingsgroup_v7.h b/src/plugins/generator/iarew/archs/avr/avrlinkersettingsgroup_v7.h new file mode 100644 index 00000000..5427937b --- /dev/null +++ b/src/plugins/generator/iarew/archs/avr/avrlinkersettingsgroup_v7.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWAVRLINKERSETTINGSGROUP_V7_H +#define QBS_IAREWAVRLINKERSETTINGSGROUP_V7_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace avr { +namespace v7 { + +class AvrLinkerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit AvrLinkerSettingsGroup(const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildConfigPage(const QString &baseDirectory, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + void buildOutputPage(const ProductData &qbsProduct); + void buildListPage(const ProductData &qbsProduct); + void buildDefinePage(const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); + void buildExtraOptionsPage(const ProductData &qbsProduct); + + QVariantList m_extraOptions; +}; + +} // namespace v7 +} // namespace avr +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWAVRLINKERSETTINGSGROUP_V7_H diff --git a/src/plugins/generator/iarew/archs/mcs51/mcs51archiversettingsgroup_v10.cpp b/src/plugins/generator/iarew/archs/mcs51/mcs51archiversettingsgroup_v10.cpp new file mode 100644 index 00000000..38264c65 --- /dev/null +++ b/src/plugins/generator/iarew/archs/mcs51/mcs51archiversettingsgroup_v10.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51archiversettingsgroup_v10.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace mcs51 { +namespace v10 { + +constexpr int kArchiverArchiveVersion = 2; +constexpr int kArchiverDataVersion = 1; + +namespace { + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + outputFile = QLatin1String("$PROJ_DIR$/") + + gen::utils::targetBinaryPath(baseDirectory, qbsProduct); + } + + QString outputFile; +}; + +} // namespace + +// Mcs51ArchiverSettingsGroup + +Mcs51ArchiverSettingsGroup::Mcs51ArchiverSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("XAR")); + setArchiveVersion(kArchiverArchiveVersion); + setDataVersion(kArchiverDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + buildOutputPage(buildRootDirectory, qbsProduct); +} + +void Mcs51ArchiverSettingsGroup::buildOutputPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(baseDirectory, qbsProduct); + // Add 'XAROverride' item (Override default). + addOptionsGroup(QByteArrayLiteral("XAROverride"), + {1}); + // Add 'XAROutput2' item (Output filename). + addOptionsGroup(QByteArrayLiteral("XAROutput2"), + {opts.outputFile}); +} + +} // namespace v10 +} // namespace mcs51 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/mcs51/mcs51archiversettingsgroup_v10.h b/src/plugins/generator/iarew/archs/mcs51/mcs51archiversettingsgroup_v10.h new file mode 100644 index 00000000..21c66433 --- /dev/null +++ b/src/plugins/generator/iarew/archs/mcs51/mcs51archiversettingsgroup_v10.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWMCS51ARCHIVERSETTINGSGROUP_V10_H +#define QBS_IAREWMCS51ARCHIVERSETTINGSGROUP_V10_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace mcs51 { +namespace v10 { + +class Mcs51ArchiverSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Mcs51ArchiverSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildOutputPage(const QString &baseDirectory, + const ProductData &qbsProduct); +}; + +} // namespace v10 +} // namespace mcs51 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWMCS51ARCHIVERSETTINGSGROUP_V10_H diff --git a/src/plugins/generator/iarew/archs/mcs51/mcs51assemblersettingsgroup_v10.cpp b/src/plugins/generator/iarew/archs/mcs51/mcs51assemblersettingsgroup_v10.cpp new file mode 100644 index 00000000..a840b29c --- /dev/null +++ b/src/plugins/generator/iarew/archs/mcs51/mcs51assemblersettingsgroup_v10.cpp @@ -0,0 +1,228 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51assemblersettingsgroup_v10.h" + +//#include "../../iarewproperty.h" +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace mcs51 { +namespace v10 { + +constexpr int kAssemblerArchiveVersion = 2; +constexpr int kAssemblerDataVersion = 6; + +namespace { + +// Language page options. + +struct LanguagePageOptions final +{ + enum MacroQuoteCharacter { + AngleBracketsQuote, + RoundBracketsQuote, + SquareBracketsQuote, + FigureBracketsQuote + }; + + explicit LanguagePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("assemblerFlags")}); + enableSymbolsCaseSensitive = flags.contains(QLatin1String("-s+")); + enableMultibyteSupport = flags.contains(QLatin1String("-n")); + + if (flags.contains(QLatin1String("-M<>"))) + macroQuoteCharacter = LanguagePageOptions::AngleBracketsQuote; + else if (flags.contains(QLatin1String("-M()"))) + macroQuoteCharacter = LanguagePageOptions::RoundBracketsQuote; + else if (flags.contains(QLatin1String("-M[]"))) + macroQuoteCharacter = LanguagePageOptions::SquareBracketsQuote; + else if (flags.contains(QLatin1String("-M{}"))) + macroQuoteCharacter = LanguagePageOptions::FigureBracketsQuote; + } + + MacroQuoteCharacter macroQuoteCharacter = AngleBracketsQuote; + int enableSymbolsCaseSensitive = 0; + int enableMultibyteSupport = 0; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + debugInfo = gen::utils::debugInformation(qbsProduct); + } + + int debugInfo = 0; +}; + +// Preprocessor page options. + +struct PreprocessorPageOptions final +{ + explicit PreprocessorPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + defineSymbols = gen::utils::cppVariantModuleProperties( + qbsProps, {QStringLiteral("defines")}); + + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + const QStringList fullIncludePaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("includePaths"), + QStringLiteral("systemIncludePaths")}); + for (const auto &fullIncludePath : fullIncludePaths) { + const QFileInfo includeFileInfo(fullIncludePath); + const QString includeFilePath = includeFileInfo.absoluteFilePath(); + if (includeFilePath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, includeFilePath); + includePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, includeFilePath); + includePaths.push_back(path); + } + } + } + + QVariantList defineSymbols; + QVariantList includePaths; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString warningLevel = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("warningLevel")); + if (warningLevel == QLatin1String("all")) { + enableWarnings = 0; + enableAllWarnings = 0; + } else if (warningLevel == QLatin1String("none")) { + enableWarnings = 1; + enableAllWarnings = 0; + } else { + enableWarnings = 0; + enableAllWarnings = 1; + } + } + + int enableWarnings = 0; + int enableAllWarnings = 0; +}; + +} // namespace + +// Mcs51AssemblerSettingsGroup + +Mcs51AssemblerSettingsGroup::Mcs51AssemblerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProject) + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("A8051")); + setArchiveVersion(kAssemblerArchiveVersion); + setDataVersion(kAssemblerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildLanguagePage(qbsProduct); + buildOutputPage(qbsProduct); + buildPreprocessorPage(buildRootDirectory, qbsProduct); + buildDiagnosticsPage(qbsProduct); +} + +void Mcs51AssemblerSettingsGroup::buildLanguagePage( + const ProductData &qbsProduct) +{ + const LanguagePageOptions opts(qbsProduct); + // Add 'ACaseSensitivity' item (User symbols are case sensitive). + addOptionsGroup(QByteArrayLiteral("ACaseSensitivity"), + {opts.enableSymbolsCaseSensitive}); + // Add 'Asm multibyte support' item (Enable multibyte support). + addOptionsGroup(QByteArrayLiteral("Asm multibyte support"), + {opts.enableMultibyteSupport}); + // Add 'MacroChars' item (Macro quote characters: ()/[]/{}/<>). + addOptionsGroup(QByteArrayLiteral("MacroChars"), + {!opts.macroQuoteCharacter}, 0); +} + +void Mcs51AssemblerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + // Add 'Debug' item (Generate debug information). + addOptionsGroup(QByteArrayLiteral("Debug"), + {opts.debugInfo}); +} + +void Mcs51AssemblerSettingsGroup::buildPreprocessorPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const PreprocessorPageOptions opts(baseDirectory, qbsProduct); + // Add 'ADefines' item (Defined symbols). + addOptionsGroup(QByteArrayLiteral("ADefines"), + opts.defineSymbols); + // Add 'Include directories' item (Additional include directories). + addOptionsGroup(QByteArrayLiteral("Include directories"), + opts.includePaths); +} + +void Mcs51AssemblerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'AWarnEnable' item (Enable/disable warnings). + addOptionsGroup(QByteArrayLiteral("AWarnEnable"), + {opts.enableWarnings}); + // Add 'AWarnWhat' item (Enable/disable all warnings). + addOptionsGroup(QByteArrayLiteral("AWarnWhat"), + {opts.enableAllWarnings}); +} + +} // namespace v10 +} // namespace mcs51 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/mcs51/mcs51assemblersettingsgroup_v10.h b/src/plugins/generator/iarew/archs/mcs51/mcs51assemblersettingsgroup_v10.h new file mode 100644 index 00000000..bb9f4b61 --- /dev/null +++ b/src/plugins/generator/iarew/archs/mcs51/mcs51assemblersettingsgroup_v10.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWMCS51ASSEMBLERSETTINGSGROUP_V10_H +#define QBS_IAREWMCS51ASSEMBLERSETTINGSGROUP_V10_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace mcs51 { +namespace v10 { + +class Mcs51AssemblerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Mcs51AssemblerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildLanguagePage(const ProductData &qbsProduct); + void buildOutputPage(const ProductData &qbsProduct); + void buildPreprocessorPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); +}; + +} // namespace v10 +} // namespace mcs51 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWMCS51ASSEMBLERSETTINGSGROUP_V10_H diff --git a/src/plugins/generator/iarew/archs/mcs51/mcs51buildconfigurationgroup_v10.cpp b/src/plugins/generator/iarew/archs/mcs51/mcs51buildconfigurationgroup_v10.cpp new file mode 100644 index 00000000..ba86bc54 --- /dev/null +++ b/src/plugins/generator/iarew/archs/mcs51/mcs51buildconfigurationgroup_v10.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51archiversettingsgroup_v10.h" +#include "mcs51assemblersettingsgroup_v10.h" +#include "mcs51buildconfigurationgroup_v10.h" +#include "mcs51compilersettingsgroup_v10.h" +#include "mcs51generalsettingsgroup_v10.h" +#include "mcs51linkersettingsgroup_v10.h" + +#include "../../iarewtoolchainpropertygroup.h" +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace mcs51 { +namespace v10 { + +Mcs51BuildConfigurationGroup::Mcs51BuildConfigurationGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) + : gen::xml::PropertyGroup("configuration") +{ + // Append configuration name item. + const QString cfgName = gen::utils::buildConfigurationName(qbsProject); + appendProperty("name", cfgName); + + // Apend toolchain name group item. + appendChild("8051"); + + // Append debug info item. + const int debugBuild = gen::utils::debugInformation(qbsProduct); + appendProperty("debug", debugBuild); + + // Append settings group items. + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); +} + +bool Mcs51BuildConfigurationGroupFactory::canCreate( + gen::utils::Architecture arch, + const Version &version) const +{ + return arch == gen::utils::Architecture::Mcs51 + && version.majorVersion() == 10; +} + +std::unique_ptr +Mcs51BuildConfigurationGroupFactory::create( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) const +{ + const auto group = new Mcs51BuildConfigurationGroup( + qbsProject, qbsProduct, qbsProductDeps); + return std::unique_ptr(group); +} + +} // namespace v10 +} // namespace mcs51 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/mcs51/mcs51buildconfigurationgroup_v10.h b/src/plugins/generator/iarew/archs/mcs51/mcs51buildconfigurationgroup_v10.h new file mode 100644 index 00000000..edd16d5d --- /dev/null +++ b/src/plugins/generator/iarew/archs/mcs51/mcs51buildconfigurationgroup_v10.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWMCS51BUILDCONFIGURATIONGROUP_V10_H +#define QBS_IAREWMCS51BUILDCONFIGURATIONGROUP_V10_H + +#include + +namespace qbs { +namespace iarew { +namespace mcs51 { +namespace v10 { + +class Mcs51BuildConfigurationGroup final + : public gen::xml::PropertyGroup +{ +private: + explicit Mcs51BuildConfigurationGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + + friend class Mcs51BuildConfigurationGroupFactory; +}; + +class Mcs51BuildConfigurationGroupFactory final + : public gen::xml::PropertyGroupFactory +{ +public: + bool canCreate(gen::utils::Architecture arch, + const Version &version) const final; + + std::unique_ptr create( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) const final; +}; + +} // namespace v10 +} // namespace mcs51 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWMCS51BUILDCONFIGURATIONGROUP_V10_H diff --git a/src/plugins/generator/iarew/archs/mcs51/mcs51compilersettingsgroup_v10.cpp b/src/plugins/generator/iarew/archs/mcs51/mcs51compilersettingsgroup_v10.cpp new file mode 100644 index 00000000..649350a9 --- /dev/null +++ b/src/plugins/generator/iarew/archs/mcs51/mcs51compilersettingsgroup_v10.cpp @@ -0,0 +1,476 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51compilersettingsgroup_v10.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace mcs51 { +namespace v10 { + +constexpr int kCompilerArchiveVersion = 7; +constexpr int kCompilerDataVersion = 12; + +namespace { + +// Output page options. + +struct OutputPageOptions final +{ + enum ModuleType { + ProgramModule, + LibraryModule + }; + + explicit OutputPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + moduleType = flags.contains(QLatin1String("--library_module")) + ? OutputPageOptions::LibraryModule + : OutputPageOptions::ProgramModule; + debugInfo = gen::utils::debugInformation(qbsProduct); + } + + int debugInfo = 0; + ModuleType moduleType = ProgramModule; +}; + +// Language one page options. + +struct LanguageOnePageOptions final +{ + enum LanguageExtension { + CLanguageExtension, + CxxLanguageExtension, + AutoLanguageExtension + }; + + enum CLanguageDialect { + C89LanguageDialect, + C99LanguageDialect + }; + + enum CxxLanguageDialect { + EmbeddedCPlusPlus, + ExtendedEmbeddedCPlusPlus + }; + + enum LanguageConformance { + AllowIarExtension, + RelaxedStandard, + StrictStandard + }; + + explicit LanguageOnePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + // File extension based by default. + languageExtension = LanguageOnePageOptions::AutoLanguageExtension; + // C language dialect. + const QStringList cLanguageVersion = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("cLanguageVersion")}); + cLanguageDialect = cLanguageVersion.contains(QLatin1String("c89")) + ? LanguageOnePageOptions::C89LanguageDialect + : LanguageOnePageOptions::C99LanguageDialect; + // C++ language dialect. + if (flags.contains(QLatin1String("--ec++"))) + cxxLanguageDialect = LanguageOnePageOptions::EmbeddedCPlusPlus; + else if (flags.contains(QLatin1String("--eec++"))) + cxxLanguageDialect = LanguageOnePageOptions::ExtendedEmbeddedCPlusPlus; + // Language conformance. + if (flags.contains(QLatin1String("-e"))) + languageConformance = LanguageOnePageOptions::AllowIarExtension; + else if (flags.contains(QLatin1String("--strict"))) + languageConformance = LanguageOnePageOptions::StrictStandard; + else + languageConformance = LanguageOnePageOptions::RelaxedStandard; + + allowVla = flags.contains(QLatin1String("--vla")); + useCppInlineSemantics = flags.contains( + QLatin1String("--use_c++_inline")); + requirePrototypes = flags.contains( + QLatin1String("--require_prototypes")); + destroyStaticObjects = !flags.contains( + QLatin1String("--no_static_destruction")); + } + + LanguageExtension languageExtension = AutoLanguageExtension; + CLanguageDialect cLanguageDialect = C89LanguageDialect; + CxxLanguageDialect cxxLanguageDialect = EmbeddedCPlusPlus; + LanguageConformance languageConformance = AllowIarExtension; + int allowVla = 0; + int useCppInlineSemantics = 0; + int requirePrototypes = 0; + int destroyStaticObjects = 0; +}; + +// Language two page options. + +struct LanguageTwoPageOptions final +{ + enum PlainCharacter { + SignedCharacter, + UnsignedCharacter + }; + + enum FloatingPointSemantic { + StrictSemantic, + RelaxedSemantic + }; + + explicit LanguageTwoPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + plainCharacter = flags.contains( + QLatin1String("--char_is_signed")) + ? LanguageTwoPageOptions::SignedCharacter + : LanguageTwoPageOptions::UnsignedCharacter; + floatingPointSemantic = flags.contains( + QLatin1String("--relaxed_fp")) + ? LanguageTwoPageOptions::RelaxedSemantic + : LanguageTwoPageOptions::StrictSemantic; + enableMultibyteSupport = flags.contains( + QLatin1String("--enable_multibytes")); + } + + PlainCharacter plainCharacter = SignedCharacter; + FloatingPointSemantic floatingPointSemantic = StrictSemantic; + int enableMultibyteSupport = 0; +}; + +// Optimizations page options. + +struct OptimizationsPageOptions final +{ + // Optimizations level radio-buttons with combo-box + // on "level" widget. + enum Strategy { + StrategyBalanced, + StrategySize, + StrategySpeed + }; + + enum Level { + LevelNone, + LevelLow, + LevelMedium, + LevelHigh + }; + + enum LevelSlave { + LevelSlave0, + LevelSlave1, + LevelSlave2, + LevelSlave3 + }; + + explicit OptimizationsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString optimization = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("optimization")); + if (optimization == QLatin1String("none")) { + optimizationStrategy = OptimizationsPageOptions::StrategyBalanced; + optimizationLevel = OptimizationsPageOptions::LevelNone; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave0; + } else if (optimization == QLatin1String("fast")) { + optimizationStrategy = OptimizationsPageOptions::StrategySpeed; + optimizationLevel = OptimizationsPageOptions::LevelHigh; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave3; + } else if (optimization == QLatin1String("small")) { + optimizationStrategy = OptimizationsPageOptions::StrategySize; + optimizationLevel = OptimizationsPageOptions::LevelHigh; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave3; + } + + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + enableCommonSubexpressionElimination = !flags.contains( + QLatin1String("--no_cse")); + enableLoopUnroll = !flags.contains(QLatin1String("--no_unroll")); + enableFunctionInlining = !flags.contains(QLatin1String("--no_inline")); + enableCodeMotion = !flags.contains(QLatin1String("--no_code_motion")); + enableTypeBasedAliasAnalysis = !flags.contains( + QLatin1String("--no_tbaa")); + enableCrossCall = !flags.contains(QLatin1String("--no_cross_call")); + disableRegisterBanks = flags.contains( + QLatin1String("--disable_register_banks")); + + disableSizeConstrains = flags.contains( + QLatin1String("--no_size_constraints")); + } + + Strategy optimizationStrategy = StrategyBalanced; + Level optimizationLevel = LevelNone; + LevelSlave optimizationLevelSlave = LevelSlave0; + // Seven bit-field flags on "enabled transformations" widget. + int enableCommonSubexpressionElimination = 0; // Common sub-expression elimination. + int enableLoopUnroll = 0; // Loop unrolling. + int enableFunctionInlining = 0; // Function inlining. + int enableCodeMotion = 0; // Code motion. + int enableTypeBasedAliasAnalysis = 0; // Type based alias analysis. + int enableCrossCall = 0; // Cross call optimization. + int disableRegisterBanks = 0; // Disabled register banks. + int disableSizeConstrains = 0; // No size constraints. +}; + +// Preprocessor page options. + +struct PreprocessorPageOptions final +{ + explicit PreprocessorPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + // TODO: Need to exclude the pre-defined maroses which are handled + // in 'General Options'. + defineSymbols = gen::utils::cppVariantModuleProperties( + qbsProps, {QStringLiteral("defines")}); + + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + const QStringList fullIncludePaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("includePaths"), + QStringLiteral("systemIncludePaths")}); + const QString dlibToolkitPath = IarewUtils::dlibToolkitRootPath(qbsProduct); + for (const QString &fullIncludePath : fullIncludePaths) { + const QFileInfo includeFileInfo(fullIncludePath); + const QString includeFilePath = includeFileInfo.absoluteFilePath(); + // Exclude dlib config includes because it already handled in + // 'General Options->Library configuration page'. + if (includeFilePath.startsWith(dlibToolkitPath)) + continue; + if (includeFilePath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, includeFilePath); + includePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, includeFilePath); + includePaths.push_back(path); + } + } + } + + QVariantList defineSymbols; + QVariantList includePaths; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + warningsAsErrors = gen::utils::cppIntegerModuleProperty( + qbsProps, QStringLiteral("treatWarningsAsErrors")); + } + + int warningsAsErrors = 0; +}; + +// Code page options. + +struct CodePageOptions final +{ + explicit CodePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + paddingForRomMonitorBreakpoints = flags.contains( + QLatin1String("--rom_mon_bp_padding")); + excludeUbrofMessagesInOutput = flags.contains( + QLatin1String("--no_ubrof_messages")); + } + + int paddingForRomMonitorBreakpoints = 0; + int excludeUbrofMessagesInOutput = 0; +}; + +} // namespace + +// Mcs51CompilerSettingsGroup + +Mcs51CompilerSettingsGroup::Mcs51CompilerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProject) + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("ICC8051")); + setArchiveVersion(kCompilerArchiveVersion); + setDataVersion(kCompilerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildOutputPage(qbsProduct); + buildLanguageOnePage(qbsProduct); + buildLanguageTwoPage(qbsProduct); + buildOptimizationsPage(qbsProduct); + buildPreprocessorPage(buildRootDirectory, qbsProduct); + buildDiagnosticsPage(qbsProduct); +} + +void Mcs51CompilerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + // Add 'CCDebugInfo' item (Generate debug info). + addOptionsGroup(QByteArrayLiteral("CCDebugInfo"), + {opts.debugInfo}); + // Add 'CCOverrideModuleTypeDefault' item + // (Override default module type). + addOptionsGroup(QByteArrayLiteral("CCOverrideModuleTypeDefault"), + {1}); + // Add 'CCRadioModuleType' item (Module type: program/library). + addOptionsGroup(QByteArrayLiteral("CCRadioModuleType"), + {opts.moduleType}); +} + +void Mcs51CompilerSettingsGroup::buildLanguageOnePage( + const ProductData &qbsProduct) +{ + const LanguageOnePageOptions opts(qbsProduct); + // Add 'IccLang' item with 'auto-extension based' + // value (Language: C/C++/Auto). + addOptionsGroup(QByteArrayLiteral("IccLang"), + {opts.languageExtension}); + // Add 'IccCDialect' item (C dialect: c89/99/11). + addOptionsGroup(QByteArrayLiteral("IccCDialect"), + {opts.cLanguageDialect}); + // Add 'IccCppDialect' item (C++ dialect: embedded/extended). + addOptionsGroup(QByteArrayLiteral("IccCppDialect"), + {opts.cxxLanguageDialect}); + // Add 'CCExt' item (Language conformance: IAR/relaxed/strict). + addOptionsGroup(QByteArrayLiteral("LangConform"), + {opts.languageConformance}); + // Add 'IccAllowVLA' item (Allow VLA). + addOptionsGroup(QByteArrayLiteral("IccAllowVLA"), + {opts.allowVla}); + // Add 'IccCppInlineSemantics' item (C++ inline semantics). + addOptionsGroup(QByteArrayLiteral("IccCppInlineSemantics"), + {opts.useCppInlineSemantics}); + // Add 'CCRequirePrototypes' item (Require prototypes). + addOptionsGroup(QByteArrayLiteral("CCRequirePrototypes"), + {opts.requirePrototypes}); + // Add 'IccStaticDestr' item (Destroy static objects). + addOptionsGroup(QByteArrayLiteral("IccStaticDestr"), + {opts.destroyStaticObjects}); +} + +void Mcs51CompilerSettingsGroup::buildLanguageTwoPage( + const ProductData &qbsProduct) +{ + const LanguageTwoPageOptions opts(qbsProduct); + // Add 'CharIs' item (Plain char is: signed/unsigned). + addOptionsGroup(QByteArrayLiteral("CharIs"), + {opts.plainCharacter}); + // Add 'IccFloatSemantics' item + // (Floatic-point semantics: strict/relaxed conformance). + addOptionsGroup(QByteArrayLiteral("IccFloatSemantics"), + {opts.floatingPointSemantic}); + // Add 'CCMultibyteSupport' item (Enable multibyte support). + addOptionsGroup(QByteArrayLiteral("CCMultibyteSupport"), + {opts.enableMultibyteSupport}); +} + +void Mcs51CompilerSettingsGroup::buildOptimizationsPage( + const ProductData &qbsProduct) +{ + const OptimizationsPageOptions opts(qbsProduct); + // Add 'CCOptStrategy', 'CCOptLevel' and + // 'CCOptLevelSlave' items (Level). + addOptionsGroup(QByteArrayLiteral("CCOptStrategy"), + {opts.optimizationStrategy}); + addOptionsGroup(QByteArrayLiteral("CCOptLevel"), + {opts.optimizationLevel}); + addOptionsGroup(QByteArrayLiteral("CCOptLevelSlave"), + {opts.optimizationLevelSlave}); + // Add 'CCAllowList2' item (Enabled transformations: 7 check boxes). + const QString transformations = QStringLiteral("%1%2%3%4%5%6%7") + .arg(opts.enableCommonSubexpressionElimination) + .arg(opts.enableLoopUnroll) + .arg(opts.enableFunctionInlining) + .arg(opts.enableCodeMotion) + .arg(opts.enableTypeBasedAliasAnalysis) + .arg(opts.enableCrossCall) + .arg(opts.disableRegisterBanks); + addOptionsGroup(QByteArrayLiteral("CCAllowList2"), + {transformations}); + // Add 'NoSizeConstraints' item (No size constraints). + addOptionsGroup(QByteArrayLiteral("NoSizeConstraints"), + {opts.disableSizeConstrains}); +} + +void Mcs51CompilerSettingsGroup::buildPreprocessorPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const PreprocessorPageOptions opts(baseDirectory, qbsProduct); + // Add 'CCDefines' item (Defines symbols). + addOptionsGroup(QByteArrayLiteral("CCDefines"), + opts.defineSymbols); + // Add 'CCIncludePath2' item (Additional include directories). + addOptionsGroup(QByteArrayLiteral("CCIncludePath2"), + opts.includePaths); +} + +void Mcs51CompilerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'CCDiagWarnAreErr' item (Treat all warnings as errors). + addOptionsGroup(QByteArrayLiteral("CCDiagWarnAreErr"), + {opts.warningsAsErrors}); +} + +void Mcs51CompilerSettingsGroup::buildCodePage( + const ProductData &qbsProduct) +{ + const CodePageOptions opts(qbsProduct); + // Add 'RomMonBpPadding' item (Padding for ROM-monitor breakpoints). + addOptionsGroup(QByteArrayLiteral("RomMonBpPadding"), + {opts.paddingForRomMonitorBreakpoints}); + // Add 'NoUBROFMessages' item (No UBROF messages in output files). + addOptionsGroup(QByteArrayLiteral("NoUBROFMessages"), + {opts.excludeUbrofMessagesInOutput}); +} + +} // namespace v10 +} // namespace mcs51 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/mcs51/mcs51compilersettingsgroup_v10.h b/src/plugins/generator/iarew/archs/mcs51/mcs51compilersettingsgroup_v10.h new file mode 100644 index 00000000..e68a7628 --- /dev/null +++ b/src/plugins/generator/iarew/archs/mcs51/mcs51compilersettingsgroup_v10.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWMCS51COMPILERSETTINGSGROUP_V10_H +#define QBS_IAREWMCS51COMPILERSETTINGSGROUP_V10_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace mcs51 { +namespace v10 { + +class Mcs51CompilerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Mcs51CompilerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildOutputPage(const ProductData &qbsProduct); + void buildLanguageOnePage(const ProductData &qbsProduct); + void buildLanguageTwoPage(const ProductData &qbsProduct); + void buildOptimizationsPage(const ProductData &qbsProduct); + void buildPreprocessorPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); + void buildCodePage(const ProductData &qbsProduct); +}; + +} // namespace v10 +} // namespace mcs51 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWMCS51COMPILERSETTINGSGROUP_V10_H diff --git a/src/plugins/generator/iarew/archs/mcs51/mcs51generalsettingsgroup_v10.cpp b/src/plugins/generator/iarew/archs/mcs51/mcs51generalsettingsgroup_v10.cpp new file mode 100644 index 00000000..b0c4b936 --- /dev/null +++ b/src/plugins/generator/iarew/archs/mcs51/mcs51generalsettingsgroup_v10.cpp @@ -0,0 +1,1013 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51generalsettingsgroup_v10.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace mcs51 { +namespace v10 { + +constexpr int kGeneralArchiveVersion = 4; +constexpr int kGeneralDataVersion = 9; + +namespace { + +// Target page options. + +struct TargetPageOptions final +{ + enum CpuCore { + CorePlain = 1, + CoreExtended1, + CoreExtended2 + }; + + enum CodeModel { + CodeModelNear = 1, + CodeModelBanked, + CodeModelFar, + CodeModelBankedExtended2 + }; + + enum DataModel { + DataModelTiny = 0, + DataModelSmall, + DataModelLarge, + DataModelGeneric, + DataModelFarGeneric, + DataModelFar + }; + + enum ConstantsMemoryPlacement { + RamMemoryPlace = 0, + RomMemoryPlace, + CodeMemoryPlace + }; + + enum CallingConvention { + DataOverlayConvention = 0, + IDataOverlayConvention, + IDataReentrantConvention, + PDataReentrantConvention, + XDataReentrantConvention, + ExtendedStackReentrantConvention + }; + + explicit TargetPageOptions(const ProductData &qbsProduct) + { + chipInfoPath = detectChipInfoPath(qbsProduct); + + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + + // Should be parsed before the 'code_model' and + // 'data_model' options, as that options are depends on it. + const QString core = IarewUtils::flagValue( + flags, QStringLiteral("--core")) + .toLower(); + if (core == QLatin1String("plain")) { + cpuCore = TargetPageOptions::CorePlain; + } else if (core == QLatin1String("extended1")) { + cpuCore = TargetPageOptions::CoreExtended1; + } else if (core == QLatin1String("extended2")) { + cpuCore = TargetPageOptions::CoreExtended2; + } else { + // If the core variant is not set, then choose the + // default values (see the compiler datasheet for + // '--core' option). + cpuCore = TargetPageOptions::CorePlain; + } + + const QString cm = IarewUtils::flagValue( + flags, QStringLiteral("--code_model")) + .toLower(); + if (cm == QLatin1String("near")) { + codeModel = TargetPageOptions::CodeModelNear; + } else if (cm == QLatin1String("banked")) { + codeModel = TargetPageOptions::CodeModelBanked; + } else if (cm == QLatin1String("far")) { + codeModel = TargetPageOptions::CodeModelFar; + } else if (cm == QLatin1String("banked_ext2")) { + codeModel = TargetPageOptions::CodeModelBankedExtended2; + } else { + // If the code model is not set, then choose the + // default values (see the compiler datasheet for + // '--code_model' option). + if (cpuCore == TargetPageOptions::CorePlain) + codeModel = TargetPageOptions::CodeModelNear; + else if (cpuCore == TargetPageOptions::CoreExtended1) + codeModel = TargetPageOptions::CodeModelFar; + else if (cpuCore == TargetPageOptions::CoreExtended2) + codeModel = TargetPageOptions::CodeModelBankedExtended2; + } + + const QString dm = IarewUtils::flagValue( + flags, QStringLiteral("--data_model")).toLower(); + if (dm == QLatin1String("tiny")) { + dataModel = TargetPageOptions::DataModelTiny; + } else if (dm == QLatin1String("small")) { + dataModel = TargetPageOptions::DataModelSmall; + } else if (dm == QLatin1String("large")) { + dataModel = TargetPageOptions::DataModelLarge; + } else if (dm == QLatin1String("generic")) { + dataModel = TargetPageOptions::DataModelGeneric; + } else if (dm == QLatin1String("far_generic")) { + dataModel = TargetPageOptions::DataModelFarGeneric; + } else if (dm == QLatin1String("far")) { + dataModel = TargetPageOptions::DataModelFar; + } else { + // If the data model is not set, then choose the + // default values (see the compiler datasheet for + // '--data_model' option). + if (cpuCore == TargetPageOptions::CorePlain) + dataModel = TargetPageOptions::DataModelSmall; + else if (cpuCore == TargetPageOptions::CoreExtended1) + dataModel = TargetPageOptions::DataModelFar; + else if (cpuCore == TargetPageOptions::CoreExtended2) + dataModel = TargetPageOptions::DataModelLarge; + } + + useExtendedStack = flags.contains(QLatin1String("--extended_stack")); + + const int regsCount = IarewUtils::flagValue( + flags, QStringLiteral("--nr_virtual_regs")) + .toInt(); + enum { MinVRegsCount = 8, MaxVRegsCount = 32, VRegsOffset = 8 }; + // The registers index starts with 0: 0 - means 8 registers, + // 1 - means 9 registers and etc. Any invalid values we interpret + // as a default value in 8 registers. + virtualRegisters = (regsCount < MinVRegsCount || regsCount > MaxVRegsCount) + ? 0 : (regsCount - VRegsOffset); + + const QString constPlace = IarewUtils::flagValue( + flags, QStringLiteral("--place_constants")) + .toLower(); + if (constPlace == QLatin1String("data")) { + constPlacement = TargetPageOptions::RamMemoryPlace; + } else if (constPlace == QLatin1String("data_rom")) { + constPlacement = TargetPageOptions::RomMemoryPlace; + } else if (constPlace == QLatin1String("code")) { + constPlacement = TargetPageOptions::CodeMemoryPlace; + } else { + // If this option is not set, then choose the + // default value (see the compiler datasheet for + // '--place_constants' option). + constPlacement = TargetPageOptions::RamMemoryPlace; + } + + const QString cc = IarewUtils::flagValue( + flags, QStringLiteral("--calling_convention")).toLower(); + if (cc == QLatin1String("data_overlay")) { + callingConvention = TargetPageOptions::DataOverlayConvention; + } else if (cc == QLatin1String("idata_overlay")) { + callingConvention = TargetPageOptions::IDataOverlayConvention; + } else if (cc == QLatin1String("idata_reentrant")) { + callingConvention = TargetPageOptions::IDataReentrantConvention; + } else if (cc == QLatin1String("pdata_reentrant")) { + callingConvention = TargetPageOptions::PDataReentrantConvention; + } else if (cc == QLatin1String("xdata_reentrant")) { + callingConvention = TargetPageOptions::XDataReentrantConvention; + } else if (cc == QLatin1String("ext_stack_reentrant")) { + callingConvention = TargetPageOptions::ExtendedStackReentrantConvention; + } else { + // If this option is not set, then choose the + // default value (see the compiler datasheet for + // '--calling_convention' option). + callingConvention = TargetPageOptions::IDataReentrantConvention; + } + } + + // Trying to indirectly detect and build the chip config path, + // which uses to show a device name in "Device information" group box. + static QString detectChipInfoPath(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + + QVariantList configPaths; + + // Enumerate all product linker config files + // (which are set trough '-f' option). + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + configPaths << IarewUtils::flagValues(flags, QStringLiteral("-f")); + + // Enumerate all product linker config files + // (which are set trough 'linkerscript' tag). + for (const auto &qbsGroup : qbsProduct.groups()) { + const auto qbsArtifacts = qbsGroup.sourceArtifacts(); + for (const auto &qbsArtifact : qbsArtifacts) { + const auto qbsTags = qbsArtifact.fileTags(); + if (!qbsTags.contains(QLatin1String("linkerscript"))) + continue; + const auto configPath = qbsArtifact.filePath(); + // Skip duplicates. + if (configPaths.contains(configPath)) + continue; + configPaths << qbsArtifact.filePath(); + } + } + + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + for (const QVariant &configPath : qAsConst(configPaths)) { + const QString fullConfigPath = configPath.toString(); + // We interested only in a config paths shipped inside of a toolkit. + if (!fullConfigPath.startsWith(toolkitPath, Qt::CaseInsensitive)) + continue; + // Extract the chip name from the linker script name. + const int underscoreIndex = fullConfigPath.lastIndexOf(QLatin1Char('_')); + if (underscoreIndex == -1) + continue; + const int dotIndex = fullConfigPath.lastIndexOf(QLatin1Char('.')); + if (dotIndex == -1) + continue; + if (dotIndex <= underscoreIndex) + continue; + const QString chipName = fullConfigPath.mid( + underscoreIndex + 1, + dotIndex - underscoreIndex - 1); + // Construct full chip info path. + const QFileInfo fullChipInfoPath(QFileInfo(fullConfigPath).absolutePath() + + QLatin1Char('/') + chipName + + QLatin1String(".i51")); + if (fullChipInfoPath.exists()) + return fullChipInfoPath.absoluteFilePath(); + } + + return {}; + } + + QString chipInfoPath; + CpuCore cpuCore = CorePlain; + CodeModel codeModel = CodeModelNear; + DataModel dataModel = DataModelTiny; + int useExtendedStack = 0; + int virtualRegisters = 0; + ConstantsMemoryPlacement constPlacement = RamMemoryPlace; + CallingConvention callingConvention = DataOverlayConvention; +}; + +// System page options. + +struct StackHeapPageOptions final +{ + explicit StackHeapPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList defineSymbols = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("defines")}); + const QStringList linkerFlags = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("driverLinkerFlags")}); + + idataStack = IarewUtils::flagValue( + defineSymbols, QStringLiteral("_IDATA_STACK_SIZE")); + if (idataStack.isEmpty()) + idataStack = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D_IDATA_STACK_SIZE")); + if (idataStack.isEmpty()) + idataStack = QLatin1String("0x40"); // Default IDATA stack size. + pdataStack = IarewUtils::flagValue( + defineSymbols, QStringLiteral("_PDATA_STACK_SIZE")); + if (pdataStack.isEmpty()) + pdataStack = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D_PDATA_STACK_SIZE")); + if (pdataStack.isEmpty()) + pdataStack = QLatin1String("0x80"); // Default PDATA stack size. + xdataStack = IarewUtils::flagValue( + defineSymbols, QStringLiteral("_XDATA_STACK_SIZE")); + if (xdataStack.isEmpty()) + xdataStack = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D_XDATA_STACK_SIZE")); + if (xdataStack.isEmpty()) + xdataStack = QLatin1String("0xEFF"); // Default XDATA stack size. + extendedStack = IarewUtils::flagValue( + defineSymbols, QStringLiteral("_EXTENDED_STACK_SIZE")); + if (extendedStack.isEmpty()) + extendedStack = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D_EXTENDED_STACK_SIZE")); + if (extendedStack.isEmpty()) + extendedStack = QLatin1String("0x3FF"); // Default EXTENDED stack size. + + xdataHeap = IarewUtils::flagValue( + defineSymbols, QStringLiteral("_XDATA_HEAP_SIZE")); + if (xdataHeap.isEmpty()) + xdataHeap = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D_XDATA_HEAP_SIZE")); + if (xdataHeap.isEmpty()) + xdataHeap = QLatin1String("0xFF"); // Default XDATA heap size. + farHeap = IarewUtils::flagValue( + defineSymbols, QStringLiteral("_FAR_HEAP_SIZE")); + if (farHeap.isEmpty()) + farHeap = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D_FAR_HEAP_SIZE")); + if (farHeap.isEmpty()) + farHeap = QLatin1String("0xFFF"); // Default FAR heap size. + far22Heap = IarewUtils::flagValue( + defineSymbols, QStringLiteral("_FAR22_HEAP_SIZE")); + if (far22Heap.isEmpty()) + far22Heap = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D_FAR22_HEAP_SIZE")); + if (far22Heap.isEmpty()) + far22Heap = QLatin1String("0xFFF"); // Default FAR22 heap size. + hugeHeap = IarewUtils::flagValue( + defineSymbols, QStringLiteral("_HUGE_HEAP_SIZE")); + if (hugeHeap.isEmpty()) + hugeHeap = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D_HUGE_HEAP_SIZE")); + if (hugeHeap.isEmpty()) + hugeHeap = QLatin1String("0xFFF"); // Default HUGE heap size. + + extStackAddress = IarewUtils::flagValue( + defineSymbols, QStringLiteral("?ESP")); + if (extStackAddress.isEmpty()) + extStackAddress = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D?ESP")); + if (extStackAddress.isEmpty()) + extStackAddress = QLatin1String("0x9B"); // Default extended stack pointer address. + extStackMask = IarewUtils::flagValue( + defineSymbols, QStringLiteral("?ESP_MASK")); + if (extStackMask.isEmpty()) + extStackMask = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D?ESP_MASK")); + if (extStackMask.isEmpty()) + extStackMask = QLatin1String("0x03"); // Default extended stack pointer mask. + } + + // Stack sizes. + QString idataStack; + QString pdataStack; + QString xdataStack; + QString extendedStack; + // Heap sizes. + QString xdataHeap; + QString farHeap; + QString far22Heap; + QString hugeHeap; + // Extended stack. + QString extStackAddress; + QString extStackMask; + int extStackOffset = 0; + int extStackStartAddress = 0; +}; + +// Data pointer page options. + +struct DptrPageOptions final +{ + enum DptrSize { + Dptr16, + Dptr24 + }; + + enum DptrVisibility { + DptrShadowed, + DptrSeparate + }; + + enum SwitchMethod { + DptrIncludeMethod, + DptrMaskMethod + }; + + explicit DptrPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + + const QString core = IarewUtils::flagValue( + flags, QStringLiteral("--core")); + + const QString dptr = IarewUtils::flagValue( + flags, QStringLiteral("--dptr")); + enum ValueIndex { SizeIndex, NumbersIndex, + VisibilityIndex, SwitchMethodIndex }; + const QStringList dptrparts = dptr.split(QLatin1Char(',')); + for (auto index = 0; index < dptrparts.count(); ++index) { + const QString part = dptrparts.at(index).toLower(); + switch (index) { + case SizeIndex: + if (part == QLatin1String("16")) { + dptrSize = DptrPageOptions::Dptr16; + } else if (part == QLatin1String("24")) { + dptrSize = DptrPageOptions::Dptr24; + } else { + // If this option is not set, then choose the + // default value (see the compiler datasheet for + // '--dptr' option). + if (core == QLatin1String("extended1")) + dptrSize = DptrPageOptions::Dptr24; + else + dptrSize = DptrPageOptions::Dptr16; + } + break; + case NumbersIndex: { + const int count = part.toInt(); + if (count < 1 || count > 8) { + // If this option is not set, then choose the + // default value (see the compiler datasheet for + // '--dptr' option). + if (core == QLatin1String("extended1")) + dptrsCountIndex = 1; // 2 DPTR's + else + dptrsCountIndex = 0; // 1 DPTR's + } else { + dptrsCountIndex = (count - 1); // DPTR's count - 1 + } + } + break; + case VisibilityIndex: + if (part == QLatin1String("shadowed")) + dptrVisibility = DptrPageOptions::DptrShadowed; + else if (part == QLatin1String("separate")) + dptrVisibility = DptrPageOptions::DptrSeparate; + else + // If this option is not set, then choose the + // default value (see the compiler datasheet for + // '--dptr' option). + dptrVisibility = DptrPageOptions::DptrSeparate; + break; + case SwitchMethodIndex: + if (part == QLatin1String("inc")) { + dptrSwitchMethod = DptrPageOptions::DptrIncludeMethod; + } else if (part.startsWith(QLatin1String("xor"))) { + dptrSwitchMethod = DptrPageOptions::DptrMaskMethod; + const int firstIndex = part.indexOf(QLatin1Char('(')); + const int lastIndex = part.indexOf(QLatin1Char(')')); + dptrMask = part.mid(firstIndex + 1, part.size() - lastIndex); + } else { + // If this option is not set, then choose the + // default value (see the compiler datasheet for + // '--dptr' option). + if (core == QLatin1String("extended1")) { + dptrSwitchMethod = DptrPageOptions::DptrIncludeMethod; + } else if (core == QLatin1String("plain")) { + dptrSwitchMethod = DptrPageOptions::DptrMaskMethod; + dptrMask = QLatin1String("0x01"); + } + } + break; + default: + break; + } + } + + const QStringList defineSymbols = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("defines")}); + const QStringList linkerFlags = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("driverLinkerFlags")}); + + dptrPbank = IarewUtils::flagValue( + defineSymbols, QStringLiteral("?PBANK")); + if (dptrPbank.isEmpty()) + dptrPbank = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D?PBANK")); + if (dptrPbank.isEmpty()) + dptrPbank = QLatin1String("0x93"); // Default 8-15 regs address. + dptrPbankExt = IarewUtils::flagValue( + defineSymbols, QStringLiteral("?PBANK_EXT")); + if (dptrPbankExt.isEmpty()) + dptrPbankExt = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D?PBANK_EXT")); + + dpsAddress = IarewUtils::flagValue( + defineSymbols, QStringLiteral("?DPS")); + if (dpsAddress.isEmpty()) + dpsAddress = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D?DPS")); + dpcAddress = IarewUtils::flagValue( + defineSymbols, QStringLiteral("?DPC")); + if (dpcAddress.isEmpty()) + dpcAddress = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D?DPC")); + + for (auto index = 0; index < 8; ++index) { + if (index == 0) { + QString dpxAddress = IarewUtils::flagValue( + defineSymbols, QStringLiteral("?DPX")); + if (dpxAddress.isEmpty()) + dpxAddress = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D?DPX")); + if (!dpxAddress.isEmpty()) + dpxAddress.prepend(QLatin1String("-D?DPX=")); + if (!dptrAddresses.contains(dpxAddress)) + dptrAddresses.push_back(dpxAddress); + } else { + QString dplAddress = IarewUtils::flagValue( + defineSymbols, QStringLiteral("?DPL%1").arg(index)); + if (dplAddress.isEmpty()) + dplAddress = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D?DPL%1").arg(index)); + if (!dplAddress.isEmpty()) + dplAddress.prepend(QStringLiteral("-D?DPL%1=").arg(index)); + if (!dptrAddresses.contains(dplAddress)) + dptrAddresses.push_back(dplAddress); + + QString dphAddress = IarewUtils::flagValue( + defineSymbols, QStringLiteral("?DPH%1").arg(index)); + if (dphAddress.isEmpty()) + dphAddress = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D?DPH%1").arg(index)); + if (!dphAddress.isEmpty()) + dphAddress.prepend(QStringLiteral("-D?DPH%1=").arg(index)); + if (!dptrAddresses.contains(dphAddress)) + dptrAddresses.push_back(dphAddress); + + QString dpxAddress = IarewUtils::flagValue( + defineSymbols, QStringLiteral("?DPX%1").arg(index)); + if (dpxAddress.isEmpty()) + dpxAddress = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D?DPX%1").arg(index)); + if (!dpxAddress.isEmpty()) + dpxAddress.prepend(QStringLiteral("-D?DPX%1=").arg(index)); + if (!dptrAddresses.contains(dpxAddress)) + dptrAddresses.push_back(dpxAddress); + } + } + } + + int dptrsCountIndex = 0; + DptrSize dptrSize = Dptr16; + DptrVisibility dptrVisibility = DptrShadowed; + SwitchMethod dptrSwitchMethod = DptrIncludeMethod; + QString dptrMask; + QString dptrPbank; + QString dptrPbankExt; + QString dpsAddress; + QString dpcAddress; + QStringList dptrAddresses; +}; + +// Code bank page options. + +struct CodeBankPageOptions final +{ + explicit CodeBankPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList defineSymbols = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("defines")}); + const QStringList linkerFlags = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("driverLinkerFlags")}); + + banksCount = IarewUtils::flagValue( + defineSymbols, QStringLiteral("_NR_OF_BANKS")); + if (banksCount.isEmpty()) + banksCount = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D_NR_OF_BANKS")); + if (banksCount.isEmpty()) + banksCount = QLatin1String("0x03"); + registerAddress = IarewUtils::flagValue( + defineSymbols, QStringLiteral("?CBANK")); + if (registerAddress.isEmpty()) + registerAddress = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D?CBANK")); + if (registerAddress.isEmpty()) + registerAddress = QLatin1String("0xF0"); + registerMask = IarewUtils::flagValue( + defineSymbols, QStringLiteral("?CBANK_MASK")); + if (registerMask.isEmpty()) + registerMask = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D?CBANK_MASK")); + if (registerMask.isEmpty()) + registerMask = QLatin1String("0xFF"); + bankStart = IarewUtils::flagValue( + defineSymbols, QStringLiteral("_CODEBANK_START")); + if (bankStart.isEmpty()) + bankStart = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D_CODEBANK_START")); + if (bankStart.isEmpty()) + bankStart = QLatin1String("0x8000"); + bankEnd = IarewUtils::flagValue( + defineSymbols, QStringLiteral("_CODEBANK_END")); + if (bankEnd.isEmpty()) + bankEnd = IarewUtils::flagValue( + linkerFlags, QStringLiteral("-D_CODEBANK_END")); + if (bankEnd.isEmpty()) + bankEnd = QLatin1String("0xFFFF"); + } + + QString banksCount; + QString registerAddress; + QString registerMask; + QString bankStart; + QString bankEnd; +}; + +// Library options page options. + +struct LibraryOptionsPageOptions final +{ + enum PrintfFormatter { + PrintfAutoFormatter = 0, + PrintfLargeFormatter = 3, + PrintfMediumFormatter = 5, + PrintfSmallFormatter = 6 + }; + + enum ScanfFormatter { + ScanfAutoFormatter = 0, + ScanfLargeFormatter = 3, + ScanfMediumFormatter = 5 + }; + + explicit LibraryOptionsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + for (const QString &flag : flags) { + if (flag.endsWith(QLatin1String("_formatted_write"), + Qt::CaseInsensitive)) { + const QString prop = flag.split( + QLatin1Char('=')).at(0).toLower(); + if (prop == QLatin1String("-e_large_write")) + printfFormatter = LibraryOptionsPageOptions::PrintfLargeFormatter; + else if (prop == QLatin1String("-e_medium_write")) + printfFormatter = LibraryOptionsPageOptions::PrintfMediumFormatter; + else if (prop == QLatin1String("-e_small_write")) + printfFormatter = LibraryOptionsPageOptions::PrintfSmallFormatter; + else + // If this option is not set, then choose the + // default value (see the compiler datasheet for + // '_formatted_write' option). + printfFormatter = LibraryOptionsPageOptions::PrintfMediumFormatter; + } else if (flag.endsWith(QLatin1String("_formatted_read"), + Qt::CaseInsensitive)) { + const QString prop = flag.split(QLatin1Char('=')) + .at(0).toLower(); + if (prop == QLatin1String("-e_large_read")) + scanfFormatter = LibraryOptionsPageOptions::ScanfLargeFormatter; + else if (prop == QLatin1String("-e_medium_read")) + scanfFormatter = LibraryOptionsPageOptions::ScanfMediumFormatter; + else + // If this option is not set, then choose the + // default value (see the compiler datasheet for + // '_formatted_read' option). + scanfFormatter = LibraryOptionsPageOptions::ScanfMediumFormatter; + } + } + } + + PrintfFormatter printfFormatter = PrintfAutoFormatter; + ScanfFormatter scanfFormatter = ScanfAutoFormatter; +}; + +// Library configuration page options. + +struct LibraryConfigPageOptions final +{ + enum RuntimeLibrary { + NoLibrary, + NormalDlibLibrary, + CustomDlibLibrary, + ClibLibrary, + CustomClibLibrary + }; + + explicit LibraryConfigPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + + const QStringList libraryPaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("staticLibraries")}); + const auto libraryBegin = libraryPaths.cbegin(); + const auto libraryEnd = libraryPaths.cend(); + + const QFileInfo dlibConfigInfo(IarewUtils::flagValue( + flags, QStringLiteral("--dlib_config"))); + const QString dlibConfigFilePath = dlibConfigInfo.absoluteFilePath(); + if (!dlibConfigFilePath.isEmpty()) { + const QString dlibToolkitPath = IarewUtils::dlibToolkitRootPath( + qbsProduct); + if (dlibConfigFilePath.startsWith(dlibToolkitPath, + Qt::CaseInsensitive)) { + libraryType = LibraryConfigPageOptions::NormalDlibLibrary; + configPath = IarewUtils::toolkitRelativeFilePath( + baseDirectory, dlibConfigFilePath); + + // Find dlib library inside of IAR toolkit directory. + const auto libraryIt = std::find_if(libraryBegin, libraryEnd, + [dlibToolkitPath]( + const QString &libraryPath) { + return libraryPath.startsWith(dlibToolkitPath); + }); + if (libraryIt != libraryEnd) { + // This means that dlib library is 'standard' (placed inside + // of IAR toolkit directory). + libraryPath = IarewUtils::toolkitRelativeFilePath( + baseDirectory, *libraryIt); + } + } else { + // This means that dlib library is 'custom' + // (but we don't know its path). + libraryType = LibraryConfigPageOptions::CustomDlibLibrary; + configPath = IarewUtils::projectRelativeFilePath( + baseDirectory, dlibConfigFilePath); + } + } else { + // Find clib library inside of IAR toolkit directory. + const QString clibToolkitPath = IarewUtils::clibToolkitRootPath( + qbsProduct); + const auto libraryIt = std::find_if(libraryBegin, libraryEnd, + [clibToolkitPath]( + const QString &libraryPath) { + return libraryPath.startsWith(clibToolkitPath); + }); + if (libraryIt != libraryEnd) { + // This means that clib library is 'standard' (placed inside + // of IAR toolkit directory). + libraryType = LibraryConfigPageOptions::ClibLibrary; + libraryPath = IarewUtils::toolkitRelativeFilePath( + baseDirectory, *libraryIt); + } else { + // This means that no any libraries are used . + libraryType = LibraryConfigPageOptions::NoLibrary; + } + } + } + + RuntimeLibrary libraryType = NoLibrary; + QString configPath; + QString libraryPath; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + binaryType = IarewUtils::outputBinaryType(qbsProduct); + binaryDirectory = gen::utils::binaryOutputDirectory( + baseDirectory, qbsProduct); + objectDirectory = gen::utils::objectsOutputDirectory( + baseDirectory, qbsProduct); + listingDirectory = gen::utils::listingOutputDirectory( + baseDirectory, qbsProduct); + } + + IarewUtils::OutputBinaryType binaryType = IarewUtils::ApplicationOutputType; + QString binaryDirectory; + QString objectDirectory; + QString listingDirectory; +}; + +} // namespace + +// Mcs51GeneralSettingsGroup + +Mcs51GeneralSettingsGroup::Mcs51GeneralSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProject) + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("General")); + setArchiveVersion(kGeneralArchiveVersion); + setDataVersion(kGeneralDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildTargetPage(qbsProduct); + buildStackHeapPage(qbsProduct); + buildDataPointerPage(qbsProduct); + buildCodeBankPage(qbsProduct); + buildLibraryOptionsPage(qbsProduct); + buildLibraryConfigPage(buildRootDirectory, qbsProduct); + buildOutputPage(buildRootDirectory, qbsProduct); +} + +void Mcs51GeneralSettingsGroup::buildTargetPage( + const ProductData &qbsProduct) +{ + const TargetPageOptions opts(qbsProduct); + // Add 'OGChipConfigPath' item (Device: ). + addOptionsGroup(QByteArrayLiteral("OGChipConfigPath"), + {opts.chipInfoPath}); + + // Add 'CPU Core' and 'CPU Core Slave' items + // (CPU core: plain/extended{1|2}). + addOptionsGroup(QByteArrayLiteral("CPU Core"), + {opts.cpuCore}); + addOptionsGroup(QByteArrayLiteral("CPU Core Slave"), + {opts.cpuCore}); + // Add 'Code Memory Model' and 'Code Memory Model slave' items + // (Code model: near/banked/far/banked extended). + addOptionsGroup(QByteArrayLiteral("Code Memory Model"), + {opts.codeModel}); + addOptionsGroup(QByteArrayLiteral("Code Memory Model slave"), + {opts.codeModel}); + // Add 'Data Memory Model' and 'Data Memory Model slave' items + // (Data model: tiny/small/large/generic/far). + addOptionsGroup(QByteArrayLiteral("Data Memory Model"), + {opts.dataModel}); + addOptionsGroup(QByteArrayLiteral("Data Memory Model slave"), + {opts.dataModel}); + // Add 'Use extended stack' and 'Use extended stack slave' items + // (Use extended stack). + addOptionsGroup(QByteArrayLiteral("Use extended stack"), + {opts.useExtendedStack}); + addOptionsGroup(QByteArrayLiteral("Use extended stack slave"), + {opts.useExtendedStack}); + // Add 'Workseg Size' item (Number of virtual registers: 8...32). + addOptionsGroup(QByteArrayLiteral("Workseg Size"), + {opts.virtualRegisters}); + // Add 'Constant Placement' item + // (Location of constants and strings: ram/rom/code memories). + addOptionsGroup(QByteArrayLiteral("Constant Placement"), + {opts.constPlacement}); + // Add 'Calling convention' item (Calling convention). + addOptionsGroup(QByteArrayLiteral("Calling convention"), + {opts.callingConvention}); +} + +void Mcs51GeneralSettingsGroup::buildStackHeapPage( + const ProductData &qbsProduct) +{ + const StackHeapPageOptions opts(qbsProduct); + // Add 'General Idata Stack Size' item (Stack size: IDATA). + addOptionsGroup(QByteArrayLiteral("General Idata Stack Size"), + {opts.idataStack}); + // Add 'General Pdata Stack Size' item (Stack size: PDATA). + addOptionsGroup(QByteArrayLiteral("General Pdata Stack Size"), + {opts.pdataStack}); + // Add 'General Xdata Stack Size' item (Stack size: XDATA). + addOptionsGroup(QByteArrayLiteral("General Xdata Stack Size"), + {opts.xdataStack}); + // Add 'General Ext Stack Size' item (Stack size: Extended). + addOptionsGroup(QByteArrayLiteral("General Ext Stack Size"), + {opts.extendedStack}); + + // Add 'General Xdata Heap Size' item (Heap size: XDATA). + addOptionsGroup(QByteArrayLiteral("General Xdata Heap Size"), + {opts.xdataHeap}); + // Add 'General Far Heap Size' item (Heap size: Far). + addOptionsGroup(QByteArrayLiteral("General Far Heap Size"), + {opts.farHeap}); + // Add 'General Far22 Heap Size' item (Heap size: Far22). + addOptionsGroup(QByteArrayLiteral("General Far22 Heap Size"), + {opts.far22Heap}); + // Add 'General Huge Heap Size' item (Heap size: Huge). + addOptionsGroup(QByteArrayLiteral("General Huge Heap Size"), + {opts.hugeHeap}); + + // Add 'Extended stack address' item + // (Extended stack pointer address). + addOptionsGroup(QByteArrayLiteral("Extended stack address"), + {opts.extStackAddress}); + // Add 'Extended stack mask' item (Extended stack pointer mask). + addOptionsGroup(QByteArrayLiteral("Extended stack mask"), + {opts.extStackMask}); + // Add 'Extended stack is offset' item + // (Extended stack pointer is an offset). + addOptionsGroup(QByteArrayLiteral("Extended stack is offset"), + {opts.extStackOffset}); +} + +void Mcs51GeneralSettingsGroup::buildDataPointerPage( + const ProductData &qbsProduct) +{ + const DptrPageOptions opts(qbsProduct); + // Add 'Nr of Datapointers' item (Number of DPTRs: 1...8). + addOptionsGroup(QByteArrayLiteral("Nr of Datapointers"), + {opts.dptrsCountIndex}); + // Add 'Datapointer Size' item (DPTR size: 16/24). + addOptionsGroup(QByteArrayLiteral("Datapointer Size"), + {opts.dptrSize}); + // Add 'Sfr Visibility' item (DPTR address: shadowed/separate). + addOptionsGroup(QByteArrayLiteral("Sfr Visibility"), + {opts.dptrVisibility}); + // Add 'Switch Method' item (Switch method: inc/mask). + addOptionsGroup(QByteArrayLiteral("Switch Method"), + {opts.dptrSwitchMethod}); + // Add 'Mask Value' item (Switch method mask). + addOptionsGroup(QByteArrayLiteral("Mask Value"), + {opts.dptrMask}); + // Add 'PDATA 8-15 register address' item (Page register + // address (for bits 8-15). + addOptionsGroup(QByteArrayLiteral("PDATA 8-15 register address"), + {opts.dptrPbank}); + // Add 'PDATA 16-31 register address' item (Page register + // address (for bits 16-31). + addOptionsGroup(QByteArrayLiteral("PDATA 16-31 register address"), + {opts.dptrPbankExt}); + // Add 'DPS Address' item (Selected DPTR register). + addOptionsGroup(QByteArrayLiteral("DPS Address"), + {opts.dpsAddress}); + // Add 'DPC Address' item (Separate DPTR control register). + addOptionsGroup(QByteArrayLiteral("DPC Address"), + {opts.dpcAddress}); + // Add 'DPTR Addresses' item (DPTR addresses: Low/High/Ext). + const QString dptrAddresses = opts.dptrAddresses.join(QLatin1Char(' ')); + addOptionsGroup(QByteArrayLiteral("DPTR Addresses"), + {dptrAddresses}); +} + +void Mcs51GeneralSettingsGroup::buildCodeBankPage( + const ProductData &qbsProduct) +{ + const CodeBankPageOptions opts(qbsProduct); + // Add 'CodeBankReg' item (Register address). + addOptionsGroup(QByteArrayLiteral("CodeBankReg"), + {opts.registerAddress}); + // Add 'CodeBankRegMask' item (Register mask). + addOptionsGroup(QByteArrayLiteral("CodeBankRegMask"), + {opts.registerMask}); + // Add 'CodeBankNrOfs' item (Number of banks). + addOptionsGroup(QByteArrayLiteral("CodeBankNrOfs"), + {opts.banksCount}); + // Add 'CodeBankStart' item (Bank start). + addOptionsGroup(QByteArrayLiteral("CodeBankStart"), + {opts.bankStart}); + // Add 'CodeBankSize' item (Bank end). + addOptionsGroup(QByteArrayLiteral("CodeBankSize"), + {opts.bankEnd}); +} + +void Mcs51GeneralSettingsGroup::buildLibraryOptionsPage( + const ProductData &qbsProduct) +{ + const LibraryOptionsPageOptions opts(qbsProduct); + // Add 'Output variant' item (Printf formatter). + addOptionsGroup(QByteArrayLiteral("Output variant"), + {opts.printfFormatter}); + // Add 'Input variant' item (Printf formatter). + addOptionsGroup(QByteArrayLiteral("Input variant"), + {opts.scanfFormatter}); +} + +void Mcs51GeneralSettingsGroup::buildLibraryConfigPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const LibraryConfigPageOptions opts(baseDirectory, qbsProduct); + // Add 'GRuntimeLibSelect2' and 'GRuntimeLibSelectSlave2' items + // (Link with runtime: none/dlib/clib/etc). + addOptionsGroup(QByteArrayLiteral("GRuntimeLibSelect2"), + {opts.libraryType}); + addOptionsGroup(QByteArrayLiteral("GRuntimeLibSelectSlave2"), + {opts.libraryType}); + // Add 'RTConfigPath' item (Runtime configuration file). + addOptionsGroup(QByteArrayLiteral("RTConfigPath"), + {opts.configPath}); + // Add 'RTLibraryPath' item (Runtime library file). + addOptionsGroup(QByteArrayLiteral("RTLibraryPath"), + {opts.libraryPath}); +} + +void Mcs51GeneralSettingsGroup::buildOutputPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(baseDirectory, qbsProduct); + // Add 'GOutputBinary' item (Output file: executable/library). + addOptionsGroup(QByteArrayLiteral("GOutputBinary"), + {opts.binaryType}); + // Add 'ExePath' item (Executable/binaries output directory). + addOptionsGroup(QByteArrayLiteral("ExePath"), + {opts.binaryDirectory}); + // Add 'ObjPath' item (Object files output directory). + addOptionsGroup(QByteArrayLiteral("ObjPath"), + {opts.objectDirectory}); + // Add 'ListPath' item (List files output directory). + addOptionsGroup(QByteArrayLiteral("ListPath"), + {opts.listingDirectory}); +} + +} // namespace v10 +} // namespace mcs51 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/mcs51/mcs51generalsettingsgroup_v10.h b/src/plugins/generator/iarew/archs/mcs51/mcs51generalsettingsgroup_v10.h new file mode 100644 index 00000000..1805a87a --- /dev/null +++ b/src/plugins/generator/iarew/archs/mcs51/mcs51generalsettingsgroup_v10.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWMCS51GENERALSETTINGSGROUP_V10_H +#define QBS_IAREWMCS51GENERALSETTINGSGROUP_V10_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace mcs51 { +namespace v10 { + +class Mcs51GeneralSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Mcs51GeneralSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildTargetPage(const ProductData &qbsProduct); + void buildStackHeapPage(const ProductData &qbsProduct); + void buildDataPointerPage(const ProductData &qbsProduct); + void buildCodeBankPage(const ProductData &qbsProduct); + void buildLibraryOptionsPage(const ProductData &qbsProduct); + void buildLibraryConfigPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildOutputPage(const QString &baseDirectory, + const ProductData &qbsProduct); +}; + +} // namespace v10 +} // namespace mcs51 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWMCS51GENERALSETTINGSGROUP_V10_H diff --git a/src/plugins/generator/iarew/archs/mcs51/mcs51linkersettingsgroup_v10.cpp b/src/plugins/generator/iarew/archs/mcs51/mcs51linkersettingsgroup_v10.cpp new file mode 100644 index 00000000..d2c7f384 --- /dev/null +++ b/src/plugins/generator/iarew/archs/mcs51/mcs51linkersettingsgroup_v10.cpp @@ -0,0 +1,335 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51linkersettingsgroup_v10.h" + +#include "../../iarewutils.h" + +#include + +namespace qbs { +namespace iarew { +namespace mcs51 { +namespace v10 { + +constexpr int kLinkerArchiveVersion = 4; +constexpr int kLinkerDataVersion = 21; + +namespace { + +// Config page options. + +struct ConfigPageOptions final +{ + explicit ConfigPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + + entryPoint = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("entryPoint")); + + // Enumerate all product linker config files + // (which are set trough 'linkerscript' tag). + for (const auto &qbsGroup : qbsProduct.groups()) { + const auto qbsArtifacts = qbsGroup.sourceArtifacts(); + for (const auto &qbsArtifact : qbsArtifacts) { + const auto qbsTags = qbsArtifact.fileTags(); + if (!qbsTags.contains(QLatin1String("linkerscript"))) + continue; + const QString fullConfigPath = qbsArtifact.filePath(); + if (fullConfigPath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullConfigPath); + configFilePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullConfigPath); + configFilePaths.push_back(path); + } + } + } + + // Enumerate all product linker config files + // (which are set trough '-f' option). + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + const QVariantList configPathValues = IarewUtils::flagValues( + flags, QStringLiteral("-f")); + for (const QVariant &configPathValue : configPathValues) { + const QString fullConfigPath = configPathValue.toString(); + if (fullConfigPath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullConfigPath); + if (!configFilePaths.contains(path)) + configFilePaths.push_back(path); + } else { + const QString path =IarewUtils::projectRelativeFilePath( + baseDirectory, fullConfigPath); + if (!configFilePaths.contains(path)) + configFilePaths.push_back(path); + } + } + + // Add libraries search paths. + const QStringList libraryPaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("libraryPaths")}); + for (const QString &libraryPath : libraryPaths) { + const QFileInfo libraryPathInfo(libraryPath); + const QString fullLibrarySearchPath = libraryPathInfo.absoluteFilePath(); + if (fullLibrarySearchPath.startsWith(toolkitPath, + Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullLibrarySearchPath); + librarySearchPaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullLibrarySearchPath); + librarySearchPaths.push_back(path); + } + } + } + + QVariantList configFilePaths; + QVariantList librarySearchPaths; + QString entryPoint; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + outputFile = gen::utils::targetBinary(qbsProduct); + } + + QString outputFile; +}; + +// List page options. + +struct ListPageOptions final +{ + enum ListingAction { + NoListing, + GenerateListing + }; + + explicit ListPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + generateMap = gen::utils::cppBooleanModuleProperty( + qbsProps, QStringLiteral("generateLinkerMapFile")) + ? ListPageOptions::GenerateListing + : ListPageOptions::NoListing; + } + + ListingAction generateMap = NoListing; +}; + +// Define page options. + +struct DefinePageOptions final +{ + explicit DefinePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + // Enumerate all linker defines. + for (const QString &flag : flags) { + if (!flag.startsWith(QLatin1String("-D"))) + continue; + const QString symbol = flag.mid(2); + // Ignore system-defined macroses, because its already + // handled in "General Options" page. + if (symbol.startsWith(QLatin1Char('?')) + || symbol.startsWith(QLatin1Char('_')) + ) { + continue; + } + defineSymbols.push_back(symbol); + } + } + + QVariantList defineSymbols; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString warningLevel = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("warningLevel")); + suppressAllWarnings = (warningLevel == QLatin1String("none")); + } + + int suppressAllWarnings = 0; +}; + +} // namespace + +// Mcs51LinkerSettingsGroup + +Mcs51LinkerSettingsGroup::Mcs51LinkerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProject) + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("XLINK")); + setArchiveVersion(kLinkerArchiveVersion); + setDataVersion(kLinkerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildConfigPage(buildRootDirectory, qbsProduct); + buildOutputPage(qbsProduct); + buildListPage(qbsProduct); + buildDefinePage(qbsProduct); + buildDiagnosticsPage(qbsProduct); + + // Should be called as latest stage! + buildExtraOptionsPage(qbsProduct); +} + +void Mcs51LinkerSettingsGroup::buildConfigPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + ConfigPageOptions opts(baseDirectory, qbsProduct); + + if (opts.configFilePaths.count() > 0) { + // Note: IAR IDE does not allow to specify a multiple config files, + // although the IAR linker support it. So, we use followig 'trick': + // we take a first config file and to add it as usual to required items; + // and then an other remainders we forward to the "Extra options page". + const QVariant configPath = opts.configFilePaths.takeFirst(); + // Add 'XclOverride' item (Override default). + addOptionsGroup(QByteArrayLiteral("XclOverride"), + {1}); + // Add 'XclFile' item (Linke configuration file). + addOptionsGroup(QByteArrayLiteral("XclFile"), + {configPath}); + + // Add remainder configuration files to the "Extra options page". + if (!opts.configFilePaths.isEmpty()) { + for (QVariant &configPath : opts.configFilePaths) + configPath = QLatin1String("-f ") + configPath.toString(); + + m_extraOptions << opts.configFilePaths; + } + } + + // Add 'xcProgramEntryLabel' item (Entry symbol). + addOptionsGroup(QByteArrayLiteral("xcProgramEntryLabel"), + {opts.entryPoint}); + // Add 'xcOverrideProgramEntryLabel' item + // (Override default program entry). + addOptionsGroup(QByteArrayLiteral("xcOverrideProgramEntryLabel"), + {1}); + // Add 'xcProgramEntryLabelSelect' item. + addOptionsGroup(QByteArrayLiteral("xcProgramEntryLabelSelect"), + {0}); + + // Add 'XIncludes' item (Libraries search paths). + addOptionsGroup(QByteArrayLiteral("XIncludes"), + opts.librarySearchPaths); +} + +void Mcs51LinkerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + // Add 'XOutOverride' item (Override default output file). + addOptionsGroup(QByteArrayLiteral("XOutOverride"), + {1}); + // Add 'OutputFile' item (Output file name). + addOptionsGroup(QByteArrayLiteral("OutputFile"), + {opts.outputFile}); +} + +void Mcs51LinkerSettingsGroup::buildListPage( + const ProductData &qbsProduct) +{ + const ListPageOptions opts(qbsProduct); + // Add 'XList' item (Generate linker listing). + addOptionsGroup(QByteArrayLiteral("XList"), + {opts.generateMap}); +} + +void Mcs51LinkerSettingsGroup::buildDefinePage( + const ProductData &qbsProduct) +{ + const DefinePageOptions opts(qbsProduct); + // Add 'XDefines' item (Defined symbols). + addOptionsGroup(QByteArrayLiteral("XDefines"), + opts.defineSymbols); +} + +void Mcs51LinkerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'SuppressAllWarn' item (Suppress all warnings). + addOptionsGroup(QByteArrayLiteral("SuppressAllWarn"), + {opts.suppressAllWarnings}); +} + +void Mcs51LinkerSettingsGroup::buildExtraOptionsPage( + const ProductData &qbsProduct) +{ + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + for (const QString &flag : flags) { + if (flag.startsWith(QLatin1String("-Z"))) + m_extraOptions.push_back(flag); + } + + if (!m_extraOptions.isEmpty()) { + // Add 'Linker Extra Options Check' (Use command line options). + addOptionsGroup(QByteArrayLiteral("Linker Extra Options Check"), + {1}); + // Add 'Linker Extra Options Edit' item (Command line options). + addOptionsGroup(QByteArrayLiteral("Linker Extra Options Edit"), + m_extraOptions); + } +} + +} // namespace v10 +} // namespace mcs51 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/mcs51/mcs51linkersettingsgroup_v10.h b/src/plugins/generator/iarew/archs/mcs51/mcs51linkersettingsgroup_v10.h new file mode 100644 index 00000000..fec80e5e --- /dev/null +++ b/src/plugins/generator/iarew/archs/mcs51/mcs51linkersettingsgroup_v10.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWMCS51LINKERSETTINGSGROUP_V10_H +#define QBS_IAREWMCS51LINKERSETTINGSGROUP_V10_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace mcs51 { +namespace v10 { + +class Mcs51LinkerSettingsGroupPrivate; + +class Mcs51LinkerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Mcs51LinkerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildConfigPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildOutputPage(const ProductData &qbsProduct); + void buildListPage(const ProductData &qbsProduct); + void buildDefinePage(const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); + void buildExtraOptionsPage(const ProductData &qbsProduct); + + QVariantList m_extraOptions; +}; + +} // namespace v10 +} // namespace mcs51 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWMCS51LINKERSETTINGSGROUP_V10_H diff --git a/src/plugins/generator/iarew/archs/msp430/msp430archiversettingsgroup_v7.cpp b/src/plugins/generator/iarew/archs/msp430/msp430archiversettingsgroup_v7.cpp new file mode 100644 index 00000000..1258412d --- /dev/null +++ b/src/plugins/generator/iarew/archs/msp430/msp430archiversettingsgroup_v7.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "msp430archiversettingsgroup_v7.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace msp430 { +namespace v7 { + +constexpr int kArchiverArchiveVersion = 4; +constexpr int kArchiverDataVersion = 0; + +namespace { + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + outputFile = QLatin1String("$PROJ_DIR$/") + + gen::utils::targetBinaryPath(baseDirectory, qbsProduct); + } + + QString outputFile; +}; + +} // namespace + +//Msp430ArchiverSettingsGroup + +Msp430ArchiverSettingsGroup::Msp430ArchiverSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("XAR")); + setArchiveVersion(kArchiverArchiveVersion); + setDataVersion(kArchiverDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + buildOutputPage(buildRootDirectory, qbsProduct); +} + +void Msp430ArchiverSettingsGroup::buildOutputPage(const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(baseDirectory, qbsProduct); + // Add 'XAROutOverride' item (Override default). + addOptionsGroup(QByteArrayLiteral("XAROutOverride"), + {1}); + // Add 'OutputFile' item (Output filename). + addOptionsGroup(QByteArrayLiteral("OutputFile"), + {opts.outputFile}); +} + +} // namespace v7 +} // namespace msp430 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/msp430/msp430archiversettingsgroup_v7.h b/src/plugins/generator/iarew/archs/msp430/msp430archiversettingsgroup_v7.h new file mode 100644 index 00000000..9da8d344 --- /dev/null +++ b/src/plugins/generator/iarew/archs/msp430/msp430archiversettingsgroup_v7.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWMSP430ARCHIVERSETTINGSGROUP_V7_H +#define QBS_IAREWMSP430ARCHIVERSETTINGSGROUP_V7_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace msp430 { +namespace v7 { + +class Msp430ArchiverSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Msp430ArchiverSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildOutputPage(const QString &baseDirectory, + const ProductData &qbsProduct); +}; + +} // namespace v7 +} // namespace msp430 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWMSP430ARCHIVERSETTINGSGROUP_V7_H diff --git a/src/plugins/generator/iarew/archs/msp430/msp430assemblersettingsgroup_v7.cpp b/src/plugins/generator/iarew/archs/msp430/msp430assemblersettingsgroup_v7.cpp new file mode 100644 index 00000000..51108e63 --- /dev/null +++ b/src/plugins/generator/iarew/archs/msp430/msp430assemblersettingsgroup_v7.cpp @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "msp430assemblersettingsgroup_v7.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace msp430 { +namespace v7 { + +constexpr int kAssemblerArchiveVersion = 5; +constexpr int kAssemblerDataVersion = 14; + +namespace { + +// Language page options. + +struct LanguagePageOptions final +{ + enum MacroQuoteCharacter { + AngleBracketsQuote, + RoundBracketsQuote, + SquareBracketsQuote, + FigureBracketsQuote + }; + + explicit LanguagePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("assemblerFlags")}); + enableSymbolsCaseSensitive = flags.contains( + QLatin1String("-s+")); + enableMultibyteSupport = flags.contains( + QLatin1String("-n")); + + if (flags.contains(QLatin1String("-M<>"))) + macroQuoteCharacter = LanguagePageOptions::AngleBracketsQuote; + else if (flags.contains(QLatin1String("-M()"))) + macroQuoteCharacter = LanguagePageOptions::RoundBracketsQuote; + else if (flags.contains(QLatin1String("-M[]"))) + macroQuoteCharacter = LanguagePageOptions::SquareBracketsQuote; + else if (flags.contains(QLatin1String("-M{}"))) + macroQuoteCharacter = LanguagePageOptions::FigureBracketsQuote; + } + + int enableSymbolsCaseSensitive = 1; + int enableMultibyteSupport = 0; + + MacroQuoteCharacter macroQuoteCharacter = AngleBracketsQuote; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + debugInfo = gen::utils::debugInformation(qbsProduct); + } + + int debugInfo = 0; +}; + +// Preprocessor page options. + +struct PreprocessorPageOptions final +{ + explicit PreprocessorPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + defineSymbols = gen::utils::cppVariantModuleProperties( + qbsProps, {QStringLiteral("defines")}); + + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + const QStringList fullIncludePaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("includePaths"), + QStringLiteral("systemIncludePaths")}); + for (const auto &fullIncludePath : fullIncludePaths) { + const QFileInfo includeFileInfo(fullIncludePath); + const QString includeFilePath = includeFileInfo.absoluteFilePath(); + if (includeFilePath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, includeFilePath); + includePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, includeFilePath); + includePaths.push_back(path); + } + } + } + + QVariantList defineSymbols; + QVariantList includePaths; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString warningLevel = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("warningLevel")); + if (warningLevel == QLatin1String("all")) { + enableWarnings = 0; + enableAllWarnings = 0; + } else if (warningLevel == QLatin1String("none")) { + enableWarnings = 1; + enableAllWarnings = 0; + } else { + enableWarnings = 0; + enableAllWarnings = 1; + } + } + + int enableWarnings = 0; + int enableAllWarnings = 0; +}; + +} // namespace + +//Msp430AssemblerSettingsGroup + +Msp430AssemblerSettingsGroup::Msp430AssemblerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("A430")); + setArchiveVersion(kAssemblerArchiveVersion); + setDataVersion(kAssemblerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildLanguagePage(qbsProduct); + buildOutputPage(qbsProduct); + buildPreprocessorPage(buildRootDirectory, qbsProduct); + buildDiagnosticsPage(qbsProduct); +} + +void Msp430AssemblerSettingsGroup::buildLanguagePage( + const ProductData &qbsProduct) +{ + const LanguagePageOptions opts(qbsProduct); + // Add 'ACaseSensitivity' item (User symbols are case sensitive). + addOptionsGroup(QByteArrayLiteral("ACaseSensitivity"), + {opts.enableSymbolsCaseSensitive}); + // Add 'AMultibyteSupport' item (Enable multibyte support). + addOptionsGroup(QByteArrayLiteral("AMultibyteSupport"), + {opts.enableMultibyteSupport}); + // Add 'MacroChars' item (Macro quote characters: ()/[]/{}/<>). + addOptionsGroup(QByteArrayLiteral("MacroChars"), + {opts.macroQuoteCharacter}); +} + +void Msp430AssemblerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + // Add 'ADebug' item (Generate debug information). + addOptionsGroup(QByteArrayLiteral("ADebug"), + {opts.debugInfo}); +} + +void Msp430AssemblerSettingsGroup::buildPreprocessorPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const PreprocessorPageOptions opts(baseDirectory, qbsProduct); + // Add 'ADefines' item (Defined symbols). + addOptionsGroup(QByteArrayLiteral("ADefines"), + opts.defineSymbols); + // Add 'AUserIncludes' item (Additional include directories). + addOptionsGroup(QByteArrayLiteral("AUserIncludes"), + opts.includePaths); +} + +void Msp430AssemblerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'AWarnEnable' item (Enable/disable warnings). + addOptionsGroup(QByteArrayLiteral("AWarnEnable"), + {opts.enableWarnings}); + // Add 'AWarnWhat' item (Enable/disable all warnings). + addOptionsGroup(QByteArrayLiteral("AWarnWhat"), + {opts.enableAllWarnings}); +} + +} // namespace v7 +} // namespace msp430 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/msp430/msp430assemblersettingsgroup_v7.h b/src/plugins/generator/iarew/archs/msp430/msp430assemblersettingsgroup_v7.h new file mode 100644 index 00000000..97a59f4c --- /dev/null +++ b/src/plugins/generator/iarew/archs/msp430/msp430assemblersettingsgroup_v7.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWMSP430ASSEMBLERSETTINGSGROUP_V7_H +#define QBS_IAREWMSP430ASSEMBLERSETTINGSGROUP_V7_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace msp430 { +namespace v7 { + +class Msp430AssemblerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Msp430AssemblerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildLanguagePage(const ProductData &qbsProduct); + void buildOutputPage(const ProductData &qbsProduct); + void buildPreprocessorPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); +}; + +} // namespace v7 +} // namespace msp430 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWMSP430ASSEMBLERSETTINGSGROUP_V7_H diff --git a/src/plugins/generator/iarew/archs/msp430/msp430buildconfigurationgroup_v7.cpp b/src/plugins/generator/iarew/archs/msp430/msp430buildconfigurationgroup_v7.cpp new file mode 100644 index 00000000..bd80cdd7 --- /dev/null +++ b/src/plugins/generator/iarew/archs/msp430/msp430buildconfigurationgroup_v7.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "msp430archiversettingsgroup_v7.h" +#include "msp430assemblersettingsgroup_v7.h" +#include "msp430buildconfigurationgroup_v7.h" +#include "msp430compilersettingsgroup_v7.h" +#include "msp430generalsettingsgroup_v7.h" +#include "msp430linkersettingsgroup_v7.h" + +#include "../../iarewtoolchainpropertygroup.h" +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace msp430 { +namespace v7 { + +Msp430BuildConfigurationGroup::Msp430BuildConfigurationGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) + : gen::xml::PropertyGroup("configuration") +{ + // Append configuration name item. + const QString cfgName = gen::utils::buildConfigurationName(qbsProject); + appendProperty("name", cfgName); + + // Apend toolchain name group item. + appendChild("MSP430"); + + // Append debug info item. + const int debugBuild = gen::utils::debugInformation(qbsProduct); + appendProperty("debug", debugBuild); + + // Append settings group items. + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); +} + +bool Msp430BuildConfigurationGroupFactory::canCreate( + gen::utils::Architecture arch, + const Version &version) const +{ + return arch == gen::utils::Architecture::Msp430 + && version.majorVersion() == 7; +} + +std::unique_ptr +Msp430BuildConfigurationGroupFactory::create( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) const +{ + const auto group = new Msp430BuildConfigurationGroup( + qbsProject, qbsProduct, qbsProductDeps); + return std::unique_ptr(group); +} + +} // namespace v7 +} // namespace msp430 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/msp430/msp430buildconfigurationgroup_v7.h b/src/plugins/generator/iarew/archs/msp430/msp430buildconfigurationgroup_v7.h new file mode 100644 index 00000000..ba0404c0 --- /dev/null +++ b/src/plugins/generator/iarew/archs/msp430/msp430buildconfigurationgroup_v7.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWMSP430BUILDCONFIGURATIONGROUP_V7_H +#define QBS_IAREWMSP430BUILDCONFIGURATIONGROUP_V7_H + +#include +#include + +namespace qbs { +namespace iarew { +namespace msp430 { +namespace v7 { + +class Msp430BuildConfigurationGroup final + : public gen::xml::PropertyGroup +{ +private: + explicit Msp430BuildConfigurationGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + + friend class Msp430BuildConfigurationGroupFactory; +}; + +class Msp430BuildConfigurationGroupFactory final + : public gen::xml::PropertyGroupFactory +{ +public: + bool canCreate(gen::utils::Architecture arch, + const Version &version) const final; + + std::unique_ptr create( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) const final; +}; + +} // namespace v7 +} // namespace msp430 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWMSP430BUILDCONFIGURATIONGROUP_V7_H diff --git a/src/plugins/generator/iarew/archs/msp430/msp430compilersettingsgroup_v7.cpp b/src/plugins/generator/iarew/archs/msp430/msp430compilersettingsgroup_v7.cpp new file mode 100644 index 00000000..5728578b --- /dev/null +++ b/src/plugins/generator/iarew/archs/msp430/msp430compilersettingsgroup_v7.cpp @@ -0,0 +1,486 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "msp430compilersettingsgroup_v7.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace msp430 { +namespace v7 { + +constexpr int kCompilerArchiveVersion = 4; +constexpr int kCompilerDataVersion = 38; + +namespace { + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + debugInfo = gen::utils::debugInformation(qbsProduct); + } + + int debugInfo = 0; +}; + +// Language one page options. + +struct LanguageOnePageOptions final +{ + enum LanguageExtension { + CLanguageExtension, + CxxLanguageExtension, + AutoLanguageExtension + }; + + enum CLanguageDialect { + C89LanguageDialect, + C99LanguageDialect + }; + + enum CxxLanguageDialect { + EmbeddedCPlusPlus, + ExtendedEmbeddedCPlusPlus + }; + + enum LanguageConformance { + AllowIarExtension, + RelaxedStandard, + StrictStandard + }; + + explicit LanguageOnePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + // File extension based by default. + languageExtension = LanguageOnePageOptions::AutoLanguageExtension; + // C language dialect. + const QStringList cLanguageVersion = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("cLanguageVersion")}); + if (cLanguageVersion.contains(QLatin1String("c89"))) + cLanguageDialect = LanguageOnePageOptions::C89LanguageDialect; + else if (cLanguageVersion.contains(QLatin1String("c99"))) + cLanguageDialect = LanguageOnePageOptions::C99LanguageDialect; + // C++ language dialect. + if (flags.contains(QLatin1String("--ec++"))) + cxxLanguageDialect = LanguageOnePageOptions::EmbeddedCPlusPlus; + else if (flags.contains(QLatin1String("--eec++"))) + cxxLanguageDialect = LanguageOnePageOptions::ExtendedEmbeddedCPlusPlus; + // Language conformance. + if (flags.contains(QLatin1String("-e"))) + languageConformance = LanguageOnePageOptions::AllowIarExtension; + else if (flags.contains(QLatin1String("--strict"))) + languageConformance = LanguageOnePageOptions::StrictStandard; + else + languageConformance = LanguageOnePageOptions::RelaxedStandard; + + allowVla = flags.contains(QLatin1String("--vla")); + useCppInlineSemantics = flags.contains( + QLatin1String("--use_c++_inline")); + requirePrototypes = flags.contains( + QLatin1String("--require_prototypes")); + destroyStaticObjects = !flags.contains( + QLatin1String("--no_static_destruction")); + } + + LanguageExtension languageExtension = AutoLanguageExtension; + CLanguageDialect cLanguageDialect = C99LanguageDialect; + CxxLanguageDialect cxxLanguageDialect = EmbeddedCPlusPlus; + LanguageConformance languageConformance = AllowIarExtension; + int allowVla = 0; + int useCppInlineSemantics = 0; + int requirePrototypes = 0; + int destroyStaticObjects = 0; +}; + +// Language two page options. + +struct LanguageTwoPageOptions final +{ + enum PlainCharacter { + SignedCharacter, + UnsignedCharacter + }; + + enum FloatingPointSemantic { + StrictSemantic, + RelaxedSemantic + }; + + explicit LanguageTwoPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + plainCharacter = flags.contains(QLatin1String("--char_is_signed")) + ? LanguageTwoPageOptions::SignedCharacter + : LanguageTwoPageOptions::UnsignedCharacter; + floatingPointSemantic = flags.contains(QLatin1String("--relaxed_fp")) + ? LanguageTwoPageOptions::RelaxedSemantic + : LanguageTwoPageOptions::StrictSemantic; + enableMultibyteSupport = flags.contains( + QLatin1String("--enable_multibytes")); + guardCalls = flags.contains( + QLatin1String("--guard_calls")); + } + + PlainCharacter plainCharacter = UnsignedCharacter; + FloatingPointSemantic floatingPointSemantic = StrictSemantic; + int enableMultibyteSupport = 0; + int guardCalls = 0; +}; + +// Code page options. + +struct CodePageOptions final +{ + enum Utilization { + UtilizationNormalUse, + UtilizationRegVarVariables, + UtilizationNotUsed + }; + + explicit CodePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + // Detect R4 utilization. + if (flags.contains(QLatin1String("--lock_r4"))) + r4utilization = UtilizationNotUsed; + else if (flags.contains(QLatin1String("--regvar_r4"))) + r4utilization = UtilizationRegVarVariables; + // Detect R5 utilization. + if (flags.contains(QLatin1String("--lock_r5"))) + r5utilization = UtilizationNotUsed; + else if (flags.contains(QLatin1String("--regvar_r54"))) + r5utilization = UtilizationRegVarVariables; + // Detect reduce stack usage. + reduceStackUsage = flags.contains( + QLatin1String("--reduce_stack_usage")); + // Detect 20-bit context save on interrupt. + save20BitContextOnInterrupt = flags.contains( + QLatin1String("--save_reg20")); + } + + Utilization r4utilization = UtilizationNormalUse; + Utilization r5utilization = UtilizationNormalUse; + int reduceStackUsage = 0; + int save20BitContextOnInterrupt = 0; +}; + +// Optimizations page options. + +struct OptimizationsPageOptions final +{ + // Optimizations level radio-buttons with + // combo-box on "level" widget. + enum Strategy { + StrategyBalanced, + StrategySize, + StrategySpeed + }; + + enum Level { + LevelNone, + LevelLow, + LevelMedium, + LevelHigh + }; + + enum LevelSlave { + LevelSlave0, + LevelSlave1, + LevelSlave2, + LevelSlave3 + }; + + explicit OptimizationsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString optimization = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("optimization")); + if (optimization == QLatin1String("none")) { + optimizationStrategy = OptimizationsPageOptions::StrategyBalanced; + optimizationLevel = OptimizationsPageOptions::LevelNone; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave0; + } else if (optimization == QLatin1String("fast")) { + optimizationStrategy = OptimizationsPageOptions::StrategySpeed; + optimizationLevel = OptimizationsPageOptions::LevelHigh; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave3; + } else if (optimization == QLatin1String("small")) { + optimizationStrategy = OptimizationsPageOptions::StrategySize; + optimizationLevel = OptimizationsPageOptions::LevelHigh; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave3; + } + + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + + disableSizeConstraints = flags.contains( + QLatin1String("--no_size_constraints")); + + enableCommonSubexpressionElimination = !flags.contains( + QLatin1String("--no_cse")); + enableLoopUnroll = !flags.contains(QLatin1String("--no_unroll")); + enableFunctionInlining = !flags.contains(QLatin1String("--no_inline")); + enableCodeMotion = !flags.contains(QLatin1String("--no_code_motion")); + enableTypeBasedAliasAnalysis = !flags.contains( + QLatin1String("--no_tbaa")); + } + + Strategy optimizationStrategy = StrategyBalanced; + Level optimizationLevel = LevelNone; + LevelSlave optimizationLevelSlave = LevelSlave0; + // Separate "no size constraints" checkbox. + int disableSizeConstraints = 0; + + // Five bit-field flags on "enabled transformations" widget. + int enableCommonSubexpressionElimination = 0; // Common sub-expression elimination. + int enableLoopUnroll = 0; // Loop unrolling. + int enableFunctionInlining = 0; // Function inlining. + int enableCodeMotion = 0; // Code motion. + int enableTypeBasedAliasAnalysis = 0; // Type based alias analysis. +}; + +// Preprocessor page options. + +struct PreprocessorPageOptions final +{ + explicit PreprocessorPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + defineSymbols = gen::utils::cppVariantModuleProperties( + qbsProps, {QStringLiteral("defines")}); + + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + const QStringList fullIncludePaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("includePaths"), + QStringLiteral("systemIncludePaths")}); + for (const QString &fullIncludePath : fullIncludePaths) { + const QFileInfo includeFileInfo(fullIncludePath); + const QString includeFilePath = includeFileInfo.absoluteFilePath(); + if (includeFilePath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, includeFilePath); + includePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, includeFilePath); + includePaths.push_back(path); + } + } + } + + QVariantList defineSymbols; + QVariantList includePaths; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + warningsAsErrors = gen::utils::cppIntegerModuleProperty( + qbsProps, QStringLiteral("treatWarningsAsErrors")); + } + + int warningsAsErrors = 0; +}; + +} // namespace + +//Msp430CompilerSettingsGroup + +Msp430CompilerSettingsGroup::Msp430CompilerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("ICC430")); + setArchiveVersion(kCompilerArchiveVersion); + setDataVersion(kCompilerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildOutputPage(qbsProduct); + buildLanguageOnePage(qbsProduct); + buildLanguageTwoPage(qbsProduct); + buildOptimizationsPage(qbsProduct); + buildPreprocessorPage(buildRootDirectory, qbsProduct); + buildDiagnosticsPage(qbsProduct); +} + +void Msp430CompilerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + // Add 'CCDebugInfo' item (Generate debug info). + addOptionsGroup(QByteArrayLiteral("CCDebugInfo"), + {opts.debugInfo}); +} + +void Msp430CompilerSettingsGroup::buildLanguageOnePage( + const ProductData &qbsProduct) +{ + const LanguageOnePageOptions opts(qbsProduct); + // Add 'IccLang' item with 'auto-extension based' + // value (Language: C/C++/Auto). + addOptionsGroup(QByteArrayLiteral("IccLang"), + {opts.languageExtension}); + // Add 'IccCDialect' item (C dialect: c89/99/11). + addOptionsGroup(QByteArrayLiteral("IccCDialect"), + {opts.cLanguageDialect}); + // Add 'IccCppDialect' item (C++ dialect: embedded/extended). + addOptionsGroup(QByteArrayLiteral("IccCppDialect"), + {opts.cxxLanguageDialect}); + // Add 'CCExt' item + // (Language conformance: IAR/relaxed/strict). + addOptionsGroup(QByteArrayLiteral("CCExt"), + {opts.languageConformance}); + // Add 'IccAllowVLA' item (Allow VLA). + addOptionsGroup(QByteArrayLiteral("IccAllowVLA"), + {opts.allowVla}); + // Add 'IccCppInlineSemantics' item (C++ inline semantics). + addOptionsGroup(QByteArrayLiteral("IccCppInlineSemantics"), + {opts.useCppInlineSemantics}); + // Add 'IccRequirePrototypes' item (Require prototypes). + addOptionsGroup(QByteArrayLiteral("CCRequirePrototypes"), + {opts.requirePrototypes}); + // Add 'IccStaticDestr' item (Destroy static objects). + addOptionsGroup(QByteArrayLiteral("IccStaticDestr"), + {opts.destroyStaticObjects}); +} + +void Msp430CompilerSettingsGroup::buildLanguageTwoPage( + const ProductData &qbsProduct) +{ + const LanguageTwoPageOptions opts(qbsProduct); + // Add 'IccCharIs' item (Plain char is: signed/unsigned). + addOptionsGroup(QByteArrayLiteral("CCCharIs"), + {opts.plainCharacter}); + // Add 'IccFloatSemantics' item (Floatic-point + // semantics: strict/relaxed conformance). + addOptionsGroup(QByteArrayLiteral("IccFloatSemantics"), + {opts.floatingPointSemantic}); + // Add 'IccMultibyteSupport' item (Enable multibyte support). + addOptionsGroup(QByteArrayLiteral("CCMultibyteSupport"), + {opts.enableMultibyteSupport}); + // Add 'CCGuardCalls' and 'OCGuardCallsSlave' item (Guard calls). + addOptionsGroup(QByteArrayLiteral("CCGuardCalls"), + {opts.guardCalls}); +} + +void Msp430CompilerSettingsGroup::buildCodePage( + const ProductData &qbsProduct) +{ + const CodePageOptions opts(qbsProduct); + // Add 'OCCR4Utilize' item + // (R4 utilization: normal/regvar/disabled). + addOptionsGroup(QByteArrayLiteral("OCCR4Utilize"), + {opts.r4utilization}); + // Add 'OCCR5Utilize' item + // (R5 utilization: normal/regvar/disabled). + addOptionsGroup(QByteArrayLiteral("OCCR5Utilize"), + {opts.r5utilization}); + // Add 'ReduceStack' item + // (Reduce stack usage). + addOptionsGroup(QByteArrayLiteral("ReduceStack"), + {opts.reduceStackUsage}); + // Add 'Save20bit' item + // (20-bit context save on interrupt). + addOptionsGroup(QByteArrayLiteral("Save20bit"), + {opts.save20BitContextOnInterrupt}); +} + +void Msp430CompilerSettingsGroup::buildOptimizationsPage( + const ProductData &qbsProduct) +{ + const OptimizationsPageOptions opts(qbsProduct); + // Add 'CCOptStrategy', 'CCOptLevel' and + // 'CCOptLevelSlave' items (Level). + addOptionsGroup(QByteArrayLiteral("CCOptStrategy"), + {opts.optimizationStrategy}); + addOptionsGroup(QByteArrayLiteral("CCOptLevel"), + {opts.optimizationLevel}); + addOptionsGroup(QByteArrayLiteral("CCOptLevelSlave"), + {opts.optimizationLevelSlave}); + + // Add 'CCOptimizationNoSizeConstraints' item (no size constraints). + addOptionsGroup(QByteArrayLiteral("CCOptimizationNoSizeConstraints"), + {opts.disableSizeConstraints}); + + // Add 'CCAllowList' item + // (Enabled transformations: 5 check boxes). + const QString bitflags = QStringLiteral("%1%2%3%4%5%6") + .arg(opts.enableCommonSubexpressionElimination) + .arg(opts.enableLoopUnroll) + .arg(opts.enableFunctionInlining) + .arg(opts.enableCodeMotion) + .arg(opts.enableTypeBasedAliasAnalysis); + addOptionsGroup(QByteArrayLiteral("CCAllowList"), + {bitflags}); +} + +void Msp430CompilerSettingsGroup::buildPreprocessorPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const PreprocessorPageOptions opts(baseDirectory, qbsProduct); + // Add 'CCDefines' item (Defines symbols). + addOptionsGroup(QByteArrayLiteral("CCDefines"), + opts.defineSymbols); + // Add 'newCCIncludePaths' item + // (Additional include directories). + addOptionsGroup(QByteArrayLiteral("newCCIncludePaths"), + opts.includePaths); +} + +void Msp430CompilerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'CCDiagWarnAreErr' item (Treat all warnings as errors). + addOptionsGroup(QByteArrayLiteral("CCDiagWarnAreErr"), + {opts.warningsAsErrors}); +} + +} // namespace v7 +} // namespace msp430 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/msp430/msp430compilersettingsgroup_v7.h b/src/plugins/generator/iarew/archs/msp430/msp430compilersettingsgroup_v7.h new file mode 100644 index 00000000..49810088 --- /dev/null +++ b/src/plugins/generator/iarew/archs/msp430/msp430compilersettingsgroup_v7.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWMSP430COMPILERSETTINGSGROUP_V7_H +#define QBS_IAREWMSP430COMPILERSETTINGSGROUP_V7_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace msp430 { +namespace v7 { + +class Msp430CompilerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Msp430CompilerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildOutputPage(const ProductData &qbsProduct); + void buildLanguageOnePage(const ProductData &qbsProduct); + void buildLanguageTwoPage(const ProductData &qbsProduct); + void buildCodePage(const ProductData &qbsProduct); + void buildOptimizationsPage(const ProductData &qbsProduct); + void buildPreprocessorPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); +}; + +} // namespace v7 +} // namespace msp430 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWMSP430COMPILERSETTINGSGROUP_V7_H diff --git a/src/plugins/generator/iarew/archs/msp430/msp430generalsettingsgroup_v7.cpp b/src/plugins/generator/iarew/archs/msp430/msp430generalsettingsgroup_v7.cpp new file mode 100644 index 00000000..d99e15bb --- /dev/null +++ b/src/plugins/generator/iarew/archs/msp430/msp430generalsettingsgroup_v7.cpp @@ -0,0 +1,482 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "msp430generalsettingsgroup_v7.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace msp430 { +namespace v7 { + +constexpr int kGeneralArchiveVersion = 21; +constexpr int kGeneralDataVersion = 34; + +namespace { + +// Target page options. + +struct TargetPageOptions final +{ + enum CodeModel { + SmallCodeModel, + LargeCodeModel + }; + + enum DataModel { + SmallDataModel, + MediumDataModel, + LargeDataModel + }; + + enum FloatingPointDoubleSize { + DoubleSize32Bits, + DoubleSize64Bits + }; + + enum HardwareMultiplierType { + AllowDirectAccessMultiplier, + UseOnlyLibraryCallsMultiplier + }; + + explicit TargetPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("driverFlags")}); + // Detect target code model. + const QString codeModelValue = IarewUtils::flagValue( + flags, QStringLiteral("--code_model")); + if (codeModelValue == QLatin1String("small")) + codeModel = TargetPageOptions::SmallCodeModel; + else if (codeModelValue == QLatin1String("large")) + codeModel = TargetPageOptions::LargeCodeModel; + // Detect target data model. + const QString dataModelValue = IarewUtils::flagValue( + flags, QStringLiteral("--data_model")); + if (dataModelValue == QLatin1String("small")) + dataModel = TargetPageOptions::SmallDataModel; + else if (dataModelValue == QLatin1String("medium")) + dataModel = TargetPageOptions::MediumDataModel; + else if (dataModelValue == QLatin1String("large")) + dataModel = TargetPageOptions::LargeDataModel; + // Detect floating point double size. + const int doubleSize = IarewUtils::flagValue( + flags, QStringLiteral("--double")).toInt(); + if (doubleSize == 32) + floatingPointDoubleSize = DoubleSize32Bits; + else if (doubleSize == 64) + floatingPointDoubleSize = DoubleSize64Bits; + // Detect hardware multiplier. + const QString multiplier = IarewUtils::flagValue( + flags, QStringLiteral("--multiplier")); + enableHardwareMultiplier = (multiplier.compare(QLatin1String("16")) == 0 + || multiplier.compare(QLatin1String("16s")) == 0 + || multiplier.compare(QLatin1String("32")) == 0); + // Detect code and read-only data position-independence. + enableRopi = flags.contains(QLatin1String("--ropi")); + // No dynamic read-write initialization. + disableDynamicReadWriteInitialization = flags.contains( + QLatin1String("--no_rw_dynamic_init")); + + // Detect target device name. + detectDeviceMenu(qbsProduct); + } + + void detectDeviceMenu(const ProductData &qbsProduct) + { + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + + // Enumerate all product linker config files + // (which are set trough 'linkerscript' tag). + for (const auto &qbsGroup : qbsProduct.groups()) { + const auto qbsArtifacts = qbsGroup.sourceArtifacts(); + for (const auto &qbsArtifact : qbsArtifacts) { + const auto qbsTags = qbsArtifact.fileTags(); + if (!qbsTags.contains(QLatin1String("linkerscript"))) + continue; + const QString fullConfigPath = qbsArtifact.filePath(); + if (!fullConfigPath.startsWith(toolkitPath, Qt::CaseInsensitive)) + continue; + + const QFileInfo configInfo(fullConfigPath); + const QString configBase = configInfo.baseName(); + if (!configBase.startsWith(QLatin1String("lnk"))) + continue; + + // Remove 'lnk' prefix. + const QString deviceName = QStringLiteral("MSP%1") + .arg(configBase.mid(3).toUpper()); + + deviceMenu = QStringLiteral("%1\t%1").arg(deviceName); + return; + } + } + + // Falling back to generic menu. + if (deviceMenu.isEmpty()) { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + const QString cpuCore = IarewUtils::flagValue(flags, QStringLiteral("--core")); + if (cpuCore.isEmpty()) + return; + deviceMenu = QStringLiteral("MSP%1\tGeneric MSP%1 device").arg(cpuCore); + return; + } + } + + CodeModel codeModel = LargeCodeModel; + DataModel dataModel = SmallDataModel; + FloatingPointDoubleSize floatingPointDoubleSize = DoubleSize32Bits; + HardwareMultiplierType hardwareMultiplierType = AllowDirectAccessMultiplier; + int enableHardwareMultiplier = 0; + int enableRopi = 0; + int disableDynamicReadWriteInitialization = 0; + QString deviceMenu; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + binaryType = IarewUtils::outputBinaryType(qbsProduct); + binaryDirectory = gen::utils::binaryOutputDirectory( + baseDirectory, qbsProduct); + objectDirectory = gen::utils::objectsOutputDirectory( + baseDirectory, qbsProduct); + listingDirectory = gen::utils::listingOutputDirectory( + baseDirectory, qbsProduct); + } + + IarewUtils::OutputBinaryType binaryType = IarewUtils::ApplicationOutputType; + QString binaryDirectory; + QString objectDirectory; + QString listingDirectory; +}; + +// Library configuration page options. + +struct LibraryConfigPageOptions final +{ + enum RuntimeLibrary { + NoLibrary, + NormalDLibrary, + FullDLibrary, + CustomDLibrary, + CLibrary, // deprecated + CustomCLibrary, // deprecated + }; + + explicit LibraryConfigPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + + const QFileInfo configInfo(IarewUtils::flagValue( + flags, + QStringLiteral("--dlib_config"))); + const QString configFilePath = configInfo.absoluteFilePath(); + + if (!configFilePath.isEmpty()) { + const QString libToolkitPath = + IarewUtils::libToolkitRootPath(qbsProduct); + + if (configFilePath.startsWith(libToolkitPath, + Qt::CaseInsensitive)) { + if (configFilePath.endsWith(QLatin1String("n.h"), + Qt::CaseInsensitive)) { + libraryType = LibraryConfigPageOptions::NormalDLibrary; + } else if (configFilePath.endsWith(QLatin1String("f.h"), + Qt::CaseInsensitive)) { + libraryType = LibraryConfigPageOptions::FullDLibrary; + } else { + libraryType = LibraryConfigPageOptions::CustomDLibrary; + } + + configPath = IarewUtils::toolkitRelativeFilePath( + baseDirectory, configFilePath); + } else { + libraryType = LibraryConfigPageOptions::CustomDLibrary; + + configPath = configFilePath; + } + } + } + + RuntimeLibrary libraryType = NormalDLibrary; + QString libraryPath; + QString configPath; +}; + +// Library options page options. + +struct LibraryOptionsPageOptions final +{ + enum PrintfFormatter { + PrintfAutoFormatter = 0, + PrintfFullFormatter = 1, + PrintfFullNoMultibytesFormatter = 2, + PrintfLargeFormatter = 3, + PrintfLargeNoMultibytesFormatter = 4, + PrintfSmallFormatter = 5, + PrintfSmallNoMultibytesFormatter = 6, + PrintfTinyFormatter = 7 + }; + + enum ScanfFormatter { + ScanfAutoFormatter = 0, + ScanfFullFormatter = 1, + ScanfFullNoMultibytesFormatter = 2, + ScanfLargeFormatter = 3, + ScanfLargeNoMultibytesFormatter = 4, + ScanfSmallFormatter = 5, + ScanfSmallNoMultibytesFormatter = 6 + }; + + explicit LibraryOptionsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + for (auto flagIt = flags.cbegin(); flagIt < flags.cend(); ++flagIt) { + if (flagIt->endsWith(QLatin1String("=_printf"), + Qt::CaseInsensitive)) { + const QString prop = flagIt->split( + QLatin1Char('=')).at(0).toLower(); + if (prop == QLatin1String("_printffull")) + printfFormatter = PrintfFullFormatter; + else if (prop == QLatin1String("_printffullnomb")) + printfFormatter = PrintfFullNoMultibytesFormatter; + else if (prop == QLatin1String("_printflarge")) + printfFormatter = PrintfLargeFormatter; + else if (prop == QLatin1String("_printflargenomb")) + printfFormatter = PrintfLargeFormatter; + else if (prop == QLatin1String("_printfsmall")) + printfFormatter = PrintfSmallFormatter; + else if (prop == QLatin1String("_printfsmallnomb")) + printfFormatter = PrintfSmallNoMultibytesFormatter; + else if (prop == QLatin1String("_printftiny")) + printfFormatter = PrintfTinyFormatter; + } else if (flagIt->endsWith(QLatin1String("=_scanf"), + Qt::CaseInsensitive)) { + const QString prop = flagIt->split( + QLatin1Char('=')).at(0).toLower(); + if (prop == QLatin1String("_scanffull")) + scanfFormatter = ScanfFullFormatter; + else if (prop == QLatin1String("_scanffullnomb")) + scanfFormatter = ScanfFullNoMultibytesFormatter; + else if (prop == QLatin1String("_scanflarge")) + scanfFormatter = ScanfLargeFormatter; + else if (prop == QLatin1String("_scanflargenomb")) + scanfFormatter = ScanfLargeFormatter; + else if (prop == QLatin1String("_scanfsmall")) + scanfFormatter = ScanfSmallFormatter; + else if (prop == QLatin1String("_scanfsmallnomb")) + scanfFormatter = ScanfSmallNoMultibytesFormatter; + } + } + } + + PrintfFormatter printfFormatter = PrintfAutoFormatter; + ScanfFormatter scanfFormatter = ScanfAutoFormatter; +}; + +// Stack/heap page options. + +struct StackHeapPageOptions final +{ + explicit StackHeapPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + + // Detect stack size. + stackSize = IarewUtils::flagValue( + flags, QStringLiteral("-D_STACK_SIZE")); + if (stackSize.isEmpty()) + stackSize = QLatin1String("A0"); + // Detect data heap16 size. + data16HeapSize = IarewUtils::flagValue( + flags, QStringLiteral("-D_DATA16_HEAP_SIZE")); + if (data16HeapSize.isEmpty()) + stackSize = QLatin1String("A0"); + // Detect data heap20 size. + data20HeapSize = IarewUtils::flagValue( + flags, QStringLiteral("-D_DATA20_HEAP_SIZE")); + if (data20HeapSize.isEmpty()) + stackSize = QLatin1String("50"); + } + + QString stackSize; + QString data16HeapSize; + QString data20HeapSize; +}; + +} // namespace + +//Msp430GeneralSettingsGroup + +Msp430GeneralSettingsGroup::Msp430GeneralSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("General")); + setArchiveVersion(kGeneralArchiveVersion); + setDataVersion(kGeneralDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildTargetPage(qbsProduct); + buildOutputPage(buildRootDirectory, qbsProduct); + buildLibraryConfigPage(buildRootDirectory, qbsProduct); + buildLibraryOptionsPage(qbsProduct); + buildStackHeapPage(qbsProduct); +} + +void Msp430GeneralSettingsGroup::buildTargetPage( + const ProductData &qbsProduct) +{ + const TargetPageOptions opts(qbsProduct); + // Add 'OGChipSelectMenu' item + // (Device: xxx). + addOptionsGroup(QByteArrayLiteral("OGChipSelectMenu"), + {opts.deviceMenu}); + + // Add 'RadioCodeModelType' item + // (Code model: small/large). + addOptionsGroup(QByteArrayLiteral("RadioCodeModelType"), + {opts.codeModel}); + // Add 'RadioDataModelType' item + // (Data model: small/medium/large). + addOptionsGroup(QByteArrayLiteral("RadioDataModelType"), + {opts.dataModel}); + // Add 'OGDouble' item + // (Floating point double size: 32/64 bits). + addOptionsGroup(QByteArrayLiteral("OGDouble"), + {opts.floatingPointDoubleSize}); + // Add 'Hardware Multiplier' item + // (Hardware multiplier). + addOptionsGroup(QByteArrayLiteral("Hardware Multiplier"), + {opts.enableHardwareMultiplier}); + if (opts.enableHardwareMultiplier) { + // Add 'RadioHardwareMultiplierType' item. + addOptionsGroup(QByteArrayLiteral("Hardware RadioHardwareMultiplierType"), + {opts.hardwareMultiplierType}); + } + // Add 'Ropi' item. + // (Position independence: Code and read-only data). + addOptionsGroup(QByteArrayLiteral("Ropi"), + {opts.enableRopi}); + // Add 'NoRwDynamicInit' item. + // (Position independence: No dynamic read/write initialization). + addOptionsGroup(QByteArrayLiteral("NoRwDynamicInit"), + {opts.disableDynamicReadWriteInitialization}); +} + +void Msp430GeneralSettingsGroup::buildOutputPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(baseDirectory, qbsProduct); + // Add 'GOutputBinary' item (Output file: executable/library). + addOptionsGroup(QByteArrayLiteral("GOutputBinary"), + {opts.binaryType}); + // Add 'ExePath' item (Executable/binaries output directory). + addOptionsGroup(QByteArrayLiteral("ExePath"), + {opts.binaryDirectory}); + // Add 'ObjPath' item (Object files output directory). + addOptionsGroup(QByteArrayLiteral("ObjPath"), + {opts.objectDirectory}); + // Add 'ListPath' item (List files output directory). + addOptionsGroup(QByteArrayLiteral("ListPath"), + {opts.listingDirectory}); +} + +void Msp430GeneralSettingsGroup::buildLibraryConfigPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const LibraryConfigPageOptions opts(baseDirectory, qbsProduct); + // Add 'GRuntimeLibSelect' and 'GRuntimeLibSelectSlave' items + // (Link with runtime: none/normal/full/custom). + addOptionsGroup(QByteArrayLiteral("GRuntimeLibSelect"), + {opts.libraryType}); + addOptionsGroup(QByteArrayLiteral("GRuntimeLibSelectSlave"), + {opts.libraryType}); + // Add 'RTConfigPath' item (Runtime configuration file). + addOptionsGroup(QByteArrayLiteral("RTConfigPath"), + {opts.configPath}); + // Add 'RTLibraryPath' item (Runtime library file). + addOptionsGroup(QByteArrayLiteral("RTLibraryPath"), + {opts.libraryPath}); +} + +void Msp430GeneralSettingsGroup::buildLibraryOptionsPage( + const ProductData &qbsProduct) +{ + const LibraryOptionsPageOptions opts(qbsProduct); + // Add 'Output variant' item (Printf formatter). + addOptionsGroup(QByteArrayLiteral("Output variant"), + {opts.printfFormatter}); + // Add 'Input variant' item (Scanf formatter). + addOptionsGroup(QByteArrayLiteral("Input variant"), + {opts.scanfFormatter}); +} + +void Msp430GeneralSettingsGroup::buildStackHeapPage( + const ProductData &qbsProduct) +{ + const StackHeapPageOptions opts(qbsProduct); + // Add 'GStackHeapOverride' item (Override default). + addOptionsGroup(QByteArrayLiteral("GStackHeapOverride"), + {1}); + // Add 'GStackSize2' item (Stack size). + addOptionsGroup(QByteArrayLiteral("GStackSize2"), + {opts.stackSize}); + // Add 'GHeapSize2' item (Heap16 size). + addOptionsGroup(QByteArrayLiteral("GHeapSize2"), + {opts.data16HeapSize}); + // Add 'GHeap20Size' item (Heap16 size). + addOptionsGroup(QByteArrayLiteral("GHeap20Size"), + {opts.data20HeapSize}); +} + +} // namespace v7 +} // namespace msp430 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/msp430/msp430generalsettingsgroup_v7.h b/src/plugins/generator/iarew/archs/msp430/msp430generalsettingsgroup_v7.h new file mode 100644 index 00000000..35e9e7b3 --- /dev/null +++ b/src/plugins/generator/iarew/archs/msp430/msp430generalsettingsgroup_v7.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWMSP430GENERALSETTINGSGROUP_V7_H +#define QBS_IAREWMSP430GENERALSETTINGSGROUP_V7_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace msp430 { +namespace v7 { + +class Msp430GeneralSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Msp430GeneralSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildTargetPage(const ProductData &qbsProduct); + void buildOutputPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildLibraryConfigPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildLibraryOptionsPage(const ProductData &qbsProduct); + void buildStackHeapPage(const ProductData &qbsProduct); +}; + +} // namespace v7 +} // namespace msp430 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWMSP430GENERALSETTINGSGROUP_V7_H diff --git a/src/plugins/generator/iarew/archs/msp430/msp430linkersettingsgroup_v7.cpp b/src/plugins/generator/iarew/archs/msp430/msp430linkersettingsgroup_v7.cpp new file mode 100644 index 00000000..85aac7e8 --- /dev/null +++ b/src/plugins/generator/iarew/archs/msp430/msp430linkersettingsgroup_v7.cpp @@ -0,0 +1,289 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "msp430linkersettingsgroup_v7.h" + +#include "../../iarewutils.h" + +#include + +namespace qbs { +namespace iarew { +namespace msp430 { +namespace v7 { + +constexpr int kLinkerArchiveVersion = 4; +constexpr int kLinkerDataVersion = 30; + +namespace { + +// Config page options. + +struct ConfigPageOptions final +{ + explicit ConfigPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + + // Enumerate all product linker config files + // (which are set trough 'linkerscript' tag). + for (const auto &qbsGroup : qbsProduct.groups()) { + const auto qbsArtifacts = qbsGroup.sourceArtifacts(); + for (const auto &qbsArtifact : qbsArtifacts) { + const auto qbsTags = qbsArtifact.fileTags(); + if (!qbsTags.contains(QLatin1String("linkerscript"))) + continue; + const QString fullConfigPath = qbsArtifact.filePath(); + if (fullConfigPath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullConfigPath); + configFilePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullConfigPath); + configFilePaths.push_back(path); + } + } + } + + // Enumerate all product linker config files + // (which are set trough '-f' option). + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + const QVariantList configPathValues = IarewUtils::flagValues( + flags, QStringLiteral("-f")); + for (const QVariant &configPathValue : configPathValues) { + const QString fullConfigPath = configPathValue.toString(); + if (fullConfigPath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullConfigPath); + if (!configFilePaths.contains(path)) + configFilePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullConfigPath); + if (!configFilePaths.contains(path)) + configFilePaths.push_back(path); + } + } + + // Library search paths. + librarySearchPaths = gen::utils::cppVariantModuleProperties( + qbsProps, {QStringLiteral("libraryPaths")}); + + // Entry point. + entryPoint = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("entryPoint")); + } + + QVariantList configFilePaths; + QVariantList librarySearchPaths; + QString entryPoint; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + outputFile = gen::utils::targetBinary(qbsProduct); + } + + QString outputFile; +}; + +// List page options. + +struct ListPageOptions final +{ + enum ListingAction { + NoListing, + GenerateListing + }; + + explicit ListPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + generateMap = gen::utils::cppBooleanModuleProperty( + qbsProps, QStringLiteral("generateLinkerMapFile")) + ? ListPageOptions::GenerateListing + : ListPageOptions::NoListing; + } + + ListingAction generateMap = NoListing; +}; + +// Define page options. + +struct DefinePageOptions final +{ + explicit DefinePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + // Enumerate all linker defines. + for (const QString &flag : flags) { + if (!flag.startsWith(QLatin1String("-D"))) + continue; + const QString symbol = flag.mid(2); + // Ignore system-defined macroses, because its already + // handled in "General Options" page. + if (symbol.startsWith(QLatin1Char('?')) + || symbol.startsWith(QLatin1Char('_')) + ) { + continue; + } + defineSymbols.push_back(symbol); + } + } + + QVariantList defineSymbols; +}; + +} // namespace + +//Msp430LinkerSettingsGroup + +Msp430LinkerSettingsGroup::Msp430LinkerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + setName(QByteArrayLiteral("XLINK")); + setArchiveVersion(kLinkerArchiveVersion); + setDataVersion(kLinkerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildConfigPage(buildRootDirectory, qbsProduct); + buildOutputPage(qbsProduct); + buildListPage(qbsProduct); + buildDefinePage(qbsProduct); + + // Should be called as latest stage! + buildExtraOptionsPage(qbsProduct); +} + +void Msp430LinkerSettingsGroup::buildConfigPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + ConfigPageOptions opts(baseDirectory, qbsProduct); + + if (opts.configFilePaths.count() > 0) { + // Note: IAR IDE does not allow to specify a multiple config files, + // although the IAR linker support it. So, we use followig 'trick': + // we take a first config file and to add it as usual to required items; + // and then an other remainders we forward to the "Extra options page". + const QVariant configPath = opts.configFilePaths.takeFirst(); + // Add 'XclOverride' item (Override default). + addOptionsGroup(QByteArrayLiteral("XclOverride"), + {1}); + // Add 'XclFile' item (Linke configuration file). + addOptionsGroup(QByteArrayLiteral("XclFile"), + {configPath}); + + // Add remainder configuration files to the "Extra options page". + if (!opts.configFilePaths.isEmpty()) { + for (QVariant &configPath : opts.configFilePaths) + configPath = QLatin1String("-f ") + configPath.toString(); + + m_extraOptions << opts.configFilePaths; + } + } + + // Add 'xcOverrideProgramEntryLabel' item + // (Override default program entry). + addOptionsGroup(QByteArrayLiteral("xcOverrideProgramEntryLabel"), + {1}); + // Add 'xcProgramEntryLabel' item (Entry point name). + addOptionsGroup(QByteArrayLiteral("xcProgramEntryLabel"), + {opts.entryPoint}); + + // Add 'XIncludes' item (Entry point name). + addOptionsGroup(QByteArrayLiteral("XIncludes"), + {opts.librarySearchPaths}); +} + +void Msp430LinkerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + + // Add 'XOutOverride' item (Output file name). + addOptionsGroup(QByteArrayLiteral("XOutOverride"), + {1}); + // Add 'OutputFile' item (Output file name). + addOptionsGroup(QByteArrayLiteral("OutputFile"), + {opts.outputFile}); +} + +void Msp430LinkerSettingsGroup::buildListPage( + const ProductData &qbsProduct) +{ + const ListPageOptions opts(qbsProduct); + // Add 'XList' item (Generate linker map file). + addOptionsGroup(QByteArrayLiteral("XList"), + {opts.generateMap}); +} + +void Msp430LinkerSettingsGroup::buildDefinePage( + const ProductData &qbsProduct) +{ + const DefinePageOptions opts(qbsProduct); + // Add 'XDefines' item (Defined symbols). + addOptionsGroup(QByteArrayLiteral("XDefines"), + opts.defineSymbols); +} + +void Msp430LinkerSettingsGroup::buildExtraOptionsPage( + const ProductData &qbsProduct) +{ + Q_UNUSED(qbsProduct) + + if (m_extraOptions.isEmpty()) + return; + + // Add 'XExtraOptionsCheck' (Use command line options). + addOptionsGroup(QByteArrayLiteral("XExtraOptionsCheck"), + {1}); + // Add 'XExtraOptions' item (Command line options). + addOptionsGroup(QByteArrayLiteral("XExtraOptions"), + m_extraOptions); +} + +} // namespace v7 +} // namespace msp430 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/msp430/msp430linkersettingsgroup_v7.h b/src/plugins/generator/iarew/archs/msp430/msp430linkersettingsgroup_v7.h new file mode 100644 index 00000000..2b6f1151 --- /dev/null +++ b/src/plugins/generator/iarew/archs/msp430/msp430linkersettingsgroup_v7.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWMSP430LINKERSETTINGSGROUP_V7_H +#define QBS_IAREWMSP430LINKERSETTINGSGROUP_V7_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace msp430 { +namespace v7 { + +class Msp430LinkerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Msp430LinkerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildConfigPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildOutputPage(const ProductData &qbsProduct); + void buildListPage(const ProductData &qbsProduct); + void buildDefinePage(const ProductData &qbsProduct); + void buildExtraOptionsPage(const ProductData &qbsProduct); + + QVariantList m_extraOptions; +}; + +} // namespace v7 +} // namespace msp430 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWMSP430LINKERSETTINGSGROUP_V7_H diff --git a/src/plugins/generator/iarew/archs/stm8/stm8archiversettingsgroup_v3.cpp b/src/plugins/generator/iarew/archs/stm8/stm8archiversettingsgroup_v3.cpp new file mode 100644 index 00000000..b08b70bc --- /dev/null +++ b/src/plugins/generator/iarew/archs/stm8/stm8archiversettingsgroup_v3.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "stm8archiversettingsgroup_v3.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace stm8 { +namespace v3 { + +constexpr int kArchiverArchiveVersion = 3; +constexpr int kArchiverDataVersion = 0; + +namespace { + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + outputFile = QLatin1String("$PROJ_DIR$/") + + gen::utils::targetBinaryPath(baseDirectory, qbsProduct); + } + + QString outputFile; +}; + +} // namespace + +// Stm8ArchiverSettingsGroup + +Stm8ArchiverSettingsGroup::Stm8ArchiverSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("IARCHIVE")); + setArchiveVersion(kArchiverArchiveVersion); + setDataVersion(kArchiverDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + buildOutputPage(buildRootDirectory, qbsProduct); +} + +void Stm8ArchiverSettingsGroup::buildOutputPage(const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(baseDirectory, qbsProduct); + // Add 'IarchiveOverride' item (Override default). + addOptionsGroup(QByteArrayLiteral("IarchiveOverride"), + {1}); + // Add 'IarchiveOutput' item (Output filename). + addOptionsGroup(QByteArrayLiteral("IarchiveOutput"), + {opts.outputFile}); +} + +} // namespace v3 +} // namespace stm8 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/stm8/stm8archiversettingsgroup_v3.h b/src/plugins/generator/iarew/archs/stm8/stm8archiversettingsgroup_v3.h new file mode 100644 index 00000000..754add3c --- /dev/null +++ b/src/plugins/generator/iarew/archs/stm8/stm8archiversettingsgroup_v3.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWSTM8ARCHIVERSETTINGSGROUP_V3_H +#define QBS_IAREWSTM8ARCHIVERSETTINGSGROUP_V3_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace stm8 { +namespace v3 { + +class Stm8ArchiverSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Stm8ArchiverSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildOutputPage(const QString &baseDirectory, + const ProductData &qbsProduct); +}; + +} // namespace v3 +} // namespace stm8 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWSTM8ARCHIVERSETTINGSGROUP_V3_H diff --git a/src/plugins/generator/iarew/archs/stm8/stm8assemblersettingsgroup_v3.cpp b/src/plugins/generator/iarew/archs/stm8/stm8assemblersettingsgroup_v3.cpp new file mode 100644 index 00000000..396ee7db --- /dev/null +++ b/src/plugins/generator/iarew/archs/stm8/stm8assemblersettingsgroup_v3.cpp @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "stm8assemblersettingsgroup_v3.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace stm8 { +namespace v3 { + +constexpr int kAssemblerArchiveVersion = 3; +constexpr int kAssemblerDataVersion = 2; + +namespace { + +// Language page options. + +struct LanguagePageOptions final +{ + enum MacroQuoteCharacter { + AngleBracketsQuote, + RoundBracketsQuote, + SquareBracketsQuote, + FigureBracketsQuote + }; + + explicit LanguagePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("assemblerFlags")}); + enableSymbolsCaseSensitive = !flags.contains( + QLatin1String("--case_insensitive")); + enableMultibyteSupport = flags.contains( + QLatin1String("--enable_multibytes")); + allowFirstColumnMnemonics = flags.contains( + QLatin1String("--mnem_first")); + allowFirstColumnDirectives = flags.contains( + QLatin1String("--dir_first")); + + if (flags.contains(QLatin1String("-M<>"))) + macroQuoteCharacter = LanguagePageOptions::AngleBracketsQuote; + else if (flags.contains(QLatin1String("-M()"))) + macroQuoteCharacter = LanguagePageOptions::RoundBracketsQuote; + else if (flags.contains(QLatin1String("-M[]"))) + macroQuoteCharacter = LanguagePageOptions::SquareBracketsQuote; + else if (flags.contains(QLatin1String("-M{}"))) + macroQuoteCharacter = LanguagePageOptions::FigureBracketsQuote; + } + + int enableSymbolsCaseSensitive = 1; + int enableMultibyteSupport = 0; + int allowFirstColumnMnemonics = 0; + int allowFirstColumnDirectives = 0; + + MacroQuoteCharacter macroQuoteCharacter = AngleBracketsQuote; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + debugInfo = gen::utils::debugInformation(qbsProduct); + } + + int debugInfo = 0; +}; + +// Preprocessor page options. + +struct PreprocessorPageOptions final +{ + explicit PreprocessorPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + defineSymbols = gen::utils::cppVariantModuleProperties( + qbsProps, {QStringLiteral("defines")}); + + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + const QStringList fullIncludePaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("includePaths"), + QStringLiteral("systemIncludePaths")}); + for (const auto &fullIncludePath : fullIncludePaths) { + const QFileInfo includeFileInfo(fullIncludePath); + const QString includeFilePath = includeFileInfo.absoluteFilePath(); + if (includeFilePath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, includeFilePath); + includePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, includeFilePath); + includePaths.push_back(path); + } + } + } + + QVariantList defineSymbols; + QVariantList includePaths; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + warningsAsErrors = gen::utils::cppIntegerModuleProperty( + qbsProps, QStringLiteral("treatWarningsAsErrors")); + } + + int warningsAsErrors = 0; +}; + +} // namespace + +// Stm8AssemblerSettingsGroup + +Stm8AssemblerSettingsGroup::Stm8AssemblerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("ASTM8")); + setArchiveVersion(kAssemblerArchiveVersion); + setDataVersion(kAssemblerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildLanguagePage(qbsProduct); + buildOutputPage(qbsProduct); + buildPreprocessorPage(buildRootDirectory, qbsProduct); + buildDiagnosticsPage(qbsProduct); +} + +void Stm8AssemblerSettingsGroup::buildLanguagePage( + const ProductData &qbsProduct) +{ + const LanguagePageOptions opts(qbsProduct); + // Add 'AsmCaseSensitivity' item (User symbols are case sensitive). + addOptionsGroup(QByteArrayLiteral("AsmCaseSensitivity"), + {opts.enableSymbolsCaseSensitive}); + // Add 'AsmMultibyteSupport' item (Enable multibyte support). + addOptionsGroup(QByteArrayLiteral("AsmMultibyteSupport"), + {opts.enableMultibyteSupport}); + // Add 'AsmAllowMnemonics' item (Allow mnemonics in first column). + addOptionsGroup(QByteArrayLiteral("AsmAllowMnemonics"), + {opts.allowFirstColumnMnemonics}); + // Add 'AsmAllowDirectives' item (Allow directives in first column). + addOptionsGroup(QByteArrayLiteral("AsmAllowDirectives"), + {opts.allowFirstColumnDirectives}); + + // Add 'AsmMacroChars' item (Macro quote characters: ()/[]/{}/<>). + addOptionsGroup(QByteArrayLiteral("AsmMacroChars"), + {opts.macroQuoteCharacter}); +} + +void Stm8AssemblerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + // Add 'AsmDebugInfo' item (Generate debug information). + addOptionsGroup(QByteArrayLiteral("AsmDebugInfo"), + {opts.debugInfo}); +} + +void Stm8AssemblerSettingsGroup::buildPreprocessorPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const PreprocessorPageOptions opts(baseDirectory, qbsProduct); + // Add 'AsmDefines' item (Defined symbols). + addOptionsGroup(QByteArrayLiteral("AsmDefines"), + opts.defineSymbols); + // Add 'AsmIncludePath' item (Additional include directories). + addOptionsGroup(QByteArrayLiteral("AsmIncludePath"), + opts.includePaths); +} + +void Stm8AssemblerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'AsmDiagnosticsWarningsAreErrors' item. + // (Treat all warnings as errors). + addOptionsGroup(QByteArrayLiteral("AsmDiagnosticsWarningsAreErrors"), + {opts.warningsAsErrors}); +} + +} // namespace v3 +} // namespace stm8 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/stm8/stm8assemblersettingsgroup_v3.h b/src/plugins/generator/iarew/archs/stm8/stm8assemblersettingsgroup_v3.h new file mode 100644 index 00000000..4d7d0485 --- /dev/null +++ b/src/plugins/generator/iarew/archs/stm8/stm8assemblersettingsgroup_v3.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWSTM8ASSEMBLERSETTINGSGROUP_V3_H +#define QBS_IAREWSTM8ASSEMBLERSETTINGSGROUP_V3_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace stm8 { +namespace v3 { + +class Stm8AssemblerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Stm8AssemblerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildLanguagePage(const ProductData &qbsProduct); + void buildOutputPage(const ProductData &qbsProduct); + void buildPreprocessorPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); +}; + +} // namespace v3 +} // namespace stm8 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWSTM8ASSEMBLERSETTINGSGROUP_V3_H diff --git a/src/plugins/generator/iarew/archs/stm8/stm8buildconfigurationgroup_v3.cpp b/src/plugins/generator/iarew/archs/stm8/stm8buildconfigurationgroup_v3.cpp new file mode 100644 index 00000000..06167f91 --- /dev/null +++ b/src/plugins/generator/iarew/archs/stm8/stm8buildconfigurationgroup_v3.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "stm8archiversettingsgroup_v3.h" +#include "stm8assemblersettingsgroup_v3.h" +#include "stm8buildconfigurationgroup_v3.h" +#include "stm8compilersettingsgroup_v3.h" +#include "stm8generalsettingsgroup_v3.h" +#include "stm8linkersettingsgroup_v3.h" + +#include "../../iarewtoolchainpropertygroup.h" +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace stm8 { +namespace v3 { + +Stm8BuildConfigurationGroup::Stm8BuildConfigurationGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) + : gen::xml::PropertyGroup("configuration") +{ + // Append configuration name item. + const QString cfgName = gen::utils::buildConfigurationName(qbsProject); + appendProperty("name", cfgName); + + // Apend toolchain name group item. + appendChild("STM8"); + + // Append debug info item. + const int debugBuild = gen::utils::debugInformation(qbsProduct); + appendProperty("debug", debugBuild); + + // Append settings group items. + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); + appendChild( + qbsProject, qbsProduct, qbsProductDeps); +} + +bool Stm8BuildConfigurationGroupFactory::canCreate( + gen::utils::Architecture arch, + const Version &version) const +{ + return arch == gen::utils::Architecture::Stm8 + && version.majorVersion() == 3; +} + +std::unique_ptr +Stm8BuildConfigurationGroupFactory::create( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) const +{ + const auto group = new Stm8BuildConfigurationGroup( + qbsProject, qbsProduct, qbsProductDeps); + return std::unique_ptr(group); +} + +} // namespace v3 +} // namespace stm8 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/stm8/stm8buildconfigurationgroup_v3.h b/src/plugins/generator/iarew/archs/stm8/stm8buildconfigurationgroup_v3.h new file mode 100644 index 00000000..c47819fe --- /dev/null +++ b/src/plugins/generator/iarew/archs/stm8/stm8buildconfigurationgroup_v3.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWSTM8BUILDCONFIGURATIONGROUP_V3_H +#define QBS_IAREWSTM8BUILDCONFIGURATIONGROUP_V3_H + +#include +#include + +namespace qbs { +namespace iarew { +namespace stm8 { +namespace v3 { + +class Stm8BuildConfigurationGroup final + : public gen::xml::PropertyGroup +{ +private: + explicit Stm8BuildConfigurationGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + + friend class Stm8BuildConfigurationGroupFactory; +}; + +class Stm8BuildConfigurationGroupFactory final + : public gen::xml::PropertyGroupFactory +{ +public: + bool canCreate(gen::utils::Architecture arch, + const Version &version) const final; + + std::unique_ptr create( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) const final; +}; + +} // namespace v3 +} // namespace stm8 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWSTM8BUILDCONFIGURATIONGROUP_V3_H diff --git a/src/plugins/generator/iarew/archs/stm8/stm8compilersettingsgroup_v3.cpp b/src/plugins/generator/iarew/archs/stm8/stm8compilersettingsgroup_v3.cpp new file mode 100644 index 00000000..912d94bd --- /dev/null +++ b/src/plugins/generator/iarew/archs/stm8/stm8compilersettingsgroup_v3.cpp @@ -0,0 +1,442 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "stm8compilersettingsgroup_v3.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace stm8 { +namespace v3 { + +constexpr int kCompilerArchiveVersion = 3; +constexpr int kCompilerDataVersion = 9; + +namespace { + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + debugInfo = gen::utils::debugInformation(qbsProduct); + } + + int debugInfo = 0; +}; + +// Language one page options. + +struct LanguageOnePageOptions final +{ + enum LanguageExtension { + CLanguageExtension, + CxxLanguageExtension, + AutoLanguageExtension + }; + + enum CLanguageDialect { + C89LanguageDialect, + C99LanguageDialect + }; + + enum CxxLanguageDialect { + EmbeddedCPlusPlus, + ExtendedEmbeddedCPlusPlus + }; + + enum LanguageConformance { + AllowIarExtension, + RelaxedStandard, + StrictStandard + }; + + explicit LanguageOnePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + // File extension based by default. + languageExtension = LanguageOnePageOptions::AutoLanguageExtension; + // C language dialect. + const QStringList cLanguageVersion = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("cLanguageVersion")}); + if (cLanguageVersion.contains(QLatin1String("c89"))) + cLanguageDialect = LanguageOnePageOptions::C89LanguageDialect; + else if (cLanguageVersion.contains(QLatin1String("c99"))) + cLanguageDialect = LanguageOnePageOptions::C99LanguageDialect; + // C++ language dialect. + if (flags.contains(QLatin1String("--ec++"))) + cxxLanguageDialect = LanguageOnePageOptions::EmbeddedCPlusPlus; + else if (flags.contains(QLatin1String("--eec++"))) + cxxLanguageDialect = LanguageOnePageOptions::ExtendedEmbeddedCPlusPlus; + // Language conformance. + if (flags.contains(QLatin1String("-e"))) + languageConformance = LanguageOnePageOptions::AllowIarExtension; + else if (flags.contains(QLatin1String("--strict"))) + languageConformance = LanguageOnePageOptions::StrictStandard; + else + languageConformance = LanguageOnePageOptions::RelaxedStandard; + + allowVla = flags.contains(QLatin1String("--vla")); + useCppInlineSemantics = flags.contains( + QLatin1String("--use_c++_inline")); + requirePrototypes = flags.contains( + QLatin1String("--require_prototypes")); + destroyStaticObjects = !flags.contains( + QLatin1String("--no_static_destruction")); + } + + LanguageExtension languageExtension = AutoLanguageExtension; + CLanguageDialect cLanguageDialect = C99LanguageDialect; + CxxLanguageDialect cxxLanguageDialect = EmbeddedCPlusPlus; + LanguageConformance languageConformance = AllowIarExtension; + int allowVla = 0; + int useCppInlineSemantics = 0; + int requirePrototypes = 0; + int destroyStaticObjects = 0; +}; + +// Language two page options. + +struct LanguageTwoPageOptions final +{ + enum PlainCharacter { + SignedCharacter, + UnsignedCharacter + }; + + enum FloatingPointSemantic { + StrictSemantic, + RelaxedSemantic + }; + + explicit LanguageTwoPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + plainCharacter = flags.contains(QLatin1String("--char_is_signed")) + ? LanguageTwoPageOptions::SignedCharacter + : LanguageTwoPageOptions::UnsignedCharacter; + floatingPointSemantic = flags.contains(QLatin1String("--relaxed_fp")) + ? LanguageTwoPageOptions::RelaxedSemantic + : LanguageTwoPageOptions::StrictSemantic; + enableMultibyteSupport = flags.contains( + QLatin1String("--enable_multibytes")); + } + + PlainCharacter plainCharacter = UnsignedCharacter; + FloatingPointSemantic floatingPointSemantic = StrictSemantic; + int enableMultibyteSupport = 0; +}; + +// Optimizations page options. + +struct OptimizationsPageOptions final +{ + // Optimizations level radio-buttons with + // combo-box on "level" widget. + enum Strategy { + StrategyBalanced, + StrategySize, + StrategySpeed + }; + + enum Level { + LevelNone, + LevelLow, + LevelMedium, + LevelHigh + }; + + enum LevelSlave { + LevelSlave0, + LevelSlave1, + LevelSlave2, + LevelSlave3 + }; + + enum VRegsNumber { + VRegs12, + VRegs16 + }; + + explicit OptimizationsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString optimization = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("optimization")); + if (optimization == QLatin1String("none")) { + optimizationStrategy = OptimizationsPageOptions::StrategyBalanced; + optimizationLevel = OptimizationsPageOptions::LevelNone; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave0; + } else if (optimization == QLatin1String("fast")) { + optimizationStrategy = OptimizationsPageOptions::StrategySpeed; + optimizationLevel = OptimizationsPageOptions::LevelHigh; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave3; + } else if (optimization == QLatin1String("small")) { + optimizationStrategy = OptimizationsPageOptions::StrategySize; + optimizationLevel = OptimizationsPageOptions::LevelHigh; + optimizationLevelSlave = OptimizationsPageOptions::LevelSlave3; + } + + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + + disableSizeConstraints = flags.contains( + QLatin1String("--no_size_constraints")); + + enableCommonSubexpressionElimination = !flags.contains( + QLatin1String("--no_cse")); + enableLoopUnroll = !flags.contains(QLatin1String("--no_unroll")); + enableFunctionInlining = !flags.contains(QLatin1String("--no_inline")); + enableCodeMotion = !flags.contains(QLatin1String("--no_code_motion")); + enableTypeBasedAliasAnalysis = !flags.contains( + QLatin1String("--no_tbaa")); + enableCrossCall = !flags.contains(QLatin1String("--no_cross_call")); + + const auto vregsCount = IarewUtils::flagValue( + flags, QStringLiteral("--vregs")).toInt(); + if (vregsCount == 12) + vregsNumber = VRegs12; + else if (vregsCount == 16) + vregsNumber = VRegs16; + } + + Strategy optimizationStrategy = StrategyBalanced; + Level optimizationLevel = LevelNone; + LevelSlave optimizationLevelSlave = LevelSlave0; + // Separate "no size constraints" checkbox. + int disableSizeConstraints = 0; + + // Six bit-field flags on "enabled transformations" widget. + int enableCommonSubexpressionElimination = 0; // Common sub-expression elimination. + int enableLoopUnroll = 0; // Loop unrolling. + int enableFunctionInlining = 0; // Function inlining. + int enableCodeMotion = 0; // Code motion. + int enableTypeBasedAliasAnalysis = 0; // Type based alias analysis. + int enableCrossCall = 0; // Cross call. + + VRegsNumber vregsNumber = VRegs16; +}; + +// Preprocessor page options. + +struct PreprocessorPageOptions final +{ + explicit PreprocessorPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + defineSymbols = gen::utils::cppVariantModuleProperties( + qbsProps, {QStringLiteral("defines")}); + + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + const QStringList fullIncludePaths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("includePaths"), + QStringLiteral("systemIncludePaths")}); + for (const QString &fullIncludePath : fullIncludePaths) { + const QFileInfo includeFileInfo(fullIncludePath); + const QString includeFilePath = includeFileInfo.absoluteFilePath(); + if (includeFilePath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, includeFilePath); + includePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, includeFilePath); + includePaths.push_back(path); + } + } + } + + QVariantList defineSymbols; + QVariantList includePaths; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + warningsAsErrors = gen::utils::cppIntegerModuleProperty( + qbsProps, QStringLiteral("treatWarningsAsErrors")); + } + + int warningsAsErrors = 0; +}; + +} // namespace + +// Stm8CompilerSettingsGroup + +Stm8CompilerSettingsGroup::Stm8CompilerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("ICCSTM8")); + setArchiveVersion(kCompilerArchiveVersion); + setDataVersion(kCompilerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildOutputPage(qbsProduct); + buildLanguageOnePage(qbsProduct); + buildLanguageTwoPage(qbsProduct); + buildOptimizationsPage(qbsProduct); + buildPreprocessorPage(buildRootDirectory, qbsProduct); + buildDiagnosticsPage(qbsProduct); +} + +void Stm8CompilerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + // Add 'IccGenerateDebugInfo' item (Generate debug info). + addOptionsGroup(QByteArrayLiteral("IccGenerateDebugInfo"), + {opts.debugInfo}); +} + +void Stm8CompilerSettingsGroup::buildLanguageOnePage( + const ProductData &qbsProduct) +{ + const LanguageOnePageOptions opts(qbsProduct); + // Add 'IccLang' item with 'auto-extension based' + // value (Language: C/C++/Auto). + addOptionsGroup(QByteArrayLiteral("IccLang"), + {opts.languageExtension}); + // Add 'IccCDialect' item (C dialect: c89/99/11). + addOptionsGroup(QByteArrayLiteral("IccCDialect"), + {opts.cLanguageDialect}); + // Add 'IccCppDialect' item (C++ dialect: embedded/extended). + addOptionsGroup(QByteArrayLiteral("IccCppDialect"), + {opts.cxxLanguageDialect}); + // Add 'IccLanguageConformance' item + // (Language conformance: IAR/relaxed/strict). + addOptionsGroup(QByteArrayLiteral("IccLanguageConformance"), + {opts.languageConformance}); + // Add 'IccAllowVLA' item (Allow VLA). + addOptionsGroup(QByteArrayLiteral("IccAllowVLA"), + {opts.allowVla}); + // Add 'IccCppInlineSemantics' item (C++ inline semantics). + addOptionsGroup(QByteArrayLiteral("IccCppInlineSemantics"), + {opts.useCppInlineSemantics}); + // Add 'IccRequirePrototypes' item (Require prototypes). + addOptionsGroup(QByteArrayLiteral("IccRequirePrototypes"), + {opts.requirePrototypes}); + // Add 'IccStaticDestr' item (Destroy static objects). + addOptionsGroup(QByteArrayLiteral("IccStaticDestr"), + {opts.destroyStaticObjects}); +} + +void Stm8CompilerSettingsGroup::buildLanguageTwoPage( + const ProductData &qbsProduct) +{ + const LanguageTwoPageOptions opts(qbsProduct); + // Add 'IccCharIs' item (Plain char is: signed/unsigned). + addOptionsGroup(QByteArrayLiteral("IccCharIs"), + {opts.plainCharacter}); + // Add 'IccFloatSemantics' item (Floatic-point + // semantics: strict/relaxed conformance). + addOptionsGroup(QByteArrayLiteral("IccFloatSemantics"), + {opts.floatingPointSemantic}); + // Add 'IccMultibyteSupport' item (Enable multibyte support). + addOptionsGroup(QByteArrayLiteral("IccMultibyteSupport"), + {opts.enableMultibyteSupport}); +} + +void Stm8CompilerSettingsGroup::buildOptimizationsPage( + const ProductData &qbsProduct) +{ + const OptimizationsPageOptions opts(qbsProduct); + // Add 'IccOptStrategy', 'IccOptLevel' and + // 'CCOptLevelSlave' items (Level). + addOptionsGroup(QByteArrayLiteral("IccOptStrategy"), + {opts.optimizationStrategy}); + addOptionsGroup(QByteArrayLiteral("IccOptLevel"), + {opts.optimizationLevel}); + addOptionsGroup(QByteArrayLiteral("IccOptLevelSlave"), + {opts.optimizationLevelSlave}); + + // Add 'IccOptNoSizeConstraints' iten (no size constraints). + addOptionsGroup(QByteArrayLiteral("IccOptNoSizeConstraints"), + {opts.disableSizeConstraints}); + + // Add 'IccOptAllowList' item + // (Enabled optimizations: 6 check boxes). + const QString bitflags = QStringLiteral("%1%2%3%4%5%6") + .arg(opts.enableCommonSubexpressionElimination) + .arg(opts.enableLoopUnroll) + .arg(opts.enableFunctionInlining) + .arg(opts.enableCodeMotion) + .arg(opts.enableTypeBasedAliasAnalysis) + .arg(opts.enableCrossCall); + addOptionsGroup(QByteArrayLiteral("IccOptAllowList"), + {bitflags}); + + // Add 'IccNoVregs' item + // (Number of virtual registers (12/16). + addOptionsGroup(QByteArrayLiteral("IccNoVregs"), + {opts.vregsNumber}); +} + +void Stm8CompilerSettingsGroup::buildPreprocessorPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const PreprocessorPageOptions opts(baseDirectory, qbsProduct); + // Add 'CCDefines' item (Defines symbols). + addOptionsGroup(QByteArrayLiteral("CCDefines"), + opts.defineSymbols); + // Add 'CCIncludePath2' item + // (Additional include directories). + addOptionsGroup(QByteArrayLiteral("CCIncludePath2"), + opts.includePaths); +} + +void Stm8CompilerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'CCDiagWarnAreErr' item (Treat all warnings as errors). + addOptionsGroup(QByteArrayLiteral("CCDiagWarnAreErr"), + {opts.warningsAsErrors}); +} + +} // namespace v3 +} // namespace stm8 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/stm8/stm8compilersettingsgroup_v3.h b/src/plugins/generator/iarew/archs/stm8/stm8compilersettingsgroup_v3.h new file mode 100644 index 00000000..29bb5c2b --- /dev/null +++ b/src/plugins/generator/iarew/archs/stm8/stm8compilersettingsgroup_v3.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWSTM8COMPILERSETTINGSGROUP_V3_H +#define QBS_IAREWSTM8COMPILERSETTINGSGROUP_V3_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace stm8 { +namespace v3 { + +class Stm8CompilerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Stm8CompilerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildOutputPage(const ProductData &qbsProduct); + void buildLanguageOnePage(const ProductData &qbsProduct); + void buildLanguageTwoPage(const ProductData &qbsProduct); + void buildOptimizationsPage(const ProductData &qbsProduct); + void buildPreprocessorPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); +}; + +} // namespace v3 +} // namespace stm8 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWSTM8COMPILERSETTINGSGROUP_V3_H diff --git a/src/plugins/generator/iarew/archs/stm8/stm8generalsettingsgroup_v3.cpp b/src/plugins/generator/iarew/archs/stm8/stm8generalsettingsgroup_v3.cpp new file mode 100644 index 00000000..9477bc36 --- /dev/null +++ b/src/plugins/generator/iarew/archs/stm8/stm8generalsettingsgroup_v3.cpp @@ -0,0 +1,366 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "stm8generalsettingsgroup_v3.h" + +#include "../../iarewutils.h" + +namespace qbs { +namespace iarew { +namespace stm8 { +namespace v3 { + +constexpr int kGeneralArchiveVersion = 4; +constexpr int kGeneralDataVersion = 2; + +namespace { + +// Target page options. + +struct TargetPageOptions final +{ + enum CodeModel { + SmallCodeModel, + MediumCodeModel, + LargeCodeModel + }; + + enum DataModel { + SmallDataModel, + MediumDataModel, + LargeDataModel + }; + + explicit TargetPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("driverFlags")}); + // Detect target code model. + const QString codeModelValue = IarewUtils::flagValue( + flags, QStringLiteral("--code_model")); + if (codeModelValue == QLatin1String("small")) + codeModel = TargetPageOptions::SmallCodeModel; + else if (codeModelValue == QLatin1String("medium")) + codeModel = TargetPageOptions::MediumCodeModel; + else if (codeModelValue == QLatin1String("large")) + codeModel = TargetPageOptions::LargeCodeModel; + // Detect target data model. + const QString dataModelValue = IarewUtils::flagValue( + flags, QStringLiteral("--data_model")); + if (dataModelValue == QLatin1String("small")) + dataModel = TargetPageOptions::SmallDataModel; + else if (dataModelValue == QLatin1String("medium")) + dataModel = TargetPageOptions::MediumDataModel; + else if (dataModelValue == QLatin1String("large")) + dataModel = TargetPageOptions::LargeDataModel; + } + + CodeModel codeModel = MediumCodeModel; + DataModel dataModel = MediumDataModel; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + binaryType = IarewUtils::outputBinaryType(qbsProduct); + binaryDirectory = gen::utils::binaryOutputDirectory( + baseDirectory, qbsProduct); + objectDirectory = gen::utils::objectsOutputDirectory( + baseDirectory, qbsProduct); + listingDirectory = gen::utils::listingOutputDirectory( + baseDirectory, qbsProduct); + } + + IarewUtils::OutputBinaryType binaryType = IarewUtils::ApplicationOutputType; + QString binaryDirectory; + QString objectDirectory; + QString listingDirectory; +}; + +// Library configuration page options. + +struct LibraryConfigPageOptions final +{ + enum RuntimeLibrary { + NoLibrary, + NormalLibrary, + FullLibrary, + CustomLibrary + }; + + explicit LibraryConfigPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleCompilerFlags(qbsProps); + + const QFileInfo configInfo(IarewUtils::flagValue( + flags, + QStringLiteral("--dlib_config"))); + const QString configFilePath = configInfo.absoluteFilePath(); + + if (!configFilePath.isEmpty()) { + const QString libToolkitPath = + IarewUtils::libToolkitRootPath(qbsProduct); + + if (configFilePath.startsWith(libToolkitPath, + Qt::CaseInsensitive)) { + if (configFilePath.endsWith(QLatin1String("n.h"), + Qt::CaseInsensitive)) { + libraryType = LibraryConfigPageOptions::NormalLibrary; + } else if (configFilePath.endsWith(QLatin1String("f.h"), + Qt::CaseInsensitive)) { + libraryType = LibraryConfigPageOptions::FullLibrary; + } else { + libraryType = LibraryConfigPageOptions::CustomLibrary; + } + + configPath = IarewUtils::toolkitRelativeFilePath( + baseDirectory, configFilePath); + } else { + libraryType = LibraryConfigPageOptions::CustomLibrary; + + configPath = configFilePath; + } + } else { + libraryType = LibraryConfigPageOptions::NoLibrary; + } + } + + RuntimeLibrary libraryType = NoLibrary; + QString configPath; +}; + +// Library options page options. + +struct LibraryOptionsPageOptions final +{ + enum PrintfFormatter { + PrintfAutoFormatter = 0, + PrintfFullFormatter = 1, + PrintfFullNoMultibytesFormatter = 2, + PrintfLargeFormatter = 3, + PrintfLargeNoMultibytesFormatter = 4, + PrintfSmallFormatter = 5, + PrintfSmallNoMultibytesFormatter = 6, + PrintfTinyFormatter = 7 + }; + + enum ScanfFormatter { + ScanfAutoFormatter = 0, + ScanfFullFormatter = 1, + ScanfFullNoMultibytesFormatter = 2, + ScanfLargeFormatter = 3, + ScanfLargeNoMultibytesFormatter = 4, + ScanfSmallFormatter = 5, + ScanfSmallNoMultibytesFormatter = 6 + }; + + explicit LibraryOptionsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + for (auto flagIt = flags.cbegin(); flagIt < flags.cend(); ++flagIt) { + if (*flagIt != QLatin1String("--redirect")) + continue; + ++flagIt; + if (flagIt->startsWith(QLatin1String("_printf="), + Qt::CaseInsensitive)) { + const QString prop = flagIt->split( + QLatin1Char('=')).at(1).toLower(); + if (prop == QLatin1String("_printffull")) + printfFormatter = PrintfFullFormatter; + else if (prop == QLatin1String("_printffullnomb")) + printfFormatter = PrintfFullNoMultibytesFormatter; + else if (prop == QLatin1String("_printflarge")) + printfFormatter = PrintfLargeFormatter; + else if (prop == QLatin1String("_printflargenomb")) + printfFormatter = PrintfLargeFormatter; + else if (prop == QLatin1String("_printfsmall")) + printfFormatter = PrintfSmallFormatter; + else if (prop == QLatin1String("_printfsmallnomb")) + printfFormatter = PrintfSmallNoMultibytesFormatter; + else if (prop == QLatin1String("_printftiny")) + printfFormatter = PrintfTinyFormatter; + } else if (flagIt->startsWith(QLatin1String("_scanf="), + Qt::CaseInsensitive)) { + const QString prop = flagIt->split( + QLatin1Char('=')).at(1).toLower(); + if (prop == QLatin1String("_scanffull")) + scanfFormatter = ScanfFullFormatter; + else if (prop == QLatin1String("_scanffullnomb")) + scanfFormatter = ScanfFullNoMultibytesFormatter; + else if (prop == QLatin1String("_scanflarge")) + scanfFormatter = ScanfLargeFormatter; + else if (prop == QLatin1String("_scanflargenomb")) + scanfFormatter = ScanfLargeFormatter; + else if (prop == QLatin1String("_scanfsmall")) + scanfFormatter = ScanfSmallFormatter; + else if (prop == QLatin1String("_scanfsmallnomb")) + scanfFormatter = ScanfSmallNoMultibytesFormatter; + } + } + } + + PrintfFormatter printfFormatter = PrintfAutoFormatter; + ScanfFormatter scanfFormatter = ScanfAutoFormatter; +}; + +// Stack/heap page options. + +struct StackHeapPageOptions final +{ + explicit StackHeapPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + const auto configDefs = IarewUtils::flagValues( + flags, QStringLiteral("--config_def")); + for (const auto &configDef : configDefs) { + const auto def = configDef.toString(); + if (def.startsWith(QLatin1String("_CSTACK_SIZE="))) { + stackSize = def.split(QLatin1Char('=')).at(1); + } else if (def.startsWith(QLatin1String("_HEAP_SIZE="))) { + heapSize = def.split(QLatin1Char('=')).at(1); + } + } + } + + QString stackSize; + QString heapSize; +}; + +} // namespace + +// Stm8GeneralSettingsGroup + +Stm8GeneralSettingsGroup::Stm8GeneralSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + Q_UNUSED(qbsProductDeps) + + setName(QByteArrayLiteral("General")); + setArchiveVersion(kGeneralArchiveVersion); + setDataVersion(kGeneralDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildTargetPage(qbsProduct); + buildOutputPage(buildRootDirectory, qbsProduct); + buildLibraryConfigPage(buildRootDirectory, qbsProduct); + buildLibraryOptionsPage(qbsProduct); + buildStackHeapPage(qbsProduct); +} + +void Stm8GeneralSettingsGroup::buildTargetPage( + const ProductData &qbsProduct) +{ + const TargetPageOptions opts(qbsProduct); + // Add 'GenCodeModel' item + // (Code model: small/medium/large). + addOptionsGroup(QByteArrayLiteral("GenCodeModel"), + {opts.codeModel}); + // Add 'GenDataModel' item + // (Data model: small/medium/large). + addOptionsGroup(QByteArrayLiteral("GenDataModel"), + {opts.dataModel}); +} + +void Stm8GeneralSettingsGroup::buildOutputPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(baseDirectory, qbsProduct); + // Add 'GOutputBinary' item (Output file: executable/library). + addOptionsGroup(QByteArrayLiteral("GOutputBinary"), + {opts.binaryType}); + // Add 'ExePath' item (Executable/binaries output directory). + addOptionsGroup(QByteArrayLiteral("ExePath"), + {opts.binaryDirectory}); + // Add 'ObjPath' item (Object files output directory). + addOptionsGroup(QByteArrayLiteral("ObjPath"), + {opts.objectDirectory}); + // Add 'ListPath' item (List files output directory). + addOptionsGroup(QByteArrayLiteral("ListPath"), + {opts.listingDirectory}); +} + +void Stm8GeneralSettingsGroup::buildLibraryConfigPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + const LibraryConfigPageOptions opts(baseDirectory, qbsProduct); + // Add 'GenRuntimeLibSelect' and 'GenRuntimeLibSelectSlave' items + // (Link with runtime: none/normal/full/custom). + addOptionsGroup(QByteArrayLiteral("GenRuntimeLibSelect"), + {opts.libraryType}); + addOptionsGroup(QByteArrayLiteral("GenRuntimeLibSelectSlave"), + {opts.libraryType}); + // Add 'GenRTConfigPath' item (Runtime configuration file). + addOptionsGroup(QByteArrayLiteral("GenRTConfigPath"), + {opts.configPath}); +} + +void Stm8GeneralSettingsGroup::buildLibraryOptionsPage( + const ProductData &qbsProduct) +{ + const LibraryOptionsPageOptions opts(qbsProduct); + // Add 'GenLibOutFormatter' item (Printf formatter). + addOptionsGroup(QByteArrayLiteral("GenLibOutFormatter"), + {opts.printfFormatter}); + // Add 'GenLibInFormatter' item (Scanf formatter). + addOptionsGroup(QByteArrayLiteral("GenLibInFormatter"), + {opts.scanfFormatter}); +} + +void Stm8GeneralSettingsGroup::buildStackHeapPage( + const ProductData &qbsProduct) +{ + const StackHeapPageOptions opts(qbsProduct); + // Add 'GenStackSize' item (Stack size). + addOptionsGroup(QByteArrayLiteral("GenStackSize"), + {opts.stackSize}); + // Add 'GenHeapSize' item (Heap size). + addOptionsGroup(QByteArrayLiteral("GenHeapSize"), + {opts.heapSize}); +} + +} // namespace v3 +} // namespace stm8 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/stm8/stm8generalsettingsgroup_v3.h b/src/plugins/generator/iarew/archs/stm8/stm8generalsettingsgroup_v3.h new file mode 100644 index 00000000..20def0fd --- /dev/null +++ b/src/plugins/generator/iarew/archs/stm8/stm8generalsettingsgroup_v3.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWSTM8GENERALSETTINGSGROUP_V3_H +#define QBS_IAREWSTM8GENERALSETTINGSGROUP_V3_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace stm8 { +namespace v3 { + +class Stm8GeneralSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Stm8GeneralSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildTargetPage(const ProductData &qbsProduct); + void buildOutputPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildLibraryConfigPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildLibraryOptionsPage(const ProductData &qbsProduct); + void buildStackHeapPage(const ProductData &qbsProduct); +}; + +} // namespace v3 +} // namespace stm8 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWSTM8GENERALSETTINGSGROUP_V3_H diff --git a/src/plugins/generator/iarew/archs/stm8/stm8linkersettingsgroup_v3.cpp b/src/plugins/generator/iarew/archs/stm8/stm8linkersettingsgroup_v3.cpp new file mode 100644 index 00000000..066c4793 --- /dev/null +++ b/src/plugins/generator/iarew/archs/stm8/stm8linkersettingsgroup_v3.cpp @@ -0,0 +1,410 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "stm8linkersettingsgroup_v3.h" + +#include "../../iarewutils.h" + +#include + +namespace qbs { +namespace iarew { +namespace stm8 { +namespace v3 { + +constexpr int kLinkerArchiveVersion = 5; +constexpr int kLinkerDataVersion = 4; + +namespace { + +// Config page options. + +struct ConfigPageOptions final +{ + explicit ConfigPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + + // Enumerate all product linker config files + // (which are set trough 'linkerscript' tag). + for (const auto &qbsGroup : qbsProduct.groups()) { + const auto qbsArtifacts = qbsGroup.sourceArtifacts(); + for (const auto &qbsArtifact : qbsArtifacts) { + const auto qbsTags = qbsArtifact.fileTags(); + if (!qbsTags.contains(QLatin1String("linkerscript"))) + continue; + const QString fullConfigPath = qbsArtifact.filePath(); + if (fullConfigPath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullConfigPath); + configFilePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullConfigPath); + configFilePaths.push_back(path); + } + } + } + + // Enumerate all product linker config files + // (which are set trough '-config' option). + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + const QVariantList configPathValues = IarewUtils::flagValues( + flags, QStringLiteral("--config")); + for (const QVariant &configPathValue : configPathValues) { + const QString fullConfigPath = configPathValue.toString(); + if (fullConfigPath.startsWith(toolkitPath, Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullConfigPath); + if (!configFilePaths.contains(path)) + configFilePaths.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullConfigPath); + if (!configFilePaths.contains(path)) + configFilePaths.push_back(path); + } + } + + // Enumerate all config definition symbols (except + // the CSTACK_SIZE and HEAP_SIZE which are handles + // on the general page). + configDefinitions = IarewUtils::flagValues( + flags, QStringLiteral("--config_def")); + configDefinitions.erase(std::remove_if( + configDefinitions.begin(), + configDefinitions.end(), + [](const auto &definition){ + const auto def = definition.toString(); + return def.startsWith(QLatin1String("_CSTACK_SIZE")) + || def.startsWith(QLatin1String("_HEAP_SIZE")); + }), configDefinitions.end()); + } + + QVariantList configFilePaths; + QVariantList configDefinitions; +}; + +struct LibraryPageOptions final +{ + explicit LibraryPageOptions(const QString &baseDirectory, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QString toolkitPath = IarewUtils::toolkitRootPath(qbsProduct); + + entryPoint = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("entryPoint")); + + // Add static libraries paths. + const QStringList staticLibrariesProps = + gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("staticLibraries")}); + for (const QString &staticLibrary : staticLibrariesProps) { + const QFileInfo staticLibraryInfo(staticLibrary); + if (staticLibraryInfo.isAbsolute()) { + const QString fullStaticLibraryPath = + staticLibraryInfo.absoluteFilePath(); + if (fullStaticLibraryPath.startsWith(toolkitPath, + Qt::CaseInsensitive)) { + const QString path = IarewUtils::toolkitRelativeFilePath( + toolkitPath, fullStaticLibraryPath); + staticLibraries.push_back(path); + } else { + const QString path = IarewUtils::projectRelativeFilePath( + baseDirectory, fullStaticLibraryPath); + staticLibraries.push_back(path); + } + } else { + staticLibraries.push_back(staticLibrary); + } + } + + // Add static libraries from product dependencies. + for (const ProductData &qbsProductDep : qbsProductDeps) { + const QString depBinaryPath = QLatin1String("$PROJ_DIR$/") + + gen::utils::targetBinaryPath(baseDirectory, + qbsProductDep); + staticLibraries.push_back(depBinaryPath); + } + } + + QString entryPoint; + QVariantList staticLibraries; +}; + +struct OptimizationsPageOptions final +{ + explicit OptimizationsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + + mergeDuplicateSections = flags.contains( + QLatin1String("--merge_duplicate_sections")); + } + + bool mergeDuplicateSections = true; +}; + +// Output page options. + +struct OutputPageOptions final +{ + explicit OutputPageOptions(const ProductData &qbsProduct) + { + outputFile = gen::utils::targetBinary(qbsProduct); + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + + enableDebugInfo = !flags.contains(QLatin1String("--strip")); + } + + QString outputFile; + bool enableDebugInfo = true; +}; + +// List page options. + +struct ListPageOptions final +{ + enum ListingAction { + NoListing, + GenerateListing + }; + + explicit ListPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + generateMap = gen::utils::cppBooleanModuleProperty( + qbsProps, QStringLiteral("generateLinkerMapFile")) + ? ListPageOptions::GenerateListing + : ListPageOptions::NoListing; + } + + ListingAction generateMap = NoListing; +}; + +// Define page options. + +struct DefinePageOptions final +{ + explicit DefinePageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + const QStringList flags = IarewUtils::cppModuleLinkerFlags(qbsProps); + + defineSymbols = IarewUtils::flagValues( + flags, QStringLiteral("--define_symbol")); + } + + QVariantList defineSymbols; +}; + +// Diagnostics page options. + +struct DiagnosticsPageOptions final +{ + explicit DiagnosticsPageOptions(const ProductData &qbsProduct) + { + const auto &qbsProps = qbsProduct.moduleProperties(); + warningsAsErrors = gen::utils::cppIntegerModuleProperty( + qbsProps, QStringLiteral("treatWarningsAsErrors")); + } + + int warningsAsErrors = 0; +}; + +} // namespace + +// Stm8LinkerSettingsGroup + +Stm8LinkerSettingsGroup::Stm8LinkerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + setName(QByteArrayLiteral("ILINK")); + setArchiveVersion(kLinkerArchiveVersion); + setDataVersion(kLinkerDataVersion); + setDataDebugInfo(gen::utils::debugInformation(qbsProduct)); + + const QString buildRootDirectory = gen::utils::buildRootPath(qbsProject); + + buildConfigPage(buildRootDirectory, qbsProduct); + buildLibraryPage(buildRootDirectory, qbsProduct, qbsProductDeps); + buildOptimizationsPage(qbsProduct); + buildOutputPage(qbsProduct); + buildListPage(qbsProduct); + buildDefinePage(qbsProduct); + buildDiagnosticsPage(qbsProduct); + + // Should be called as latest stage! + buildExtraOptionsPage(qbsProduct); +} + +void Stm8LinkerSettingsGroup::buildConfigPage( + const QString &baseDirectory, + const ProductData &qbsProduct) +{ + ConfigPageOptions opts(baseDirectory, qbsProduct); + + if (opts.configFilePaths.count() > 0) { + // Note: IAR IDE does not allow to specify a multiple config files, + // although the IAR linker support it. So, we use followig 'trick': + // we take a first config file and to add it as usual to required items; + // and then an other remainders we forward to the "Extra options page". + const QVariant configPath = opts.configFilePaths.takeFirst(); + // Add 'IlinkIcfOverride' item (Override default). + addOptionsGroup(QByteArrayLiteral("IlinkIcfOverride"), + {1}); + // Add 'IlinkIcfFile' item (Linke configuration file). + addOptionsGroup(QByteArrayLiteral("IlinkIcfFile"), + {configPath}); + + // Add remainder configuration files to the "Extra options page". + if (!opts.configFilePaths.isEmpty()) { + for (QVariant &configPath : opts.configFilePaths) + configPath = QLatin1String("--config ") + configPath.toString(); + + m_extraOptions << opts.configFilePaths; + } + } + + // Add 'IlinkConfigDefines' item (Configuration file + // symbol definitions). + addOptionsGroup(QByteArrayLiteral("IlinkConfigDefines"), + opts.configDefinitions); +} + +void Stm8LinkerSettingsGroup::buildLibraryPage( + const QString &baseDirectory, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) +{ + LibraryPageOptions opts(baseDirectory, qbsProduct, qbsProductDeps); + + // Add 'IlinkOverrideProgramEntryLabel' item + // (Override default program entry). + addOptionsGroup(QByteArrayLiteral("IlinkOverrideProgramEntryLabel"), + {1}); + + if (opts.entryPoint.isEmpty()) { + // Add 'IlinkProgramEntryLabelSelect' item + // (Defined by application). + addOptionsGroup(QByteArrayLiteral("IlinkProgramEntryLabelSelect"), + {1}); + } else { + // Add 'IlinkProgramEntryLabel' item + // (Entry symbol). + addOptionsGroup(QByteArrayLiteral("IlinkProgramEntryLabel"), + {opts.entryPoint}); + } + + // Add 'IlinkAdditionalLibs' item (Additional libraries). + addOptionsGroup(QByteArrayLiteral("IlinkAdditionalLibs"), + {opts.staticLibraries}); +} + +void Stm8LinkerSettingsGroup::buildOptimizationsPage( + const ProductData &qbsProduct) +{ + OptimizationsPageOptions opts(qbsProduct); + + // Add 'IlinkOptMergeDuplSections' item + // (Merge duplicate sections). + addOptionsGroup(QByteArrayLiteral("IlinkOptMergeDuplSections"), + {opts.mergeDuplicateSections}); +} + +void Stm8LinkerSettingsGroup::buildOutputPage( + const ProductData &qbsProduct) +{ + const OutputPageOptions opts(qbsProduct); + + // Add 'IlinkOutputFile' item (Output file name). + addOptionsGroup(QByteArrayLiteral("IlinkOutputFile"), + {opts.outputFile}); + // Add 'IlinkDebugInfoEnable' item + // (Include debug information in output). + addOptionsGroup(QByteArrayLiteral("IlinkDebugInfoEnable"), + {opts.enableDebugInfo}); +} + +void Stm8LinkerSettingsGroup::buildListPage( + const ProductData &qbsProduct) +{ + const ListPageOptions opts(qbsProduct); + // Add 'IlinkMapFile' item (Generate linker map file). + addOptionsGroup(QByteArrayLiteral("IlinkMapFile"), + {opts.generateMap}); +} + +void Stm8LinkerSettingsGroup::buildDefinePage( + const ProductData &qbsProduct) +{ + const DefinePageOptions opts(qbsProduct); + // Add 'IlinkDefines' item (Defined symbols). + addOptionsGroup(QByteArrayLiteral("IlinkDefines"), + opts.defineSymbols); +} + +void Stm8LinkerSettingsGroup::buildDiagnosticsPage( + const ProductData &qbsProduct) +{ + const DiagnosticsPageOptions opts(qbsProduct); + // Add 'IlinkWarningsAreErrors' item (Treat all warnings as errors). + addOptionsGroup(QByteArrayLiteral("IlinkWarningsAreErrors"), + {opts.warningsAsErrors}); +} + +void Stm8LinkerSettingsGroup::buildExtraOptionsPage( + const ProductData &qbsProduct) +{ + Q_UNUSED(qbsProduct) + + if (m_extraOptions.isEmpty()) + return; + + // Add 'IlinkUseExtraOptions' (Use command line options). + addOptionsGroup(QByteArrayLiteral("IlinkUseExtraOptions"), + {1}); + // Add 'IlinkExtraOptions' item (Command line options). + addOptionsGroup(QByteArrayLiteral("IlinkExtraOptions"), + m_extraOptions); +} + +} // namespace v3 +} // namespace stm8 +} // namespace iarew +} // namespace qbs diff --git a/src/plugins/generator/iarew/archs/stm8/stm8linkersettingsgroup_v3.h b/src/plugins/generator/iarew/archs/stm8/stm8linkersettingsgroup_v3.h new file mode 100644 index 00000000..b214ebe3 --- /dev/null +++ b/src/plugins/generator/iarew/archs/stm8/stm8linkersettingsgroup_v3.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWSTM8LINKERSETTINGSGROUP_V3_H +#define QBS_IAREWSTM8LINKERSETTINGSGROUP_V3_H + +#include "../../iarewsettingspropertygroup.h" + +namespace qbs { +namespace iarew { +namespace stm8 { +namespace v3 { + +class Stm8LinkerSettingsGroup final : public IarewSettingsPropertyGroup +{ +public: + explicit Stm8LinkerSettingsGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + +private: + void buildConfigPage(const QString &baseDirectory, + const ProductData &qbsProduct); + void buildLibraryPage(const QString &baseDirectory, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps); + void buildOptimizationsPage(const ProductData &qbsProduct); + + + + void buildOutputPage(const ProductData &qbsProduct); + void buildListPage(const ProductData &qbsProduct); + void buildDefinePage(const ProductData &qbsProduct); + void buildDiagnosticsPage(const ProductData &qbsProduct); + void buildExtraOptionsPage(const ProductData &qbsProduct); + + QVariantList m_extraOptions; +}; + +} // namespace v3 +} // namespace stm8 +} // namespace iarew +} // namespace qbs + +#endif // QBS_IAREWSTM8LINKERSETTINGSGROUP_V3_H diff --git a/src/plugins/generator/iarew/iarew.pri b/src/plugins/generator/iarew/iarew.pri new file mode 100644 index 00000000..68a3593f --- /dev/null +++ b/src/plugins/generator/iarew/iarew.pri @@ -0,0 +1 @@ +qbsPluginTarget = iarewgenerator diff --git a/src/plugins/generator/iarew/iarew.pro b/src/plugins/generator/iarew/iarew.pro new file mode 100644 index 00000000..54244951 --- /dev/null +++ b/src/plugins/generator/iarew/iarew.pro @@ -0,0 +1,131 @@ +include(iarew.pri) +include(../../plugins.pri) +include(../../../shared/json/json.pri) + +QT = core + +# Plugin file. + +SOURCES += \ + $$PWD/iarewgeneratorplugin.cpp \ + +# Common files. + +HEADERS += \ + $$PWD/iarewfileversionproperty.h \ + $$PWD/iarewgenerator.h \ + $$PWD/iarewoptionpropertygroup.h \ + $$PWD/iarewproject.h \ + $$PWD/iarewprojectwriter.h \ + $$PWD/iarewsettingspropertygroup.h \ + $$PWD/iarewsourcefilepropertygroup.h \ + $$PWD/iarewsourcefilespropertygroup.h \ + $$PWD/iarewtoolchainpropertygroup.h \ + $$PWD/iarewutils.h \ + $$PWD/iarewversioninfo.h \ + $$PWD/iarewworkspace.h \ + $$PWD/iarewworkspacewriter.h + +SOURCES += \ + $$PWD/iarewfileversionproperty.cpp \ + $$PWD/iarewgenerator.cpp \ + $$PWD/iarewoptionpropertygroup.cpp \ + $$PWD/iarewproject.cpp \ + $$PWD/iarewprojectwriter.cpp \ + $$PWD/iarewsettingspropertygroup.cpp \ + $$PWD/iarewsourcefilepropertygroup.cpp \ + $$PWD/iarewsourcefilespropertygroup.cpp \ + $$PWD/iarewtoolchainpropertygroup.cpp \ + $$PWD/iarewutils.cpp \ + $$PWD/iarewworkspace.cpp \ + $$PWD/iarewworkspacewriter.cpp + +# For ARM architecture. + +HEADERS += \ + $$PWD/archs/arm/armarchiversettingsgroup_v8.h \ + $$PWD/archs/arm/armassemblersettingsgroup_v8.h \ + $$PWD/archs/arm/armbuildconfigurationgroup_v8.h \ + $$PWD/archs/arm/armcompilersettingsgroup_v8.h \ + $$PWD/archs/arm/armgeneralsettingsgroup_v8.h \ + $$PWD/archs/arm/armlinkersettingsgroup_v8.h + +SOURCES += \ + $$PWD/archs/arm/armarchiversettingsgroup_v8.cpp \ + $$PWD/archs/arm/armassemblersettingsgroup_v8.cpp \ + $$PWD/archs/arm/armbuildconfigurationgroup_v8.cpp \ + $$PWD/archs/arm/armcompilersettingsgroup_v8.cpp \ + $$PWD/archs/arm/armgeneralsettingsgroup_v8.cpp \ + $$PWD/archs/arm/armlinkersettingsgroup_v8.cpp + +# For AVR architecture. + +HEADERS += \ + $$PWD/archs/avr/avrarchiversettingsgroup_v7.h \ + $$PWD/archs/avr/avrassemblersettingsgroup_v7.h \ + $$PWD/archs/avr/avrbuildconfigurationgroup_v7.h \ + $$PWD/archs/avr/avrcompilersettingsgroup_v7.h \ + $$PWD/archs/avr/avrgeneralsettingsgroup_v7.h \ + $$PWD/archs/avr/avrlinkersettingsgroup_v7.h + +SOURCES += \ + $$PWD/archs/avr/avrarchiversettingsgroup_v7.cpp \ + $$PWD/archs/avr/avrassemblersettingsgroup_v7.cpp \ + $$PWD/archs/avr/avrbuildconfigurationgroup_v7.cpp \ + $$PWD/archs/avr/avrcompilersettingsgroup_v7.cpp \ + $$PWD/archs/avr/avrgeneralsettingsgroup_v7.cpp \ + $$PWD/archs/avr/avrlinkersettingsgroup_v7.cpp + +# For MCS51 architecture. + +HEADERS += \ + $$PWD/archs/mcs51/mcs51archiversettingsgroup_v10.h \ + $$PWD/archs/mcs51/mcs51assemblersettingsgroup_v10.h \ + $$PWD/archs/mcs51/mcs51buildconfigurationgroup_v10.h \ + $$PWD/archs/mcs51/mcs51compilersettingsgroup_v10.h \ + $$PWD/archs/mcs51/mcs51generalsettingsgroup_v10.h \ + $$PWD/archs/mcs51/mcs51linkersettingsgroup_v10.h + +SOURCES += \ + $$PWD/archs/mcs51/mcs51archiversettingsgroup_v10.cpp \ + $$PWD/archs/mcs51/mcs51assemblersettingsgroup_v10.cpp \ + $$PWD/archs/mcs51/mcs51buildconfigurationgroup_v10.cpp \ + $$PWD/archs/mcs51/mcs51compilersettingsgroup_v10.cpp \ + $$PWD/archs/mcs51/mcs51generalsettingsgroup_v10.cpp \ + $$PWD/archs/mcs51/mcs51linkersettingsgroup_v10.cpp + +# For STM8 architecture. + +HEADERS += \ + $$PWD/archs/stm8/stm8archiversettingsgroup_v3.h \ + $$PWD/archs/stm8/stm8assemblersettingsgroup_v3.h \ + $$PWD/archs/stm8/stm8buildconfigurationgroup_v3.h \ + $$PWD/archs/stm8/stm8compilersettingsgroup_v3.h \ + $$PWD/archs/stm8/stm8generalsettingsgroup_v3.h \ + $$PWD/archs/stm8/stm8linkersettingsgroup_v3.h + +SOURCES += \ + $$PWD/archs/stm8/stm8archiversettingsgroup_v3.cpp \ + $$PWD/archs/stm8/stm8assemblersettingsgroup_v3.cpp \ + $$PWD/archs/stm8/stm8buildconfigurationgroup_v3.cpp \ + $$PWD/archs/stm8/stm8compilersettingsgroup_v3.cpp \ + $$PWD/archs/stm8/stm8generalsettingsgroup_v3.cpp \ + $$PWD/archs/stm8/stm8linkersettingsgroup_v3.cpp + +# For MSP430 architecture. + +HEADERS += \ + $$PWD/archs/msp430/msp430archiversettingsgroup_v7.h \ + $$PWD/archs/msp430/msp430assemblersettingsgroup_v7.h \ + $$PWD/archs/msp430/msp430buildconfigurationgroup_v7.h \ + $$PWD/archs/msp430/msp430compilersettingsgroup_v7.h \ + $$PWD/archs/msp430/msp430generalsettingsgroup_v7.h \ + $$PWD/archs/msp430/msp430linkersettingsgroup_v7.h + +SOURCES += \ + $$PWD/archs/msp430/msp430archiversettingsgroup_v7.cpp \ + $$PWD/archs/msp430/msp430assemblersettingsgroup_v7.cpp \ + $$PWD/archs/msp430/msp430buildconfigurationgroup_v7.cpp \ + $$PWD/archs/msp430/msp430compilersettingsgroup_v7.cpp \ + $$PWD/archs/msp430/msp430generalsettingsgroup_v7.cpp \ + $$PWD/archs/msp430/msp430linkersettingsgroup_v7.cpp diff --git a/src/plugins/generator/iarew/iarew.qbs b/src/plugins/generator/iarew/iarew.qbs new file mode 100644 index 00000000..82c95ea5 --- /dev/null +++ b/src/plugins/generator/iarew/iarew.qbs @@ -0,0 +1,131 @@ +import qbs +import "../../qbsplugin.qbs" as QbsPlugin + +QbsPlugin { + Depends { name: "qbsjson" } + + name: "iarewgenerator" + + files: ["iarewgeneratorplugin.cpp"] + + Group { + name: "IAR EW generator common" + files: [ + "iarewfileversionproperty.cpp", + "iarewfileversionproperty.h", + "iarewgenerator.cpp", + "iarewgenerator.h", + "iarewoptionpropertygroup.cpp", + "iarewoptionpropertygroup.h", + "iarewproject.cpp", + "iarewproject.h", + "iarewprojectwriter.cpp", + "iarewprojectwriter.h", + "iarewsettingspropertygroup.cpp", + "iarewsettingspropertygroup.h", + "iarewsourcefilepropertygroup.cpp", + "iarewsourcefilepropertygroup.h", + "iarewsourcefilespropertygroup.cpp", + "iarewsourcefilespropertygroup.h", + "iarewtoolchainpropertygroup.cpp", + "iarewtoolchainpropertygroup.h", + "iarewutils.cpp", + "iarewutils.h", + "iarewversioninfo.h", + "iarewworkspace.cpp", + "iarewworkspace.h", + "iarewworkspacewriter.cpp", + "iarewworkspacewriter.h", + ] + } + Group { + name: "IAR EW generator for ARM" + prefix: "archs/arm/" + files: [ + "armarchiversettingsgroup_v8.cpp", + "armarchiversettingsgroup_v8.h", + "armassemblersettingsgroup_v8.cpp", + "armassemblersettingsgroup_v8.h", + "armbuildconfigurationgroup_v8.cpp", + "armbuildconfigurationgroup_v8.h", + "armcompilersettingsgroup_v8.cpp", + "armcompilersettingsgroup_v8.h", + "armgeneralsettingsgroup_v8.cpp", + "armgeneralsettingsgroup_v8.h", + "armlinkersettingsgroup_v8.cpp", + "armlinkersettingsgroup_v8.h", + ] + } + Group { + name: "IAR EW generator for AVR" + prefix: "archs/avr/" + files: [ + "avrarchiversettingsgroup_v7.cpp", + "avrarchiversettingsgroup_v7.h", + "avrassemblersettingsgroup_v7.cpp", + "avrassemblersettingsgroup_v7.h", + "avrbuildconfigurationgroup_v7.cpp", + "avrbuildconfigurationgroup_v7.h", + "avrcompilersettingsgroup_v7.cpp", + "avrcompilersettingsgroup_v7.h", + "avrgeneralsettingsgroup_v7.cpp", + "avrgeneralsettingsgroup_v7.h", + "avrlinkersettingsgroup_v7.cpp", + "avrlinkersettingsgroup_v7.h", + ] + } + Group { + name: "IAR EW generator for MCS51" + prefix: "archs/mcs51/" + files: [ + "mcs51archiversettingsgroup_v10.cpp", + "mcs51archiversettingsgroup_v10.h", + "mcs51assemblersettingsgroup_v10.cpp", + "mcs51assemblersettingsgroup_v10.h", + "mcs51buildconfigurationgroup_v10.cpp", + "mcs51buildconfigurationgroup_v10.h", + "mcs51compilersettingsgroup_v10.cpp", + "mcs51compilersettingsgroup_v10.h", + "mcs51generalsettingsgroup_v10.cpp", + "mcs51generalsettingsgroup_v10.h", + "mcs51linkersettingsgroup_v10.cpp", + "mcs51linkersettingsgroup_v10.h", + ] + } + Group { + name: "IAR EW generator for STM8" + prefix: "archs/stm8/" + files: [ + "stm8archiversettingsgroup_v3.cpp", + "stm8archiversettingsgroup_v3.h", + "stm8assemblersettingsgroup_v3.cpp", + "stm8assemblersettingsgroup_v3.h", + "stm8buildconfigurationgroup_v3.cpp", + "stm8buildconfigurationgroup_v3.h", + "stm8compilersettingsgroup_v3.cpp", + "stm8compilersettingsgroup_v3.h", + "stm8generalsettingsgroup_v3.cpp", + "stm8generalsettingsgroup_v3.h", + "stm8linkersettingsgroup_v3.cpp", + "stm8linkersettingsgroup_v3.h", + ] + } + Group { + name: "IAR EW generator for MSP430" + prefix: "archs/msp430/" + files: [ + "msp430archiversettingsgroup_v7.cpp", + "msp430archiversettingsgroup_v7.h", + "msp430assemblersettingsgroup_v7.cpp", + "msp430assemblersettingsgroup_v7.h", + "msp430buildconfigurationgroup_v7.cpp", + "msp430buildconfigurationgroup_v7.h", + "msp430compilersettingsgroup_v7.cpp", + "msp430compilersettingsgroup_v7.h", + "msp430generalsettingsgroup_v7.cpp", + "msp430generalsettingsgroup_v7.h", + "msp430linkersettingsgroup_v7.cpp", + "msp430linkersettingsgroup_v7.h", + ] + } +} diff --git a/src/plugins/generator/iarew/iarewfileversionproperty.cpp b/src/plugins/generator/iarew/iarewfileversionproperty.cpp new file mode 100644 index 00000000..4324405a --- /dev/null +++ b/src/plugins/generator/iarew/iarewfileversionproperty.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "iarewfileversionproperty.h" +#include "iarewversioninfo.h" + +namespace qbs { + +static QByteArray buildFileVersion(const gen::VersionInfo &versionInfo) +{ + switch (versionInfo.marketingVersion()) { + case 3: + case 7: + case 8: + case 10: + return QByteArrayLiteral("3"); + default: + return {}; + } +} + +IarewFileVersionProperty::IarewFileVersionProperty( + const gen::VersionInfo &versionInfo) +{ + setName(QByteArrayLiteral("fileVersion")); + const QByteArray fileVersion = buildFileVersion(versionInfo); + setValue(fileVersion); +} + +} // namespace qbs diff --git a/src/plugins/generator/iarew/iarewfileversionproperty.h b/src/plugins/generator/iarew/iarewfileversionproperty.h new file mode 100644 index 00000000..2ed56c1e --- /dev/null +++ b/src/plugins/generator/iarew/iarewfileversionproperty.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWFILEVERSIONPROPERTY_H +#define QBS_IAREWFILEVERSIONPROPERTY_H + +#include +#include + +namespace qbs { + +class IarewFileVersionProperty final : public gen::xml::Property +{ +public: + explicit IarewFileVersionProperty( + const gen::VersionInfo &versionInfo); +}; + +} // namespace qbs + +#endif // QBS_IAREWFILEVERSIONPROPERTY_H diff --git a/src/plugins/generator/iarew/iarewgenerator.cpp b/src/plugins/generator/iarew/iarewgenerator.cpp new file mode 100644 index 00000000..c5345a6d --- /dev/null +++ b/src/plugins/generator/iarew/iarewgenerator.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "iarewgenerator.h" +#include "iarewproject.h" +#include "iarewprojectwriter.h" +#include "iarewworkspace.h" +#include "iarewworkspacewriter.h" + +#include + +#include +#include + +#include + +namespace qbs { + +static QString targetFilePath(const QString &baseName, + const QString &baseBuildDirectory) +{ + return QDir(baseBuildDirectory).absoluteFilePath( + baseName + QStringLiteral(".ewp")); +} + +static QString targetFilePath(const GeneratableProductData &product, + const QString &baseBuildDirectory) +{ + return targetFilePath(product.name(), baseBuildDirectory); +} + +static void writeProjectFiles(const std::map> &projects, + const Internal::Logger &logger) +{ + for (const auto &item : projects) { + const QString projectFilePath = item.first; + Internal::FileSaver file(projectFilePath.toStdString()); + if (!file.open()) + throw ErrorInfo(Internal::Tr::tr("Cannot open %s for writing") + .arg(projectFilePath)); + + std::shared_ptr project = item.second; + IarewProjectWriter writer(file.device()); + if (!(writer.write(project.get()) && file.commit())) + throw ErrorInfo(Internal::Tr::tr("Failed to generate %1") + .arg(projectFilePath)); + + logger.qbsInfo() << Internal::Tr::tr("Generated %1").arg( + QFileInfo(projectFilePath).fileName()); + } +} + +static void writeWorkspace(const std::shared_ptr &wokspace, + const QString &workspaceFilePath, + const Internal::Logger &logger) +{ + Internal::FileSaver file(workspaceFilePath.toStdString()); + if (!file.open()) + throw ErrorInfo(Internal::Tr::tr("Cannot open %s for writing") + .arg(workspaceFilePath)); + + IarewWorkspaceWriter writer(file.device()); + if (!(writer.write(wokspace.get()) && file.commit())) + throw ErrorInfo(Internal::Tr::tr("Failed to generate %1") + .arg(workspaceFilePath)); + + logger.qbsInfo() << Internal::Tr::tr("Generated %1").arg( + QFileInfo(workspaceFilePath).fileName()); +} + +IarewGenerator::IarewGenerator(const gen::VersionInfo &versionInfo) + : m_versionInfo(versionInfo) +{ +} + +QString IarewGenerator::generatorName() const +{ + return QStringLiteral("iarew%1").arg(m_versionInfo.marketingVersion()); +} + +void IarewGenerator::reset() +{ + m_workspace.reset(); + m_workspaceFilePath.clear(); + m_projects.clear(); +} + +void IarewGenerator::generate() +{ + GeneratableProjectIterator it(project()); + it.accept(this); + + writeProjectFiles(m_projects, logger()); + writeWorkspace(m_workspace, m_workspaceFilePath, logger()); + + reset(); +} + +void IarewGenerator::visitProject(const GeneratableProject &project) +{ + const QDir buildDir = project.baseBuildDirectory(); + + m_workspaceFilePath = buildDir.absoluteFilePath( + project.name() + QStringLiteral(".eww")); + m_workspace = std::make_shared(m_workspaceFilePath); +} + +void IarewGenerator::visitProjectData(const GeneratableProject &project, + const GeneratableProjectData &projectData) +{ + Q_UNUSED(project) + Q_UNUSED(projectData) +} + +void IarewGenerator::visitProduct(const GeneratableProject &project, + const GeneratableProjectData &projectData, + const GeneratableProductData &productData) +{ + Q_UNUSED(projectData); + const QString projectFilePath = targetFilePath( + productData, project.baseBuildDirectory().absolutePath()); + const auto targetProject = std::make_shared( + project, productData, + m_versionInfo); + + m_projects.insert({projectFilePath, targetProject}); + m_workspace->addProject(projectFilePath); +} + +} // namespace qbs diff --git a/src/plugins/generator/iarew/iarewgenerator.h b/src/plugins/generator/iarew/iarewgenerator.h new file mode 100644 index 00000000..fa1d93bc --- /dev/null +++ b/src/plugins/generator/iarew/iarewgenerator.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWGENERATOR_H +#define QBS_IAREWGENERATOR_H + +#include "iarewversioninfo.h" + +#include +#include + +namespace qbs { + +class IarewProject; +class IarewWorkspace; + +class IarewGenerator final : public ProjectGenerator, private IGeneratableProjectVisitor +{ +public: + explicit IarewGenerator(const gen::VersionInfo &versionInfo); + + QString generatorName() const final; + void generate() final; + +private: + void reset(); + + void visitProject(const GeneratableProject &project) final; + void visitProjectData(const GeneratableProject &project, + const GeneratableProjectData &projectData) final; + void visitProduct(const GeneratableProject &project, + const GeneratableProjectData &projectData, + const GeneratableProductData &productData) final; + + const gen::VersionInfo m_versionInfo; + std::shared_ptr m_workspace; + QString m_workspaceFilePath; + std::map> m_projects; +}; + +} // namespace qbs + +#endif // QBS_IAREWGENERATOR_H diff --git a/src/plugins/generator/iarew/iarewgeneratorplugin.cpp b/src/plugins/generator/iarew/iarewgeneratorplugin.cpp new file mode 100644 index 00000000..bdd15c48 --- /dev/null +++ b/src/plugins/generator/iarew/iarewgeneratorplugin.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "iarewgenerator.h" +#include "iarewversioninfo.h" + +#include +#include + +static void QbsIarewGeneratorPluginLoad() +{ + for (const auto &info : qbs::IarewVersionInfo::knownVersions) { + qbs::ProjectGeneratorManager::registerGenerator( + std::make_shared(info)); + } +} + +static void QbsIarewGeneratorPluginUnload() +{ +} + +#ifndef GENERATOR_EXPORT +#if defined(WIN32) || defined(_WIN32) +#define GENERATOR_EXPORT __declspec(dllexport) +#else +#define GENERATOR_EXPORT __attribute__((visibility("default"))) +#endif +#endif + +QBS_REGISTER_STATIC_PLUGIN(extern "C" GENERATOR_EXPORT, iarewgenerator, + QbsIarewGeneratorPluginLoad, QbsIarewGeneratorPluginUnload) diff --git a/src/plugins/generator/iarew/iarewoptionpropertygroup.cpp b/src/plugins/generator/iarew/iarewoptionpropertygroup.cpp new file mode 100644 index 00000000..c44b30ed --- /dev/null +++ b/src/plugins/generator/iarew/iarewoptionpropertygroup.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "iarewoptionpropertygroup.h" + +namespace qbs { + +IarewOptionPropertyGroup::IarewOptionPropertyGroup( + const QByteArray &name, QVariantList states, int version) + : gen::xml::PropertyGroup(QByteArrayLiteral("option")) +{ + // Append name property item. + appendChild(QByteArrayLiteral("name"), + name); + + // Append version property item. + if (version >= 0) + appendChild(QByteArrayLiteral("version"), + version); + + // Append state property items. + for (auto &state : states) { + if (state.isNull()) + continue; + appendChild(QByteArrayLiteral("state"), + std::move(state)); + } +} + +} // namespace qbs diff --git a/src/plugins/generator/iarew/iarewoptionpropertygroup.h b/src/plugins/generator/iarew/iarewoptionpropertygroup.h new file mode 100644 index 00000000..d80df283 --- /dev/null +++ b/src/plugins/generator/iarew/iarewoptionpropertygroup.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWOPTIONPROPERTYGROUP_H +#define QBS_IAREWOPTIONPROPERTYGROUP_H + +#include + +//#include + +namespace qbs { + +class IarewOptionPropertyGroup final + : public gen::xml::PropertyGroup +{ +public: + explicit IarewOptionPropertyGroup(const QByteArray &name, QVariantList states, + int version = -1); +}; + +} // namespace qbs + +#endif // QBS_IAREWOPTIONPROPERTYGROUP_H diff --git a/src/plugins/generator/iarew/iarewproject.cpp b/src/plugins/generator/iarew/iarewproject.cpp new file mode 100644 index 00000000..f33ae4fc --- /dev/null +++ b/src/plugins/generator/iarew/iarewproject.cpp @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "iarewfileversionproperty.h" +#include "iarewproject.h" +#include "iarewsourcefilespropertygroup.h" +#include "iarewutils.h" +#include "iarewversioninfo.h" + +#include "archs/arm/armbuildconfigurationgroup_v8.h" +#include "archs/avr/avrbuildconfigurationgroup_v7.h" +#include "archs/mcs51/mcs51buildconfigurationgroup_v10.h" +#include "archs/stm8/stm8buildconfigurationgroup_v3.h" +#include "archs/msp430/msp430buildconfigurationgroup_v7.h" + +#include + +namespace qbs { + +IarewProject::IarewProject(const GeneratableProject &genProject, + const GeneratableProductData &genProduct, + const gen::VersionInfo &versionInfo) +{ + Q_ASSERT(genProject.projects.size() == genProject.commandLines.size()); + Q_ASSERT(genProject.projects.size() == genProduct.data.size()); + + // Create available configuration group factories. + m_factories.push_back(std::make_unique< + iarew::arm::v8::ArmBuildConfigurationGroupFactory>()); + m_factories.push_back(std::make_unique< + iarew::avr::v7::AvrBuildConfigurationGroupFactory>()); + m_factories.push_back(std::make_unique< + iarew::mcs51::v10::Mcs51BuildConfigurationGroupFactory>()); + m_factories.push_back(std::make_unique< + iarew::stm8::v3::Stm8BuildConfigurationGroupFactory>()); + m_factories.push_back(std::make_unique< + iarew::msp430::v7::Msp430BuildConfigurationGroupFactory>()); + + // Construct file version item. + appendChild(versionInfo); + + // Construct all build configurations items. + const int configsCount = std::max(genProject.projects.size(), + genProduct.data.size()); + for (auto configIndex = 0; configIndex < configsCount; ++configIndex) { + const qbs::Project qbsProject = genProject.projects + .values().at(configIndex); + const ProductData qbsProduct = genProduct.data.values().at(configIndex); + const QString confName = gen::utils::buildConfigurationName(qbsProject); + const std::vector qbsProductDeps = gen::utils::dependenciesOf + (qbsProduct, genProject, confName); + + const auto arch = gen::utils::architecture(qbsProject); + if (arch == gen::utils::Architecture::Unknown) + throw ErrorInfo(Internal::Tr::tr("Target architecture is not set," + " please use the 'profile' option")); + + // Construct the build configuration item, which are depend from + // the architecture and the version. + const auto factoryEnd = m_factories.cend(); + const auto factoryIt = std::find_if( + m_factories.cbegin(), factoryEnd, + [arch, versionInfo](const auto &factory) { + return factory->canCreate(arch, versionInfo.version()); + }); + if (factoryIt == factoryEnd) { + throw ErrorInfo(Internal::Tr::tr("Incompatible target architecture '%1'" + " for IAR EW version %2xxx") + .arg(gen::utils::architectureName(arch)) + .arg(versionInfo.marketingVersion())); + } + auto configGroup = (*factoryIt)->create( + qbsProject, qbsProduct, qbsProductDeps); + appendChild(std::move(configGroup)); + } + + // Construct all file groups items. + QMapIterator dataIt(genProduct.data); + while (dataIt.hasNext()) { + dataIt.next(); + const auto groups = dataIt.value().groups(); + for (const auto &group : groups) { + // Ignore disabled groups (e.g. when its condition property is false). + if (!group.isEnabled()) + continue; + auto sourceArtifacts = group.sourceArtifacts(); + // Remove the linker script artifacts. + sourceArtifacts.erase(std::remove_if(sourceArtifacts.begin(), + sourceArtifacts.end(), + [](const auto &artifact){ + const auto tags = artifact.fileTags(); + return tags.contains(QLatin1String("linkerscript")); + }), sourceArtifacts.end()); + + if (sourceArtifacts.isEmpty()) + continue; + appendChild( + genProject, group.name(), sourceArtifacts); + } + } +} + +} // namespace qbs diff --git a/src/plugins/generator/iarew/iarewproject.h b/src/plugins/generator/iarew/iarewproject.h new file mode 100644 index 00000000..df260fcd --- /dev/null +++ b/src/plugins/generator/iarew/iarewproject.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWPROJECT_H +#define QBS_IAREWPROJECT_H + +#include +#include +#include + +#include + +namespace qbs { + +class IarewProject final : public gen::xml::Project +{ +public: + explicit IarewProject(const GeneratableProject &genProject, + const GeneratableProductData &genProduct, + const gen::VersionInfo &versionInfo); +private: + std::vector> m_factories; +}; + +} // namespace qbs + +#endif // QBS_IAREWPROJECT_H diff --git a/src/plugins/generator/iarew/iarewprojectwriter.cpp b/src/plugins/generator/iarew/iarewprojectwriter.cpp new file mode 100644 index 00000000..fcb2103f --- /dev/null +++ b/src/plugins/generator/iarew/iarewprojectwriter.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "iarewprojectwriter.h" + +namespace qbs { + +IarewProjectWriter::IarewProjectWriter(std::ostream *device) + : gen::xml::ProjectWriter(device) +{ +} + +void IarewProjectWriter::visitProjectStart(const gen::xml::Project *project) +{ + Q_UNUSED(project) + writer()->writeStartElement(QStringLiteral("project")); +} + +void IarewProjectWriter::visitProjectEnd(const gen::xml::Project *project) +{ + Q_UNUSED(project) + writer()->writeEndElement(); +} + +} // namespace qbs diff --git a/src/plugins/generator/iarew/iarewprojectwriter.h b/src/plugins/generator/iarew/iarewprojectwriter.h new file mode 100644 index 00000000..9356dbec --- /dev/null +++ b/src/plugins/generator/iarew/iarewprojectwriter.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWPROJECTWRITER_H +#define QBS_IAREWPROJECTWRITER_H + +#include + +namespace qbs { + +class IarewProjectWriter final : public gen::xml::ProjectWriter +{ + Q_DISABLE_COPY(IarewProjectWriter) +public: + explicit IarewProjectWriter(std::ostream *device); + +private: + void visitProjectStart(const gen::xml::Project *project) final; + void visitProjectEnd(const gen::xml::Project *project) final; +}; + +} // namespace qbs + +#endif // QBS_IAREWPROJECTWRITER_H diff --git a/src/plugins/generator/iarew/iarewsettingspropertygroup.cpp b/src/plugins/generator/iarew/iarewsettingspropertygroup.cpp new file mode 100644 index 00000000..a07d59e0 --- /dev/null +++ b/src/plugins/generator/iarew/iarewsettingspropertygroup.cpp @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "iarewoptionpropertygroup.h" +#include "iarewsettingspropertygroup.h" + +namespace qbs { + +constexpr int kDataWantNonLocalPropertyValue = 1; + +IarewSettingsPropertyGroup::IarewSettingsPropertyGroup() + : gen::xml::PropertyGroup(QByteArrayLiteral("settings")) +{ + // Append name property item. + m_nameProperty = appendChild( + QByteArrayLiteral("name"), QVariant{}); + + // Append archive version property item. + m_archiveVersionProperty = appendChild( + QByteArrayLiteral("archiveVersion"), QVariant{}); + + // Append data property group item. + m_dataPropertyGroup = appendChild( + QByteArrayLiteral("data")); + // Append data version property item. + m_dataVersionProperty = m_dataPropertyGroup->appendChild< + gen::xml::Property>( + QByteArrayLiteral("version"), QVariant{}); + // Append data want non-local property item. + m_dataPropertyGroup->appendChild( + QByteArrayLiteral("wantNonLocal"), + kDataWantNonLocalPropertyValue); + // Append data debug property item. + m_dataDebugProperty = m_dataPropertyGroup->appendChild< + gen::xml::Property>( + QByteArrayLiteral("debug"), QVariant{}); +} + +void IarewSettingsPropertyGroup::setName(const QByteArray &name) +{ + // There is no way to move-construct a QVariant from T, thus name is shallow-copied + m_nameProperty->setValue(QVariant(name)); +} + +QByteArray IarewSettingsPropertyGroup::name() const +{ + return m_nameProperty->value().toByteArray(); +} + +void IarewSettingsPropertyGroup::setArchiveVersion( + int archiveVersion) +{ + m_archiveVersionProperty->setValue(archiveVersion); +} + +int IarewSettingsPropertyGroup::archiveVersion() const +{ + return m_archiveVersionProperty->value().toInt(); +} + +void IarewSettingsPropertyGroup::setDataVersion(int dataVersion) +{ + m_dataVersionProperty->setValue(dataVersion); +} + +void IarewSettingsPropertyGroup::setDataDebugInfo(int debugInfo) +{ + m_dataDebugProperty->setValue(debugInfo); +} + +void IarewSettingsPropertyGroup::addOptionsGroup( + const QByteArray &name, + QVariantList states, + int version) +{ + m_dataPropertyGroup->appendChild(name, std::move(states), version); +} + +} // namespace qbs diff --git a/src/plugins/generator/iarew/iarewsettingspropertygroup.h b/src/plugins/generator/iarew/iarewsettingspropertygroup.h new file mode 100644 index 00000000..b1feebcc --- /dev/null +++ b/src/plugins/generator/iarew/iarewsettingspropertygroup.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWSETTINGSPROPERTYGROUP_H +#define QBS_IAREWSETTINGSPROPERTYGROUP_H + +#include + +namespace qbs { + +class IarewSettingsPropertyGroup : public gen::xml::PropertyGroup +{ +public: + explicit IarewSettingsPropertyGroup(); + + void setName(const QByteArray &name); + QByteArray name() const; + + void setArchiveVersion(int archiveVersion); + int archiveVersion() const; + +protected: + void setDataVersion(int dataVersion); + void setDataDebugInfo(int debugInfo); + + void addOptionsGroup(const QByteArray &name, QVariantList states, + int version = -1); + +private: + // Don't delete all this RAW pointers explicitly! + gen::xml::Property *m_nameProperty = nullptr; + gen::xml::Property *m_archiveVersionProperty = nullptr; + + gen::xml::Property *m_dataPropertyGroup = nullptr; + gen::xml::Property *m_dataVersionProperty = nullptr; + gen::xml::Property *m_dataDebugProperty = nullptr; +}; + +} // namespace qbs + +#endif // QBS_IAREWSETTINGSPROPERTYGROUP_H diff --git a/src/plugins/generator/iarew/iarewsourcefilepropertygroup.cpp b/src/plugins/generator/iarew/iarewsourcefilepropertygroup.cpp new file mode 100644 index 00000000..33c26e8b --- /dev/null +++ b/src/plugins/generator/iarew/iarewsourcefilepropertygroup.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "iarewsourcefilepropertygroup.h" +#include "iarewutils.h" + +#include + +#include + +namespace qbs { + +IarewSourceFilePropertyGroup::IarewSourceFilePropertyGroup( + const GeneratableProject &genProject, + const ArtifactData &sourceArtifact) + : gen::xml::PropertyGroup(QByteArrayLiteral("file")) +{ + // Create file path property item. + const QString fullFilePath = sourceArtifact.filePath(); + const QString relativeFilePath = IarewUtils::projectRelativeFilePath( + genProject.baseBuildDirectory().absolutePath(), + fullFilePath); + appendChild(QByteArrayLiteral("name"), + relativeFilePath); +} + +} // namespace qbs diff --git a/src/plugins/generator/iarew/iarewsourcefilepropertygroup.h b/src/plugins/generator/iarew/iarewsourcefilepropertygroup.h new file mode 100644 index 00000000..6d008357 --- /dev/null +++ b/src/plugins/generator/iarew/iarewsourcefilepropertygroup.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWSOURCEFILEPROPERTYGROUP_H +#define QBS_IAREWSOURCEFILEPROPERTYGROUP_H + +#include + +namespace qbs { + +class ArtifactData; +struct GeneratableProject; + +class IarewSourceFilePropertyGroup final + : public gen::xml::PropertyGroup +{ +public: + explicit IarewSourceFilePropertyGroup( + const GeneratableProject &genProject, + const ArtifactData &sourceArtifact); +}; + +} // namespace qbs + +#endif // QBS_IAREWSOURCEFILEPROPERTYGROUP_H diff --git a/src/plugins/generator/iarew/iarewsourcefilespropertygroup.cpp b/src/plugins/generator/iarew/iarewsourcefilespropertygroup.cpp new file mode 100644 index 00000000..adb5925b --- /dev/null +++ b/src/plugins/generator/iarew/iarewsourcefilespropertygroup.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "iarewsourcefilepropertygroup.h" +#include "iarewsourcefilespropertygroup.h" + +#include + +#include + +namespace qbs { + +IarewSourceFilesPropertyGroup::IarewSourceFilesPropertyGroup( + const GeneratableProject &genProject, + const QString &filesGroupName, + const QList &sourceFiles) + : gen::xml::PropertyGroup(QByteArrayLiteral("group")) +{ + // Create group name property item. + appendChild(QByteArrayLiteral("name"), + filesGroupName); + + // Create file paths property items. + for (const auto &sourceFile : sourceFiles) + appendChild(genProject, + sourceFile); +} + +} // namespace qbs diff --git a/src/plugins/generator/iarew/iarewsourcefilespropertygroup.h b/src/plugins/generator/iarew/iarewsourcefilespropertygroup.h new file mode 100644 index 00000000..8d8a4be0 --- /dev/null +++ b/src/plugins/generator/iarew/iarewsourcefilespropertygroup.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWSOURCEFILESPROPERTYGROUP_H +#define QBS_IAREWSOURCEFILESPROPERTYGROUP_H + +#include + +namespace qbs { + +class IarewSourceFilesPropertyGroup final + : public gen::xml::PropertyGroup +{ +public: + explicit IarewSourceFilesPropertyGroup( + const GeneratableProject &genProject, + const QString &filesGroupName, + const QList &sourceFiles); +}; + +} // namespace qbs + +#endif // QBS_IAREWSOURCEFILESPROPERTYGROUP_H diff --git a/src/plugins/generator/iarew/iarewtoolchainpropertygroup.cpp b/src/plugins/generator/iarew/iarewtoolchainpropertygroup.cpp new file mode 100644 index 00000000..dba5015b --- /dev/null +++ b/src/plugins/generator/iarew/iarewtoolchainpropertygroup.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "iarewtoolchainpropertygroup.h" + +namespace qbs { + +IarewToolchainPropertyGroup::IarewToolchainPropertyGroup( + const QByteArray &toolchainName) + : gen::xml::PropertyGroup(QByteArrayLiteral("toolchain")) +{ + // Append toolchain name property item. + appendProperty(QByteArrayLiteral("name"), toolchainName); +} + +} // namespace qbs diff --git a/src/plugins/generator/iarew/iarewtoolchainpropertygroup.h b/src/plugins/generator/iarew/iarewtoolchainpropertygroup.h new file mode 100644 index 00000000..e5bd6f07 --- /dev/null +++ b/src/plugins/generator/iarew/iarewtoolchainpropertygroup.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWTOOLCHAINPROPERTYGROUP_H +#define QBS_IAREWTOOLCHAINPROPERTYGROUP_H + +#include + +namespace qbs { + +class IarewToolchainPropertyGroup final + : public gen::xml::PropertyGroup +{ +public: + explicit IarewToolchainPropertyGroup( + const QByteArray &toolchainName); +}; + +} // namespace qbs + +#endif // QBS_IAREWTOOLCHAINPROPERTYGROUP_H diff --git a/src/plugins/generator/iarew/iarewutils.cpp b/src/plugins/generator/iarew/iarewutils.cpp new file mode 100644 index 00000000..7196a391 --- /dev/null +++ b/src/plugins/generator/iarew/iarewutils.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "iarewutils.h" + +#include + +namespace qbs { +namespace IarewUtils { + +QString toolkitRootPath(const ProductData &qbsProduct) +{ + QDir dir(qbsProduct.moduleProperties() + .getModuleProperty(Internal::StringConstants::cppModule(), + QStringLiteral("toolchainInstallPath")) + .toString()); + dir.cdUp(); + return dir.absolutePath(); +} + +QString dlibToolkitRootPath(const ProductData &qbsProduct) +{ + return toolkitRootPath(qbsProduct) + QLatin1String("/lib/dlib"); +} + +QString clibToolkitRootPath(const ProductData &qbsProduct) +{ + return toolkitRootPath(qbsProduct) + QLatin1String("/lib/clib"); +} + +QString libToolkitRootPath(const ProductData &qbsProduct) +{ + return toolkitRootPath(qbsProduct) + QLatin1String("/lib"); +} + +QString toolkitRelativeFilePath(const QString &basePath, + const QString &fullFilePath) +{ + return QLatin1String("$TOOLKIT_DIR$/") + + gen::utils::relativeFilePath(basePath, fullFilePath); +} + +QString projectRelativeFilePath(const QString &basePath, + const QString &fullFilePath) +{ + return QLatin1String("$PROJ_DIR$/") + + gen::utils::relativeFilePath(basePath, fullFilePath); +} + +OutputBinaryType outputBinaryType(const ProductData &qbsProduct) +{ + const auto &qbsProductType = qbsProduct.type(); + if (qbsProductType.contains(QLatin1String("application"))) + return ApplicationOutputType; + if (qbsProductType.contains(QLatin1String("staticlibrary"))) + return LibraryOutputType; + return ApplicationOutputType; +} + +QString flagValue(const QStringList &flags, const QString &flagKey) +{ + // Seach for full 'flagKey' option matching. + const auto flagBegin = flags.cbegin(); + const auto flagEnd = flags.cend(); + auto flagIt = std::find_if(flagBegin, flagEnd, [flagKey](const QString &flag) { + return flag == flagKey; + }); + if (flagIt == flagEnd) { + // Search for start/end of 'flagKey' matching. + flagIt = std::find_if(flagBegin, flagEnd, [flagKey](const QString &flag) { + return flag.startsWith(flagKey) || flag.endsWith(flagKey); + }); + if (flagIt == flagEnd) + return {}; + } + + QString value; + // Check that option is in form of 'flagKey='. + if (flagIt->contains(QLatin1Char('='))) { + value = flagIt->split(QLatin1Char('=')).at(1).trimmed(); + } else if (flagKey.count() < flagIt->count()) { + // In this case an option is in form of 'flagKey'. + value = flagIt->mid(flagKey.count()).trimmed(); + } else { + // In this case an option is in form of 'flagKey '. + ++flagIt; + if (flagIt < flagEnd) + value = (*flagIt).trimmed(); + else + return {}; + } + return value; +} + +QVariantList flagValues(const QStringList &flags, const QString &flagKey) +{ + QVariantList values; + for (auto flagIt = flags.cbegin(); flagIt < flags.cend(); ++flagIt) { + if (*flagIt != flagKey) + continue; + ++flagIt; + values.push_back(*flagIt); + } + return values; +} + +QStringList cppModuleCompilerFlags(const PropertyMap &qbsProps) +{ + return gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("driverFlags"), QStringLiteral("cFlags"), + QStringLiteral("cppFlags"), QStringLiteral("cxxFlags"), + QStringLiteral("commonCompilerFlags")}); +} + +QStringList cppModuleAssemblerFlags(const PropertyMap &qbsProps) +{ + return gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("assemblerFlags")}); +} + +QStringList cppModuleLinkerFlags(const PropertyMap &qbsProps) +{ + return gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("driverFlags"), + QStringLiteral("driverLinkerFlags")}); +} + +} // namespace IarewUtils +} // namespace qbs diff --git a/src/plugins/generator/iarew/iarewutils.h b/src/plugins/generator/iarew/iarewutils.h new file mode 100644 index 00000000..2d32ac18 --- /dev/null +++ b/src/plugins/generator/iarew/iarewutils.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWUTILS_H +#define QBS_IAREWUTILS_H + +#include + +#include + +namespace qbs { +namespace IarewUtils { + +enum OutputBinaryType { + ApplicationOutputType, + LibraryOutputType +}; + +OutputBinaryType outputBinaryType(const ProductData &qbsProduct); + +QString toolkitRootPath(const ProductData &qbsProduct); + +QString dlibToolkitRootPath(const ProductData &qbsProduct); + +QString clibToolkitRootPath(const ProductData &qbsProduct); + +QString libToolkitRootPath(const ProductData &qbsProduct); + +QString toolkitRelativeFilePath(const QString &basePath, + const QString &fullFilePath); + +QString projectRelativeFilePath(const QString &basePath, + const QString &fullFilePath); + +QString flagValue(const QStringList &flags, const QString &flagKey); + +QVariantList flagValues(const QStringList &flags, const QString &flagKey); + +QStringList cppModuleCompilerFlags(const PropertyMap &qbsProps); + +QStringList cppModuleAssemblerFlags(const PropertyMap &qbsProps); + +QStringList cppModuleLinkerFlags(const PropertyMap &qbsProps); + +} // namespace IarewUtils +} // namespace qbs + +#endif // QBS_IAREWUTILS_H diff --git a/src/plugins/generator/iarew/iarewversioninfo.h b/src/plugins/generator/iarew/iarewversioninfo.h new file mode 100644 index 00000000..3dc45955 --- /dev/null +++ b/src/plugins/generator/iarew/iarewversioninfo.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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_IAREWVERSIONINFO_H +#define QBS_IAREWVERSIONINFO_H + +#include +#include + +namespace qbs { +namespace IarewVersionInfo { + +constexpr gen::VersionInfo knownVersions[] = { + {Version(8), {gen::utils::Architecture::Arm}}, + {Version(7), {gen::utils::Architecture::Avr, + gen::utils::Architecture::Msp430}}, + {Version(10), {gen::utils::Architecture::Mcs51}}, + {Version(3), {gen::utils::Architecture::Stm8}}, +}; + +} // namespace IarewVersionInfo +} // namespace qbs + +#endif // QBS_IAREWVERSIONINFO_H diff --git a/src/plugins/generator/iarew/iarewworkspace.cpp b/src/plugins/generator/iarew/iarewworkspace.cpp new file mode 100644 index 00000000..bfe22aea --- /dev/null +++ b/src/plugins/generator/iarew/iarewworkspace.cpp @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "iarewworkspace.h" + +#include + +namespace qbs { + +IarewWorkspace::IarewWorkspace(const QString &workspacePath) + : gen::xml::Workspace(workspacePath) +{ + appendChild( + QByteArrayLiteral("batchBuild")); +} + +void IarewWorkspace::addProject(const QString &projectFilePath) +{ + const QString relativeProjectPath = QLatin1String("$WS_DIR$/") + + m_baseDirectory.relativeFilePath(projectFilePath); + + const auto projectGroup = appendChild( + QByteArrayLiteral("project")); + projectGroup->appendProperty("path", relativeProjectPath); +} + +} // namespace qbs diff --git a/src/plugins/generator/iarew/iarewworkspace.h b/src/plugins/generator/iarew/iarewworkspace.h new file mode 100644 index 00000000..ea14e417 --- /dev/null +++ b/src/plugins/generator/iarew/iarewworkspace.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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_IAREWWORKSPACE_H +#define QBS_IAREWWORKSPACE_H + +#include + +#include + +namespace qbs { + +class IarewWorkspace final : public gen::xml::Workspace +{ +public: + explicit IarewWorkspace(const QString &workspacePath); + void addProject(const QString &projectPath) final; +}; + +} // namespace qbs + +#endif // QBS_IAREWWORKSPACE_H diff --git a/src/plugins/generator/iarew/iarewworkspacewriter.cpp b/src/plugins/generator/iarew/iarewworkspacewriter.cpp new file mode 100644 index 00000000..b06080e3 --- /dev/null +++ b/src/plugins/generator/iarew/iarewworkspacewriter.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "iarewworkspacewriter.h" + +namespace qbs { + +IarewWorkspaceWriter::IarewWorkspaceWriter(std::ostream *device) + : gen::xml::WorkspaceWriter(device) +{ +} + +void IarewWorkspaceWriter::visitWorkspaceStart(const gen::xml::Workspace *workspace) +{ + Q_UNUSED(workspace) + writer()->writeStartElement(QStringLiteral("workspace")); +} + +void IarewWorkspaceWriter::visitWorkspaceEnd(const gen::xml::Workspace *workspace) +{ + Q_UNUSED(workspace) + writer()->writeEndElement(); +} + +} // namespace qbs diff --git a/src/plugins/generator/iarew/iarewworkspacewriter.h b/src/plugins/generator/iarew/iarewworkspacewriter.h new file mode 100644 index 00000000..e60b102d --- /dev/null +++ b/src/plugins/generator/iarew/iarewworkspacewriter.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_IAREWWORKSPACEWRITER_H +#define QBS_IAREWWORKSPACEWRITER_H + +#include + +namespace qbs { + +class IarewWorkspaceWriter final : public gen::xml::WorkspaceWriter +{ + Q_DISABLE_COPY(IarewWorkspaceWriter) +public: + explicit IarewWorkspaceWriter(std::ostream *device); + +private: + void visitWorkspaceStart(const gen::xml::Workspace *workspace) final; + void visitWorkspaceEnd(const gen::xml::Workspace *workspace) final; +}; + +} // namespace qbs + +#endif // QBS_IAREWWORKSPACEWRITER_H diff --git a/src/plugins/generator/keiluv/archs/arm/armbuildtargetgroup_v5.cpp b/src/plugins/generator/keiluv/archs/arm/armbuildtargetgroup_v5.cpp new file mode 100644 index 00000000..c5cb5f04 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armbuildtargetgroup_v5.cpp @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armbuildtargetgroup_v5.h" +#include "armcommonpropertygroup_v5.h" +#include "armdebugoptiongroup_v5.h" +#include "armdlloptiongroup_v5.h" +#include "armtargetcommonoptionsgroup_v5.h" +#include "armtargetgroup_v5.h" +#include "armutilitiesgroup_v5.h" + +#include "../../keiluvconstants.h" +#include "../../keiluvfilesgroupspropertygroup.h" + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +ArmBuildTargetGroup::ArmBuildTargetGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps) + : gen::xml::PropertyGroup("Target") +{ + // Append target name item (it is a build configuration name). + const QString targetName = gen::utils::buildConfigurationName( + qbsProject); + appendProperty(QByteArrayLiteral("TargetName"), targetName); + // Append toolset number group item. + appendChild(QByteArrayLiteral("ToolsetNumber"), + QByteArrayLiteral("0x4")); + // Append toolset name group item. + appendChild(QByteArrayLiteral("ToolsetName"), + QByteArrayLiteral("ARM-ADS")); + + // Append target option group item. + const auto targetOptionGroup = appendChild( + QByteArrayLiteral("TargetOption")); + + targetOptionGroup->appendChild( + qbsProject, qbsProduct); + targetOptionGroup->appendChild( + qbsProject, qbsProduct); + targetOptionGroup->appendChild( + qbsProject, qbsProduct); + targetOptionGroup->appendChild( + qbsProject, qbsProduct); + targetOptionGroup->appendChild( + qbsProject, qbsProduct); + targetOptionGroup->appendChild( + qbsProject, qbsProduct); + + // Append files group. + appendChild(qbsProject, qbsProduct, + qbsProductDeps); +} + +bool ArmBuildTargetGroupFactory::canCreate( + gen::utils::Architecture arch, + const Version &version) const +{ + return arch == gen::utils::Architecture::Arm + && version.majorVersion() == qbs::KeiluvConstants::v5::kUVisionVersion; +} + +std::unique_ptr +ArmBuildTargetGroupFactory::create( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps) const +{ + const auto group = new ArmBuildTargetGroup( + qbsProject, qbsProduct, qbsProductDeps); + return std::unique_ptr(group); +} + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/arm/armbuildtargetgroup_v5.h b/src/plugins/generator/keiluv/archs/arm/armbuildtargetgroup_v5.h new file mode 100644 index 00000000..9b8d9fe2 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armbuildtargetgroup_v5.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVARMBUILDTARGETGROUP_V5_H +#define QBS_KEILUVARMBUILDTARGETGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +class ArmBuildTargetGroup final : public gen::xml::PropertyGroup +{ +private: + explicit ArmBuildTargetGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps); + + friend class ArmBuildTargetGroupFactory; +}; + +class ArmBuildTargetGroupFactory final + : public gen::xml::PropertyGroupFactory +{ +public: + bool canCreate(gen::utils::Architecture arch, + const Version &version) const final; + + std::unique_ptr create( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps) const final; +}; + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVARMBUILDTARGETGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/arm/armcommonpropertygroup_v5.cpp b/src/plugins/generator/keiluv/archs/arm/armcommonpropertygroup_v5.cpp new file mode 100644 index 00000000..e7a73823 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armcommonpropertygroup_v5.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armcommonpropertygroup_v5.h" + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +ArmCommonPropertyGroup::ArmCommonPropertyGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("CommonProperty") +{ + Q_UNUSED(qbsProject) + Q_UNUSED(qbsProduct) +} + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/arm/armcommonpropertygroup_v5.h b/src/plugins/generator/keiluv/archs/arm/armcommonpropertygroup_v5.h new file mode 100644 index 00000000..4d45decc --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armcommonpropertygroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVARMCOMMONPROPERTYGROUP_V5_H +#define QBS_KEILUVARMCOMMONPROPERTYGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +class ArmCommonPropertyGroup final : public gen::xml::PropertyGroup +{ +public: + explicit ArmCommonPropertyGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs + +#endif // QBS_MCS51COMMONPROPERTYGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/arm/armdebugoptiongroup_v5.cpp b/src/plugins/generator/keiluv/archs/arm/armdebugoptiongroup_v5.cpp new file mode 100644 index 00000000..553df646 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armdebugoptiongroup_v5.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armdebugoptiongroup_v5.h" + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +ArmDebugOptionGroup::ArmDebugOptionGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("DebugOption") +{ + Q_UNUSED(qbsProject) + Q_UNUSED(qbsProduct) +} + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/arm/armdebugoptiongroup_v5.h b/src/plugins/generator/keiluv/archs/arm/armdebugoptiongroup_v5.h new file mode 100644 index 00000000..22d5bb88 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armdebugoptiongroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVARMDEBUGOPTIONGROUP_V5_H +#define QBS_KEILUVARMDEBUGOPTIONGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +class ArmDebugOptionGroup final : public gen::xml::PropertyGroup +{ +public: + explicit ArmDebugOptionGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVARMDEBUGOPTIONGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/arm/armdlloptiongroup_v5.cpp b/src/plugins/generator/keiluv/archs/arm/armdlloptiongroup_v5.cpp new file mode 100644 index 00000000..9e1bdff0 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armdlloptiongroup_v5.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armdlloptiongroup_v5.h" + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +ArmDllOptionGroup::ArmDllOptionGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("DllOption") +{ + Q_UNUSED(qbsProject) + Q_UNUSED(qbsProduct) +} + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/arm/armdlloptiongroup_v5.h b/src/plugins/generator/keiluv/archs/arm/armdlloptiongroup_v5.h new file mode 100644 index 00000000..948eb156 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armdlloptiongroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVARMDLLOPTIONGROUP_V5_H +#define QBS_KEILUVARMDLLOPTIONGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +class ArmDllOptionGroup final : public gen::xml::PropertyGroup +{ +public: + explicit ArmDllOptionGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVARMDLLOPTIONGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/arm/armtargetassemblergroup_v5.cpp b/src/plugins/generator/keiluv/archs/arm/armtargetassemblergroup_v5.cpp new file mode 100644 index 00000000..424cb335 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armtargetassemblergroup_v5.cpp @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armtargetassemblergroup_v5.h" + +#include "../../keiluvutils.h" + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +namespace { + +struct AssemblerPageOptions final +{ + explicit AssemblerPageOptions(const Project &qbsProject, + const ProductData &qbsProduct) + { + Q_UNUSED(qbsProject) + + const auto &qbsProps = qbsProduct.moduleProperties(); + const auto flags = qbs::KeiluvUtils::cppModuleAssemblerFlags(qbsProps); + + // Read-only position independent. + enableRopi = flags.contains(QLatin1String("/ropi")); + // Read-write position independent. + enableRwpi = flags.contains(QLatin1String("/rwpi")); + // Enable thumb mode. + enableThumbMode = flags.contains(QLatin1String("--16")); + // Split load and store multiple. + splitLdm = flags.contains(QLatin1String("--split_ldm")); + // Generation code. + generateExecuteOnlyCode = flags.contains(QLatin1String("--execute_only")); + + // Warning levels. + const QString wLevel = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("warningLevel")); + disableWarnings = wLevel == QLatin1String("none"); + + // Define symbols. + defineSymbols = qbs::KeiluvUtils::defines(qbsProps); + // Include paths. + includePaths = qbs::KeiluvUtils::includes(qbsProps); + + // Interpret other compiler flags as a misc controls (exclude only + // that flags which are was already handled). + for (auto flagIt = flags.cbegin(); flagIt < flags.cend(); ++flagIt) { + if (flagIt->contains(QLatin1String("/ropi")) + || flagIt->contains(QLatin1String("/rwpi")) + || flagIt->contains(QLatin1String("--16")) + || flagIt->contains(QLatin1String("--split_ldm")) + || flagIt->contains(QLatin1String("--execute_only")) + || flagIt->contains(QLatin1String("--nowarn")) + ) { + continue; + } + if (flagIt->startsWith(QLatin1String("-I")) + || flagIt->startsWith(QLatin1String("--cpu")) + || flagIt->startsWith(QLatin1String("--fpu")) + || flagIt->startsWith(QLatin1String("-pd")) + ) { + ++flagIt; + continue; + } + miscControls.push_back(*flagIt); + } + } + + int enableRopi = 0; + int enableRwpi = 0; + int enableThumbMode = 0; + int disableWarnings = 0; + int splitLdm = 0; + int generateExecuteOnlyCode = 0; + + QStringList defineSymbols; + QStringList includePaths; + QStringList miscControls; +}; + +} // namespace + +ArmTargetAssemblerGroup::ArmTargetAssemblerGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("Aads") +{ + const AssemblerPageOptions opts(qbsProject, qbsProduct); + + // Add 'ROPI' item. + appendProperty(QByteArrayLiteral("Ropi"), opts.enableRopi); + // Add 'RWPI' item. + appendProperty(QByteArrayLiteral("Rwpi"), opts.enableRwpi); + // Add 'Use thumb mode' item. + appendProperty(QByteArrayLiteral("thumb"), opts.enableThumbMode); + // Add 'Slpit LDM' item. + appendProperty(QByteArrayLiteral("SplitLS"), opts.splitLdm); + // Add 'Disable warnings' item. + appendProperty(QByteArrayLiteral("NoWarn"), opts.disableWarnings); + // Add 'Generate code exedutable only' item. + appendProperty(QByteArrayLiteral("useXo"), opts.generateExecuteOnlyCode); + + // Add other various controls. + // Note: A sub-items order makes sense! + const auto variousControlsGroup = appendChild( + QByteArrayLiteral("VariousControls")); + // Add 'Misc Controls' item. + variousControlsGroup->appendMultiLineProperty( + QByteArrayLiteral("MiscControls"), + opts.miscControls, QLatin1Char(' ')); + // Add 'Define' item. + variousControlsGroup->appendMultiLineProperty( + QByteArrayLiteral("Define"), opts.defineSymbols); + // Add an empty 'Undefine' item. + variousControlsGroup->appendProperty( + QByteArrayLiteral("Undefine"), {}); + // Add 'Include Paths' item. + variousControlsGroup->appendMultiLineProperty( + QByteArrayLiteral("IncludePath"), + opts.includePaths, QLatin1Char(';')); +} + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/arm/armtargetassemblergroup_v5.h b/src/plugins/generator/keiluv/archs/arm/armtargetassemblergroup_v5.h new file mode 100644 index 00000000..d61b11fe --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armtargetassemblergroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVARMTARGETASSEMBLERGROUP_V3 +#define QBS_KEILUVARMTARGETASSEMBLERGROUP_V3 + +#include + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +class ArmTargetAssemblerGroup final : public gen::xml::PropertyGroup +{ +public: + explicit ArmTargetAssemblerGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVARMTARGETASSEMBLERGROUP_V3 diff --git a/src/plugins/generator/keiluv/archs/arm/armtargetcommonoptionsgroup_v5.cpp b/src/plugins/generator/keiluv/archs/arm/armtargetcommonoptionsgroup_v5.cpp new file mode 100644 index 00000000..f7e3de3d --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armtargetcommonoptionsgroup_v5.cpp @@ -0,0 +1,208 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armtargetcommonoptionsgroup_v5.h" + +#include "../../keiluvutils.h" + +#include + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +namespace { + +const struct DeviceEntry { + QByteArray cpu; // CPU option. + std::set fpus; // FPU's options. + QByteArray device; // Project file entry. +} deviceDict[] = { + {"8-M.Base", {}, "ARMv8MBL"}, + {"8-M.Main", {"FPv5-SP"}, "ARMv8MML_SP"}, + {"8-M.Main", {"FPv5_D16"}, "ARMv8MML_DP"}, + {"8-M.Main", {"SoftVFP"},"ARMv8MML"}, + {"8-M.Main.dsp", {"FPv5-SP"}, "ARMv8MML_DSP_SP"}, + {"8-M.Main.dsp", {"FPv5_D16"}, "ARMv8MML_DSP_DP"}, + {"8-M.Main.dsp", {"SoftVFP"}, "ARMv8MML_DSP"}, + {"Cortex-M0", {}, "ARMCM0"}, + {"Cortex-M0+", {}, "ARMCM0P"}, + {"Cortex-M0plus", {}, "ARMCM0P"}, + {"Cortex-M23", {}, "ARMCM23"}, // same as ARMCM23_TZ + {"Cortex-M3", {}, "ARMCM3"}, + {"Cortex-M4", {}, "ARMCM4"}, + {"Cortex-M4.fp", {}, "ARMCM4_FP"}, + {"Cortex-M7", {"SoftVFP"}, "ARMCM7"}, + {"Cortex-M7.fp.dp", {}, "ARMCM7_DP"}, + {"Cortex-M7.fp.sp", {}, "ARMCM7_SP"}, + {"SC000", {}, "ARMSC000"}, + {"SC300", {}, "ARMSC300"}, + {"Cortex-M33.no_dsp", {"SoftVFP"}, "ARMCM33"}, // same as ARMCM33_TZ + {"Cortex-M33", {"FPv5-SP", "softvfp+vfpv2"}, "ARMCM33_DSP_FP"}, // same as ARMCM33_DSP_FP_TZ +}; + +struct CommonPageOptions final +{ + explicit CommonPageOptions(const Project &qbsProject, + const ProductData &qbsProduct) + { + Q_UNUSED(qbsProject) + + const auto &qbsProps = qbsProduct.moduleProperties(); + const auto flags = KeiluvUtils::cppModuleCompilerFlags(qbsProps); + + // Browse information. + // ??? + + // Debug information. + debugInfo = gen::utils::debugInformation(qbsProduct); + + // Output parameters. + executableName = gen::utils::targetBinary(qbsProduct); + // Fix output binary name if it is a library. Because + // the IDE appends an additional suffix (.LIB) to end + // of an output library name. + if (executableName.endsWith(QLatin1String(".lib"))) + executableName = qbsProduct.targetName(); + + const QString baseDirectory = gen::utils::buildRootPath(qbsProject); + objectDirectory = QDir::toNativeSeparators( + gen::utils::objectsOutputDirectory( + baseDirectory, qbsProduct)); + listingDirectory = QDir::toNativeSeparators( + gen::utils::listingOutputDirectory( + baseDirectory, qbsProduct)); + + // Target type. + targetType = KeiluvUtils::outputBinaryType(qbsProduct); + + // Detect the device name from the command line options + // (like --cpu and --fpu). + const auto cpu = gen::utils::firstFlagValue( + flags, QStringLiteral("--cpu")).toLatin1(); + const auto fpus = gen::utils::allFlagValues( + flags, QStringLiteral("--fpu")); + + for (const auto &deviceEntry : deviceDict) { + // Since Qt 5.12 we can use QByteArray::compare(..., Qt::CaseInsensitive) + // instead. + if (cpu.toLower() != deviceEntry.cpu.toLower()) + continue; + + size_t fpuMatches = 0; + const auto dictFpuBegin = std::cbegin(deviceDict->fpus); + const auto dictFpuEnd = std::cend(deviceDict->fpus); + for (const auto &fpu : fpus) { + const auto dictFpuIt = std::find_if( + dictFpuBegin, dictFpuEnd, + [fpu](const QByteArray &dictFpu) { + return fpu.compare(QString::fromLatin1(dictFpu), + Qt::CaseInsensitive) == 0; + }); + if (dictFpuIt != dictFpuEnd) + ++fpuMatches; + } + + if (fpuMatches < deviceEntry.fpus.size()) + continue; + + deviceName = QString::fromLatin1(deviceEntry.device); + cpuType = QString::fromLatin1(deviceEntry.cpu); + break; + } + } + + int debugInfo = false; + int browseInfo = false; + QString deviceName; + QString cpuType; + QString executableName; + QString objectDirectory; + QString listingDirectory; + KeiluvUtils::OutputBinaryType targetType = + KeiluvUtils::ApplicationOutputType; +}; + +} // namespace + +ArmTargetCommonOptionsGroup::ArmTargetCommonOptionsGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("TargetCommonOption") +{ + const CommonPageOptions opts(qbsProject, qbsProduct); + + // Fill device items. + appendProperty(QByteArrayLiteral("Device"), + opts.deviceName); + appendProperty(QByteArrayLiteral("Vendor"), + QByteArrayLiteral("ARM")); + appendProperty(QByteArrayLiteral("PackID"), + QByteArrayLiteral("ARM.CMSIS.5.6.0")); + appendProperty(QByteArrayLiteral("PackURL"), + QByteArrayLiteral("http://www.keil.com/pack/")); + + const auto cpuType = QStringLiteral("CPUTYPE(\"%1\")") + .arg(opts.cpuType); + appendProperty(QByteArrayLiteral("Cpu"), cpuType); + + // Add 'Debug Information' item. + appendProperty(QByteArrayLiteral("DebugInformation"), + opts.debugInfo); + // Add 'Browse Information' item. + appendProperty(QByteArrayLiteral("BrowseInformation"), + opts.browseInfo); + + // Add 'Name of Executable'. + appendProperty(QByteArrayLiteral("OutputName"), + opts.executableName); + // Add 'Output objects directory'. + appendProperty(QByteArrayLiteral("OutputDirectory"), + opts.objectDirectory); + // Add 'Output listing directory'. + appendProperty(QByteArrayLiteral("ListingPath"), + opts.listingDirectory); + + // Add 'Create Executable/Library' item. + const int isExecutable = (opts.targetType + == KeiluvUtils::ApplicationOutputType); + const int isLibrary = (opts.targetType + == KeiluvUtils::LibraryOutputType); + appendProperty(QByteArrayLiteral("CreateExecutable"), + isExecutable); + appendProperty(QByteArrayLiteral("CreateLib"), + isLibrary); +} + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/arm/armtargetcommonoptionsgroup_v5.h b/src/plugins/generator/keiluv/archs/arm/armtargetcommonoptionsgroup_v5.h new file mode 100644 index 00000000..b7d4ffee --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armtargetcommonoptionsgroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVARMTARGETCOMMONOPTIONSGROUP_V5_H +#define QBS_KEILUVARMTARGETCOMMONOPTIONSGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +class ArmTargetCommonOptionsGroup final : public gen::xml::PropertyGroup +{ +public: + explicit ArmTargetCommonOptionsGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVARMTARGETCOMMONOPTIONSGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/arm/armtargetcompilergroup_v5.cpp b/src/plugins/generator/keiluv/archs/arm/armtargetcompilergroup_v5.cpp new file mode 100644 index 00000000..c923bd9b --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armtargetcompilergroup_v5.cpp @@ -0,0 +1,218 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armtargetcompilergroup_v5.h" + +#include "../../keiluvutils.h" + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +namespace { + +struct CompilerPageOptions final +{ + enum WarningLevel { + WarningLevelUnspecified = 0, + WarningLevelNone, + WarningLevelAll + }; + + enum OptimizationLevel { + OptimizationLevelUnspecified = 0, + OptimizationLevelNone, + OptimizationLevelOne, + OptimizationLevelTwo, + OptimizationLevelThree, + }; + + explicit CompilerPageOptions(const Project &qbsProject, + const ProductData &qbsProduct) + { + Q_UNUSED(qbsProject) + + const auto &qbsProps = qbsProduct.moduleProperties(); + const auto flags = qbs::KeiluvUtils::cppModuleCompilerFlags(qbsProps); + + // Warning levels. + const QString wLevel = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("warningLevel")); + if (wLevel == QLatin1String("none")) + warningLevel = WarningLevelNone; + else if (wLevel == QLatin1String("all")) + warningLevel = WarningLevelAll; + else + warningLevel = WarningLevelUnspecified; + + // Generation code. + generateExecuteOnlyCode = flags.contains(QLatin1String("--execute_only")); + + // Optimization levels. + const QString oLevel = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("optimization")); + if (oLevel == QLatin1String("fast")) + enableTimeOptimization = 1; + else if (oLevel == QLatin1String("small")) + optimizationLevel = OptimizationLevelThree; + else if (oLevel == QLatin1String("none")) + optimizationLevel = OptimizationLevelNone; + + // Split load and store multiple. + splitLdm = flags.contains(QLatin1String("--split_ldm")); + // One ELF section per function. + splitSections = flags.contains(QLatin1String("--split_sections")); + // String ANSI C. + useStrictAnsiC = flags.contains(QLatin1String("--strict")); + // Enum container always int. + forceEnumAsInt = flags.contains(QLatin1String("--enum_is_int")); + // Plain char is signed. + useSignedChar = flags.contains(QLatin1String("--signed_chars")); + // Read-only position independent. + enableRopi = flags.contains(QLatin1String("/ropi")); + // Read-write position independent. + enableRwpi = flags.contains(QLatin1String("/rwpi")); + + // C-language version. + const QString clVersion = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("cLanguageVersion")); + // C99 mode. + useC99Language = clVersion.contains(QLatin1String("c99")); + + // Define symbols. + defineSymbols = qbs::KeiluvUtils::defines(qbsProps); + // Include paths. + includePaths = qbs::KeiluvUtils::includes(qbsProps); + + // Interpret other compiler flags as a misc controls (exclude only + // that flags which are was already handled). + for (auto flagIt = flags.cbegin(); flagIt < flags.cend(); ++flagIt) { + if (flagIt->contains(QLatin1String("--execute_only")) + || flagIt->contains(QLatin1String("--split_ldm")) + || flagIt->contains(QLatin1String("--split_sections")) + || flagIt->contains(QLatin1String("--strict")) + || flagIt->contains(QLatin1String("--enum_is_int")) + || flagIt->contains(QLatin1String("--signed_chars")) + || flagIt->contains(QLatin1String("/ropi")) + || flagIt->contains(QLatin1String("/rwpi")) + || flagIt->contains(QLatin1String("--c99")) + ) { + continue; + } + if (flagIt->startsWith(QLatin1String("-O")) + || flagIt->startsWith(QLatin1String("-W")) + || flagIt->startsWith(QLatin1String("-D")) + || flagIt->startsWith(QLatin1String("-I")) + || flagIt->startsWith(QLatin1String("--cpu")) + || flagIt->startsWith(QLatin1String("--fpu")) + ) { + ++flagIt; + continue; + } + miscControls.push_back(*flagIt); + } + } + + WarningLevel warningLevel = WarningLevelAll; + OptimizationLevel optimizationLevel = OptimizationLevelUnspecified; + int enableTimeOptimization = 0; + int generateExecuteOnlyCode = 0; + int splitLdm = 0; + int splitSections = 0; + int useStrictAnsiC = 0; + int forceEnumAsInt = 0; + int useSignedChar = 0; + int enableRopi = 0; + int enableRwpi = 0; + int useC99Language = 0; + + QStringList defineSymbols; + QStringList includePaths; + QStringList miscControls; +}; + +} // namespace + +ArmTargetCompilerGroup::ArmTargetCompilerGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("Cads") +{ + const CompilerPageOptions opts(qbsProject, qbsProduct); + + // Add 'Code Optimization' items. + appendProperty(QByteArrayLiteral("Optim"), opts.optimizationLevel); + appendProperty(QByteArrayLiteral("oTime"), opts.enableTimeOptimization); + // Add 'Slpit LDM' item. + appendProperty(QByteArrayLiteral("SplitLS"), opts.splitLdm); + // Add 'Slpit sections' item. + appendProperty(QByteArrayLiteral("OneElfS"), opts.splitSections); + // Add 'Strict ANSI C' item. + appendProperty(QByteArrayLiteral("Strict"), opts.useStrictAnsiC); + // Add 'Enums as int' item. + appendProperty(QByteArrayLiteral("EnumInt"), opts.forceEnumAsInt); + // Add 'Plain char as signed' item. + appendProperty(QByteArrayLiteral("PlainCh"), opts.useSignedChar); + // Add 'ROPI' item. + appendProperty(QByteArrayLiteral("Ropi"), opts.enableRopi); + // Add 'RWPI' item. + appendProperty(QByteArrayLiteral("Rwpi"), opts.enableRwpi); + // Add 'Warnings' item. + appendProperty(QByteArrayLiteral("wLevel"), opts.warningLevel); + // Add 'Use C99' item. + appendProperty(QByteArrayLiteral("uC99"), opts.useC99Language); + // Add 'Generate code exedutable only' item. + appendProperty(QByteArrayLiteral("useXo"), opts.generateExecuteOnlyCode); + + // Add other various controls. + // Note: A sub-items order makes sense! + const auto variousControlsGroup = appendChild( + QByteArrayLiteral("VariousControls")); + // Add 'Misc Controls' item. + variousControlsGroup->appendMultiLineProperty( + QByteArrayLiteral("MiscControls"), + opts.miscControls, QLatin1Char(' ')); + // Add 'Define' item. + variousControlsGroup->appendMultiLineProperty( + QByteArrayLiteral("Define"), opts.defineSymbols); + // Add an empty 'Undefine' item. + variousControlsGroup->appendProperty( + QByteArrayLiteral("Undefine"), {}); + // Add 'Include Paths' item. + variousControlsGroup->appendMultiLineProperty( + QByteArrayLiteral("IncludePath"), + opts.includePaths, QLatin1Char(';')); +} + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/arm/armtargetcompilergroup_v5.h b/src/plugins/generator/keiluv/archs/arm/armtargetcompilergroup_v5.h new file mode 100644 index 00000000..89759ee0 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armtargetcompilergroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVARMTARGETCOMPILERGROUP_V5_H +#define QBS_KEILUVARMTARGETCOMPILERGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +class ArmTargetCompilerGroup final : public gen::xml::PropertyGroup +{ +public: + explicit ArmTargetCompilerGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVARMTARGETCOMPILERGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/arm/armtargetgroup_v5.cpp b/src/plugins/generator/keiluv/archs/arm/armtargetgroup_v5.cpp new file mode 100644 index 00000000..61c7b566 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armtargetgroup_v5.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armtargetassemblergroup_v5.h" +#include "armtargetcompilergroup_v5.h" +#include "armtargetgroup_v5.h" +#include "armtargetlinkergroup_v5.h" +#include "armtargetmiscgroup_v5.h" + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +ArmTargetGroup::ArmTargetGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("TargetArmAds") +{ + appendChild(qbsProject, qbsProduct); + appendChild(qbsProject, qbsProduct); + appendChild(qbsProject, qbsProduct); + appendChild(qbsProject, qbsProduct); +} + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/arm/armtargetgroup_v5.h b/src/plugins/generator/keiluv/archs/arm/armtargetgroup_v5.h new file mode 100644 index 00000000..7472fe67 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armtargetgroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVARMTARGETGROUP_V5_H +#define QBS_KEILUVARMTARGETGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +class ArmTargetGroup final : public gen::xml::PropertyGroup +{ +public: + explicit ArmTargetGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVARMTARGETGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/arm/armtargetlinkergroup_v5.cpp b/src/plugins/generator/keiluv/archs/arm/armtargetlinkergroup_v5.cpp new file mode 100644 index 00000000..5532b58b --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armtargetlinkergroup_v5.cpp @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armtargetlinkergroup_v5.h" + +#include "../../keiluvutils.h" + +#include + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +namespace { + +struct LinkerPageOptions final +{ + explicit LinkerPageOptions(const Project &qbsProject, + const ProductData &qbsProduct) + { + Q_UNUSED(qbsProject) + + const auto &qbsProps = qbsProduct.moduleProperties(); + const auto flags = qbs::KeiluvUtils::cppModuleLinkerFlags(qbsProps); + + // Read-only position independent. + enableRopi = flags.contains(QLatin1String("--ropi")); + // Read-write position independent. + enableRwpi = flags.contains(QLatin1String("--rwpi")); + // Don't search standard libraries. + dontSearchLibs = flags.contains(QLatin1String("--noscanlib")); + // Report 'might fail' conditions as errors. + enableReportMightFail = flags.contains(QLatin1String("--strict")); + + QStringList scatterFiles; + + // Enumerate all product linker config files + // (which are set trough 'linkerscript' tag). + for (const auto &qbsGroup : qbsProduct.groups()) { + if (!qbsGroup.isEnabled()) + continue; + const auto qbsArtifacts = qbsGroup.sourceArtifacts(); + for (const auto &qbsArtifact : qbsArtifacts) { + const auto qbsTags = qbsArtifact.fileTags(); + if (!qbsTags.contains(QLatin1String("linkerscript"))) + continue; + const QString scatterFile = QFileInfo(qbsArtifact.filePath()) + .absoluteFilePath(); + scatterFiles.push_back(scatterFile); + } + } + + // Enumerate all scatter files + // (which are set trough '--scatter' option). + const QStringList scatters = gen::utils::allFlagValues( + flags, QStringLiteral("--scatter")); + for (const auto &scatter : scatters) { + const QString scatterFile = QFileInfo(scatter) + .absoluteFilePath(); + if (!scatterFiles.contains(scatterFile)) + scatterFiles.push_back(scatterFile); + } + + // Transform all paths to relative. + const QString baseDirectory = qbs::gen::utils::buildRootPath(qbsProject); + std::transform(scatterFiles.begin(), scatterFiles.end(), + std::back_inserter(scatterFiles), + [baseDirectory](const QString &scatterFile) { + return gen::utils::relativeFilePath(baseDirectory, scatterFile); + }); + + // Make a first scatter file as a main scatter file. + // Other scatter files will be interpretes as a misc controls. + if (scatterFiles.count() > 0) + mainScatterFile = scatterFiles.takeFirst(); + + for (const auto &scatterFile : qAsConst(scatterFiles)) { + const auto control = QStringLiteral("--scatter %1").arg(scatterFile); + miscControls.push_back(control); + } + + // Interpret other compiler flags as a misc controls (exclude only + // that flags which are was already handled). + for (auto flagIt = flags.cbegin(); flagIt < flags.cend(); ++flagIt) { + if (flagIt->contains(QLatin1String("--ropi")) + || flagIt->contains(QLatin1String("--rwpi")) + || flagIt->contains(QLatin1String("--noscanlib")) + || flagIt->contains(QLatin1String("--strict")) + ) { + continue; + } + if (flagIt->startsWith(QLatin1String("--scatter")) + ) { + ++flagIt; + continue; + } + miscControls.push_back(*flagIt); + } + } + + int enableRopi = 0; + int enableRwpi = 0; + int dontSearchLibs = 0; + int enableReportMightFail = 0; + + QString mainScatterFile; + QStringList miscControls; +}; + +} // namespace + +ArmTargetLinkerGroup::ArmTargetLinkerGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("LDads") +{ + const LinkerPageOptions opts(qbsProject, qbsProduct); + + // Add 'ROPI' item. + appendProperty(QByteArrayLiteral("Ropi"), opts.enableRopi); + // Add 'RWPI' item. + appendProperty(QByteArrayLiteral("Rwpi"), opts.enableRwpi); + // Add 'Don't search standard libraries' item. + appendProperty(QByteArrayLiteral("noStLib"), opts.dontSearchLibs); + // Add 'Report might fail' item. + appendProperty(QByteArrayLiteral("RepFail"), opts.enableReportMightFail); + // Add 'Scatter file' item. + appendProperty(QByteArrayLiteral("ScatterFile"), + QDir::toNativeSeparators(opts.mainScatterFile)); +} + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/arm/armtargetlinkergroup_v5.h b/src/plugins/generator/keiluv/archs/arm/armtargetlinkergroup_v5.h new file mode 100644 index 00000000..db82d4bc --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armtargetlinkergroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVARMTARGETLINKERGROUP_V5_H +#define QBS_KEILUVARMTARGETLINKERGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +class ArmTargetLinkerGroup final : public gen::xml::PropertyGroup +{ +public: + explicit ArmTargetLinkerGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVARMTARGETLINKERGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/arm/armtargetmiscgroup_v5.cpp b/src/plugins/generator/keiluv/archs/arm/armtargetmiscgroup_v5.cpp new file mode 100644 index 00000000..18da47c8 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armtargetmiscgroup_v5.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armtargetmiscgroup_v5.h" + +#include "../../keiluvutils.h" + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +namespace { + +struct MiscPageOptions final +{ + explicit MiscPageOptions(const Project &qbsProject, + const ProductData &qbsProduct) + { + Q_UNUSED(qbsProject) + + const auto &qbsProps = qbsProduct.moduleProperties(); + const auto flags = qbs::KeiluvUtils::cppModuleCompilerFlags(qbsProps); + + generateLinkerMap = gen::utils::cppBooleanModuleProperty( + qbsProps, QStringLiteral("generateLinkerMapFile")); + } + + int generateLinkerMap = 0; +}; + +} // namespace + +ArmTargetMiscGroup::ArmTargetMiscGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("ArmAdsMisc") +{ + const MiscPageOptions opts(qbsProject, qbsProduct); + + // Add 'Generate linker map file' item. + appendProperty(QByteArrayLiteral("AdsLLst"), opts.generateLinkerMap); +} + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/arm/armtargetmiscgroup_v5.h b/src/plugins/generator/keiluv/archs/arm/armtargetmiscgroup_v5.h new file mode 100644 index 00000000..025b2796 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armtargetmiscgroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVARMTARGETMISCGROUP_V5_H +#define QBS_KEILUVARMTARGETMISCGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +class ArmTargetMiscGroup final : public gen::xml::PropertyGroup +{ +public: + explicit ArmTargetMiscGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVARMTARGETMISCGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/arm/armutilitiesgroup_v5.cpp b/src/plugins/generator/keiluv/archs/arm/armutilitiesgroup_v5.cpp new file mode 100644 index 00000000..eab3cc56 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armutilitiesgroup_v5.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "armutilitiesgroup_v5.h" + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +ArmUtilitiesGroup::ArmUtilitiesGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("Utilities") +{ + Q_UNUSED(qbsProject) + Q_UNUSED(qbsProduct) +} + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/arm/armutilitiesgroup_v5.h b/src/plugins/generator/keiluv/archs/arm/armutilitiesgroup_v5.h new file mode 100644 index 00000000..a34a3a39 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/arm/armutilitiesgroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVARMUTILITIESGROUP_V5_H +#define QBS_KEILUVARMUTILITIESGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace arm { +namespace v5 { + +class ArmUtilitiesGroup final : public gen::xml::PropertyGroup +{ +public: + explicit ArmUtilitiesGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace arm +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVARMUTILITIESGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51buildtargetgroup_v5.cpp b/src/plugins/generator/keiluv/archs/mcs51/mcs51buildtargetgroup_v5.cpp new file mode 100644 index 00000000..87b3100d --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51buildtargetgroup_v5.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51buildtargetgroup_v5.h" +#include "mcs51commonpropertygroup_v5.h" +#include "mcs51debugoptiongroup_v5.h" +#include "mcs51dlloptiongroup_v5.h" +#include "mcs51targetcommonoptionsgroup_v5.h" +#include "mcs51targetgroup_v5.h" +#include "mcs51utilitiesgroup_v5.h" +#include "mcs51utils.h" + +#include "../../keiluvconstants.h" +#include "../../keiluvfilesgroupspropertygroup.h" + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +Mcs51BuildTargetGroup::Mcs51BuildTargetGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps) + : gen::xml::PropertyGroup("Target") +{ + // Append target name item (it is a build configuration name). + const QString targetName = gen::utils::buildConfigurationName( + qbsProject); + appendProperty(QByteArrayLiteral("TargetName"), targetName); + // Append toolset number group item. + appendChild(QByteArrayLiteral("ToolsetNumber"), + QByteArrayLiteral("0x0")); + // Append toolset name group item. + appendChild(QByteArrayLiteral("ToolsetName"), + QByteArrayLiteral("MCS-51")); + + // Append target option group item. + const auto targetOptionGroup = appendChild( + QByteArrayLiteral("TargetOption")); + + targetOptionGroup->appendChild( + qbsProject, qbsProduct); + targetOptionGroup->appendChild( + qbsProject, qbsProduct); + targetOptionGroup->appendChild( + qbsProject, qbsProduct); + targetOptionGroup->appendChild( + qbsProject, qbsProduct); + targetOptionGroup->appendChild( + qbsProject, qbsProduct); + targetOptionGroup->appendChild( + qbsProject, qbsProduct); + + // Append files group. + appendChild(qbsProject, qbsProduct, + qbsProductDeps); +} + +bool Mcs51BuildTargetGroupFactory::canCreate( + gen::utils::Architecture arch, + const Version &version) const +{ + return arch == gen::utils::Architecture::Mcs51 + && version.majorVersion() == qbs::KeiluvConstants::v5::kUVisionVersion; +} + +std::unique_ptr +Mcs51BuildTargetGroupFactory::create( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps) const +{ + const auto group = new Mcs51BuildTargetGroup( + qbsProject, qbsProduct, qbsProductDeps); + return std::unique_ptr(group); +} + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51buildtargetgroup_v5.h b/src/plugins/generator/keiluv/archs/mcs51/mcs51buildtargetgroup_v5.h new file mode 100644 index 00000000..ef0c0ccb --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51buildtargetgroup_v5.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVMCS51BUILDTARGETGROUP_V5_H +#define QBS_KEILUVMCS51BUILDTARGETGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +class Mcs51BuildTargetGroup final : public gen::xml::PropertyGroup +{ +private: + explicit Mcs51BuildTargetGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps); + + friend class Mcs51BuildTargetGroupFactory; +}; + +class Mcs51BuildTargetGroupFactory final + : public gen::xml::PropertyGroupFactory +{ +public: + bool canCreate(gen::utils::Architecture arch, + const Version &version) const final; + + std::unique_ptr create( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps) const final; +}; + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVMCS51BUILDTARGETGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51commonpropertygroup_v5.cpp b/src/plugins/generator/keiluv/archs/mcs51/mcs51commonpropertygroup_v5.cpp new file mode 100644 index 00000000..3d4d3304 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51commonpropertygroup_v5.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51commonpropertygroup_v5.h" + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +Mcs51CommonPropertyGroup::Mcs51CommonPropertyGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("CommonProperty") +{ + Q_UNUSED(qbsProject) + Q_UNUSED(qbsProduct) +} + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51commonpropertygroup_v5.h b/src/plugins/generator/keiluv/archs/mcs51/mcs51commonpropertygroup_v5.h new file mode 100644 index 00000000..66dfc7a3 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51commonpropertygroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVMCS51COMMONPROPERTYGROUP_V5_H +#define QBS_KEILUVMCS51COMMONPROPERTYGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +class Mcs51CommonPropertyGroup final : public gen::xml::PropertyGroup +{ +public: + explicit Mcs51CommonPropertyGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs + +#endif // QBS_MCS51COMMONPROPERTYGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51debugoptiongroup_v5.cpp b/src/plugins/generator/keiluv/archs/mcs51/mcs51debugoptiongroup_v5.cpp new file mode 100644 index 00000000..2175cc48 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51debugoptiongroup_v5.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51debugoptiongroup_v5.h" + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +Mcs51DebugOptionGroup::Mcs51DebugOptionGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("DebugOption") +{ + Q_UNUSED(qbsProject) + Q_UNUSED(qbsProduct) +} + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51debugoptiongroup_v5.h b/src/plugins/generator/keiluv/archs/mcs51/mcs51debugoptiongroup_v5.h new file mode 100644 index 00000000..473cf605 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51debugoptiongroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVMCS51DEBUGOPTIONGROUP_V5_H +#define QBS_KEILUVMCS51DEBUGOPTIONGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +class Mcs51DebugOptionGroup final : public gen::xml::PropertyGroup +{ +public: + explicit Mcs51DebugOptionGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVMCS51DEBUGOPTIONGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51dlloptiongroup_v5.cpp b/src/plugins/generator/keiluv/archs/mcs51/mcs51dlloptiongroup_v5.cpp new file mode 100644 index 00000000..59aec721 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51dlloptiongroup_v5.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51dlloptiongroup_v5.h" + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +Mcs51DllOptionGroup::Mcs51DllOptionGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("DllOption") +{ + Q_UNUSED(qbsProject) + Q_UNUSED(qbsProduct) +} + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51dlloptiongroup_v5.h b/src/plugins/generator/keiluv/archs/mcs51/mcs51dlloptiongroup_v5.h new file mode 100644 index 00000000..e16833ac --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51dlloptiongroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVMCS51DLLOPTIONGROUP_V5_H +#define QBS_KEILUVMCS51DLLOPTIONGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +class Mcs51DllOptionGroup final : public gen::xml::PropertyGroup +{ +public: + explicit Mcs51DllOptionGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVMCS51DLLOPTIONGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51targetassemblergroup_v5.cpp b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetassemblergroup_v5.cpp new file mode 100644 index 00000000..8d725771 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetassemblergroup_v5.cpp @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51targetassemblergroup_v5.h" +#include "mcs51utils.h" + +#include "../../keiluvutils.h" + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +namespace { + +struct AssemblerPageOptions final +{ + explicit AssemblerPageOptions(const Project &qbsProject, + const ProductData &qbsProduct) + { + Q_UNUSED(qbsProject) + + const auto &qbsProps = qbsProduct.moduleProperties(); + const auto flags = qbs::KeiluvUtils::cppModuleAssemblerFlags(qbsProps); + + // Don't use standard macro. + if (flags.contains(QLatin1String("NOMACRO"), Qt::CaseInsensitive)) + useStandardMacroProcessor = false; + + // Use MPL. + if (flags.contains(QLatin1String("MPL"), Qt::CaseInsensitive)) + useMacroProcessingLanguage = true; + + // Define 8051 SFR names. + if (flags.contains(QLatin1String("NOMOD51"), Qt::CaseInsensitive)) + suppressSfrNames = true; + + // Define symbols. + defineSymbols = qbs::KeiluvUtils::defines(qbsProps); + // Include paths. + includePaths = qbs::KeiluvUtils::includes(qbsProps); + + // Interpret other assembler flags as a misc controls (exclude only + // that flags which are was already handled). + for (const auto &flag : flags) { + if (flag.compare(QLatin1String("NOMACRO"), + Qt::CaseInsensitive) == 0 + || flag.compare(QLatin1String("MACRO"), + Qt::CaseInsensitive) == 0 + || flag.compare(QLatin1String("NOMPL"), + Qt::CaseInsensitive) == 0 + || flag.compare(QLatin1String("MPL"), + Qt::CaseInsensitive) == 0 + || flag.compare(QLatin1String("NOMOD51"), + Qt::CaseInsensitive) == 0 + || flag.compare(QLatin1String("MOD51"), + Qt::CaseInsensitive) == 0 + ) { + continue; + } + miscControls.push_back(flag); + } + } + + int useStandardMacroProcessor = true; + int useMacroProcessingLanguage = false; + int suppressSfrNames = false; + QStringList defineSymbols; + QStringList includePaths; + QStringList miscControls; +}; + +} // namespace + +Mcs51TargetAssemblerGroup::Mcs51TargetAssemblerGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("Ax51") +{ + const AssemblerPageOptions opts(qbsProject, qbsProduct); + + // Add 'Macro processor (Standard)' + appendProperty(QByteArrayLiteral("UseStandard"), + opts.useStandardMacroProcessor); + // Add 'Macro processor (MPL)' + appendProperty(QByteArrayLiteral("UseMpl"), + opts.useMacroProcessingLanguage); + // Add 'Define 8051 SFR names' + appendProperty(QByteArrayLiteral("UseMod51"), + opts.suppressSfrNames); + + // Add other various controls. + // Note: A sub-items order makes sense! + const auto variousControlsGroup = appendChild( + QByteArrayLiteral("VariousControls")); + // Add 'Misc Controls' item. + variousControlsGroup->appendMultiLineProperty( + QByteArrayLiteral("MiscControls"), + opts.miscControls, QLatin1Char(' ')); + // Add 'Define' item. + variousControlsGroup->appendMultiLineProperty( + QByteArrayLiteral("Define"), + opts.defineSymbols); + // Add an empty 'Undefine' item. + variousControlsGroup->appendProperty( + QByteArrayLiteral("Undefine"), {}); + // Add 'Include Paths' item. + variousControlsGroup->appendMultiLineProperty( + QByteArrayLiteral("IncludePath"), + opts.includePaths, QLatin1Char(';')); +} + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51targetassemblergroup_v5.h b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetassemblergroup_v5.h new file mode 100644 index 00000000..f9a82304 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetassemblergroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVMCS51TARGETASSEMBLERGROUP_V3 +#define QBS_KEILUVMCS51TARGETASSEMBLERGROUP_V3 + +#include + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +class Mcs51TargetAssemblerGroup final : public gen::xml::PropertyGroup +{ +public: + explicit Mcs51TargetAssemblerGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVMCS51TARGETASSEMBLERGROUP_V3 diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51targetcommonoptionsgroup_v5.cpp b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetcommonoptionsgroup_v5.cpp new file mode 100644 index 00000000..de1be1ef --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetcommonoptionsgroup_v5.cpp @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51targetcommonoptionsgroup_v5.h" + +#include "../../keiluvutils.h" + +#include + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +namespace { + +struct CommonPageOptions final +{ + explicit CommonPageOptions(const Project &qbsProject, + const ProductData &qbsProduct) + { + Q_UNUSED(qbsProject) + + const auto &qbsProps = qbsProduct.moduleProperties(); + const auto flags = KeiluvUtils::cppModuleCompilerFlags(qbsProps); + + // Browse information. + if (flags.contains(QLatin1String("BROWSE"), Qt::CaseInsensitive)) + browseInfo = true; + + // Debug information. + debugInfo = gen::utils::debugInformation(qbsProduct); + + // Output parameters. + executableName = gen::utils::targetBinary(qbsProduct); + // Fix output binary name if it is a library. Because + // the IDE appends an additional suffix (.LIB) to end + // of an output library name. + if (executableName.endsWith(QLatin1String(".lib"))) + executableName = qbsProduct.targetName(); + + const QString baseDirectory = gen::utils::buildRootPath(qbsProject); + objectDirectory = QDir::toNativeSeparators( + gen::utils::objectsOutputDirectory( + baseDirectory, qbsProduct)); + listingDirectory = QDir::toNativeSeparators( + gen::utils::listingOutputDirectory( + baseDirectory, qbsProduct)); + + // Target type. + targetType = KeiluvUtils::outputBinaryType(qbsProduct); + } + + int browseInfo = false; + int debugInfo = false; + QString executableName; + QString objectDirectory; + QString listingDirectory; + KeiluvUtils::OutputBinaryType targetType = + KeiluvUtils::ApplicationOutputType; +}; + +} // namespace + +Mcs51TargetCommonOptionsGroup::Mcs51TargetCommonOptionsGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("TargetCommonOption") +{ + const CommonPageOptions opts(qbsProject, qbsProduct); + + // Add 'Generic 8051 device' items, + // because we can't detect a target device + // form the present command lines. + appendProperty(QByteArrayLiteral("Device"), + QByteArrayLiteral("8051 (all Variants)")); + appendProperty(QByteArrayLiteral("Vendor"), + QByteArrayLiteral("Generic")); + appendProperty(QByteArrayLiteral("DeviceId"), + QByteArrayLiteral("2994")); + + // Add 'Debug Information' item. + appendProperty(QByteArrayLiteral("DebugInformation"), + opts.debugInfo); + // Add 'Browse Information' item. + appendProperty(QByteArrayLiteral("BrowseInformation"), + opts.browseInfo); + + // Add 'Name of Executable'. + appendProperty(QByteArrayLiteral("OutputName"), + opts.executableName); + // Add 'Output objects directory'. + appendProperty(QByteArrayLiteral("OutputDirectory"), + opts.objectDirectory); + // Add 'Output listing directory'. + appendProperty(QByteArrayLiteral("ListingPath"), + opts.listingDirectory); + + // Add 'Create Executable/Library' item. + const int isExecutable = (opts.targetType + == KeiluvUtils::ApplicationOutputType); + const int isLibrary = (opts.targetType + == KeiluvUtils::LibraryOutputType); + appendProperty(QByteArrayLiteral("CreateExecutable"), + isExecutable); + appendProperty(QByteArrayLiteral("CreateLib"), + isLibrary); +} + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51targetcommonoptionsgroup_v5.h b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetcommonoptionsgroup_v5.h new file mode 100644 index 00000000..eccd9d42 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetcommonoptionsgroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVMCS51TARGETCOMMONOPTIONSGROUP_V5_H +#define QBS_KEILUVMCS51TARGETCOMMONOPTIONSGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +class Mcs51TargetCommonOptionsGroup final : public gen::xml::PropertyGroup +{ +public: + explicit Mcs51TargetCommonOptionsGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVMCS51TARGETCOMMONOPTIONSGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51targetcompilergroup_v5.cpp b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetcompilergroup_v5.cpp new file mode 100644 index 00000000..e33c0c21 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetcompilergroup_v5.cpp @@ -0,0 +1,281 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51targetcompilergroup_v5.h" +#include "mcs51utils.h" + +#include "../../keiluvutils.h" + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +namespace { + +struct CompilerPageOptions final +{ + enum WarningLevel { + WarningLevelNone = 0, + WarningLevelOne, + WarningLevelTwo + }; + + enum OptimizationLevel { + ConstantFoldingOptimizationLevel = 0, + DeadCodeEliminationOptimizationLevel, + DataOverlayingOptimizationLevel, + PeepholeOptimizationLevel, + RegisterVariablesOptimizationLevel, + CommonSubexpressionEliminationOptimizationLevel, + LoopRotationOptimizationLevel, + ExtendedIndexAccessOptimizationLevel, + ReuseCommonEntryCodeOptimizationLevel, + CommonBlockSubroutinesOptimizationLevel, + RearrangeCodeOptimizationLevel, + ReuseCommonExitCodeOptimizationLevel + }; + + enum OptimizationEmphasis { + FavorSizeOptimizationEmphasis = 0, + FavorSpeedOptimizationEmphasis + }; + + enum FloatFuzzyBits { + NoFloatFuzzyBits = 0, + OneFloatFuzzyBit, + TwoFloatFuzzyBits, + ThreeFloatFuzzyBits, + FourFloatFuzzyBits, + FiveFloatFuzzyBits, + SixFloatFuzzyBits, + SevenFloatFuzzyBits + }; + + explicit CompilerPageOptions(const Project &qbsProject, + const ProductData &qbsProduct) + { + Q_UNUSED(qbsProject) + + const auto &qbsProps = qbsProduct.moduleProperties(); + const auto flags = qbs::KeiluvUtils::cppModuleCompilerFlags(qbsProps); + + // Warnings. + const QString level = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("warningLevel")); + if (level == QLatin1String("none")) { + warningLevel = WarningLevelNone; + } else if (level == QLatin1String("all")) { + warningLevel = WarningLevelTwo; + } else { + // In this case take it directly from the compiler command line, + // e.g. parse the line in a form: 'WARNINGLEVEL (2)' + const auto warnValue = KeiluvUtils::flagValue( + flags, QStringLiteral("WARNINGLEVEL")); + bool ok = false; + const auto level = warnValue.toInt(&ok); + if (ok && gen::utils::inBounds( + level, int(WarningLevelNone),int(WarningLevelTwo))) { + warningLevel = static_cast(level); + } + } + + // Optimizations. + const QString optimization = gen::utils::cppStringModuleProperty( + qbsProps, QStringLiteral("optimization")); + if (optimization == QLatin1String("fast")) { + optimizationEmphasis = FavorSpeedOptimizationEmphasis; + } else if (level == QLatin1String("small")) { + optimizationEmphasis = FavorSizeOptimizationEmphasis; + } else if (level == QLatin1String("small")) { + // Don't supported by C51 compiler. + } else { + // In this case take it directly from the compiler command line, + // e.g. parse the line in a form: 'OPTIMIZE (8, SPEED)' + const auto optValue = KeiluvUtils::flagValue( + flags, QStringLiteral("OPTIMIZE")); + const auto parts = KeiluvUtils::flagValueParts(optValue); + for (const auto &part : parts) { + bool ok = false; + const auto level = part.toInt(&ok); + if (ok && (level >= ConstantFoldingOptimizationLevel) + && (level <= ReuseCommonExitCodeOptimizationLevel)) { + optimizationLevel = static_cast(level); + } else if (part.compare(QLatin1String("SIZE"), + Qt::CaseInsensitive) == 0) { + optimizationEmphasis = FavorSizeOptimizationEmphasis; + } else if (part.compare(QLatin1String("SPEED"), + Qt::CaseInsensitive) == 0) { + optimizationEmphasis = FavorSpeedOptimizationEmphasis; + } + } + } + + // Don't use absolute register accesses. + if (flags.contains(QLatin1String("NOAREGS"), Qt::CaseInsensitive)) + dontuseAbsoluteRegsAccess = true; + + // Enable ANSI integer promotion rules. + if (flags.contains(QLatin1String("NOINTPROMOTE"), Qt::CaseInsensitive)) + enableIntegerPromotionRules = false; + + // Keep variables in order. + if (flags.contains(QLatin1String("ORDER"), Qt::CaseInsensitive)) + keepVariablesInOrder = true; + + // Don't use interrupt vector. + if (flags.contains(QLatin1String("NOINTVECTOR"), Qt::CaseInsensitive)) + useInterruptVector = false; + + // Interrupt vector address. + interruptVectorAddress = KeiluvUtils::flagValue( + flags, QStringLiteral("INTVECTOR")); + + // Float fuzzy bits count. + const auto bitsValue = KeiluvUtils::flagValue( + flags, QStringLiteral("FLOATFUZZY")); + bool ok = false; + const auto bits = bitsValue.toInt(&ok); + if (ok && gen::utils::inBounds( + bits, int(NoFloatFuzzyBits), int(SevenFloatFuzzyBits))) { + floatFuzzyBits = static_cast(bits); + } + + // Define symbols. + defineSymbols = qbs::KeiluvUtils::defines(qbsProps); + // Include paths. + includePaths = qbs::KeiluvUtils::includes(qbsProps); + + // Interpret other compiler flags as a misc controls (exclude only + // that flags which are was already handled). + for (const auto &flag : flags) { + if (flag.startsWith(QLatin1String("WARNINGLEVEL"), + Qt::CaseInsensitive) + || flag.startsWith(QLatin1String("OPTIMIZE"), + Qt::CaseInsensitive) + || flag.startsWith(QLatin1String("FLOATFUZZY"), + Qt::CaseInsensitive) + || flag.compare(QLatin1String("NOAREGS"), + Qt::CaseInsensitive) == 0 + || flag.compare(QLatin1String("AREGS"), + Qt::CaseInsensitive) == 0 + || flag.compare(QLatin1String("NOINTPROMOTE"), + Qt::CaseInsensitive) == 0 + || flag.compare(QLatin1String("INTPROMOTE"), + Qt::CaseInsensitive) == 0 + || flag.compare(QLatin1String("NOINTVECTOR"), + Qt::CaseInsensitive) == 0 + || flag.compare(QLatin1String("INTVECTOR"), + Qt::CaseInsensitive) == 0 + || flag.compare(QLatin1String("ORDER"), + Qt::CaseInsensitive) == 0 + || flag.compare(QLatin1String("BROSWE"), + Qt::CaseInsensitive) == 0 + ) { + continue; + } + miscControls.push_back(flag); + } + } + + WarningLevel warningLevel = WarningLevelTwo; + OptimizationLevel optimizationLevel = ReuseCommonEntryCodeOptimizationLevel; + OptimizationEmphasis optimizationEmphasis = FavorSpeedOptimizationEmphasis; + FloatFuzzyBits floatFuzzyBits = ThreeFloatFuzzyBits; + int dontuseAbsoluteRegsAccess = false; + int enableIntegerPromotionRules = true; + int keepVariablesInOrder = false; + int useInterruptVector = true; + QString interruptVectorAddress; + QStringList defineSymbols; + QStringList includePaths; + QStringList miscControls; +}; + +} // namespace + +Mcs51TargetCompilerGroup::Mcs51TargetCompilerGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("C51") +{ + const CompilerPageOptions opts(qbsProject, qbsProduct); + + // Add 'Code Optimization' options. + appendProperty(QByteArrayLiteral("Optimize"), + opts.optimizationLevel); + appendProperty(QByteArrayLiteral("SizeSpeed"), + opts.optimizationEmphasis); + // Add 'Warnings' options. + appendProperty(QByteArrayLiteral("WarningLevel"), + opts.warningLevel); + // Add 'Don't use absolute register access' item. + appendProperty(QByteArrayLiteral("uAregs"), + opts.dontuseAbsoluteRegsAccess); + // Add 'Enable integer promotion rules' item. + appendProperty(QByteArrayLiteral("IntegerPromotion"), + opts.enableIntegerPromotionRules); + // Add 'Keep variables in order' item. + appendProperty(QByteArrayLiteral("VariablesInOrder"), + opts.keepVariablesInOrder); + // Add 'Use interrupt vector' item. + appendProperty(QByteArrayLiteral("UseInterruptVector"), + opts.useInterruptVector); + appendProperty(QByteArrayLiteral("InterruptVectorAddress"), + opts.interruptVectorAddress); + // Add 'Float fuzzy bits' item. + appendProperty(QByteArrayLiteral("Fuzzy"), + opts.floatFuzzyBits); + + // Add other various controls. + // Note: A sub-items order makes sense! + const auto variousControlsGroup = appendChild( + QByteArrayLiteral("VariousControls")); + // Add 'Misc Controls' item. + variousControlsGroup->appendMultiLineProperty( + QByteArrayLiteral("MiscControls"), + opts.miscControls, QLatin1Char(' ')); + // Add 'Define' item. + variousControlsGroup->appendMultiLineProperty( + QByteArrayLiteral("Define"), + opts.defineSymbols); + // Add an empty 'Undefine' item. + variousControlsGroup->appendProperty( + QByteArrayLiteral("Undefine"), {}); + // Add 'Include Paths' item. + variousControlsGroup->appendMultiLineProperty( + QByteArrayLiteral("IncludePath"), + opts.includePaths, QLatin1Char(';')); +} + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51targetcompilergroup_v5.h b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetcompilergroup_v5.h new file mode 100644 index 00000000..0f1e52ce --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetcompilergroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVMCS51TARGETCOMPILERGROUP_V5_H +#define QBS_KEILUVMCS51TARGETCOMPILERGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +class Mcs51TargetCompilerGroup final : public gen::xml::PropertyGroup +{ +public: + explicit Mcs51TargetCompilerGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVMCS51TARGETCOMPILERGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51targetgroup_v5.cpp b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetgroup_v5.cpp new file mode 100644 index 00000000..2a05649d --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetgroup_v5.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51targetassemblergroup_v5.h" +#include "mcs51targetcompilergroup_v5.h" +#include "mcs51targetgroup_v5.h" +#include "mcs51targetlinkergroup_v5.h" +#include "mcs51targetmiscgroup_v5.h" + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +Mcs51TargetGroup::Mcs51TargetGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("Target51") +{ + appendChild(qbsProject, qbsProduct); + appendChild(qbsProject, qbsProduct); + appendChild(qbsProject, qbsProduct); + appendChild(qbsProject, qbsProduct); +} + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51targetgroup_v5.h b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetgroup_v5.h new file mode 100644 index 00000000..d256dfcc --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetgroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVMCS51TARGETGROUP_V5_H +#define QBS_KEILUVMCS51TARGETGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +class Mcs51TargetGroup final : public gen::xml::PropertyGroup +{ +public: + explicit Mcs51TargetGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVMCS51TARGETGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51targetlinkergroup_v5.cpp b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetlinkergroup_v5.cpp new file mode 100644 index 00000000..4d52b627 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetlinkergroup_v5.cpp @@ -0,0 +1,240 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51targetlinkergroup_v5.h" +#include "mcs51utils.h" + +#include "../../keiluvutils.h" + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +namespace { + +struct LinkerPageOptions final +{ + explicit LinkerPageOptions(const Project &qbsProject, + const ProductData &qbsProduct) + { + Q_UNUSED(qbsProject) + + const auto &qbsProps = qbsProduct.moduleProperties(); + const auto flags = qbs::KeiluvUtils::cppModuleLinkerFlags(qbsProps); + + // Handle all 'BIT' memory flags. + parseMemory(flags, QStringLiteral("BIT"), + bitAddresses, bitSegments); + // Handle all 'CODE' memory flags. + parseMemory(flags, QStringLiteral("CODE"), + codeAddresses, codeSegments); + // Handle all 'DATA' memory flags. + parseMemory(flags, QStringLiteral("DATA"), + dataAddresses, dataSegments); + // Handle all 'IDATA' memory flags. + parseMemory(flags, QStringLiteral("IDATA"), + idataAddresses, idataSegments); + // Handle all 'PDATA' memory flags. + parseMemory(flags, QStringLiteral("PDATA"), + pdataAddresses, pdataSegments); + // Handle all 'XDATA' memory flags. + parseMemory(flags, QStringLiteral("XDATA"), + xdataAddresses, xdataSegments); + + // Enumerate all flags in a form like: + // 'PRECEDE(foo, bar) PRECEDE(baz)'. + const auto precedeValues = KeiluvUtils::flagValues( + flags, QStringLiteral("PRECEDE")); + for (const auto &precedeValue : precedeValues) { + const auto parts = KeiluvUtils::flagValueParts(precedeValue); + precedeSegments.reserve(precedeSegments.size() + parts.count()); + std::copy(parts.cbegin(), parts.cend(), + std::back_inserter(precedeSegments)); + } + + // Enumerate all flags in a form like: + // 'STACK(foo, bar) STACK(baz)'. + const auto stackValues = KeiluvUtils::flagValues( + flags, QStringLiteral("STACK")); + for (const auto &stackValue : stackValues) { + const auto parts = KeiluvUtils::flagValueParts(stackValue); + stackSegments.reserve(stackSegments.size() + parts.count()); + std::copy(parts.cbegin(), parts.cend(), + std::back_inserter(stackSegments)); + } + + // Interpret other linker flags as a misc controls (exclude only + // that flags which are was already handled). + for (const auto &flag : flags) { + if (flag.startsWith(QLatin1String("BIT"), + Qt::CaseInsensitive) + || flag.startsWith(QLatin1String("CODE"), + Qt::CaseInsensitive) + || flag.startsWith(QLatin1String("DATA"), + Qt::CaseInsensitive) + || flag.startsWith(QLatin1String("IDATA"), + Qt::CaseInsensitive) + || flag.startsWith(QLatin1String("PDATA"), + Qt::CaseInsensitive) + || flag.startsWith(QLatin1String("XDATA"), + Qt::CaseInsensitive) + || flag.startsWith(QLatin1String("PRECEDE"), + Qt::CaseInsensitive) + || flag.startsWith(QLatin1String("STACK"), + Qt::CaseInsensitive) + ) { + continue; + } + miscControls.push_back(flag); + } + } + + static void parseMemory(const QStringList &flags, + const QString &flagKey, + QStringList &destAddresses, + QStringList &destSegments) + { + // Handle all flags in a form like: + // 'FLAGKEY(0x00-0x20, 30, foo, bar(0x40)) FLAGKEY(baz)'. + const auto values = KeiluvUtils::flagValues(flags, flagKey); + for (const auto &value : values) { + const auto parts = KeiluvUtils::flagValueParts(value); + for (const auto &part : parts) { + if (part.contains(QLatin1Char('-'))) { + // Seems, it is an address range. + destAddresses.push_back(part); + } else { + // Check on address (specified in decimal + // or hexadecimal form). + bool ok = false; + part.toInt(&ok, 16); + if (!ok) + part.toInt(&ok, 10); + if (ok) { + // Seems, it is just a single address. + destAddresses.push_back(part); + } else { + // Seems it is a segment name. + destSegments.push_back(part); + } + } + } + } + } + + QStringList bitAddresses; + QStringList bitSegments; + QStringList codeAddresses; + QStringList codeSegments; + QStringList dataAddresses; + QStringList dataSegments; + QStringList idataAddresses; + QStringList idataSegments; + QStringList pdataAddresses; + QStringList pdataSegments; + QStringList xdataAddresses; + QStringList xdataSegments; + + QStringList precedeSegments; + QStringList stackSegments; + + QStringList miscControls; +}; + +} // namespace + +Mcs51TargetLinkerGroup::Mcs51TargetLinkerGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("Lx51") +{ + const LinkerPageOptions opts(qbsProject, qbsProduct); + + // Add 'Misc Controls' item. + appendMultiLineProperty(QByteArrayLiteral("MiscControls"), + opts.miscControls, QLatin1Char(' ')); + + // Add 'Use Memory Layout from Target Dialog' item. + // Note: we always disable it, as we expect that + // the layout will be specified from the linker's + // command line. + appendProperty(QByteArrayLiteral("UseMemoryFromTarget"), + 0); + + // Add 'Bit Range' item. + appendMultiLineProperty(QByteArrayLiteral("BitBaseAddress"), + opts.bitAddresses); + // Add 'Code Range' item. + appendMultiLineProperty(QByteArrayLiteral("CodeBaseAddress"), + opts.codeAddresses); + // Add 'Data Range' item. + appendMultiLineProperty(QByteArrayLiteral("DataBaseAddress"), + opts.dataAddresses); + // Add 'IData Range' item. + appendMultiLineProperty(QByteArrayLiteral("IDataBaseAddress"), + opts.idataAddresses); + // Add 'PData Range' item. + appendMultiLineProperty(QByteArrayLiteral("PDataBaseAddress"), + opts.pdataAddresses); + // Add 'XData Range' item. + appendMultiLineProperty(QByteArrayLiteral("XDataBaseAddress"), + opts.xdataAddresses); + + // Add 'Bit Segment' item. + appendMultiLineProperty(QByteArrayLiteral("BitSegmentName"), + opts.bitSegments); + // Add 'Code Segment' item. + appendMultiLineProperty(QByteArrayLiteral("CodeSegmentName"), + opts.codeSegments); + // Add 'Data Segment' item. + appendMultiLineProperty(QByteArrayLiteral("DataSegmentName"), + opts.dataSegments); + // Add 'IData Segment' item. + appendMultiLineProperty(QByteArrayLiteral("IDataSegmentName"), + opts.idataSegments); + + // Note: PData has not segments! + + // Add 'XData Segment' item. + appendMultiLineProperty(QByteArrayLiteral("XDataSegmentName"), + opts.xdataSegments); + // Add 'Precede' item. + appendMultiLineProperty(QByteArrayLiteral("Precede"), + opts.precedeSegments); + // Add 'Stack' item. + appendMultiLineProperty(QByteArrayLiteral("Stack"), + opts.stackSegments); +} + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51targetlinkergroup_v5.h b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetlinkergroup_v5.h new file mode 100644 index 00000000..d701c5e0 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetlinkergroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVMCS51TARGETLINKERGROUP_V5_H +#define QBS_KEILUVMCS51TARGETLINKERGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +class Mcs51TargetLinkerGroup final : public gen::xml::PropertyGroup +{ +public: + explicit Mcs51TargetLinkerGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVMCS51TARGETLINKERGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51targetmiscgroup_v5.cpp b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetmiscgroup_v5.cpp new file mode 100644 index 00000000..5d95d443 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetmiscgroup_v5.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51targetmiscgroup_v5.h" +#include "mcs51utils.h" + +#include "../../keiluvutils.h" + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +namespace { + +struct MiscPageOptions final +{ + enum MemoryModel { + SmallMemoryModel = 0, + CompactMemoryModel, + LargeMemoryModel + }; + + enum CodeRomSize { + SmallCodeRomSize = 0, + CompactCodeRomSize, + LargeCodeRomSize + }; + + explicit MiscPageOptions(const Project &qbsProject, + const ProductData &qbsProduct) + { + Q_UNUSED(qbsProject) + + const auto &qbsProps = qbsProduct.moduleProperties(); + const auto flags = qbs::KeiluvUtils::cppModuleCompilerFlags(qbsProps); + + // Memory model. + if (flags.contains(QLatin1String("COMPACT"), Qt::CaseInsensitive)) + memoryModel = CompactMemoryModel; + else if (flags.contains(QLatin1String("LARGE"), Qt::CaseInsensitive)) + memoryModel = LargeMemoryModel; + + // Code ROM size. + const auto sizeValue = KeiluvUtils::flagValue( + flags, QStringLiteral("ROM")); + if (sizeValue == QLatin1String("SMALL")) + coderomSize = SmallCodeRomSize; + else if (sizeValue == QLatin1String("COMPACT")) + coderomSize = CompactCodeRomSize; + } + + MemoryModel memoryModel = SmallMemoryModel; + CodeRomSize coderomSize = LargeCodeRomSize; +}; + +} // namespace + +Mcs51TargetMiscGroup::Mcs51TargetMiscGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("Target51Misc") +{ + const MiscPageOptions opts(qbsProject, qbsProduct); + + // Add 'Memory Model' options item. + appendProperty(QByteArrayLiteral("MemoryModel"), + opts.memoryModel); + // Add 'ROM Size' options item. + appendProperty(QByteArrayLiteral("RomSize"), + opts.coderomSize); +} + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51targetmiscgroup_v5.h b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetmiscgroup_v5.h new file mode 100644 index 00000000..f1680d92 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51targetmiscgroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVMCS51TARGETMISCGROUP_V5_H +#define QBS_KEILUVMCS51TARGETMISCGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +class Mcs51TargetMiscGroup final : public gen::xml::PropertyGroup +{ +public: + explicit Mcs51TargetMiscGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVMCS51TARGETMISCGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51utilitiesgroup_v5.cpp b/src/plugins/generator/keiluv/archs/mcs51/mcs51utilitiesgroup_v5.cpp new file mode 100644 index 00000000..8e8307f7 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51utilitiesgroup_v5.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51utilitiesgroup_v5.h" + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +Mcs51UtilitiesGroup::Mcs51UtilitiesGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct) + : gen::xml::PropertyGroup("Utilities") +{ + Q_UNUSED(qbsProject) + Q_UNUSED(qbsProduct) +} + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51utilitiesgroup_v5.h b/src/plugins/generator/keiluv/archs/mcs51/mcs51utilitiesgroup_v5.h new file mode 100644 index 00000000..f95ec67c --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51utilitiesgroup_v5.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVMCS51UTILITIESGROUP_V5_H +#define QBS_KEILUVMCS51UTILITIESGROUP_V5_H + +#include + +namespace qbs { +namespace keiluv { +namespace mcs51 { +namespace v5 { + +class Mcs51UtilitiesGroup final : public gen::xml::PropertyGroup +{ +public: + explicit Mcs51UtilitiesGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct); +}; + +} // namespace v5 +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVMCS51UTILITIESGROUP_V5_H diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51utils.cpp b/src/plugins/generator/keiluv/archs/mcs51/mcs51utils.cpp new file mode 100644 index 00000000..698f0df7 --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51utils.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "mcs51utils.h" + +namespace qbs { +namespace keiluv { +namespace mcs51 { + +namespace KeiluvUtils { + +static QString extractValue(const QString &flag) +{ + const auto openBracketIndex = flag.indexOf(QLatin1Char('(')); + const auto closeBracketIndex = flag.indexOf(QLatin1Char(')')); + const auto n = closeBracketIndex - openBracketIndex - 1; + return flag.mid(openBracketIndex + 1, n); +} + +QStringList flagValues(const QStringList &flags, const QString &flagKey) +{ + QStringList values; + for (const auto &flag : flags) { + if (!flag.startsWith(flagKey, Qt::CaseInsensitive)) + continue; + const auto value = extractValue(flag); + values.push_back(value); + } + return values; +} + +QString flagValue(const QStringList &flags, const QString &flagKey) +{ + const auto flagEnd = flags.cend(); + const auto flagIt = std::find_if(flags.cbegin(), flagEnd, + [flagKey](const auto &flag) { + return flag.startsWith(flagKey, Qt::CaseInsensitive); + }); + if (flagIt == flagEnd) + return {}; // Flag key not found. + return extractValue(*flagIt); +} + +QStringList flagValueParts(const QString &flagValue, const QLatin1Char &sep) +{ + auto parts = flagValue.split(sep); + std::transform(parts.begin(), parts.end(), parts.begin(), + [](const auto &part) { return part.trimmed(); }); + return parts; +} + +} // namespace KeiluvUtils + +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs diff --git a/src/plugins/generator/keiluv/archs/mcs51/mcs51utils.h b/src/plugins/generator/keiluv/archs/mcs51/mcs51utils.h new file mode 100644 index 00000000..a1a9706e --- /dev/null +++ b/src/plugins/generator/keiluv/archs/mcs51/mcs51utils.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVMCS51UTILS_H +#define QBS_KEILUVMCS51UTILS_H + +#include + +namespace qbs { + +namespace keiluv { +namespace mcs51 { + +namespace KeiluvUtils { + +QStringList flagValues(const QStringList &flags, const QString &flagKey); + +QString flagValue(const QStringList &flags, const QString &flagKey); + +QStringList flagValueParts(const QString &flagValue, + const QLatin1Char &sep = QLatin1Char(',')); + +} // namespace KeiluvUtils + +} // namespace mcs51 +} // namespace keiluv +} // namespace qbs + +#endif // QBS_KEILUVMCS51UTILS_H diff --git a/src/plugins/generator/keiluv/keiluv.pri b/src/plugins/generator/keiluv/keiluv.pri new file mode 100644 index 00000000..6995fb05 --- /dev/null +++ b/src/plugins/generator/keiluv/keiluv.pri @@ -0,0 +1 @@ +qbsPluginTarget = keiluvgenerator diff --git a/src/plugins/generator/keiluv/keiluv.pro b/src/plugins/generator/keiluv/keiluv.pro new file mode 100644 index 00000000..052277ef --- /dev/null +++ b/src/plugins/generator/keiluv/keiluv.pro @@ -0,0 +1,90 @@ +include(keiluv.pri) +include(../../plugins.pri) +include(../../../shared/json/json.pri) + +QT = core + +# Plugin file. + +SOURCES += \ + $$PWD/keiluvgeneratorplugin.cpp \ + +# Common files. + +HEADERS += \ + $$PWD/keiluvconstants.h \ + $$PWD/keiluvfilesgroupspropertygroup.h \ + $$PWD/keiluvgenerator.h \ + $$PWD/keiluvproject.h \ + $$PWD/keiluvprojectwriter.h \ + $$PWD/keiluvutils.h \ + $$PWD/keiluvversioninfo.h \ + $$PWD/keiluvworkspace.h \ + $$PWD/keiluvworkspacewriter.h + +SOURCES += \ + $$PWD/keiluvfilesgroupspropertygroup.cpp \ + $$PWD/keiluvgenerator.cpp \ + $$PWD/keiluvproject.cpp \ + $$PWD/keiluvprojectwriter.cpp \ + $$PWD/keiluvutils.cpp \ + $$PWD/keiluvworkspace.cpp \ + $$PWD/keiluvworkspacewriter.cpp + +# For MCS51 architecture. + +HEADERS += \ + $$PWD/archs/mcs51/mcs51buildtargetgroup_v5.h \ + $$PWD/archs/mcs51/mcs51commonpropertygroup_v5.h \ + $$PWD/archs/mcs51/mcs51debugoptiongroup_v5.h \ + $$PWD/archs/mcs51/mcs51dlloptiongroup_v5.h \ + $$PWD/archs/mcs51/mcs51targetassemblergroup_v5.h \ + $$PWD/archs/mcs51/mcs51targetcommonoptionsgroup_v5.h \ + $$PWD/archs/mcs51/mcs51targetcompilergroup_v5.h \ + $$PWD/archs/mcs51/mcs51targetgroup_v5.h \ + $$PWD/archs/mcs51/mcs51targetlinkergroup_v5.h \ + $$PWD/archs/mcs51/mcs51targetmiscgroup_v5.h \ + $$PWD/archs/mcs51/mcs51utilitiesgroup_v5.h \ + $$PWD/archs/mcs51/mcs51utils.h + +SOURCES += \ + $$PWD/archs/mcs51/mcs51buildtargetgroup_v5.cpp \ + $$PWD/archs/mcs51/mcs51commonpropertygroup_v5.cpp \ + $$PWD/archs/mcs51/mcs51debugoptiongroup_v5.cpp \ + $$PWD/archs/mcs51/mcs51dlloptiongroup_v5.cpp \ + $$PWD/archs/mcs51/mcs51targetassemblergroup_v5.cpp \ + $$PWD/archs/mcs51/mcs51targetcommonoptionsgroup_v5.cpp \ + $$PWD/archs/mcs51/mcs51targetcompilergroup_v5.cpp \ + $$PWD/archs/mcs51/mcs51targetgroup_v5.cpp \ + $$PWD/archs/mcs51/mcs51targetlinkergroup_v5.cpp \ + $$PWD/archs/mcs51/mcs51targetmiscgroup_v5.cpp \ + $$PWD/archs/mcs51/mcs51utilitiesgroup_v5.cpp \ + $$PWD/archs/mcs51/mcs51utils.cpp + +# For ARM architecture. + +HEADERS += \ + $$PWD/archs/arm/armbuildtargetgroup_v5.h \ + $$PWD/archs/arm/armcommonpropertygroup_v5.h \ + $$PWD/archs/arm/armdebugoptiongroup_v5.h \ + $$PWD/archs/arm/armdlloptiongroup_v5.h \ + $$PWD/archs/arm/armtargetassemblergroup_v5.h \ + $$PWD/archs/arm/armtargetcommonoptionsgroup_v5.h \ + $$PWD/archs/arm/armtargetcompilergroup_v5.h \ + $$PWD/archs/arm/armtargetgroup_v5.h \ + $$PWD/archs/arm/armtargetlinkergroup_v5.h \ + $$PWD/archs/arm/armtargetmiscgroup_v5.h \ + $$PWD/archs/arm/armutilitiesgroup_v5.h + +SOURCES += \ + $$PWD/archs/arm/armbuildtargetgroup_v5.cpp \ + $$PWD/archs/arm/armcommonpropertygroup_v5.cpp \ + $$PWD/archs/arm/armdebugoptiongroup_v5.cpp \ + $$PWD/archs/arm/armdlloptiongroup_v5.cpp \ + $$PWD/archs/arm/armtargetassemblergroup_v5.cpp \ + $$PWD/archs/arm/armtargetcommonoptionsgroup_v5.cpp \ + $$PWD/archs/arm/armtargetcompilergroup_v5.cpp \ + $$PWD/archs/arm/armtargetgroup_v5.cpp \ + $$PWD/archs/arm/armtargetlinkergroup_v5.cpp \ + $$PWD/archs/arm/armtargetmiscgroup_v5.cpp \ + $$PWD/archs/arm/armutilitiesgroup_v5.cpp diff --git a/src/plugins/generator/keiluv/keiluv.qbs b/src/plugins/generator/keiluv/keiluv.qbs new file mode 100644 index 00000000..65f8fa02 --- /dev/null +++ b/src/plugins/generator/keiluv/keiluv.qbs @@ -0,0 +1,89 @@ +import qbs +import "../../qbsplugin.qbs" as QbsPlugin + +QbsPlugin { + Depends { name: "qbsjson" } + + name: "keiluvgenerator" + + files: ["keiluvgeneratorplugin.cpp"] + + Group { + name: "KEIL UV generator common" + files: [ + "keiluvfilesgroupspropertygroup.cpp", + "keiluvfilesgroupspropertygroup.h", + "keiluvgenerator.cpp", + "keiluvgenerator.h", + "keiluvproject.cpp", + "keiluvproject.h", + "keiluvprojectwriter.cpp", + "keiluvprojectwriter.h", + "keiluvutils.cpp", + "keiluvutils.h", + "keiluvversioninfo.h", + "keiluvworkspace.cpp", + "keiluvworkspace.h", + "keiluvworkspacewriter.cpp", + "keiluvworkspacewriter.h", + ] + } + Group { + name: "KEIL UV generator for MCS51" + prefix: "archs/mcs51/" + files: [ + "mcs51buildtargetgroup_v5.cpp", + "mcs51buildtargetgroup_v5.h", + "mcs51commonpropertygroup_v5.cpp", + "mcs51commonpropertygroup_v5.h", + "mcs51debugoptiongroup_v5.cpp", + "mcs51debugoptiongroup_v5.h", + "mcs51dlloptiongroup_v5.cpp", + "mcs51dlloptiongroup_v5.h", + "mcs51targetassemblergroup_v5.cpp", + "mcs51targetassemblergroup_v5.h", + "mcs51targetcommonoptionsgroup_v5.cpp", + "mcs51targetcommonoptionsgroup_v5.h", + "mcs51targetcompilergroup_v5.cpp", + "mcs51targetcompilergroup_v5.h", + "mcs51targetgroup_v5.cpp", + "mcs51targetgroup_v5.h", + "mcs51targetlinkergroup_v5.cpp", + "mcs51targetlinkergroup_v5.h", + "mcs51targetmiscgroup_v5.cpp", + "mcs51targetmiscgroup_v5.h", + "mcs51utilitiesgroup_v5.cpp", + "mcs51utilitiesgroup_v5.h", + "mcs51utils.cpp", + "mcs51utils.h", + ] + } + Group { + name: "KEIL UV generator for ARM" + prefix: "archs/arm/" + files: [ + "armbuildtargetgroup_v5.cpp", + "armbuildtargetgroup_v5.h", + "armcommonpropertygroup_v5.cpp", + "armcommonpropertygroup_v5.h", + "armdebugoptiongroup_v5.cpp", + "armdebugoptiongroup_v5.h", + "armdlloptiongroup_v5.cpp", + "armdlloptiongroup_v5.h", + "armtargetassemblergroup_v5.cpp", + "armtargetassemblergroup_v5.h", + "armtargetcommonoptionsgroup_v5.cpp", + "armtargetcommonoptionsgroup_v5.h", + "armtargetcompilergroup_v5.cpp", + "armtargetcompilergroup_v5.h", + "armtargetgroup_v5.cpp", + "armtargetgroup_v5.h", + "armtargetlinkergroup_v5.cpp", + "armtargetlinkergroup_v5.h", + "armtargetmiscgroup_v5.cpp", + "armtargetmiscgroup_v5.h", + "armutilitiesgroup_v5.cpp", + "armutilitiesgroup_v5.h", + ] + } +} diff --git a/src/plugins/generator/keiluv/keiluvconstants.h b/src/plugins/generator/keiluv/keiluvconstants.h new file mode 100644 index 00000000..0d27ab81 --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvconstants.h @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVCONSTANTS_H +#define QBS_KEILUVCONSTANTS_H + +namespace qbs { +namespace KeiluvConstants { + +namespace v5 { +constexpr int kUVisionVersion = 5; +} + +} // namespace KeiluvConstants +} // namespace qbs + +#endif // QBS_KEILUVCONSTANTS_H diff --git a/src/plugins/generator/keiluv/keiluvfilesgroupspropertygroup.cpp b/src/plugins/generator/keiluv/keiluvfilesgroupspropertygroup.cpp new file mode 100644 index 00000000..d1fda987 --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvfilesgroupspropertygroup.cpp @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "keiluvfilesgroupspropertygroup.h" +#include "keiluvutils.h" + +#include + +#include + +namespace qbs { + +class KeiluvFilePropertyGroup final : public gen::xml::PropertyGroup +{ +public: + explicit KeiluvFilePropertyGroup( + const QString &fullFilePath, + const QString &baseDirectory) + : gen::xml::PropertyGroup("File") + { + const QFileInfo fileInfo(fullFilePath); + const auto fileName = fileInfo.fileName(); + const auto fileType = encodeFileType(fileInfo.suffix()); + const auto filePath = QDir::toNativeSeparators( + gen::utils::relativeFilePath( + baseDirectory, + fileInfo.absoluteFilePath())); + + appendChild(QByteArrayLiteral("FileName"), + fileName); + appendChild(QByteArrayLiteral("FileType"), + fileType); + appendChild(QByteArrayLiteral("FilePath"), + filePath); + } + +private: + enum FileType { + UnknownFileType = 0, + CSourceFileType = 1, + AssemblerFileType = 2, + LibraryFileType = 4, + TextFileType = 5, + CppSourceFileType = 8, + }; + + static FileType encodeFileType(const QString &fileSuffix) + { + if (fileSuffix.compare(QLatin1String("c"), + Qt::CaseInsensitive) == 0) { + return CSourceFileType; + } else if (fileSuffix.compare(QLatin1String("cpp"), + Qt::CaseInsensitive) == 0) { + return CppSourceFileType; + } else if (fileSuffix.compare(QLatin1String("s"), + Qt::CaseInsensitive) == 0 + || fileSuffix.compare(QLatin1String("a51"), + Qt::CaseInsensitive) == 0) { + return AssemblerFileType; + } else if (fileSuffix.compare(QLatin1String("lib"), + Qt::CaseInsensitive) == 0) { + return LibraryFileType; + } else { + // All header files, text files and include files + // interpretes as a text file types. + return TextFileType; + } + } +}; + +class KeiluvFilesPropertyGroup final : public gen::xml::PropertyGroup +{ +public: + explicit KeiluvFilesPropertyGroup( + const QList &sourceArtifacts, + const QString &baseDirectory) + : gen::xml::PropertyGroup("Files") + { + for (const auto &artifact : sourceArtifacts) + appendChild(artifact.filePath(), + baseDirectory); + } + + explicit KeiluvFilesPropertyGroup( + const QStringList &filePaths, + const QString &baseDirectory) + : gen::xml::PropertyGroup("Files") + { + for (const auto &filePath : filePaths) + appendChild(filePath, + baseDirectory); + } +}; + +class KeiluvFileGroupPropertyGroup final : public gen::xml::PropertyGroup +{ +public: + explicit KeiluvFileGroupPropertyGroup( + const QString &groupName, + const QList &sourceArtifacts, + const QString &baseDirectory) + : gen::xml::PropertyGroup("Group") + { + appendChild(QByteArrayLiteral("GroupName"), + groupName); + + appendChild(sourceArtifacts, + baseDirectory); + } + + explicit KeiluvFileGroupPropertyGroup( + const QString &groupName, + const QStringList &filePaths, + const QString &baseDirectory) + : gen::xml::PropertyGroup("Group") + { + appendChild(QByteArrayLiteral("GroupName"), + groupName); + + appendChild(filePaths, + baseDirectory); + } +}; + +KeiluvFilesGroupsPropertyGroup::KeiluvFilesGroupsPropertyGroup( + const Project &qbsProject, + const ProductData &qbsProduct, + const std::vector &qbsProductDeps) + : gen::xml::PropertyGroup(QByteArrayLiteral("Groups")) +{ + const auto baseDirectory = gen::utils::buildRootPath(qbsProject); + + // Build source items. + for (const auto &group : qbsProduct.groups()) { + // Ignore disabled groups (e.g. when its condition property is false). + if (!group.isEnabled()) + continue; + auto sourceArtifacts = group.sourceArtifacts(); + // Remove the linker script artifacts. + sourceArtifacts.erase(std::remove_if(sourceArtifacts.begin(), + sourceArtifacts.end(), + [](const auto &artifact){ + const auto tags = artifact.fileTags(); + return tags.contains(QLatin1String("linkerscript")); + }), sourceArtifacts.end()); + + if (sourceArtifacts.isEmpty()) + continue; + appendChild( + group.name(), sourceArtifacts, baseDirectory); + } + + // Build local static library items. + const auto &qbsProps = qbsProduct.moduleProperties(); + const auto staticLibs = KeiluvUtils::staticLibraries(qbsProps); + if (!staticLibs.isEmpty()) { + appendChild( + QStringLiteral("Static Libs"), staticLibs, baseDirectory); + } + + // Build dependency library items. + const auto deps = KeiluvUtils::dependencies(qbsProductDeps); + if (!deps.isEmpty()) { + appendChild( + QStringLiteral("Dependencies"), deps, baseDirectory); + } +} + +} // namespace qbs diff --git a/src/plugins/generator/keiluv/keiluvfilesgroupspropertygroup.h b/src/plugins/generator/keiluv/keiluvfilesgroupspropertygroup.h new file mode 100644 index 00000000..bde86b89 --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvfilesgroupspropertygroup.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVFILESGROUPSPROPERTYGROUP_H +#define QBS_KEILUVFILESGROUPSPROPERTYGROUP_H + +#include + +#include + +namespace qbs { + +class KeiluvFilesGroupsPropertyGroup final + : public gen::xml::PropertyGroup +{ +public: + explicit KeiluvFilesGroupsPropertyGroup( + const qbs::Project &qbsProject, + const qbs::ProductData &qbsProduct, + const std::vector &qbsProductDeps); +}; + +} // namespace qbs + +#endif // QBS_KEILUVFILESGROUPSPROPERTYGROUP_H diff --git a/src/plugins/generator/keiluv/keiluvgenerator.cpp b/src/plugins/generator/keiluv/keiluvgenerator.cpp new file mode 100644 index 00000000..f98f55ab --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvgenerator.cpp @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "keiluvgenerator.h" +#include "keiluvproject.h" +#include "keiluvprojectwriter.h" +#include "keiluvworkspace.h" +#include "keiluvworkspacewriter.h" + +#include +#include + +#include +#include + +#include + +namespace qbs { + +static void writeProjectFiles(const std::map> &projects, + const Internal::Logger &logger) +{ + for (const auto &item : projects) { + const QString projectFilePath = item.first; + Internal::FileSaver file(projectFilePath.toStdString()); + if (!file.open()) + throw ErrorInfo(Internal::Tr::tr("Cannot open %s for writing") + .arg(projectFilePath)); + + std::shared_ptr project = item.second; + KeiluvProjectWriter writer(file.device()); + if (!(writer.write(project.get()) && file.commit())) + throw ErrorInfo(Internal::Tr::tr("Failed to generate %1") + .arg(projectFilePath)); + + logger.qbsInfo() << Internal::Tr::tr("Generated %1").arg( + QFileInfo(projectFilePath).fileName()); + } +} + +static void writeWorkspace(const std::shared_ptr &wokspace, + const QString &workspaceFilePath, + const Internal::Logger &logger) +{ + Internal::FileSaver file(workspaceFilePath.toStdString()); + if (!file.open()) + throw ErrorInfo(Internal::Tr::tr("Cannot open %s for writing") + .arg(workspaceFilePath)); + + KeiluvWorkspaceWriter writer(file.device()); + if (!(writer.write(wokspace.get()) && file.commit())) + throw ErrorInfo(Internal::Tr::tr("Failed to generate %1") + .arg(workspaceFilePath)); + + logger.qbsInfo() << Internal::Tr::tr("Generated %1").arg( + QFileInfo(workspaceFilePath).fileName()); +} + +KeiluvGenerator::KeiluvGenerator(const gen::VersionInfo &versionInfo) + : m_versionInfo(versionInfo) +{ +} + +QString KeiluvGenerator::generatorName() const +{ + return QStringLiteral("keiluv%1").arg(m_versionInfo.marketingVersion()); +} + +void KeiluvGenerator::reset() +{ + m_workspace.reset(); + m_workspaceFilePath.clear(); + m_projects.clear(); +} + +void KeiluvGenerator::generate() +{ + GeneratableProjectIterator it(project()); + it.accept(this); + + writeProjectFiles(m_projects, logger()); + writeWorkspace(m_workspace, m_workspaceFilePath, logger()); + + reset(); +} + +void KeiluvGenerator::visitProject(const GeneratableProject &project) +{ + const QDir buildDir = project.baseBuildDirectory(); + + m_workspaceFilePath = buildDir.absoluteFilePath( + project.name() + QStringLiteral(".uvmpw")); + m_workspace = std::make_shared(m_workspaceFilePath); +} + +void KeiluvGenerator::visitProjectData( + const GeneratableProject &project, + const GeneratableProjectData &projectData) +{ + Q_UNUSED(project) + Q_UNUSED(projectData) +} + +void KeiluvGenerator::visitProduct( + const GeneratableProject &project, + const GeneratableProjectData &projectData, + const GeneratableProductData &productData) +{ + Q_UNUSED(projectData); + + const QDir baseBuildDir(project.baseBuildDirectory().absolutePath()); + const QString projFileName = productData.name() + QLatin1String(".uvprojx"); + const QString projectFilePath = baseBuildDir.absoluteFilePath(projFileName); + const auto targetProject = std::make_shared( + project, productData, m_versionInfo); + + m_projects.insert({projectFilePath, targetProject}); + m_workspace->addProject(projectFilePath); +} + +} // namespace qbs diff --git a/src/plugins/generator/keiluv/keiluvgenerator.h b/src/plugins/generator/keiluv/keiluvgenerator.h new file mode 100644 index 00000000..25b89b9c --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvgenerator.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVGENERATOR_H +#define QBS_KEILUVGENERATOR_H + +#include "keiluvversioninfo.h" + +#include +#include + +namespace qbs { + +class KeiluvProject; +class KeiluvWorkspace; + +class KeiluvGenerator final : public ProjectGenerator, + private IGeneratableProjectVisitor +{ +public: + explicit KeiluvGenerator(const gen::VersionInfo &versionInfo); + + QString generatorName() const final; + void generate() final; + +private: + void reset(); + + void visitProject(const GeneratableProject &project) final; + void visitProjectData(const GeneratableProject &project, + const GeneratableProjectData &projectData) final; + void visitProduct(const GeneratableProject &project, + const GeneratableProjectData &projectData, + const GeneratableProductData &productData) final; + + const gen::VersionInfo m_versionInfo; + std::shared_ptr m_workspace; + QString m_workspaceFilePath; + std::map> m_projects; +}; + +} // namespace qbs + +#endif // QBS_KEILUVGENERATOR_H diff --git a/src/plugins/generator/keiluv/keiluvgeneratorplugin.cpp b/src/plugins/generator/keiluv/keiluvgeneratorplugin.cpp new file mode 100644 index 00000000..abbccc3d --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvgeneratorplugin.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "keiluvgenerator.h" +#include "keiluvversioninfo.h" + +#include +#include + +static void QbsKeiluvGeneratorPluginLoad() +{ + for (const auto &info : qbs::KeiluvVersionInfo::knownVersions) { + qbs::ProjectGeneratorManager::registerGenerator( + std::make_shared(info)); + } +} + +static void QbsKeiluvGeneratorPluginUnload() +{ +} + +#ifndef GENERATOR_EXPORT +#if defined(WIN32) || defined(_WIN32) +#define GENERATOR_EXPORT __declspec(dllexport) +#else +#define GENERATOR_EXPORT __attribute__((visibility("default"))) +#endif +#endif + +QBS_REGISTER_STATIC_PLUGIN(extern "C" GENERATOR_EXPORT, keiluvgenerator, + QbsKeiluvGeneratorPluginLoad, QbsKeiluvGeneratorPluginUnload) diff --git a/src/plugins/generator/keiluv/keiluvproject.cpp b/src/plugins/generator/keiluv/keiluvproject.cpp new file mode 100644 index 00000000..89db73b1 --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvproject.cpp @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "keiluvconstants.h" +#include "keiluvproject.h" +#include "keiluvutils.h" +#include "keiluvversioninfo.h" + +#include "archs/mcs51/mcs51buildtargetgroup_v5.h" +#include "archs/arm/armbuildtargetgroup_v5.h" + +#include + +namespace qbs { + +static QString keilProjectSchema(const gen::VersionInfo &info) +{ + const auto v = info.marketingVersion(); + switch (v) { + case KeiluvConstants::v5::kUVisionVersion: + return QStringLiteral("2.1"); + default: + return {}; + } +} + +KeiluvProject::KeiluvProject(const qbs::GeneratableProject &genProject, + const qbs::GeneratableProductData &genProduct, + const gen::VersionInfo &versionInfo) +{ + Q_ASSERT(genProject.projects.size() == genProject.commandLines.size()); + Q_ASSERT(genProject.projects.size() == genProduct.data.size()); + + // Create available configuration group factories. + m_factories.push_back(std::make_unique< + keiluv::mcs51::v5::Mcs51BuildTargetGroupFactory>()); + m_factories.push_back(std::make_unique< + keiluv::arm::v5::ArmBuildTargetGroupFactory>()); + + // Construct schema version item (is it depends on a project version?). + const auto schema = keilProjectSchema(versionInfo); + appendChild(QByteArrayLiteral("SchemaVersion"), + schema); + + // Construct targets group. + const auto targetsGroup = appendChild( + QByteArrayLiteral("Targets")); + + // Construct all build target items. + const int configsCount = std::max(genProject.projects.size(), + genProduct.data.size()); + for (auto configIndex = 0; configIndex < configsCount; ++configIndex) { + const qbs::Project qbsProject = genProject.projects + .values().at(configIndex); + const qbs::ProductData qbsProduct = genProduct.data + .values().at(configIndex); + const QString confName = gen::utils::buildConfigurationName(qbsProject); + const std::vector qbsProductDeps = gen::utils::dependenciesOf + (qbsProduct, genProject, confName); + + const auto arch = gen::utils::architecture(qbsProject); + if (arch == gen::utils::Architecture::Unknown) + throw ErrorInfo(Internal::Tr::tr("Target architecture is not set," + " please use the 'profile' option")); + + // Construct the build target item, which are depend from + // the architecture and the version. + const auto factoryEnd = m_factories.cend(); + const auto factoryIt = std::find_if(m_factories.cbegin(), factoryEnd, + [arch, versionInfo](const auto &factory) { + return factory->canCreate(arch, versionInfo.version()); + }); + if (factoryIt == factoryEnd) { + throw ErrorInfo(Internal::Tr::tr("Incompatible target architecture '%1'" + " for KEIL UV version %2") + .arg(gen::utils::architectureName(arch)) + .arg(versionInfo.marketingVersion())); + } + + auto targetGroup = (*factoryIt)->create( + qbsProject, qbsProduct, qbsProductDeps); + targetsGroup->appendChild(std::move(targetGroup)); + } +} + +} // namespace qbs diff --git a/src/plugins/generator/keiluv/keiluvproject.h b/src/plugins/generator/keiluv/keiluvproject.h new file mode 100644 index 00000000..da86f71f --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvproject.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVPROJECT_H +#define QBS_KEILUVPROJECT_H + +#include + +#include +#include + +#include + +namespace qbs { + +class KeiluvProject final : public gen::xml::Project +{ +public: + explicit KeiluvProject( + const qbs::GeneratableProject &genProject, + const qbs::GeneratableProductData &genProduct, + const gen::VersionInfo &versionInfo); +private: + std::vector> m_factories; +}; + +} // namespace qbs + +#endif // QBS_KEILUVPROJECT_H diff --git a/src/plugins/generator/keiluv/keiluvprojectwriter.cpp b/src/plugins/generator/keiluv/keiluvprojectwriter.cpp new file mode 100644 index 00000000..77616335 --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvprojectwriter.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "keiluvprojectwriter.h" + +namespace qbs { + +KeiluvProjectWriter::KeiluvProjectWriter(std::ostream *device) + : gen::xml::ProjectWriter(device) +{ +} + +void KeiluvProjectWriter::visitProjectStart(const gen::xml::Project *project) +{ + Q_UNUSED(project) + writer()->writeStartElement(QStringLiteral("Project")); + writer()->writeAttribute( + QStringLiteral("xmlns:xsi"), + QStringLiteral("http://www.w3.org/2001/XMLSchema-instance")); + writer()->writeAttribute( + QStringLiteral("xsi:noNamespaceSchemaLocation"), + QStringLiteral("project_proj.xsd")); +} + +void KeiluvProjectWriter::visitProjectEnd(const gen::xml::Project *project) +{ + Q_UNUSED(project) + writer()->writeEndElement(); +} + +} // namespace qbs diff --git a/src/plugins/generator/keiluv/keiluvprojectwriter.h b/src/plugins/generator/keiluv/keiluvprojectwriter.h new file mode 100644 index 00000000..6f890c8e --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvprojectwriter.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVPROJECTWRITER_H +#define QBS_KEILUVPROJECTWRITER_H + +#include + +namespace qbs { + +class KeiluvProjectWriter final : public gen::xml::ProjectWriter +{ + Q_DISABLE_COPY(KeiluvProjectWriter) +public: + explicit KeiluvProjectWriter(std::ostream *device); + +private: + void visitProjectStart(const gen::xml::Project *project) final; + void visitProjectEnd(const gen::xml::Project *project) final; +}; + +} // namespace qbs + +#endif // QBS_KEILUVPROJECTWRITER_H diff --git a/src/plugins/generator/keiluv/keiluvutils.cpp b/src/plugins/generator/keiluv/keiluvutils.cpp new file mode 100644 index 00000000..ef93b973 --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvutils.cpp @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "keiluvutils.h" + +#include + +namespace qbs { +namespace KeiluvUtils { + +OutputBinaryType outputBinaryType(const ProductData &qbsProduct) +{ + const auto &qbsProductType = qbsProduct.type(); + if (qbsProductType.contains(QLatin1String("application"))) + return ApplicationOutputType; + if (qbsProductType.contains(QLatin1String("staticlibrary"))) + return LibraryOutputType; + return ApplicationOutputType; +} + +QString toolkitRootPath(const ProductData &qbsProduct) +{ + QDir dir(qbsProduct.moduleProperties() + .getModuleProperty(Internal::StringConstants::cppModule(), + QStringLiteral("toolchainInstallPath")) + .toString()); + dir.cdUp(); + const auto path = dir.absolutePath(); + return QDir::toNativeSeparators(path); +} + +QStringList cppModuleCompilerFlags(const PropertyMap &qbsProps) +{ + return gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("driverFlags"), QStringLiteral("cFlags"), + QStringLiteral("cppFlags"), QStringLiteral("cxxFlags"), + QStringLiteral("commonCompilerFlags")}); +} + +QStringList cppModuleAssemblerFlags(const PropertyMap &qbsProps) +{ + return gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("assemblerFlags")}); +} + +QStringList cppModuleLinkerFlags(const PropertyMap &qbsProps) +{ + return gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("driverLinkerFlags")}); +} + +QStringList includes(const PropertyMap &qbsProps) +{ + auto paths = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("includePaths"), + QStringLiteral("systemIncludePaths")}); + // Transform include path separators to native. + std::transform(paths.begin(), paths.end(), paths.begin(), + [](const auto &path) { + return QDir::toNativeSeparators(path); + }); + return paths; +} + +QStringList defines(const PropertyMap &qbsProps) +{ + return gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("defines")}); +} + +QStringList staticLibraries(const PropertyMap &qbsProps) +{ + auto libs = gen::utils::cppStringModuleProperties( + qbsProps, {QStringLiteral("staticLibraries")}); + // Transform library path separators to native. + std::transform(libs.begin(), libs.end(), libs.begin(), + [](const auto &path) { + return QDir::toNativeSeparators(path); + }); + return libs; +} + +QStringList dependencies(const std::vector &qbsProductDeps) +{ + QStringList deps; + for (const ProductData &qbsProductDep : qbsProductDeps) { + const auto path = qbsProductDep.buildDirectory() + + QLatin1String("/obj/") + + gen::utils::targetBinary(qbsProductDep); + deps.push_back(QDir::toNativeSeparators(path)); + } + return deps; +} + +} // namespace KeiluvUtils +} // namespace qbs diff --git a/src/plugins/generator/keiluv/keiluvutils.h b/src/plugins/generator/keiluv/keiluvutils.h new file mode 100644 index 00000000..92209dc5 --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvutils.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVUTILS_H +#define QBS_KEILUVUTILS_H + +#include + +#include + +namespace qbs { +namespace KeiluvUtils { + +enum OutputBinaryType { + ApplicationOutputType, + LibraryOutputType +}; + +OutputBinaryType outputBinaryType(const ProductData &qbsProduct); + +QString toolkitRootPath(const ProductData &qbsProduct); + +QStringList cppModuleCompilerFlags(const PropertyMap &qbsProps); + +QStringList cppModuleAssemblerFlags(const PropertyMap &qbsProps); + +QStringList cppModuleLinkerFlags(const PropertyMap &qbsProps); + +QStringList includes(const PropertyMap &qbsProps); +QStringList defines(const PropertyMap &qbsProps); +QStringList staticLibraries(const PropertyMap &qbsProps); +QStringList dependencies(const std::vector &qbsProductDeps); + +} // namespace KeiluvUtils +} // namespace qbs + +#endif // QBS_KEILUVUTILS_H diff --git a/src/plugins/generator/keiluv/keiluvversioninfo.h b/src/plugins/generator/keiluv/keiluvversioninfo.h new file mode 100644 index 00000000..cc379c3d --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvversioninfo.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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_KEILUVVERSIONINFO_H +#define QBS_KEILUVVERSIONINFO_H + +#include "keiluvconstants.h" + +#include +#include + +namespace qbs { +namespace KeiluvVersionInfo { + +constexpr gen::VersionInfo knownVersions[] = { + {Version(KeiluvConstants::v5::kUVisionVersion), + {gen::utils::Architecture::Mcs51, + gen::utils::Architecture::Arm}}, +}; + +} // namespace KeiluvVersionInfo +} // namespace qbs + +#endif // QBS_KEILUVVERSIONINFO_H diff --git a/src/plugins/generator/keiluv/keiluvworkspace.cpp b/src/plugins/generator/keiluv/keiluvworkspace.cpp new file mode 100644 index 00000000..b4df9200 --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvworkspace.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "keiluvworkspace.h" + +#include + +namespace qbs { + +KeiluvWorkspace::KeiluvWorkspace(const QString &workspacePath) + : gen::xml::Workspace(workspacePath) +{ + // Construct schema version item. + appendChild(QByteArrayLiteral("SchemaVersion"), + QStringLiteral("1.0")); + + // Construct workspace name item. + appendChild(QByteArrayLiteral("WorkspaceName"), + QStringLiteral("WorkSpace")); +} + +void KeiluvWorkspace::addProject(const QString &projectFilePath) +{ + const QString relativeProjectPath = QDir::toNativeSeparators( + m_baseDirectory.relativeFilePath(projectFilePath)); + + const auto projectGroup = appendChild( + QByteArrayLiteral("project")); + projectGroup->appendProperty("PathAndName", relativeProjectPath); +} + +} // namespace qbs diff --git a/src/plugins/generator/keiluv/keiluvworkspace.h b/src/plugins/generator/keiluv/keiluvworkspace.h new file mode 100644 index 00000000..2b274c99 --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvworkspace.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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_KEILUVWORKSPACE_H +#define QBS_KEILUVWORKSPACE_H + +#include + +namespace qbs { + +class KeiluvWorkspace final : public gen::xml::Workspace +{ +public: + explicit KeiluvWorkspace(const QString &workspacePath); + void addProject(const QString &projectPath) final; +}; + +} // namespace qbs + +#endif // QBS_KEILUVWORKSPACE_H diff --git a/src/plugins/generator/keiluv/keiluvworkspacewriter.cpp b/src/plugins/generator/keiluv/keiluvworkspacewriter.cpp new file mode 100644 index 00000000..c886b0e6 --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvworkspacewriter.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include "keiluvworkspacewriter.h" + +namespace qbs { + +KeiluvWorkspaceWriter::KeiluvWorkspaceWriter(std::ostream *device) + : gen::xml::WorkspaceWriter(device) +{ +} + +void KeiluvWorkspaceWriter::visitWorkspaceStart(const gen::xml::Workspace *workspace) +{ + Q_UNUSED(workspace) + writer()->writeStartElement(QStringLiteral("ProjectWorkspace")); + writer()->writeAttribute( + QStringLiteral("xmlns:xsi"), + QStringLiteral("http://www.w3.org/2001/XMLSchema-instance")); + writer()->writeAttribute( + QStringLiteral("xsi:noNamespaceSchemaLocation"), + QStringLiteral("project_mpw.xsd")); +} + +void KeiluvWorkspaceWriter::visitWorkspaceEnd(const gen::xml::Workspace *workspace) +{ + Q_UNUSED(workspace) + writer()->writeEndElement(); +} + +} // namespace qbs diff --git a/src/plugins/generator/keiluv/keiluvworkspacewriter.h b/src/plugins/generator/keiluv/keiluvworkspacewriter.h new file mode 100644 index 00000000..2419ad76 --- /dev/null +++ b/src/plugins/generator/keiluv/keiluvworkspacewriter.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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. +** +****************************************************************************/ + +#ifndef QBS_KEILUVWORKSPACEWRITER_H +#define QBS_KEILUVWORKSPACEWRITER_H + +#include + +namespace qbs { + +class KeiluvWorkspaceWriter final : public gen::xml::WorkspaceWriter +{ + Q_DISABLE_COPY(KeiluvWorkspaceWriter) +public: + explicit KeiluvWorkspaceWriter(std::ostream *device); + +private: + void visitWorkspaceStart(const gen::xml::Workspace *workspace) final; + void visitWorkspaceEnd(const gen::xml::Workspace *workspace) final; +}; + +} // namespace qbs + +#endif // QBS_KEILUVWORKSPACEWRITER_H diff --git a/src/plugins/generator/makefilegenerator/makefilegenerator.cpp b/src/plugins/generator/makefilegenerator/makefilegenerator.cpp new file mode 100644 index 00000000..e07dda13 --- /dev/null +++ b/src/plugins/generator/makefilegenerator/makefilegenerator.cpp @@ -0,0 +1,362 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "makefilegenerator.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace qbs { +using namespace Internal; + +QString qbs::MakefileGenerator::generatorName() const +{ + return QStringLiteral("makefile"); +} + +static QString quote(const QString &s) +{ + QString quoted = shellQuote(s); + quoted.replace(QLatin1Char('$'), QLatin1String("$$")); // For make + quoted.replace(QLatin1String("$$(SRCDIR)"), QLatin1String("$(SRCDIR)")); + quoted.replace(QLatin1String("$$(BUILD_ROOT)"), QLatin1String("$(BUILD_ROOT)")); + quoted.replace(QLatin1String("$$(INSTALL_ROOT)"), QLatin1String("$(INSTALL_ROOT)")); + return quoted; +} + +enum class TargetType { Product, Path }; +static QString makeValidTargetName(const QString &name, TargetType targetType) +{ + QString modifiedName = name; + switch (targetType) { + case TargetType::Product: { + static const QRegularExpression illegalChar(QStringLiteral("[^_.0-9A-Za-z]")); + modifiedName.replace(illegalChar, QStringLiteral("_")); + break; + } + case TargetType::Path: + if (HostOsInfo::isWindowsHost()) { + modifiedName = QDir::toNativeSeparators(modifiedName); + modifiedName = quote(modifiedName); + } else { + modifiedName.replace(QLatin1Char(' '), QStringLiteral("\\ ")); + } + } + return modifiedName; +} + +static QString makeValidTargetName(const ProductData &product) +{ + QString name = makeValidTargetName(product.name(), TargetType::Product); + if (!product.multiplexConfigurationId().isEmpty()) + name.append(QLatin1Char('_')).append(product.multiplexConfigurationId()); + return name; +} + +using PrefixSpec = std::pair; +static QString replacePrefix(const QString &path, const std::vector &candidates) +{ + for (const PrefixSpec &prefixSpec : candidates) { + if (path.startsWith(prefixSpec.first) + && (path.size() == prefixSpec.first.size() + || path.at(prefixSpec.first.size()) == QLatin1Char('/'))) { + QString p = path; + return p.replace(0, prefixSpec.first.size(), + QLatin1String("$(") + prefixSpec.second + QLatin1Char(')')); + } + } + return path; +} + +static QString bruteForcePathReplace(const QString &value, const QString &srcDir, + const QString &buildDir, const QString &installRoot) +{ + QString transformedValue = value; + if (!installRoot.isEmpty()) + transformedValue.replace(installRoot, QStringLiteral("$(INSTALL_ROOT)")); + transformedValue.replace(buildDir, QStringLiteral("$(BUILD_ROOT)")); + transformedValue.replace(srcDir, QStringLiteral("$(SRCDIR)")); + return transformedValue; +} + +static QString mkdirCmdLine(const QString &dir) +{ + if (HostOsInfo::isWindowsHost()) + return QStringLiteral("if not exist %1 mkdir %1 & if not exist %1 exit 1").arg(dir); + return QStringLiteral("mkdir -p ") + dir; +} + +static QString installFileCommand() +{ + return HostOsInfo::isWindowsHost() ? QStringLiteral("copy /Y") + : QStringLiteral("install -m 644 -p"); +} + +static QString installProgramCommand() +{ + return HostOsInfo::isWindowsHost() ? installFileCommand() + : QStringLiteral("install -m 755 -p"); +} + +static QString removeCommand() +{ + return HostOsInfo::isWindowsHost() ? QStringLiteral("del") : QStringLiteral("rm -f"); +} + +void qbs::MakefileGenerator::generate() +{ + const auto projects = project().projects.values(); + for (const Project &theProject : projects) { + const QString makefileFilePath = theProject.projectData().buildDirectory() + + QLatin1String("/Makefile"); + QFile makefile(makefileFilePath); + if (!makefile.open(QIODevice::WriteOnly)) { + throw ErrorInfo(Tr::tr("Failed to create '%1': %2") + .arg(makefileFilePath, makefile.errorString())); + } + QTextStream stream(&makefile); + ErrorInfo error; + const ProjectTransformerData projectTransformerData = theProject.transformerData(&error); + if (error.hasError()) + throw error; + stream << "# This file was generated by qbs" << "\n\n"; + stream << "INSTALL_FILE = " << installFileCommand() << '\n'; + stream << "INSTALL_PROGRAM = " << installProgramCommand() << '\n'; + stream << "RM = " << removeCommand() << '\n'; + stream << '\n'; + const ProjectData projectData = theProject.projectData(); + const QString srcDir = QFileInfo(projectData.location().filePath()).path(); + if (srcDir.contains(QLatin1Char(' '))) { + throw ErrorInfo(Tr::tr("The project directory '%1' contains space characters, which" + "is not supported by this generator.").arg(srcDir)); + } + stream << "SRCDIR = " << QDir::toNativeSeparators(srcDir) << '\n'; + const QString &buildDir = projectData.buildDirectory(); + if (buildDir.contains(QLatin1Char(' '))) { + throw ErrorInfo(Tr::tr("The build directory '%1' contains space characters, which" + "is not supported by this generator.").arg(buildDir)); + } + stream << "BUILD_ROOT = " << QDir::toNativeSeparators(buildDir) << '\n'; + QString installRoot; + const QList allInstallables = projectData.installableArtifacts(); + if (!allInstallables.empty()) { + installRoot = allInstallables.first().installData().installRoot(); + if (installRoot.contains(QLatin1Char(' '))) { + throw ErrorInfo(Tr::tr("The install root '%1' contains space characters, which" + "is not supported by this generator.").arg(installRoot)); + } + stream << "INSTALL_ROOT = " << QDir::toNativeSeparators(installRoot) << '\n'; + } + stream << "\nall:\n"; + const std::vector srcDirPrefixSpecs{std::make_pair(srcDir, + QStringLiteral("SRCDIR"))}; + const auto prefixifiedSrcDirPath = [&srcDirPrefixSpecs](const QString &path) { + return replacePrefix(path, srcDirPrefixSpecs); + }; + const std::vector buildRootPrefixSpecs{ + std::make_pair(buildDir, QStringLiteral("BUILD_ROOT"))}; + const auto prefixifiedBuildDirPath = [&buildRootPrefixSpecs](const QString &path) { + return replacePrefix(path, buildRootPrefixSpecs); + }; + const std::vector installRootPrefixSpecs{ + std::make_pair(installRoot, QStringLiteral("INSTALL_ROOT"))}; + const auto prefixifiedInstallDirPath + = [&installRoot, &installRootPrefixSpecs](const QString &path) { + if (installRoot.isEmpty()) + return path; + return replacePrefix(path, installRootPrefixSpecs); + }; + const auto transformedOutputFilePath = [=](const ArtifactData &output) { + return makeValidTargetName(prefixifiedBuildDirPath(output.filePath()), + TargetType::Path); + }; + const auto transformedInputFilePath = [=](const ArtifactData &input) { + return makeValidTargetName(prefixifiedSrcDirPath(input.filePath()), TargetType::Path); + }; + const auto transformedArtifactFilePath = [=](const ArtifactData &artifact) { + return artifact.isGenerated() ? transformedOutputFilePath(artifact) + : transformedInputFilePath(artifact); + }; + QStringList allTargets; + QStringList allDefaultTargets; + QStringList filesCreatedByJsCommands; + bool jsCommandsEncountered = false; + for (const auto &d : projectTransformerData) { + const ProductData productData = d.first; + const QString productTarget = makeValidTargetName(productData); + const ProductTransformerData productTransformerData = d.second; + const bool builtByDefault = productData.properties().value( + StringConstants::builtByDefaultProperty()).toBool(); + if (builtByDefault) + allDefaultTargets.push_back(productTarget); + allTargets.push_back(productTarget); + stream << productTarget << ':'; + const auto targetArtifacts = productData.targetArtifacts(); + for (const ArtifactData &ta : targetArtifacts) + stream << ' ' << transformedOutputFilePath(ta); + stream << '\n'; + for (const TransformerData &transformerData : productTransformerData) { + stream << transformedOutputFilePath(transformerData.outputs().constFirst()) << ":"; + const auto inputs = transformerData.inputs(); + for (const ArtifactData &input : inputs) + stream << ' ' << transformedArtifactFilePath(input); + stream << '\n'; + Set createdDirs; + const auto outputs = transformerData.outputs(); + for (const ArtifactData &output : outputs) { + const QString outputDir = QFileInfo(output.filePath()).path(); + if (createdDirs.insert(outputDir).second) + stream << "\t" << mkdirCmdLine(QDir::toNativeSeparators( + prefixifiedBuildDirPath(outputDir))) + << '\n'; + } + bool processCommandEncountered = false; + const auto commands = transformerData.commands(); + for (const RuleCommand &command : commands) { + if (command.type() == RuleCommand::JavaScriptCommandType) { + jsCommandsEncountered = true; + continue; + } + processCommandEncountered = true; + stream << '\t' << QDir::toNativeSeparators( + quote(bruteForcePathReplace(command.executable(), srcDir, + buildDir, installRoot))); + // TODO: Optionally use environment? + const auto args = command.arguments(); + for (const QString &arg : args) { + stream << ' ' + << quote(bruteForcePathReplace(arg, srcDir, buildDir, installRoot)); + } + stream << '\n'; + } + for (int i = 1; i < transformerData.outputs().size(); ++i) { + stream << transformedOutputFilePath(transformerData.outputs().at(i)) << ": " + << transformedOutputFilePath(transformerData.outputs().at(i-1)) << '\n'; + } + if (!processCommandEncountered && builtByDefault) { + const auto outputs = transformerData.outputs(); + for (const ArtifactData &output : outputs) + filesCreatedByJsCommands.push_back(output.filePath()); + } + } + stream << "install-" << productTarget << ": " << productTarget << '\n'; + Set createdDirs; + const auto installableArtifacts = productData.installableArtifacts(); + for (const ArtifactData &artifact : productData.installableArtifacts()) { + const QString &outputDir = artifact.installData().localInstallDir(); + if (outputDir.contains(QLatin1Char(' '))) { + logger().qbsWarning() << Tr::tr("Skipping installation of '%1', because " + "target directory '%2' contains spaces.") + .arg(artifact.filePath(), outputDir); + continue; + } + if (createdDirs.insert(outputDir).second) + stream << "\t" << mkdirCmdLine(QDir::toNativeSeparators( + prefixifiedInstallDirPath(outputDir))) + << '\n'; + const QFileInfo fileInfo(artifact.filePath()); + const QString transformedInputFilePath + = QDir::toNativeSeparators((artifact.isGenerated() + ? prefixifiedBuildDirPath(fileInfo.path()) + : prefixifiedSrcDirPath(fileInfo.path())) + + QLatin1Char('/') + quote(fileInfo.fileName())); + const QString transformedOutputDir + = QDir::toNativeSeparators(prefixifiedInstallDirPath( + artifact.installData().localInstallDir())); + stream << "\t" + << (artifact.isExecutable() ? "$(INSTALL_PROGRAM) " : "$(INSTALL_FILE) ") + << transformedInputFilePath << ' ' << transformedOutputDir << '\n'; + } + stream << "clean-" << productTarget << ":\n"; + for (const ArtifactData &artifact : productData.generatedArtifacts()) { + const QFileInfo fileInfo(artifact.filePath()); + const QString transformedFilePath = QDir::toNativeSeparators( + prefixifiedBuildDirPath(fileInfo.path()) + + QLatin1Char('/') + quote(fileInfo.fileName())); + stream << '\t'; + if (HostOsInfo::isWindowsHost()) + stream << '-'; + stream << "$(RM) " << transformedFilePath << '\n'; + } + } + + stream << "all:"; + for (const QString &target : qAsConst(allDefaultTargets)) + stream << ' ' << target; + stream << '\n'; + stream << "install:"; + for (const QString &target : qAsConst(allDefaultTargets)) + stream << ' ' << "install-" << target; + stream << '\n'; + stream << "clean:"; + for (const QString &target : qAsConst(allTargets)) + stream << ' ' << "clean-" << target; + stream << '\n'; + if (!filesCreatedByJsCommands.empty()) { + logger().qbsWarning() << Tr::tr("Some rules used by this project are not " + "Makefile-compatible, because they depend entirely on JavaScriptCommands. " + "The build is probably not fully functional. " + "Affected build artifacts:\n\t%1") + .arg(filesCreatedByJsCommands.join(QLatin1String("\n\t"))); + } else if (jsCommandsEncountered) { + logger().qbsWarning() << Tr::tr("Some rules in this project use JavaScriptCommands, " + "which cannot be converted to Makefile-compatible constructs. The build may " + "not be fully functional."); + } + logger().qbsInfo() << Tr::tr("Makefile successfully generated at '%1'.") + .arg(makefileFilePath); + } +} + +} // namespace qbs diff --git a/src/plugins/generator/makefilegenerator/makefilegenerator.h b/src/plugins/generator/makefilegenerator/makefilegenerator.h new file mode 100644 index 00000000..b2b23f08 --- /dev/null +++ b/src/plugins/generator/makefilegenerator/makefilegenerator.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_MAKEFILEGENERATOR_H +#define QBS_MAKEFILEGENERATOR_H + +#include + +namespace qbs { + +class MakefileGenerator : public ProjectGenerator +{ + QString generatorName() const override; + void generate() override; +}; + +} // namespace qbs + +#endif // Include guard. diff --git a/src/plugins/generator/makefilegenerator/makefilegenerator.pri b/src/plugins/generator/makefilegenerator/makefilegenerator.pri new file mode 100644 index 00000000..971e63d2 --- /dev/null +++ b/src/plugins/generator/makefilegenerator/makefilegenerator.pri @@ -0,0 +1 @@ +qbsPluginTarget = makefilegenerator diff --git a/src/plugins/generator/makefilegenerator/makefilegenerator.pro b/src/plugins/generator/makefilegenerator/makefilegenerator.pro new file mode 100644 index 00000000..c03191c3 --- /dev/null +++ b/src/plugins/generator/makefilegenerator/makefilegenerator.pro @@ -0,0 +1,11 @@ +include(makefilegenerator.pri) +include(../../plugins.pri) + +QT = core + +HEADERS += \ + $$PWD/makefilegenerator.h + +SOURCES += \ + $$PWD/makefilegenerator.cpp \ + $$PWD/makefilegeneratorplugin.cpp diff --git a/src/plugins/generator/makefilegenerator/makefilegenerator.qbs b/src/plugins/generator/makefilegenerator/makefilegenerator.qbs new file mode 100644 index 00000000..baabc43e --- /dev/null +++ b/src/plugins/generator/makefilegenerator/makefilegenerator.qbs @@ -0,0 +1,11 @@ +import qbs +import "../../qbsplugin.qbs" as QbsPlugin + +QbsPlugin { + name: "makefilegenerator" + files: [ + "makefilegenerator.cpp", + "makefilegenerator.h", + "makefilegeneratorplugin.cpp", + ] +} diff --git a/src/plugins/generator/makefilegenerator/makefilegeneratorplugin.cpp b/src/plugins/generator/makefilegenerator/makefilegeneratorplugin.cpp new file mode 100644 index 00000000..5789414b --- /dev/null +++ b/src/plugins/generator/makefilegenerator/makefilegeneratorplugin.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "makefilegenerator.h" + +#include +#include + +static void MakefileGeneratorPluginLoad() +{ + qbs::ProjectGeneratorManager::registerGenerator( + std::make_shared()); +} + +static void MakefileGeneratorPluginUnload() +{ +} + +#ifndef GENERATOR_EXPORT +#if defined(WIN32) || defined(_WIN32) +#define GENERATOR_EXPORT __declspec(dllexport) +#else +#define GENERATOR_EXPORT __attribute__((visibility("default"))) +#endif +#endif + +QBS_REGISTER_STATIC_PLUGIN(extern "C" GENERATOR_EXPORT, makefilegenerator, + MakefileGeneratorPluginLoad, MakefileGeneratorPluginUnload) diff --git a/src/plugins/generator/visualstudio/msbuildfiltersproject.cpp b/src/plugins/generator/visualstudio/msbuildfiltersproject.cpp new file mode 100644 index 00000000..a15e7fef --- /dev/null +++ b/src/plugins/generator/visualstudio/msbuildfiltersproject.cpp @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildfiltersproject.h" + +#include "msbuild/msbuilditemgroup.h" + +#include "msbuild/items/msbuildclcompile.h" +#include "msbuild/items/msbuildclinclude.h" +#include "msbuild/items/msbuildfilter.h" +#include "msbuild/items/msbuildnone.h" + +#include + +#include + +#include + +namespace qbs { + +static QStringList sourceFileExtensions() +{ + return {QStringLiteral("c"), QStringLiteral("C"), QStringLiteral("cpp"), + QStringLiteral("cxx"), QStringLiteral("c++"), QStringLiteral("cc"), + QStringLiteral("cs"), QStringLiteral("def"), QStringLiteral("java"), + QStringLiteral("m"), QStringLiteral("mm")}; +} + +static QStringList headerFileExtensions() +{ + return {QStringLiteral("h"), QStringLiteral("H"), QStringLiteral("hpp"), + QStringLiteral("hxx"), QStringLiteral("h++")}; +} + +static std::vector defaultItemGroupFilters(IMSBuildItemGroup *parent = nullptr) +{ + const auto sourceFilter = new MSBuildFilter(QStringLiteral("Source Files"), sourceFileExtensions(), parent); + const auto headerFilter = new MSBuildFilter(QStringLiteral("Header Files"), headerFileExtensions(), parent); + + const auto formFilter = new MSBuildFilter(QStringLiteral("Form Files"), + QStringList() << QStringLiteral("ui"), parent); + const auto resourceFilter = new MSBuildFilter(QStringLiteral("Resource Files"), + QStringList() + << QStringLiteral("qrc") + << QStringLiteral("rc") + << QStringLiteral("*"), parent); + resourceFilter->setParseFiles(false); + const auto generatedFilter = new MSBuildFilter(QStringLiteral("Generated Files"), + QStringList() << QStringLiteral("moc"), parent); + generatedFilter->setSourceControlFiles(false); + const auto translationFilter = new MSBuildFilter(QStringLiteral("Translation Files"), + QStringList() << QStringLiteral("ts"), parent); + translationFilter->setParseFiles(false); + + return std::vector { + sourceFilter, headerFilter, formFilter, resourceFilter, generatedFilter, translationFilter + }; +} + +static bool matchesFilter(const MSBuildFilter *filter, const QString &filePath) +{ + return filter->extensions().contains(QFileInfo(filePath).completeSuffix()); +} + +MSBuildFiltersProject::MSBuildFiltersProject(const GeneratableProductData &product, + QObject *parent) + : MSBuildProject(parent) +{ + // Normally this would be versionInfo.toolsVersion() but for some reason it seems + // filters projects are always v4.0 + setToolsVersion(QStringLiteral("4.0")); + + const auto itemGroup = new MSBuildItemGroup(this); + const auto filterOptions = defaultItemGroupFilters(); + for (const auto options : filterOptions) { + const auto filter = new MSBuildFilter(options->include(), options->extensions(), itemGroup); + filter->appendProperty(QStringLiteral("ParseFiles"), options->parseFiles()); + filter->appendProperty(QStringLiteral("SourceControlFiles"), options->sourceControlFiles()); + } + + Internal::Set allFiles; + const auto productDatas = product.data.values(); + for (const auto &productData : productDatas) { + for (const auto &groupData : productData.groups()) + if (groupData.isEnabled()) + allFiles.unite(Internal::Set::fromList(groupData.allFilePaths())); + } + + MSBuildItemGroup *headerFilesGroup = nullptr; + MSBuildItemGroup *sourceFilesGroup = nullptr; + MSBuildItemGroup *filesGroup = nullptr; + + for (const auto &filePath : allFiles) { + MSBuildFileItem *fileItem = nullptr; + + for (const MSBuildFilter *options : filterOptions) { + if (matchesFilter(options, filePath)) { + if (options->include() == QStringLiteral("Header Files")) { + if (!headerFilesGroup) + headerFilesGroup = new MSBuildItemGroup(this); + fileItem = new MSBuildClInclude(headerFilesGroup); + } else if (options->include() == QStringLiteral("Source Files")) { + if (!sourceFilesGroup) + sourceFilesGroup = new MSBuildItemGroup(this); + fileItem = new MSBuildClCompile(sourceFilesGroup); + } + + if (fileItem) { + fileItem->setFilterName(options->include()); + break; + } + } + } + + if (!fileItem) { + if (!filesGroup) + filesGroup = new MSBuildItemGroup(this); + fileItem = new MSBuildNone(filesGroup); + } + fileItem->setFilePath(filePath); + } + + qDeleteAll(filterOptions); +} + +} // namespace qbs diff --git a/src/plugins/generator/visualstudio/msbuildfiltersproject.h b/src/plugins/generator/visualstudio/msbuildfiltersproject.h new file mode 100644 index 00000000..5d67bfaa --- /dev/null +++ b/src/plugins/generator/visualstudio/msbuildfiltersproject.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDFILTERSPROJECT_H +#define MSBUILDFILTERSPROJECT_H + +#include "msbuild/msbuildproject.h" + +#include +#include + +namespace qbs { + +class MSBuildFilter; + +class MSBuildFiltersProject : public MSBuildProject +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildFiltersProject) +public: + explicit MSBuildFiltersProject(const GeneratableProductData &product, + QObject *parent = nullptr); +}; + +} // namespace qbs + +#endif // MSBUILDFILTERSPROJECT_H diff --git a/src/plugins/generator/visualstudio/msbuildqbsgenerateproject.cpp b/src/plugins/generator/visualstudio/msbuildqbsgenerateproject.cpp new file mode 100644 index 00000000..24fc991a --- /dev/null +++ b/src/plugins/generator/visualstudio/msbuildqbsgenerateproject.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildqbsgenerateproject.h" + +#include "msbuild/msbuildimport.h" +#include "msbuild/msbuildproperty.h" +#include "msbuild/msbuildpropertygroup.h" + +#include +#include +#include + +namespace qbs { + +MSBuildQbsGenerateProject::MSBuildQbsGenerateProject( + const GeneratableProject &project, + const Internal::VisualStudioVersionInfo &versionInfo, + VisualStudioGenerator *parent) + : MSBuildTargetProject(project, versionInfo, parent) +{ + const auto cppDefaultProps = new MSBuildImport(this); + cppDefaultProps->setProject(QStringLiteral("$(VCTargetsPath)\\Microsoft.Cpp.Default.props")); + + const auto group = new MSBuildPropertyGroup(this); + group->setLabel(QStringLiteral("Configuration")); + group->appendProperty(QStringLiteral("PlatformToolset"), + versionInfo.platformToolsetVersion()); + group->appendProperty(QStringLiteral("ConfigurationType"), + QStringLiteral("Makefile")); + const auto params = Internal::shellQuote(project.commandLine(), + Internal::HostOsInfo::HostOsWindows); + group->appendProperty(QStringLiteral("NMakeBuildCommandLine"), + QStringLiteral("$(QbsGenerateCommandLine) ") + params); + + const auto cppProps = new MSBuildImport(this); + cppProps->setProject(QStringLiteral("$(VCTargetsPath)\\Microsoft.Cpp.props")); + + const auto import = new MSBuildImport(this); + import->setProject(QStringLiteral("$(VCTargetsPath)\\Microsoft.Cpp.targets")); +} + +} // namespace qbs diff --git a/src/plugins/generator/visualstudio/msbuildqbsgenerateproject.h b/src/plugins/generator/visualstudio/msbuildqbsgenerateproject.h new file mode 100644 index 00000000..8c5ef80c --- /dev/null +++ b/src/plugins/generator/visualstudio/msbuildqbsgenerateproject.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDQBSGENERATEPROJECT_H +#define MSBUILDQBSGENERATEPROJECT_H + +#include "msbuildtargetproject.h" + +#include +#include + +namespace qbs { + +class MSBuildQbsGenerateProject : public MSBuildTargetProject +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildQbsGenerateProject) +public: + MSBuildQbsGenerateProject(const GeneratableProject &project, + const Internal::VisualStudioVersionInfo &versionInfo, + VisualStudioGenerator *parent = nullptr); +}; + +} // namespace qbs + +#endif // MSBUILDQBSGENERATEPROJECT_H diff --git a/src/plugins/generator/visualstudio/msbuildqbsproductproject.cpp b/src/plugins/generator/visualstudio/msbuildqbsproductproject.cpp new file mode 100644 index 00000000..6f77212d --- /dev/null +++ b/src/plugins/generator/visualstudio/msbuildqbsproductproject.cpp @@ -0,0 +1,428 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildqbsproductproject.h" + +#include "msbuild/msbuildimport.h" +#include "msbuild/msbuildimportgroup.h" +#include "msbuild/msbuilditemdefinitiongroup.h" +#include "msbuild/msbuilditemgroup.h" +#include "msbuild/msbuilditemmetadata.h" +#include "msbuild/msbuildproperty.h" +#include "msbuild/msbuildpropertygroup.h" + +#include "msbuild/items/msbuildclcompile.h" +#include "msbuild/items/msbuildclinclude.h" +#include "msbuild/items/msbuildlink.h" +#include "msbuild/items/msbuildnone.h" + +#include "msbuildutils.h" +#include "visualstudiogenerator.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace qbs { +using namespace Internal; + +MSBuildQbsProductProject::MSBuildQbsProductProject( + const GeneratableProject &project, + const GeneratableProductData &product, + const Internal::VisualStudioVersionInfo &versionInfo, + VisualStudioGenerator *parent) + : MSBuildTargetProject(project, versionInfo, parent) +{ + Q_ASSERT(project.projects.size() == project.commandLines.size()); + Q_ASSERT(project.projects.size() == product.data.size()); + + const int count = std::max(project.projects.size(), product.data.size()); + + globalsPropertyGroup()->appendProperty(QStringLiteral("QbsProductName"), product.name()); + + const auto cppDefaultProps = new MSBuildImport(this); + cppDefaultProps->setProject(QStringLiteral("$(VCTargetsPath)\\Microsoft.Cpp.Default.props")); + + for (int i = 0; i < count; ++i) { + addConfiguration( + project, + project.projects.values().at(i), + product.data.values().at(i), + project.commandLines.values().at(i)); + } + + const auto cppProps = new MSBuildImport(this); + cppProps->setProject(QStringLiteral("$(VCTargetsPath)\\Microsoft.Cpp.props")); + + for (int i = 0; i < count; ++i) + addItemDefGroup(project.projects.values().at(i), product.data.values().at(i)); + + addFiles(project, product); +} + +static QString productTargetPath(const qbs::ProductData &productData) +{ + const QString fullPath = productData.targetExecutable(); + if (!fullPath.isEmpty()) + return QFileInfo(fullPath).absolutePath(); + return productData.properties().value(QStringLiteral("buildDirectory")).toString(); +} + +static bool listEnvironmentVariableContainsValue(const QString &environmentVariable, + const QString &value) +{ + return environmentVariable.contains(QLatin1Char(';') + value + QLatin1Char(';')) + || environmentVariable.startsWith(value + QLatin1Char(';')) + || environmentVariable.endsWith(QLatin1Char(';') + value); +} + +void MSBuildQbsProductProject::addConfiguration(const GeneratableProject &project, + const Project &buildTask, + const ProductData &productData, + const QStringList &buildConfigurationCommandLine) +{ + const auto targetDir = Internal::PathUtils::toNativeSeparators( + productTargetPath(productData), Internal::HostOsInfo::HostOsWindows); + + auto configurationDir = Internal::PathUtils::toNativeSeparators( + project.baseBuildDirectory().absolutePath() + + QLatin1Char('\\') + + MSBuildUtils::configurationName(buildTask), + Internal::HostOsInfo::HostOsWindows); + auto relativeTargetDir = targetDir; + if (targetDir.startsWith(configurationDir)) + relativeTargetDir = QStringLiteral("$(SolutionDir)$(Configuration)") + + relativeTargetDir.mid(configurationDir.size()); + + const auto &properties = productData.moduleProperties(); + + const bool debugBuild = properties.getModuleProperty(StringConstants::qbsModule(), + QStringLiteral("debugInformation")) + .toBool(); + + const auto includePaths = QStringList() + << properties.getModulePropertiesAsStringList(QStringLiteral("cpp"), + QStringLiteral("includePaths")) + << properties.getModulePropertiesAsStringList(QStringLiteral("cpp"), + QStringLiteral("systemIncludePaths")); + const auto cppDefines = properties + .getModulePropertiesAsStringList(QStringLiteral("cpp"), QStringLiteral("defines")); + + const auto sep = Internal::HostOsInfo::pathListSeparator(Internal::HostOsInfo::HostOsWindows); + + const auto propertyGroup1 = new MSBuildPropertyGroup(this); + propertyGroup1->setCondition(MSBuildUtils::buildTaskCondition(buildTask)); + propertyGroup1->setLabel(QStringLiteral("Configuration")); + propertyGroup1->appendProperty(QStringLiteral("UseDebugLibraries"), + debugBuild ? QStringLiteral("true") : QStringLiteral("false")); + + // General - General + // We need a trailing backslash for $(OutDir); See also the VS documentation: + // https://docs.microsoft.com/en-us/cpp/ide/common-macros-for-build-commands-and-properties + propertyGroup1->appendProperty(QStringLiteral("OutDir"), relativeTargetDir + QLatin1Char('\\')); + propertyGroup1->appendProperty(QStringLiteral("TargetName"), productData.targetName()); + propertyGroup1->appendProperty(QStringLiteral("PlatformToolset"), + versionInfo().platformToolsetVersion()); + propertyGroup1->appendProperty(QStringLiteral("ConfigurationType"), QStringLiteral("Makefile")); + + // VS possible values: Unicode|MultiByte|NotSet + propertyGroup1->appendProperty(QStringLiteral("CharacterSet"), + properties.getModuleProperty(QStringLiteral("cpp"), + QStringLiteral("windowsApiCharacterSet")) == QStringLiteral("unicode") + ? QStringLiteral("MultiByte") : QStringLiteral("NotSet")); + + // Debugging + propertyGroup1->appendProperty(QStringLiteral("DebuggerFlavor"), + QStringLiteral("WindowsLocalDebugger")); + propertyGroup1->appendProperty(QStringLiteral("LocalDebuggerCommand"), + QStringLiteral("$(OutDir)$(TargetName)$(TargetExt)")); + propertyGroup1->appendProperty(QStringLiteral("LocalDebuggerWorkingDirectory"), + QStringLiteral("$(OutDir)")); + + auto env = buildTask.getRunEnvironment(productData, project.installOptions, + QProcessEnvironment(), QStringList(), nullptr) + .runEnvironment(); + if (!env.isEmpty()) { + const auto systemEnv = QProcessEnvironment::systemEnvironment(); + const auto keys = systemEnv.keys(); + for (const auto &key : keys) { + if (!env.contains(key)) + continue; + + // Don't duplicate keys from the system environment + if (env.value(key) == systemEnv.value(key)) { + env.remove(key); + continue; + } + + // Cleverly concatenate list variables to avoid duplicating system environment + const QString systemValue = systemEnv.value(key); + QString overriddenValue = env.value(key); + if (listEnvironmentVariableContainsValue(overriddenValue, systemValue)) { + env.insert(key, overriddenValue.replace(systemValue, + QLatin1Char('%') + key + QLatin1Char('%'))); + } + + QString installRoot = project.installOptions.installRoot(); + if (!installRoot.isEmpty()) { + if (listEnvironmentVariableContainsValue(overriddenValue, installRoot)) { + env.insert(key, overriddenValue.replace(installRoot, + QStringLiteral("$(QbsInstallRoot)"))); + } + } else { + installRoot = Internal::PathUtils::toNativeSeparators( + QDir(buildTask.projectData().buildDirectory()).absoluteFilePath( + project.installOptions.defaultInstallRoot()), + Internal::HostOsInfo::HostOsWindows); + if (listEnvironmentVariableContainsValue(overriddenValue, installRoot)) { + env.insert(key, overriddenValue.replace(installRoot, + QStringLiteral("$(SolutionDir)$(Configuration)\\install-root"))); + } + } + } + + propertyGroup1->appendProperty(QStringLiteral("LocalDebuggerEnvironment"), + env.toStringList().join(QLatin1String("\n"))); + } + + // NMake - General + // Skip configuration name, that's handled in qbs-shared.props + const auto params = Internal::shellQuote(buildConfigurationCommandLine.mid(1), + Internal::HostOsInfo::HostOsWindows); + propertyGroup1->appendProperty(QStringLiteral("NMakeBuildCommandLine"), + QStringLiteral("$(QbsBuildCommandLine) ") + params); + propertyGroup1->appendProperty(QStringLiteral("NMakeReBuildCommandLine"), + QStringLiteral("$(QbsReBuildCommandLine) ") + params); + propertyGroup1->appendProperty(QStringLiteral("NMakeCleanCommandLine"), + QStringLiteral("$(QbsCleanCommandLine) ") + params); + propertyGroup1->appendProperty(QStringLiteral("NMakeOutput"), + QStringLiteral("$(OutDir)$(TargetName)$(TargetExt)")); + + // NMake - IntelliSense + propertyGroup1->appendProperty(QStringLiteral("NMakePreprocessorDefinitions"), + cppDefines.join(sep)); + propertyGroup1->appendProperty(QStringLiteral("NMakeIncludeSearchPath"), + includePaths.join(sep)); +} + +static QString subsystemVersion(const QString &version) +{ + const auto v = Version::fromString(version); + return QStringLiteral("%1.%2").arg( + QString::number(v.majorVersion()), + QString::number(v.minorVersion()).rightJustified(2, QLatin1Char('0'))); +} + +void MSBuildQbsProductProject::addItemDefGroup(const Project &project, + const ProductData &productData) +{ + const auto &properties = productData.moduleProperties(); + + const bool consoleApp = productData.properties().value(QStringLiteral("consoleApplication")) + .toBool(); + const bool debugBuild = properties.getModuleProperty(StringConstants::qbsModule(), + QStringLiteral("debugInformation")) + .toBool(); + const auto optimizationLevel = properties.getModuleProperty(StringConstants::qbsModule(), + QStringLiteral("optimization")) + .toString(); + const auto warningLevel = properties.getModuleProperty(StringConstants::qbsModule(), + QStringLiteral("warningLevel")) + .toString(); + + const auto includePaths = QStringList() + << properties.getModulePropertiesAsStringList(QStringLiteral("cpp"), + QStringLiteral("includePaths")) + << properties.getModulePropertiesAsStringList(QStringLiteral("cpp"), + QStringLiteral("systemIncludePaths")); + const auto cppDefines = properties.getModulePropertiesAsStringList( + QStringLiteral("cpp"), QStringLiteral("defines")); + + const auto sep = Internal::HostOsInfo::pathListSeparator(Internal::HostOsInfo::HostOsWindows); + + const auto itemDefGroup = new MSBuildItemDefinitionGroup(this); + itemDefGroup->setCondition(MSBuildUtils::buildTaskCondition(project)); + + const auto compile = new MSBuildClCompile(itemDefGroup); + + // C++ - General + compile->appendProperty(QStringLiteral("AdditionalIncludeDirectories"), + includePaths.join(sep) + + sep + + QStringLiteral("%(AdditionalIncludeDirectories)")); + if (warningLevel == QStringLiteral("none")) + compile->appendProperty(QStringLiteral("WarningLevel"), + QStringLiteral("TurnOffAllWarnings")); + else if (warningLevel == QStringLiteral("all")) + compile->appendProperty(QStringLiteral("WarningLevel"), + QStringLiteral("EnableAllWarnings")); + else + compile->appendProperty(QStringLiteral("WarningLevel"), + QStringLiteral("Level3")); // this is VS default. + + // C++ - Optimization + compile->appendProperty(QStringLiteral("Optimization"), + optimizationLevel == QStringLiteral("none") + ? QStringLiteral("Disabled") + : QStringLiteral("MaxSpeed")); + + // C++ - Preprocessor + compile->appendProperty(QStringLiteral("PreprocessorDefinitions"), + cppDefines.join(sep) + + sep + + QStringLiteral("%(PreprocessorDefinitions)")); + + // C++ - Code Generation + compile->appendProperty(QStringLiteral("RuntimeLibrary"), debugBuild + ? QStringLiteral("MultiThreadedDebugDLL") + : QStringLiteral("MultiThreadedDLL")); + + const auto link = new MSBuildLink(itemDefGroup); + + // Linker - General + link->appendProperty(QStringLiteral("AdditionalLibraryDirectories"), + properties.getModulePropertiesAsStringList(QStringLiteral("cpp"), + QStringLiteral("libraryPaths")).join(sep)); + + // Linker - Input + link->appendProperty(QStringLiteral("AdditionalDependencies"), + properties.getModulePropertiesAsStringList(QStringLiteral("cpp"), + QStringLiteral("staticLibraries")).join(sep) + + sep + QStringLiteral("%(AdditionalDependencies)")); + + // Linker - Debugging + link->appendProperty(QStringLiteral("GenerateDebugInformation"), + debugBuild ? QStringLiteral("true") : QStringLiteral("false")); + + // Linker - System + link->appendProperty(QStringLiteral("SubSystem"), + consoleApp ? QStringLiteral("Console") : QStringLiteral("Windows")); + const auto subsysVersion = properties.getModuleProperty( + QStringLiteral("cpp"), QStringLiteral("minimumWindowsVersion")).toString(); + if (!subsysVersion.isEmpty()) + link->appendProperty(QStringLiteral("MinimumRequiredVersion"), + subsystemVersion(subsysVersion)); + + // Linker - Optimization + link->appendProperty(QStringLiteral("OptimizeReferences"), + debugBuild ? QStringLiteral("false") : QStringLiteral("true")); +} + +static MSBuildFileItem *fileItemForFileTags(const QList &fileTags, + IMSBuildItemGroup *parent = nullptr) +{ + if (fileTags.contains(QStringLiteral("hpp"))) + return new MSBuildClInclude(parent); + if (fileTags.contains(QStringLiteral("c")) || fileTags.contains(QStringLiteral("cpp"))) + return new MSBuildClCompile(parent); + return new MSBuildNone(parent); +} + +void MSBuildQbsProductProject::addFiles(const GeneratableProject &project, + const GeneratableProductData &product) +{ + const auto itemGroup = new MSBuildItemGroup(this); + + addQbsFile(project, product, itemGroup); + + std::map sourceFileNodes; + std::map sourceFileEnabledConfigurations; + + // Create a ClCompile item for each source file, keeping track of which configurations that + // file's containing group is enabled in + QMapIterator productDataIt(product.data); + while (productDataIt.hasNext()) { + productDataIt.next(); + const auto groups = productDataIt.value().groups(); + for (const auto &group : groups) { + const auto sourceArtifacts = group.allSourceArtifacts(); + for (const auto &sourceArtifact : sourceArtifacts) { + const auto filePath = sourceArtifact.filePath(); + if (sourceFileNodes.find(filePath) == sourceFileNodes.end()) { + sourceFileNodes.insert({ + filePath, + fileItemForFileTags(sourceArtifact.fileTags(), itemGroup) + }); + } + auto fileItem = sourceFileNodes[filePath]; + QString path = project.baseBuildDirectory().relativeFilePath(filePath); + // The path still might not be relative (for example if the file item is + // located on a different drive) + if (QFileInfo(path).isRelative()) + path = QStringLiteral("$(ProjectDir)") + path; + fileItem->setFilePath(path); + if (group.isEnabled()) + sourceFileEnabledConfigurations[filePath] << productDataIt.key(); + } + } + } + + // Add ExcludedFromBuild item metadata to each file for each configuration + // where that file's containing group is disabled + for (const auto &sourceFileNode : sourceFileNodes) { + QMapIterator projIt(project.projects); + while (projIt.hasNext()) { + projIt.next(); + if (!sourceFileEnabledConfigurations[sourceFileNode.first].contains(projIt.key())) { + const auto metadata = new MSBuildItemMetadata( + QStringLiteral("ExcludedFromBuild"), + QStringLiteral("true"), + sourceFileNode.second); + metadata->setCondition(QStringLiteral("'$(Configuration)|$(Platform)'=='") + + MSBuildUtils::fullName(projIt.value()) + + QStringLiteral("'")); + } + } + } + + const auto import = new MSBuildImport(this); + import->setProject(QStringLiteral("$(VCTargetsPath)\\Microsoft.Cpp.targets")); +} + +void MSBuildQbsProductProject::addQbsFile(const GeneratableProject &project, + const GeneratableProductData &product, + MSBuildItemGroup *itemGroup) +{ + const auto fileItem = new MSBuildNone(itemGroup); + QString path = project.baseBuildDirectory().relativeFilePath(product.location().filePath()); + // The path still might not be relative (for example if the file item is + // located on a different drive) + if (QFileInfo(path).isRelative()) + path = QStringLiteral("$(ProjectDir)") + path; + fileItem->setFilePath(path); +} + + +} // namespace qbs diff --git a/src/plugins/generator/visualstudio/msbuildqbsproductproject.h b/src/plugins/generator/visualstudio/msbuildqbsproductproject.h new file mode 100644 index 00000000..f1a815f3 --- /dev/null +++ b/src/plugins/generator/visualstudio/msbuildqbsproductproject.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDQBSPRODUCTPROJECT_H +#define MSBUILDQBSPRODUCTPROJECT_H + +#include "msbuildtargetproject.h" + +#include +#include + +#include + +namespace qbs { + +class MSBuildImportGroup; +class MSBuildItemGroup; +class MSBuildProperty; + +class VisualStudioGenerator; + +class MSBuildQbsProductProject : public MSBuildTargetProject +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildQbsProductProject) +public: + MSBuildQbsProductProject(const GeneratableProject &project, + const GeneratableProductData &product, + const Internal::VisualStudioVersionInfo &versionInfo, + VisualStudioGenerator *parent = nullptr); + +private: + using ProjectConfigurations = QHash>; + + void addConfiguration(const GeneratableProject &project, const Project &buildTask, + const ProductData &productData, + const QStringList &buildConfigurationCommandLine); + void addItemDefGroup(const Project &project, + const ProductData &productData); + void addFiles(const GeneratableProject &project, const GeneratableProductData &product); + void addQbsFile(const GeneratableProject &project, const GeneratableProductData &product, + MSBuildItemGroup *itemGroup); +}; + +} // namespace qbs + +#endif // MSBUILDQBSPRODUCTPROJECT_H diff --git a/src/plugins/generator/visualstudio/msbuildsharedsolutionpropertiesproject.cpp b/src/plugins/generator/visualstudio/msbuildsharedsolutionpropertiesproject.cpp new file mode 100644 index 00000000..878ebbcd --- /dev/null +++ b/src/plugins/generator/visualstudio/msbuildsharedsolutionpropertiesproject.cpp @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildsharedsolutionpropertiesproject.h" + +#include "msbuild/msbuildpropertygroup.h" + +#include +#include + +#include + +namespace qbs { + +static QString qbsCommandLine(const GeneratableProject &project, + const QString &subCommand, + const QString &qbsSettingsDir, + const Internal::VisualStudioVersionInfo &versionInfo) +{ + auto addEnvironmentVariableArgument = [](Internal::CommandLine &cl, const QString &var, + const QString &prefix = QString()) { + cl.appendRawArgument(QStringLiteral("\"%1$(%2)\"").arg(prefix, var)); + }; + + auto realSubCommand = subCommand; + if (subCommand == QStringLiteral("rebuild")) + realSubCommand = QStringLiteral("build"); + + // "path/to/qbs.exe" {build|clean} + // --settings-dir "path/to/settings/directory/" + // -f "path/to/project.qbs" -d "/build/directory/" + // -p product_name [[configuration key:value]...] + Internal::CommandLine commandLine; + commandLine.setProgram(QStringLiteral("\"$(QbsExecutablePath)\""), true); + commandLine.appendArgument(realSubCommand); + + if (!qbsSettingsDir.isEmpty()) { + commandLine.appendArgument(QStringLiteral("--settings-dir")); + addEnvironmentVariableArgument(commandLine, QStringLiteral("QbsSettingsDir")); + } + + commandLine.appendArgument(QStringLiteral("-f")); + addEnvironmentVariableArgument(commandLine, QStringLiteral("QbsProjectFile")); + commandLine.appendArgument(QStringLiteral("-d")); + addEnvironmentVariableArgument(commandLine, QStringLiteral("QbsBuildDir")); + + if (subCommand == QStringLiteral("generate")) { + commandLine.appendArgument(QStringLiteral("-g")); + commandLine.appendArgument(QStringLiteral("visualstudio%1") + .arg(versionInfo.marketingVersion())); + } else { + commandLine.appendArgument(QStringLiteral("-p")); + addEnvironmentVariableArgument(commandLine, QStringLiteral("QbsProductName")); + + commandLine.appendArgument(QStringLiteral("--wait-lock")); + } + + if (realSubCommand == QStringLiteral("build") + && !project.installOptions.installRoot().isEmpty()) { + commandLine.appendArgument(QStringLiteral("--install-root")); + addEnvironmentVariableArgument(commandLine, QStringLiteral("QbsInstallRoot")); + } + + if (realSubCommand == QStringLiteral("build") && subCommand == QStringLiteral("rebuild")) { + commandLine.appendArgument(QStringLiteral("--check-timestamps")); + commandLine.appendArgument(QStringLiteral("--force-probe-execution")); + } + + addEnvironmentVariableArgument(commandLine, QStringLiteral("Configuration"), + QStringLiteral("config:")); + + return commandLine.toCommandLine(Internal::HostOsInfo::HostOsWindows); +} + +MSBuildSharedSolutionPropertiesProject::MSBuildSharedSolutionPropertiesProject( + const Internal::VisualStudioVersionInfo &versionInfo, + const GeneratableProject &project, + const QFileInfo &qbsExecutable, + const QString &qbsSettingsDir) +{ + setDefaultTargets(QStringLiteral("Build")); + setToolsVersion(versionInfo.toolsVersion()); + + const auto group = new MSBuildPropertyGroup(this); + group->setLabel(QStringLiteral("UserMacros")); + + // Order's important here... a variable must be listed before one that uses it + group->appendProperty(QStringLiteral("QbsExecutablePath"), + QStringLiteral("$(QbsExecutableDir)") + qbsExecutable.fileName()); + if (!project.installOptions.installRoot().isEmpty()) { + group->appendProperty(QStringLiteral("QbsInstallRoot"), + Internal::PathUtils::toNativeSeparators( + project.installOptions.installRoot(), + Internal::HostOsInfo::HostOsWindows)); + } + + group->appendProperty(QStringLiteral("QbsProjectFile"), + QStringLiteral("$(QbsProjectDir)") + + project.filePath().fileName()); + + // Trailing '.' is not a typo. It prevents the trailing slash from combining with the closing + // quote to form an escape sequence. Unfortunately, Visual Studio expands variables *before* + // passing them to the underlying command shell, so there's not much we can do with regard to + // doing it "properly". Setting environment variables through MSBuild and using them in place + // of actual arguments does not work either, as Visual Studio apparently expands the environment + // variables as well, before passing them to the underlying shell. + group->appendProperty(QStringLiteral("QbsBuildDir"), + QStringLiteral("$(SolutionDir).")); + + group->appendProperty(QStringLiteral("QbsBuildCommandLine"), + qbsCommandLine(project, QStringLiteral("build"), + qbsSettingsDir, versionInfo)); + group->appendProperty(QStringLiteral("QbsReBuildCommandLine"), + qbsCommandLine(project, QStringLiteral("rebuild"), + qbsSettingsDir, versionInfo)); + group->appendProperty(QStringLiteral("QbsCleanCommandLine"), + qbsCommandLine(project, QStringLiteral("clean"), + qbsSettingsDir, versionInfo)); + group->appendProperty(QStringLiteral("QbsGenerateCommandLine"), + qbsCommandLine(project, QStringLiteral("generate"), + qbsSettingsDir, versionInfo)); +} + +} // namespace qbs diff --git a/src/plugins/generator/visualstudio/msbuildsharedsolutionpropertiesproject.h b/src/plugins/generator/visualstudio/msbuildsharedsolutionpropertiesproject.h new file mode 100644 index 00000000..f3848ce2 --- /dev/null +++ b/src/plugins/generator/visualstudio/msbuildsharedsolutionpropertiesproject.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDSHAREDSOLUTIONPROPERTIESPROJECT_H +#define MSBUILDSHAREDSOLUTIONPROPERTIESPROJECT_H + +#include "msbuild/msbuildproject.h" + +#include +#include + +namespace qbs { + +class MSBuildSharedSolutionPropertiesProject : public MSBuildProject +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildSharedSolutionPropertiesProject) +public: + MSBuildSharedSolutionPropertiesProject(const Internal::VisualStudioVersionInfo &versionInfo, + const GeneratableProject &project, + const QFileInfo &qbsExecutable, const QString &qbsSettingsDir); +}; + +} // namespace qbs + +#endif // MSBUILDSHAREDSOLUTIONPROPERTIESPROJECT_H diff --git a/src/plugins/generator/visualstudio/msbuildsolutionpropertiesproject.cpp b/src/plugins/generator/visualstudio/msbuildsolutionpropertiesproject.cpp new file mode 100644 index 00000000..ea5546c7 --- /dev/null +++ b/src/plugins/generator/visualstudio/msbuildsolutionpropertiesproject.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildsolutionpropertiesproject.h" + +#include "msbuild/msbuildpropertygroup.h" + +#include + +#include + +namespace qbs { + +MSBuildSolutionPropertiesProject::MSBuildSolutionPropertiesProject( + const Internal::VisualStudioVersionInfo &versionInfo, + const GeneratableProject &project, + const QFileInfo &qbsExecutable, + const QString &qbsSettingsDir) +{ + setDefaultTargets(QStringLiteral("Build")); + setToolsVersion(versionInfo.toolsVersion()); + + const auto group = new MSBuildPropertyGroup(this); + group->setLabel(QStringLiteral("UserMacros")); + + static const auto win = Internal::HostOsInfo::HostOsWindows; + + group->appendProperty(QStringLiteral("QbsExecutableDir"), + Internal::PathUtils::toNativeSeparators(qbsExecutable.path(), win) + + Internal::HostOsInfo::pathSeparator(win)); + group->appendProperty(QStringLiteral("QbsProjectDir"), + Internal::PathUtils::toNativeSeparators(project.filePath().path(), win) + + Internal::HostOsInfo::pathSeparator(win)); + + if (!qbsSettingsDir.isEmpty()) { + group->appendProperty(QStringLiteral("QbsSettingsDir"), + Internal::PathUtils::toNativeSeparators(qbsSettingsDir, win) + + Internal::HostOsInfo::pathSeparator(win) + QLatin1Char('.')); + } +} + +} // namespace qbs diff --git a/src/plugins/generator/visualstudio/msbuildsolutionpropertiesproject.h b/src/plugins/generator/visualstudio/msbuildsolutionpropertiesproject.h new file mode 100644 index 00000000..1c49f790 --- /dev/null +++ b/src/plugins/generator/visualstudio/msbuildsolutionpropertiesproject.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDSOLUTIONPROPERTIESPROJECT_H +#define MSBUILDSOLUTIONPROPERTIESPROJECT_H + +#include "msbuild/msbuildproject.h" + +#include +#include + +#include + +namespace qbs { + +class MSBuildSolutionPropertiesProject : public MSBuildProject +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildSolutionPropertiesProject) +public: + MSBuildSolutionPropertiesProject(const Internal::VisualStudioVersionInfo &versionInfo, + const GeneratableProject &project, + const QFileInfo &qbsExecutable, + const QString &qbsSettingsDir); +}; + +} // namespace qbs + +#endif // MSBUILDSOLUTIONPROPERTIESPROJECT_H diff --git a/src/plugins/generator/visualstudio/msbuildtargetproject.cpp b/src/plugins/generator/visualstudio/msbuildtargetproject.cpp new file mode 100644 index 00000000..bcd65406 --- /dev/null +++ b/src/plugins/generator/visualstudio/msbuildtargetproject.cpp @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildtargetproject.h" +#include "msbuildutils.h" +#include "visualstudiogenerator.h" + +#include "msbuild/msbuildimport.h" +#include "msbuild/msbuildimportgroup.h" +#include "msbuild/msbuilditem.h" +#include "msbuild/msbuilditemgroup.h" +#include "msbuild/msbuildproperty.h" +#include "msbuild/msbuildpropertygroup.h" + +namespace qbs { + +class MSBuildTargetProjectPrivate +{ +public: + MSBuildTargetProjectPrivate(const Internal::VisualStudioVersionInfo &versionInfo) + : versionInfo(versionInfo) {} + MSBuildPropertyGroup *globalsPropertyGroup = nullptr; + MSBuildProperty *projectGuidProperty = nullptr; + const Internal::VisualStudioVersionInfo &versionInfo; +}; + +MSBuildTargetProject::MSBuildTargetProject(const GeneratableProject &project, + const Internal::VisualStudioVersionInfo &versionInfo, + VisualStudioGenerator *parent) + : MSBuildProject(parent) + , d(new MSBuildTargetProjectPrivate(versionInfo)) +{ + setDefaultTargets(QStringLiteral("Build")); + setToolsVersion(versionInfo.toolsVersion()); + + const auto projectConfigurationsGroup = new MSBuildItemGroup(this); + projectConfigurationsGroup->setLabel(QStringLiteral("ProjectConfigurations")); + + QMapIterator it(project.projects); + while (it.hasNext()) { + it.next(); + const auto item = new MSBuildItem(QStringLiteral("ProjectConfiguration"), + projectConfigurationsGroup); + item->setInclude(MSBuildUtils::fullName(it.value())); + item->appendProperty(QStringLiteral("Configuration"), it.key()); + item->appendProperty(QStringLiteral("Platform"), MSBuildUtils::platform(it.value())); + } + + d->globalsPropertyGroup = new MSBuildPropertyGroup(this); + d->globalsPropertyGroup->setLabel(QStringLiteral("Globals")); + d->projectGuidProperty = new MSBuildProperty(QStringLiteral("ProjectGuid"), + QUuid::createUuid().toString(), + d->globalsPropertyGroup); + + // Trigger creation of the property sheets ImportGroup + propertySheetsImportGroup(); +} + +MSBuildTargetProject::~MSBuildTargetProject() = default; + +const Internal::VisualStudioVersionInfo &MSBuildTargetProject::versionInfo() const +{ + return d->versionInfo; +} + +QUuid MSBuildTargetProject::guid() const +{ + return {d->projectGuidProperty->value().toString()}; +} + +void MSBuildTargetProject::setGuid(const QUuid &guid) +{ + d->projectGuidProperty->setValue(guid.toString()); +} + +MSBuildPropertyGroup *MSBuildTargetProject::globalsPropertyGroup() +{ + return d->globalsPropertyGroup; +} + +MSBuildImportGroup *MSBuildTargetProject::propertySheetsImportGroup() +{ + MSBuildImportGroup *importGroup = nullptr; + for (const auto &child : children()) { + if (auto group = qobject_cast(child)) { + if (group->label() == QStringLiteral("PropertySheets")) { + importGroup = group; + break; + } + } + } + + if (!importGroup) { + importGroup = new MSBuildImportGroup(this); + importGroup->setLabel(QStringLiteral("PropertySheets")); + } + + return importGroup; +} + +void MSBuildTargetProject::appendPropertySheet(const QString &path, bool optional) +{ + const auto import = new MSBuildImport(propertySheetsImportGroup()); + import->setProject(path); + if (optional) + import->setCondition(QStringLiteral("Exists('%1')").arg(path)); +} + +} // namespace qbs diff --git a/src/plugins/generator/visualstudio/msbuildtargetproject.h b/src/plugins/generator/visualstudio/msbuildtargetproject.h new file mode 100644 index 00000000..496441a8 --- /dev/null +++ b/src/plugins/generator/visualstudio/msbuildtargetproject.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDTARGETPROJECT_H +#define MSBUILDTARGETPROJECT_H + +#include "msbuild/msbuildproject.h" + +#include +#include + +namespace qbs { + +class MSBuildImportGroup; +class MSBuildPropertyGroup; +class MSBuildTargetProjectPrivate; +class VisualStudioGenerator; + +class MSBuildTargetProject : public MSBuildProject +{ + Q_OBJECT + Q_DISABLE_COPY(MSBuildTargetProject) +protected: + MSBuildTargetProject(const GeneratableProject &project, + const Internal::VisualStudioVersionInfo &versionInfo, + VisualStudioGenerator *parent = nullptr); + +public: + ~MSBuildTargetProject() override; + + const Internal::VisualStudioVersionInfo &versionInfo() const; + + QUuid guid() const; + void setGuid(const QUuid &guid); + + MSBuildPropertyGroup *globalsPropertyGroup(); + MSBuildImportGroup *propertySheetsImportGroup(); + void appendPropertySheet(const QString &path, bool optional = false); + +private: + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // MSBUILDTARGETPROJECT_H diff --git a/src/plugins/generator/visualstudio/msbuildutils.h b/src/plugins/generator/visualstudio/msbuildutils.h new file mode 100644 index 00000000..3eceeba2 --- /dev/null +++ b/src/plugins/generator/visualstudio/msbuildutils.h @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef MSBUILDUTILS_H +#define MSBUILDUTILS_H + +#include + +namespace qbs { + +class MSBuildUtils +{ +public: + static QString _qbsArchitecture(const qbs::Project &project) + { + return project.projectConfiguration() + .value(QStringLiteral("qbs")).toMap() + .value(QStringLiteral("architecture")).toString(); + } + + static const QString visualStudioArchitectureName(const QString &qbsArch, bool useDisplayName) + { + if (qbsArch == QStringLiteral("x86") && useDisplayName) + return qbsArch; + + // map of qbs architecture names to MSBuild architecture names + static const QMap map { + {QStringLiteral("x86"), QStringLiteral("Win32")}, + {QStringLiteral("x86_64"), QStringLiteral("x64")}, + {QStringLiteral("ia64"), QStringLiteral("Itanium")}, + {QStringLiteral("arm"), QStringLiteral("ARM")}, + {QStringLiteral("arm64"), QStringLiteral("ARM64")} + }; + return map[qbsArch]; + } + + static QString configurationName(const qbs::Project &project) + { + return project.projectConfiguration() + .value(QStringLiteral("qbs")).toMap() + .value(QStringLiteral("configurationName")).toString(); + } + + static QString displayPlatform(const qbs::Project &project) + { + const auto architecture = _qbsArchitecture(project); + auto displayPlatform = visualStudioArchitectureName(architecture, true); + if (displayPlatform.isEmpty()) + displayPlatform = architecture; + return displayPlatform; + } + + static QString platform(const qbs::Project &project) + { + const auto architecture = _qbsArchitecture(project); + auto platform = visualStudioArchitectureName(architecture, false); + if (platform.isEmpty()) { + qWarning() << "WARNING: Unsupported architecture \"" + << architecture << "\"; using \"Win32\" platform."; + platform = QStringLiteral("Win32"); + } + + return platform; + } + + static QString fullDisplayName(const qbs::Project &project) + { + return QStringLiteral("%1|%2") + .arg(configurationName(project)) + .arg(displayPlatform(project)); + } + + static QString fullName(const qbs::Project &project) + { + return QStringLiteral("%1|%2").arg(configurationName(project)).arg(platform(project)); + } + + static QString buildTaskCondition(const Project &buildTask) + { + return QStringLiteral("'$(Configuration)|$(Platform)'=='") + + MSBuildUtils::fullName(buildTask) + + QStringLiteral("'"); + } +}; + +} // namespace qbs + +#endif // MSBUILDUTILS_H diff --git a/src/plugins/generator/visualstudio/visualstudio.pri b/src/plugins/generator/visualstudio/visualstudio.pri new file mode 100644 index 00000000..13a48e85 --- /dev/null +++ b/src/plugins/generator/visualstudio/visualstudio.pri @@ -0,0 +1 @@ +qbsPluginTarget = visualstudiogenerator diff --git a/src/plugins/generator/visualstudio/visualstudio.pro b/src/plugins/generator/visualstudio/visualstudio.pro new file mode 100644 index 00000000..49aee0eb --- /dev/null +++ b/src/plugins/generator/visualstudio/visualstudio.pro @@ -0,0 +1,32 @@ +include(visualstudio.pri) +include(../../plugins.pri) +include(../../../shared/json/json.pri) +include(../../../lib/msbuild/use_msbuild.pri) +# Using the indirect usage of corelib via plugins.pri breaks linking on mingw +include(../../../lib/corelib/use_corelib.pri) + +INCLUDEPATH += ../../../lib/msbuild + +QT = core + +HEADERS += \ + $$PWD/msbuildfiltersproject.h \ + $$PWD/msbuildqbsgenerateproject.h \ + $$PWD/msbuildqbsproductproject.h \ + $$PWD/msbuildsharedsolutionpropertiesproject.h \ + $$PWD/msbuildsolutionpropertiesproject.h \ + $$PWD/msbuildtargetproject.h \ + $$PWD/msbuildutils.h \ + $$PWD/visualstudiogenerator.h \ + $$PWD/visualstudioguidpool.h + +SOURCES += \ + $$PWD/msbuildfiltersproject.cpp \ + $$PWD/msbuildqbsgenerateproject.cpp \ + $$PWD/msbuildqbsproductproject.cpp \ + $$PWD/msbuildsharedsolutionpropertiesproject.cpp \ + $$PWD/msbuildsolutionpropertiesproject.cpp \ + $$PWD/msbuildtargetproject.cpp \ + $$PWD/visualstudiogenerator.cpp \ + $$PWD/visualstudiogeneratorplugin.cpp \ + $$PWD/visualstudioguidpool.cpp diff --git a/src/plugins/generator/visualstudio/visualstudio.qbs b/src/plugins/generator/visualstudio/visualstudio.qbs new file mode 100644 index 00000000..dcb165d5 --- /dev/null +++ b/src/plugins/generator/visualstudio/visualstudio.qbs @@ -0,0 +1,34 @@ +import qbs +import "../../qbsplugin.qbs" as QbsPlugin + +QbsPlugin { + Depends { name: "qbsjson" } + Depends { name: "qbsmsbuild" } + + name: "visualstudiogenerator" + + files: ["visualstudiogeneratorplugin.cpp"] + + Group { + name: "Visual Studio generator" + files: [ + "msbuildfiltersproject.cpp", + "msbuildfiltersproject.h", + "msbuildqbsgenerateproject.cpp", + "msbuildqbsgenerateproject.h", + "msbuildqbsproductproject.cpp", + "msbuildqbsproductproject.h", + "msbuildsharedsolutionpropertiesproject.cpp", + "msbuildsharedsolutionpropertiesproject.h", + "msbuildsolutionpropertiesproject.cpp", + "msbuildsolutionpropertiesproject.h", + "msbuildtargetproject.cpp", + "msbuildtargetproject.h", + "msbuildutils.h", + "visualstudiogenerator.cpp", + "visualstudiogenerator.h", + "visualstudioguidpool.cpp", + "visualstudioguidpool.h", + ] + } +} diff --git a/src/plugins/generator/visualstudio/visualstudiogenerator.cpp b/src/plugins/generator/visualstudio/visualstudiogenerator.cpp new file mode 100644 index 00000000..8d50bb5f --- /dev/null +++ b/src/plugins/generator/visualstudio/visualstudiogenerator.cpp @@ -0,0 +1,367 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "msbuildfiltersproject.h" +#include "msbuildqbsgenerateproject.h" +#include "msbuildsharedsolutionpropertiesproject.h" +#include "msbuildsolutionpropertiesproject.h" +#include "msbuildqbsproductproject.h" +#include "msbuildutils.h" +#include "visualstudiogenerator.h" +#include "visualstudioguidpool.h" + +#include "msbuild/msbuildpropertygroup.h" +#include "msbuild/msbuildproject.h" + +#include "solution/visualstudiosolution.h" +#include "solution/visualstudiosolutionfileproject.h" +#include "solution/visualstudiosolutionglobalsection.h" +#include "solution/visualstudiosolutionfolderproject.h" + +#include "io/msbuildprojectwriter.h" +#include "io/visualstudiosolutionwriter.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace qbs { + +using namespace Internal; + +class VisualStudioGeneratorPrivate +{ + friend class SolutionDependenciesVisitor; +public: + VisualStudioGeneratorPrivate(const Internal::VisualStudioVersionInfo &versionInfo) + : versionInfo(versionInfo) {} + + Internal::VisualStudioVersionInfo versionInfo; + + std::shared_ptr guidPool; + std::shared_ptr solution; + QString solutionFilePath; + QMap> msbuildProjects; + QMap solutionProjects; + QMap solutionFolders; + QList> propertySheetNames; + + void reset(); +}; + +void VisualStudioGeneratorPrivate::reset() +{ + guidPool.reset(); + solution.reset(); + solutionFilePath.clear(); + msbuildProjects.clear(); + solutionProjects.clear(); + solutionFolders.clear(); + propertySheetNames.clear(); +} + +class SolutionDependenciesVisitor : public IGeneratableProjectVisitor +{ +public: + SolutionDependenciesVisitor(VisualStudioGenerator *generator) + : generator(generator) { + } + + void visitProject(const GeneratableProject &project) override { + Q_UNUSED(project); + nestedProjects = new VisualStudioSolutionGlobalSection( + QStringLiteral("NestedProjects"), generator->d->solution.get()); + generator->d->solution->appendGlobalSection(nestedProjects); + } + + void visitProjectData(const GeneratableProject &project, + const GeneratableProjectData &parentProjectData, + const GeneratableProjectData &projectData) override { + Q_UNUSED(project); + // The root project will have a null GeneratableProjectData + // as its parent object (so skip giving it a parent folder) + if (!parentProjectData.name().isEmpty()) { + nestedProjects->appendProperty( + generator->d->solutionFolders.value(projectData.uniqueName())->guid() + .toString(), + generator->d->solutionFolders.value(parentProjectData.uniqueName())->guid() + .toString()); + } + } + + void visitProduct(const GeneratableProject &project, + const GeneratableProjectData &projectData, + const GeneratableProductData &productData) override { + Q_UNUSED(project); + Q_UNUSED(projectData); + const auto dependencies = productData.dependencies(); + for (const auto &dep : dependencies) { + generator->d->solution->addDependency( + generator->d->solutionProjects.value(productData.name()), + generator->d->solutionProjects.value(dep)); + } + + nestedProjects->appendProperty( + generator->d->solutionProjects.value(productData.name())->guid().toString(), + generator->d->solutionFolders.value(projectData.uniqueName())->guid() + .toString()); + } + +private: + VisualStudioGenerator *generator = nullptr; + VisualStudioSolutionGlobalSection *nestedProjects = nullptr; +}; + +VisualStudioGenerator::VisualStudioGenerator(const VisualStudioVersionInfo &versionInfo) + : d(new VisualStudioGeneratorPrivate(versionInfo)) +{ + if (d->versionInfo.usesVcBuild()) + throw ErrorInfo(Tr::tr("VCBuild (Visual Studio 2008 and below) is not supported")); + else if (!d->versionInfo.usesMsBuild()) + throw ErrorInfo(Tr::tr("Unknown/unsupported build engine")); + Q_ASSERT(d->versionInfo.usesSolutions()); +} + +VisualStudioGenerator::~VisualStudioGenerator() = default; + +QString VisualStudioGenerator::generatorName() const +{ + return QStringLiteral("visualstudio%1").arg(d->versionInfo.marketingVersion()); +} + +void VisualStudioGenerator::addPropertySheets(const GeneratableProject &project) +{ + { + const auto fileName = QStringLiteral("qbs.props"); + d->propertySheetNames.push_back({ fileName, true }); + d->msbuildProjects.insert(project.baseBuildDirectory().absoluteFilePath(fileName), + std::make_shared( + d->versionInfo, project, + qbsExecutableFilePath(), qbsSettingsDir())); + } + + { + const auto fileName = QStringLiteral("qbs-shared.props"); + d->propertySheetNames.push_back({ fileName, false }); + d->msbuildProjects.insert(project.baseBuildDirectory().absoluteFilePath(fileName), + std::make_shared( + d->versionInfo, project, + qbsExecutableFilePath(), qbsSettingsDir())); + } +} + +void VisualStudioGenerator::addPropertySheets( + const std::shared_ptr &targetProject) +{ + for (const auto &pair : qAsConst(d->propertySheetNames)) { + targetProject->appendPropertySheet( + QStringLiteral("$(SolutionDir)\\") + pair.first, pair.second); + } +} + +static QString targetFilePath(const QString &baseName, const QString &baseBuildDirectory) +{ + return QDir(baseBuildDirectory).absoluteFilePath(baseName + QStringLiteral(".vcxproj")); +} + +static QString targetFilePath(const GeneratableProductData &product, + const QString &baseBuildDirectory) +{ + return targetFilePath(product.name(), baseBuildDirectory); +} + +static void addDefaultGlobalSections(const GeneratableProject &topLevelProject, + VisualStudioSolution *solution) +{ + const auto configurationPlatformsSection = new VisualStudioSolutionGlobalSection( + QStringLiteral("SolutionConfigurationPlatforms"), solution); + solution->appendGlobalSection(configurationPlatformsSection); + for (const auto &qbsProject : topLevelProject.projects) + configurationPlatformsSection->appendProperty(MSBuildUtils::fullName(qbsProject), + MSBuildUtils::fullName(qbsProject)); + + const auto projectConfigurationPlatformsSection = new VisualStudioSolutionGlobalSection( + QStringLiteral("ProjectConfigurationPlatforms"), solution); + solution->appendGlobalSection(projectConfigurationPlatformsSection); + projectConfigurationPlatformsSection->setPost(true); + const auto projects = solution->fileProjects(); + for (const auto project : projects) { + for (const auto &qbsProject : topLevelProject.projects) { + projectConfigurationPlatformsSection->appendProperty( + QStringLiteral("%1.%2.ActiveCfg") + .arg(project->guid().toString()) + .arg(MSBuildUtils::fullDisplayName(qbsProject)), + MSBuildUtils::fullName(qbsProject)); + projectConfigurationPlatformsSection->appendProperty( + QStringLiteral("%1.%2.Build.0") + .arg(project->guid().toString()) + .arg(MSBuildUtils::fullDisplayName(qbsProject)), + MSBuildUtils::fullName(qbsProject)); + } + } + + const auto solutionPropsSection = new VisualStudioSolutionGlobalSection( + QStringLiteral("SolutionProperties"), solution); + solution->appendGlobalSection(solutionPropsSection); + solutionPropsSection->appendProperty(QStringLiteral("HideSolutionNode"), + QStringLiteral("FALSE")); +} + +static void writeProjectFiles(const QMap> &projects) +{ + // Write out all the MSBuild project files to disk + QMapIterator> it(projects); + while (it.hasNext()) { + it.next(); + const auto projectFilePath = it.key(); + Internal::FileSaver file(projectFilePath.toStdString()); + if (!file.open()) + throw ErrorInfo(Tr::tr("Cannot open %s for writing").arg(projectFilePath)); + + std::shared_ptr project = it.value(); + MSBuildProjectWriter writer(file.device()); + if (!(writer.write(project.get()) && file.commit())) + throw ErrorInfo(Tr::tr("Failed to generate %1").arg(projectFilePath)); + } +} + +static void writeSolution(const std::shared_ptr &solution, + const QString &solutionFilePath, + const Internal::Logger &logger) +{ + 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().toStdString()); + if (!(writer.write(solution.get()) && file.commit())) + throw ErrorInfo(Tr::tr("Failed to generate %1").arg(solutionFilePath)); + + logger.qbsInfo() << Tr::tr("Generated %1").arg(QFileInfo(solutionFilePath).fileName()); +} + +void VisualStudioGenerator::generate() +{ + GeneratableProjectIterator it(project()); + it.accept(this); + + addDefaultGlobalSections(project(), d->solution.get()); + + // Second pass: connection solution project interdependencies and project nesting hierarchy + SolutionDependenciesVisitor solutionDependenciesVisitor(this); + it.accept(&solutionDependenciesVisitor); + + writeProjectFiles(d->msbuildProjects); + writeSolution(d->solution, d->solutionFilePath, logger()); + + d->reset(); +} + +void VisualStudioGenerator::visitProject(const GeneratableProject &project) +{ + addPropertySheets(project); + + const auto buildDir = project.baseBuildDirectory(); + + d->guidPool = std::make_shared( + buildDir.absoluteFilePath(project.name() + + QStringLiteral(".guid.txt")).toStdString()); + + d->solutionFilePath = buildDir.absoluteFilePath(project.name() + QStringLiteral(".sln")); + d->solution = std::make_shared(d->versionInfo); + + // Create a helper project to re-run qbs generate + const auto qbsGenerate = QStringLiteral("qbs-generate"); + const auto projectFilePath = targetFilePath(qbsGenerate, buildDir.absolutePath()); + const auto relativeProjectFilePath = QFileInfo(d->solutionFilePath).dir() + .relativeFilePath(projectFilePath); + auto targetProject = std::make_shared(project, d->versionInfo); + targetProject->setGuid(d->guidPool->drawProductGuid(relativeProjectFilePath.toStdString())); + d->msbuildProjects.insert(projectFilePath, targetProject); + + addPropertySheets(targetProject); + + const auto solutionProject = new VisualStudioSolutionFileProject( + targetFilePath(qbsGenerate, project.baseBuildDirectory().absolutePath()), + d->solution.get()); + solutionProject->setGuid(targetProject->guid()); + d->solution->appendProject(solutionProject); + d->solutionProjects.insert(qbsGenerate, solutionProject); +} + +void VisualStudioGenerator::visitProjectData(const GeneratableProject &project, + const GeneratableProjectData &projectData) +{ + Q_UNUSED(project); + const auto solutionFolder = new VisualStudioSolutionFolderProject(d->solution.get()); + solutionFolder->setName(projectData.name()); + d->solution->appendProject(solutionFolder); + QBS_CHECK(!d->solutionFolders.contains(projectData.uniqueName())); + d->solutionFolders.insert(projectData.uniqueName(), solutionFolder); +} + +void VisualStudioGenerator::visitProduct(const GeneratableProject &project, + const GeneratableProjectData &projectData, + const GeneratableProductData &productData) +{ + Q_UNUSED(projectData); + const auto projectFilePath = targetFilePath(productData, + project.baseBuildDirectory().absolutePath()); + const auto relativeProjectFilePath = QFileInfo(d->solutionFilePath) + .dir().relativeFilePath(projectFilePath); + auto targetProject = std::make_shared(project, productData, + d->versionInfo); + targetProject->setGuid(d->guidPool->drawProductGuid(relativeProjectFilePath.toStdString())); + + addPropertySheets(targetProject); + + d->msbuildProjects.insert(projectFilePath, targetProject); + d->msbuildProjects.insert(projectFilePath + QStringLiteral(".filters"), + std::make_shared(productData)); + + const auto solutionProject = new VisualStudioSolutionFileProject( + targetFilePath(productData, project.baseBuildDirectory().absolutePath()), + d->solution.get()); + solutionProject->setGuid(targetProject->guid()); + d->solution->appendProject(solutionProject); + d->solutionProjects.insert(productData.name(), solutionProject); +} + +} // namespace qbs diff --git a/src/plugins/generator/visualstudio/visualstudiogenerator.h b/src/plugins/generator/visualstudio/visualstudiogenerator.h new file mode 100644 index 00000000..e104ca42 --- /dev/null +++ b/src/plugins/generator/visualstudio/visualstudiogenerator.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef QBS_VISUALSTUDIOGENERATOR_H +#define QBS_VISUALSTUDIOGENERATOR_H + +#include +#include +#include +#include "visualstudioguidpool.h" + +#include +#include +#include + +namespace qbs { + +namespace Internal { class VisualStudioVersionInfo; } + +class MSBuildProject; +class MSBuildTargetProject; + +class VisualStudioGeneratorPrivate; +class VisualStudioSolution; +class VisualStudioSolutionFileProject; +class VisualStudioSolutionFolderProject; + +class VisualStudioGenerator : public ProjectGenerator, private IGeneratableProjectVisitor +{ + friend class SolutionDependenciesVisitor; +public: + explicit VisualStudioGenerator(const Internal::VisualStudioVersionInfo &versionInfo); + ~VisualStudioGenerator() override; + QString generatorName() const override; + void generate() override; + +private: + void visitProject(const GeneratableProject &project) override; + void visitProjectData(const GeneratableProject &project, + const GeneratableProjectData &projectData) override; + void visitProduct(const GeneratableProject &project, + const GeneratableProjectData &projectData, + const GeneratableProductData &productData) override; + + void addPropertySheets(const GeneratableProject &project); + void addPropertySheets(const std::shared_ptr &targetProject); + + std::unique_ptr d; +}; + +} // namespace qbs + +#endif // QBS_VISUALSTUDIOGENERATOR_H diff --git a/src/plugins/generator/visualstudio/visualstudiogeneratorplugin.cpp b/src/plugins/generator/visualstudio/visualstudiogeneratorplugin.cpp new file mode 100644 index 00000000..ef4e39fc --- /dev/null +++ b/src/plugins/generator/visualstudio/visualstudiogeneratorplugin.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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 "visualstudiogenerator.h" + +#include +#include + +static void QbsVisualStudioGeneratorPluginLoad() +{ + for (const auto &info : qbs::Internal::VisualStudioVersionInfo::knownVersions()) { + if (info.usesMsBuild()) + qbs::ProjectGeneratorManager::registerGenerator( + std::make_shared(info)); + } +} + +static void QbsVisualStudioGeneratorPluginUnload() +{ +} + +#ifndef GENERATOR_EXPORT +#if defined(WIN32) || defined(_WIN32) +#define GENERATOR_EXPORT __declspec(dllexport) +#else +#define GENERATOR_EXPORT __attribute__((visibility("default"))) +#endif +#endif + +QBS_REGISTER_STATIC_PLUGIN(extern "C" GENERATOR_EXPORT, visualstudiogenerator, + QbsVisualStudioGeneratorPluginLoad, QbsVisualStudioGeneratorPluginUnload) diff --git a/src/plugins/generator/visualstudio/visualstudioguidpool.cpp b/src/plugins/generator/visualstudio/visualstudioguidpool.cpp new file mode 100644 index 00000000..d6d716ff --- /dev/null +++ b/src/plugins/generator/visualstudio/visualstudioguidpool.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#include "visualstudioguidpool.h" +#include +#include +#include + +#include +#include +#include + +#include + +using namespace Json; + +namespace qbs { + +class VisualStudioGuidPoolPrivate +{ +public: + std::string storeFilePath; + std::map productGuids; +}; + +VisualStudioGuidPool::VisualStudioGuidPool(const std::string &storeFilePath) + : d(std::make_shared()) +{ + // Read any existing GUIDs from the on-disk store + std::ifstream file(Internal::utf8_to_native_path(d->storeFilePath = storeFilePath)); + if (file.is_open()) { + const auto data = JsonDocument::fromJson(std::string { + std::istreambuf_iterator(file), + std::istreambuf_iterator() + }).object(); + for (auto it = data.constBegin(), end = data.constEnd(); it != end; ++it) { + d->productGuids.insert({ + it.key(), + QUuid(QString::fromStdString(it.value().toString())) + }); + } + } +} + +VisualStudioGuidPool::~VisualStudioGuidPool() +{ + Internal::FileSaver file(d->storeFilePath); + if (file.open()) { + JsonObject productData; + for (const auto &it : d->productGuids) + productData.insert(it.first, it.second.toString().toStdString()); + + const auto data = JsonDocument(productData).toJson(); + file.write(std::vector { data.cbegin(), data.cend() }); + file.commit(); + } +} + +QUuid VisualStudioGuidPool::drawProductGuid(const std::string &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 new file mode 100644 index 00000000..dd6dc9eb --- /dev/null +++ b/src/plugins/generator/visualstudio/visualstudioguidpool.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +#ifndef VISUALSTUDIOGUIDPOOL_H +#define VISUALSTUDIOGUIDPOOL_H + +#include + +#include + +namespace qbs { + +class VisualStudioGuidPoolPrivate; + +/*! + * Provides persistent storage for GUIDs related to Visual Studio project file nodes. + * These are stored on disk separately from project files and so allow projects to be + * regenerated while retaining the same GUIDs. This helps avoid unnecessary project + * reloads in Visual Studio, and helps ease source control usage. + */ +class VisualStudioGuidPool +{ +public: + explicit VisualStudioGuidPool(const std::string &storeFilePath); + ~VisualStudioGuidPool(); + + QUuid drawProductGuid(const std::string &productName); + +private: + std::shared_ptr d; +}; + +} // namespace qbs + +#endif // VISUALSTUDIOGUIDPOOL_H diff --git a/src/plugins/plugins.pri b/src/plugins/plugins.pri new file mode 100644 index 00000000..b09619ca --- /dev/null +++ b/src/plugins/plugins.pri @@ -0,0 +1,21 @@ +include(qbs_plugin_common.pri) + +TARGET = $$qbsPluginTarget +DESTDIR = $$qbsPluginDestDir + +isEmpty(QBSLIBDIR): QBSLIBDIR = $$OUT_PWD/../../../../$${QBS_LIBRARY_DIRNAME} +isEmpty(QBS_RPATH): QBS_RPATH = ../.. +include($${PWD}/../lib/corelib/use_corelib.pri) + +TEMPLATE = lib + +CONFIG += c++14 +CONFIG(static, static|shared): CONFIG += create_prl +CONFIG += plugin + +!isEmpty(QBS_PLUGINS_INSTALL_DIR): \ + installPrefix = $${QBS_PLUGINS_INSTALL_DIR} +else: \ + installPrefix = $${QBS_INSTALL_PREFIX}/$${QBS_LIBRARY_DIRNAME} +target.path = $${installPrefix}/qbs/plugins +INSTALLS += target diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro new file mode 100644 index 00000000..9fe2e6b5 --- /dev/null +++ b/src/plugins/plugins.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS = generator scanner diff --git a/src/plugins/plugins.qbs b/src/plugins/plugins.qbs new file mode 100644 index 00000000..ee101f60 --- /dev/null +++ b/src/plugins/plugins.qbs @@ -0,0 +1,14 @@ +import qbs + +Project { + name: "qbs plugins" + references: [ + "generator/clangcompilationdb/clangcompilationdb.qbs", + "generator/makefilegenerator/makefilegenerator.qbs", + "generator/visualstudio/visualstudio.qbs", + "generator/iarew/iarew.qbs", + "generator/keiluv/keiluv.qbs", + "scanner/cpp/cpp.qbs", + "scanner/qt/qt.qbs" + ] +} diff --git a/src/plugins/qbs_plugin_common.pri b/src/plugins/qbs_plugin_common.pri new file mode 100644 index 00000000..45ade498 --- /dev/null +++ b/src/plugins/qbs_plugin_common.pri @@ -0,0 +1,9 @@ +include(../library_dirname.pri) +include(../install_prefix.pri) + +!isEmpty(QBS_PLUGINS_BUILD_DIR) { + destdirPrefix = $${QBS_PLUGINS_BUILD_DIR} +} else { + destdirPrefix = $$shadowed($$PWD)/../../$${QBS_LIBRARY_DIRNAME} +} +qbsPluginDestDir = $${destdirPrefix}/qbs/plugins diff --git a/src/plugins/qbsplugin.qbs b/src/plugins/qbsplugin.qbs new file mode 100644 index 00000000..b37fffe8 --- /dev/null +++ b/src/plugins/qbsplugin.qbs @@ -0,0 +1,49 @@ +import qbs +import qbs.FileInfo + +QbsProduct { + property bool isForDarwin: qbs.targetOS.contains("darwin") + property bool staticBuild: Qt.core.staticBuild || qbsbuildconfig.staticBuild + Depends { name: "cpp" } + Depends { name: "bundle"; condition: isForDarwin } + Depends { name: "Qt.core" } + Depends { name: "qbsbuildconfig" } + Depends { name: "qbscore"; condition: !staticBuild } + type: (staticBuild ? ["staticlibrary"] : [isForDarwin ? "loadablemodule" : "dynamiclibrary"]) + .concat(["qbsplugin"]) + Properties { + condition: staticBuild + cpp.defines: ["QBS_STATIC_LIB"] + } + cpp.includePaths: base.concat(["../../../lib/corelib"]) + cpp.visibility: "minimal" + Group { + fileTagsFilter: [isForDarwin ? "loadablemodule" : "dynamiclibrary"] + .concat(qbs.buildVariant === "debug" + ? [isForDarwin ? "debuginfo_loadablemodule" : "debuginfo_dll"] : []) + qbs.install: true + qbs.installDir: targetInstallDir + qbs.installSourceBase: buildDirectory + } + targetInstallDir: qbsbuildconfig.pluginsInstallDir + Properties { + condition: isForDarwin + bundle.isBundle: false + } + + Export { + Depends { name: "cpp" } + Properties { + condition: qbs.targetOS.contains("darwin") + cpp.linkerFlags: ["-u", "_qbs_static_plugin_register_" + name] + } + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.linkerFlags: "--require-defined=qbs_static_plugin_register_" + name + } + Properties { + condition: qbs.toolchain.contains("msvc") + cpp.linkerFlags: "/INCLUDE:qbs_static_plugin_register_" + name + } + } +} diff --git a/src/plugins/scanner/cpp/CPlusPlusForwardDeclarations.h b/src/plugins/scanner/cpp/CPlusPlusForwardDeclarations.h new file mode 100644 index 00000000..d0d7f663 --- /dev/null +++ b/src/plugins/scanner/cpp/CPlusPlusForwardDeclarations.h @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// Copyright (c) 2008 Roberto Raggi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef CPLUSPLUS_CPLUSPLUSFORWARDDECLARATIONS_H +#define CPLUSPLUS_CPLUSPLUSFORWARDDECLARATIONS_H + +#include +#include + +#ifndef CPLUSPLUS_WITHOUT_QT +# include + +//# if defined(CPLUSPLUS_BUILD_LIB) +//# define CPLUSPLUS_EXPORT Q_DECL_EXPORT +//# elif defined(CPLUSPLUS_BUILD_STATIC_LIB) +//# define CPLUSPLUS_EXPORT +//# else +//# define CPLUSPLUS_EXPORT Q_DECL_IMPORT +//# endif +//#else +# define CPLUSPLUS_EXPORT +#endif + +namespace CPlusPlus { + +class TranslationUnit; +class Control; +class MemoryPool; +class DiagnosticClient; + +class Identifier; +class Literal; +class StringLiteral; +class NumericLiteral; + +class SymbolTable; + +// names +class NameVisitor; +class Name; +class Identifier; +class TemplateNameId; +class DestructorNameId; +class OperatorNameId; +class ConversionNameId; +class QualifiedNameId; +class SelectorNameId; + +// types +class TypeMatcher; +class FullySpecifiedType; +class TypeVisitor; +class Type; +class UndefinedType; +class VoidType; +class IntegerType; +class FloatType; +class PointerToMemberType; +class PointerType; +class ReferenceType; +class ArrayType; +class NamedType; + +// symbols +class SymbolVisitor; +class Symbol; +class Scope; +class UsingNamespaceDirective; +class UsingDeclaration; +class Declaration; +class Argument; +class TypenameArgument; +class Function; +class Namespace; +class NamespaceAlias; +class Template; +class BaseClass; +class Block; +class Class; +class Enum; +class ForwardClassDeclaration; + +class Token; + +// Objective-C symbols +class ObjCBaseClass; +class ObjCBaseProtocol; +class ObjCClass; +class ObjCForwardClassDeclaration; +class ObjCProtocol; +class ObjCForwardProtocolDeclaration; +class ObjCMethod; +class ObjCPropertyDeclaration; + +} // end of namespace CPlusPlus + +#endif // CPLUSPLUS_CPLUSPLUSFORWARDDECLARATIONS_H diff --git a/src/plugins/scanner/cpp/Lexer.cpp b/src/plugins/scanner/cpp/Lexer.cpp new file mode 100644 index 00000000..16556ac7 --- /dev/null +++ b/src/plugins/scanner/cpp/Lexer.cpp @@ -0,0 +1,669 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// Copyright (c) 2008 Roberto Raggi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "Lexer.h" +#include + +namespace CPlusPlus { + +Lexer::Lexer(const char *firstChar, const char *lastChar) + : _state(State_Default), + _currentLine(1) +{ + setSource(firstChar, lastChar); +} + +Lexer::~Lexer() = default; + +void Lexer::setSource(const char *firstChar, const char *lastChar) +{ + _firstChar = firstChar; + _lastChar = lastChar; + _currentChar = _firstChar - 1; + _tokenStart = _currentChar; + _yychar = '\n'; +} + +void Lexer::setStartWithNewline(bool enabled) +{ + if (enabled) + _yychar = '\n'; + else + _yychar = ' '; +} + +int Lexer::state() const +{ return _state; } + +void Lexer::setState(int state) +{ _state = state; } + +bool Lexer::qtMocRunEnabled() const +{ return f._qtMocRunEnabled; } + +void Lexer::setQtMocRunEnabled(bool onoff) +{ f._qtMocRunEnabled = onoff; } + +bool Lexer::cxx0xEnabled() const +{ return f._cxx0xEnabled; } + +void Lexer::setCxxOxEnabled(bool onoff) +{ f._cxx0xEnabled = onoff; } + +bool Lexer::objCEnabled() const +{ return f._objCEnabled; } + +void Lexer::setObjCEnabled(bool onoff) +{ f._objCEnabled = onoff; } + +bool Lexer::isIncremental() const +{ return f._isIncremental; } + +void Lexer::setIncremental(bool isIncremental) +{ f._isIncremental = isIncremental; } + +bool Lexer::scanCommentTokens() const +{ return f._scanCommentTokens; } + +void Lexer::setScanCommentTokens(bool onoff) +{ f._scanCommentTokens = onoff; } + +void Lexer::setScanAngleStringLiteralTokens(bool onoff) +{ f._scanAngleStringLiteralTokens = onoff; } + +void Lexer::pushLineStartOffset() +{ + ++_currentLine; +} + +unsigned Lexer::tokenOffset() const +{ return _tokenStart - _firstChar; } + +unsigned Lexer::tokenLength() const +{ return _currentChar - _tokenStart; } + +const char *Lexer::tokenBegin() const +{ return _tokenStart; } + +const char *Lexer::tokenEnd() const +{ return _currentChar; } + +unsigned Lexer::currentLine() const +{ return _currentLine; } + +void Lexer::scan(Token *tok) +{ + tok->reset(); + scan_helper(tok); + tok->f.length = _currentChar - _tokenStart; +} + +void Lexer::scan_helper(Token *tok) +{ + _Lagain: + while (_yychar && std::isspace(_yychar)) { + if (_yychar == '\n') { + tok->f.joined = false; + tok->f.newline = true; + } else { + tok->f.whitespace = true; + } + yyinp(); + } + + tok->lineno = _currentLine; + _tokenStart = _currentChar; + tok->offset = _currentChar - _firstChar; + + if (_state == State_MultiLineComment || _state == State_MultiLineDoxyComment) { + const int originalState = _state; + + if (! _yychar) { + tok->f.kind = T_EOF_SYMBOL; + return; + } + + while (_yychar) { + if (_yychar != '*') + yyinp(); + else { + yyinp(); + if (_yychar == '/') { + yyinp(); + _state = State_Default; + break; + } + } + } + + if (! f._scanCommentTokens) + goto _Lagain; + + else if (originalState == State_MultiLineComment) + tok->f.kind = T_COMMENT; + else + tok->f.kind = T_DOXY_COMMENT; + return; // done + } + + if (! _yychar) { + tok->f.kind = T_EOF_SYMBOL; + return; + } + + unsigned char ch = _yychar; + yyinp(); + + switch (ch) { + case '\\': + while (_yychar != '\n' && std::isspace(_yychar)) + yyinp(); + // ### assert(! _yychar || _yychar == '\n'); + if (_yychar == '\n') { + tok->f.joined = true; + tok->f.newline = false; + yyinp(); + } + goto _Lagain; + + case '"': case '\'': { + const unsigned char quote = ch; + + tok->f.kind = quote == '"' + ? T_STRING_LITERAL + : T_CHAR_LITERAL; + + while (_yychar && _yychar != quote) { + if (_yychar == '\n') + break; + else if (_yychar != '\\') + yyinp(); + else { + yyinp(); // skip `\\' + + if (_yychar) + yyinp(); + } + } + // assert(_yychar == quote); + + if (_yychar == quote) + yyinp(); + } break; + + case '{': + tok->f.kind = T_LBRACE; + break; + + case '}': + tok->f.kind = T_RBRACE; + break; + + case '[': + tok->f.kind = T_LBRACKET; + break; + + case ']': + tok->f.kind = T_RBRACKET; + break; + + case '#': + if (_yychar == '#') { + tok->f.kind = T_POUND_POUND; + yyinp(); + } else { + tok->f.kind = T_POUND; + } + break; + + case '(': + tok->f.kind = T_LPAREN; + break; + + case ')': + tok->f.kind = T_RPAREN; + break; + + case ';': + tok->f.kind = T_SEMICOLON; + break; + + case ':': + if (_yychar == ':') { + yyinp(); + tok->f.kind = T_COLON_COLON; + } else { + tok->f.kind = T_COLON; + } + break; + + case '.': + if (_yychar == '*') { + yyinp(); + tok->f.kind = T_DOT_STAR; + } else if (_yychar == '.') { + yyinp(); + // ### assert(_yychar); + if (_yychar == '.') { + yyinp(); + tok->f.kind = T_DOT_DOT_DOT; + } else { + tok->f.kind = T_ERROR; + } + } else if (std::isdigit(_yychar)) { + do { + if (_yychar == 'e' || _yychar == 'E') { + yyinp(); + if (_yychar == '-' || _yychar == '+') { + yyinp(); + // ### assert(std::isdigit(_yychar)); + } + } else if (std::isalnum(_yychar) || _yychar == '.') { + yyinp(); + } else { + break; + } + } while (_yychar); + tok->f.kind = T_NUMERIC_LITERAL; + } else { + tok->f.kind = T_DOT; + } + break; + + case '?': + tok->f.kind = T_QUESTION; + break; + + case '+': + if (_yychar == '+') { + yyinp(); + tok->f.kind = T_PLUS_PLUS; + } else if (_yychar == '=') { + yyinp(); + tok->f.kind = T_PLUS_EQUAL; + } else { + tok->f.kind = T_PLUS; + } + break; + + case '-': + if (_yychar == '-') { + yyinp(); + tok->f.kind = T_MINUS_MINUS; + } else if (_yychar == '=') { + yyinp(); + tok->f.kind = T_MINUS_EQUAL; + } else if (_yychar == '>') { + yyinp(); + if (_yychar == '*') { + yyinp(); + tok->f.kind = T_ARROW_STAR; + } else { + tok->f.kind = T_ARROW; + } + } else { + tok->f.kind = T_MINUS; + } + break; + + case '*': + if (_yychar == '=') { + yyinp(); + tok->f.kind = T_STAR_EQUAL; + } else { + tok->f.kind = T_STAR; + } + break; + + case '/': + if (_yychar == '/') { + yyinp(); + + bool doxy = false; + + if (_yychar == '/' || _yychar == '!') { + yyinp(); + + if (_yychar == '<') + yyinp(); + + if (_yychar != '\n' && std::isspace(_yychar)) + doxy = true; + } + + while (_yychar && _yychar != '\n') + yyinp(); + + if (! f._scanCommentTokens) + goto _Lagain; + + tok->f.kind = doxy ? T_CPP_DOXY_COMMENT : T_CPP_COMMENT; + + } else if (_yychar == '*') { + yyinp(); + + bool doxy = false; + + if (_yychar == '*' || _yychar == '!') { + const unsigned char ch = _yychar; + + yyinp(); + + if (ch == '*' && _yychar == '/') + goto _Ldone; + + if (_yychar == '<') + yyinp(); + + if (! _yychar || std::isspace(_yychar)) + doxy = true; + } + + while (_yychar) { + if (_yychar != '*') { + yyinp(); + } else { + yyinp(); + if (_yychar == '/') + break; + } + } + + _Ldone: + if (_yychar) + yyinp(); + else + _state = doxy ? State_MultiLineDoxyComment : State_MultiLineComment; + + if (! f._scanCommentTokens) + goto _Lagain; + + tok->f.kind = doxy ? T_DOXY_COMMENT : T_COMMENT; + + } else if (_yychar == '=') { + yyinp(); + tok->f.kind = T_SLASH_EQUAL; + } else { + tok->f.kind = T_SLASH; + } + break; + + case '%': + if (_yychar == '=') { + yyinp(); + tok->f.kind = T_PERCENT_EQUAL; + } else { + tok->f.kind = T_PERCENT; + } + break; + + case '^': + if (_yychar == '=') { + yyinp(); + tok->f.kind = T_CARET_EQUAL; + } else { + tok->f.kind = T_CARET; + } + break; + + case '&': + if (_yychar == '&') { + yyinp(); + tok->f.kind = T_AMPER_AMPER; + } else if (_yychar == '=') { + yyinp(); + tok->f.kind = T_AMPER_EQUAL; + } else { + tok->f.kind = T_AMPER; + } + break; + + case '|': + if (_yychar == '|') { + yyinp(); + tok->f.kind = T_PIPE_PIPE; + } else if (_yychar == '=') { + yyinp(); + tok->f.kind = T_PIPE_EQUAL; + } else { + tok->f.kind = T_PIPE; + } + break; + + case '~': + if (_yychar == '=') { + yyinp(); + tok->f.kind = T_TILDE_EQUAL; + } else { + tok->f.kind = T_TILDE; + } + break; + + case '!': + if (_yychar == '=') { + yyinp(); + tok->f.kind = T_EXCLAIM_EQUAL; + } else { + tok->f.kind = T_EXCLAIM; + } + break; + + case '=': + if (_yychar == '=') { + yyinp(); + tok->f.kind = T_EQUAL_EQUAL; + } else { + tok->f.kind = T_EQUAL; + } + break; + + case '<': + if (f._scanAngleStringLiteralTokens) { + //const char *yytext = _currentChar; + while (_yychar && _yychar != '>') + yyinp(); + //int yylen = _currentChar - yytext; + // ### assert(_yychar == '>'); + if (_yychar == '>') + yyinp(); + tok->f.kind = T_ANGLE_STRING_LITERAL; + } else if (_yychar == '<') { + yyinp(); + if (_yychar == '=') { + yyinp(); + tok->f.kind = T_LESS_LESS_EQUAL; + } else + tok->f.kind = T_LESS_LESS; + } else if (_yychar == '=') { + yyinp(); + tok->f.kind = T_LESS_EQUAL; + } else { + tok->f.kind = T_LESS; + } + break; + + case '>': + if (_yychar == '>') { + yyinp(); + if (_yychar == '=') { + yyinp(); + tok->f.kind = T_GREATER_GREATER_EQUAL; + } else + tok->f.kind = T_LESS_LESS; + tok->f.kind = T_GREATER_GREATER; + } else if (_yychar == '=') { + yyinp(); + tok->f.kind = T_GREATER_EQUAL; + } else { + tok->f.kind = T_GREATER; + } + break; + + case ',': + tok->f.kind = T_COMMA; + break; + + default: { + if (f._objCEnabled) { + if (ch == '@' && _yychar >= 'a' && _yychar <= 'z') { + //const char *yytext = _currentChar; + + do { + yyinp(); + if (! (isalnum(_yychar) || _yychar == '_' || _yychar == '$')) + break; + } while (_yychar); + + // const int yylen = _currentChar - yytext; + //tok->f.kind = classifyObjCAtKeyword(yytext, yylen); /// ### FIXME + break; + } else if (ch == '@' && _yychar == '"') { + // objc @string literals + ch = _yychar; + yyinp(); + tok->f.kind = T_AT_STRING_LITERAL; + + //const char *yytext = _currentChar; + + while (_yychar && _yychar != '"') { + if (_yychar != '\\') + yyinp(); + else { + yyinp(); // skip `\\' + + if (_yychar) + yyinp(); + } + } + // assert(_yychar == '"'); + + //int yylen = _currentChar - yytext; + + if (_yychar == '"') + yyinp(); + + break; + } + } + + if (ch == 'L' && (_yychar == '"' || _yychar == '\'')) { + // wide char/string literals + ch = _yychar; + yyinp(); + + const unsigned char quote = ch; + + tok->f.kind = quote == '"' + ? T_WIDE_STRING_LITERAL + : T_WIDE_CHAR_LITERAL; + + //const char *yytext = _currentChar; + + while (_yychar && _yychar != quote) { + if (_yychar != '\\') + yyinp(); + else { + yyinp(); // skip `\\' + + if (_yychar) + yyinp(); + } + } + // assert(_yychar == quote); + + //int yylen = _currentChar - yytext; + + if (_yychar == quote) + yyinp(); + + } else if (std::isalpha(ch) || ch == '_' || ch == '$') { + //const char *yytext = _currentChar - 1; + while (std::isalnum(_yychar) || _yychar == '_' || _yychar == '$') + yyinp(); + //int yylen = _currentChar - yytext; + tok->f.kind = T_IDENTIFIER; + break; + } else if (std::isdigit(ch)) { + //const char *yytext = _currentChar - 1; + while (_yychar) { + if (_yychar == 'e' || _yychar == 'E') { + yyinp(); + if (_yychar == '-' || _yychar == '+') { + yyinp(); + // ### assert(std::isdigit(_yychar)); + } + } else if (std::isalnum(_yychar) || _yychar == '.') { + yyinp(); + } else { + break; + } + } + //int yylen = _currentChar - yytext; + tok->f.kind = T_NUMERIC_LITERAL; + break; + } else { + tok->f.kind = T_ERROR; + break; + } + } // default + + } // switch +} + +} // namespace CPlusPlus diff --git a/src/plugins/scanner/cpp/Lexer.h b/src/plugins/scanner/cpp/Lexer.h new file mode 100644 index 00000000..8f55f84e --- /dev/null +++ b/src/plugins/scanner/cpp/Lexer.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// Copyright (c) 2008 Roberto Raggi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef CPLUSPLUS_LEXER_H +#define CPLUSPLUS_LEXER_H + +#include "CPlusPlusForwardDeclarations.h" +#include "Token.h" + + +namespace CPlusPlus { + +class CPLUSPLUS_EXPORT Lexer +{ + Lexer(const Lexer &other); + void operator =(const Lexer &other); + +public: + enum State { + State_Default, + State_MultiLineComment, + State_MultiLineDoxyComment + }; + + Lexer(const char *firstChar, const char *lastChar); + ~Lexer(); + + bool qtMocRunEnabled() const; + void setQtMocRunEnabled(bool onoff); + + bool cxx0xEnabled() const; + void setCxxOxEnabled(bool onoff); + + bool objCEnabled() const; + void setObjCEnabled(bool onoff); + + void scan(Token *tok); + + inline void operator()(Token *tok) + { scan(tok); } + + unsigned tokenOffset() const; + unsigned tokenLength() const; + const char *tokenBegin() const; + const char *tokenEnd() const; + unsigned currentLine() const; + + bool scanCommentTokens() const; + void setScanCommentTokens(bool onoff); + + bool scanAngleStringLiteralTokens() const; + void setScanAngleStringLiteralTokens(bool onoff); + + void setStartWithNewline(bool enabled); + + int state() const; + void setState(int state); + + bool isIncremental() const; + void setIncremental(bool isIncremental); + +private: + void scan_helper(Token *tok); + void setSource(const char *firstChar, const char *lastChar); + static int classify(const char *string, int length, bool q, bool cxx0x); + static int classifyObjCAtKeyword(const char *s, int n); + static int classifyOperator(const char *string, int length); + + inline void yyinp() + { + if (++_currentChar == _lastChar) + _yychar = 0; + else { + _yychar = *_currentChar; + if (_yychar == '\n') + pushLineStartOffset(); + } + } + + void pushLineStartOffset(); + +private: + struct Flags { + unsigned _isIncremental: 1; + unsigned _scanCommentTokens: 1; + unsigned _scanAngleStringLiteralTokens: 1; + unsigned _qtMocRunEnabled: 1; + unsigned _cxx0xEnabled: 1; + unsigned _objCEnabled: 1; + }; + + const char *_firstChar = nullptr; + const char *_currentChar = nullptr; + const char *_lastChar = nullptr; + const char *_tokenStart = nullptr; + unsigned char _yychar = 0; + int _state = 0; + Flags f{}; + unsigned _currentLine = 0; +}; + +} // end of namespace CPlusPlus + + +#endif // CPLUSPLUS_LEXER_H diff --git a/src/plugins/scanner/cpp/Token.cpp b/src/plugins/scanner/cpp/Token.cpp new file mode 100644 index 00000000..b488bf0d --- /dev/null +++ b/src/plugins/scanner/cpp/Token.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// Copyright (c) 2008 Roberto Raggi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "Token.h" +#ifndef CPLUSPLUS_NO_PARSER +# include "Literals.h" +#endif + +using namespace CPlusPlus; + +static const char *token_names[] = { + (""), (""), + + (""), (""), + (""), (""), + + (""), (""), (""), + (""), (""), (""), + ("<@string literal>"), (""), + + ("&"), ("&&"), ("&="), ("->"), ("->*"), ("^"), ("^="), (":"), ("::"), + (","), ("/"), ("/="), ("."), ("..."), (".*"), ("="), ("=="), ("!"), + ("!="), (">"), (">="), (">>"), (">>="), ("{"), ("["), ("<"), ("<="), + ("<<"), ("<<="), ("("), ("-"), ("-="), ("--"), ("%"), ("%="), ("|"), + ("|="), ("||"), ("+"), ("+="), ("++"), ("#"), ("##"), ("?"), ("}"), + ("]"), (")"), (";"), ("*"), ("*="), ("~"), ("~="), + + ("asm"), ("auto"), ("bool"), ("break"), ("case"), ("catch"), ("char"), + ("class"), ("const"), ("const_cast"), ("continue"), ("default"), + ("delete"), ("do"), ("double"), ("dynamic_cast"), ("else"), ("enum"), + ("explicit"), ("export"), ("extern"), ("false"), ("float"), ("for"), + ("friend"), ("goto"), ("if"), ("inline"), ("int"), ("long"), + ("mutable"), ("namespace"), ("new"), ("operator"), ("private"), + ("protected"), ("public"), ("register"), ("reinterpret_cast"), + ("return"), ("short"), ("signed"), ("sizeof"), ("static"), + ("static_cast"), ("struct"), ("switch"), ("template"), ("this"), + ("throw"), ("true"), ("try"), ("typedef"), ("typeid"), ("typename"), + ("union"), ("unsigned"), ("using"), ("virtual"), ("void"), + ("volatile"), ("wchar_t"), ("while"), + + // gnu + ("__attribute__"), ("__typeof__"), + + // objc @keywords + ("@catch"), ("@class"), ("@compatibility_alias"), ("@defs"), ("@dynamic"), + ("@encode"), ("@end"), ("@finally"), ("@implementation"), ("@interface"), + ("@not_keyword"), ("@optional"), ("@package"), ("@private"), ("@property"), + ("@protected"), ("@protocol"), ("@public"), ("@required"), ("@selector"), + ("@synchronized"), ("@synthesize"), ("@throw"), ("@try"), + + // Qt keywords + ("SIGNAL"), ("SLOT"), ("Q_SIGNAL"), ("Q_SLOT"), ("signals"), ("slots"), + ("Q_FOREACH"), ("Q_D"), ("Q_Q"), + ("Q_INVOKABLE"), ("Q_PROPERTY"), ("Q_INTERFACES"), ("Q_ENUMS"), ("Q_FLAGS"), + ("Q_PRIVATE_SLOT"), ("Q_DECLARE_INTERFACE"), ("Q_OBJECT"), ("Q_GADGET"), + ("Q_NAMESPACE"), + +}; + +void Token::reset() +{ + f = {}; + offset = 0; + ptr = nullptr; +} + +const char *Token::name(int kind) +{ return token_names[kind]; } + +#ifndef CPLUSPLUS_NO_PARSER +const char *Token::spell() const +{ + switch (f.kind) { + case T_IDENTIFIER: + return identifier->chars(); + + case T_NUMERIC_LITERAL: + case T_CHAR_LITERAL: + case T_STRING_LITERAL: + case T_AT_STRING_LITERAL: + case T_ANGLE_STRING_LITERAL: + case T_WIDE_CHAR_LITERAL: + case T_WIDE_STRING_LITERAL: + return literal->chars(); + + default: + return token_names[f.kind]; + } // switch +} +#endif + + diff --git a/src/plugins/scanner/cpp/Token.h b/src/plugins/scanner/cpp/Token.h new file mode 100644 index 00000000..a042c108 --- /dev/null +++ b/src/plugins/scanner/cpp/Token.h @@ -0,0 +1,368 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// Copyright (c) 2008 Roberto Raggi +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef CPLUSPLUS_TOKEN_H +#define CPLUSPLUS_TOKEN_H + +#include "CPlusPlusForwardDeclarations.h" + +namespace CPlusPlus { + +enum Kind { + T_EOF_SYMBOL = 0, + T_ERROR, + + T_CPP_COMMENT, + T_CPP_DOXY_COMMENT, + T_COMMENT, + T_DOXY_COMMENT, + T_IDENTIFIER, + + T_FIRST_LITERAL, + T_NUMERIC_LITERAL = T_FIRST_LITERAL, + T_CHAR_LITERAL, + T_WIDE_CHAR_LITERAL, + T_STRING_LITERAL, + T_WIDE_STRING_LITERAL, + T_AT_STRING_LITERAL, + T_ANGLE_STRING_LITERAL, + T_LAST_LITERAL = T_ANGLE_STRING_LITERAL, + + T_FIRST_OPERATOR, + T_AMPER = T_FIRST_OPERATOR, + T_AMPER_AMPER, + T_AMPER_EQUAL, + T_ARROW, + T_ARROW_STAR, + T_CARET, + T_CARET_EQUAL, + T_COLON, + T_COLON_COLON, + T_COMMA, + T_SLASH, + T_SLASH_EQUAL, + T_DOT, + T_DOT_DOT_DOT, + T_DOT_STAR, + T_EQUAL, + T_EQUAL_EQUAL, + T_EXCLAIM, + T_EXCLAIM_EQUAL, + T_GREATER, + T_GREATER_EQUAL, + T_GREATER_GREATER, + T_GREATER_GREATER_EQUAL, + T_LBRACE, + T_LBRACKET, + T_LESS, + T_LESS_EQUAL, + T_LESS_LESS, + T_LESS_LESS_EQUAL, + T_LPAREN, + T_MINUS, + T_MINUS_EQUAL, + T_MINUS_MINUS, + T_PERCENT, + T_PERCENT_EQUAL, + T_PIPE, + T_PIPE_EQUAL, + T_PIPE_PIPE, + T_PLUS, + T_PLUS_EQUAL, + T_PLUS_PLUS, + T_POUND, + T_POUND_POUND, + T_QUESTION, + T_RBRACE, + T_RBRACKET, + T_RPAREN, + T_SEMICOLON, + T_STAR, + T_STAR_EQUAL, + T_TILDE, + T_TILDE_EQUAL, + T_LAST_OPERATOR = T_TILDE_EQUAL, + + T_FIRST_KEYWORD, + T_ASM = T_FIRST_KEYWORD, + T_AUTO, + T_BOOL, + T_BREAK, + T_CASE, + T_CATCH, + T_CHAR, + T_CLASS, + T_CONST, + T_CONST_CAST, + T_CONTINUE, + T_DEFAULT, + T_DELETE, + T_DO, + T_DOUBLE, + T_DYNAMIC_CAST, + T_ELSE, + T_ENUM, + T_EXPLICIT, + T_EXPORT, + T_EXTERN, + T_FALSE, + T_FLOAT, + T_FOR, + T_FRIEND, + T_GOTO, + T_IF, + T_INLINE, + T_INT, + T_LONG, + T_MUTABLE, + T_NAMESPACE, + T_NEW, + T_OPERATOR, + T_PRIVATE, + T_PROTECTED, + T_PUBLIC, + T_REGISTER, + T_REINTERPRET_CAST, + T_RETURN, + T_SHORT, + T_SIGNED, + T_SIZEOF, + T_STATIC, + T_STATIC_CAST, + T_STRUCT, + T_SWITCH, + T_TEMPLATE, + T_THIS, + T_THROW, + T_TRUE, + T_TRY, + T_TYPEDEF, + T_TYPEID, + T_TYPENAME, + T_UNION, + T_UNSIGNED, + T_USING, + T_VIRTUAL, + T_VOID, + T_VOLATILE, + T_WCHAR_T, + T_WHILE, + + T___ATTRIBUTE__, + T___TYPEOF__, + + // obj c++ @ keywords + T_FIRST_OBJC_AT_KEYWORD, + + T_AT_CATCH = T_FIRST_OBJC_AT_KEYWORD, + T_AT_CLASS, + T_AT_COMPATIBILITY_ALIAS, + T_AT_DEFS, + T_AT_DYNAMIC, + T_AT_ENCODE, + T_AT_END, + T_AT_FINALLY, + T_AT_IMPLEMENTATION, + T_AT_INTERFACE, + T_AT_NOT_KEYWORD, + T_AT_OPTIONAL, + T_AT_PACKAGE, + T_AT_PRIVATE, + T_AT_PROPERTY, + T_AT_PROTECTED, + T_AT_PROTOCOL, + T_AT_PUBLIC, + T_AT_REQUIRED, + T_AT_SELECTOR, + T_AT_SYNCHRONIZED, + T_AT_SYNTHESIZE, + T_AT_THROW, + T_AT_TRY, + + T_LAST_OBJC_AT_KEYWORD = T_AT_TRY, + + T_FIRST_QT_KEYWORD, + + // Qt keywords + T_SIGNAL = T_FIRST_QT_KEYWORD, + T_SLOT, + T_Q_SIGNAL, + T_Q_SLOT, + T_Q_SIGNALS, + T_Q_SLOTS, + T_Q_FOREACH, + T_Q_D, + T_Q_Q, + T_Q_INVOKABLE, + T_Q_PROPERTY, + T_Q_INTERFACES, + T_Q_ENUMS, + T_Q_FLAGS, + T_Q_PRIVATE_SLOT, + T_Q_DECLARE_INTERFACE, + T_Q_OBJECT, + T_Q_GADGET, + T_Q_NAMESPACE, + T_LAST_KEYWORD = T_Q_NAMESPACE, + + // aliases + T_OR = T_PIPE_PIPE, + T_AND = T_AMPER_AMPER, + T_NOT = T_EXCLAIM, + T_XOR = T_CARET, + T_BITOR = T_PIPE, + T_COMPL = T_TILDE, + T_OR_EQ = T_PIPE_EQUAL, + T_AND_EQ = T_AMPER_EQUAL, + T_BITAND = T_AMPER, + T_NOT_EQ = T_EXCLAIM_EQUAL, + T_XOR_EQ = T_CARET_EQUAL, + + T___ASM = T_ASM, + T___ASM__ = T_ASM, + + T_TYPEOF = T___TYPEOF__, + T___TYPEOF = T___TYPEOF__, + + T___INLINE = T_INLINE, + T___INLINE__ = T_INLINE, + + T___CONST = T_CONST, + T___CONST__ = T_CONST, + + T___VOLATILE = T_VOLATILE, + T___VOLATILE__ = T_VOLATILE, + + T___ATTRIBUTE = T___ATTRIBUTE__ +}; + +class CPLUSPLUS_EXPORT Token +{ +public: + inline bool is(unsigned k) const { return f.kind == k; } + inline bool isNot(unsigned k) const { return f.kind != k; } +#ifndef CPLUSPLUS_NO_PARSER + const char *spell() const; +#endif + void reset(); + + inline unsigned kind() const { return f.kind; } + inline bool newline() const { return f.newline; } + inline bool whitespace() const { return f.whitespace; } + inline bool joined() const { return f.joined; } + inline bool expanded() const { return f.expanded; } + inline bool generated() const { return f.generated; } + inline unsigned length() const { return f.length; } + + inline unsigned begin() const + { return offset; } + + inline unsigned end() const + { return offset + f.length; } + + inline bool isLiteral() const + { return f.kind >= T_FIRST_LITERAL && f.kind <= T_LAST_LITERAL; } + + inline bool isOperator() const + { return f.kind >= T_FIRST_OPERATOR && f.kind <= T_LAST_OPERATOR; } + + inline bool isKeyword() const + { return f.kind >= T_FIRST_KEYWORD && f.kind < T_FIRST_QT_KEYWORD; } + + inline bool isComment() const + { return f.kind == T_COMMENT || f.kind == T_DOXY_COMMENT || + f.kind == T_CPP_COMMENT || f.kind == T_CPP_DOXY_COMMENT; } + + inline bool isObjCAtKeyword() const + { return f.kind >= T_FIRST_OBJC_AT_KEYWORD && f.kind <= T_LAST_OBJC_AT_KEYWORD; } + + static const char *name(int kind); + +public: + struct Flags { + unsigned kind : 8; + unsigned newline : 1; + unsigned whitespace : 1; + unsigned joined : 1; + unsigned expanded : 1; + unsigned generated : 1; + unsigned pad : 3; + unsigned length : 16; + }; + Flags f{}; + + unsigned offset = 0; + + union { + void *ptr = nullptr; +#ifndef CPLUSPLUS_NO_PARSER + const Literal *literal; + const NumericLiteral *number; + const StringLiteral *string; + const Identifier *identifier; +#endif + unsigned close_brace; + unsigned lineno; + }; +}; + +} // end of namespace CPlusPlus + + +#endif // CPLUSPLUS_TOKEN_H diff --git a/src/plugins/scanner/cpp/cpp.pri b/src/plugins/scanner/cpp/cpp.pri new file mode 100644 index 00000000..515e6563 --- /dev/null +++ b/src/plugins/scanner/cpp/cpp.pri @@ -0,0 +1 @@ +qbsPluginTarget = qbs_cpp_scanner diff --git a/src/plugins/scanner/cpp/cpp.pro b/src/plugins/scanner/cpp/cpp.pro new file mode 100644 index 00000000..12a6d7b6 --- /dev/null +++ b/src/plugins/scanner/cpp/cpp.pro @@ -0,0 +1,10 @@ +include(cpp.pri) +include(../../plugins.pri) +DEFINES += CPLUSPLUS_NO_PARSER + +QT = core + +HEADERS += CPlusPlusForwardDeclarations.h Lexer.h Token.h ../scanner.h \ + cpp_global.h +SOURCES += Lexer.cpp Token.cpp \ + cppscanner.cpp diff --git a/src/plugins/scanner/cpp/cpp.qbs b/src/plugins/scanner/cpp/cpp.qbs new file mode 100644 index 00000000..93a64e7c --- /dev/null +++ b/src/plugins/scanner/cpp/cpp.qbs @@ -0,0 +1,18 @@ +import qbs 1.0 +import "../../qbsplugin.qbs" as QbsPlugin + +QbsPlugin { + cpp.defines: base.concat(["CPLUSPLUS_NO_PARSER"]) + name: "qbs_cpp_scanner" + files: [ + "../scanner.h", + "CPlusPlusForwardDeclarations.h", + "Lexer.cpp", + "Lexer.h", + "Token.cpp", + "Token.h", + "cpp_global.h", + "cppscanner.cpp" + ] +} + diff --git a/src/plugins/scanner/cpp/cpp_global.h b/src/plugins/scanner/cpp/cpp_global.h new file mode 100644 index 00000000..80b10145 --- /dev/null +++ b/src/plugins/scanner/cpp/cpp_global.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** 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 CPP_GLOBAL_H +#define CPP_GLOBAL_H + +#if defined(WIN32) || defined(_WIN32) +#define CPPSCANNER_EXPORT __declspec(dllexport) +#else +#define CPPSCANNER_EXPORT __attribute__((visibility("default"))) +#endif + +#endif // CPP_GLOBAL_H diff --git a/src/plugins/scanner/cpp/cppscanner.cpp b/src/plugins/scanner/cpp/cppscanner.cpp new file mode 100644 index 00000000..0acb2517 --- /dev/null +++ b/src/plugins/scanner/cpp/cppscanner.cpp @@ -0,0 +1,335 @@ +/**************************************************************************** +** +** 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 "../scanner.h" +#include "cpp_global.h" +#include "Lexer.h" + +using namespace CPlusPlus; + +#include +#include + +#ifdef Q_OS_UNIX +#include +#include +#include +#include +#include +#else +#include +#endif + +#include +#include +#include + +#include +#include + +struct ScanResult +{ + char *fileName = nullptr; + int size = 0; + int flags = 0; +}; + +struct Opaq +{ + enum FileType + { + FT_UNKNOWN, FT_HPP, FT_CPP, FT_C, FT_OBJC, FT_OBJCPP, FT_RC + }; + + Opaq() + : +#ifdef Q_OS_UNIX + fd(0), + mapl(0), +#endif + fileContent(nullptr), + fileType(FT_UNKNOWN), + hasQObjectMacro(false), + hasPluginMetaDataMacro(false), + currentResultIndex(0) + {} + + ~Opaq() + { +#ifdef Q_OS_UNIX + if (fileContent) + munmap(fileContent, mapl); + if (fd) + close(fd); +#endif + } + +#ifdef Q_OS_WIN + QFile file; +#endif +#ifdef Q_OS_UNIX + int fd; + size_t mapl; +#endif + + QString fileName; + char *fileContent; + FileType fileType; + QList includedFiles; + bool hasQObjectMacro; + bool hasPluginMetaDataMacro; + int currentResultIndex; +}; + +class TokenComparator +{ + const char * const m_fileContent; +public: + TokenComparator(const char *fileContent) + : m_fileContent(fileContent) + { + } + + bool equals(const Token &tk, const QLatin1String &literal) const + { + return static_cast(tk.length()) == literal.size() + && memcmp(m_fileContent + tk.begin(), literal.data(), literal.size()) == 0; + } +}; + +static void scanCppFile(void *opaq, CPlusPlus::Lexer &yylex, bool scanForFileTags, + bool scanForDependencies) +{ + const QLatin1String includeLiteral("include"); + const QLatin1String importLiteral("import"); + const QLatin1String defineLiteral("define"); + const QLatin1String qobjectLiteral("Q_OBJECT"); + const QLatin1String qgadgetLiteral("Q_GADGET"); + const QLatin1String qnamespaceLiteral("Q_NAMESPACE"); + const QLatin1String pluginMetaDataLiteral("Q_PLUGIN_METADATA"); + const auto opaque = static_cast(opaq); + const TokenComparator tc(opaque->fileContent); + Token tk; + Token oldTk; + ScanResult scanResult; + + yylex(&tk); + + while (tk.isNot(T_EOF_SYMBOL)) { + if (tk.newline() && tk.is(T_POUND)) { + yylex(&tk); + + if (scanForDependencies && !tk.newline() && tk.is(T_IDENTIFIER)) { + if (tc.equals(tk, includeLiteral) || tc.equals(tk, importLiteral)) + { + yylex.setScanAngleStringLiteralTokens(true); + yylex(&tk); + yylex.setScanAngleStringLiteralTokens(false); + + if (!tk.newline() && (tk.is(T_STRING_LITERAL) || tk.is(T_ANGLE_STRING_LITERAL))) { + scanResult.size = int(tk.length() - 2); + if (tk.is(T_STRING_LITERAL)) + scanResult.flags = SC_LOCAL_INCLUDE_FLAG; + else + scanResult.flags = SC_GLOBAL_INCLUDE_FLAG; + scanResult.fileName = opaque->fileContent + tk.begin() + 1; + opaque->includedFiles.push_back(scanResult); + } + } + } + } else if (tk.is(T_IDENTIFIER)) { + if (scanForFileTags) { + if (oldTk.is(T_IDENTIFIER) && tc.equals(oldTk, defineLiteral)) { + // Someone was clever and redefined Q_OBJECT or Q_PLUGIN_METADATA. + // Example: iplugin.h in Qt Creator. + } else { + if (tc.equals(tk, qobjectLiteral) || tc.equals(tk, qgadgetLiteral) || + tc.equals(tk, qnamespaceLiteral)) + { + opaque->hasQObjectMacro = true; + } else if (tc.equals(tk, pluginMetaDataLiteral)) + { + opaque->hasPluginMetaDataMacro = true; + } + if (!scanForDependencies && opaque->hasQObjectMacro + && (opaque->hasPluginMetaDataMacro + || opaque->fileType == Opaq::FT_CPP + || opaque->fileType == Opaq::FT_OBJCPP)) + break; + } + } + + } + oldTk = tk; + yylex(&tk); + } +} + +static void *openScanner(const unsigned short *filePath, const char *fileTags, int flags) +{ + std::unique_ptr opaque(new Opaq); + opaque->fileName = QString::fromUtf16(filePath); + const int fileTagsLength = static_cast(std::strlen(fileTags)); + const QList &tagList = QByteArray::fromRawData(fileTags, fileTagsLength).split(','); + if (tagList.contains("hpp")) + opaque->fileType = Opaq::FT_HPP; + else if (tagList.contains("cpp")) + opaque->fileType = Opaq::FT_CPP; + else if (tagList.contains("objcpp")) + opaque->fileType = Opaq::FT_OBJCPP; + else + opaque->fileType = Opaq::FT_UNKNOWN; + + size_t mapl = 0; +#ifdef Q_OS_UNIX + QString filePathS = opaque->fileName; + + opaque->fd = open(qPrintable(filePathS), O_RDONLY); + if (opaque->fd == -1) { + opaque->fd = 0; + return nullptr; + } + + struct stat s{}; + int r = fstat(opaque->fd, &s); + if (r != 0) + return nullptr; + mapl = s.st_size; + opaque->mapl = mapl; + + void *vmap = mmap(0, s.st_size, PROT_READ, MAP_PRIVATE, opaque->fd, 0); + if (vmap == MAP_FAILED) // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) + return nullptr; +#else + opaque->file.setFileName(opaque->fileName); + if (!opaque->file.open(QFile::ReadOnly)) + return nullptr; + + uchar *vmap = opaque->file.map(0, opaque->file.size()); + mapl = opaque->file.size(); +#endif + if (!vmap) + return nullptr; + + opaque->fileContent = reinterpret_cast(vmap); + + // Check for UTF-8 Byte Order Mark (BOM). Skip if found. + if (mapl >= 3 + && opaque->fileContent[0] == char(0xef) + && opaque->fileContent[1] == char(0xbb) + && opaque->fileContent[2] == char(0xbf)) { + opaque->fileContent += 3; + mapl -= 3; + } + + CPlusPlus::Lexer lex(opaque->fileContent, opaque->fileContent + mapl); + scanCppFile(opaque.get(), lex, flags & ScanForFileTagsFlag, flags & ScanForDependenciesFlag); + return opaque.release(); +} + +static void closeScanner(void *ptr) +{ + const auto opaque = static_cast(ptr); + delete opaque; +} + +static const char *next(void *opaq, int *size, int *flags) +{ + const auto opaque = static_cast(opaq); + if (opaque->currentResultIndex < opaque->includedFiles.size()) { + const ScanResult &result = opaque->includedFiles.at(opaque->currentResultIndex); + ++opaque->currentResultIndex; + *size = result.size; + *flags = result.flags; + return result.fileName; + } + *size = 0; + *flags = 0; + return nullptr; +} + +static const char **additionalFileTags(void *opaq, int *size) +{ + static const char *thMocCpp[] = { "moc_cpp" }; + static const char *thMocHpp[] = { "moc_hpp" }; + static const char *thMocPluginHpp[] = { "moc_hpp_plugin" }; + static const char *thMocPluginCpp[] = { "moc_cpp_plugin" }; + + const auto opaque = static_cast(opaq); + if (opaque->hasQObjectMacro) { + *size = 1; + switch (opaque->fileType) { + case Opaq::FT_CPP: + case Opaq::FT_OBJCPP: + return opaque->hasPluginMetaDataMacro ? thMocPluginCpp : thMocCpp; + case Opaq::FT_HPP: + return opaque->hasPluginMetaDataMacro ? thMocPluginHpp : thMocHpp; + default: + break; + } + } + *size = 0; + return nullptr; +} + +ScannerPlugin includeScanner = +{ + "include_scanner", + "cpp,cpp_pch_src,c,c_pch_src,objcpp,objcpp_pch_src,objc,objc_pch_src,rc", + openScanner, + closeScanner, + next, + additionalFileTags, + ScannerUsesCppIncludePaths | ScannerRecursiveDependencies +}; + +ScannerPlugin *cppScanners[] = { &includeScanner, NULL }; + +static void QbsCppScannerPluginLoad() +{ + qbs::Internal::ScannerPluginManager::instance()->registerPlugins(cppScanners); +} + +static void QbsCppScannerPluginUnload() +{ +} + +QBS_REGISTER_STATIC_PLUGIN(extern "C" CPPSCANNER_EXPORT, qbs_cpp_scanner, + QbsCppScannerPluginLoad, QbsCppScannerPluginUnload) + diff --git a/src/plugins/scanner/qt/qt.pri b/src/plugins/scanner/qt/qt.pri new file mode 100644 index 00000000..71e3a8a4 --- /dev/null +++ b/src/plugins/scanner/qt/qt.pri @@ -0,0 +1 @@ +qbsPluginTarget = qbs_qt_scanner diff --git a/src/plugins/scanner/qt/qt.pro b/src/plugins/scanner/qt/qt.pro new file mode 100644 index 00000000..f231ea9e --- /dev/null +++ b/src/plugins/scanner/qt/qt.pro @@ -0,0 +1,8 @@ +include(qt.pri) +include(../../plugins.pri) + +QT = core + +HEADERS += ../scanner.h +SOURCES += \ + qtscanner.cpp diff --git a/src/plugins/scanner/qt/qt.qbs b/src/plugins/scanner/qt/qt.qbs new file mode 100644 index 00000000..a6aea1c3 --- /dev/null +++ b/src/plugins/scanner/qt/qt.qbs @@ -0,0 +1,11 @@ +import qbs 1.0 +import "../../qbsplugin.qbs" as QbsPlugin + +QbsPlugin { + name: "qbs_qt_scanner" + files: [ + "../scanner.h", + "qtscanner.cpp" + ] +} + diff --git a/src/plugins/scanner/qt/qtscanner.cpp b/src/plugins/scanner/qt/qtscanner.cpp new file mode 100644 index 00000000..22ef294d --- /dev/null +++ b/src/plugins/scanner/qt/qtscanner.cpp @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#if defined(WIN32) || defined(_WIN32) +#define SCANNER_EXPORT __declspec(dllexport) +#else +#define SCANNER_EXPORT __attribute__((visibility("default"))) +#endif + +#include "../scanner.h" + +#include +#include + +#include + +#ifdef Q_OS_UNIX +#include +#include +#include +#include +#include +#else +#include +#endif + +#include +#include + +#include + +struct OpaqQrc +{ +#ifdef Q_OS_UNIX + int fd = 0; + int mapl = 0; +#else + QFile *file = nullptr; +#endif + + char *map = nullptr; + QXmlStreamReader *xml = nullptr; + QByteArray current; + OpaqQrc() = default; + + ~OpaqQrc() + { +#ifdef Q_OS_UNIX + if (map) + munmap (map, mapl); + if (fd) + close (fd); +#else + delete file; +#endif + delete xml; + } +}; + +static void *openScannerQrc(const unsigned short *filePath, const char *fileTags, int flags) +{ + Q_UNUSED(flags); + Q_UNUSED(fileTags); + std::unique_ptr opaque(new OpaqQrc); + +#ifdef Q_OS_UNIX + QString filePathS = QString::fromUtf16(filePath); + opaque->fd = open(qPrintable(filePathS), O_RDONLY); + if (opaque->fd == -1) { + opaque->fd = 0; + return nullptr; + } + + struct stat s{}; + int r = fstat(opaque->fd, &s); + if (r != 0) + return nullptr; + const int fileSize = static_cast(s.st_size); + opaque->mapl = fileSize; + + void *map = mmap(0, s.st_size, PROT_READ, MAP_PRIVATE, opaque->fd, 0); + if (map == nullptr) + return nullptr; +#else + opaque->file = new QFile(QString::fromUtf16(filePath)); + if (!opaque->file->open(QFile::ReadOnly)) + return nullptr; + + const int fileSize = opaque->file->size(); + uchar *map = opaque->file->map(0, fileSize); + if (!map) + return nullptr; +#endif + + opaque->map = reinterpret_cast(map); + opaque->xml = new QXmlStreamReader(QByteArray::fromRawData(opaque->map, fileSize)); + + return static_cast(opaque.release()); +} + +static void closeScannerQrc(void *ptr) +{ + const auto opaque = static_cast(ptr); + delete opaque; +} + +static const char *nextQrc(void *opaq, int *size, int *flags) +{ + const auto o = static_cast(opaq); + while (!o->xml->atEnd()) { + o->xml->readNext(); + switch (o->xml->tokenType()) { + case QXmlStreamReader::StartElement: + if (o->xml->name() == QLatin1String("file")) { + o->current = o->xml->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).toUtf8(); + *flags = SC_LOCAL_INCLUDE_FLAG; + *size = o->current.size(); + return o->current.data(); + } + break; + case QXmlStreamReader::EndDocument: + return nullptr; + default: + break; + } + } + return nullptr; +} + +static const char **additionalFileTagsQrc(void *, int *size) +{ + *size = 0; + return nullptr; +} + +ScannerPlugin qrcScanner = +{ + "qt_qrc_scanner", + "qrc", + openScannerQrc, + closeScannerQrc, + nextQrc, + additionalFileTagsQrc, + NoScannerFlags +}; + +ScannerPlugin *qtScanners[] = {&qrcScanner, NULL}; + +static void QbsQtScannerPluginLoad() +{ + qbs::Internal::ScannerPluginManager::instance()->registerPlugins(qtScanners); +} + +static void QbsQtScannerPluginUnload() +{ +} + +QBS_REGISTER_STATIC_PLUGIN(extern "C" SCANNER_EXPORT, qbs_qt_scanner, + QbsQtScannerPluginLoad, QbsQtScannerPluginUnload) diff --git a/src/plugins/scanner/scanner.h b/src/plugins/scanner/scanner.h new file mode 100644 index 00000000..0d62a2ea --- /dev/null +++ b/src/plugins/scanner/scanner.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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 SCANNER_H +#define SCANNER_H + + +#ifdef __cplusplus +extern "C" { +#endif + +#define SC_LOCAL_INCLUDE_FLAG 0x1 +#define SC_GLOBAL_INCLUDE_FLAG 0x2 + +enum OpenScannerFlags +{ + ScanForDependenciesFlag = 0x01, + ScanForFileTagsFlag = 0x02 +}; + +/** + * Open a file that's going to be scanned. + * The file path encoding is UTF-16 on all platforms. + * The file tags are in CSV format. + * + * Returns a scanner handle. + */ +typedef void *(*scanOpen_f) (const unsigned short *filePath, const char *fileTags, int flags); + +/** + * Closes the given scanner handle. + */ +typedef void (*scanClose_f) (void *opaq); + +/** + * Return the next result (filename) of the scan. + */ +typedef const char *(*scanNext_f) (void *opaq, int *size, int *flags); + +/** + * Returns a list of type hints for the scanned file. + * May return null. + * + * Example: if a C++ header file contains Q_OBJECT, + * the type hint 'moc_hpp' is returned. + */ +typedef const char** (*scanAdditionalFileTags_f) (void *opaq, int *size); + +enum ScannerFlags +{ + NoScannerFlags = 0x00, + ScannerUsesCppIncludePaths = 0x01, + ScannerRecursiveDependencies = 0x02 +}; + +class ScannerPlugin +{ +public: + const char *name; + const char *fileTags; // CSV + scanOpen_f open; + scanClose_f close; + scanNext_f next; + scanAdditionalFileTags_f additionalFileTags; + int flags; +}; + +#ifdef __cplusplus +} +#endif +#endif // SCANNER_H diff --git a/src/plugins/scanner/scanner.pro b/src/plugins/scanner/scanner.pro new file mode 100644 index 00000000..68acae7d --- /dev/null +++ b/src/plugins/scanner/scanner.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs +SUBDIRS = cpp qt + diff --git a/src/plugins/use_plugin.pri b/src/plugins/use_plugin.pri new file mode 100644 index 00000000..53be9f4d --- /dev/null +++ b/src/plugins/use_plugin.pri @@ -0,0 +1,10 @@ +include(qbs_plugin_common.pri) + +qbsPluginLibName = $$qbsPluginTarget +win32:CONFIG(debug, debug|release):CONFIG(static, static|shared): \ + qbsPluginLibName = $${qbsPluginLibName}d +LIBS += -l$$qbsPluginLibName + +macos: QMAKE_LFLAGS += -Wl,-u,_qbs_static_plugin_register_$$qbsPluginTarget +!macos:gcc: QMAKE_LFLAGS += -Wl,--require-defined=qbs_static_plugin_register_$$qbsPluginTarget +msvc: QMAKE_LFLAGS += /INCLUDE:qbs_static_plugin_register_$$qbsPluginTarget diff --git a/src/shared/bundledqt/bundledqt.qbs b/src/shared/bundledqt/bundledqt.qbs new file mode 100644 index 00000000..5995ae4d --- /dev/null +++ b/src/shared/bundledqt/bundledqt.qbs @@ -0,0 +1,173 @@ +import qbs +import qbs.File +import qbs.FileInfo + +Product { + Depends { name: "qbsbuildconfig" } + Depends { name: "Qt"; submodules: ["core", "gui", "network", "printsupport", "widgets", "xml"] } + Depends { name: "Qt.test"; condition: project.withTests === true } + Depends { name: "Qt.script"; condition: !qbsbuildconfig.useBundledQtScript; required: false } + Depends { + name: "Qt"; + submodules: [ "dbus", "xcb_qpa_lib-private" ]; + required: false + } + + condition: { + if (!qbsbuildconfig.enableBundledQt) + return false; + if (Qt.core.staticBuild) + throw("Cannot bundle static Qt libraries"); + return true; + } + + readonly property string qtDebugLibrarySuffix: { + if (Qt.core.qtBuildVariant !== "debug") + return ""; + if (qbs.targetOS.contains("windows")) + return "d"; + if (qbs.targetOS.contains("darwin")) + return "_debug"; + return ""; + } + + Group { + name: "qt.conf" + files: ["qt.conf"] + qbs.install: true + qbs.installDir: qbsbuildconfig.appInstallDir + } + + Group { + name: "Qt libraries" + files: { + function getLibsForQtModule(mod) { + if (mod === "script" && !Qt[mod].present) + return []; + if ((mod !== "core") && !Qt[mod].hasLibrary) + return []; + if (Qt[mod].isStaticLibrary) + return []; + + var list = []; + if (qbs.targetOS.contains("windows")) { + var basename = FileInfo.baseName(Qt[mod].libNameForLinker); + var dir = Qt.core.binPath; + list.push(dir + "/" + basename + ".dll"); + + } else if (qbs.targetOS.contains("linux")) { + var fp = Qt[mod].libFilePath; + var basename = FileInfo.baseName(fp); + var dir = FileInfo.path(fp); + list.push(dir + "/" + basename + ".so"); + list.push(dir + "/" + basename + ".so." + Qt.core.versionMajor); + list.push(dir + "/" + basename + ".so." + Qt.core.versionMajor + "." + Qt.core.versionMinor); + list.push(fp); + + } else if (Qt.core.frameworkBuild) { + var fp = Qt[mod].libFilePathRelease; + var fpd = fp + "_debug"; + + if (qtDebugLibrarySuffix) + list.push(fpd); + + var suffix = ".framework/"; + var frameworkPath = fp.substr(0, fp.lastIndexOf(suffix) + suffix.length - 1); + var versionsPath = frameworkPath + "/Versions"; + var versionPath = versionsPath + "/" + Qt.core.versionMajor; + list.push(frameworkPath + "/Resources"); + list.push(versionPath + "/Resources/Info.plist"); + list.push(versionPath + "/" + FileInfo.fileName(fp)); + if (qtDebugLibrarySuffix) + list.push(versionPath + "/" + FileInfo.fileName(fpd)); + if (qbsbuildconfig.installApiHeaders) { + list.push(frameworkPath + "/Headers"); + list.push(versionPath + "/Headers/**"); + } + } + return list; + } + + var qtModules = Object.getOwnPropertyNames(Qt); + var libraries = Array.prototype.concat.apply([], qtModules.map(getLibsForQtModule)); + + // Qt might be bundled with additional libraries + if (qbs.targetOS.contains("linux")) { + var dir = FileInfo.path(Qt.core.libFilePathRelease); + var addons = [ "libicui18n", "libicuuc", "libicudata" ]; + addons.forEach(function(lib) { + var fp = dir + "/" + lib + ".so"; + if (File.exists(fp)) + libraries.push(fp + "*"); + }); + } + + return libraries; + } + + qbs.install: true + qbs.installDir: qbsbuildconfig.libInstallDir + qbs.installSourceBase: qbs.targetOS.contains("windows") ? Qt.core.binPath : Qt.core.libPath + } + + Group { + name: "Windows Plugins" + condition: qbs.targetOS.contains("windows") + prefix: Qt.core.pluginPath + "/" + files: [ + "platforms/qwindows" + qtDebugLibrarySuffix + cpp.dynamicLibrarySuffix, + "styles/qwindowsvistastyle" + qtDebugLibrarySuffix + cpp.dynamicLibrarySuffix + ] + qbs.install: true + qbs.installDir: "plugins" + qbs.installSourceBase: prefix + } + + Group { + name: "macOS Plugins" + condition: qbs.targetOS.contains("darwin") + prefix: Qt.core.pluginPath + "/" + files: [ + "platforms/libqcocoa" + qtDebugLibrarySuffix + cpp.dynamicLibrarySuffix, + "styles/libqmacstyle" + qtDebugLibrarySuffix + cpp.dynamicLibrarySuffix + ] + qbs.install: true + qbs.installDir: "plugins" + qbs.installSourceBase: prefix + } + + Group { + name: "Linux Plugins" + condition: qbs.targetOS.contains("linux") + prefix: Qt.core.pluginPath + "/" + files: [ + "platforms/libqxcb" + cpp.dynamicLibrarySuffix, + "platformthemes/libqgtk3" + cpp.dynamicLibrarySuffix + ] + qbs.install: true + qbs.installDir: "plugins" + qbs.installSourceBase: prefix + } + + Group { + name: "MinGW Runtime DLLs" + condition: qbs.targetOS.contains("windows") && qbs.toolchain.contains("mingw") + + files: { + var libFileGlobs = [ + "*libgcc_s*.dll", + "*libstdc++-6.dll", + "*libwinpthread-1.dll" + ]; + var searchPaths = cpp.compilerLibraryPaths; + return Array.prototype.concat.apply([], searchPaths.map(function(path) { + return libFileGlobs.map(function(glob) { + return path + "/" + glob; + }); + })); + } + + qbs.install: true + qbs.installDir: "bin" + } +} diff --git a/src/shared/bundledqt/qt.conf b/src/shared/bundledqt/qt.conf new file mode 100644 index 00000000..7963d664 --- /dev/null +++ b/src/shared/bundledqt/qt.conf @@ -0,0 +1,2 @@ +[Paths] +Prefix = .. diff --git a/src/shared/json/README.md b/src/shared/json/README.md new file mode 100644 index 00000000..fa4a151a --- /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 00000000..949e2745 --- /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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 +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 +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); } +}; + +using offset = uint32_t; + +// 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(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(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(((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 {(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(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 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(nullptr), 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(this), o); + } + + JsonArray toArray(Array *a) const + { + return JsonArray(const_cast(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); + const auto h = (Header *)raw; + h->tag = JsonDocument::BinaryFormatTag; + h->version = 1; + const auto 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 offsets; + + Entry *entryAt(size_t i) const { + return reinterpret_cast(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(nullptr), t(type) +{ +} + +/*! + \internal + */ +JsonValue::JsonValue(Internal::Data *data, Internal::Base *base, const Internal::Value &v) + : d(nullptr), 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(nullptr), t(Bool) +{ + this->b = b; +} + +/*! + Creates a value of type Double, with value \a n. + */ +JsonValue::JsonValue(double n) + : d(nullptr), t(Double) +{ + this->dbl = n; +} + +/*! + \overload + Creates a value of type Double, with value \a n. + */ +JsonValue::JsonValue(int n) + : d(nullptr), 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(nullptr), t(Double) +{ + this->dbl = double(n); +} + +/*! + Creates a value of type String, with value \a s. + */ +JsonValue::JsonValue(const std::string &s) + : d(nullptr), t(String) +{ + stringData = new Internal::SharedString; + stringData->s = s; + stringData->ref.ref(); +} + +JsonValue::JsonValue(const char *s) + : d(nullptr), 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(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(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({}); +} + +/*! + 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(base)) + == JsonArray(other.d, static_cast(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(base)) + == JsonObject(other.d, static_cast(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(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(nullptr), a(nullptr) +{ +} + +JsonArray::JsonArray(std::initializer_list args) + : d(nullptr), a(nullptr) +{ + for (const auto &arg : args) + append(arg); +} + +/*! + \fn JsonArray::JsonArray(std::initializer_list 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::Undefined}; + + return {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::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 {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(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(d->header->root()); +} + +/*! + \internal + */ +void JsonArray::compact() +{ + if (!d || !d->compactionCounter) + return; + + detach(); + d->compact(); + a = static_cast(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(nullptr), o(nullptr) +{ +} + +JsonObject::JsonObject(std::initializer_list > args) + : d(nullptr), o(nullptr) +{ + for (const auto &arg : args) + insert(arg.first, arg.second); +} + +/*! + \fn JsonObject::JsonObject(std::initializer_list > 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> 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::Undefined}; + + bool keyExists; + int i = o->indexOf(key, &keyExists); + if (!keyExists) + return {JsonValue::Undefined}; + return {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 {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((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 {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::Undefined}; + + bool keyExists; + int index = o->indexOf(key, &keyExists); + if (!keyExists) + return {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 {this, int(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 {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 {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(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(d->header->root()); +} + +/*! + \internal + */ +void JsonObject::compact() +{ + if (!d || !d->compactionCounter) + return; + + detach(); + d->compact(); + o = static_cast(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::Undefined}; + + Internal::Entry *e = o->entryAt(i); + return {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(nullptr) +{ +} + +/*! + * Creates a JsonDocument from \a object. + */ +JsonDocument::JsonDocument(const JsonObject &object) + : d(nullptr) +{ + setObject(object); +} + +/*! + * Constructs a JsonDocument from \a array. + */ +JsonDocument::JsonDocument(const JsonArray &array) + : d(nullptr) +{ + 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 {}; + } + + const auto d = new Internal::Data((char *)data, size); + d->ownsData = false; + + if (validation != BypassValidation && !d->valid()) { + delete d; + return {}; + } + + return {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 nullptr; + } + *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 {}; + + 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 {}; + + const uint32_t size = sizeof(Internal::Header) + root.size; + char *raw = (char *)malloc(size); + if (!raw) + return {}; + + memcpy(raw, data.data(), size); + const auto d = new Internal::Data(raw, size); + + if (validation != BypassValidation && !d->valid()) { + delete d; + return {}; + } + + return {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(d->header->root()), json, 0, (format == Compact)); + else + Internal::objectToJson(static_cast(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(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; + + const auto 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; + + const auto 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(b)); + } + return {}; +} + +/*! + 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(b)); + } + return {}; +} + +/*! + 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(d->header->root())) + == JsonObject(other.d, static_cast(other.d->header->root())); + else + return JsonArray(d, static_cast(d->header->root())) + == JsonArray(other.d, static_cast(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) +{ + const auto 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::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(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(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(nullptr), 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(std::max(end - json, std::ptrdiff_t(256))); + data = (char *)malloc(dataLength); + + // fill in Header data + const auto 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; + } + const auto d = new Data(data, current); + return {d}; + } + +error: +#ifdef PARSER_DEBUG + std::cerr << ">>>>> parser error"; +#endif + if (error) { + error->offset = static_cast(json - head); + error->error = lastError; + } + free(data); + return {}; +} + + +void Parser::ParsedObject::insert(uint32_t offset) +{ + const auto newEntry = reinterpret_cast(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.empty()) { + int tableSize = static_cast(parsedObject.offsets.size()) * sizeof(uint32_t); + table = reserveSpace(tableSize); + memcpy(data + table, &*parsedObject.offsets.begin(), tableSize); + } + + const auto o = (Object *)(data + objectOffset); + o->tableOffset = table - objectOffset; + o->size = current - objectOffset; + o->is_object = true; + o->length = static_cast(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 + const auto 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 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.empty()) { + int tableSize = static_cast(values.size() * sizeof(Value)); + table = reserveSpace(tableSize); + memcpy(data + table, values.data(), tableSize); + } + + const auto a = (Array *)(data + arrayOffset); + a->tableOffset = table - arrayOffset; + a->size = current - arrayOffset; + a->is_object = false; + a->length = static_cast(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(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(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(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) { + const auto o = static_cast(base); + for (int i = 0; i < (int)o->length; ++i) + reserve += o->entryAt(i)->usedStorage(o); + } else { + const auto a = static_cast(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; + const auto 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) { + const auto o = static_cast(base); + const auto no = static_cast(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 { + const auto a = static_cast(base); + const auto na = static_cast(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(header->root())->isValid(); + else + res = static_cast(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(base(b))->isValid(); + if (type == JsonValue::Object) + return static_cast(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(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 00000000..6cb6ec1a --- /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 +#include +#include +#include + +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 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; + using iterator_category = std::random_access_iterator_tag; + using difference_type = int; + using value_type = JsonValue; + using reference = JsonValueRef; + using pointer = JsonValueRefPtr; + + 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; + using iterator_category = std::random_access_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = JsonValue; + using reference = JsonValue; + using pointer = JsonValuePtr; + + 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(); } + using size_type = int ; + using value_type = JsonValue; + using pointer = value_type *; + using const_pointer = const value_type *; + using reference = JsonValueRef; + using const_reference = JsonValue; + using difference_type = int; + +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 > args); + ~JsonObject(); + + JsonObject(const JsonObject &other); + JsonObject &operator =(const JsonObject &other); + + using Keys = std::vector; + 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: + using iterator_category = std::bidirectional_iterator_tag; + using difference_type = int; + using value_type = JsonValue; + using reference = JsonValueRef; + + 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: + using iterator_category = std::bidirectional_iterator_tag; + using difference_type = int; + using value_type = JsonValue; + using reference = JsonValue; + + 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 + using mapped_type = JsonValue; + using key_type = std::string; + using size_type = int; + + 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 00000000..1b44027c --- /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 00000000..56d20896 --- /dev/null +++ b/src/shared/json/json.qbs @@ -0,0 +1,16 @@ +import qbs + +StaticLibrary { + name: "qbsjson" + Depends { name: "cpp" } + cpp.cxxLanguageVersion: "c++14" + 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 new file mode 100644 index 00000000..ae58f41a --- /dev/null +++ b/src/src.qbs @@ -0,0 +1,13 @@ +import qbs + +Project { + references: [ + "app/apps.qbs", + "lib/libs.qbs", + "libexec/libexec.qbs", + "packages/packages.qbs", + "plugins/plugins.qbs", + "shared/json/json.qbs", + "shared/bundledqt/bundledqt.qbs", + ] +} diff --git a/static-res.pro b/static-res.pro new file mode 100644 index 00000000..cbf9aa9d --- /dev/null +++ b/static-res.pro @@ -0,0 +1,74 @@ +TEMPLATE = aux + +!isEmpty(QBS_APPS_DESTDIR): qbsbindir = $${QBS_APPS_DESTDIR} +else: qbsbindir = bin + +envSpec = +unix:qbs_disable_rpath { + include(src/library_dirname.pri) + !isEmpty(QBS_DESTDIR): qbslibdir = $$QBS_DESTDIR + else: qbslibdir = $$OUT_PWD/$$QBS_LIBRARY_DIRNAME + macos: envVar = DYLD_LIBRARY_PATH + else: envVar = LD_LIBRARY_PATH + oldVal = $$getenv($$envVar) + newVal = $$qbslibdir + !isEmpty(oldVal): newVal = $$newVal:$$oldVal + envSpec = $$envVar=$$newVal +} + +builddirname = qbsres +typedescdir = share/qbs/qml-type-descriptions +typedescdir_src = $$builddirname/default/install-root/$$typedescdir +!isEmpty(QBS_QML_TYPE_DESCRIPTIONS_BUILD_DIR): \ + typedescdir_dst = $$QBS_QML_TYPE_DESCRIPTIONS_BUILD_DIR +else:!isEmpty(QBS_RESOURCES_BUILD_DIR): \ + typedescdir_dst = $$QBS_RESOURCES_BUILD_DIR/$$typedescdir +else: \ + typedescdir_dst = $$typedescdir + +qbsres.target = $$builddirname/default/default.bg +qbsres.commands = \ + $$envSpec $$shell_quote($$shell_path($$qbsbindir/qbs)) \ + build \ + --settings-dir $$shell_quote($$builddirname/settings) \ + -f $$shell_quote($$PWD/qbs.qbs) \ + -d $$shell_quote($$builddirname) \ + -p $$shell_quote("qbs resources") \ + qbs.installPrefix:undefined \ + project.withCode:false \ + project.withDocumentation:false \ + profile:none + +qbsqmltypes.target = $$typedescdir_dst/qbs.qmltypes +qbsqmltypes.commands = \ + $$sprintf($$QMAKE_MKDIR_CMD, \ + $$shell_quote($$shell_path($$typedescdir_dst))) $$escape_expand(\\n\\t) \ + $$QMAKE_COPY \ + $$shell_quote($$shell_path($$typedescdir_src/qbs.qmltypes)) \ + $$shell_quote($$shell_path($$typedescdir_dst/qbs.qmltypes)) +qbsqmltypes.depends += qbsres + +qbsbundle.target = $$typedescdir_dst/qbs-bundle.json +qbsbundle.commands = \ + $$sprintf($$QMAKE_MKDIR_CMD, \ + $$shell_quote($$shell_path($$typedescdir_dst))) $$escape_expand(\\n\\t) \ + $$QMAKE_COPY \ + $$shell_quote($$shell_path($$typedescdir_src/qbs-bundle.json)) \ + $$shell_quote($$shell_path($$typedescdir_dst/qbs-bundle.json)) +qbsbundle.depends += qbsres + +QMAKE_EXTRA_TARGETS += qbsres qbsqmltypes qbsbundle + +PRE_TARGETDEPS += $$qbsqmltypes.target $$qbsbundle.target + +include(src/install_prefix.pri) + +qbstypedescfiles.files = $$qbsqmltypes.target $$qbsbundle.target +!isEmpty(QBS_QML_TYPE_DESCRIPTIONS_INSTALL_DIR): \ + installPrefix = $${QBS_QML_TYPE_DESCRIPTIONS_INSTALL_DIR} +else:!isEmpty(QBS_RESOURCES_INSTALL_DIR): \ + installPrefix = $${QBS_RESOURCES_INSTALL_DIR}/$$typedescdir +else: \ + installPrefix = $${QBS_INSTALL_PREFIX}/$$typedescdir +qbstypedescfiles.path = $${installPrefix} +INSTALLS += qbstypedescfiles diff --git a/static.pro b/static.pro new file mode 100644 index 00000000..86a9db24 --- /dev/null +++ b/static.pro @@ -0,0 +1,83 @@ +TEMPLATE = aux + +DATA_DIRS = share/qbs/imports share/qbs/modules share/qbs/module-providers +PYTHON_DATA_DIRS = src/3rdparty/python/lib +win32:DATA_FILES = $$PWD/bin/ibmsvc.xml $$PWD/bin/ibqbs.bat +LIBEXEC_FILES = $$PWD/src/3rdparty/python/bin/dmgbuild + +# For use in custom compilers which just copy files +defineReplace(stripSrcDir) { + return($$relative_path($$absolute_path($$1, $$OUT_PWD), $$_PRO_FILE_PWD_)) +} + +defineReplace(stripPythonSrcDir) { + return($$relative_path($$absolute_path($$1, $$OUT_PWD), \ + $$_PRO_FILE_PWD_/src/3rdparty/python/lib/python2.7/site-packages)) +} + +for(data_dir, DATA_DIRS) { + files = $$files($$PWD/$$data_dir/*, true) + for(file, files):!exists($$file/*):FILES += $$file +} +FILES += $$DATA_FILES + +for(data_dir, PYTHON_DATA_DIRS) { + files = $$files($$PWD/$$data_dir/*, true) + for(file, files):!exists($$file/*):PYTHON_FILES += $$file +} +PYTHON_FILES += $$PYTHON_DATA_FILES + +OTHER_FILES += $$FILES $$LIBEXEC_FILES + +!isEqual(PWD, $$OUT_PWD)|!isEmpty(QBS_RESOURCES_BUILD_DIR) { + copy2build.input = FILES + !isEmpty(QBS_RESOURCES_BUILD_DIR): \ + copy2build.output = $${QBS_RESOURCES_BUILD_DIR}/${QMAKE_FUNC_FILE_IN_stripSrcDir} + else: \ + copy2build.output = ${QMAKE_FUNC_FILE_IN_stripSrcDir} + copy2build.commands = $$QMAKE_COPY ${QMAKE_FILE_IN} ${QMAKE_FILE_OUT} + copy2build.name = COPY ${QMAKE_FILE_IN} + copy2build.CONFIG += no_link target_predeps + QMAKE_EXTRA_COMPILERS += copy2build +} + +copy2build_python.input = PYTHON_FILES +!isEmpty(QBS_RESOURCES_BUILD_DIR): \ + copy2build_python.output = \ + $${QBS_RESOURCES_BUILD_DIR}/share/qbs/python/${QMAKE_FUNC_FILE_IN_stripPythonSrcDir} +else: \ + copy2build_python.output = share/qbs/python/${QMAKE_FUNC_FILE_IN_stripPythonSrcDir} +copy2build_python.commands = $$QMAKE_COPY ${QMAKE_FILE_IN} ${QMAKE_FILE_OUT} +copy2build_python.name = COPY ${QMAKE_FILE_IN} +copy2build_python.CONFIG += no_link target_predeps +QMAKE_EXTRA_COMPILERS += copy2build_python + +libexec_copy.input = LIBEXEC_FILES +!isEmpty(QBS_LIBEXEC_DESTDIR): \ + libexec_copy.output = $${QBS_LIBEXEC_DESTDIR}/${QMAKE_FILE_IN_BASE}${QMAKE_FILE_EXT} +else: \ + libexec_copy.output = libexec/qbs/${QMAKE_FILE_IN_BASE}${QMAKE_FILE_EXT} +libexec_copy.commands = $$QMAKE_COPY ${QMAKE_FILE_IN} ${QMAKE_FILE_OUT} +libexec_copy.name = COPY ${QMAKE_FILE_IN} +libexec_copy.CONFIG += no_link target_predeps +QMAKE_EXTRA_COMPILERS += libexec_copy + +include(src/install_prefix.pri) + +share.files = share/qbs +!isEmpty(QBS_RESOURCES_INSTALL_DIR): \ + installPrefix = $${QBS_RESOURCES_INSTALL_DIR} +else: \ + installPrefix = $${QBS_INSTALL_PREFIX} +share.path = $${installPrefix}/share +examples.files = examples +examples.path = $${share.path}/qbs +python_bin.files = $$files(src/3rdparty/python/bin/*) +!isEmpty(QBS_LIBEXEC_INSTALL_DIR): \ + python_bin.path = $${QBS_LIBEXEC_INSTALL_DIR} +else: \ + python_bin.path = $${QBS_INSTALL_PREFIX}/libexec/qbs +python.files = $$files(src/3rdparty/python/lib/python2.7/site-packages/*.py, true) +python.base = $$PWD/src/3rdparty/python/lib/python2.7/site-packages +python.path = $${share.path}/qbs/python +INSTALLS += share examples python_bin python diff --git a/tests/auto/api/api.pro b/tests/auto/api/api.pro new file mode 100644 index 00000000..d9c42e7b --- /dev/null +++ b/tests/auto/api/api.pro @@ -0,0 +1,28 @@ +TARGET = tst_api + +HEADERS = tst_api.h +SOURCES = tst_api.cpp + +include(../../../src/library_dirname.pri) +isEmpty(QBS_RELATIVE_LIBEXEC_PATH) { + win32:QBS_RELATIVE_LIBEXEC_PATH=. + else:QBS_RELATIVE_LIBEXEC_PATH=../libexec/qbs +} +isEmpty(QBS_RELATIVE_PLUGINS_PATH):QBS_RELATIVE_PLUGINS_PATH=../$${QBS_LIBRARY_DIRNAME} +isEmpty(QBS_RELATIVE_SEARCH_PATH):QBS_RELATIVE_SEARCH_PATH=.. +DEFINES += QBS_RELATIVE_LIBEXEC_PATH=\\\"$${QBS_RELATIVE_LIBEXEC_PATH}\\\" +DEFINES += QBS_RELATIVE_PLUGINS_PATH=\\\"$${QBS_RELATIVE_PLUGINS_PATH}\\\" +DEFINES += QBS_RELATIVE_SEARCH_PATH=\\\"$${QBS_RELATIVE_SEARCH_PATH}\\\" +qbs_enable_project_file_updates:DEFINES += QBS_ENABLE_PROJECT_FILE_UPDATES + +include(../auto.pri) + +DATA_DIRS = testdata + +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/api/api.qbs b/tests/auto/api/api.qbs new file mode 100644 index 00000000..09e0af7d --- /dev/null +++ b/tests/auto/api/api.qbs @@ -0,0 +1,20 @@ +import qbs +import qbs.Utilities + +QbsAutotest { + testName: "api" + files: ["../shared.h", "tst_api.h", "tst_api.cpp"] + cpp.defines: base.concat([ + "SRCDIR=" + Utilities.cStringQuote(path), + "QBS_RELATIVE_LIBEXEC_PATH=" + Utilities.cStringQuote(qbsbuildconfig.relativeLibexecPath), + "QBS_RELATIVE_SEARCH_PATH=" + Utilities.cStringQuote(qbsbuildconfig.relativeSearchPath), + "QBS_RELATIVE_PLUGINS_PATH=" + Utilities.cStringQuote(qbsbuildconfig.relativePluginsPath) + ]).concat(qbsbuildconfig.enableProjectFileUpdates ? ["QBS_ENABLE_PROJECT_FILE_UPDATES"] : []) + + Group { + name: "testdata" + prefix: "testdata/" + files: ["**/*"] + fileTags: [] + } +} diff --git a/tests/auto/api/testdata/QBS-728/QBS-728.qbs b/tests/auto/api/testdata/QBS-728/QBS-728.qbs new file mode 100644 index 00000000..5969e13c --- /dev/null +++ b/tests/auto/api/testdata/QBS-728/QBS-728.qbs @@ -0,0 +1,5 @@ +Product { + property bool isBlubbOS: qbs.targetOS.contains("blubb-OS") + qbs.profiles: isBlubbOS ? ["blubb-profile"] : [project.profile] + qbs.architecture: "blubb-arch" +} diff --git a/tests/auto/api/testdata/add-qobject-macro-to-cpp-file/add-qobject-macro-to-cpp-file.qbs b/tests/auto/api/testdata/add-qobject-macro-to-cpp-file/add-qobject-macro-to-cpp-file.qbs new file mode 100644 index 00000000..3794408b --- /dev/null +++ b/tests/auto/api/testdata/add-qobject-macro-to-cpp-file/add-qobject-macro-to-cpp-file.qbs @@ -0,0 +1,4 @@ +QtApplication { + files: ["main.cpp", "object.h", "object.cpp"] +} + diff --git a/tests/auto/api/testdata/add-qobject-macro-to-cpp-file/main.cpp b/tests/auto/api/testdata/add-qobject-macro-to-cpp-file/main.cpp new file mode 100644 index 00000000..39f308dc --- /dev/null +++ b/tests/auto/api/testdata/add-qobject-macro-to-cpp-file/main.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 "object.h" + +int main() +{ + Object o; + o.f(); +} diff --git a/tests/auto/api/testdata/add-qobject-macro-to-cpp-file/object.cpp b/tests/auto/api/testdata/add-qobject-macro-to-cpp-file/object.cpp new file mode 100644 index 00000000..55ceb9e7 --- /dev/null +++ b/tests/auto/api/testdata/add-qobject-macro-to-cpp-file/object.cpp @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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 "object.h" + +#include + +// class InternalClass : public QObject +// { +// Q_OBJECT +// }; + +void Object::f() { } + + +// #include "object.moc" diff --git a/tests/auto/api/testdata/add-qobject-macro-to-cpp-file/object.h b/tests/auto/api/testdata/add-qobject-macro-to-cpp-file/object.h new file mode 100644 index 00000000..8cb60ead --- /dev/null +++ b/tests/auto/api/testdata/add-qobject-macro-to-cpp-file/object.h @@ -0,0 +1,32 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +class Object { +public: + void f(); +}; diff --git a/tests/auto/api/testdata/added-file-persistent/added-file-persistent.qbs b/tests/auto/api/testdata/added-file-persistent/added-file-persistent.qbs new file mode 100644 index 00000000..1f3b28f9 --- /dev/null +++ b/tests/auto/api/testdata/added-file-persistent/added-file-persistent.qbs @@ -0,0 +1,6 @@ +CppApplication { + files: [ + 'main.cpp', + /* 'file.cpp' */ + ] +} diff --git a/tests/auto/api/testdata/added-file-persistent/file.cpp b/tests/auto/api/testdata/added-file-persistent/file.cpp new file mode 100644 index 00000000..833057ed --- /dev/null +++ b/tests/auto/api/testdata/added-file-persistent/file.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void f() { } diff --git a/tests/auto/api/testdata/added-file-persistent/main.cpp b/tests/auto/api/testdata/added-file-persistent/main.cpp new file mode 100644 index 00000000..17dbeb05 --- /dev/null +++ b/tests/auto/api/testdata/added-file-persistent/main.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void f(); + +int main() { f(); } diff --git a/tests/auto/api/testdata/app-without-sources/a.c b/tests/auto/api/testdata/app-without-sources/a.c new file mode 100644 index 00000000..fab68c6a --- /dev/null +++ b/tests/auto/api/testdata/app-without-sources/a.c @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int foo() { return 42; } diff --git a/tests/auto/api/testdata/app-without-sources/app-without-sources.qbs b/tests/auto/api/testdata/app-without-sources/app-without-sources.qbs new file mode 100644 index 00000000..4cc25ad6 --- /dev/null +++ b/tests/auto/api/testdata/app-without-sources/app-without-sources.qbs @@ -0,0 +1,38 @@ +Project { + StaticLibrary { + name: "a" + + Depends { name: "cpp" } + + files: [ + "a.c", + ] + } + + StaticLibrary { + name: "b" + + Depends { name: "a" } + Depends { name: "cpp" } + + files: [ + "b.c", + ] + } + + CppApplication { + name: "appWithoutSources" + consoleApplication: true + + // HACK: cpp.entryPoint currently not working 100% with gcc + Properties { + condition: qbs.toolchain.contains("msvc") + cpp.entryPoint: "main" + cpp.dynamicLibraries: ["ucrt", "kernel32"] + } + cpp.entryPoint: undefined + + Depends { name: "a" } + Depends { name: "b" } + } +} diff --git a/tests/auto/api/testdata/app-without-sources/b.c b/tests/auto/api/testdata/app-without-sources/b.c new file mode 100644 index 00000000..31c64c75 --- /dev/null +++ b/tests/auto/api/testdata/app-without-sources/b.c @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 + +int foo(); // defined in a.cpp + +int main() +{ + return foo(); +} + diff --git a/tests/auto/api/testdata/base-properties/imports/Bar.qbs b/tests/auto/api/testdata/base-properties/imports/Bar.qbs new file mode 100644 index 00000000..cef4cb46 --- /dev/null +++ b/tests/auto/api/testdata/base-properties/imports/Bar.qbs @@ -0,0 +1,4 @@ +Product { + Depends { name: "cpp" } + cpp.defines: ["FROM_BAR"] +} diff --git a/tests/auto/api/testdata/base-properties/imports/Foo.qbs b/tests/auto/api/testdata/base-properties/imports/Foo.qbs new file mode 100644 index 00000000..1d90a1c1 --- /dev/null +++ b/tests/auto/api/testdata/base-properties/imports/Foo.qbs @@ -0,0 +1,6 @@ +Bar { + type: "application" + consoleApplication: true + cpp.defines: base.concat(["FROM_FOO"]) +} + diff --git a/tests/auto/api/testdata/base-properties/main.cpp b/tests/auto/api/testdata/base-properties/main.cpp new file mode 100644 index 00000000..9581f76b --- /dev/null +++ b/tests/auto/api/testdata/base-properties/main.cpp @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef FROM_FOO +#error FROM_FOO missing! +#endif +#ifndef FROM_BAR +#error FROM_BAR missing! +#endif +#ifndef FROM_PRJ +#error FROM_PRJ missing! +#endif + +int main() +{ + return 0; +} + diff --git a/tests/auto/api/testdata/base-properties/prj.qbs b/tests/auto/api/testdata/base-properties/prj.qbs new file mode 100644 index 00000000..f7035ba0 --- /dev/null +++ b/tests/auto/api/testdata/base-properties/prj.qbs @@ -0,0 +1,9 @@ +import "imports/Foo.qbs" as Foo + +Project { + Foo { + cpp.defines: base.concat(["FROM_PRJ"]); + files: "main.cpp" + } +} + diff --git a/tests/auto/api/testdata/build-error-code-location/build-error-code-location.qbs b/tests/auto/api/testdata/build-error-code-location/build-error-code-location.qbs new file mode 100644 index 00000000..afa7e1f2 --- /dev/null +++ b/tests/auto/api/testdata/build-error-code-location/build-error-code-location.qbs @@ -0,0 +1,10 @@ +Product { + name: "p" + type: ["p.out"] + Rule { + multiplex: true + outputFileTags: ["p.out"] + outputArtifacts: { } + prepare: {} + } +} diff --git a/tests/auto/api/testdata/build-properties-source/build-properties-source.qbs b/tests/auto/api/testdata/build-properties-source/build-properties-source.qbs new file mode 100644 index 00000000..3237e90d --- /dev/null +++ b/tests/auto/api/testdata/build-properties-source/build-properties-source.qbs @@ -0,0 +1,15 @@ +Project { + Product { + type: "application" + consoleApplication: true + name: "HelloWorld" + + Depends { name: 'cpp' } + + Group { + cpp.defines: ['WORLD="BANANA"'] + files : [ "main.cpp" ] + } + } +} + diff --git a/tests/auto/api/testdata/build-properties-source/main.cpp b/tests/auto/api/testdata/build-properties-source/main.cpp new file mode 100644 index 00000000..f830ee1f --- /dev/null +++ b/tests/auto/api/testdata/build-properties-source/main.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** 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 + +#ifndef WORLD +# error WORLD is not defined +#endif + +int main() +{ + puts("Hello " WORLD "!"); +} diff --git a/tests/auto/api/testdata/build-single-file/build-single-file.qbs b/tests/auto/api/testdata/build-single-file/build-single-file.qbs new file mode 100644 index 00000000..903a52f9 --- /dev/null +++ b/tests/auto/api/testdata/build-single-file/build-single-file.qbs @@ -0,0 +1,32 @@ +import qbs.TextFile + +CppApplication { + consoleApplication: true + files: ["ignored1.cpp", "ignored2.cpp", "compiled.cpp"] + + cpp.includePaths: [buildDirectory] + Group { + files: ["pch.h"] + fileTags: ["cpp_pch_src"] + } + + install: true + installDir: "" + + Rule { + multiplex: true + Artifact { + filePath: "generated.h" + fileTags: ["hpp"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { + var header = new TextFile(output.filePath, TextFile.WriteOnly); + header.close(); + }; + return [cmd]; + } + } +} diff --git a/tests/auto/api/testdata/build-single-file/compiled.cpp b/tests/auto/api/testdata/build-single-file/compiled.cpp new file mode 100644 index 00000000..11955617 --- /dev/null +++ b/tests/auto/api/testdata/build-single-file/compiled.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 + +void f() +{ + std::cout << "test"; +} diff --git a/tests/auto/api/testdata/build-single-file/ignored1.cpp b/tests/auto/api/testdata/build-single-file/ignored1.cpp new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/build-single-file/ignored2.cpp b/tests/auto/api/testdata/build-single-file/ignored2.cpp new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/build-single-file/pch.h b/tests/auto/api/testdata/build-single-file/pch.h new file mode 100644 index 00000000..604782e4 --- /dev/null +++ b/tests/auto/api/testdata/build-single-file/pch.h @@ -0,0 +1 @@ +#include diff --git a/tests/auto/api/testdata/buildgraph-info/buildgraph-info.qbs b/tests/auto/api/testdata/buildgraph-info/buildgraph-info.qbs new file mode 100644 index 00000000..d1762513 --- /dev/null +++ b/tests/auto/api/testdata/buildgraph-info/buildgraph-info.qbs @@ -0,0 +1,3 @@ +Product { + qbs.shellPath: "/bin/bash" +} diff --git a/tests/auto/api/testdata/buildgraph-locking/buildgraph-locking.qbs b/tests/auto/api/testdata/buildgraph-locking/buildgraph-locking.qbs new file mode 100644 index 00000000..5e7259ea --- /dev/null +++ b/tests/auto/api/testdata/buildgraph-locking/buildgraph-locking.qbs @@ -0,0 +1,2 @@ +Project { +} diff --git a/tests/auto/api/testdata/change-dependent-lib/change-dependent-lib.qbs b/tests/auto/api/testdata/change-dependent-lib/change-dependent-lib.qbs new file mode 100644 index 00000000..222dc447 --- /dev/null +++ b/tests/auto/api/testdata/change-dependent-lib/change-dependent-lib.qbs @@ -0,0 +1,26 @@ +Project { + Application { + name : "HelloWorld" + Group { + files : [ "main.cpp" ] + } + Depends { name: "cpp" } + Depends { name: "mylib" } + } + + DynamicLibrary { + name : "mylib" + version: "1.2.3" + Group { + files : [ "mylib.cpp" ] + } + Depends { name: "cpp" } + cpp.defines: ["XXXX"] + + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } +} + diff --git a/tests/auto/api/testdata/change-dependent-lib/main.cpp b/tests/auto/api/testdata/change-dependent-lib/main.cpp new file mode 100644 index 00000000..8ad414c4 --- /dev/null +++ b/tests/auto/api/testdata/change-dependent-lib/main.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" + +#include + +DLL_IMPORT int mylib_hello(); + +int main() +{ + puts("application says hello!"); + return mylib_hello(); +} + diff --git a/tests/auto/api/testdata/change-dependent-lib/mylib.cpp b/tests/auto/api/testdata/change-dependent-lib/mylib.cpp new file mode 100644 index 00000000..28cb69f9 --- /dev/null +++ b/tests/auto/api/testdata/change-dependent-lib/mylib.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" + +#include + +DLL_EXPORT int mylib_hello() +{ + puts("mylib says hello!"); + return 0; +} diff --git a/tests/auto/api/testdata/check-outputs/check-outputs.qbs b/tests/auto/api/testdata/check-outputs/check-outputs.qbs new file mode 100644 index 00000000..519a95ea --- /dev/null +++ b/tests/auto/api/testdata/check-outputs/check-outputs.qbs @@ -0,0 +1,36 @@ +import qbs.File + +Project { + Product { + type: 'application' + consoleApplication: true + Group { + files: 'foo.txt' + fileTags: ['text'] + } + Depends { name: 'cpp' } + } + + Rule { + inputs: ['text'] + Artifact { + fileTags: ['cpp'] + filePath: input.baseName + '.cpp' + } + Artifact { + fileTags: ['ghost'] + filePath: input.baseName + '.ghost' + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.inp = inputs["text"][0].filePath; + cmd.out = outputs["cpp"][0].filePath; + cmd.description = "generating " + outputs["cpp"][0].fileName; + cmd.sourceCode = function() { + File.copy(inp, out); + }; + return cmd; + } + } +} diff --git a/tests/auto/api/testdata/check-outputs/foo.txt b/tests/auto/api/testdata/check-outputs/foo.txt new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/api/testdata/check-outputs/foo.txt @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/api/testdata/codegen/codegen.qbs b/tests/auto/api/testdata/codegen/codegen.qbs new file mode 100644 index 00000000..d8a21757 --- /dev/null +++ b/tests/auto/api/testdata/codegen/codegen.qbs @@ -0,0 +1,73 @@ +import qbs.FileInfo + +Project { + property string name: 'codegen' + property string osSpecificName: name.toUpperCase() + '_' + qbs.targetPlatform.toUpperCase() + + Product { + type: 'application' + consoleApplication: true + name: project.name + property var replacements: ({ + NUMBERTYPE: "int", + STRINGTYPE: "char **", + FUNCTIONNAME: "main" + }) + Group { + files: 'foo.txt' + fileTags: ['text'] + } + Depends { name: 'cpp' } + Depends { name: 'Qt.core' } + } + + Rule { + inputs: ['text'] + Artifact { + fileTags: ['cpp'] + filePath: input.baseName + '.cpp' + } + prepare: { + function expandMacros(str, table) + { + var rex = /\$\w+/; + var m = rex.exec(str); + while (m != null) { + str = str.substr(0, m.index) + + table[m[0].substr(1)] + + str.substr(m.index + m[0].length); + m = rex.exec(str); + } + return str; + } + + // check whether multipart module name translation is working + var actual = product.moduleProperty("Qt.core", "mocName"); + if (!actual || !actual.contains("moc")) + throw "multipart module name translation is broken"; + + // check whether we can access project properties here + var expected = "CODEGEN_" + product.moduleProperty("qbs", + "targetPlatform").toUpperCase(); + if (project.osSpecificName !== expected) + throw "Wrong project property value: " + project.osSpecificName + + "\nexpected: " + expected; + + var code = '$NUMBERTYPE $FUNCTIONNAME($NUMBERTYPE, $STRINGTYPE) { return 0; }'; + code = expandMacros(code, product.replacements); + var args = ['echo ' + code + '>' + output.filePath] + var cmd + if (product.moduleProperty("qbs", "hostOS").contains('windows')) { + cmd = new Command(product.qbs.windowsShellPath, ['/C'].concat(args)); + } else { + args[0] = args[0].replace(/\(/g, '\\(') + args[0] = args[0].replace(/\)/g, '\\)') + args[0] = args[0].replace(/;/g, '\\;') + cmd = new Command(product.qbs.shellPath, ['-c'].concat(args)) + } + cmd.description = 'generate\t' + FileInfo.fileName(output.filePath); + cmd.highlight = 'codegen'; + return cmd; + } + } +} diff --git a/tests/auto/api/testdata/codegen/foo.txt b/tests/auto/api/testdata/codegen/foo.txt new file mode 100644 index 00000000..557db03d --- /dev/null +++ b/tests/auto/api/testdata/codegen/foo.txt @@ -0,0 +1 @@ +Hello World diff --git a/tests/auto/api/testdata/command-extraction/command-extraction.qbs b/tests/auto/api/testdata/command-extraction/command-extraction.qbs new file mode 100644 index 00000000..253992dd --- /dev/null +++ b/tests/auto/api/testdata/command-extraction/command-extraction.qbs @@ -0,0 +1,3 @@ +CppApplication { + files: "main.cpp" +} diff --git a/tests/auto/api/testdata/command-extraction/main.cpp b/tests/auto/api/testdata/command-extraction/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/api/testdata/command-extraction/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/api/testdata/dependency-on-multiplexed-type/dependency-on-multiplexed-type.qbs b/tests/auto/api/testdata/dependency-on-multiplexed-type/dependency-on-multiplexed-type.qbs new file mode 100644 index 00000000..cfc2769d --- /dev/null +++ b/tests/auto/api/testdata/dependency-on-multiplexed-type/dependency-on-multiplexed-type.qbs @@ -0,0 +1,18 @@ +import qbs + +Project { + Product { name: "dep"; type: "x" } + Product { + name: "p1" + multiplexByQbsProperties: "architectures" + qbs.architectures: ["a", "b"] + aggregate: true + Depends { productTypes: "x" } + multiplexedType: "x" + } + Product { + name: "p2" + Depends { productTypes: "x" } + } +} + diff --git a/tests/auto/api/testdata/disabled-product/disabled-product.qbs b/tests/auto/api/testdata/disabled-product/disabled-product.qbs new file mode 100644 index 00000000..dad8f5d4 --- /dev/null +++ b/tests/auto/api/testdata/disabled-product/disabled-product.qbs @@ -0,0 +1,8 @@ +CppApplication { + condition: false + files: "main.cpp" + Group { + condition: qbs.targetOS.contains("stuff") + qbs.install: false + } +} diff --git a/tests/auto/api/testdata/disabled-product/main.cpp b/tests/auto/api/testdata/disabled-product/main.cpp new file mode 100644 index 00000000..1f5a432d --- /dev/null +++ b/tests/auto/api/testdata/disabled-product/main.cpp @@ -0,0 +1 @@ +thiswillnotcompile diff --git a/tests/auto/api/testdata/disabled-project/disabled-project.qbs b/tests/auto/api/testdata/disabled-project/disabled-project.qbs new file mode 100644 index 00000000..49f35be9 --- /dev/null +++ b/tests/auto/api/testdata/disabled-project/disabled-project.qbs @@ -0,0 +1,6 @@ +import qbs.File + +Project { + condition: File.exists("blubb"); + references: "blubb/nosuchfile.qbs" +} diff --git a/tests/auto/api/testdata/disabled_install_group/disabled_install_group.qbs b/tests/auto/api/testdata/disabled_install_group/disabled_install_group.qbs new file mode 100644 index 00000000..c300f639 --- /dev/null +++ b/tests/auto/api/testdata/disabled_install_group/disabled_install_group.qbs @@ -0,0 +1,9 @@ +CppApplication { + consoleApplication: true + files: "main.cpp" + Group { + condition: false + qbs.install: true + fileTagsFilter: product.type + } +} diff --git a/tests/auto/api/testdata/disabled_install_group/main.cpp b/tests/auto/api/testdata/disabled_install_group/main.cpp new file mode 100644 index 00000000..e14f806b --- /dev/null +++ b/tests/auto/api/testdata/disabled_install_group/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { } diff --git a/tests/auto/api/testdata/disappeared-wildcard-file/disappeared-wildcard-file.qbs b/tests/auto/api/testdata/disappeared-wildcard-file/disappeared-wildcard-file.qbs new file mode 100644 index 00000000..3c38e18a --- /dev/null +++ b/tests/auto/api/testdata/disappeared-wildcard-file/disappeared-wildcard-file.qbs @@ -0,0 +1,4 @@ +Product { + name: "dummy" + files: "*.txt" +} diff --git a/tests/auto/api/testdata/disappeared-wildcard-file/file1.txt b/tests/auto/api/testdata/disappeared-wildcard-file/file1.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/disappeared-wildcard-file/file2.txt b/tests/auto/api/testdata/disappeared-wildcard-file/file2.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/duplicate-product-names/explicit.qbs b/tests/auto/api/testdata/duplicate-product-names/explicit.qbs new file mode 100644 index 00000000..b9a97bf5 --- /dev/null +++ b/tests/auto/api/testdata/duplicate-product-names/explicit.qbs @@ -0,0 +1,5 @@ +Project { + Product { name: "blubb" } + Product { name: "blubb" } + Product { name: "blubb" } +} diff --git a/tests/auto/api/testdata/duplicate-product-names/implicit-indirect.qbs b/tests/auto/api/testdata/duplicate-product-names/implicit-indirect.qbs new file mode 100644 index 00000000..bc651c07 --- /dev/null +++ b/tests/auto/api/testdata/duplicate-product-names/implicit-indirect.qbs @@ -0,0 +1,3 @@ +Project { + references: ["subdir1/subproject.qbs", "subdir2/subproject.qbs"] +} diff --git a/tests/auto/api/testdata/duplicate-product-names/implicit.qbs b/tests/auto/api/testdata/duplicate-product-names/implicit.qbs new file mode 100644 index 00000000..409af1ab --- /dev/null +++ b/tests/auto/api/testdata/duplicate-product-names/implicit.qbs @@ -0,0 +1,5 @@ +Project { + Product { } + Product { } + Product { } +} diff --git a/tests/auto/api/testdata/duplicate-product-names/subdir1/subproject.qbs b/tests/auto/api/testdata/duplicate-product-names/subdir1/subproject.qbs new file mode 100644 index 00000000..86718b57 --- /dev/null +++ b/tests/auto/api/testdata/duplicate-product-names/subdir1/subproject.qbs @@ -0,0 +1 @@ +Product { } diff --git a/tests/auto/api/testdata/duplicate-product-names/subdir2/subproject.qbs b/tests/auto/api/testdata/duplicate-product-names/subdir2/subproject.qbs new file mode 100644 index 00000000..86718b57 --- /dev/null +++ b/tests/auto/api/testdata/duplicate-product-names/subdir2/subproject.qbs @@ -0,0 +1 @@ +Product { } diff --git a/tests/auto/api/testdata/empty-filetag-list/dontcompilethis.cpp b/tests/auto/api/testdata/empty-filetag-list/dontcompilethis.cpp new file mode 100644 index 00000000..bac3b631 --- /dev/null +++ b/tests/auto/api/testdata/empty-filetag-list/dontcompilethis.cpp @@ -0,0 +1 @@ +This is not C++. diff --git a/tests/auto/api/testdata/empty-filetag-list/empty-filetag-list.qbs b/tests/auto/api/testdata/empty-filetag-list/empty-filetag-list.qbs new file mode 100644 index 00000000..a13065db --- /dev/null +++ b/tests/auto/api/testdata/empty-filetag-list/empty-filetag-list.qbs @@ -0,0 +1,6 @@ +CppApplication { + Group { + files: "dontcompilethis.cpp" + fileTags: [] + } +} diff --git a/tests/auto/api/testdata/empty-submodules-list/empty-submodules-list.qbs b/tests/auto/api/testdata/empty-submodules-list/empty-submodules-list.qbs new file mode 100644 index 00000000..b39b1ebc --- /dev/null +++ b/tests/auto/api/testdata/empty-submodules-list/empty-submodules-list.qbs @@ -0,0 +1,6 @@ +CppApplication { + Depends { + name: "dummy" + submodules: [] + } +} diff --git a/tests/auto/api/testdata/enable-and-disable-product/enable-and-disable-product.qbs b/tests/auto/api/testdata/enable-and-disable-product/enable-and-disable-product.qbs new file mode 100644 index 00000000..fc90377a --- /dev/null +++ b/tests/auto/api/testdata/enable-and-disable-product/enable-and-disable-product.qbs @@ -0,0 +1,6 @@ +CppApplication { + property string prop: undefined // Influences source artifact properties and the product condition + condition: prop + cpp.visibility: prop + files: "main.cpp" +} diff --git a/tests/auto/api/testdata/enable-and-disable-product/main.cpp b/tests/auto/api/testdata/enable-and-disable-product/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/api/testdata/enable-and-disable-product/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/api/testdata/error-in-setup-run-environment/error-in-setup-run-environment.qbs b/tests/auto/api/testdata/error-in-setup-run-environment/error-in-setup-run-environment.qbs new file mode 100644 index 00000000..63a79551 --- /dev/null +++ b/tests/auto/api/testdata/error-in-setup-run-environment/error-in-setup-run-environment.qbs @@ -0,0 +1,3 @@ +CppApplication { + Depends { name: "mymodule" } +} diff --git a/tests/auto/api/testdata/error-in-setup-run-environment/modules/mymodule/mymodule.qbs b/tests/auto/api/testdata/error-in-setup-run-environment/modules/mymodule/mymodule.qbs new file mode 100644 index 00000000..1b136fdb --- /dev/null +++ b/tests/auto/api/testdata/error-in-setup-run-environment/modules/mymodule/mymodule.qbs @@ -0,0 +1,5 @@ +Module { + setupRunEnvironment: { + trallala + } +} diff --git a/tests/auto/api/testdata/excluded-inputs/excluded-inputs.qbs b/tests/auto/api/testdata/excluded-inputs/excluded-inputs.qbs new file mode 100644 index 00000000..fe3cacd6 --- /dev/null +++ b/tests/auto/api/testdata/excluded-inputs/excluded-inputs.qbs @@ -0,0 +1,113 @@ +import qbs.File +import qbs.TextFile + +Project { + Product { + name: "dep" + type: "the_tag" + Rule { + multiplex: true + Artifact { + filePath: "file1.txt" + fileTags: "the_tag" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.writeLine("the_content"); + f.close(); + }; + return cmd; + } + } + Rule { + inputs: "the_tag" + excludedInputs: "the_other_tag" + Artifact { + filePath: "file2.txt" + fileTags: "the_other_tag" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.sourceCode = function() { + File.copy(input.filePath, output.filePath); + var f = new TextFile(output.filePath, TextFile.Append); + f.writeLine("the_other_content"); + f.close(); + }; + return cmd; + } + } + Group { + fileTagsFilter: "the_other_tag" + fileTags: "the_tag" + } + } + Product { + name: "p" + type: "p_type" + Depends { name: "dep" } + Rule { + multiplex: true + inputsFromDependencies: "the_tag" + Artifact { + filePath: "dummy1.txt" + fileTags: "p_type" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName;; + if (!inputs["the_tag"] || inputs["the_tag"].length != 2) + throw "Huch?"; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + }; + return cmd; + } + } + Rule { + multiplex: true + inputsFromDependencies: "the_tag" + excludedInputs: "the_other_tag" + Artifact { + filePath: "dummy2.txt" + fileTags: "p_type" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName;; + if (!inputs["the_tag"] || inputs["the_tag"].length != 1) + throw "Huch?"; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + }; + return cmd; + } + } + Rule { + multiplex: true + explicitlyDependsOnFromDependencies: "the_tag" + excludedInputs: "the_other_tag" + Artifact { + filePath: "dummy3.txt" + fileTags: "p_type" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + if (!explicitlyDependsOn["the_tag"] || explicitlyDependsOn["the_tag"].length != 1) + throw "Huch?"; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + }; + return cmd; + } + } + } +} diff --git a/tests/auto/api/testdata/explicitly-depends-on/a.in b/tests/auto/api/testdata/explicitly-depends-on/a.in new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/explicitly-depends-on/b.in b/tests/auto/api/testdata/explicitly-depends-on/b.in new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/explicitly-depends-on/c.in b/tests/auto/api/testdata/explicitly-depends-on/c.in new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/explicitly-depends-on/compiler.cpp b/tests/auto/api/testdata/explicitly-depends-on/compiler.cpp new file mode 100644 index 00000000..57b7016d --- /dev/null +++ b/tests/auto/api/testdata/explicitly-depends-on/compiler.cpp @@ -0,0 +1,11 @@ +#include + +#include + +int main(int argc, char *argv[]) +{ + assert(argc == 3); + std::ofstream target(argv[2]); + assert(target); + target << argv[1]; +} diff --git a/tests/auto/api/testdata/explicitly-depends-on/explicitly-depends-on.qbs b/tests/auto/api/testdata/explicitly-depends-on/explicitly-depends-on.qbs new file mode 100644 index 00000000..1823384f --- /dev/null +++ b/tests/auto/api/testdata/explicitly-depends-on/explicitly-depends-on.qbs @@ -0,0 +1,69 @@ +import qbs.FileInfo +import qbs.TextFile + +Project { + CppApplication { + name: "compiler" + files: ["compiler.cpp"] + Group { + fileTagsFilter: ["application"] + fileTags: ["compiler"] + } + } + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: "p" + type: ["mytype"] + + Depends { name: "compiler" } + Depends { name: "cpp" } + + Rule { + inputs: ["mytype.in"] + explicitlyDependsOnFromDependencies: ["compiler"] + Artifact { + filePath: input.fileName + ".out" + fileTags: product.type + } + prepare: { + var compiler = explicitlyDependsOn["compiler"][0].filePath; + var cmd = new Command(compiler, [input.filePath, output.filePath]); + cmd.description = "compiling " + input.fileName; + cmd.highlight = "compiler"; + return [cmd]; + } + } + + Rule { + multiplex: true + explicitlyDependsOnFromDependencies: ["compiler"] + Artifact { + filePath: "compiler-name.txt" + fileTags: product.type + } + prepare: { + var nameCmd = new JavaScriptCommand(); + nameCmd.description = "writing compiler name"; + nameCmd.sourceCode = function() { + var compiler = explicitlyDependsOn["compiler"][0].filePath; + var file = new TextFile(output.filePath, TextFile.WriteOnly); + file.write("compiler file name: " + FileInfo.baseName(compiler)); + file.close(); + } + return [nameCmd]; + } + } + + FileTagger { + patterns: "*.in" + fileTags: ["mytype.in"] + } + + files: ["a.in", "b.in", "c.in"] + } +} diff --git a/tests/auto/api/testdata/export-item-with-group/export-item-with-group.qbs b/tests/auto/api/testdata/export-item-with-group/export-item-with-group.qbs new file mode 100644 index 00000000..b2079e95 --- /dev/null +++ b/tests/auto/api/testdata/export-item-with-group/export-item-with-group.qbs @@ -0,0 +1,15 @@ +Project { + Product { + name: "dep" + Export { + Depends { name: "cpp" } + Group { files: ["main.cpp"] } + } + } + + Application { + name: "app" + consoleApplication: true + Depends { name: "dep" } + } +} diff --git a/tests/auto/api/testdata/export-item-with-group/main.cpp b/tests/auto/api/testdata/export-item-with-group/main.cpp new file mode 100644 index 00000000..e14f806b --- /dev/null +++ b/tests/auto/api/testdata/export-item-with-group/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { } diff --git a/tests/auto/api/testdata/export-simple/export-simple.qbs b/tests/auto/api/testdata/export-simple/export-simple.qbs new file mode 100644 index 00000000..01177049 --- /dev/null +++ b/tests/auto/api/testdata/export-simple/export-simple.qbs @@ -0,0 +1,51 @@ +Project { + Application { + name : "HelloWorld" + destinationDirectory: "bin" + Group { + files : [ "main.cpp" ] + } + Depends { name: "cpp" } + Depends { name: 'dummy' } + } + + Product { + name: 'dummy' + Group { + files: 'main.cpp' + qbs.install: true + } + Export { + Depends { name: 'dummy2' } + Properties { // QBS-550 + condition: false + qbs.optimization: "ludicrous speed" + } + } + } + + Product { + name: 'dummy2' + Group { + files: 'lib1.cpp' + qbs.install: true + } + Export { + Depends { name: 'lib1' } + } + } + + DynamicLibrary { + name : "lib1" + destinationDirectory: "bin" + Group { + files : [ "lib1.cpp" ] + } + Depends { name: "cpp" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } +} + diff --git a/tests/auto/api/testdata/export-simple/lib1.cpp b/tests/auto/api/testdata/export-simple/lib1.cpp new file mode 100644 index 00000000..2b22d499 --- /dev/null +++ b/tests/auto/api/testdata/export-simple/lib1.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" +#include + +DLL_EXPORT int lib1_hello() +{ + puts("lib1 says hello!"); + return 0; +} diff --git a/tests/auto/api/testdata/export-simple/main.cpp b/tests/auto/api/testdata/export-simple/main.cpp new file mode 100644 index 00000000..820e6f3b --- /dev/null +++ b/tests/auto/api/testdata/export-simple/main.cpp @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" +#include + +DLL_IMPORT int lib1_hello(); + +int main() +{ + puts("application says hello!"); + return lib1_hello(); +} + diff --git a/tests/auto/api/testdata/export-with-recursive-depends/export-with-recursive-depends.qbs b/tests/auto/api/testdata/export-with-recursive-depends/export-with-recursive-depends.qbs new file mode 100644 index 00000000..937a73e2 --- /dev/null +++ b/tests/auto/api/testdata/export-with-recursive-depends/export-with-recursive-depends.qbs @@ -0,0 +1,13 @@ +Project { + CppApplication { + name: "app1" + files: "main1.cpp" + Export { Depends { name: "module1" } } + } + + CppApplication { + name: "app2" + Depends { name: "app1" } + files: "main2.cpp" + } +} diff --git a/tests/auto/api/testdata/export-with-recursive-depends/main1.cpp b/tests/auto/api/testdata/export-with-recursive-depends/main1.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/api/testdata/export-with-recursive-depends/main1.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/api/testdata/export-with-recursive-depends/main2.cpp b/tests/auto/api/testdata/export-with-recursive-depends/main2.cpp new file mode 100644 index 00000000..6ea3da44 --- /dev/null +++ b/tests/auto/api/testdata/export-with-recursive-depends/main2.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() +{ +#ifndef HAS_FOO + blubb(); +#endif +} diff --git a/tests/auto/api/testdata/export-with-recursive-depends/modules/module1/module1.qbs b/tests/auto/api/testdata/export-with-recursive-depends/modules/module1/module1.qbs new file mode 100644 index 00000000..c7aadde6 --- /dev/null +++ b/tests/auto/api/testdata/export-with-recursive-depends/modules/module1/module1.qbs @@ -0,0 +1,3 @@ +Module { + Depends { name: "module2" } +} diff --git a/tests/auto/api/testdata/export-with-recursive-depends/modules/module2/module2.qbs b/tests/auto/api/testdata/export-with-recursive-depends/modules/module2/module2.qbs new file mode 100644 index 00000000..2b5f6014 --- /dev/null +++ b/tests/auto/api/testdata/export-with-recursive-depends/modules/module2/module2.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "cpp" } + cpp.defines: ["HAS_FOO"] +} diff --git a/tests/auto/api/testdata/fallback-gcc/fallback-gcc.qbs b/tests/auto/api/testdata/fallback-gcc/fallback-gcc.qbs new file mode 100644 index 00000000..e2fb255a --- /dev/null +++ b/tests/auto/api/testdata/fallback-gcc/fallback-gcc.qbs @@ -0,0 +1,20 @@ +CppApplication { + name: "app" + cpp._skipAllChecks: true + Profile { + name: "unixProfile" + qbs.targetOS: ["unix"] + qbs.toolchain: ["gcc"] + // qbs.targetPlatform: "unix" + // qbs.toolchainType: "gcc" + } + Profile { + name: "gccProfile" + qbs.targetOS: [] + qbs.toolchain: ["gcc"] + // qbs.targetPlatform: undefined + // qbs.toolchainType: "gcc" + } + multiplexByQbsProperties: ["profiles"] + qbs.profiles: ["unixProfile", "gccProfile"] +} diff --git a/tests/auto/api/testdata/file-tagger/bla.txt b/tests/auto/api/testdata/file-tagger/bla.txt new file mode 100644 index 00000000..26185684 --- /dev/null +++ b/tests/auto/api/testdata/file-tagger/bla.txt @@ -0,0 +1,15 @@ +#include + +class MyObject : public QObject +{ + Q_OBJECT +}; + +int main() +{ + MyObject obj; + return 0; +} + +#include "bla.moc" + diff --git a/tests/auto/api/testdata/file-tagger/moc_cpp.qbs b/tests/auto/api/testdata/file-tagger/moc_cpp.qbs new file mode 100644 index 00000000..43fa5f33 --- /dev/null +++ b/tests/auto/api/testdata/file-tagger/moc_cpp.qbs @@ -0,0 +1,42 @@ +import qbs.TextFile +import qbs.FileInfo + +Project { + Product { + type: "application" + consoleApplication: true + name: "moc_cpp" + + Depends { + name: "Qt.core" + } + + Group { + files: 'bla.txt' + fileTags: ['text'] + } + } + + Rule { + inputs: ['text'] + Artifact { + fileTags: ['cpp'] + filePath: input.baseName + '.cpp' + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.sourceCode = function () { + var file = new TextFile(input.filePath, TextFile.ReadOnly); + var text = file.readAll(); + file.close(); + file = new TextFile(output.filePath, TextFile.WriteOnly); + file.truncate(); + file.write(text); + file.close(); + } + cmd.description = 'generating ' + FileInfo.fileName(output.filePath); + cmd.highlight = 'codegen'; + return cmd; + } + } +} diff --git a/tests/auto/api/testdata/filetagsfilter_override/InstalledApp.qbs b/tests/auto/api/testdata/filetagsfilter_override/InstalledApp.qbs new file mode 100644 index 00000000..21db6b37 --- /dev/null +++ b/tests/auto/api/testdata/filetagsfilter_override/InstalledApp.qbs @@ -0,0 +1,9 @@ +CppApplication { + type: "application" + consoleApplication: true + Group { + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: "hurz" + } +} diff --git a/tests/auto/api/testdata/filetagsfilter_override/filetagsfilter_override.qbs b/tests/auto/api/testdata/filetagsfilter_override/filetagsfilter_override.qbs new file mode 100644 index 00000000..6483e885 --- /dev/null +++ b/tests/auto/api/testdata/filetagsfilter_override/filetagsfilter_override.qbs @@ -0,0 +1,10 @@ +import "InstalledApp.qbs" as InstalledApp + +InstalledApp { + files: "main.cpp" + Group { + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: "habicht" + } +} diff --git a/tests/auto/api/testdata/filetagsfilter_override/main.cpp b/tests/auto/api/testdata/filetagsfilter_override/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/api/testdata/filetagsfilter_override/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/api/testdata/generated-files-list/generated-files-list.qbs b/tests/auto/api/testdata/generated-files-list/generated-files-list.qbs new file mode 100644 index 00000000..666d7264 --- /dev/null +++ b/tests/auto/api/testdata/generated-files-list/generated-files-list.qbs @@ -0,0 +1,14 @@ +CppApplication { + Depends { name: "Qt.widgets" } + consoleApplication: true + cpp.cxxLanguageVersion: "c++11" + cpp.debugInformation: false + cpp.separateDebugInformation: false + files: [ + "main.cpp", + "mainwindow.cpp", + "mainwindow.h", + "mainwindow.ui" + ] +} + diff --git a/tests/auto/api/testdata/generated-files-list/main.cpp b/tests/auto/api/testdata/generated-files-list/main.cpp new file mode 100644 index 00000000..9713be60 --- /dev/null +++ b/tests/auto/api/testdata/generated-files-list/main.cpp @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** 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 "mainwindow.h" +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/tests/auto/api/testdata/generated-files-list/mainwindow.cpp b/tests/auto/api/testdata/generated-files-list/mainwindow.cpp new file mode 100644 index 00000000..92a5e0a7 --- /dev/null +++ b/tests/auto/api/testdata/generated-files-list/mainwindow.cpp @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** 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 "mainwindow.h" +#include "ui_mainwindow.h" + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); +} + +MainWindow::~MainWindow() +{ + delete ui; +} diff --git a/tests/auto/api/testdata/generated-files-list/mainwindow.h b/tests/auto/api/testdata/generated-files-list/mainwindow.h new file mode 100644 index 00000000..76f9c80c --- /dev/null +++ b/tests/auto/api/testdata/generated-files-list/mainwindow.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; +}; + +#endif // MAINWINDOW_H diff --git a/tests/auto/api/testdata/generated-files-list/mainwindow.ui b/tests/auto/api/testdata/generated-files-list/mainwindow.ui new file mode 100644 index 00000000..6050363f --- /dev/null +++ b/tests/auto/api/testdata/generated-files-list/mainwindow.ui @@ -0,0 +1,24 @@ + + MainWindow + + + + 0 + 0 + 400 + 300 + + + + MainWindow + + + + + + + + + + + diff --git a/tests/auto/api/testdata/infinite-loop-js/infinite-loop.qbs b/tests/auto/api/testdata/infinite-loop-js/infinite-loop.qbs new file mode 100644 index 00000000..bb616a9e --- /dev/null +++ b/tests/auto/api/testdata/infinite-loop-js/infinite-loop.qbs @@ -0,0 +1,19 @@ +Product { + type: "mytype" + Rule { + multiplex: true + Artifact { + filePath: "output.txt" + fileTags: "mytype" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Running infinite loop"; + cmd.sourceCode = function() { + while (true) + ; + } + return cmd; + } + } +} diff --git a/tests/auto/api/testdata/infinite-loop-process/infinite-loop.qbs b/tests/auto/api/testdata/infinite-loop-process/infinite-loop.qbs new file mode 100644 index 00000000..f4ea8bf8 --- /dev/null +++ b/tests/auto/api/testdata/infinite-loop-process/infinite-loop.qbs @@ -0,0 +1,32 @@ +Project { + CppApplication { + type: "application" + consoleApplication: true // suppress bundle generation + files: "main.cpp" + name: "infinite-loop" + cpp.cxxLanguageVersion: "c++11" + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.driverFlags: "-pthread" + } + } + + Product { + type: "mytype" + name: "caller" + Depends { name: "infinite-loop" } + Depends { + name: "cpp" // Make sure build environment is set up properly. + condition: qbs.hostOS.contains("windows") && qbs.toolchain.contains("gcc") + } + Rule { + inputsFromDependencies: "application" + outputFileTags: "mytype" + prepare: { + var cmd = new Command(inputs["application"][0].filePath); + cmd.description = "Calling application that runs forever"; + return cmd; + } + } + } +} diff --git a/tests/auto/api/testdata/infinite-loop-process/main.cpp b/tests/auto/api/testdata/infinite-loop-process/main.cpp new file mode 100644 index 00000000..6a7a5367 --- /dev/null +++ b/tests/auto/api/testdata/infinite-loop-process/main.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 +#include + +int main() +{ + std::this_thread::sleep_for(std::chrono::seconds(700)); +} diff --git a/tests/auto/api/testdata/infinite-loop-resolving/infinite-loop-resolving.qbs b/tests/auto/api/testdata/infinite-loop-resolving/infinite-loop-resolving.qbs new file mode 100644 index 00000000..e9d0db41 --- /dev/null +++ b/tests/auto/api/testdata/infinite-loop-resolving/infinite-loop-resolving.qbs @@ -0,0 +1,3 @@ +Product { + type: { while (true); return "Haha!"; } +} diff --git a/tests/auto/api/testdata/inherit-qbs-search-paths/imports/Foo.qbs b/tests/auto/api/testdata/inherit-qbs-search-paths/imports/Foo.qbs new file mode 100644 index 00000000..8920c0fc --- /dev/null +++ b/tests/auto/api/testdata/inherit-qbs-search-paths/imports/Foo.qbs @@ -0,0 +1,6 @@ +Product { + type: "application" + consoleApplication: true + Depends { name: 'bli' } +} + diff --git a/tests/auto/api/testdata/inherit-qbs-search-paths/main.cpp b/tests/auto/api/testdata/inherit-qbs-search-paths/main.cpp new file mode 100644 index 00000000..98e6c8c3 --- /dev/null +++ b/tests/auto/api/testdata/inherit-qbs-search-paths/main.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef HAVE_BLI +#error HAVE_BLI missing! +#endif + +int main() +{ + return 0; +} + diff --git a/tests/auto/api/testdata/inherit-qbs-search-paths/prj.qbs b/tests/auto/api/testdata/inherit-qbs-search-paths/prj.qbs new file mode 100644 index 00000000..d79a1ed7 --- /dev/null +++ b/tests/auto/api/testdata/inherit-qbs-search-paths/prj.qbs @@ -0,0 +1,12 @@ +import "imports/Foo.qbs" as Foo + +Project { + qbsSearchPaths: "subdir" + Project { + qbsSearchPaths: "subdir2" + Foo { + files: "main.cpp" + } + } +} + diff --git a/tests/auto/api/testdata/inherit-qbs-search-paths/subdir/modules/bli/m.qbs b/tests/auto/api/testdata/inherit-qbs-search-paths/subdir/modules/bli/m.qbs new file mode 100644 index 00000000..34ac9e8e --- /dev/null +++ b/tests/auto/api/testdata/inherit-qbs-search-paths/subdir/modules/bli/m.qbs @@ -0,0 +1,5 @@ +Module { + Depends {name : "cpp" } + cpp.defines: ["HAVE_BLI"] +} + diff --git a/tests/auto/api/testdata/inherit-qbs-search-paths/subdir2/modules/bla/m.qbs b/tests/auto/api/testdata/inherit-qbs-search-paths/subdir2/modules/bla/m.qbs new file mode 100644 index 00000000..de93f18e --- /dev/null +++ b/tests/auto/api/testdata/inherit-qbs-search-paths/subdir2/modules/bla/m.qbs @@ -0,0 +1,5 @@ +Module { + Depends {name : "cpp" } + cpp.defines: ["HAVE_BLA"] +} + diff --git a/tests/auto/api/testdata/installed-artifact/installed-artifact.qbs b/tests/auto/api/testdata/installed-artifact/installed-artifact.qbs new file mode 100644 index 00000000..d3f91662 --- /dev/null +++ b/tests/auto/api/testdata/installed-artifact/installed-artifact.qbs @@ -0,0 +1,27 @@ + +Project { + CppApplication { + name: "other app" + files: ["main.cpp"] + } + + CppApplication { + name: "installedApp" + type: "application" + consoleApplication: true + Depends { name: "other app" } + Group { + files: "main.cpp" + qbs.install: true + qbs.installDir: "src" + } + qbs.installPrefix: "/usr" + install: true + installDir: "bin" + Group { + fileTagsFilter: "obj" + qbs.install: true + qbs.installDir: "objects" + } + } +} diff --git a/tests/auto/api/testdata/installed-artifact/main.cpp b/tests/auto/api/testdata/installed-artifact/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/api/testdata/installed-artifact/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/api/testdata/is-runnable/is-runnable.qbs b/tests/auto/api/testdata/is-runnable/is-runnable.qbs new file mode 100644 index 00000000..87027929 --- /dev/null +++ b/tests/auto/api/testdata/is-runnable/is-runnable.qbs @@ -0,0 +1,12 @@ +Project { + CppApplication { + name: "app" + } + DynamicLibrary { + name: "lib" + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } +} diff --git a/tests/auto/api/testdata/lib-same-source/lib-same-source.qbs b/tests/auto/api/testdata/lib-same-source/lib-same-source.qbs new file mode 100644 index 00000000..97a614eb --- /dev/null +++ b/tests/auto/api/testdata/lib-same-source/lib-same-source.qbs @@ -0,0 +1,21 @@ +Project { + Product { + type: "application" + consoleApplication: true + name : "HelloWorldApp" + Depends { name: 'cpp' } + Group { + files : [ "main.cpp" ] + } + } + + Product { + type: "staticlibrary" + name : "HelloWorldLib" + Depends { name: 'cpp' } + Group { + files : [ "main.cpp" ] + } + } +} + diff --git a/tests/auto/api/testdata/lib-same-source/main.cpp b/tests/auto/api/testdata/lib-same-source/main.cpp new file mode 100644 index 00000000..47e1013f --- /dev/null +++ b/tests/auto/api/testdata/lib-same-source/main.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 + +int main() +{ + puts("Hello WOrld!"); +} diff --git a/tests/auto/api/testdata/link-dynamiclibs-staticlibs/dynamic1.cpp b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/dynamic1.cpp new file mode 100644 index 00000000..ed5f822f --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/dynamic1.cpp @@ -0,0 +1,12 @@ +#include + +#include "../dllexport.h" + +void static1_hello(); + +DLL_EXPORT int dynamic1_hello() +{ + static1_hello(); + puts("dynamic1 says hello!"); + return 0; +} diff --git a/tests/auto/api/testdata/link-dynamiclibs-staticlibs/dynamic2.cpp b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/dynamic2.cpp new file mode 100644 index 00000000..b2db5d23 --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/dynamic2.cpp @@ -0,0 +1,10 @@ +#include "../dllexport.h" +#include "static2.h" +#include + +DLL_EXPORT void dynamic2_hello() +{ + TestMe tm; + tm.hello(); + puts("dynamic2 says hello!"); +} diff --git a/tests/auto/api/testdata/link-dynamiclibs-staticlibs/link-dynamiclibs-staticlibs.qbs b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/link-dynamiclibs-staticlibs.qbs new file mode 100644 index 00000000..b2a54080 --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/link-dynamiclibs-staticlibs.qbs @@ -0,0 +1,45 @@ +Project { + Application { + name : "HelloWorld" + files : [ "main.cpp" ] + Depends { name: "cpp" } + Depends { name: "dynamic1" } + } + + DynamicLibrary { + name : "dynamic1" + files : [ "dynamic1.cpp" ] + Depends { name: "cpp" } + Depends { name: "static1" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } + + StaticLibrary { + name: "static1" + files: [ "static1.cpp" ] + Depends { name: "cpp" } + Depends { name: "dynamic2" } + } + + DynamicLibrary { + name: "dynamic2" + files: [ "dynamic2.cpp" ] + Depends { name: "cpp" } + Depends { name: "static2" } + cpp.visibility: 'hidden' + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } + + StaticLibrary { + name: "static2" + files: [ "static2.cpp", "static2.h" ] + Depends { name: "cpp" } + } +} + diff --git a/tests/auto/api/testdata/link-dynamiclibs-staticlibs/main.cpp b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/main.cpp new file mode 100644 index 00000000..73c0922d --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/main.cpp @@ -0,0 +1,11 @@ +#include "../dllexport.h" +#include + +DLL_IMPORT int dynamic1_hello(); + +int main() +{ + int result = dynamic1_hello(); + puts("application says hello!"); + return result; +} diff --git a/tests/auto/api/testdata/link-dynamiclibs-staticlibs/static1.cpp b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/static1.cpp new file mode 100644 index 00000000..81c53bd4 --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/static1.cpp @@ -0,0 +1,10 @@ +#include "../dllexport.h" +#include + +DLL_IMPORT void dynamic2_hello(); + +void static1_hello() +{ + dynamic2_hello(); + puts("static1 says hello!"); +} diff --git a/tests/auto/api/testdata/link-dynamiclibs-staticlibs/static2.cpp b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/static2.cpp new file mode 100644 index 00000000..073b1360 --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/static2.cpp @@ -0,0 +1,7 @@ +#include "static2.h" +#include + +void TestMe::hello() const +{ + puts("static2 says hello!"); +} diff --git a/tests/auto/api/testdata/link-dynamiclibs-staticlibs/static2.h b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/static2.h new file mode 100644 index 00000000..2e07ef32 --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs-staticlibs/static2.h @@ -0,0 +1,10 @@ +#ifndef STATIC2_H +#define STATIC2_H + +class TestMe +{ +public: + void hello() const; +}; + +#endif // STATIC2_H diff --git a/tests/auto/api/testdata/link-dynamiclibs/lib1.cpp b/tests/auto/api/testdata/link-dynamiclibs/lib1.cpp new file mode 100644 index 00000000..14666cf5 --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs/lib1.cpp @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" +#include + +DLL_IMPORT void lib2_hello(); + +DLL_EXPORT int lib1_hello() +{ + puts("lib1 says hello!"); + lib2_hello(); + return 0; +} diff --git a/tests/auto/api/testdata/link-dynamiclibs/lib2.cpp b/tests/auto/api/testdata/link-dynamiclibs/lib2.cpp new file mode 100644 index 00000000..01938b16 --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs/lib2.cpp @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" +#include + +DLL_IMPORT void lib3_hello(); + +DLL_EXPORT void lib2_hello() +{ + puts("lib2 says hello!"); + lib3_hello(); +} + diff --git a/tests/auto/api/testdata/link-dynamiclibs/lib3.cpp b/tests/auto/api/testdata/link-dynamiclibs/lib3.cpp new file mode 100644 index 00000000..b90a9998 --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs/lib3.cpp @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" +#include + +DLL_EXPORT void lib3_hello() +{ + puts("lib3 says hello!"); +} + +DLL_EXPORT char* lib3_greeting() +{ + static char greeting[] = "hello"; + return greeting; +} + diff --git a/tests/auto/api/testdata/link-dynamiclibs/lib4.cpp b/tests/auto/api/testdata/link-dynamiclibs/lib4.cpp new file mode 100644 index 00000000..2d66e40f --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs/lib4.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** 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 "lib4.h" + +TestMe::TestMe() +{ +} + +void TestMe::hello1() const +{ + puts("lib4 says hello!"); +} + +void TestMe::hello2Impl() const +{ + puts("lib4 says hello inline!"); +} diff --git a/tests/auto/api/testdata/link-dynamiclibs/lib4.h b/tests/auto/api/testdata/link-dynamiclibs/lib4.h new file mode 100644 index 00000000..63258b2e --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs/lib4.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef LIB4_H +#define LIB4_H + +#include "../dllexport.h" +#include + +#ifdef TEST_LIB +# define LIB_EXPORT DLL_EXPORT +#else +# define LIB_EXPORT DLL_IMPORT +#endif + +class LIB_EXPORT TestMe +{ +public: + TestMe(); + void hello1() const; + inline void hello2() const { hello2Impl(); } +private: + void hello2Impl() const; +}; + +#endif // LIB4_H diff --git a/tests/auto/api/testdata/link-dynamiclibs/link-dynamiclibs.qbs b/tests/auto/api/testdata/link-dynamiclibs/link-dynamiclibs.qbs new file mode 100644 index 00000000..cc86a440 --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs/link-dynamiclibs.qbs @@ -0,0 +1,71 @@ +Project { + Application { + name : "HelloWorld" + Group { + files : [ "main.cpp" ] + } + Depends { name: "cpp" } + Depends { name: "lib1" } + Depends { name: "lib4" } + } + + DynamicLibrary { + name : "lib1" + Group { + files : [ "lib1.cpp" ] + } + Depends { name: "cpp" } + Depends { name: "lib2" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } + + DynamicLibrary { + name : "lib2" + cpp.visibility: 'default' + Group { + files : [ "lib2.cpp" ] + } + Depends { name: "cpp" } + Depends { name: "lib3" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } + + DynamicLibrary { + name : "lib3" + cpp.visibility: 'hidden' + Group { + files : [ "lib3.cpp" ] + } + Depends { name: "cpp" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } + + DynamicLibrary { + name : "lib4" + cpp.visibility: 'hiddenInlines' + cpp.defines: "TEST_LIB" + Group { + files : [ "lib4.h", "lib4.cpp" ] + } + Depends { name: "cpp" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + + Export { + Depends { name: "cpp" } + cpp.includePaths: [product.sourceDirectory] + } + } +} + diff --git a/tests/auto/api/testdata/link-dynamiclibs/main.cpp b/tests/auto/api/testdata/link-dynamiclibs/main.cpp new file mode 100644 index 00000000..64f7948c --- /dev/null +++ b/tests/auto/api/testdata/link-dynamiclibs/main.cpp @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** 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 +#include + +DLL_IMPORT int lib1_hello(); + +int main() +{ + puts("application says hello!"); + TestMe test; + test.hello1(); + test.hello2(); + return lib1_hello(); +} + diff --git a/tests/auto/api/testdata/link-static-lib/helper1/helper1.cpp b/tests/auto/api/testdata/link-static-lib/helper1/helper1.cpp new file mode 100644 index 00000000..178cdbf1 --- /dev/null +++ b/tests/auto/api/testdata/link-static-lib/helper1/helper1.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 "helper1.h" +#include + +int getSomeNumber() +{ + return 12 * getOddNumber(); +} + diff --git a/tests/auto/api/testdata/link-static-lib/helper1/helper1.h b/tests/auto/api/testdata/link-static-lib/helper1/helper1.h new file mode 100644 index 00000000..eec93c85 --- /dev/null +++ b/tests/auto/api/testdata/link-static-lib/helper1/helper1.h @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef HELPER1_H +#define HELPER1_H + +extern int getSomeNumber(); + +#endif + diff --git a/tests/auto/api/testdata/link-static-lib/helper2/helper2.cpp b/tests/auto/api/testdata/link-static-lib/helper2/helper2.cpp new file mode 100644 index 00000000..b1ddfa9e --- /dev/null +++ b/tests/auto/api/testdata/link-static-lib/helper2/helper2.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 "helper2.h" + +int getOddNumber() +{ + return 13; +} + diff --git a/tests/auto/api/testdata/link-static-lib/helper2/helper2.h b/tests/auto/api/testdata/link-static-lib/helper2/helper2.h new file mode 100644 index 00000000..e246033c --- /dev/null +++ b/tests/auto/api/testdata/link-static-lib/helper2/helper2.h @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef HELPER2_H +#define HELPER2_H + +extern int getOddNumber(); + +#endif + diff --git a/tests/auto/api/testdata/link-static-lib/link-static-lib.qbs b/tests/auto/api/testdata/link-static-lib/link-static-lib.qbs new file mode 100644 index 00000000..6960c7d1 --- /dev/null +++ b/tests/auto/api/testdata/link-static-lib/link-static-lib.qbs @@ -0,0 +1,45 @@ +Project { + Product { + type: "application" + consoleApplication: true + name: "HelloWorld" + files : [ "main.cpp" ] + Depends { name: "cpp" } + Depends { name: "mystaticlib" } + } + + StaticLibrary { + name : "mystaticlib" + files : [ "mystaticlib.cpp" ] + Depends { name: "cpp" } + Depends { name: "helper1" } + } + + StaticLibrary { + name : "helper1" + files : [ + "helper1/helper1.h", + "helper1/helper1.cpp" + ] + Depends { name: "cpp" } + Depends { name: "helper2" } + Export { + Depends { name: "cpp" } + cpp.includePaths: [product.sourceDirectory + '/helper1'] + } + } + + StaticLibrary { + name : "helper2" + files : [ + "helper2/helper2.h", + "helper2/helper2.cpp" + ] + Depends { name: "cpp" } + Export { + Depends { name: "cpp" } + cpp.includePaths: [product.sourceDirectory + '/helper2'] + } + } +} + diff --git a/tests/auto/api/testdata/link-static-lib/main.cpp b/tests/auto/api/testdata/link-static-lib/main.cpp new file mode 100644 index 00000000..5f6aed0b --- /dev/null +++ b/tests/auto/api/testdata/link-static-lib/main.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 + +int bla(); + +int main() +{ + return bla(); +} + diff --git a/tests/auto/api/testdata/link-static-lib/mystaticlib.cpp b/tests/auto/api/testdata/link-static-lib/mystaticlib.cpp new file mode 100644 index 00000000..71777ef0 --- /dev/null +++ b/tests/auto/api/testdata/link-static-lib/mystaticlib.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 +#include + +int bla() +{ + int n = getSomeNumber(); + printf("Hello World! The magic number is %d.", n); + return n; +} diff --git a/tests/auto/api/testdata/link-static-lib/mystaticlibhelper.cpp b/tests/auto/api/testdata/link-static-lib/mystaticlibhelper.cpp new file mode 100644 index 00000000..96ccb1b4 --- /dev/null +++ b/tests/auto/api/testdata/link-static-lib/mystaticlibhelper.cpp @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int helper_function() +{ + return 156; +} + diff --git a/tests/auto/api/testdata/link-staticlib-dynamiclib/link-staticlib-dynamiclib.qbs b/tests/auto/api/testdata/link-staticlib-dynamiclib/link-staticlib-dynamiclib.qbs new file mode 100644 index 00000000..8573b2f6 --- /dev/null +++ b/tests/auto/api/testdata/link-staticlib-dynamiclib/link-staticlib-dynamiclib.qbs @@ -0,0 +1,19 @@ +Project { + CppApplication { + Depends { name: "mystaticlib" } + name: "app" + consoleApplication: true + files: ["main.cpp"] + } + StaticLibrary { + Depends { name: "cpp" } + Depends { name: "mydynamiclib" } + name: "mystaticlib" + files: ["mystaticlib.cpp"] + } + DynamicLibrary { + name: "mydynamiclib" + Depends { name: "cpp" } + files: ["mydynamiclib.cpp"] + } +} diff --git a/tests/auto/api/testdata/link-staticlib-dynamiclib/main.cpp b/tests/auto/api/testdata/link-staticlib-dynamiclib/main.cpp new file mode 100644 index 00000000..f371b16a --- /dev/null +++ b/tests/auto/api/testdata/link-staticlib-dynamiclib/main.cpp @@ -0,0 +1,7 @@ +int static_foo(); + +int main() +{ + return static_foo() - 156; +} + diff --git a/tests/auto/api/testdata/link-staticlib-dynamiclib/mydynamiclib.cpp b/tests/auto/api/testdata/link-staticlib-dynamiclib/mydynamiclib.cpp new file mode 100644 index 00000000..2918b364 --- /dev/null +++ b/tests/auto/api/testdata/link-staticlib-dynamiclib/mydynamiclib.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT int dynamic_foo() { return 12; } diff --git a/tests/auto/api/testdata/link-staticlib-dynamiclib/mystaticlib.cpp b/tests/auto/api/testdata/link-staticlib-dynamiclib/mystaticlib.cpp new file mode 100644 index 00000000..31a1ab0d --- /dev/null +++ b/tests/auto/api/testdata/link-staticlib-dynamiclib/mystaticlib.cpp @@ -0,0 +1,5 @@ +#include "../dllexport.h" + +DLL_IMPORT int dynamic_foo(); + +int static_foo() { return dynamic_foo() * 13; } diff --git a/tests/auto/api/testdata/link-staticlibs-dynamiclibs/dynamic1.cpp b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/dynamic1.cpp new file mode 100644 index 00000000..3f8a5f8d --- /dev/null +++ b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/dynamic1.cpp @@ -0,0 +1,11 @@ +#include "../dllexport.h" +#include "static2.h" +#include + +DLL_EXPORT int dynamic1_hello() +{ + TestMe tm; + tm.hello(); + puts("dynamic1 says hello!"); + return 1; +} diff --git a/tests/auto/api/testdata/link-staticlibs-dynamiclibs/dynamic2.cpp b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/dynamic2.cpp new file mode 100644 index 00000000..75594185 --- /dev/null +++ b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/dynamic2.cpp @@ -0,0 +1,7 @@ +#include "../dllexport.h" +#include + +DLL_EXPORT void dynamic2_hello() +{ + puts("dynamic2 says hello!"); +} diff --git a/tests/auto/api/testdata/link-staticlibs-dynamiclibs/link-staticlibs-dynamiclibs.qbs b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/link-staticlibs-dynamiclibs.qbs new file mode 100644 index 00000000..d7ed6c86 --- /dev/null +++ b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/link-staticlibs-dynamiclibs.qbs @@ -0,0 +1,52 @@ +Project { + Application { + name : "HelloWorld" + files : [ "main.cpp" ] + Depends { name: "cpp" } + Depends { name: "static1" } + } + + StaticLibrary { + name: "static1" + files: [ "static1.cpp" ] + Depends { name: "cpp" } + Depends { name: "dynamic1" } + + Probe { + id: osCheck + property bool isNormalUnix: qbs.targetOS.contains("unix") + && !qbs.targetOS.contains("darwin") + configure: { console.info("is normal unix: " + (isNormalUnix ? "yes" : "no")); } + } + } + + DynamicLibrary { + name : "dynamic1" + files : [ "dynamic1.cpp" ] + Depends { name: "cpp" } + Depends { name: "static2" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } + + StaticLibrary { + name: "static2" + files: [ "static2.cpp", "static2.h" ] + Depends { name: "cpp" } + Depends { name: "dynamic2" } + } + + DynamicLibrary { + name: "dynamic2" + files: [ "dynamic2.cpp" ] + Depends { name: "cpp" } + cpp.visibility: 'hidden' + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } +} + diff --git a/tests/auto/api/testdata/link-staticlibs-dynamiclibs/main.cpp b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/main.cpp new file mode 100644 index 00000000..4b819334 --- /dev/null +++ b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/main.cpp @@ -0,0 +1,10 @@ +#include + +void static1_hello(); + +int main() +{ + static1_hello(); + puts("application says hello!"); + return 0; +} diff --git a/tests/auto/api/testdata/link-staticlibs-dynamiclibs/static1.cpp b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/static1.cpp new file mode 100644 index 00000000..a3058c63 --- /dev/null +++ b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/static1.cpp @@ -0,0 +1,10 @@ +#include "../dllexport.h" +#include + +DLL_IMPORT int dynamic1_hello(); + +void static1_hello() +{ + int n = dynamic1_hello(); + printf("static%d says hello!\n", n); +} diff --git a/tests/auto/api/testdata/link-staticlibs-dynamiclibs/static2.cpp b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/static2.cpp new file mode 100644 index 00000000..374bf7ce --- /dev/null +++ b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/static2.cpp @@ -0,0 +1,11 @@ +#include "../dllexport.h" +#include "static2.h" +#include + +DLL_IMPORT void dynamic2_hello(); + +void TestMe::hello() const +{ + dynamic2_hello(); + puts("static2 says hello!"); +} diff --git a/tests/auto/api/testdata/link-staticlibs-dynamiclibs/static2.h b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/static2.h new file mode 100644 index 00000000..2e07ef32 --- /dev/null +++ b/tests/auto/api/testdata/link-staticlibs-dynamiclibs/static2.h @@ -0,0 +1,10 @@ +#ifndef STATIC2_H +#define STATIC2_H + +class TestMe +{ +public: + void hello() const; +}; + +#endif // STATIC2_H 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 00000000..c6091f9f --- /dev/null +++ b/tests/auto/api/testdata/local-profiles/local-profiles.qbs @@ -0,0 +1,44 @@ +Project { + property string windowsProfile: "windowsProfile" + property bool enableProfiles + property string mingwToolchain: "mingw" + property string mingwProfile: "mingwProfile" + Profile { + name: windowsProfile + qbs.targetPlatform: "windows" + } + + Profile { + name: project.mingwProfile + condition: enableProfiles + baseProfile: project.windowsProfile + qbs.toolchainType: project.mingwToolchain + } + + Application { + name: "app" + Depends { name: "cpp"; required: false } + aggregate: false + multiplexByQbsProperties: ["buildVariants"] + qbs.buildVariants: ["debug", "release"] + qbs.profile: project.mingwProfile + } + DynamicLibrary { + name: "lib" + + Depends { name: "cpp"; required: false } + + property string clangToolchain: "clang" + property string clangProfileName: "clangProfile" + + Profile { + name: product.clangProfileName + condition: project.enableProfiles + qbs.targetPlatform: "linux" + qbs.toolchainType: product.clangToolchain + } + + multiplexByQbsProperties: ["profiles"] + qbs.profiles: [project.mingwProfile, "clangProfile"] + } +} diff --git a/tests/auto/api/testdata/lots-of-dots/dotty.matrix.ui b/tests/auto/api/testdata/lots-of-dots/dotty.matrix.ui new file mode 100644 index 00000000..4b7d6a45 --- /dev/null +++ b/tests/auto/api/testdata/lots-of-dots/dotty.matrix.ui @@ -0,0 +1,21 @@ + + + + + Form + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + diff --git a/tests/auto/api/testdata/lots-of-dots/lots-of-dots.qbs b/tests/auto/api/testdata/lots-of-dots/lots-of-dots.qbs new file mode 100644 index 00000000..096fcaf0 --- /dev/null +++ b/tests/auto/api/testdata/lots-of-dots/lots-of-dots.qbs @@ -0,0 +1,16 @@ +Project { + QtGuiApplication { + type: "application" + consoleApplication: true + name: "lots.of.dots" + cpp.cxxLanguageVersion: "c++11" + files : [ + "m.a.i.n.cpp", + "object.narf.h", + "object.narf.cpp", + "polka.dots.qrc", + "dotty.matrix.ui" + ] + } +} + diff --git a/tests/auto/api/testdata/lots-of-dots/m.a.i.n.cpp b/tests/auto/api/testdata/lots-of-dots/m.a.i.n.cpp new file mode 100644 index 00000000..427f9907 --- /dev/null +++ b/tests/auto/api/testdata/lots-of-dots/m.a.i.n.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** 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 "object.narf.h" +#include +#include + +int main() +{ + ObjectNarf obj; + puts("..."); +} + diff --git a/tests/auto/api/testdata/lots-of-dots/object.narf.cpp b/tests/auto/api/testdata/lots-of-dots/object.narf.cpp new file mode 100644 index 00000000..3b09b744 --- /dev/null +++ b/tests/auto/api/testdata/lots-of-dots/object.narf.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 "object.narf.h" +#include + +ObjectNarf::ObjectNarf(QObject *parent) + : QObject(parent) +{} + diff --git a/tests/auto/api/testdata/lots-of-dots/object.narf.h b/tests/auto/api/testdata/lots-of-dots/object.narf.h new file mode 100644 index 00000000..76147bf4 --- /dev/null +++ b/tests/auto/api/testdata/lots-of-dots/object.narf.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef OBJECT_H +#define OBJECT_H +#include + +class ObjectNarf : public QObject +{ + Q_OBJECT +public: + ObjectNarf(QObject *parent = nullptr); +}; + +#endif + diff --git a/tests/auto/api/testdata/lots-of-dots/polka.dots.qrc b/tests/auto/api/testdata/lots-of-dots/polka.dots.qrc new file mode 100644 index 00000000..815b1100 --- /dev/null +++ b/tests/auto/api/testdata/lots-of-dots/polka.dots.qrc @@ -0,0 +1,5 @@ + + + m.a.i.n.cpp + + diff --git a/tests/auto/api/testdata/missing-qobject-header/main.cpp b/tests/auto/api/testdata/missing-qobject-header/main.cpp new file mode 100644 index 00000000..5e371580 --- /dev/null +++ b/tests/auto/api/testdata/missing-qobject-header/main.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 "myobject.h" + +int main() +{ + MyObject().func(); +} diff --git a/tests/auto/api/testdata/missing-qobject-header/myobject.cpp b/tests/auto/api/testdata/missing-qobject-header/myobject.cpp new file mode 100644 index 00000000..5fc1d328 --- /dev/null +++ b/tests/auto/api/testdata/missing-qobject-header/myobject.cpp @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** 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 "myobject.h" + +void MyObject::func() +{ +} diff --git a/tests/auto/api/testdata/missing-qobject-header/myobject.h b/tests/auto/api/testdata/missing-qobject-header/myobject.h new file mode 100644 index 00000000..a2a97db9 --- /dev/null +++ b/tests/auto/api/testdata/missing-qobject-header/myobject.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** 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 + +class MyObject : public QObject +{ + Q_OBJECT + +public: + void func(); + +}; diff --git a/tests/auto/api/testdata/missing-source-file/file1.txt b/tests/auto/api/testdata/missing-source-file/file1.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/missing-source-file/file2.txt.missing b/tests/auto/api/testdata/missing-source-file/file2.txt.missing new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/missing-source-file/file3.txt b/tests/auto/api/testdata/missing-source-file/file3.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/missing-source-file/missing-source-file.qbs b/tests/auto/api/testdata/missing-source-file/missing-source-file.qbs new file mode 100644 index 00000000..bc17bc7d --- /dev/null +++ b/tests/auto/api/testdata/missing-source-file/missing-source-file.qbs @@ -0,0 +1,7 @@ +Product { + files: [ + "file1.txt", + "file2.txt", + "file3.txt", + ] +} diff --git a/tests/auto/api/testdata/moc-cpp/bla.cpp b/tests/auto/api/testdata/moc-cpp/bla.cpp new file mode 100644 index 00000000..2b73d97e --- /dev/null +++ b/tests/auto/api/testdata/moc-cpp/bla.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** 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 + +class MyObject : public QObject +{ + Q_OBJECT +}; + +int main() +{ + MyObject obj; + return 0; +} + +#include "bla.moc" + diff --git a/tests/auto/api/testdata/moc-cpp/moc-cpp.qbs b/tests/auto/api/testdata/moc-cpp/moc-cpp.qbs new file mode 100644 index 00000000..a6445a39 --- /dev/null +++ b/tests/auto/api/testdata/moc-cpp/moc-cpp.qbs @@ -0,0 +1,13 @@ +Project { + Product { + type: "application" + consoleApplication: true + name: "moc_cpp" + + Depends { + name: "Qt.core" + } + + files: ["bla.cpp"] + } +} diff --git a/tests/auto/api/testdata/moc-hpp-included/moc-hpp-included.qbs b/tests/auto/api/testdata/moc-hpp-included/moc-hpp-included.qbs new file mode 100644 index 00000000..a484b9c2 --- /dev/null +++ b/tests/auto/api/testdata/moc-hpp-included/moc-hpp-included.qbs @@ -0,0 +1,19 @@ +Project { + Product { + type: "application" + consoleApplication: true + name: "moc_hpp_included" + + Depends { name: "Qt.core" } + + cpp.cxxLanguageVersion: "c++11" + + files: ["object.cpp", "object.h"] + + Group { + condition: qbs.targetOS.contains("darwin") + files: ["object2.mm", "object2.h"] + } + } +} + diff --git a/tests/auto/api/testdata/moc-hpp-included/object.cpp b/tests/auto/api/testdata/moc-hpp-included/object.cpp new file mode 100644 index 00000000..4f1502af --- /dev/null +++ b/tests/auto/api/testdata/moc-hpp-included/object.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** 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 "object.h" + +Object::Object(QObject *parent) + : QObject(parent) +{} + +#include "moc_object.cpp" +#include + +int main() +{ + Object obj; + printf("Hello World\n"); +} + diff --git a/tests/auto/api/testdata/moc-hpp-included/object.h b/tests/auto/api/testdata/moc-hpp-included/object.h new file mode 100644 index 00000000..3a0a5581 --- /dev/null +++ b/tests/auto/api/testdata/moc-hpp-included/object.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef OBJECT_H +#define OBJECT_H +#include + +class Object : public QObject +{ + Q_OBJECT +public: + Object(QObject *parent = nullptr); +}; + +#endif + diff --git a/tests/auto/api/testdata/moc-hpp-included/object2.h b/tests/auto/api/testdata/moc-hpp-included/object2.h new file mode 100644 index 00000000..b73f4b0d --- /dev/null +++ b/tests/auto/api/testdata/moc-hpp-included/object2.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef OBJECT2_H +#define OBJECT2_H +#include + +class Object2 : public QObject +{ + Q_OBJECT +public: + Object2(QObject *parent = nullptr); +}; + +#endif + diff --git a/tests/auto/api/testdata/moc-hpp-included/object2.mm b/tests/auto/api/testdata/moc-hpp-included/object2.mm new file mode 100644 index 00000000..6fd95d31 --- /dev/null +++ b/tests/auto/api/testdata/moc-hpp-included/object2.mm @@ -0,0 +1,16 @@ +#include "object2.h" + +Object2::Object2(QObject *parent) + : QObject(parent) +{} + +#include "moc_object2.cpp" +#include + +int main2() +{ + Object2 obj; + printf("Hello World\n"); + return 0; +} + diff --git a/tests/auto/api/testdata/moc-hpp/moc-hpp.qbs b/tests/auto/api/testdata/moc-hpp/moc-hpp.qbs new file mode 100644 index 00000000..27120aad --- /dev/null +++ b/tests/auto/api/testdata/moc-hpp/moc-hpp.qbs @@ -0,0 +1,17 @@ +Project { + Product { + type: "application" + consoleApplication: true + name: "moc_hpp" + + Depends { name: "Qt.core" } + + cpp.cxxLanguageVersion: "c++11" + + files : [ + "object.h", + "object.cpp" + ] + } +} + diff --git a/tests/auto/api/testdata/moc-hpp/object.cpp b/tests/auto/api/testdata/moc-hpp/object.cpp new file mode 100644 index 00000000..601893c3 --- /dev/null +++ b/tests/auto/api/testdata/moc-hpp/object.cpp @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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 "object.h" +#include + +Object::Object(QObject *parent) + : QObject(parent) +{} + +int main() +{ + Object obj; + printf("Hello World\n"); +} + diff --git a/tests/auto/api/testdata/moc-hpp/object.h b/tests/auto/api/testdata/moc-hpp/object.h new file mode 100644 index 00000000..3a0a5581 --- /dev/null +++ b/tests/auto/api/testdata/moc-hpp/object.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef OBJECT_H +#define OBJECT_H +#include + +class Object : public QObject +{ + Q_OBJECT +public: + Object(QObject *parent = nullptr); +}; + +#endif + diff --git a/tests/auto/api/testdata/multi-arch/host+target.input b/tests/auto/api/testdata/multi-arch/host+target.input new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/multi-arch/host-tool.input b/tests/auto/api/testdata/multi-arch/host-tool.input new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/multi-arch/multi-arch.qbs b/tests/auto/api/testdata/multi-arch/multi-arch.qbs new file mode 100644 index 00000000..71a66dfc --- /dev/null +++ b/tests/auto/api/testdata/multi-arch/multi-arch.qbs @@ -0,0 +1,56 @@ +import qbs.FileInfo +import qbs.TextFile + +Project { + property string hostProfile + property string targetProfile + Product { + property stringList myProfiles + name: "p1" + type: "output" + qbs.profiles: myProfiles ? myProfiles : [project.targetProfile, project.hostProfile] + Group { + files: "host+target.input" + fileTags: "input" + } + qbs.installPrefix: "" + Group { + fileTagsFilter: "output" + qbs.install: true + qbs.installDir: qbs.profile + } + } + Product { + name: "p2" + type: "output" + qbs.profiles: [project.hostProfile] + Group { + files: "host-tool.input" + fileTags: "input" + } + qbs.installPrefix: "" + Group { + fileTagsFilter: "output" + qbs.install: true + qbs.installDir: qbs.profile + } + } + + Rule { + inputs: "input" + Artifact { + filePath: FileInfo.baseName(input.fileName) + ".output" + fileTags: "output" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { + var file = new TextFile(output.filePath, TextFile.WriteOnly); + file.write(product.moduleProperty("qbs", "architecture")); + file.close(); + } + return cmd; + } + } +} diff --git a/tests/auto/api/testdata/multiplexing/multiplexing.qbs b/tests/auto/api/testdata/multiplexing/multiplexing.qbs new file mode 100644 index 00000000..75958ed6 --- /dev/null +++ b/tests/auto/api/testdata/multiplexing/multiplexing.qbs @@ -0,0 +1,94 @@ +import qbs.TextFile + +Project { + Project { + name: "subproject 1" + Product { + name: "multiplex-using-export" + multiplexByQbsProperties: ["architectures"] + qbs.architectures: ["TRS-80", "C64"] + Depends { name: "multiplex-with-export" } + } + Product { + name: "multiplex-without-aggregator-2-depend-on-non-multiplexed" + multiplexByQbsProperties: ["architectures"] + qbs.architectures: ["TRS-80", "C64"] + Depends { name: "no-multiplexing" } + } + Product { + name: "multiplex-with-aggregator-2" + aggregate: true + multiplexByQbsProperties: ["architectures"] + qbs.architectures: ["TRS-80", "C64"] + qbs.architecture: "Atari ST" + } + Product { + name: "multiplex-with-aggregator-2-dependent" + Depends { name: "multiplex-with-aggregator-2" } + } + Product { + name: "non-multiplexed-with-dependencies-on-multiplexed" + Depends { name: "multiplex-without-aggregator-2" } + } + Product { + name: "non-multiplexed-with-dependencies-on-multiplexed-via-export1" + Depends { name: "multiplex-with-export" } + } + Product { + name: "non-multiplexed-with-dependencies-on-multiplexed-via-export2" + Depends { name: "nonmultiplex-with-export" } + } + Product { + name: "non-multiplexed-with-dependencies-on-aggregation-via-export" + Depends { name: "nonmultiplex-exporting-aggregation" } + } + } + + Project { + name: "subproject 2" + Product { + name: "no-multiplexing" + } + Product { + name: "multiplex-without-aggregator-2" + multiplexByQbsProperties: ["architectures"] + qbs.architectures: ["TRS-80", "C64"] + } + Product { + name: "multiplex-with-export" + multiplexByQbsProperties: ["architectures"] + qbs.architectures: ["TRS-80", "C64"] + Export { Depends { name: "multiplex-without-aggregator-2" } } + } + Product { + name: "nonmultiplex-with-export" + Export { Depends { name: "multiplex-without-aggregator-2" } } + } + Product { + name: "nonmultiplex-exporting-aggregation" + Export { Depends { name: "multiplex-with-aggregator-2" } } + } + Product { + name: "multiplex-without-aggregator-4" + multiplexByQbsProperties: ["architectures", "buildVariants"] + qbs.architectures: ["TRS-80", "C64"] + qbs.buildVariants: ["debug", "release"] + } + Product { + name: "multiplex-without-aggregator-4-depends-2" + multiplexByQbsProperties: ["architectures", "buildVariants"] + qbs.architectures: ["TRS-80", "C64"] + qbs.buildVariants: ["debug", "release"] + Depends { name: "multiplex-without-aggregator-2" } + } + } + + Product { + name: "aggregate-with-dependencies-on-aggregation-via-export" + Depends { name: "nonmultiplex-exporting-aggregation" } + aggregate: true + multiplexByQbsProperties: ["architectures"] + qbs.architectures: ["TRS-80", "C64"] + qbs.architecture: "Atari ST" + } +} diff --git a/tests/auto/api/testdata/new-output-artifact-in-dependency/lib.cpp b/tests/auto/api/testdata/new-output-artifact-in-dependency/lib.cpp new file mode 100644 index 00000000..5997c193 --- /dev/null +++ b/tests/auto/api/testdata/new-output-artifact-in-dependency/lib.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" + +DLL_EXPORT void f() {} diff --git a/tests/auto/api/testdata/new-output-artifact-in-dependency/main.cpp b/tests/auto/api/testdata/new-output-artifact-in-dependency/main.cpp new file mode 100644 index 00000000..1d53a1b2 --- /dev/null +++ b/tests/auto/api/testdata/new-output-artifact-in-dependency/main.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +//void f(); + +int main() +{ +// f(); +} diff --git a/tests/auto/api/testdata/new-output-artifact-in-dependency/new-output-artifact-in-dependency.qbs b/tests/auto/api/testdata/new-output-artifact-in-dependency/new-output-artifact-in-dependency.qbs new file mode 100644 index 00000000..5c3e475b --- /dev/null +++ b/tests/auto/api/testdata/new-output-artifact-in-dependency/new-output-artifact-in-dependency.qbs @@ -0,0 +1,17 @@ +Project { + DynamicLibrary { + //Depends { name: "cpp" } + name: "lib" + files: "lib.cpp" + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } + + CppApplication { + name: "app" + files: "main.cpp" + Depends { name: "lib" } + } +} diff --git a/tests/auto/api/testdata/new-pattern-match/new-pattern-match.qbs b/tests/auto/api/testdata/new-pattern-match/new-pattern-match.qbs new file mode 100644 index 00000000..f4e0856f --- /dev/null +++ b/tests/auto/api/testdata/new-pattern-match/new-pattern-match.qbs @@ -0,0 +1,3 @@ +Product { + files: "*.txt" +} diff --git a/tests/auto/api/testdata/nonexistingprojectproperties/invalidaccessfromproduct.qbs b/tests/auto/api/testdata/nonexistingprojectproperties/invalidaccessfromproduct.qbs new file mode 100644 index 00000000..5f479c1c --- /dev/null +++ b/tests/auto/api/testdata/nonexistingprojectproperties/invalidaccessfromproduct.qbs @@ -0,0 +1 @@ +Project { Product { type: project.blubb } } diff --git a/tests/auto/api/testdata/nonexistingprojectproperties/nonexistingprojectproperties.qbs b/tests/auto/api/testdata/nonexistingprojectproperties/nonexistingprojectproperties.qbs new file mode 100644 index 00000000..ba86b0ab --- /dev/null +++ b/tests/auto/api/testdata/nonexistingprojectproperties/nonexistingprojectproperties.qbs @@ -0,0 +1 @@ +Project { } diff --git a/tests/auto/api/testdata/objc/main.mm b/tests/auto/api/testdata/objc/main.mm new file mode 100644 index 00000000..c461fa43 --- /dev/null +++ b/tests/auto/api/testdata/objc/main.mm @@ -0,0 +1,13 @@ +#import +#include + +int main(int argc, char **argv) +{ + // We support both C++ + std::cout << "Hello from C++" << std::endl; + // And Objective-C + NSDictionary *version = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]; + NSString *productVersion = [version objectForKey:@"ProductVersion"]; + NSLog(@"Hello, macOS %@!", productVersion); + // So it's Objective-C++ +} diff --git a/tests/auto/api/testdata/objc/objc.qbs b/tests/auto/api/testdata/objc/objc.qbs new file mode 100644 index 00000000..845eb8d3 --- /dev/null +++ b/tests/auto/api/testdata/objc/objc.qbs @@ -0,0 +1,7 @@ +Project { + CppApplication { + condition: qbs.targetOS.contains("macos") + files: "main.mm" + cpp.frameworks: [ "Foundation" ] + } +} diff --git a/tests/auto/api/testdata/precompiled-header-dynamic/autogen.h.in b/tests/auto/api/testdata/precompiled-header-dynamic/autogen.h.in new file mode 100644 index 00000000..becfbba8 --- /dev/null +++ b/tests/auto/api/testdata/precompiled-header-dynamic/autogen.h.in @@ -0,0 +1,6 @@ +#ifndef AUTOGEN_IN_H +#define AUTOGEN_IN_H + +inline void f() { } + +#endif // AUTOGEN_IN_H diff --git a/tests/auto/api/testdata/precompiled-header-dynamic/main.cpp b/tests/auto/api/testdata/precompiled-header-dynamic/main.cpp new file mode 100644 index 00000000..45aace45 --- /dev/null +++ b/tests/auto/api/testdata/precompiled-header-dynamic/main.cpp @@ -0,0 +1,32 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() +{ + f(); +} diff --git a/tests/auto/api/testdata/precompiled-header-dynamic/pch.h b/tests/auto/api/testdata/precompiled-header-dynamic/pch.h new file mode 100644 index 00000000..8e7c7e5e --- /dev/null +++ b/tests/auto/api/testdata/precompiled-header-dynamic/pch.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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 "autogen.h" diff --git a/tests/auto/api/testdata/precompiled-header-dynamic/precompiled-header-dynamic.qbs b/tests/auto/api/testdata/precompiled-header-dynamic/precompiled-header-dynamic.qbs new file mode 100644 index 00000000..2fd58d24 --- /dev/null +++ b/tests/auto/api/testdata/precompiled-header-dynamic/precompiled-header-dynamic.qbs @@ -0,0 +1,29 @@ +import qbs.File + +CppApplication { + name: "MyApp" + consoleApplication: true + cpp.includePaths: [product.buildDirectory] + Group { + files: ["pch.h"] + fileTags: ["cpp_pch_src"] + } + Group { + files: ["autogen.h.in"] + fileTags: ["hpp.in"] + } + files: ["main.cpp"] + Rule { + inputs: ["hpp.in"] + Artifact { + filePath: "autogen.h" + fileTags: ["hpp"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Generating " + output.fileName; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); } + return [cmd]; + } + } +} diff --git a/tests/auto/api/testdata/precompiled-header-new/main.cpp b/tests/auto/api/testdata/precompiled-header-new/main.cpp new file mode 100644 index 00000000..b719b42e --- /dev/null +++ b/tests/auto/api/testdata/precompiled-header-new/main.cpp @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** 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 "myobject.h" +#include +#include +#include + +using namespace std; + +int main() +{ + MyObject obj; + list lst; + lst.push_back(1); + lst.push_back(2); + lst.push_back(3); + lst.push_back(4); + lst.push_back(5); + lst.push_back(6); + lst.push_back(7); + lst.push_back(8); + lst.push_back(9); + reverse(lst.begin(), lst.end()); + for (list::iterator it=lst.begin(); it != lst.end(); ++it) + cout << *it << ", "; + cout << endl; + return 0; +} + diff --git a/tests/auto/api/testdata/precompiled-header-new/myobject.cpp b/tests/auto/api/testdata/precompiled-header-new/myobject.cpp new file mode 100644 index 00000000..018b91e9 --- /dev/null +++ b/tests/auto/api/testdata/precompiled-header-new/myobject.cpp @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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 +#include "myobject.h" + +MyObject::MyObject() +{ + std::cout << "MyObject::MyObject()\n"; +} + +MyObject::~MyObject() +{ + std::cout << "MyObject::~MyObject()" << std::endl; +} + diff --git a/tests/auto/api/testdata/precompiled-header-new/myobject.h b/tests/auto/api/testdata/precompiled-header-new/myobject.h new file mode 100644 index 00000000..88d234c1 --- /dev/null +++ b/tests/auto/api/testdata/precompiled-header-new/myobject.h @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef MYOBJECT_H +#define MYOBJECT_H + +class MyObject +{ +public: + MyObject(); + ~MyObject(); +}; + +#endif + diff --git a/tests/auto/api/testdata/precompiled-header-new/precompiled-header-new.qbs b/tests/auto/api/testdata/precompiled-header-new/precompiled-header-new.qbs new file mode 100644 index 00000000..059400a2 --- /dev/null +++ b/tests/auto/api/testdata/precompiled-header-new/precompiled-header-new.qbs @@ -0,0 +1,10 @@ +CppApplication { + name: "MyApp" + consoleApplication: true + Group { + name: "precompiled headers" + files: ["stable.h"] + fileTags: ["cpp_pch_src"] + } + files: ["myobject.h", "main.cpp", "myobject.cpp"] +} diff --git a/tests/auto/api/testdata/precompiled-header-new/stable.h b/tests/auto/api/testdata/precompiled-header-new/stable.h new file mode 100644 index 00000000..d9a6222d --- /dev/null +++ b/tests/auto/api/testdata/precompiled-header-new/stable.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +/* Add C includes here */ + +#if defined __cplusplus +/* Add C++ includes here */ + +# include +# include +#endif + diff --git a/tests/auto/api/testdata/process-result/main.cpp b/tests/auto/api/testdata/process-result/main.cpp new file mode 100644 index 00000000..7b29c120 --- /dev/null +++ b/tests/auto/api/testdata/process-result/main.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 +#include + +int main(int argc, char *argv[]) +{ + std::cout << "stdout"; + std::cerr << "stderr"; + return atoi(argv[1]); +} diff --git a/tests/auto/api/testdata/process-result/process-result.qbs b/tests/auto/api/testdata/process-result/process-result.qbs new file mode 100644 index 00000000..5b71ecaa --- /dev/null +++ b/tests/auto/api/testdata/process-result/process-result.qbs @@ -0,0 +1,35 @@ +Project { + CppApplication { + name: "app" + consoleApplication: true + files: ["main.cpp"] + } + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: "app-caller" + type: "mytype" + Depends { name: "app" } + Depends { name: "cpp" } + property bool redirectStdout + property bool redirectStderr + property int argument + Rule { + inputsFromDependencies: ["application"] + outputFileTags: "mytype" + prepare: { + var cmd = new Command(inputs["application"][0].filePath, [product.argument]); + if (product.redirectStdout) + cmd.stdoutFilePath = product.buildDirectory + "/stdout.txt"; + if (product.redirectStderr) + cmd.stderrFilePath = product.buildDirectory + "/stderr.txt"; + cmd.description = "Building app-caller"; + return [cmd]; + } + } + } +} diff --git a/tests/auto/api/testdata/productNameWithDots/app.cpp b/tests/auto/api/testdata/productNameWithDots/app.cpp new file mode 100644 index 00000000..210c8274 --- /dev/null +++ b/tests/auto/api/testdata/productNameWithDots/app.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { return 0; } diff --git a/tests/auto/api/testdata/productNameWithDots/lib.cpp b/tests/auto/api/testdata/productNameWithDots/lib.cpp new file mode 100644 index 00000000..a28368d9 --- /dev/null +++ b/tests/auto/api/testdata/productNameWithDots/lib.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void foo() {} diff --git a/tests/auto/api/testdata/productNameWithDots/productNameWithDots.qbs b/tests/auto/api/testdata/productNameWithDots/productNameWithDots.qbs new file mode 100644 index 00000000..972598c4 --- /dev/null +++ b/tests/auto/api/testdata/productNameWithDots/productNameWithDots.qbs @@ -0,0 +1,13 @@ +Project { + CppApplication { + name: "myapp" + consoleApplication: true + Depends { name: "foo.bar.bla" } + files: ["app.cpp"] + } + StaticLibrary { + Depends { name: "cpp" } + name: "foo.bar.bla" + files: ["lib.cpp"] + } +} diff --git a/tests/auto/api/testdata/project-data-after-product-invalidation/file.cpp b/tests/auto/api/testdata/project-data-after-product-invalidation/file.cpp new file mode 100644 index 00000000..833057ed --- /dev/null +++ b/tests/auto/api/testdata/project-data-after-product-invalidation/file.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void f() { } diff --git a/tests/auto/api/testdata/project-data-after-product-invalidation/main.cpp b/tests/auto/api/testdata/project-data-after-product-invalidation/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/api/testdata/project-data-after-product-invalidation/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/api/testdata/project-data-after-product-invalidation/project-data-after-product-invalidation.qbs b/tests/auto/api/testdata/project-data-after-product-invalidation/project-data-after-product-invalidation.qbs new file mode 100644 index 00000000..1794205b --- /dev/null +++ b/tests/auto/api/testdata/project-data-after-product-invalidation/project-data-after-product-invalidation.qbs @@ -0,0 +1,7 @@ +CppApplication { + name: "theProduct" + files: [ + "file.cpp", + "main.cpp", + ] +} diff --git a/tests/auto/api/testdata/project-editing/existingfile1.txt b/tests/auto/api/testdata/project-editing/existingfile1.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/project-editing/existingfile2.txt b/tests/auto/api/testdata/project-editing/existingfile2.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/project-editing/existingfile3.txt b/tests/auto/api/testdata/project-editing/existingfile3.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/project-editing/file.cpp b/tests/auto/api/testdata/project-editing/file.cpp new file mode 100644 index 00000000..50841a22 --- /dev/null +++ b/tests/auto/api/testdata/project-editing/file.cpp @@ -0,0 +1,27 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ diff --git a/tests/auto/api/testdata/project-editing/file.h b/tests/auto/api/testdata/project-editing/file.h new file mode 100644 index 00000000..50841a22 --- /dev/null +++ b/tests/auto/api/testdata/project-editing/file.h @@ -0,0 +1,27 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ diff --git a/tests/auto/api/testdata/project-editing/main.cpp b/tests/auto/api/testdata/project-editing/main.cpp new file mode 100644 index 00000000..50841a22 --- /dev/null +++ b/tests/auto/api/testdata/project-editing/main.cpp @@ -0,0 +1,27 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ diff --git a/tests/auto/api/testdata/project-editing/newfile1.txt b/tests/auto/api/testdata/project-editing/newfile1.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/project-editing/newfile2.txt b/tests/auto/api/testdata/project-editing/newfile2.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/project-editing/newfile3.txt b/tests/auto/api/testdata/project-editing/newfile3.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/project-editing/newfile4.txt b/tests/auto/api/testdata/project-editing/newfile4.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/project-editing/project-editing.qbs b/tests/auto/api/testdata/project-editing/project-editing.qbs new file mode 100644 index 00000000..f8c0aef4 --- /dev/null +++ b/tests/auto/api/testdata/project-editing/project-editing.qbs @@ -0,0 +1,37 @@ +CppApplication { + Group { + name: "Existing Group 1" + files: ["existingfile1.txt"] + } + property string aFile: "existingfile2.txt" + Group { + name: "Existing Group 2" + files: product.aFile + } + Group { + name: "Existing Group 3" + files: { + var file = "existingfile3.txt"; + return file; + } + } + Group { + name: "Existing Group 4" + prefix: "subdir/" + files: [] + } + Group { + name: "Existing Group 5" + prefix: "blubb" + files: [] + } + Group { + name: "Group with wildcards" + files: "*.klaus" + } + Group { + name: "Other group with wildcards" + files: "*.wildcard" + } + files: "main.cpp" +} diff --git a/tests/auto/api/testdata/project-editing/project-with-no-files.qbs b/tests/auto/api/testdata/project-editing/project-with-no-files.qbs new file mode 100644 index 00000000..b12ac681 --- /dev/null +++ b/tests/auto/api/testdata/project-editing/project-with-no-files.qbs @@ -0,0 +1,5 @@ +CppApplication { + Group { + files: "file.cpp" + } +} diff --git a/tests/auto/api/testdata/project-editing/subdir/file.txt b/tests/auto/api/testdata/project-editing/subdir/file.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/project-editing/test.wildcard b/tests/auto/api/testdata/project-editing/test.wildcard new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/project-invalidation/project.early-error.qbs b/tests/auto/api/testdata/project-invalidation/project.early-error.qbs new file mode 100644 index 00000000..701da0ea --- /dev/null +++ b/tests/auto/api/testdata/project-invalidation/project.early-error.qbs @@ -0,0 +1,4 @@ +Product { + type: "mytype" + files: "nosuchfile.txt" +} diff --git a/tests/auto/api/testdata/project-invalidation/project.late-error.qbs b/tests/auto/api/testdata/project-invalidation/project.late-error.qbs new file mode 100644 index 00000000..ce033adb --- /dev/null +++ b/tests/auto/api/testdata/project-invalidation/project.late-error.qbs @@ -0,0 +1,12 @@ +Product { + type: "mytype" + + Rule { + inputs: ["mytype"] + Artifact { + filePath: "blubb" + fileTags: "mytype" + } + prepare: [] + } +} diff --git a/tests/auto/api/testdata/project-invalidation/project.no-error.qbs b/tests/auto/api/testdata/project-invalidation/project.no-error.qbs new file mode 100644 index 00000000..f37cbd64 --- /dev/null +++ b/tests/auto/api/testdata/project-invalidation/project.no-error.qbs @@ -0,0 +1,3 @@ +Product { + type: "mytype" +} diff --git a/tests/auto/api/testdata/project-locking/project-locking.qbs b/tests/auto/api/testdata/project-locking/project-locking.qbs new file mode 100644 index 00000000..5e7259ea --- /dev/null +++ b/tests/auto/api/testdata/project-locking/project-locking.qbs @@ -0,0 +1,2 @@ +Project { +} diff --git a/tests/auto/api/testdata/project-properties-by-name/main1.cpp b/tests/auto/api/testdata/project-properties-by-name/main1.cpp new file mode 100644 index 00000000..ddc4a766 --- /dev/null +++ b/tests/auto/api/testdata/project-properties-by-name/main1.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef SUB1 +#error "Missing define" +#endif +#ifdef SUB2 +#error "Extraneous define" +#endif + +int main() +{ +} diff --git a/tests/auto/api/testdata/project-properties-by-name/main2.cpp b/tests/auto/api/testdata/project-properties-by-name/main2.cpp new file mode 100644 index 00000000..7cc7fdc5 --- /dev/null +++ b/tests/auto/api/testdata/project-properties-by-name/main2.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef SUB2 +#error "Missing define" +#endif +#ifdef SUB1 +#error "Extraneous define" +#endif + +int main() +{ +} diff --git a/tests/auto/api/testdata/project-properties-by-name/project-properties-by-name.qbs b/tests/auto/api/testdata/project-properties-by-name/project-properties-by-name.qbs new file mode 100644 index 00000000..0bc9d3ea --- /dev/null +++ b/tests/auto/api/testdata/project-properties-by-name/project-properties-by-name.qbs @@ -0,0 +1,20 @@ +Project { + name: "toplevel" + property stringList theDefines: [] + Project { + name: "subproject1" + CppApplication { + name: "subproduct1" + files: ["main1.cpp"] + cpp.defines: project.theDefines + } + } + Project { + name: "subproject2" + CppApplication { + name: "subproduct2" + files: ["main2.cpp"] + cpp.defines: project.theDefines + } + } +} diff --git a/tests/auto/api/testdata/project-with-probe-and-profile-item/project-with-probe-and-profile-item.qbs b/tests/auto/api/testdata/project-with-probe-and-profile-item/project-with-probe-and-profile-item.qbs new file mode 100644 index 00000000..d7dc02cc --- /dev/null +++ b/tests/auto/api/testdata/project-with-probe-and-profile-item/project-with-probe-and-profile-item.qbs @@ -0,0 +1,19 @@ +Project { + + property bool probesEvaluated: probe.found + + Probe { + id: probe + configure: { + found = true; + } + } + + Profile { + name: "the-profile" + cpp.includePaths: { + if (!probesEvaluated) + throw "project-level probes not evaluated"; + } + } +} diff --git a/tests/auto/api/testdata/project-with-properties-item/project-with-properties-item.qbs b/tests/auto/api/testdata/project-with-properties-item/project-with-properties-item.qbs new file mode 100644 index 00000000..866ec4ec --- /dev/null +++ b/tests/auto/api/testdata/project-with-properties-item/project-with-properties-item.qbs @@ -0,0 +1,10 @@ +Project { + property string binPath: "/usr/bin" + property string libPath: "/usr/lib" + + Properties { + condition: qbs.targetOS.contains("macos") + binPath: "/Users/boo" + libPath: "/Libraries/foo" + } +} diff --git a/tests/auto/api/testdata/projectd b/tests/auto/api/testdata/projectd new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/properties-blocks/main.cpp b/tests/auto/api/testdata/properties-blocks/main.cpp new file mode 100644 index 00000000..5473bffa --- /dev/null +++ b/tests/auto/api/testdata/properties-blocks/main.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** 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 + +#ifndef HAVE_MAIN_CPP +# error missing define HAVE_MAIN_CPP +#endif +#ifndef DEFINE_IN_PROPERTIES +# error missing define DEFINE_IN_PROPERTIES +#endif + +int main() +{ +#ifdef _DEBUG + puts("Hello World! (debug version)"); +#else + puts("Hello World! (release version)"); +#endif +} + diff --git a/tests/auto/api/testdata/properties-blocks/properties-blocks.qbs b/tests/auto/api/testdata/properties-blocks/properties-blocks.qbs new file mode 100644 index 00000000..dda4652d --- /dev/null +++ b/tests/auto/api/testdata/properties-blocks/properties-blocks.qbs @@ -0,0 +1,27 @@ +Product { + Depends { name: 'cpp' } + + Properties { + condition: true + type: 'application' + consoleApplication: true + name: 'HelloWorld' + } + + Properties { + condition: name == 'HelloWorld' + cpp.defines: ['DEFINE_IN_PROPERTIES'] + } + + Properties { + condition: qbs.targetOS.contains("weird") + cpp.staticLibraries: "abc" + } + + Group { + cpp.defines: outer.concat(['HAVE_MAIN_CPP', cpp.debugInformation ? '_DEBUG' : '_RELEASE']) + files: ['main.cpp'] + } +} + + diff --git a/tests/auto/api/testdata/qt5-plugin/echointerface.h b/tests/auto/api/testdata/qt5-plugin/echointerface.h new file mode 100644 index 00000000..b414f247 --- /dev/null +++ b/tests/auto/api/testdata/qt5-plugin/echointerface.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef ECHOINTERFACE_H +#define ECHOINTERFACE_H + +#include + +//! [0] +class EchoInterface +{ +public: + virtual ~EchoInterface() {} + virtual QString echo(const QString &message) = 0; +}; + + +QT_BEGIN_NAMESPACE + +#define EchoInterface_iid "org.qt-project.Qt.Examples.EchoInterface" + +Q_DECLARE_INTERFACE(EchoInterface, EchoInterface_iid) +QT_END_NAMESPACE + +//! [0] +#endif diff --git a/tests/auto/api/testdata/qt5-plugin/echoplugin.cpp b/tests/auto/api/testdata/qt5-plugin/echoplugin.cpp new file mode 100644 index 00000000..0d4065fd --- /dev/null +++ b/tests/auto/api/testdata/qt5-plugin/echoplugin.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 "echoplugin.h" + +QString EchoPlugin::echo(const QString &message) +{ + return message; +} diff --git a/tests/auto/api/testdata/qt5-plugin/echoplugin.h b/tests/auto/api/testdata/qt5-plugin/echoplugin.h new file mode 100644 index 00000000..b8281c0b --- /dev/null +++ b/tests/auto/api/testdata/qt5-plugin/echoplugin.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef ECHOPLUGIN_H +#define ECHOPLUGIN_H + +#include +#include +#include "echointerface.h" + +class EchoPlugin : public QObject, EchoInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.EchoInterface" FILE "echoplugin.json") + Q_INTERFACES(EchoInterface) + +public: + QString echo(const QString &message); +}; + +#endif diff --git a/tests/auto/api/testdata/qt5-plugin/echoplugin.json.source b/tests/auto/api/testdata/qt5-plugin/echoplugin.json.source new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/tests/auto/api/testdata/qt5-plugin/echoplugin.json.source @@ -0,0 +1 @@ +{} diff --git a/tests/auto/api/testdata/qt5-plugin/echoplugin_dummy.cpp b/tests/auto/api/testdata/qt5-plugin/echoplugin_dummy.cpp new file mode 100644 index 00000000..bee4e9b2 --- /dev/null +++ b/tests/auto/api/testdata/qt5-plugin/echoplugin_dummy.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void dummyFunc() {} diff --git a/tests/auto/api/testdata/qt5-plugin/qt5-plugin.qbs b/tests/auto/api/testdata/qt5-plugin/qt5-plugin.qbs new file mode 100644 index 00000000..20471162 --- /dev/null +++ b/tests/auto/api/testdata/qt5-plugin/qt5-plugin.qbs @@ -0,0 +1,51 @@ +import qbs.base +import qbs.File +import qbs.FileInfo + +DynamicLibrary { + name: "echoplugin" + + Depends { name: "Qt.core" } + Depends { name: "cpp" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + + Group { + condition: Qt.core.versionMajor >= 5 + files: [ + "echoplugin.h", + "echoplugin.cpp", + ] + } + Group { + condition: Qt.core.versionMajor >= 5 + files: ["echoplugin.json.source"] + fileTags: ["json_in"] + } + + Group { + condition: Qt.core.versionMajor < 5 + files: "echoplugin_dummy.cpp" + } + + cpp.includePaths: buildDirectory + + Rule { + condition: Qt.core.versionMajor >= 5 + inputs: ["json_in"] + Artifact { + filePath: "echoplugin.json" + fileTags: ["qt_plugin_metadata"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + FileInfo.fileName(output.filePath); + cmd.sourceCode = function() { + File.copy(input.filePath, output.filePath); + } + return cmd; + } + } +} diff --git a/tests/auto/api/testdata/rc/main.cpp b/tests/auto/api/testdata/rc/main.cpp new file mode 100644 index 00000000..ed95605b --- /dev/null +++ b/tests/auto/api/testdata/rc/main.cpp @@ -0,0 +1,32 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() +{ + return 0; +} diff --git a/tests/auto/api/testdata/rc/rc.qbs b/tests/auto/api/testdata/rc/rc.qbs new file mode 100644 index 00000000..212adc4a --- /dev/null +++ b/tests/auto/api/testdata/rc/rc.qbs @@ -0,0 +1,15 @@ +Application { + type: "application" + consoleApplication: true + name: "rctest" + + Depends { name: 'cpp' } + + cpp.includePaths: "subdir" + + files: [ + "main.cpp", + "test.rc" + ] +} + diff --git a/tests/auto/api/testdata/rc/subdir/rc-include.h b/tests/auto/api/testdata/rc/subdir/rc-include.h new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/rc/test.rc b/tests/auto/api/testdata/rc/test.rc new file mode 100644 index 00000000..ff7a7200 --- /dev/null +++ b/tests/auto/api/testdata/rc/test.rc @@ -0,0 +1,24 @@ +#define IDR_VERSION1 1 + +#include + +IDR_VERSION1 VERSIONINFO +FILEVERSION 1,0,0,0 +PRODUCTVERSION 1,0,0,0 +FILEOS 0x00000004 +FILETYPE 0x00000000 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "FFFF0000" + BEGIN + VALUE "FileVersion", "1.0.0.0\0" + VALUE "ProductVersion", "1.0.0.0\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xFFFF, 0x0000 + END +END + diff --git a/tests/auto/api/testdata/recursive-wildcards/dir/file1.txt b/tests/auto/api/testdata/recursive-wildcards/dir/file1.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/recursive-wildcards/dir/subdir/file2.txt b/tests/auto/api/testdata/recursive-wildcards/dir/subdir/file2.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/recursive-wildcards/recursive-wildcards.qbs b/tests/auto/api/testdata/recursive-wildcards/recursive-wildcards.qbs new file mode 100644 index 00000000..df54577f --- /dev/null +++ b/tests/auto/api/testdata/recursive-wildcards/recursive-wildcards.qbs @@ -0,0 +1,8 @@ +Product { + qbs.installPrefix: "" + Group { + files: "dir/**" + qbs.install: true + qbs.installDir: "dir" + } +} diff --git a/tests/auto/api/testdata/referenced-file-errors/ambiguousdir/p1.qbs b/tests/auto/api/testdata/referenced-file-errors/ambiguousdir/p1.qbs new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/referenced-file-errors/ambiguousdir/p2.qbs b/tests/auto/api/testdata/referenced-file-errors/ambiguousdir/p2.qbs new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/referenced-file-errors/cycle.qbs b/tests/auto/api/testdata/referenced-file-errors/cycle.qbs new file mode 100644 index 00000000..ec1a9092 --- /dev/null +++ b/tests/auto/api/testdata/referenced-file-errors/cycle.qbs @@ -0,0 +1,5 @@ +Project { + property string productName: "p1" + Product { name: project.productName } + references: ["referenced-file-errors.qbs"] +} diff --git a/tests/auto/api/testdata/referenced-file-errors/emptydir/.gitignore b/tests/auto/api/testdata/referenced-file-errors/emptydir/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/referenced-file-errors/modules/brokenmodule/brokenmodule.qbs b/tests/auto/api/testdata/referenced-file-errors/modules/brokenmodule/brokenmodule.qbs new file mode 100644 index 00000000..b52f12cc --- /dev/null +++ b/tests/auto/api/testdata/referenced-file-errors/modules/brokenmodule/brokenmodule.qbs @@ -0,0 +1,3 @@ +Module { + syntax error +} diff --git a/tests/auto/api/testdata/referenced-file-errors/okay.qbs b/tests/auto/api/testdata/referenced-file-errors/okay.qbs new file mode 100644 index 00000000..4f88b399 --- /dev/null +++ b/tests/auto/api/testdata/referenced-file-errors/okay.qbs @@ -0,0 +1 @@ +Product { name: "p2" } diff --git a/tests/auto/api/testdata/referenced-file-errors/okay2.qbs b/tests/auto/api/testdata/referenced-file-errors/okay2.qbs new file mode 100644 index 00000000..0c5b2675 --- /dev/null +++ b/tests/auto/api/testdata/referenced-file-errors/okay2.qbs @@ -0,0 +1 @@ +Product { name: "p4" } diff --git a/tests/auto/api/testdata/referenced-file-errors/referenced-file-errors.qbs b/tests/auto/api/testdata/referenced-file-errors/referenced-file-errors.qbs new file mode 100644 index 00000000..eb33f145 --- /dev/null +++ b/tests/auto/api/testdata/referenced-file-errors/referenced-file-errors.qbs @@ -0,0 +1,30 @@ +Project { + references: [ + "ambiguousdir", + "cycle.qbs", + "emptydir", + "nosuchfile.qbs", + "okay.qbs", + "wrongtype.qbs", + ] + + SubProject { + filePath: "cycle.qbs" + Properties { + productName: "p3" + } + } + + SubProject { + filePath: "nosuchfile.qbs" + } + + SubProject { + filePath: "okay2.qbs" + } + + Product { + name: "p5" + Depends { name: "brokenmodule" } + } +} diff --git a/tests/auto/api/testdata/referenced-file-errors/wrongtype.qbs b/tests/auto/api/testdata/referenced-file-errors/wrongtype.qbs new file mode 100644 index 00000000..cd8c6471 --- /dev/null +++ b/tests/auto/api/testdata/referenced-file-errors/wrongtype.qbs @@ -0,0 +1 @@ +Module { } diff --git a/tests/auto/api/testdata/references/invalid1.qbs b/tests/auto/api/testdata/references/invalid1.qbs new file mode 100644 index 00000000..ec4095d8 --- /dev/null +++ b/tests/auto/api/testdata/references/invalid1.qbs @@ -0,0 +1,3 @@ +Project { + references: "subdir-with-no-project" +} \ No newline at end of file diff --git a/tests/auto/api/testdata/references/invalid2.qbs b/tests/auto/api/testdata/references/invalid2.qbs new file mode 100644 index 00000000..249c80b4 --- /dev/null +++ b/tests/auto/api/testdata/references/invalid2.qbs @@ -0,0 +1,3 @@ +Project { + references: "subdir-with-multiple-projects" +} \ No newline at end of file diff --git a/tests/auto/api/testdata/references/subdir-with-multiple-projects/subproject1.qbs b/tests/auto/api/testdata/references/subdir-with-multiple-projects/subproject1.qbs new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/references/subdir-with-multiple-projects/subproject2.qbs b/tests/auto/api/testdata/references/subdir-with-multiple-projects/subproject2.qbs new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/references/subdir-with-multiple-projects/subproject3.qbs b/tests/auto/api/testdata/references/subdir-with-multiple-projects/subproject3.qbs new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/references/subdir-with-no-project/test.txt b/tests/auto/api/testdata/references/subdir-with-no-project/test.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/references/subdir-with-one-project/p.qbs b/tests/auto/api/testdata/references/subdir-with-one-project/p.qbs new file mode 100644 index 00000000..ba86b0ab --- /dev/null +++ b/tests/auto/api/testdata/references/subdir-with-one-project/p.qbs @@ -0,0 +1 @@ +Project { } diff --git a/tests/auto/api/testdata/references/subdir-with-one-project/test.txt b/tests/auto/api/testdata/references/subdir-with-one-project/test.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/references/valid.qbs b/tests/auto/api/testdata/references/valid.qbs new file mode 100644 index 00000000..97ed7f56 --- /dev/null +++ b/tests/auto/api/testdata/references/valid.qbs @@ -0,0 +1,3 @@ +Project { + references: "subdir-with-one-project" +} diff --git a/tests/auto/api/testdata/relaxed-mode-recovery/relaxed-mode-recovery.qbs b/tests/auto/api/testdata/relaxed-mode-recovery/relaxed-mode-recovery.qbs new file mode 100644 index 00000000..7a066b69 --- /dev/null +++ b/tests/auto/api/testdata/relaxed-mode-recovery/relaxed-mode-recovery.qbs @@ -0,0 +1,8 @@ +Project { + Product { + name: "dep" + Export { Depends { name: "blubb" } } + } + Product { name: "p1"; Depends { name: "dep" } } + Product { name: "p2"; Depends { name: "dep" } } +} diff --git a/tests/auto/api/testdata/remove-file-dependency/main.cpp b/tests/auto/api/testdata/remove-file-dependency/main.cpp new file mode 100644 index 00000000..5c0b0393 --- /dev/null +++ b/tests/auto/api/testdata/remove-file-dependency/main.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 "someheader.h" +#include + +int main() +{ + printf("The magic value is %d.\n", magicValue()); + return 0; +} + diff --git a/tests/auto/api/testdata/remove-file-dependency/removeFileDependency.qbs b/tests/auto/api/testdata/remove-file-dependency/removeFileDependency.qbs new file mode 100644 index 00000000..27bdf96c --- /dev/null +++ b/tests/auto/api/testdata/remove-file-dependency/removeFileDependency.qbs @@ -0,0 +1,5 @@ +CppApplication { + files: ["main.cpp"] + // Do not reference header files here to force them to be FileDependency objects. +} + diff --git a/tests/auto/api/testdata/remove-file-dependency/someheader.h b/tests/auto/api/testdata/remove-file-dependency/someheader.h new file mode 100644 index 00000000..f0efc4e7 --- /dev/null +++ b/tests/auto/api/testdata/remove-file-dependency/someheader.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +inline int magicValue() { return 156; } diff --git a/tests/auto/api/testdata/rename-product/lib.cpp b/tests/auto/api/testdata/rename-product/lib.cpp new file mode 100644 index 00000000..9c7d00c5 --- /dev/null +++ b/tests/auto/api/testdata/rename-product/lib.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" + +MY_EXPORT void f() { } diff --git a/tests/auto/api/testdata/rename-product/main.cpp b/tests/auto/api/testdata/rename-product/main.cpp new file mode 100644 index 00000000..1351aad1 --- /dev/null +++ b/tests/auto/api/testdata/rename-product/main.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void f(); + +int main() +{ + f(); +} diff --git a/tests/auto/api/testdata/rename-product/rename.qbs b/tests/auto/api/testdata/rename-product/rename.qbs new file mode 100644 index 00000000..9d23bf80 --- /dev/null +++ b/tests/auto/api/testdata/rename-product/rename.qbs @@ -0,0 +1,18 @@ +Project { + CppApplication { + Depends { name: "TheLib" } + cpp.defines: "MY_EXPORT=" + files: "main.cpp" + } + + DynamicLibrary { + name: "TheLib" + Depends { name: "cpp" } + cpp.defines: "MY_EXPORT=DLL_EXPORT" + files: "lib.cpp" + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } +} diff --git a/tests/auto/api/testdata/rename-target-artifact/lib.cpp b/tests/auto/api/testdata/rename-target-artifact/lib.cpp new file mode 100644 index 00000000..9c7d00c5 --- /dev/null +++ b/tests/auto/api/testdata/rename-target-artifact/lib.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" + +MY_EXPORT void f() { } diff --git a/tests/auto/api/testdata/rename-target-artifact/main.cpp b/tests/auto/api/testdata/rename-target-artifact/main.cpp new file mode 100644 index 00000000..1351aad1 --- /dev/null +++ b/tests/auto/api/testdata/rename-target-artifact/main.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void f(); + +int main() +{ + f(); +} diff --git a/tests/auto/api/testdata/rename-target-artifact/rename.qbs b/tests/auto/api/testdata/rename-target-artifact/rename.qbs new file mode 100644 index 00000000..810b0eb0 --- /dev/null +++ b/tests/auto/api/testdata/rename-target-artifact/rename.qbs @@ -0,0 +1,21 @@ +Project { + CppApplication { + Depends { name: "TheLib" } + cpp.defines: "MY_EXPORT=" + qbs.buildVariant: "release" + files: "main.cpp" + } + + DynamicLibrary { + name: "TheLib" + targetName: "the_lib" + Depends { name: "cpp" } + cpp.defines: "MY_EXPORT=DLL_EXPORT" + qbs.buildVariant: "release" + files: "lib.cpp" + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } +} diff --git a/tests/auto/api/testdata/renamed-qbs-source-file/renamed-qbs-source-file.qbs b/tests/auto/api/testdata/renamed-qbs-source-file/renamed-qbs-source-file.qbs new file mode 100644 index 00000000..d295d43e --- /dev/null +++ b/tests/auto/api/testdata/renamed-qbs-source-file/renamed-qbs-source-file.qbs @@ -0,0 +1,9 @@ +Project { + references: "the-product/the-prodduct.qbs" + Product { + Group { + files: "the-product/*.qbs" + fileTags: [] + } + } +} diff --git a/tests/auto/api/testdata/renamed-qbs-source-file/the-product/the-prodduct.qbs b/tests/auto/api/testdata/renamed-qbs-source-file/the-product/the-prodduct.qbs new file mode 100644 index 00000000..86718b57 --- /dev/null +++ b/tests/auto/api/testdata/renamed-qbs-source-file/the-product/the-prodduct.qbs @@ -0,0 +1 @@ +Product { } diff --git a/tests/auto/api/testdata/restored-warnings/file.cpp b/tests/auto/api/testdata/restored-warnings/file.cpp new file mode 100644 index 00000000..56757a70 --- /dev/null +++ b/tests/auto/api/testdata/restored-warnings/file.cpp @@ -0,0 +1 @@ +void f() {} diff --git a/tests/auto/api/testdata/restored-warnings/main.cpp b/tests/auto/api/testdata/restored-warnings/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/api/testdata/restored-warnings/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/api/testdata/restored-warnings/restored-warnings.qbs b/tests/auto/api/testdata/restored-warnings/restored-warnings.qbs new file mode 100644 index 00000000..bbdfbead --- /dev/null +++ b/tests/auto/api/testdata/restored-warnings/restored-warnings.qbs @@ -0,0 +1,14 @@ +import qbs.Process 1.5 + +CppApplication { + name: "theProduct" + + property bool moreFiles: false + cpp.blubb: true + + files: ["file.cpp", "main.cpp"] + Group { + condition: moreFiles + files: ["blubb.cpp"] + } +} diff --git a/tests/auto/api/testdata/rule-conflict/main.cpp b/tests/auto/api/testdata/rule-conflict/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/api/testdata/rule-conflict/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/api/testdata/rule-conflict/pch1.h b/tests/auto/api/testdata/rule-conflict/pch1.h new file mode 100644 index 00000000..50841a22 --- /dev/null +++ b/tests/auto/api/testdata/rule-conflict/pch1.h @@ -0,0 +1,27 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ diff --git a/tests/auto/api/testdata/rule-conflict/pch2.h b/tests/auto/api/testdata/rule-conflict/pch2.h new file mode 100644 index 00000000..50841a22 --- /dev/null +++ b/tests/auto/api/testdata/rule-conflict/pch2.h @@ -0,0 +1,27 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ diff --git a/tests/auto/api/testdata/rule-conflict/rule-conflict.qbs b/tests/auto/api/testdata/rule-conflict/rule-conflict.qbs new file mode 100644 index 00000000..7bd462f1 --- /dev/null +++ b/tests/auto/api/testdata/rule-conflict/rule-conflict.qbs @@ -0,0 +1,8 @@ +CppApplication { + files: "main.cpp" + Group { + name: "pch files" + files: ["pch1.h", "pch2.h"] + fileTags: "cpp_pch_src" + } +} diff --git a/tests/auto/api/testdata/run-disabled-product/run-disabled-product.qbs b/tests/auto/api/testdata/run-disabled-product/run-disabled-product.qbs new file mode 100644 index 00000000..b187084d --- /dev/null +++ b/tests/auto/api/testdata/run-disabled-product/run-disabled-product.qbs @@ -0,0 +1,4 @@ +CppApplication { + name: "app" + condition: false +} diff --git a/tests/auto/api/testdata/same-base-name/lib.c b/tests/auto/api/testdata/same-base-name/lib.c new file mode 100644 index 00000000..1f889906 --- /dev/null +++ b/tests/auto/api/testdata/same-base-name/lib.c @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 + +extern void printHelloC() +{ + printf("Hello from C in " __FILE__ "\n"); +} diff --git a/tests/auto/api/testdata/same-base-name/lib.cpp b/tests/auto/api/testdata/same-base-name/lib.cpp new file mode 100644 index 00000000..e2d0e2e5 --- /dev/null +++ b/tests/auto/api/testdata/same-base-name/lib.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 + +extern "C" void printHelloCpp() +{ + std::cout << "Hello from C++ in " << __FILE__ << std::endl; +} diff --git a/tests/auto/api/testdata/same-base-name/lib.m b/tests/auto/api/testdata/same-base-name/lib.m new file mode 100644 index 00000000..17cd2dce --- /dev/null +++ b/tests/auto/api/testdata/same-base-name/lib.m @@ -0,0 +1,6 @@ +#import + +extern void printHelloObjc() +{ + NSLog(@"Hello from Objective-C in " __FILE__); +} diff --git a/tests/auto/api/testdata/same-base-name/lib.mm b/tests/auto/api/testdata/same-base-name/lib.mm new file mode 100644 index 00000000..ee284b08 --- /dev/null +++ b/tests/auto/api/testdata/same-base-name/lib.mm @@ -0,0 +1,8 @@ +#include +#import + +extern "C" void printHelloObjcpp() +{ + NSLog(@"Hello from Objective-C++..."); + std::cout << "...in " __FILE__ << std::endl; +} diff --git a/tests/auto/api/testdata/same-base-name/main.c b/tests/auto/api/testdata/same-base-name/main.c new file mode 100644 index 00000000..7f8cd22e --- /dev/null +++ b/tests/auto/api/testdata/same-base-name/main.c @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +extern void printHelloC(); +extern void printHelloCpp(); + +#ifdef __APPLE__ +extern void printHelloObjc(); +extern void printHelloObjcpp(); +#endif + +int main() +{ + printHelloC(); + printHelloCpp(); +#ifdef __APPLE__ + printHelloObjc(); + printHelloObjcpp(); +#endif + return 0; +} diff --git a/tests/auto/api/testdata/same-base-name/same-base-name.qbs b/tests/auto/api/testdata/same-base-name/same-base-name.qbs new file mode 100644 index 00000000..8448d04f --- /dev/null +++ b/tests/auto/api/testdata/same-base-name/same-base-name.qbs @@ -0,0 +1,31 @@ +Project { + CppApplication { + type: "application" + consoleApplication: true + Depends { name: "basenamelib" } + name: "basename" + files: "main.c" + } + + StaticLibrary { + Depends { name: "cpp" } + name: "basenamelib" + files: [ + "lib.c", + "lib.cpp" + ] + + Group { + condition: qbs.targetOS.contains("darwin") + files: [ + "lib.m", + "lib.mm" + ] + } + + Export { + Depends { name: "cpp" } + cpp.frameworks: qbs.targetOS.contains("darwin") ? "Foundation" : undefined + } + } +} diff --git a/tests/auto/api/testdata/simple-probe/main.cpp b/tests/auto/api/testdata/simple-probe/main.cpp new file mode 100644 index 00000000..210c8274 --- /dev/null +++ b/tests/auto/api/testdata/simple-probe/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { return 0; } diff --git a/tests/auto/api/testdata/simple-probe/simple-probe.qbs b/tests/auto/api/testdata/simple-probe/simple-probe.qbs new file mode 100644 index 00000000..8e76acfe --- /dev/null +++ b/tests/auto/api/testdata/simple-probe/simple-probe.qbs @@ -0,0 +1,31 @@ +import qbs.Probes + +CppApplication { + Probe { + id: probe1 + property string someString + configure: { + someString = "one"; + found = true; + } + } + Probe { + id: probe2 + configure: { + found = false; + } + } + type: ["application"] + name: "MyApp" + consoleApplication: { + if (!probe1.found) + throw "probe1 not found"; + if (probe2.found) + throw "probe2 unexpectedly found"; + if (probe1.someString !== "one") + throw "probe1.someString expected to be \"one\"." + return true + } + files: ["main.cpp"] +} + diff --git a/tests/auto/api/testdata/soft-dependency/main.cpp b/tests/auto/api/testdata/soft-dependency/main.cpp new file mode 100644 index 00000000..eb71ab61 --- /dev/null +++ b/tests/auto/api/testdata/soft-dependency/main.cpp @@ -0,0 +1,32 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() +{ + thisShouldNotLink(); +} diff --git a/tests/auto/api/testdata/soft-dependency/soft-dependency.qbs b/tests/auto/api/testdata/soft-dependency/soft-dependency.qbs new file mode 100644 index 00000000..42d2f2de --- /dev/null +++ b/tests/auto/api/testdata/soft-dependency/soft-dependency.qbs @@ -0,0 +1,10 @@ +CppApplication { + Depends { + name: "nosuchmodule" + required: false + } + Properties { + condition: nosuchmodule.present + files: "main.cpp" + } +} diff --git a/tests/auto/api/testdata/source-file-in-build-dir/file.cpp b/tests/auto/api/testdata/source-file-in-build-dir/file.cpp new file mode 100644 index 00000000..e14f806b --- /dev/null +++ b/tests/auto/api/testdata/source-file-in-build-dir/file.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { } diff --git a/tests/auto/api/testdata/source-file-in-build-dir/source-file-in-build-dir.qbs b/tests/auto/api/testdata/source-file-in-build-dir/source-file-in-build-dir.qbs new file mode 100644 index 00000000..85115fc7 --- /dev/null +++ b/tests/auto/api/testdata/source-file-in-build-dir/source-file-in-build-dir.qbs @@ -0,0 +1,27 @@ +import qbs.TextFile + +CppApplication { + name: "theProduct" + type: base.concat(["dummy"]) + Rule { + multiplex: true + Artifact { + filePath: "generated.cpp" + fileTags: ["dummy"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + }; + return [cmd]; + } + } + + Group { + name: "the group" + files: "**/*.cpp" + } +} diff --git a/tests/auto/api/testdata/static-lib-deps/a1.cpp b/tests/auto/api/testdata/static-lib-deps/a1.cpp new file mode 100644 index 00000000..460eb52e --- /dev/null +++ b/tests/auto/api/testdata/static-lib-deps/a1.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 + +void a1() +{ + std::cout << "a1" << std::endl; +} + diff --git a/tests/auto/api/testdata/static-lib-deps/a2.cpp b/tests/auto/api/testdata/static-lib-deps/a2.cpp new file mode 100644 index 00000000..4ab7404f --- /dev/null +++ b/tests/auto/api/testdata/static-lib-deps/a2.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 + +void a2() +{ + std::cout << "a2" << std::endl; +} + diff --git a/tests/auto/api/testdata/static-lib-deps/b.cpp b/tests/auto/api/testdata/static-lib-deps/b.cpp new file mode 100644 index 00000000..cec54420 --- /dev/null +++ b/tests/auto/api/testdata/static-lib-deps/b.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void a1(); + +void b() +{ + a1(); +} + diff --git a/tests/auto/api/testdata/static-lib-deps/c.cpp b/tests/auto/api/testdata/static-lib-deps/c.cpp new file mode 100644 index 00000000..0ca246c8 --- /dev/null +++ b/tests/auto/api/testdata/static-lib-deps/c.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void a2(); + +void c() +{ + a2(); +} + diff --git a/tests/auto/api/testdata/static-lib-deps/d.cpp b/tests/auto/api/testdata/static-lib-deps/d.cpp new file mode 100644 index 00000000..a7ecfd1e --- /dev/null +++ b/tests/auto/api/testdata/static-lib-deps/d.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifdef WITH_PTHREAD +#include +#elif defined(WITH_LEX_YACC) +extern "C" int yywrap(void); +extern "C" void yyerror(char const *s); +extern void printGreeting(); +#elif defined(WITH_SETUPAPI) +#include +#include +#endif + +void b(); +void c(); + +int d() +{ + b(); + c(); + +#ifdef WITH_PTHREAD + pthread_t self = pthread_self(); + return static_cast(self); +#elif defined(WITH_LEX_YACC) + yywrap(); + yyerror("no error"); + printGreeting(); + return 0; +#elif defined(WITH_SETUPAPI) + CABINET_INFO ci; + ci.SetId = 0; + SetupIterateCabinet(L"invalid-file-path", 0, NULL, NULL); + return ci.SetId; +#else + return 0; +#endif +} diff --git a/tests/auto/api/testdata/static-lib-deps/d.mm b/tests/auto/api/testdata/static-lib-deps/d.mm new file mode 100644 index 00000000..5bf48966 --- /dev/null +++ b/tests/auto/api/testdata/static-lib-deps/d.mm @@ -0,0 +1,8 @@ +#import + +void printGreeting() +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSLog (@"Hello darkness, my old friend!"); + [pool drain]; +} diff --git a/tests/auto/api/testdata/static-lib-deps/e.cpp b/tests/auto/api/testdata/static-lib-deps/e.cpp new file mode 100644 index 00000000..aea27921 --- /dev/null +++ b/tests/auto/api/testdata/static-lib-deps/e.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int d(); + +int e() +{ + return d(); +} + diff --git a/tests/auto/api/testdata/static-lib-deps/main.cpp b/tests/auto/api/testdata/static-lib-deps/main.cpp new file mode 100644 index 00000000..b42d35a2 --- /dev/null +++ b/tests/auto/api/testdata/static-lib-deps/main.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int e(); + +int main() +{ + return e(); +} + diff --git a/tests/auto/api/testdata/static-lib-deps/static-lib-deps.qbs b/tests/auto/api/testdata/static-lib-deps/static-lib-deps.qbs new file mode 100644 index 00000000..b584b4d4 --- /dev/null +++ b/tests/auto/api/testdata/static-lib-deps/static-lib-deps.qbs @@ -0,0 +1,95 @@ +Project { + StaticLibrary { + name: "a" + + Depends { name: "cpp" } + + files: [ + "a1.cpp", + "a2.cpp", + ] + } + StaticLibrary { + name: "b" + + Depends { name: "cpp" } + + Depends { name: "a" } + + files: [ + "b.cpp", + ] + } + StaticLibrary { + name: "c" + + Depends { name: "cpp" } + + Depends { name: "a" } + + files: [ + "c.cpp", + ] + } + StaticLibrary { + name: "d" + + Depends { name: "cpp" } + + Depends { name: "b" } + Depends { name: "c" } + + files: [ + "d.cpp", + ] + + Group { + condition: qbs.targetOS.contains("macos") + files: ["d.mm"] + } + + Properties { + condition: qbs.targetOS.contains("windows") + cpp.defines: ["WITH_SETUPAPI"] + cpp.staticLibraries: ["setupapi"] + } + Properties { + condition: qbs.targetOS.contains("macos") + cpp.defines: ["WITH_LEX_YACC"] + cpp.staticLibraries: ["l", "y"] + cpp.frameworks: ["Foundation"] + } + Properties { + condition: qbs.targetOS.contains("linux") + cpp.defines: ["WITH_PTHREAD"] + cpp.staticLibraries: ["pthread"] + } + } + StaticLibrary { + name: "e" + + Depends { name: "cpp" } + + Depends { name: "d" } + + files: [ + "e.cpp", + ] + } + CppApplication { + name: "staticLibDeps" + type: "application" + consoleApplication: true + + Depends { name: "e" } + + Properties { + condition: qbs.targetOS.contains("linux") + cpp.driverFlags: ["-static"] + } + + files: [ + "main.cpp", + ] + } +} diff --git a/tests/auto/api/testdata/subprojects/resources/imports/LibraryType/type.js b/tests/auto/api/testdata/subprojects/resources/imports/LibraryType/type.js new file mode 100644 index 00000000..cb07f8e5 --- /dev/null +++ b/tests/auto/api/testdata/subprojects/resources/imports/LibraryType/type.js @@ -0,0 +1 @@ +function type() { return "dynamiclibrary"; } diff --git a/tests/auto/api/testdata/subprojects/resources/modules/QtCoreDepender/qtcoredepender.qbs b/tests/auto/api/testdata/subprojects/resources/modules/QtCoreDepender/qtcoredepender.qbs new file mode 100644 index 00000000..906193ad --- /dev/null +++ b/tests/auto/api/testdata/subprojects/resources/modules/QtCoreDepender/qtcoredepender.qbs @@ -0,0 +1,3 @@ +Module { + Depends { name: "cute.core" } +} diff --git a/tests/auto/api/testdata/subprojects/resources/modules/cute/core/core.qbs b/tests/auto/api/testdata/subprojects/resources/modules/cute/core/core.qbs new file mode 100644 index 00000000..ae9b4721 --- /dev/null +++ b/tests/auto/api/testdata/subprojects/resources/modules/cute/core/core.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "cpp" } + cpp.includePaths: [path] +} diff --git a/tests/auto/api/testdata/subprojects/resources/modules/cute/core/cuteglobal.h b/tests/auto/api/testdata/subprojects/resources/modules/cute/core/cuteglobal.h new file mode 100644 index 00000000..3a0d4a81 --- /dev/null +++ b/tests/auto/api/testdata/subprojects/resources/modules/cute/core/cuteglobal.h @@ -0,0 +1 @@ +#define Q_DECL_EXPORT DLL_EXPORT diff --git a/tests/auto/api/testdata/subprojects/subproject1/main.cpp b/tests/auto/api/testdata/subprojects/subproject1/main.cpp new file mode 100644 index 00000000..1351aad1 --- /dev/null +++ b/tests/auto/api/testdata/subprojects/subproject1/main.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void f(); + +int main() +{ + f(); +} diff --git a/tests/auto/api/testdata/subprojects/subproject2/subproject2.qbs b/tests/auto/api/testdata/subprojects/subproject2/subproject2.qbs new file mode 100644 index 00000000..b0abb38b --- /dev/null +++ b/tests/auto/api/testdata/subprojects/subproject2/subproject2.qbs @@ -0,0 +1,13 @@ +Project { + name: "subproject2" + property string libNamePrefix: "test" + SubProject { + filePath: "subproject3/subproject3.qbs" + inheritProperties: true + Properties { + name: "overridden name" + condition: qbs.targetOS.length > 0 + libNameSuffix: "Lib" + } + } +} diff --git a/tests/auto/api/testdata/subprojects/subproject2/subproject3/subproject3.qbs b/tests/auto/api/testdata/subprojects/subproject2/subproject3/subproject3.qbs new file mode 100644 index 00000000..f2c63794 --- /dev/null +++ b/tests/auto/api/testdata/subprojects/subproject2/subproject3/subproject3.qbs @@ -0,0 +1,15 @@ +import LibraryType + +Project { + condition: false + property string libNameSuffix: "blubb" + Product { + name: project.libNamePrefix + project.libNameSuffix + type: LibraryType.type() + Depends { name: "cpp" } + Depends { name: "QtCoreDepender" } + cpp.defines: "MY_EXPORT=Q_DECL_EXPORT" + files: "testlib.cpp" + Export { Depends { name: "cute.core" } } + } +} diff --git a/tests/auto/api/testdata/subprojects/subproject2/subproject3/testlib.cpp b/tests/auto/api/testdata/subprojects/subproject2/subproject3/testlib.cpp new file mode 100644 index 00000000..6d3db70a --- /dev/null +++ b/tests/auto/api/testdata/subprojects/subproject2/subproject3/testlib.cpp @@ -0,0 +1,32 @@ +/**************************************************************************** +** +** 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 +#include "../../../dllexport.h" + +MY_EXPORT void f() {} diff --git a/tests/auto/api/testdata/subprojects/toplevelproject.qbs b/tests/auto/api/testdata/subprojects/toplevelproject.qbs new file mode 100644 index 00000000..402e4050 --- /dev/null +++ b/tests/auto/api/testdata/subprojects/toplevelproject.qbs @@ -0,0 +1,17 @@ +Project { + name: "top level project" + references: ["subproject2"] + + Project { + condition: true + name: "app-project" + CppApplication { + name: "app" + Depends { name: "testLib" } + cpp.defines: "MY_EXPORT=" + files: "subproject1/main.cpp" + } + } + + qbsSearchPaths: ["resources"] +} diff --git a/tests/auto/api/testdata/target-artifact-status/target-artifact-status.qbs b/tests/auto/api/testdata/target-artifact-status/target-artifact-status.qbs new file mode 100644 index 00000000..2f577661 --- /dev/null +++ b/tests/auto/api/testdata/target-artifact-status/target-artifact-status.qbs @@ -0,0 +1,28 @@ +import qbs.TextFile + +Product { + name: "p" + type: "p_type" + property bool enableTagging + Rule { + multiplex: true + Artifact { filePath: "a1"; fileTags: "p_type" } + Artifact { filePath: "a2"; fileTags: "x" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating outputs"; + cmd.sourceCode = function() { + var f = new TextFile(outputs.p_type[0].filePath, TextFile.WriteOnly); + f.close(); + f = new TextFile(outputs.x[0].filePath, TextFile.WriteOnly); + f.close(); + }; + return cmd; + } + } + Group { + condition: enableTagging + fileTagsFilter: "x" + fileTags: "p_type" + } +} diff --git a/tests/auto/api/testdata/timeout-js/timeout.qbs b/tests/auto/api/testdata/timeout-js/timeout.qbs new file mode 100644 index 00000000..26aa4ce8 --- /dev/null +++ b/tests/auto/api/testdata/timeout-js/timeout.qbs @@ -0,0 +1,20 @@ +Product { + type: "product-under-test" + Rule { + multiplex: true + Artifact { + filePath: "output.txt" + fileTags: "product-under-test" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Running infinite loop"; + cmd.sourceCode = function() { + while (true) + ; + } + cmd.timeout = 3; + return cmd; + } + } +} diff --git a/tests/auto/api/testdata/timeout-process/main.cpp b/tests/auto/api/testdata/timeout-process/main.cpp new file mode 100644 index 00000000..f9b9336b --- /dev/null +++ b/tests/auto/api/testdata/timeout-process/main.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Jochen Ulrich +** 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 +#include + +int main() +{ + std::this_thread::sleep_for(std::chrono::seconds(700)); + return 0; +} + diff --git a/tests/auto/api/testdata/timeout-process/timeout.qbs b/tests/auto/api/testdata/timeout-process/timeout.qbs new file mode 100644 index 00000000..30f39094 --- /dev/null +++ b/tests/auto/api/testdata/timeout-process/timeout.qbs @@ -0,0 +1,40 @@ +Project { + CppApplication { + type: "application" + consoleApplication: true // suppress bundle generation + files: "main.cpp" + name: "infinite-loop" + cpp.cxxLanguageVersion: "c++11" + cpp.minimumOsxVersion: "10.8" // For + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.driverFlags: "-pthread" + } + } + + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + type: "product-under-test" + name: "caller" + Depends { name: "infinite-loop" } + Depends { + name: "cpp" // Make sure build environment is set up properly. + condition: qbs.hostOS.contains("windows") && qbs.toolchain.contains("gcc") + } + Rule { + inputsFromDependencies: "application" + outputFileTags: "product-under-test" + prepare: { + var cmd = new Command(inputs["application"][0].filePath); + cmd.description = "Calling application that runs forever"; + cmd.timeout = 3; + return cmd; + } + } + } +} diff --git a/tests/auto/api/testdata/tool-in-module/use-outside-project/modules/thetool/thetool.qbs b/tests/auto/api/testdata/tool-in-module/use-outside-project/modules/thetool/thetool.qbs new file mode 100644 index 00000000..d72ebda1 --- /dev/null +++ b/tests/auto/api/testdata/tool-in-module/use-outside-project/modules/thetool/thetool.qbs @@ -0,0 +1,27 @@ +import qbs.FileInfo + +Module { + Depends { name: "cpp" } + Group { + name: "thetool binary" + files: FileInfo.cleanPath(FileInfo.joinPaths(path, "..", "..", + "thetool" + (qbs.hostOS.contains("windows") ? ".exe" : ""))); + fileTags: ["thetool.thetool"] + filesAreTargets: true + } + + Rule { + multiplex: true + explicitlyDependsOnFromDependencies: ["thetool.thetool"] + Artifact { + filePath: "tool-output.txt" + fileTags: ["thetool.output"] + } + prepare: { + var cmd = new Command(explicitlyDependsOn["thetool.thetool"][0].filePath, + output.filePath); + cmd.description = "running the tool"; + return [cmd]; + } + } +} diff --git a/tests/auto/api/testdata/tool-in-module/use-outside-project/use-outside-project.qbs b/tests/auto/api/testdata/tool-in-module/use-outside-project/use-outside-project.qbs new file mode 100644 index 00000000..81db730d --- /dev/null +++ b/tests/auto/api/testdata/tool-in-module/use-outside-project/use-outside-project.qbs @@ -0,0 +1,5 @@ +Product { + name: "user-outside-project" + type: ["thetool.output"] + Depends { name: "thetool" } +} diff --git a/tests/auto/api/testdata/tool-in-module/use-within-project/main.cpp b/tests/auto/api/testdata/tool-in-module/use-within-project/main.cpp new file mode 100644 index 00000000..723b8a3b --- /dev/null +++ b/tests/auto/api/testdata/tool-in-module/use-within-project/main.cpp @@ -0,0 +1,10 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + assert(argc == 2); + std::ofstream file(argv[1]); + assert(file.is_open()); + file << "content"; +} diff --git a/tests/auto/api/testdata/tool-in-module/use-within-project/tool-input.txt b/tests/auto/api/testdata/tool-in-module/use-within-project/tool-input.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/tool-in-module/use-within-project/use-within-project.qbs b/tests/auto/api/testdata/tool-in-module/use-within-project/use-within-project.qbs new file mode 100644 index 00000000..d0f7c35e --- /dev/null +++ b/tests/auto/api/testdata/tool-in-module/use-within-project/use-within-project.qbs @@ -0,0 +1,46 @@ +Project { + CppApplication { + name: "thetool" + consoleApplication: true + files: "main.cpp" + + property bool skip: { + var result = qbs.targetPlatform !== qbs.hostPlatform; + if (result) + console.info("Skip this test"); + return result; + } + + install: true + installDir: "" + qbs.installPrefix: "" + Group { + fileTagsFilter: ["application"] + fileTags: ["thetool.thetool"] + } + + Export { + Depends { name: "cpp" } + Rule { + multiplex: true + explicitlyDependsOnFromDependencies: ["thetool.thetool"] + Artifact { + filePath: "tool-output.txt" + fileTags: ["thetool.output"] + } + prepare: { + var cmd = new Command(explicitlyDependsOn["thetool.thetool"][0].filePath, + output.filePath); + cmd.description = "running the tool"; + return [cmd]; + } + } + } + } + + Product { + name: "user-in-project" + type: ["thetool.output"] + Depends { name: "thetool" } + } +} diff --git a/tests/auto/api/testdata/transformer-data/transformer-data.qbs b/tests/auto/api/testdata/transformer-data/transformer-data.qbs new file mode 100644 index 00000000..f9433ed7 --- /dev/null +++ b/tests/auto/api/testdata/transformer-data/transformer-data.qbs @@ -0,0 +1,35 @@ +import qbs.File +import qbs.TextFile + +Product { + type: ["theType"] + Rule { + multiplex: true + Artifact { + filePath: "artifact1" + fileTags: ["type1"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + }; + return [cmd]; + } + } + Rule { + inputs: ["type1"] + Artifact { + filePath: "artifact2" + fileTags: ["theType"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating " + output.fileName; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); }; + return [cmd]; + } + } +} diff --git a/tests/auto/api/testdata/transformers/main.cpp b/tests/auto/api/testdata/transformers/main.cpp new file mode 100644 index 00000000..03650959 --- /dev/null +++ b/tests/auto/api/testdata/transformers/main.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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 +#include +#include + +using namespace std; + +bool displayTextFile(const string &dirPath, const string &fileName) +{ + string fullPath = dirPath + fileName; + ifstream istream(fullPath.c_str()); + if (!istream.is_open()) { + cout << "Cannot open " << fileName << endl; + return false; + } + cout << "---" << fileName << "---" << endl; + char buf[256]; + unsigned int i = 1; + while (istream.good()) { + istream.getline(buf, sizeof(buf)); + cout << i++ << ": " << buf << endl; + } + return true; +} + +int main(int, char **argv) +{ + string appPath(argv[0]); + size_t i = appPath.find_last_of('/'); + if (i == string::npos) + i = appPath.find_last_of('\\'); + if (i == string::npos) // No path, plain executable was called + appPath.clear(); + else + appPath.resize(i + 1); + if (!displayTextFile(appPath, "foo.txt")) + return 1; + if (!displayTextFile(appPath, "bar.txt")) + return 2; + cout << "-------------" << endl; + return 0; +} + diff --git a/tests/auto/api/testdata/transformers/transformers.qbs b/tests/auto/api/testdata/transformers/transformers.qbs new file mode 100644 index 00000000..bbc5c3c6 --- /dev/null +++ b/tests/auto/api/testdata/transformers/transformers.qbs @@ -0,0 +1,92 @@ +import qbs.File +import qbs.TextFile +import qbs.Xml +import qbs.FileInfo + +Project { + Product { + name: "HelloWorld" + type: "application" + consoleApplication: true + + Group { + files: ["main.cpp"] + fileTags: ["main"] + } + + Depends { name: "cpp" } + + Rule { + // no inputs -> just a generator + multiplex: true + Artifact { + filePath: "foo.txt" + fileTags: "text" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating foo.txt"; + cmd.highlight = "linker"; + cmd.sourceCode = function () { + File.remove(output.filePath); + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.write("Dear Sir/Madam,\n\n"); + f.write("this is a generated file.\n\n\n"); + f.write("Best Regards and Mellow Greetings,\nYour Build Tool.\n"); + f.close(); + } + return cmd; + } + } + + Rule { + multiplex: true + // no inputs -> just a generator + Artifact { + filePath: "foo.xml" + fileTags: "xml" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating foo.xml"; + cmd.highlight = "linker"; + cmd.sourceCode = function () { + File.remove(output.filePath); + var doc = new Xml.DomDocument(); + var root = doc.createElement("root"); + doc.appendChild(root); + + var tag = doc.createElement("Greeting"); + root.appendChild(tag); + tag.appendChild(doc.createTextNode("text node")); + doc.save(output.filePath); + } + return cmd; + } + } + + Rule { + inputs: ["main"] + Artifact { + filePath: "bar.txt" + fileTags: "text" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating bar.txt"; + cmd.highlight = "linker"; + cmd.inputFileName = input.filePath; + cmd.sourceCode = function() { + File.remove(output.filePath); + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.write("Dear Sir/Madam,\n\n"); + f.write("this file was generated from " + inputFileName + ".\n\n\n"); + f.write("Best Regards and Mellow Greetings,\nYour Build Tool.\n"); + f.close(); + } + return cmd; + } + } + } +} + diff --git a/tests/auto/api/testdata/two-default-property-values/modules/mymodule/mymodule.qbs b/tests/auto/api/testdata/two-default-property-values/modules/mymodule/mymodule.qbs new file mode 100644 index 00000000..8ac7b75a --- /dev/null +++ b/tests/auto/api/testdata/two-default-property-values/modules/mymodule/mymodule.qbs @@ -0,0 +1,23 @@ +import qbs.TextFile + +Module { + property string direct + property string indirect: direct ? "set" : "unset" + + Rule { + inputs: ["txt"] + Artifact { + filePath: product.moduleProperty("mymodule", "indirect") + fileTags: ["mymodule"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + }; + return [cmd]; + } + } +} diff --git a/tests/auto/api/testdata/two-default-property-values/modules/myothermodule/myothermodule.qbs b/tests/auto/api/testdata/two-default-property-values/modules/myothermodule/myothermodule.qbs new file mode 100644 index 00000000..12df4434 --- /dev/null +++ b/tests/auto/api/testdata/two-default-property-values/modules/myothermodule/myothermodule.qbs @@ -0,0 +1,3 @@ +Module { + Depends { name: "mymodule" } +} diff --git a/tests/auto/api/testdata/two-default-property-values/test.txt b/tests/auto/api/testdata/two-default-property-values/test.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/api/testdata/two-default-property-values/two-default-property-values.qbs b/tests/auto/api/testdata/two-default-property-values/two-default-property-values.qbs new file mode 100644 index 00000000..5e3e0f5a --- /dev/null +++ b/tests/auto/api/testdata/two-default-property-values/two-default-property-values.qbs @@ -0,0 +1,11 @@ +Product { + name: "two-default-property-values" + type: "mymodule" + Depends { name: "mymodule" } + Depends { name: "myothermodule" } + mymodule.direct: "dummy" + Group { + files: ["test.txt"] + fileTags: ["txt"] + } +} diff --git a/tests/auto/api/testdata/type-change/main.cpp b/tests/auto/api/testdata/type-change/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/api/testdata/type-change/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/api/testdata/type-change/type-change.qbs b/tests/auto/api/testdata/type-change/type-change.qbs new file mode 100644 index 00000000..e8154b83 --- /dev/null +++ b/tests/auto/api/testdata/type-change/type-change.qbs @@ -0,0 +1,5 @@ +Product { + files: "main.cpp" + Depends { name: "cpp" } + // type: "application" +} diff --git a/tests/auto/api/testdata/uic/bla.cpp b/tests/auto/api/testdata/uic/bla.cpp new file mode 100644 index 00000000..eac879fa --- /dev/null +++ b/tests/auto/api/testdata/uic/bla.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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 "bla.h" + +int main() +{ + Ui::MainWindow mainWindow; + Q_UNUSED(mainWindow); +} + diff --git a/tests/auto/api/testdata/uic/bla.h b/tests/auto/api/testdata/uic/bla.h new file mode 100644 index 00000000..50c1bc15 --- /dev/null +++ b/tests/auto/api/testdata/uic/bla.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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 "ui.h" diff --git a/tests/auto/api/testdata/uic/ui.h b/tests/auto/api/testdata/uic/ui.h new file mode 100644 index 00000000..9bbd5814 --- /dev/null +++ b/tests/auto/api/testdata/uic/ui.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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 "ui_ui.h" diff --git a/tests/auto/api/testdata/uic/ui.ui b/tests/auto/api/testdata/uic/ui.ui new file mode 100644 index 00000000..b07f62d0 --- /dev/null +++ b/tests/auto/api/testdata/uic/ui.ui @@ -0,0 +1,31 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + 0 + 0 + 800 + 25 + + + + + + + + diff --git a/tests/auto/api/testdata/uic/uic.qbs b/tests/auto/api/testdata/uic/uic.qbs new file mode 100644 index 00000000..50260d73 --- /dev/null +++ b/tests/auto/api/testdata/uic/uic.qbs @@ -0,0 +1,14 @@ +Project { + QtGuiApplication { + type: "application" + consoleApplication: true + name: "ui" + + files: [ + "bla.cpp", + "bla.h", + "ui.ui", + "ui.h" + ] + } +} diff --git a/tests/auto/api/tst_api.cpp b/tests/auto/api/tst_api.cpp new file mode 100644 index 00000000..9e0e9ee5 --- /dev/null +++ b/tests/auto/api/tst_api.cpp @@ -0,0 +1,3130 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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_api.h" + +#include "../shared.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#define VERIFY_NO_ERROR(errorInfo) \ + QVERIFY2(!errorInfo.hasError(), qPrintable(errorInfo.toString())) + +#define WAIT_FOR_NEW_TIMESTAMP() waitForNewTimestamp(m_workingDataDir) + +class LogSink: public qbs::ILogSink +{ +public: + QString output; + + void doPrintWarning(const qbs::ErrorInfo &error) override { + qDebug("%s", qPrintable(error.toString())); + warnings.push_back(error); + } + void doPrintMessage(qbs::LoggerLevel, const QString &message, const QString &) override { + output += message; + } + + QList warnings; +}; + +class BuildDescriptionReceiver : public QObject +{ + Q_OBJECT +public: + QString descriptions; + QStringList descriptionLines; + + void handleDescription(const QString &, const QString &description) { + descriptions += description; + descriptionLines << description; + } +}; + +class ProcessResultReceiver : public QObject +{ + Q_OBJECT +public: + QString output; + std::vector results; + + void handleProcessResult(const qbs::ProcessResult &result) { + results.push_back(result); + output += result.stdErr().join(QLatin1Char('\n')); + output += result.stdOut().join(QLatin1Char('\n')); + } +}; + +class TaskReceiver : public QObject +{ + Q_OBJECT +public: + QString taskDescriptions; + + void handleTaskStart(const QString &task) { taskDescriptions += task; } +}; + + +static void removeBuildDir(const qbs::SetupProjectParameters ¶ms) +{ + QString message; + const QString dir = params.buildRoot() + '/' + params.configurationName(); + if (!qbs::Internal::removeDirectoryWithContents(dir, &message)) + qFatal("Could not remove build dir: %s", qPrintable(message)); +} + +static bool waitForFinished(qbs::AbstractJob *job, int timeout = 0) +{ + if (job->state() == qbs::AbstractJob::StateFinished) + return true; + QEventLoop loop; + QObject::connect(job, &qbs::AbstractJob::finished, &loop, &QEventLoop::quit); + if (timeout > 0) { + QTimer timer; + QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + timer.setSingleShot(true); + timer.start(timeout); + loop.exec(); + return timer.isActive(); // Timer ended the loop <=> job did not finish. + } + loop.exec(); + return true; +} + + +TestApi::TestApi() + : m_logSink(new LogSink) + , m_sourceDataDir(testDataSourceDir(SRCDIR "/testdata")) + , m_workingDataDir(testWorkDir(QStringLiteral("api"))) +{ +} + +TestApi::~TestApi() +{ + delete m_logSink; +} + +void TestApi::initTestCase() +{ + QString errorMessage; + qbs::Internal::removeDirectoryWithContents(m_workingDataDir, &errorMessage); + QVERIFY2(qbs::Internal::copyFileRecursion(m_sourceDataDir, + m_workingDataDir, false, true, &errorMessage), + qPrintable(errorMessage)); + QVERIFY(copyDllExportHeader(m_sourceDataDir, m_workingDataDir)); +} + +void TestApi::init() +{ + m_logSink->warnings.clear(); + m_logSink->setLogLevel(qbs::LoggerInfo); +} + +void TestApi::addQObjectMacroToCppFile() +{ + BuildDescriptionReceiver receiver; + qbs::ErrorInfo errorInfo = doBuildProject("add-qobject-macro-to-cpp-file", &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY2(!receiver.descriptions.contains("moc"), qPrintable(receiver.descriptions)); + receiver.descriptions.clear(); + + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("object.cpp", "// ", ""); + errorInfo = doBuildProject("add-qobject-macro-to-cpp-file", &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY2(receiver.descriptions.contains("moc"), qPrintable(receiver.descriptions)); +} + +static bool isAboutUndefinedSymbols(const QString &_message) +{ + const QString message = _message.toLower(); + return message.contains("undefined") || message.contains("unresolved"); +} + +void TestApi::addedFilePersistent() +{ + // On the initial run, linking will fail. + const QString relProjectFilePath = "added-file-persistent"; + ProcessResultReceiver receiver; + qbs::ErrorInfo errorInfo = doBuildProject(relProjectFilePath, 0, &receiver); + QVERIFY(errorInfo.hasError()); + QVERIFY2(isAboutUndefinedSymbols(receiver.output), qPrintable((receiver.output))); + receiver.output.clear(); + + // Add a file. qbs must schedule it for rule application on the next build. + WAIT_FOR_NEW_TIMESTAMP(); + const qbs::SetupProjectParameters params = defaultSetupParameters(relProjectFilePath); + REPLACE_IN_FILE(params.projectFilePath(), "/* 'file.cpp' */", "'file.cpp'"); + std::unique_ptr setupJob(qbs::Project().setupProject(params, m_logSink, + 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + setupJob.reset(nullptr); + + // Remove the file again. qbs must unschedule the rule application again. + // Consequently, the linking step must fail as in the initial run. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(params.projectFilePath(), "'file.cpp'", "/* 'file.cpp' */"); + errorInfo = doBuildProject(relProjectFilePath, 0, &receiver); + QVERIFY(errorInfo.hasError()); + QVERIFY2(isAboutUndefinedSymbols(receiver.output), qPrintable((receiver.output))); + + // Add the file again. qbs must schedule it for rule application on the next build. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(params.projectFilePath(), "/* 'file.cpp' */", "'file.cpp'"); + setupJob.reset(qbs::Project().setupProject(params, m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + setupJob.reset(nullptr); + + // qbs must remember that a file was scheduled for rule application. The build must then + // succeed, as now all necessary symbols are linked in. + errorInfo = doBuildProject(relProjectFilePath); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::baseProperties() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("base-properties/prj.qbs"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::buildGraphInfo() +{ + SettingsPtr s = settings(); + qbs::Internal::TemporaryProfile p("bgInfoProfile", s.get()); + p.p.setValue("qbs.targetPlatform", "xenix"); + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("buildgraph-info"); + setupParams.setTopLevelProfile(p.p.name()); + setupParams.setOverriddenValues({std::make_pair("qbs.architecture", "arm")}); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + const QString bgFilePath = setupParams.buildRoot() + QLatin1Char('/') + + relativeBuildGraphFilePath(); + QVERIFY2(QFileInfo::exists(bgFilePath), qPrintable(bgFilePath)); + qbs::Project::BuildGraphInfo bgInfo + = qbs::Project::getBuildGraphInfo(bgFilePath, QStringList()); + QVERIFY(bgInfo.error.hasError()); // Build graph is still locked. + setupJob.reset(nullptr); + const QStringList requestedProperties({"qbs.architecture", "qbs.shellPath", + "qbs.targetPlatform"}); + bgInfo = qbs::Project::getBuildGraphInfo(bgFilePath, requestedProperties); + QVERIFY2(!bgInfo.error.hasError(), qPrintable(bgInfo.error.toString())); + QCOMPARE(bgFilePath, bgInfo.bgFilePath); + QCOMPARE(bgInfo.profileData.size(), 1); + QCOMPARE(bgInfo.profileData.value(p.p.name()).toMap().size(), 1); + QCOMPARE(bgInfo.profileData.value(p.p.name()).toMap().value("qbs").toMap().value( + "targetPlatform"), + p.p.value("qbs.targetPlatform")); + QCOMPARE(bgInfo.overriddenProperties, setupParams.overriddenValues()); + QCOMPARE(bgInfo.requestedProperties.size(), requestedProperties.size()); + QCOMPARE(bgInfo.requestedProperties.value("qbs.architecture").toString(), QString("arm")); + QCOMPARE(bgInfo.requestedProperties.value("qbs.shellPath").toString(), QString("/bin/bash")); + QCOMPARE(bgInfo.requestedProperties.value("qbs.targetPlatform").toString(), QString("xenix")); +} + +void TestApi::buildErrorCodeLocation() +{ + const qbs::ErrorInfo errorInfo + = doBuildProject("build-error-code-location/build-error-code-location.qbs"); + QVERIFY(errorInfo.hasError()); + const qbs::ErrorItem errorItem = errorInfo.items().front(); + QCOMPARE(errorItem.description(), + QString("Rule.outputArtifacts must return an array of objects.")); + const qbs::CodeLocation errorLoc = errorItem.codeLocation(); + QCOMPARE(QFileInfo(errorLoc.filePath()).fileName(), QString("build-error-code-location.qbs")); + QCOMPARE(errorLoc.line(), 7); + QCOMPARE(errorLoc.column(), 26); +} + +void TestApi::buildGraphLocking() +{ + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("buildgraph-locking"); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + const qbs::Project project = setupJob->project(); + Q_UNUSED(project); + + // Case 1: Setting up a competing project from scratch. + setupJob.reset(qbs::Project().setupProject(setupParams, m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY(setupJob->error().hasError()); + QVERIFY2(setupJob->error().toString().contains("lock"), + qPrintable(setupJob->error().toString())); + + // Case 2: Setting up a non-competing project and then making it competing. + qbs::SetupProjectParameters setupParams2 = setupParams; + setupParams2.setBuildRoot(setupParams.buildRoot() + "/2"); + setupJob.reset(qbs::Project().setupProject(setupParams2, m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + const QString buildDirName = relativeBuildDir(setupParams2.configurationName()); + const QString lockFile = setupParams2.buildRoot() + '/' + buildDirName + '/' + buildDirName + + ".bg.lock"; + QVERIFY2(QFileInfo(lockFile).isFile(), qPrintable(lockFile)); + qbs::Project project2 = setupJob->project(); + QVERIFY(project2.isValid()); + setupJob.reset(project2.setupProject(setupParams, m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY(setupJob->error().hasError()); + QVERIFY2(setupJob->error().toString().contains("lock"), + qPrintable(setupJob->error().toString())); + QVERIFY2(QFileInfo(lockFile).isFile(), qPrintable(lockFile)); + + // Case 3: Changing the build directory of an existing project to something non-competing. + qbs::SetupProjectParameters setupParams3 = setupParams2; + setupParams3.setBuildRoot(setupParams.buildRoot() + "/3"); + setupJob.reset(qbs::Project().setupProject(setupParams3, m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + project2 = qbs::Project(); + QVERIFY2(!QFileInfo(lockFile).exists(), qPrintable(lockFile)); + const QString newLockFile = setupParams3.buildRoot() + '/' + buildDirName + '/' + + buildDirName + ".bg.lock"; + QVERIFY2(QFileInfo(newLockFile).isFile(), qPrintable(newLockFile)); + qbs::Project project3 = setupJob->project(); + QVERIFY(project3.isValid()); + + // Case 4: Changing the build directory again, but cancelling the job. + setupJob.reset(project3.setupProject(setupParams2, m_logSink, 0)); + QThread::sleep(1); + setupJob->cancel(); + waitForFinished(setupJob.get()); + QVERIFY(setupJob->error().hasError()); + QVERIFY2(!QFileInfo(lockFile).exists(), qPrintable(lockFile)); + QVERIFY2(QFileInfo(newLockFile).isFile(), qPrintable(newLockFile)); + setupJob.reset(nullptr); + project3 = qbs::Project(); + QVERIFY2(!QFileInfo(newLockFile).exists(), qPrintable(newLockFile)); +} + +void TestApi::buildProject() +{ + QFETCH(QString, projectSubDir); + QFETCH(QString, productFileName); + const QString projectFilePath = projectSubDir + QLatin1Char('/') + projectSubDir + + QLatin1String(".qbs"); + qbs::SetupProjectParameters params = defaultSetupParameters(projectFilePath); + removeBuildDir(params); + qbs::ErrorInfo errorInfo = doBuildProject(projectFilePath); + VERIFY_NO_ERROR(errorInfo); + QVERIFY(regularFileExists(relativeBuildGraphFilePath())); + if (!productFileName.isEmpty()) { + QVERIFY2(regularFileExists(productFileName), qPrintable(productFileName)); + QVERIFY2(QFile::remove(productFileName), qPrintable(productFileName)); + } + + WAIT_FOR_NEW_TIMESTAMP(); + qbs::BuildOptions options; + options.setForceTimestampCheck(true); + errorInfo = doBuildProject(projectFilePath, 0, 0, 0, options); + VERIFY_NO_ERROR(errorInfo); + if (!productFileName.isEmpty()) + QVERIFY2(regularFileExists(productFileName), qPrintable(productFileName)); + QVERIFY(regularFileExists(relativeBuildGraphFilePath())); +} + +void TestApi::buildProject_data() +{ + QTest::addColumn("projectSubDir"); + QTest::addColumn("productFileName"); + QTest::newRow("BPs in Sources") + << QString("build-properties-source") + << relativeExecutableFilePath("HelloWorld"); + QTest::newRow("code generator") + << QString("codegen") + << relativeExecutableFilePath("codegen"); + QTest::newRow("link static libs") + << QString("link-static-lib") + << relativeExecutableFilePath("HelloWorld"); + QTest::newRow("link staticlib dynamiclib") + << QString("link-staticlib-dynamiclib") + << relativeExecutableFilePath("app"); + QTest::newRow("precompiled header new") + << QString("precompiled-header-new") + << relativeExecutableFilePath("MyApp"); + QTest::newRow("precompiled header dynamic") + << QString("precompiled-header-dynamic") + << relativeExecutableFilePath("MyApp"); + QTest::newRow("lots of dots") + << QString("lots-of-dots") + << relativeExecutableFilePath("lots.of.dots"); + QTest::newRow("Qt5 plugin") + << QString("qt5-plugin") + << relativeProductBuildDir("echoplugin") + '/' + + qbs::Internal::HostOsInfo::dynamicLibraryName("echoplugin"); + QTest::newRow("Q_OBJECT in source") + << QString("moc-cpp") + << relativeExecutableFilePath("moc_cpp"); + QTest::newRow("Q_OBJECT in header") + << QString("moc-hpp") + << relativeExecutableFilePath("moc_hpp"); + QTest::newRow("Q_OBJECT in header, moc_XXX.cpp included") + << QString("moc-hpp-included") + << relativeExecutableFilePath("moc_hpp_included"); + QTest::newRow("app and lib with same source file") + << QString("lib-same-source") + << relativeExecutableFilePath("HelloWorldApp"); + QTest::newRow("source files with the same base name but different extensions") + << QString("same-base-name") + << relativeExecutableFilePath("basename"); + QTest::newRow("static library dependencies") + << QString("static-lib-deps") + << relativeExecutableFilePath("staticLibDeps"); + QTest::newRow("simple probes") + << QString("simple-probe") + << relativeExecutableFilePath("MyApp"); + QTest::newRow("application without sources") + << QString("app-without-sources") + << relativeExecutableFilePath("appWithoutSources"); + QTest::newRow("productNameWithDots") + << QString("productNameWithDots") + << relativeExecutableFilePath("myapp"); + QTest::newRow("only default properties") + << QString("two-default-property-values") + << relativeProductBuildDir("two-default-property-values") + "/set"; + QTest::newRow("Export item with Group") + << QString("export-item-with-group") + << relativeExecutableFilePath("app"); + QTest::newRow("QBS-728") + << QString("QBS-728") + << QString(); +} + +void TestApi::buildProjectDryRun() +{ + QFETCH(QString, projectSubDir); + QFETCH(QString, productFileName); + const QString projectFilePath = projectSubDir + QLatin1Char('/') + projectSubDir + + QLatin1String(".qbs"); + qbs::SetupProjectParameters params = defaultSetupParameters(projectFilePath); + removeBuildDir(params); + qbs::BuildOptions options; + options.setDryRun(true); + const qbs::ErrorInfo errorInfo = doBuildProject(projectFilePath, 0, 0, 0, options); + VERIFY_NO_ERROR(errorInfo); + QVERIFY2(!QFileInfo::exists(relativeBuildDir()), qPrintable(QDir(relativeBuildDir()) + .entryList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System).join(", "))); +} + +void TestApi::buildProjectDryRun_data() +{ + return buildProject_data(); +} + +void TestApi::buildSingleFile() +{ + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("build-single-file"); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + qbs::Project project = setupJob->project(); + qbs::BuildOptions options; + options.setFilesToConsider(QStringList(setupParams.buildRoot() + "/compiled.cpp")); + options.setActiveFileTags(QStringList("obj")); + m_logSink->setLogLevel(qbs::LoggerMaxLevel); + std::unique_ptr buildJob(project.buildAllProducts(options)); + BuildDescriptionReceiver receiver; + connect(buildJob.get(), &qbs::BuildJob::reportCommandDescription, &receiver, + &BuildDescriptionReceiver::handleDescription); + waitForFinished(buildJob.get()); + QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString())); + QCOMPARE(receiver.descriptions.count("compiling"), 2); + QCOMPARE(receiver.descriptions.count("precompiling"), 1); + QVERIFY2(receiver.descriptions.contains("generating generated.h"), + qPrintable(receiver.descriptions)); + QVERIFY2(receiver.descriptions.contains("compiling compiled.cpp"), + qPrintable(receiver.descriptions)); +} + +void TestApi::canonicalToolchainList() +{ + // All the known toolchain lists should be equal + QCOMPARE(qbs::canonicalToolchain(QStringList({"xcode", "clang", "llvm", "gcc"})), + QStringList({"xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"clang", "llvm", "gcc"})), + QStringList({"clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"clang-cl", "msvc"})), + QStringList({"clang-cl", "msvc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"llvm", "gcc"})), + QStringList({"llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"mingw", "gcc"})), + QStringList({"mingw", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"gcc"})), + QStringList({"gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"msvc"})), + QStringList({"msvc"})); + + // Single names should canonicalize to the known lists + QCOMPARE(qbs::canonicalToolchain(QStringList({"xcode"})), + QStringList({"xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"clang"})), + QStringList({"clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"clang-cl"})), + QStringList({"clang-cl", "msvc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"llvm"})), + QStringList({"llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"mingw"})), + QStringList({"mingw", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"gcc"})), + QStringList({"gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"msvc"})), + QStringList({"msvc"})); + + // Missing some in the middle + QCOMPARE(qbs::canonicalToolchain(QStringList({"xcode", "llvm", "gcc"})), + QStringList({"xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"xcode", "clang", "gcc"})), + QStringList({"xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"xcode", "gcc"})), + QStringList({"xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"clang", "llvm"})), + QStringList({"clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"clang", "gcc"})), + QStringList({"clang", "llvm", "gcc"})); + + // Sorted wrong, missing some in the middle + QCOMPARE(qbs::canonicalToolchain(QStringList({"gcc", "llvm", "clang", "xcode"})), + QStringList({"xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"clang", "gcc", "llvm", "xcode"})), + QStringList({"xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"llvm", "clang", "xcode", "gcc"})), + QStringList({"xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"gcc", "llvm", "clang"})), + QStringList({"clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"gcc", "clang", "xcode"})), + QStringList({"xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"gcc", "llvm"})), + QStringList({"llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"gcc", "mingw"})), + QStringList({"mingw", "gcc"})); + + // Duplicates + QCOMPARE(qbs::canonicalToolchain(QStringList({"gcc", "llvm", "clang", "xcode", "xcode", + "xcode"})), + QStringList({"xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"clang", "gcc", "llvm", "clang", "xcode"})), + QStringList({"xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"llvm", "clang", "clang", "xcode", "xcode", + "gcc"})), + QStringList({"xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"llvm", "clang", "gcc", "llvm", "clang"})), + QStringList({"clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"xcode", "gcc", "clang", "gcc", "clang", + "xcode"})), + QStringList({"xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"llvm", "gcc", "llvm", "llvm"})), + QStringList({"llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain(QStringList({"gcc", "gcc", "gcc", "mingw"})), + QStringList({"mingw", "gcc"})); + + // Custom insanity + QCOMPARE(qbs::canonicalToolchain( + QStringList({"crazy", "gcc", "llvm", "clang", "xcode", "insane"})), + QStringList({"crazy", "insane", "xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain( + QStringList({"crazy", "gcc", "llvm", "clang", "xcode", "insane", "crazy"})), + QStringList({"crazy", "insane", "xcode", "clang", "llvm", "gcc"})); + QCOMPARE(qbs::canonicalToolchain( + QStringList({"crazy", "insane", "gcc", "trade", "llvm", "clang", "xcode", + "insane", "mark", "crazy"})), + QStringList({"crazy", "insane", "trade", "mark", "xcode", "clang", "llvm", "gcc"})); +} + +void TestApi::checkOutputs() +{ + QFETCH(bool, check); + qbs::SetupProjectParameters params = defaultSetupParameters("/check-outputs"); + qbs::BuildOptions options; + options.setForceOutputCheck(check); + removeBuildDir(params); + qbs::ErrorInfo errorInfo = doBuildProject("/check-outputs", 0, 0, 0, options); + if (check) + QVERIFY(errorInfo.hasError()); + else + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::checkOutputs_data() +{ + QTest::addColumn("check"); + QTest::newRow("checked outputs") << true; + QTest::newRow("unchecked outputs") << false; +} + +qbs::GroupData findGroup(const qbs::ProductData &product, const QString &name) +{ + const auto groups = product.groups(); + for (const qbs::GroupData &g : groups) { + if (g.name() == name) + return g; + } + return qbs::GroupData(); +} + +#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES + +static qbs::Project::ProductSelection defaultProducts() +{ + return qbs::Project::ProductSelectionDefaultOnly; +} + +void TestApi::changeContent() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("project-editing"); + std::unique_ptr job; + qbs::Project project; + qbs::ProjectData projectData; + qbs::ProductData product; + + const auto resolve = [&] { + job.reset(project.setupProject(setupParams, m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + project = job->project(); + projectData = project.projectData(); + QCOMPARE(projectData.allProducts().size(), 1); + product = projectData.allProducts().front(); + }; + resolve(); + QVERIFY(product.groups().size() >= 8); + + // Error handling: Invalid product. + qbs::ErrorInfo errorInfo = project.addGroup(qbs::ProductData(), "blubb"); + QVERIFY(errorInfo.hasError()); + QVERIFY(errorInfo.toString().contains("invalid")); + + // Error handling: Empty group name. + errorInfo = project.addGroup(product, QString()); + QVERIFY(errorInfo.hasError()); + QVERIFY(errorInfo.toString().contains("empty")); + + WAIT_FOR_NEW_TIMESTAMP(); + errorInfo = project.addGroup(product, "New Group 1"); + VERIFY_NO_ERROR(errorInfo); + + errorInfo = project.addGroup(product, "New Group 2"); + VERIFY_NO_ERROR(errorInfo); + + resolve(); + QVERIFY(product.groups().size() >= 10); + + // Error handling: Group already inserted. + errorInfo = project.addGroup(product, "New Group 1"); + QVERIFY(errorInfo.hasError()); + QVERIFY(errorInfo.toString().contains("already")); + + // Error handling: Add list of files with double entries. + errorInfo = project.addFiles(product, qbs::GroupData(), QStringList() << "file.cpp" + << "file.cpp"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("more than once"), qPrintable(errorInfo.toString())); + + // Add files to empty array literal. + WAIT_FOR_NEW_TIMESTAMP(); + qbs::GroupData group = findGroup(product, "New Group 1"); + QVERIFY(group.isValid()); + errorInfo = project.addFiles(product, group, QStringList() << "file.h" << "file.cpp"); + VERIFY_NO_ERROR(errorInfo); + + // Error handling: Add the same file again. + resolve(); + group = findGroup(product, "New Group 1"); + QVERIFY(group.isValid()); + errorInfo = project.addFiles(product, group, QStringList() << "file.cpp"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("already"), qPrintable(errorInfo.toString())); + + // Remove one of the newly added files again. + WAIT_FOR_NEW_TIMESTAMP(); + errorInfo = project.removeFiles(product, group, QStringList("file.h")); + VERIFY_NO_ERROR(errorInfo); + + // Error handling: Try to remove the same file again. + resolve(); + group = findGroup(product, "New Group 1"); + QVERIFY(group.isValid()); + errorInfo = project.removeFiles(product, group, QStringList() << "file.h"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("not known"), qPrintable(errorInfo.toString())); + + // Error handling: Try to remove a file from a complex list. + group = findGroup(product, "Existing Group 2"); + QVERIFY(group.isValid()); + errorInfo = project.removeFiles(product, group, QStringList() << "existingfile2.txt"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("complex"), qPrintable(errorInfo.toString())); + + // Remove file from product's 'files' binding. + WAIT_FOR_NEW_TIMESTAMP(); + errorInfo = project.removeFiles(product, qbs::GroupData(), QStringList("main.cpp")); + VERIFY_NO_ERROR(errorInfo); + resolve(); + + // Add file to non-empty array literal. + WAIT_FOR_NEW_TIMESTAMP(); + group = findGroup(product, "Existing Group 1"); + QVERIFY(group.isValid()); + errorInfo = project.addFiles(product, group, QStringList() << "newfile1.txt"); + VERIFY_NO_ERROR(errorInfo); + resolve(); + + // Add files to list represented as a single string. + WAIT_FOR_NEW_TIMESTAMP(); + errorInfo = project.addFiles(product, qbs::GroupData(), QStringList() << "newfile2.txt"); + VERIFY_NO_ERROR(errorInfo); + resolve(); + + // Add files to list represented as an identifier. + WAIT_FOR_NEW_TIMESTAMP(); + group = findGroup(product, "Existing Group 2"); + QVERIFY(group.isValid()); + errorInfo = project.addFiles(product, group, QStringList() << "newfile3.txt"); + VERIFY_NO_ERROR(errorInfo); + resolve(); + + // Add files to list represented as a block of code (not yet implemented). + group = findGroup(product, "Existing Group 3"); + QVERIFY(group.isValid()); + errorInfo = project.addFiles(product, group, QStringList() << "newfile4.txt"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("complex"), qPrintable(errorInfo.toString())); + + // Add file to group with directory prefix. + WAIT_FOR_NEW_TIMESTAMP(); + group = findGroup(product, "Existing Group 4"); + QVERIFY(group.isValid()); + errorInfo = project.addFiles(product, group, QStringList() << "file.txt"); + VERIFY_NO_ERROR(errorInfo); + resolve(); + + // Error handling: Add file to group with non-directory prefix. + group = findGroup(product, "Existing Group 5"); + QVERIFY(group.isValid()); + errorInfo = project.addFiles(product, group, QStringList() << "newfile1.txt"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("prefix"), qPrintable(errorInfo.toString())); + + // Remove group. + WAIT_FOR_NEW_TIMESTAMP(); + group = findGroup(product, "Existing Group 5"); + QVERIFY(group.isValid()); + errorInfo = project.removeGroup(product, group); + VERIFY_NO_ERROR(errorInfo); + resolve(); + + // Error handling: Try to remove the same group again. + errorInfo = project.removeGroup(product, group); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("does not exist"), qPrintable(errorInfo.toString())); + + // Add a file to a group where the file name is already matched by a wildcard. + projectData = project.projectData(); + QVERIFY(projectData.products().size() == 1); + product = projectData.products().front(); + group = findGroup(product, "Group with wildcards"); + QVERIFY(group.isValid()); + QFile newFile("koerper.klaus"); + QVERIFY2(newFile.open(QIODevice::WriteOnly), qPrintable(newFile.errorString())); + newFile.close(); + errorInfo = project.addFiles(product, group, QStringList() << newFile.fileName()); + VERIFY_NO_ERROR(errorInfo); + resolve(); + group = findGroup(product, "Group with wildcards"); + QVERIFY(group.isValid()); + QCOMPARE(group.sourceArtifactsFromWildcards().size(), 1); + QCOMPARE(group.sourceArtifactsFromWildcards().front().filePath(), + QFileInfo(newFile).absoluteFilePath()); + + // Error checking: Try to remove a file that originates from a wildcard pattern. + projectData = project.projectData(); + QVERIFY(projectData.products().size() == 1); + product = projectData.products().front(); + group = findGroup(product, "Other group with wildcards"); + QVERIFY(group.isValid()); + errorInfo = project.removeFiles(product, group, QStringList() << "test.wildcard"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("pattern"), qPrintable(errorInfo.toString())); + + // Check whether building will take the added and removed cpp files into account. + // This must not be moved below the re-resolving test!!! + qbs::BuildOptions buildOptions; + buildOptions.setDryRun(true); + BuildDescriptionReceiver rcvr; + std::unique_ptr buildJob(project.buildAllProducts(buildOptions, defaultProducts(), + this)); + connect(buildJob.get(), &qbs::BuildJob::reportCommandDescription, + &rcvr, &BuildDescriptionReceiver::handleDescription); + waitForFinished(buildJob.get()); + QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString())); + QVERIFY(rcvr.descriptions.contains("compiling file.cpp")); + QVERIFY(!rcvr.descriptions.contains("compiling main.cpp")); + + // Error handling: Try to change the project during a build. + buildJob.reset(project.buildAllProducts(buildOptions, defaultProducts(), this)); + errorInfo = project.addGroup(projectData.products().front(), "blubb"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("in progress"), qPrintable(errorInfo.toString())); + waitForFinished(buildJob.get()); + errorInfo = project.addGroup(projectData.products().front(), "blubb"); + VERIFY_NO_ERROR(errorInfo); + + project = qbs::Project(); + job.reset(nullptr); + buildJob.reset(nullptr); + removeBuildDir(setupParams); + // Add a file to the top level of a product that does not have a "files" binding yet. + setupParams.setProjectFilePath(QDir::cleanPath(m_workingDataDir + + "/project-editing/project-with-no-files.qbs")); + + resolve(); + WAIT_FOR_NEW_TIMESTAMP(); + errorInfo = project.addFiles(product, qbs::GroupData(), QStringList("main.cpp")); + VERIFY_NO_ERROR(errorInfo); + resolve(); + rcvr.descriptions.clear(); + buildJob.reset(project.buildAllProducts(buildOptions, defaultProducts(), this)); + connect(buildJob.get(), &qbs::BuildJob::reportCommandDescription, + &rcvr, &BuildDescriptionReceiver::handleDescription); + waitForFinished(buildJob.get()); + QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString())); + QVERIFY(rcvr.descriptions.contains("compiling main.cpp")); + job.reset(project.setupProject(setupParams, m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); +} + +#endif // QBS_ENABLE_PROJECT_FILE_UPDATES + +void TestApi::commandExtraction() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("/command-extraction"); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + qbs::Project project = setupJob->project(); + qbs::ProjectData projectData = project.projectData(); + QCOMPARE(projectData.allProducts().size(), 1); + qbs::ProductData productData = projectData.allProducts().front(); + qbs::ErrorInfo errorInfo; + const QString projectDirPath = QDir::cleanPath(QFileInfo(setupParams.projectFilePath()).path()); + const QString sourceFilePath = projectDirPath + "/main.cpp"; + + // Before the first build, no rules exist. + qbs::RuleCommandList commands + = project.ruleCommands(productData, sourceFilePath, "obj", &errorInfo); + QCOMPARE(commands.size(), 0); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("No rule"), qPrintable(errorInfo.toString())); + + qbs::BuildOptions options; + options.setDryRun(true); + std::unique_ptr buildJob(project.buildAllProducts(options)); + waitForFinished(buildJob.get()); + QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString())); + projectData = project.projectData(); + QCOMPARE(projectData.allProducts().size(), 1); + productData = projectData.allProducts().front(); + errorInfo = qbs::ErrorInfo(); + + // After the build, the compile command must be found. + commands = project.ruleCommands(productData, sourceFilePath, "obj", &errorInfo); + QCOMPARE(commands.size(), 1); + QVERIFY2(!errorInfo.hasError(), qPrintable(errorInfo.toString())); + const qbs::RuleCommand command = commands.front(); + QCOMPARE(command.type(), qbs::RuleCommand::ProcessCommandType); + QVERIFY(!command.executable().isEmpty()); + QVERIFY(!command.arguments().empty()); +} + +void TestApi::dependencyOnMultiplexedType() +{ + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("/dependency-on-multiplexed-type"); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + qbs::Project project = setupJob->project(); + qbs::ProjectData projectData = project.projectData(); + const QList allProducts = projectData.allProducts(); + QCOMPARE(allProducts.size(), 5); + int depCount = 0; + int p1Count = 0; + int p2Count = 0; + for (const qbs::ProductData &p : allProducts) { + if (p.name() == "dep") { + ++depCount; + QCOMPARE(p.dependencies().size(), 0); + } else if (p.name() == "p1") { + ++p1Count; + if (p.multiplexConfigurationId().isEmpty()) // aggregate + QCOMPARE(p.dependencies().size(), 3); + else + QCOMPARE(p.dependencies().size(), 1); + } else { + QVERIFY(p.name() == "p2"); + ++p2Count; + + // FIXME: This is an odd effect of our current algorithm: We collect the products + // matching the requested type and add Depends items with their names ("p1" in + // this case). Later, the algorithm checking for compatibility regarding the + // multiplexing axes picks the aggregate. However, the aggregate does not have + // a matching type... It's not entirely clear what the real expected + // result should be here. + QCOMPARE(p.dependencies().size(), 2); + } + } + std::unique_ptr buildJob(project.buildAllProducts(qbs::BuildOptions())); + waitForFinished(buildJob.get()); + QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString())); +} + +void TestApi::changeDependentLib() +{ + qbs::ErrorInfo errorInfo = doBuildProject("change-dependent-lib"); + VERIFY_NO_ERROR(errorInfo); + + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("change-dependent-lib.qbs", "cpp.defines: [\"XXXX\"]", + "cpp.defines: [\"ABCD\"]"); + errorInfo = doBuildProject("change-dependent-lib"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::enableAndDisableProduct() +{ + BuildDescriptionReceiver bdr; + qbs::ErrorInfo errorInfo = doBuildProject("enable-and-disable-product", &bdr); + VERIFY_NO_ERROR(errorInfo); + QVERIFY(!bdr.descriptions.contains("compiling")); + + WAIT_FOR_NEW_TIMESTAMP(); + QFile projectFile("enable-and-disable-product.qbs"); + QVERIFY(projectFile.open(QIODevice::ReadWrite)); + QByteArray content = projectFile.readAll(); + content.replace("undefined", "'hidden'"); + projectFile.resize(0); + projectFile.write(content); + projectFile.close(); + bdr.descriptions.clear(); + errorInfo = doBuildProject("enable-and-disable-product", &bdr); + VERIFY_NO_ERROR(errorInfo); + QVERIFY(bdr.descriptions.contains("linking")); + + WAIT_FOR_NEW_TIMESTAMP(); + touch("main.cpp"); + QVERIFY(projectFile.open(QIODevice::ReadWrite)); + content = projectFile.readAll(); + content.replace("'hidden'", "undefined"); + projectFile.resize(0); + projectFile.write(content); + projectFile.close(); + bdr.descriptions.clear(); + errorInfo = doBuildProject("enable-and-disable-product", &bdr); + VERIFY_NO_ERROR(errorInfo); + QVERIFY(!bdr.descriptions.contains("compiling")); +} + +void TestApi::errorInSetupRunEnvironment() +{ + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("error-in-setup-run-environment"); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + const qbs::Project project = job->project(); + QVERIFY(project.isValid()); + QCOMPARE(project.projectData().products().size(), 1); + const qbs::ProductData product = project.projectData().products().front(); + + bool exceptionCaught = false; + try { + const SettingsPtr s = settings(); + qbs::RunEnvironment runEnv = project.getRunEnvironment(product, qbs::InstallOptions(), + QProcessEnvironment(), QStringList(), s.get()); + qbs::ErrorInfo error; + const QProcessEnvironment env = runEnv.runEnvironment(&error); + QVERIFY(error.hasError()); + QVERIFY(error.toString().contains("trallala")); + } catch (const qbs::ErrorInfo &) { + exceptionCaught = true; + } + QVERIFY(!exceptionCaught); +} + +void TestApi::excludedInputs() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("excluded-inputs"); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + const qbs::Project project = job->project(); + std::unique_ptr buildJob(project.buildAllProducts(qbs::BuildOptions())); + waitForFinished(buildJob.get()); + QVERIFY2(!buildJob->error().hasError(), qPrintable(job->error().toString())); + QVERIFY(project.isValid()); + QCOMPARE(project.projectData().products().size(), 2); + qbs::ProductData depProduct; + qbs::ProductData pProduct; + for (const qbs::ProductData &p : project.projectData().products()) { + if (p.name() == "dep") + depProduct = p; + else if (p.name() == "p") + pProduct = p; + } + QVERIFY(depProduct.isValid()); + QVERIFY(pProduct.isValid()); + int theTagCount = 0; + for (const qbs::ArtifactData &artifact : depProduct.targetArtifacts()) { + if (!artifact.fileTags().contains("the_tag")) + continue; + ++theTagCount; + QFile f(artifact.filePath()); + QVERIFY2(f.open(QIODevice::ReadOnly), qPrintable(f.errorString())); + const QByteArray content = f.readAll(); + QVERIFY2(content.contains("the_content"), content.constData()); + QCOMPARE(artifact.fileTags().contains("the_other_tag"), + content.contains("the_other_content")); + } + QCOMPARE(theTagCount, 2); + int dummyCount = 0; + for (const qbs::ArtifactData &artifact : pProduct.targetArtifacts()) { + QFileInfo fi(artifact.filePath()); + QVERIFY2(fi.exists(), qPrintable(fi.filePath())); + if (fi.fileName().startsWith("dummy")) + ++dummyCount; + } + QCOMPARE(dummyCount, 3); +} + +static qbs::ErrorInfo forceRuleEvaluation(const qbs::Project &project) +{ + qbs::BuildOptions buildOptions; + buildOptions.setDryRun(true); + std::unique_ptr buildJob(project.buildAllProducts(buildOptions)); + waitForFinished(buildJob.get()); + return buildJob->error(); +} + +void TestApi::disabledInstallGroup() +{ + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("disabled_install_group"); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + const qbs::Project project = job->project(); + + const qbs::ErrorInfo errorInfo = forceRuleEvaluation(project); + VERIFY_NO_ERROR(errorInfo); + + qbs::ProjectData projectData = project.projectData(); + QCOMPARE(projectData.allProducts().size(), 1); + qbs::ProductData product = projectData.allProducts().front(); + const QList targets = product.targetArtifacts(); + QCOMPARE(targets.size(), 1); + QVERIFY(targets.front().isGenerated()); + QVERIFY(targets.front().isExecutable()); + QVERIFY(targets.front().isTargetArtifact()); + QCOMPARE(projectData.installableArtifacts().size(), 0); + QCOMPARE(product.targetExecutable(), targets.front().filePath()); +} + +void TestApi::disabledProduct() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("disabled-product"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::disabledProject() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("disabled-project"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::disappearedWildcardFile() +{ + const qbs::SetupProjectParameters setupParams + = defaultSetupParameters("disappeared-wildcard-file/disappeared-wildcard-file.qbs"); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, nullptr)); + QVERIFY(waitForFinished(setupJob.get())); + VERIFY_NO_ERROR(setupJob->error()); + + qbs::Project project = setupJob->project(); + qbs::ProjectData projectData = project.projectData(); + QVERIFY(projectData.isValid()); + QList products = projectData.allProducts(); + QCOMPARE(products.size(), 1); + QCOMPARE(products.first().groups().size(), 1); + QCOMPARE(products.first().groups().first().allFilePaths().size(), 2); + + std::unique_ptr buildJob(project.buildAllProducts({})); + QVERIFY(waitForFinished(buildJob.get())); + VERIFY_NO_ERROR(buildJob->error()); + + WAIT_FOR_NEW_TIMESTAMP(); + const QString fileToRemove = QFileInfo(setupParams.projectFilePath()).path() + "/file2.txt"; + QVERIFY(QFile::remove(fileToRemove)); + buildJob.reset(project.buildAllProducts({})); + QVERIFY(waitForFinished(buildJob.get())); + QVERIFY(buildJob->error().hasError()); + QVERIFY2(buildJob->error().toString().contains( + tr("Source file '%1' has disappeared.") + .arg(fileToRemove)), qPrintable(buildJob->error().toString())); + + setupJob.reset(project.setupProject(setupParams, m_logSink, nullptr)); + QVERIFY(waitForFinished(setupJob.get())); + VERIFY_NO_ERROR(setupJob->error()); + + project = setupJob->project(); + projectData = project.projectData(); + QVERIFY(projectData.isValid()); + products = projectData.allProducts(); + QCOMPARE(products.size(), 1); + QCOMPARE(products.first().groups().size(), 1); + QCOMPARE(products.first().groups().first().allFilePaths().size(), 1); + + buildJob.reset(project.buildAllProducts({})); + QVERIFY(waitForFinished(buildJob.get())); + VERIFY_NO_ERROR(buildJob->error()); +} + +void TestApi::renamedQbsSource() +{ + const qbs::SetupProjectParameters setupParams + = defaultSetupParameters("renamed-qbs-source-file/renamed-qbs-source-file.qbs"); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, nullptr)); + QVERIFY(waitForFinished(setupJob.get())); + VERIFY_NO_ERROR(setupJob->error()); + qbs::Project project = setupJob->project(); + QCOMPARE(project.projectData().allProducts().size(), 2); + + std::unique_ptr buildJob(project.buildAllProducts({})); + QVERIFY(waitForFinished(buildJob.get())); + VERIFY_NO_ERROR(buildJob->error()); + + WAIT_FOR_NEW_TIMESTAMP(); + const QString oldFilePath = QFileInfo(setupParams.projectFilePath()).path() + + "/the-product/the-prodduct.qbs"; + const QString newFilePath = QFileInfo(setupParams.projectFilePath()).path() + + "/the-product/the-product.qbs"; + QVERIFY(QFile::rename(oldFilePath, newFilePath)); + REPLACE_IN_FILE(setupParams.projectFilePath(), "prodduct", "product"); + buildJob.reset(project.buildAllProducts({})); + QVERIFY(waitForFinished(buildJob.get())); + QVERIFY(buildJob->error().hasError()); + QVERIFY2(buildJob->error().toString().contains( + tr("Source file '%1' has disappeared.") + .arg(oldFilePath)), qPrintable(buildJob->error().toString())); + + setupJob.reset(project.setupProject(setupParams, m_logSink, nullptr)); + QVERIFY(waitForFinished(setupJob.get())); + VERIFY_NO_ERROR(setupJob->error()); + + project = setupJob->project(); + QCOMPARE(project.projectData().allProducts().size(), 2); + + buildJob.reset(project.buildAllProducts({})); + QVERIFY(waitForFinished(buildJob.get())); + VERIFY_NO_ERROR(buildJob->error()); +} + +void TestApi::duplicateProductNames() +{ + QFETCH(QString, projectFileName); + const qbs::ErrorInfo errorInfo = doBuildProject("duplicate-product-names/" + projectFileName); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("Duplicate product name"), + qPrintable(errorInfo.toString())); +} + +void TestApi::duplicateProductNames_data() +{ + QTest::addColumn("projectFileName"); + QTest::newRow("Names explicitly set") << QString("explicit.qbs"); + QTest::newRow("Unnamed products in same file") << QString("implicit.qbs"); + QTest::newRow("Unnamed products in files of the same name") << QString("implicit-indirect.qbs"); + +} + +void TestApi::emptyFileTagList() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("empty-filetag-list"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::emptySubmodulesList() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("empty-submodules-list"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::explicitlyDependsOn() +{ + BuildDescriptionReceiver receiver; + qbs::ErrorInfo errorInfo = doBuildProject("explicitly-depends-on", &receiver); + VERIFY_NO_ERROR(errorInfo); + if (m_logSink->output.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QVERIFY2(receiver.descriptions.contains("compiling compiler.cpp"), + qPrintable(receiver.descriptions)); + QVERIFY2(receiver.descriptions.contains("compiling a.in"), qPrintable(receiver.descriptions)); + QVERIFY2(receiver.descriptions.contains("compiling b.in"), qPrintable(receiver.descriptions)); + QVERIFY2(receiver.descriptions.contains("compiling c.in"), qPrintable(receiver.descriptions)); + QFile txtFile(relativeProductBuildDir("p") + "/compiler-name.txt"); + QVERIFY2(txtFile.open(QIODevice::ReadOnly), qPrintable(txtFile.errorString())); + const QByteArray content = txtFile.readAll(); + QCOMPARE(content, QByteArray("compiler file name: compiler")); + receiver.descriptions.clear(); + + errorInfo = doBuildProject("explicitly-depends-on", &receiver); + QVERIFY2(!receiver.descriptions.contains("compiling compiler.cpp"), + qPrintable(receiver.descriptions)); + QVERIFY2(!receiver.descriptions.contains("compiling a.in"), qPrintable(receiver.descriptions)); + QVERIFY2(!receiver.descriptions.contains("compiling b.in"), qPrintable(receiver.descriptions)); + QVERIFY2(!receiver.descriptions.contains("compiling c.in"), qPrintable(receiver.descriptions)); + VERIFY_NO_ERROR(errorInfo); + + WAIT_FOR_NEW_TIMESTAMP(); + touch("compiler.cpp"); + waitForFileUnlock(); + errorInfo = doBuildProject("explicitly-depends-on", &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY2(receiver.descriptions.contains("compiling compiler.cpp"), + qPrintable(receiver.descriptions)); + QVERIFY2(receiver.descriptions.contains("compiling a.in"), qPrintable(receiver.descriptions)); + QVERIFY2(receiver.descriptions.contains("compiling b.in"), qPrintable(receiver.descriptions)); + QVERIFY2(receiver.descriptions.contains("compiling c.in"), qPrintable(receiver.descriptions)); +} + +void TestApi::exportSimple() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("export-simple"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::exportWithRecursiveDepends() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("export-with-recursive-depends"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::fallbackGcc() +{ + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("fallback-gcc/fallback-gcc.qbs"); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, nullptr)); + waitForFinished(job.get()); + VERIFY_NO_ERROR(job->error()); + + qbs::ProjectData project = job->project().projectData(); + QVERIFY(project.isValid()); + QList products = project.allProducts(); + QCOMPARE(products.size(), 2); + for (const qbs::ProductData &p : qAsConst(products)) { + if (p.profile() == "unixProfile") { + qbs::PropertyMap moduleProps = p.moduleProperties(); + QCOMPARE(moduleProps.getModuleProperty("qbs", "targetOS").toStringList(), + QStringList({"unix"})); + QCOMPARE(moduleProps.getModuleProperty("qbs", "toolchain").toStringList(), + QStringList({"gcc"})); + QCOMPARE(QFileInfo(moduleProps.getModuleProperty("cpp", "cxxCompilerName").toString()) + .completeBaseName(), QString("g++")); + QCOMPARE(moduleProps.getModuleProperty("cpp", "dynamicLibrarySuffix").toString(), + QString(".so")); + } else { + QCOMPARE(p.profile(), QString("gccProfile")); + qbs::PropertyMap moduleProps = p.moduleProperties(); + QCOMPARE(moduleProps.getModuleProperty("qbs", "targetOS").toStringList(), + QStringList()); + QCOMPARE(moduleProps.getModuleProperty("qbs", "toolchain").toStringList(), + QStringList({"gcc"})); + QCOMPARE(QFileInfo(moduleProps.getModuleProperty("cpp", "cxxCompilerName").toString()) + .completeBaseName(), QString("g++")); + QCOMPARE(moduleProps.getModuleProperty("cpp", "dynamicLibrarySuffix").toString(), + QString()); + } + } +} + +void TestApi::fileTagger() +{ + BuildDescriptionReceiver receiver; + const qbs::ErrorInfo errorInfo = doBuildProject("file-tagger/moc_cpp.qbs", &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY2(receiver.descriptions.contains("moc bla.cpp"), qPrintable(receiver.descriptions)); +} + +void TestApi::fileTagsFilterOverride() +{ + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("filetagsfilter_override"); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + qbs::Project project = job->project(); + + const qbs::ErrorInfo errorInfo = forceRuleEvaluation(project); + VERIFY_NO_ERROR(errorInfo); + + qbs::ProjectData projectData = project.projectData(); + QCOMPARE(projectData.allProducts().size(), 1); + const qbs::ProductData product = projectData.allProducts().front(); + QList installableFiles = product.installableArtifacts(); + QCOMPARE(installableFiles.size(), 1); + QVERIFY(installableFiles.front().installData().installFilePath().contains("habicht")); +} + +void TestApi::generatedFilesList() +{ + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("generated-files-list"); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + QVERIFY(waitForFinished(setupJob.get())); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + qbs::Project project = setupJob->project(); + qbs::BuildOptions options; + options.setExecuteRulesOnly(true); + const std::unique_ptr buildJob(project.buildAllProducts(options)); + QVERIFY(waitForFinished(buildJob.get())); + VERIFY_NO_ERROR(buildJob->error()); + const qbs::ProjectData projectData = project.projectData(); + QCOMPARE(projectData.products().size(), 1); + const qbs::ProductData product = projectData.products().front(); + QString uiFilePath; + QVERIFY(product.generatedArtifacts().size() >= 6); + const auto artifacts = product.generatedArtifacts(); + for (const qbs::ArtifactData &a : artifacts) { + QVERIFY(a.isGenerated()); + QFileInfo fi(a.filePath()); + using qbs::Internal::HostOsInfo; + const QStringList possibleFileNames = QStringList() + << "main.cpp.o" << "main.cpp.obj" + << "mainwindow.cpp.o" << "mainwindow.cpp.obj" + << "moc_mainwindow.cpp" << "moc_mainwindow.cpp.o" << "moc_mainwindow.cpp.obj" + << "ui_mainwindow.h" + << HostOsInfo::appendExecutableSuffix("generated-files-list"); + QVERIFY2(possibleFileNames.contains(fi.fileName()) || fi.fileName().endsWith(".plist") + || fi.fileName().contains("qt_plugin_import"), + qPrintable(fi.fileName())); + } + const auto groups = product.groups(); + for (const qbs::GroupData &group : groups) { + const auto artifacts = group.sourceArtifacts(); + for (const qbs::ArtifactData &a : artifacts) { + QVERIFY(!a.isGenerated()); + QVERIFY(!a.isTargetArtifact()); + if (a.fileTags().contains(QLatin1String("ui"))) { + uiFilePath = a.filePath(); + break; + } + } + if (!uiFilePath.isEmpty()) + break; + } + QVERIFY(!uiFilePath.isEmpty()); + const QStringList directParents = project.generatedFiles(product, uiFilePath, false); + QCOMPARE(directParents.size(), 1); + const QFileInfo uiHeaderFileInfo(directParents.front()); + QCOMPARE(uiHeaderFileInfo.fileName(), QStringLiteral("ui_mainwindow.h")); + QVERIFY(!uiHeaderFileInfo.exists()); + const QStringList allParents = project.generatedFiles(product, uiFilePath, true); + QCOMPARE(allParents.size(), 3); +} + +void TestApi::infiniteLoopBuilding() +{ + QFETCH(QString, projectDirName); + qbs::SetupProjectParameters setupParams + = defaultSetupParameters(projectDirName + "/infinite-loop.qbs"); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + qbs::Project project = setupJob->project(); + const std::unique_ptr buildJob(project.buildAllProducts(qbs::BuildOptions())); + QTimer::singleShot(1000, buildJob.get(), &qbs::AbstractJob::cancel); + QVERIFY(waitForFinished(buildJob.get(), testTimeoutInMsecs())); + QVERIFY(buildJob->error().hasError()); +} + +void TestApi::infiniteLoopBuilding_data() +{ + QTest::addColumn("projectDirName"); + QTest::newRow("JS Command") << QString("infinite-loop-js"); + QTest::newRow("Process Command") << QString("infinite-loop-process"); +} + +void TestApi::infiniteLoopResolving() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("infinite-loop-resolving"); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + QTimer::singleShot(1000, setupJob.get(), &qbs::AbstractJob::cancel); + QVERIFY(waitForFinished(setupJob.get(), testTimeoutInMsecs())); + QVERIFY2(setupJob->error().toString().toLower().contains("cancel"), + qPrintable(setupJob->error().toString())); +} + +void TestApi::inheritQbsSearchPaths() +{ + const QString projectFilePath = "inherit-qbs-search-paths/prj.qbs"; + qbs::ErrorInfo errorInfo = doBuildProject(projectFilePath); + VERIFY_NO_ERROR(errorInfo); + + WAIT_FOR_NEW_TIMESTAMP(); + QFile projectFile(m_workingDataDir + '/' + projectFilePath); + QVERIFY(projectFile.open(QIODevice::ReadWrite)); + QByteArray content = projectFile.readAll(); + content.replace("qbsSearchPaths: \"subdir\"", "//qbsSearchPaths: \"subdir\""); + projectFile.resize(0); + projectFile.write(content); + projectFile.close(); + errorInfo = doBuildProject(projectFilePath); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("Dependency 'bli' not found"), + qPrintable(errorInfo.toString())); + + QVariantMap overriddenValues; + overriddenValues.insert("project.qbsSearchPaths", + QStringList() << m_workingDataDir + "/inherit-qbs-search-paths/subdir"); + errorInfo = doBuildProject(projectFilePath, 0, 0, 0, qbs::BuildOptions(), overriddenValues); + VERIFY_NO_ERROR(errorInfo); +} + +template typename T::value_type findElem(const T &list, Pred p) +{ + const auto it = std::find_if(list.cbegin(), list.cend(), p); + return it == list.cend() ? typename T::value_type() : *it; +} + +void TestApi::installableFiles() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("installed-artifact"); + QVariantMap overriddenValues; + overriddenValues.insert(QStringLiteral("qbs.installRoot"), QStringLiteral("/tmp")); + setupParams.setOverriddenValues(overriddenValues); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + qbs::Project project = job->project(); + + const qbs::ErrorInfo errorInfo = forceRuleEvaluation(project); + VERIFY_NO_ERROR(errorInfo); + + qbs::ProjectData projectData = project.projectData(); + QCOMPARE(projectData.allProducts().size(), 2); + qbs::ProductData product = findElem(projectData.allProducts(), [](const qbs::ProductData &p) { + return p.name() == QLatin1String("installedApp"); + }); + QVERIFY(product.isValid()); + const QList beforeInstallableFiles = product.installableArtifacts(); + QCOMPARE(beforeInstallableFiles.size(), 3); + for (const qbs::ArtifactData &f : beforeInstallableFiles) { + if (!QFileInfo(f.filePath()).fileName().startsWith("main")) { + QVERIFY(f.isExecutable()); + QString expectedTargetFilePath = qbs::Internal::HostOsInfo + ::appendExecutableSuffix(QStringLiteral("/tmp/usr/bin/installedApp")); + QCOMPARE(f.installData().localInstallFilePath(), expectedTargetFilePath); + QCOMPARE(product.targetExecutable(), expectedTargetFilePath); + break; + } + } + + setupParams = defaultSetupParameters("recursive-wildcards"); + setupParams.setOverriddenValues(overriddenValues); + job.reset(project.setupProject(setupParams, m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + project = job->project(); + projectData = project.projectData(); + QCOMPARE(projectData.allProducts().size(), 1); + product = projectData.allProducts().front(); + const QList afterInstallableFiles = product.installableArtifacts(); + QCOMPARE(afterInstallableFiles.size(), 2); + for (const qbs::ArtifactData &f : afterInstallableFiles) + QVERIFY(!f.isExecutable()); + QCOMPARE(afterInstallableFiles.front().installData().localInstallFilePath(), + QLatin1String("/tmp/dir/file1.txt")); + QCOMPARE(afterInstallableFiles.last().installData().localInstallFilePath(), + QLatin1String("/tmp/dir/file2.txt")); +} + +void TestApi::isRunnable() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("is-runnable"); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + qbs::Project project = job->project(); + const QList products = project.projectData().products(); + QCOMPARE(products.size(), 2); + for (const qbs::ProductData &p : products) { + QVERIFY2(p.name() == "app" || p.name() == "lib", qPrintable(p.name())); + if (p.name() == "app") + QVERIFY(p.isRunnable()); + else + QVERIFY(!p.isRunnable()); + } +} + +void TestApi::linkDynamicLibs() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("link-dynamiclibs"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::linkDynamicAndStaticLibs() +{ + BuildDescriptionReceiver bdr; + qbs::BuildOptions options; + options.setEchoMode(qbs::CommandEchoModeCommandLine); + const qbs::ErrorInfo errorInfo = doBuildProject("link-dynamiclibs-staticlibs", &bdr, nullptr, + nullptr, options); + VERIFY_NO_ERROR(errorInfo); + + // The dependent static libs should not appear in the link command for the executable. + const SettingsPtr s = settings(); + const qbs::Profile buildProfile(profileName(), s.get()); + if (profileToolchain(buildProfile).contains("gcc")) { + static const std::regex appLinkCmdRex(" -o [^ ]*/HelloWorld" QBS_HOST_EXE_SUFFIX " "); + QString appLinkCmd; + for (const QString &line : qAsConst(bdr.descriptionLines)) { + const auto ln = line.toStdString(); + if (std::regex_search(ln, appLinkCmdRex)) { + appLinkCmd = line; + break; + } + } + QVERIFY(!appLinkCmd.isEmpty()); + QVERIFY(!appLinkCmd.contains("static1")); + QVERIFY(!appLinkCmd.contains("static2")); + } +} + +void TestApi::linkStaticAndDynamicLibs() +{ + BuildDescriptionReceiver bdr; + qbs::BuildOptions options; + options.setEchoMode(qbs::CommandEchoModeCommandLine); + m_logSink->output.clear(); + const qbs::ErrorInfo errorInfo = doBuildProject("link-staticlibs-dynamiclibs", &bdr, nullptr, + nullptr, options); + VERIFY_NO_ERROR(errorInfo); + const bool isNormalUnix = m_logSink->output.contains("is normal unix: yes"); + const bool isNotNormalUnix = m_logSink->output.contains("is normal unix: no"); + QVERIFY2(isNormalUnix != isNotNormalUnix, qPrintable(m_logSink->output)); + + // The dependencies libdynamic1.so and libstatic2.a must not appear in the link command for the + // executable. The -rpath-link line for libdynamic1.so must be there. + const SettingsPtr s = settings(); + const qbs::Profile buildProfile(profileName(), s.get()); + if (profileToolchain(buildProfile).contains("gcc")) { + static const std::regex appLinkCmdRex(" -o [^ ]*/HelloWorld" QBS_HOST_EXE_SUFFIX " "); + QString appLinkCmd; + for (const QString &line : qAsConst(bdr.descriptionLines)) { + const auto ln = line.toStdString(); + if (std::regex_search(ln, appLinkCmdRex)) { + appLinkCmd = line; + break; + } + } + QVERIFY(!appLinkCmd.isEmpty()); + if (isNormalUnix) { + const std::regex rpathLinkRex("-rpath-link=\\S*/" + + relativeProductBuildDir("dynamic2").toStdString()); + const auto ln = appLinkCmd.toStdString(); + QVERIFY(std::regex_search(ln, rpathLinkRex)); + } + QVERIFY(!appLinkCmd.contains("libstatic2.a")); + QVERIFY(!appLinkCmd.contains("libdynamic2.so")); + } +} + +void TestApi::listBuildSystemFiles() +{ + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("subprojects/toplevelproject.qbs"); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + const auto buildSystemFiles = qbs::Internal::Set::fromStdSet( + job->project().buildSystemFiles()); + QVERIFY(buildSystemFiles.contains(setupParams.projectFilePath())); + QVERIFY(buildSystemFiles.contains(setupParams.buildRoot() + "/subproject2/subproject2.qbs")); + QVERIFY(buildSystemFiles.contains(setupParams.buildRoot() + + "/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)}); + std::unique_ptr 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.get(), &qbs::AbstractJob::taskStarted, taskDescHandler); + waitForFinished(job.get()); + 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 products = project.allProducts(); + QCOMPARE(products.size(), 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(), + qbs::Internal::HostOsInfo::appendExecutableSuffix(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(), + qbs::Internal::HostOsInfo::appendExecutableSuffix(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.get(), &qbs::AbstractJob::taskStarted, taskDescHandler); + waitForFinished(job.get()); + 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\"", "\"gcc\""); + projectFile.resize(0); + projectFile.write(content); + projectFile.close(); + job.reset(qbs::Project().setupProject(setupParams, m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + project = job->project().projectData(); + products = project.allProducts(); + QCOMPARE(products.size(), 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(), + qbs::Internal::HostOsInfo::appendExecutableSuffix(QString("g++"))); + } + } + } + QCOMPARE(clangProfiles, 1); +} + +void TestApi::localProfiles_data() +{ + QTest::addColumn("enableProfiles"); + QTest::newRow("profiles enabled") << true; + QTest::newRow("profiles disabled") << false; +} + +void TestApi::missingSourceFile() +{ + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("missing-source-file/missing-source-file.qbs"); + setupParams.setProductErrorMode(qbs::ErrorHandlingMode::Relaxed); + m_logSink->setLogLevel(qbs::LoggerMinLevel); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + qbs::ProjectData project = job->project().projectData(); + QCOMPARE(project.allProducts().size(), 1); + qbs::ProductData product = project.allProducts().front(); + QCOMPARE(product.groups().size(), 1); + qbs::GroupData group = product.groups().front(); + QCOMPARE(group.allSourceArtifacts().size(), 2); + + QFile::rename("file2.txt.missing", "file2.txt"); + job.reset(qbs::Project().setupProject(setupParams, m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + project = job->project().projectData(); + QCOMPARE(project.allProducts().size(), 1); + product = project.allProducts().front(); + QCOMPARE(product.groups().size(), 1); + group = product.groups().front(); + QCOMPARE(group.allSourceArtifacts().size(), 3); +} + +void TestApi::mocCppIncluded() +{ + // Initial build. + qbs::ErrorInfo errorInfo = doBuildProject("moc-hpp-included"); + VERIFY_NO_ERROR(errorInfo); + + // Touch header and try again. + WAIT_FOR_NEW_TIMESTAMP(); + QFile headerFile("object.h"); + QVERIFY2(headerFile.open(QIODevice::WriteOnly | QIODevice::Append), + qPrintable(headerFile.errorString())); + headerFile.write("\n"); + headerFile.close(); + errorInfo = doBuildProject("moc-hpp-included"); + VERIFY_NO_ERROR(errorInfo); + + // Touch cpp file and try again. + WAIT_FOR_NEW_TIMESTAMP(); + QFile cppFile("object.cpp"); + QVERIFY2(cppFile.open(QIODevice::WriteOnly | QIODevice::Append), + qPrintable(cppFile.errorString())); + cppFile.write("\n"); + cppFile.close(); + errorInfo = doBuildProject("moc-hpp-included"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::multiArch() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("multi-arch"); + const SettingsPtr s = settings(); + qbs::Internal::TemporaryProfile tph("host", s.get()); + qbs::Profile hostProfile = tph.p; + hostProfile.setValue("qbs.architecture", "host-arch"); + qbs::Internal::TemporaryProfile tpt("target", s.get()); + qbs::Profile targetProfile = tpt.p; + targetProfile.setValue("qbs.architecture", "target-arch"); + QVariantMap overriddenValues; + overriddenValues.insert("project.hostProfile", hostProfile.name()); + overriddenValues.insert("project.targetProfile", targetProfile.name()); + setupParams.setOverriddenValues(overriddenValues); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + qbs::Project project = setupJob->project(); + QCOMPARE(project.profile(), profileName()); + const QList &products = project.projectData().products(); + QCOMPARE(products.size(), 3); + QList hostProducts; + QList targetProducts; + for (const qbs::ProductData &p : products) { + QVERIFY2(p.profile() == hostProfile.name() || p.profile() == targetProfile.name(), + qPrintable(p.profile())); + if (p.profile() == hostProfile.name()) + hostProducts.push_back(p); + else + targetProducts.push_back(p); + } + QCOMPARE(hostProducts.size(), 2); + QCOMPARE(targetProducts.size(), 1); + QCOMPARE(targetProducts.front().name(), QLatin1String("p1")); + QStringList hostProductNames + = QStringList() << hostProducts.front().name() << hostProducts.last().name(); + QCOMPARE(hostProductNames.count("p1"), 1); + QCOMPARE(hostProductNames.count("p2"), 1); + + const QString p1HostMultiplexCfgId = hostProducts.at(0).multiplexConfigurationId(); + const QString p2HostMultiplexCfgId = hostProducts.at(1).multiplexConfigurationId(); + const QString p1TargetMultiplexCfgId = targetProducts.at(0).multiplexConfigurationId(); + + std::unique_ptr buildJob(project.buildAllProducts(qbs::BuildOptions())); + waitForFinished(buildJob.get()); + QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString())); + const QString outputBaseDir = setupParams.buildRoot() + '/'; + QFile p1HostArtifact(outputBaseDir + + relativeProductBuildDir("p1", QString(), p1HostMultiplexCfgId) + + "/host+target.output"); + QVERIFY2(p1HostArtifact.exists(), qPrintable(p1HostArtifact.fileName())); + QVERIFY2(p1HostArtifact.open(QIODevice::ReadOnly), qPrintable(p1HostArtifact.errorString())); + QCOMPARE(p1HostArtifact.readAll().constData(), "host-arch"); + QFile p1TargetArtifact(outputBaseDir + + relativeProductBuildDir("p1", QString(), p1TargetMultiplexCfgId) + + "/host+target.output"); + QVERIFY2(p1TargetArtifact.exists(), qPrintable(p1TargetArtifact.fileName())); + QVERIFY2(p1TargetArtifact.open(QIODevice::ReadOnly), qPrintable(p1TargetArtifact.errorString())); + QCOMPARE(p1TargetArtifact.readAll().constData(), "target-arch"); + QFile p2Artifact(outputBaseDir + + relativeProductBuildDir("p2", QString(), p2HostMultiplexCfgId) + + "/host-tool.output"); + QVERIFY2(p2Artifact.exists(), qPrintable(p2Artifact.fileName())); + QVERIFY2(p2Artifact.open(QIODevice::ReadOnly), qPrintable(p2Artifact.errorString())); + QCOMPARE(p2Artifact.readAll().constData(), "host-arch"); + + const QString installRoot = outputBaseDir + relativeBuildDir() + '/' + + qbs::InstallOptions::defaultInstallRoot(); + std::unique_ptr installJob(project.installAllProducts(qbs::InstallOptions())); + waitForFinished(installJob.get()); + QVERIFY2(!installJob->error().hasError(), qPrintable(installJob->error().toString())); + QFile p1HostArtifactInstalled(installRoot + "/host/host+target.output"); + QVERIFY2(p1HostArtifactInstalled.exists(), qPrintable(p1HostArtifactInstalled.fileName())); + QFile p1TargetArtifactInstalled(installRoot + "/target/host+target.output"); + QVERIFY2(p1TargetArtifactInstalled.exists(), qPrintable(p1TargetArtifactInstalled.fileName())); + QFile p2ArtifactInstalled(installRoot + "/host/host-tool.output"); + QVERIFY2(p2ArtifactInstalled.exists(), qPrintable(p2ArtifactInstalled.fileName())); + + // Error check: Try to build for the same profile twice. + overriddenValues.insert("project.targetProfile", hostProfile.name()); + setupParams.setOverriddenValues(overriddenValues); + setupJob.reset(project.setupProject(setupParams, m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY(setupJob->error().hasError()); + QVERIFY2(setupJob->error().toString().contains("Duplicate entry 'host' in qbs.profiles."), + qPrintable(setupJob->error().toString())); + + // Error check: Try to build for the same profile twice, this time attaching + // the properties via the product name. + overriddenValues.remove(QStringLiteral("project.targetProfile")); + overriddenValues.insert("products.p1.myProfiles", + targetProfile.name() + ',' + targetProfile.name()); + setupParams.setOverriddenValues(overriddenValues); + setupJob.reset(project.setupProject(setupParams, m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY(setupJob->error().hasError()); + QVERIFY2(setupJob->error().toString().contains("Duplicate entry 'target' in qbs.profiles."), + qPrintable(setupJob->error().toString())); +} + +struct ProductDataSelector +{ + void clear() + { + name.clear(); + qbsProperties.clear(); + } + + bool matches(const qbs::ProductData &p) const + { + return name == p.name() && qbsPropertiesMatch(p); + } + + bool qbsPropertiesMatch(const qbs::ProductData &p) const + { + for (auto it = qbsProperties.begin(); it != qbsProperties.end(); ++it) { + if (it.value() != p.moduleProperties().getModuleProperty("qbs", it.key())) + return false; + } + return true; + } + + QString name; + QVariantMap qbsProperties; +}; + +static qbs::ProductData takeMatchingProduct(QList &products, + const ProductDataSelector &s) +{ + qbs::ProductData result; + auto it = std::find_if(products.begin(), products.end(), + [&s] (const qbs::ProductData &pd) { return s.matches(pd); }); + if (it != products.end()) { + result = *it; + products.erase(it); + } + return result; +} + +void TestApi::multiplexing() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("multiplexing"); + std::unique_ptr setupJob( + qbs::Project().setupProject(setupParams, m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + qbs::Project project = setupJob->project(); + QList products = project.projectData().allProducts(); + qbs::ProductData product; + ProductDataSelector selector; + selector.name = "no-multiplexing"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(!product.isMultiplexed()); + QVERIFY(product.dependencies().empty()); + + selector.clear(); + selector.name = "multiplex-without-aggregator-2"; + selector.qbsProperties["architecture"] = "TRS-80"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QVERIFY(product.dependencies().empty()); + + selector.qbsProperties["architecture"] = "C64"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QVERIFY(product.dependencies().empty()); + + selector.clear(); + selector.name = "multiplex-with-export"; + selector.qbsProperties["architecture"] = "TRS-80"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QVERIFY(product.dependencies().empty()); + + selector.qbsProperties["architecture"] = "C64"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QVERIFY(product.dependencies().empty()); + + selector.clear(); + selector.name = "nonmultiplex-with-export"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(!product.isMultiplexed()); + QVERIFY(product.dependencies().empty()); + + selector.clear(); + selector.name = "nonmultiplex-exporting-aggregation"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(!product.isMultiplexed()); + QVERIFY(product.dependencies().empty()); + + selector.clear(); + selector.name = "multiplex-using-export"; + selector.qbsProperties["architecture"] = "TRS-80"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 2); + + selector.qbsProperties["architecture"] = "C64"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 2); + + selector.clear(); + selector.name = "multiplex-without-aggregator-2-depend-on-non-multiplexed"; + selector.qbsProperties["architecture"] = "TRS-80"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 1); + + selector.qbsProperties["architecture"] = "C64"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 1); + + selector.clear(); + selector.name = "multiplex-without-aggregator-4"; + selector.qbsProperties["architecture"] = "C64"; + selector.qbsProperties["buildVariant"] = "debug"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QVERIFY(product.dependencies().empty()); + selector.qbsProperties["buildVariant"] = "release"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QVERIFY(product.dependencies().empty()); + selector.qbsProperties["architecture"] = "TRS-80"; + selector.qbsProperties["buildVariant"] = "debug"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QVERIFY(product.dependencies().empty()); + selector.qbsProperties["buildVariant"] = "release"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QVERIFY(product.dependencies().empty()); + + selector.clear(); + selector.name = "multiplex-without-aggregator-4-depends-2"; + selector.qbsProperties["architecture"] = "C64"; + selector.qbsProperties["buildVariant"] = "debug"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 1); + selector.qbsProperties["buildVariant"] = "release"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 1); + selector.qbsProperties["architecture"] = "TRS-80"; + selector.qbsProperties["buildVariant"] = "debug"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 1); + selector.qbsProperties["buildVariant"] = "release"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 1); + + selector.clear(); + selector.name = "multiplex-with-aggregator-2"; + selector.qbsProperties["architecture"] = "C64"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 0); + selector.qbsProperties["architecture"] = "TRS-80"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 0); + selector.qbsProperties["architecture"] = "Atari ST"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 2); + + selector.clear(); + selector.name = "multiplex-with-aggregator-2-dependent"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(!product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 1); + + selector.clear(); + selector.name = "non-multiplexed-with-dependencies-on-multiplexed"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(!product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 2); + + selector.clear(); + selector.name = "non-multiplexed-with-dependencies-on-multiplexed-via-export1"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(!product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 4); + + selector.clear(); + selector.name = "non-multiplexed-with-dependencies-on-multiplexed-via-export2"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(!product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 3); + + selector.clear(); + selector.name = "non-multiplexed-with-dependencies-on-aggregation-via-export"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(!product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 2); + + selector.clear(); + selector.name = "aggregate-with-dependencies-on-aggregation-via-export"; + selector.qbsProperties["architecture"] = "C64"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 2); + selector.qbsProperties["architecture"] = "TRS-80"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 2); + selector.qbsProperties["architecture"] = "Atari ST"; + product = takeMatchingProduct(products, selector); + QVERIFY(product.isValid()); + QVERIFY(product.isMultiplexed()); + QCOMPARE(product.dependencies().size(), 4); + + QVERIFY(products.empty()); +} + +void TestApi::newOutputArtifactInDependency() +{ + BuildDescriptionReceiver receiver; + qbs::ErrorInfo errorInfo + = doBuildProject("new-output-artifact-in-dependency", &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY(receiver.descriptions.contains("linking app")); + const QByteArray linkingLibString = QByteArray("linking ") + + qbs::Internal::HostOsInfo::dynamicLibraryName("lib").toLatin1(); + QVERIFY(!receiver.descriptions.contains(linkingLibString)); + receiver.descriptions.clear(); + + WAIT_FOR_NEW_TIMESTAMP(); + QFile projectFile("new-output-artifact-in-dependency.qbs"); + QVERIFY2(projectFile.open(QIODevice::ReadWrite), qPrintable(projectFile.errorString())); + QByteArray contents = projectFile.readAll(); + contents.replace("//Depends", "Depends"); + projectFile.resize(0); + projectFile.write(contents); + projectFile.close(); + errorInfo = doBuildProject("new-output-artifact-in-dependency", &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY(receiver.descriptions.contains("linking app")); + QVERIFY(receiver.descriptions.contains(linkingLibString)); +} + +void TestApi::newPatternMatch() +{ + TaskReceiver receiver; + qbs::ErrorInfo errorInfo = doBuildProject("new-pattern-match", 0, 0, &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY2(receiver.taskDescriptions.contains("Resolving"), qPrintable(m_logSink->output)); + receiver.taskDescriptions.clear(); + + errorInfo = doBuildProject("new-pattern-match", 0, 0, &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY(!receiver.taskDescriptions.contains("Resolving")); + + WAIT_FOR_NEW_TIMESTAMP(); + QFile f("test.txt"); + QVERIFY2(f.open(QIODevice::WriteOnly), qPrintable(f.errorString())); + f.close(); + errorInfo = doBuildProject("new-pattern-match", 0, 0, &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY(receiver.taskDescriptions.contains("Resolving")); + receiver.taskDescriptions.clear(); + + errorInfo = doBuildProject("new-pattern-match", 0, 0, &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY(!receiver.taskDescriptions.contains("Resolving")); + + WAIT_FOR_NEW_TIMESTAMP(); + f.remove(); + errorInfo = doBuildProject("new-pattern-match", 0, 0, &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY(receiver.taskDescriptions.contains("Resolving")); +} + +void TestApi::nonexistingProjectPropertyFromProduct() +{ + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("nonexistingprojectproperties"); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QEXPECT_FAIL("", "QBS-432", Abort); + QVERIFY(job->error().hasError()); + QVERIFY2(job->error().toString().contains(QLatin1String("blubb")), + qPrintable(job->error().toString())); +} + +void TestApi::nonexistingProjectPropertyFromCommandLine() +{ + qbs::SetupProjectParameters setupParams + = defaultSetupParameters("nonexistingprojectproperties"); + removeBuildDir(setupParams); + QVariantMap projectProperties; + projectProperties.insert(QStringLiteral("project.blubb"), QStringLiteral("true")); + setupParams.setOverriddenValues(projectProperties); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY(job->error().hasError()); + QVERIFY2(job->error().toString().contains(QLatin1String("blubb")), + qPrintable(job->error().toString())); +} + +void TestApi::objC() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("objc"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::projectDataAfterProductInvalidation() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("project-data-after-" + "product-invalidation/project-data-after-product-invalidation.qbs"); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + qbs::Project project = setupJob->project(); + QVERIFY(project.isValid()); + QCOMPARE(project.projectData().products().size(), 1); + QVERIFY(project.projectData().products().front().generatedArtifacts().empty()); + std::unique_ptr buildJob(project.buildAllProducts(qbs::BuildOptions())); + waitForFinished(buildJob.get()); + QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString())); + QCOMPARE(project.projectData().products().size(), 1); + const qbs::ProductData productAfterBulding = project.projectData().products().front(); + QVERIFY(!productAfterBulding.generatedArtifacts().empty()); + QFile projectFile(setupParams.projectFilePath()); + WAIT_FOR_NEW_TIMESTAMP(); + QVERIFY2(projectFile.open(QIODevice::ReadWrite), qPrintable(projectFile.errorString())); + QByteArray content = projectFile.readAll(); + QVERIFY(!content.isEmpty()); + content.replace("\"file.cpp", "// \"file.cpp"); + projectFile.resize(0); + projectFile.write(content); + projectFile.flush(); + setupJob.reset(project.setupProject(setupParams, m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + QVERIFY(!project.isValid()); + project = setupJob->project(); + QVERIFY(project.isValid()); + QCOMPARE(project.projectData().products().size(), 1); + QVERIFY(project.projectData().products().front().generatedArtifacts() + == productAfterBulding.generatedArtifacts()); + buildJob.reset(project.buildAllProducts(qbs::BuildOptions())); + waitForFinished(buildJob.get()); + QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString())); + QCOMPARE(project.projectData().products().size(), 1); + QVERIFY(project.projectData().products().front().generatedArtifacts() + != productAfterBulding.generatedArtifacts()); +} + +void TestApi::processResult() +{ + waitForFileUnlock(); + removeBuildDir(defaultSetupParameters("process-result")); + + QFETCH(int, expectedExitCode); + QFETCH(bool, redirectStdout); + QFETCH(bool, redirectStderr); + QVariantMap overridden; + overridden.insert("products.app-caller.argument", expectedExitCode); + overridden.insert("products.app-caller.redirectStdout", redirectStdout); + overridden.insert("products.app-caller.redirectStderr", redirectStderr); + ProcessResultReceiver resultReceiver; + const qbs::ErrorInfo errorInfo = doBuildProject("process-result", + nullptr, &resultReceiver, nullptr, qbs::BuildOptions(), overridden); + if (m_logSink->output.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QCOMPARE(expectedExitCode != 0, errorInfo.hasError()); + QVERIFY(resultReceiver.results.size() > 1); + const qbs::ProcessResult &result = resultReceiver.results.back(); + QVERIFY2(result.executableFilePath().contains("app"), qPrintable(result.executableFilePath())); + QCOMPARE(expectedExitCode, result.exitCode()); + QCOMPARE(expectedExitCode == 0, result.success()); + QCOMPARE(result.error(), QProcess::UnknownError); + struct CheckParams { + CheckParams(bool r, QString f, QByteArray c, QStringList co) + : redirect(r) + , fileName(std::move(f)) + , expectedContent(std::move(c)) + , consoleOutput(std::move(co)) + {} + bool redirect; + QString fileName; + QByteArray expectedContent; + const QStringList consoleOutput; + }; + const std::vector checkParams({ + CheckParams(redirectStdout, "stdout.txt", "stdout", result.stdOut()), + CheckParams(redirectStderr, "stderr.txt", "stderr", result.stdErr()) + }); + for (const CheckParams &p : checkParams) { + QFile f(relativeProductBuildDir("app-caller") + '/' + p.fileName); + QCOMPARE(f.exists(), p.redirect); + if (p.redirect) { + QVERIFY2(f.open(QIODevice::ReadOnly), qPrintable(f.errorString())); + QCOMPARE(f.readAll(), p.expectedContent); + QCOMPARE(p.consoleOutput, QStringList()); + } else { + QCOMPARE(p.consoleOutput.join("").toLocal8Bit(), p.expectedContent); + } + } +} + +void TestApi::processResult_data() +{ + QTest::addColumn("expectedExitCode"); + QTest::addColumn("redirectStdout"); + QTest::addColumn("redirectStderr"); + QTest::newRow("success, no redirection") << 0 << false << false; + QTest::newRow("success, stdout redirection") << 0 << true << false; + QTest::newRow("failure, stderr redirection") << 1 << false << true; +} + +void TestApi::projectInvalidation() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("project-invalidation"); + QVERIFY(QFile::copy("project.no-error.qbs", "project-invalidation.qbs")); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + qbs::Project project = setupJob->project(); + QVERIFY(project.isValid()); + WAIT_FOR_NEW_TIMESTAMP(); + copyFileAndUpdateTimestamp("project.early-error.qbs", "project-invalidation.qbs"); + setupJob.reset(project.setupProject(setupParams, m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY(setupJob->error().hasError()); + QVERIFY(project.isValid()); // Error in Loader, old project still valid. + WAIT_FOR_NEW_TIMESTAMP(); + copyFileAndUpdateTimestamp("project.late-error.qbs", "project-invalidation.qbs"); + setupJob.reset(project.setupProject(setupParams, m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY(setupJob->error().hasError()); + QVERIFY(!project.isValid()); // Error in build data re-resolving, old project not valid anymore. +} + +void TestApi::projectLocking() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("project-locking"); + std::unique_ptr setupJob(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + qbs::Project project = setupJob->project(); + setupJob.reset(project.setupProject(setupParams, m_logSink, 0)); + std::unique_ptr setupJob2(project.setupProject(setupParams, + m_logSink, 0)); + waitForFinished(setupJob2.get()); + QVERIFY(setupJob2->error().hasError()); + QVERIFY2(setupJob2->error().toString() + .contains("Cannot start a job while another one is in progress."), + qPrintable(setupJob2->error().toString())); + waitForFinished(setupJob.get()); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); +} + +void TestApi::projectPropertiesByName() +{ + const QString projectFile = "project-properties-by-name/project-properties-by-name.qbs"; + qbs::ErrorInfo errorInfo = doBuildProject(projectFile); + QVERIFY(errorInfo.hasError()); + QVariantMap overridden; + overridden.insert("project.theDefines", QStringList() << "SUB1" << "SUB2"); + errorInfo = doBuildProject(projectFile, 0, 0, 0, qbs::BuildOptions(), overridden); + QVERIFY(errorInfo.hasError()); + overridden.clear(); + overridden.insert("projects.subproject1.theDefines", QStringList() << "SUB1"); + errorInfo = doBuildProject(projectFile, 0, 0, 0, qbs::BuildOptions(), overridden); + QVERIFY(errorInfo.hasError()); + overridden.insert("projects.subproject2.theDefines", QStringList() << "SUB2"); + errorInfo = doBuildProject(projectFile, 0, 0, 0, qbs::BuildOptions(), overridden); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::projectWithPropertiesItem() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("project-with-properties-item"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::projectWithProbeAndProfileItem() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("project-with-probe-and-profile-item"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::propertiesBlocks() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("properties-blocks"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::rc() +{ + BuildDescriptionReceiver bdr; + ProcessResultReceiver prr; + const auto buildRc = [this, &bdr, &prr]() { + bdr.descriptions.clear(); + bdr.descriptionLines.clear(); + prr.output.clear(); + prr.results.clear(); + const qbs::ErrorInfo errorInfo = doBuildProject("rc", &bdr, &prr); + if (errorInfo.hasError()) + qDebug() << prr.output; + return errorInfo; + }; + const auto rcFileWasCompiled = [&bdr]() { + return bdr.descriptions.contains("compiling test.rc"); + }; + qbs::ErrorInfo error = buildRc(); + VERIFY_NO_ERROR(error); + QCOMPARE(rcFileWasCompiled(), qbs::Internal::HostOsInfo::isWindowsHost()); + WAIT_FOR_NEW_TIMESTAMP(); + error = buildRc(); + VERIFY_NO_ERROR(error); + QVERIFY(!rcFileWasCompiled()); + touch("subdir/rc-include.h"); + error = buildRc(); + VERIFY_NO_ERROR(error); + QCOMPARE(rcFileWasCompiled(), qbs::Internal::HostOsInfo::isWindowsHost()); +} + +void TestApi::referencedFileErrors() +{ + QFETCH(bool, relaxedMode); + qbs::SetupProjectParameters params = defaultSetupParameters("referenced-file-errors"); + params.setDryRun(true); + params.setProductErrorMode(relaxedMode ? qbs::ErrorHandlingMode::Relaxed + : qbs::ErrorHandlingMode::Strict); + m_logSink->setLogLevel(qbs::LoggerMinLevel); + std::unique_ptr job(qbs::Project().setupProject(params, m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(job->error().hasError() != relaxedMode, qPrintable(job->error().toString())); + const qbs::Project project = job->project(); + QCOMPARE(project.isValid(), relaxedMode); + if (!relaxedMode) + return; + const QList products = project.projectData().allProducts(); + QCOMPARE(products.size(), 5); + for (const qbs::ProductData &p : products) + QCOMPARE(p.isEnabled(), p.name() != "p5"); +} + +void TestApi::referencedFileErrors_data() +{ + QTest::addColumn("relaxedMode"); + QTest::newRow("strict mode") << false; + QTest::newRow("relaxed mode") << true; +} + +qbs::SetupProjectParameters TestApi::defaultSetupParameters(const QString &projectFileOrDir) const +{ + QFileInfo fi(m_workingDataDir + QLatin1Char('/') + projectFileOrDir); + QString projectDirPath; + QString projectFilePath; + if (fi.isDir()) { + projectDirPath = fi.absoluteFilePath(); + projectFilePath = projectDirPath + QLatin1Char('/') + projectFileOrDir + + QStringLiteral(".qbs"); + } else { + projectDirPath = fi.absolutePath(); + projectFilePath = fi.absoluteFilePath(); + } + + qbs::SetupProjectParameters setupParams; + setupParams.setEnvironment(QProcessEnvironment::systemEnvironment()); + setupParams.setProjectFilePath(projectFilePath); + setupParams.setPropertyCheckingMode(qbs::ErrorHandlingMode::Strict); + setupParams.setOverrideBuildGraphData(true); + QDir::setCurrent(projectDirPath); + setupParams.setBuildRoot(projectDirPath); + const SettingsPtr s = settings(); + const qbs::Preferences prefs(s.get(), profileName()); + setupParams.setSearchPaths(prefs.searchPaths(QDir::cleanPath(QCoreApplication::applicationDirPath() + + QLatin1String("/" QBS_RELATIVE_SEARCH_PATH)))); + setupParams.setPluginPaths(prefs.pluginPaths(QDir::cleanPath(QCoreApplication::applicationDirPath() + + QLatin1String("/" QBS_RELATIVE_PLUGINS_PATH)))); + setupParams.setLibexecPath(QDir::cleanPath(QCoreApplication::applicationDirPath() + + QLatin1String("/" QBS_RELATIVE_LIBEXEC_PATH))); + setupParams.setTopLevelProfile(profileName()); + setupParams.setConfigurationName(QStringLiteral("default")); + setupParams.setSettingsDirectory(settings()->baseDirectory()); + return setupParams; +} + +void TestApi::references() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("references/invalid1.qbs"); + const QString projectDir = QDir::cleanPath(m_workingDataDir + "/references"); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY(job->error().hasError()); + QString errorString = job->error().toString(); + QVERIFY2(errorString.contains("does not contain"), qPrintable(errorString)); + + setupParams.setProjectFilePath(projectDir + QLatin1String("/invalid2.qbs")); + job.reset(qbs::Project().setupProject(setupParams, m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY(job->error().hasError()); + errorString = job->error().toString(); + QVERIFY2(errorString.contains("contains more than one"), qPrintable(errorString)); + + setupParams.setProjectFilePath(projectDir + QLatin1String("/valid.qbs")); + job.reset(qbs::Project().setupProject(setupParams, m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + const qbs::ProjectData topLevelProject = job->project().projectData(); + QCOMPARE(topLevelProject.subProjects().size(), 1); + const QString subProjectFileName + = QFileInfo(topLevelProject.subProjects().front().location().filePath()).fileName(); + QCOMPARE(subProjectFileName, QString("p.qbs")); +} + +void TestApi::relaxedModeRecovery() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("relaxed-mode-recovery"); + setupParams.setProductErrorMode(qbs::ErrorHandlingMode::Relaxed); + setupParams.setPropertyCheckingMode(qbs::ErrorHandlingMode::Relaxed); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + if (m_logSink->warnings.size() != 4) { + const auto errors = m_logSink->warnings; + for (const qbs::ErrorInfo &error : errors) + qDebug() << error.toString(); + } + QCOMPARE(m_logSink->warnings.size(), 4); + + const auto errors = m_logSink->warnings; + for (const qbs::ErrorInfo &error : errors) { + QVERIFY2(!error.toString().contains("ASSERT") + && (error.toString().contains("Dependency 'blubb' not found") + || error.toString().contains("Product 'p1' had errors and was disabled") + || error.toString().contains("Product 'p2' had errors and was disabled")), + qPrintable(error.toString())); + } +} + +void TestApi::renameProduct() +{ + // Initial run. + qbs::ErrorInfo errorInfo = doBuildProject("rename-product/rename.qbs"); + VERIFY_NO_ERROR(errorInfo); + + // Rename lib and adapt Depends item. + WAIT_FOR_NEW_TIMESTAMP(); + QFile f("rename.qbs"); + QVERIFY(f.open(QIODevice::ReadWrite)); + QByteArray contents = f.readAll(); + contents.replace("TheLib", "thelib"); + f.resize(0); + f.write(contents); + f.close(); + errorInfo = doBuildProject("rename-product/rename.qbs"); + VERIFY_NO_ERROR(errorInfo); + + // Rename lib and don't adapt Depends item. + WAIT_FOR_NEW_TIMESTAMP(); + QVERIFY(f.open(QIODevice::ReadWrite)); + contents = f.readAll(); + const int libNameIndex = contents.lastIndexOf("thelib"); + QVERIFY(libNameIndex != -1); + contents.replace(libNameIndex, 6, "TheLib"); + f.resize(0); + f.write(contents); + f.close(); + errorInfo = doBuildProject("rename-product/rename.qbs"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("Dependency 'thelib' not found"), + qPrintable(errorInfo.toString())); +} + +void TestApi::renameTargetArtifact() +{ + // Initial run. + BuildDescriptionReceiver receiver; + qbs::ErrorInfo errorInfo = doBuildProject("rename-target-artifact/rename.qbs", &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY2(receiver.descriptions.contains("compiling"), qPrintable(receiver.descriptions)); + QCOMPARE(receiver.descriptions.count("linking"), 2); + receiver.descriptions.clear(); + + // Rename library file name. + WAIT_FOR_NEW_TIMESTAMP(); + QFile f("rename.qbs"); + QVERIFY(f.open(QIODevice::ReadWrite)); + QByteArray contents = f.readAll(); + contents.replace("the_lib", "TheLib"); + f.resize(0); + f.write(contents); + f.close(); + errorInfo = doBuildProject("rename-target-artifact/rename.qbs", &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY2(!receiver.descriptions.contains("compiling"), qPrintable(receiver.descriptions)); + QCOMPARE(receiver.descriptions.count("linking"), 2); +} + +void TestApi::removeFileDependency() +{ + qbs::ErrorInfo errorInfo = doBuildProject("remove-file-dependency/removeFileDependency.qbs"); + VERIFY_NO_ERROR(errorInfo); + + QFile::remove("someheader.h"); + ProcessResultReceiver receiver; + errorInfo = doBuildProject("remove-file-dependency/removeFileDependency.qbs", 0, &receiver); + QVERIFY(errorInfo.hasError()); + QVERIFY2(receiver.output.contains("someheader.h"), qPrintable(receiver.output)); +} + +void TestApi::resolveProject() +{ + QFETCH(QString, projectSubDir); + QFETCH(QString, productFileName); + + const qbs::SetupProjectParameters params = defaultSetupParameters(projectSubDir); + removeBuildDir(params); + const std::unique_ptr setupJob(qbs::Project().setupProject(params, + m_logSink, 0)); + waitForFinished(setupJob.get()); + VERIFY_NO_ERROR(setupJob->error()); + QVERIFY2(!QFile::exists(productFileName), qPrintable(productFileName)); + QVERIFY(regularFileExists(relativeBuildGraphFilePath())); +} + +void TestApi::resolveProject_data() +{ + return buildProject_data(); +} + +void TestApi::resolveProjectDryRun() +{ + QFETCH(QString, projectSubDir); + QFETCH(QString, productFileName); + + qbs::SetupProjectParameters params = defaultSetupParameters(projectSubDir); + params.setDryRun(true); + removeBuildDir(params); + const std::unique_ptr setupJob(qbs::Project().setupProject(params, + m_logSink, 0)); + waitForFinished(setupJob.get()); + VERIFY_NO_ERROR(setupJob->error()); + QVERIFY2(!QFile::exists(productFileName), qPrintable(productFileName)); + QVERIFY(!regularFileExists(relativeBuildGraphFilePath())); +} + +void TestApi::resolveProjectDryRun_data() +{ + return resolveProject_data(); +} + +void TestApi::restoredWarnings() +{ + qbs::SetupProjectParameters setupParams = defaultSetupParameters("restored-warnings"); + setupParams.setPropertyCheckingMode(qbs::ErrorHandlingMode::Relaxed); + setupParams.setProductErrorMode(qbs::ErrorHandlingMode::Relaxed); + + // Initial resolving: Errors are new. + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + job.reset(nullptr); + QCOMPARE(toSet(m_logSink->warnings).size(), 2); + const auto beforeErrors = m_logSink->warnings; + for (const qbs::ErrorInfo &e : beforeErrors) { + const QString msg = e.toString(); + QVERIFY2(msg.contains("Superfluous version") + || msg.contains("Property 'blubb' is not declared"), + qPrintable(msg)); + } + m_logSink->warnings.clear(); + + // Re-resolving with no changes: Errors come from the stored build graph. + job.reset(qbs::Project().setupProject(setupParams, m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + job.reset(nullptr); + QCOMPARE(toSet(m_logSink->warnings).size(), 2); + m_logSink->warnings.clear(); + + // Re-resolving with changes: Errors come from the re-resolving, stored ones must be suppressed. + QVariantMap overridenValues; + overridenValues.insert("products.theProduct.moreFiles", true); + setupParams.setOverriddenValues(overridenValues); + job.reset(qbs::Project().setupProject(setupParams, m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + job.reset(nullptr); + QCOMPARE(toSet(m_logSink->warnings).size(), 3); // One more for the additional group + const auto afterErrors = m_logSink->warnings; + for (const qbs::ErrorInfo &e : afterErrors) { + const QString msg = e.toString(); + QVERIFY2(msg.contains("Superfluous version") + || msg.contains("Property 'blubb' is not declared") + || msg.contains("blubb.cpp' does not exist"), + qPrintable(msg)); + } + m_logSink->warnings.clear(); +} + +void TestApi::ruleConflict() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("rule-conflict"); + QVERIFY(errorInfo.hasError()); + const QString errorString = errorInfo.toString(); + QVERIFY2(errorString.contains("conflict") && errorString.contains("pch1.h") + && errorString.contains("pch2.h"), qPrintable(errorString)); +} + +void TestApi::runEnvForDisabledProduct() +{ + const qbs::SetupProjectParameters params + = defaultSetupParameters("run-disabled-product/run-disabled-product.qbs"); + const std::unique_ptr setupJob(qbs::Project().setupProject(params, + m_logSink, 0)); + QVERIFY(waitForFinished(setupJob.get())); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + const qbs::Project project = setupJob->project(); + const std::unique_ptr buildJob(project.buildAllProducts(qbs::BuildOptions())); + QVERIFY(waitForFinished(buildJob.get())); + QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString())); + const qbs::ProjectData projectData = project.projectData(); + const QList products = projectData.products(); + QCOMPARE(products.size(), 1); + const qbs::ProductData product = products.front(); + qbs::RunEnvironment runEnv = project.getRunEnvironment( + product, qbs::InstallOptions(), QProcessEnvironment(), QStringList(), + settings().get()); + qbs::ErrorInfo runError; + const QProcessEnvironment env = runEnv.runEnvironment(&runError); + QVERIFY2(runError.toString().contains("Cannot run disabled product 'app'"), + qPrintable(runError.toString())); + runError.clear(); + runEnv.runTarget(QString(), QStringList(), true, &runError); + QVERIFY2(runError.toString().contains("Cannot run disabled product 'app'"), + qPrintable(runError.toString())); +} + +void TestApi::softDependency() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("soft-dependency"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::sourceFileInBuildDir() +{ + VERIFY_NO_ERROR(doBuildProject("source-file-in-build-dir")); + qbs::SetupProjectParameters setupParams = defaultSetupParameters("source-file-in-build-dir"); + const QString generatedFile = relativeProductBuildDir("theProduct") + "/generated.cpp"; + QVERIFY2(regularFileExists(generatedFile), qPrintable(generatedFile)); + std::unique_ptr job(qbs::Project().setupProject(setupParams, + m_logSink, 0)); + waitForFinished(job.get()); + QVERIFY2(!job->error().hasError(), qPrintable(job->error().toString())); + const qbs::ProjectData projectData = job->project().projectData(); + QCOMPARE(projectData.allProducts().size(), 1); + const qbs::ProductData product = projectData.allProducts().front(); + QCOMPARE(product.profile(), profileName()); + const qbs::GroupData group = findGroup(product, "the group"); + QVERIFY(group.isValid()); + QCOMPARE(group.allFilePaths().size(), 1); +} + +void TestApi::subProjects() +{ + const qbs::SetupProjectParameters params + = defaultSetupParameters("subprojects/toplevelproject.qbs"); + removeBuildDir(params); + + // Check all three types of subproject creation, plus property overrides. + qbs::ErrorInfo errorInfo = doBuildProject("subprojects/toplevelproject.qbs"); + VERIFY_NO_ERROR(errorInfo); + + // Disabling both the project with the dependency and the one with the dependent + // should not cause an error. + WAIT_FOR_NEW_TIMESTAMP(); + QFile f(params.projectFilePath()); + QVERIFY(f.open(QIODevice::ReadWrite)); + QByteArray contents = f.readAll(); + contents.replace("condition: true", "condition: false"); + f.resize(0); + f.write(contents); + f.close(); + f.setFileName(params.buildRoot() + "/subproject2/subproject2.qbs"); + QVERIFY(f.open(QIODevice::ReadWrite)); + contents = f.readAll(); + contents.replace("condition: qbs.targetOS.length > 0", "condition: false"); + f.resize(0); + f.write(contents); + f.close(); + errorInfo = doBuildProject("subprojects/toplevelproject.qbs"); + VERIFY_NO_ERROR(errorInfo); + + // Disabling the project with the dependency only is an error. + // This tests also whether changes in sub-projects are detected. + WAIT_FOR_NEW_TIMESTAMP(); + f.setFileName(params.projectFilePath()); + QVERIFY(f.open(QIODevice::ReadWrite)); + contents = f.readAll(); + contents.replace("condition: false", "condition: true"); + f.resize(0); + f.write(contents); + f.close(); + errorInfo = doBuildProject("subprojects/toplevelproject.qbs"); + QVERIFY(errorInfo.hasError()); + QVERIFY2(errorInfo.toString().contains("Dependency 'testLib' not found"), + qPrintable(errorInfo.toString())); +} + +void TestApi::targetArtifactStatus_data() +{ + QTest::addColumn("enableTagging"); + QTest::newRow("tagging off") << false; + QTest::newRow("tagging on") << true; + QTest::newRow("tagging off again") << false; +} + +void TestApi::targetArtifactStatus() +{ + QFETCH(bool, enableTagging); + qbs::SetupProjectParameters params + = defaultSetupParameters("target-artifact-status/target-artifact-status.qbs"); + params.setOverriddenValues({std::make_pair("products.p.enableTagging", enableTagging)}); + const std::unique_ptr setupJob(qbs::Project().setupProject(params, + m_logSink, 0)); + waitForFinished(setupJob.get()); + VERIFY_NO_ERROR(setupJob->error()); + const qbs::Project project = setupJob->project(); + QVERIFY(project.isValid()); + const std::unique_ptr buildJob(project.buildAllProducts(qbs::BuildOptions())); + QVERIFY(waitForFinished(buildJob.get())); + VERIFY_NO_ERROR(buildJob->error()); + const qbs::ProjectData projectData = project.projectData(); + const QList products = projectData.products(); + QCOMPARE(products.size(), 1); + const qbs::ProductData product = products.front(); + QCOMPARE(product.targetArtifacts().size(), enableTagging ? 2 : 1); +} + +void TestApi::timeout() +{ + QFETCH(QString, projectDirName); + const auto setupParams = defaultSetupParameters(projectDirName + "/timeout.qbs"); + std::unique_ptr setupJob{ + qbs::Project().setupProject(setupParams, m_logSink, nullptr)}; + waitForFinished(setupJob.get()); + if (m_logSink->output.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + auto project = setupJob->project(); + const auto products = project.projectData().products(); + QList helperProducts; + qbs::ProductData productUnderTest; + for (const auto &product : products) { + if (!product.type().contains(QLatin1String("product-under-test"))) + helperProducts.append(product); + else + productUnderTest = product; + } + const std::unique_ptr buildHelpersJob{ + project.buildSomeProducts(helperProducts, qbs::BuildOptions())}; + QVERIFY(waitForFinished(buildHelpersJob.get(), testTimeoutInMsecs())); + if (buildHelpersJob->error().hasError()) { + qDebug().noquote() << buildHelpersJob->error().toString(); + QFAIL("Could not build helper products"); + } + + const std::unique_ptr buildJob(project.buildOneProduct(productUnderTest, + qbs::BuildOptions())); + QVERIFY(waitForFinished(buildJob.get(), testTimeoutInMsecs())); + QVERIFY(buildJob->error().hasError()); + const auto errorString = buildJob->error().toString(); + QVERIFY2(errorString.contains("cancel"), qPrintable(errorString)); + QVERIFY(errorString.contains("timeout")); +} + +void TestApi::timeout_data() +{ + QTest::addColumn("projectDirName"); + QTest::newRow("JS Command") << QString("timeout-js"); + QTest::newRow("Process Command") << QString("timeout-process"); +} + +void TestApi::toolInModule() +{ + QVariantMap overrides({std::make_pair("qbs.installRoot", m_workingDataDir + + "/tool-in-module/use-outside-project")}); + + qbs::SetupProjectParameters params + = defaultSetupParameters("tool-in-module/use-within-project/use-within-project.qbs"); + params.setOverriddenValues(overrides); + std::unique_ptr setupJob( + qbs::Project().setupProject(params, m_logSink, 0)); + QVERIFY(waitForFinished(setupJob.get())); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + if (m_logSink->output.contains("Skip this test")) + QSKIP("Skip this test"); + + std::unique_ptr buildJob(setupJob->project() + .buildAllProducts(qbs::BuildOptions())); + + QVERIFY(waitForFinished(buildJob.get())); + QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString())); + + const QString toolOutput = relativeProductBuildDir("user-in-project") + "/tool-output.txt"; + QVERIFY2(QFile::exists(toolOutput), qPrintable(toolOutput)); + + params = defaultSetupParameters("tool-in-module/use-outside-project/use-outside-project.qbs"); + setupJob.reset(qbs::Project().setupProject(params, m_logSink, 0)); + QVERIFY(waitForFinished(setupJob.get())); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + const auto project = setupJob->project(); + const auto projectData = project.projectData(); + const auto products = projectData.products(); + QCOMPARE(products.size(), 1); + const qbs::ProductData product = products.front(); + const auto groups = product.groups(); + for (const qbs::GroupData &group : groups) + QVERIFY(group.name() != "thetool binary"); + buildJob.reset(setupJob->project().buildAllProducts(qbs::BuildOptions())); + QVERIFY(waitForFinished(buildJob.get())); + QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString())); + const QString toolOutput2 = relativeProductBuildDir("user-outside-project") + + "/tool-output.txt"; + QVERIFY2(QFile::exists(toolOutput2), qPrintable(toolOutput2)); +} + +void TestApi::trackAddQObjectHeader() +{ + const qbs::SetupProjectParameters params + = defaultSetupParameters("missing-qobject-header/missingheader.qbs"); + QFile qbsFile(params.projectFilePath()); + QVERIFY(qbsFile.open(QIODevice::WriteOnly | QIODevice::Truncate)); + qbsFile.write("import qbs.base 1.0\nCppApplication {\n Depends { name: 'Qt.core' }\n" + " files: ['main.cpp', 'myobject.cpp']\n}"); + qbsFile.close(); + ProcessResultReceiver receiver; + qbs::ErrorInfo errorInfo + = doBuildProject("missing-qobject-header/missingheader.qbs", 0, &receiver); + QVERIFY(errorInfo.hasError()); + QVERIFY2(isAboutUndefinedSymbols(receiver.output), qPrintable(receiver.output)); + + WAIT_FOR_NEW_TIMESTAMP(); + QVERIFY(qbsFile.open(QIODevice::WriteOnly | QIODevice::Truncate)); + qbsFile.write("import qbs.base 1.0\nCppApplication {\n Depends { name: 'Qt.core' }\n" + " files: ['main.cpp', 'myobject.cpp','myobject.h']\n}"); + qbsFile.close(); + errorInfo = doBuildProject("missing-qobject-header/missingheader.qbs"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::trackRemoveQObjectHeader() +{ + const qbs::SetupProjectParameters params + = defaultSetupParameters("missing-qobject-header/missingheader.qbs"); + removeBuildDir(params); + QFile qbsFile(params.projectFilePath()); + QVERIFY(qbsFile.open(QIODevice::WriteOnly | QIODevice::Truncate)); + qbsFile.write("import qbs.base 1.0\nCppApplication {\n Depends { name: 'Qt.core' }\n" + " files: ['main.cpp', 'myobject.cpp','myobject.h']\n}"); + qbsFile.close(); + qbs::ErrorInfo errorInfo = doBuildProject("missing-qobject-header/missingheader.qbs"); + VERIFY_NO_ERROR(errorInfo); + + WAIT_FOR_NEW_TIMESTAMP(); + QVERIFY(qbsFile.open(QIODevice::WriteOnly | QIODevice::Truncate)); + qbsFile.write("import qbs.base 1.0\nCppApplication {\n Depends { name: 'Qt.core' }\n" + " files: ['main.cpp', 'myobject.cpp']\n}"); + qbsFile.close(); + ProcessResultReceiver receiver; + errorInfo = doBuildProject("missing-qobject-header/missingheader.qbs", 0, &receiver); + QVERIFY(errorInfo.hasError()); + QVERIFY2(isAboutUndefinedSymbols(receiver.output), qPrintable(receiver.output)); +} + +void TestApi::transformerData() +{ + const qbs::SetupProjectParameters params + = defaultSetupParameters("transformer-data/transformer-data.qbs"); + const std::unique_ptr setupJob(qbs::Project().setupProject(params, + m_logSink, 0)); + QVERIFY(waitForFinished(setupJob.get())); + QVERIFY2(!setupJob->error().hasError(), qPrintable(setupJob->error().toString())); + const qbs::Project project = setupJob->project(); + const std::unique_ptr buildJob(project.buildAllProducts(qbs::BuildOptions())); + QVERIFY(waitForFinished(buildJob.get())); + QVERIFY2(!buildJob->error().hasError(), qPrintable(buildJob->error().toString())); + qbs::ErrorInfo error; + const qbs::ProjectTransformerData projectTData = project.transformerData(&error); + QVERIFY2(!error.hasError(), qPrintable(error.toString())); + QCOMPARE(projectTData.size(), 1); + const qbs::ProductTransformerData productTData = projectTData.first().second; + QCOMPARE(productTData.size(), 2); + bool firstTransformerFound = false; + bool secondTransformerFound = false; + for (const qbs::TransformerData &tData : productTData) { + if (tData.inputs().empty()) { + firstTransformerFound = true; + QCOMPARE(tData.outputs().size(), 1); + QCOMPARE(QFileInfo(tData.outputs().first().filePath()).fileName(), + QString("artifact1")); + QCOMPARE(tData.commands().size(), 1); + QCOMPARE(tData.commands().first().type(), qbs::RuleCommand::JavaScriptCommandType); + } else { + secondTransformerFound = true; + QCOMPARE(tData.inputs().size(), 1); + QCOMPARE(QFileInfo(tData.inputs().first().filePath()).fileName(), QString("artifact1")); + QCOMPARE(tData.outputs().size(), 1); + QCOMPARE(QFileInfo(tData.outputs().first().filePath()).fileName(), + QString("artifact2")); + QCOMPARE(tData.commands().size(), 1); + QCOMPARE(tData.commands().first().type(), qbs::RuleCommand::JavaScriptCommandType); + } + } + QVERIFY(firstTransformerFound); + QVERIFY(secondTransformerFound); +} + +void TestApi::transformers() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("transformers/transformers.qbs"); + VERIFY_NO_ERROR(errorInfo); +} + +void TestApi::typeChange() +{ + BuildDescriptionReceiver receiver; + qbs::ErrorInfo errorInfo = doBuildProject("type-change", &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY2(!receiver.descriptions.contains("compiling"), qPrintable(receiver.descriptions)); + + WAIT_FOR_NEW_TIMESTAMP(); + QFile projectFile("type-change.qbs"); + QVERIFY2(projectFile.open(QIODevice::ReadWrite), qPrintable(projectFile.errorString())); + QByteArray content = projectFile.readAll(); + content.replace("//", ""); + projectFile.resize(0); + projectFile.write(content); + projectFile.close(); + errorInfo = doBuildProject("type-change", &receiver); + VERIFY_NO_ERROR(errorInfo); + QVERIFY2(receiver.descriptions.contains("compiling"), qPrintable(receiver.descriptions)); +} + +void TestApi::uic() +{ + const qbs::ErrorInfo errorInfo = doBuildProject("uic"); + VERIFY_NO_ERROR(errorInfo); +} + + +qbs::ErrorInfo TestApi::doBuildProject( + const QString &projectFilePath, BuildDescriptionReceiver *buildDescriptionReceiver, + ProcessResultReceiver *procResultReceiver, TaskReceiver *taskReceiver, + const qbs::BuildOptions &options, const QVariantMap &overriddenValues) +{ + qbs::SetupProjectParameters params = defaultSetupParameters(projectFilePath); + params.setOverriddenValues(overriddenValues); + params.setDryRun(options.dryRun()); + const std::unique_ptr setupJob(qbs::Project().setupProject(params, + m_logSink, 0)); + if (taskReceiver) { + connect(setupJob.get(), &qbs::AbstractJob::taskStarted, + taskReceiver, &TaskReceiver::handleTaskStart); + } + waitForFinished(setupJob.get()); + if (setupJob->error().hasError()) + return setupJob->error(); + const std::unique_ptr buildJob(setupJob->project().buildAllProducts(options)); + if (buildDescriptionReceiver) { + connect(buildJob.get(), &qbs::BuildJob::reportCommandDescription, + buildDescriptionReceiver, &BuildDescriptionReceiver::handleDescription); + } + if (procResultReceiver) { + connect(buildJob.get(), &qbs::BuildJob::reportProcessResult, + procResultReceiver, &ProcessResultReceiver::handleProcessResult); + } + waitForFinished(buildJob.get()); + return buildJob->error(); +} + +QTEST_MAIN(TestApi) + +#include "tst_api.moc" diff --git a/tests/auto/api/tst_api.h b/tests/auto/api/tst_api.h new file mode 100644 index 00000000..d6514e17 --- /dev/null +++ b/tests/auto/api/tst_api.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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$ +** +****************************************************************************/ + +#ifndef QBS_TST_API_H +#define QBS_TST_API_H + +#include + +#include +#include + +namespace qbs { +class ErrorInfo; +class SetupProjectParameters; +} + +class BuildDescriptionReceiver; +class LogSink; +class ProcessResultReceiver; +class TaskReceiver; + +class TestApi : public QObject +{ + Q_OBJECT + +public: + TestApi(); + ~TestApi() override; + +private slots: + void initTestCase(); + void init(); + + void addQObjectMacroToCppFile(); + void addedFilePersistent(); + void baseProperties(); + void buildErrorCodeLocation(); + void buildGraphInfo(); + void buildGraphLocking(); + void buildProject(); + void buildProject_data(); + void buildProjectDryRun(); + void buildProjectDryRun_data(); + void buildSingleFile(); + void canonicalToolchainList(); +#ifdef QBS_ENABLE_PROJECT_FILE_UPDATES + void changeContent(); +#endif + void changeDependentLib(); + void checkOutputs(); + void checkOutputs_data(); + void commandExtraction(); + void dependencyOnMultiplexedType(); + void disabledInstallGroup(); + void disabledProduct(); + void disabledProject(); + void disappearedWildcardFile(); + void duplicateProductNames(); + void duplicateProductNames_data(); + void emptyFileTagList(); + void emptySubmodulesList(); + void enableAndDisableProduct(); + void errorInSetupRunEnvironment(); + void excludedInputs(); + void explicitlyDependsOn(); + void exportSimple(); + void exportWithRecursiveDepends(); + void fallbackGcc(); + void fileTagger(); + void fileTagsFilterOverride(); + void generatedFilesList(); + void infiniteLoopBuilding(); + void infiniteLoopBuilding_data(); + void infiniteLoopResolving(); + void inheritQbsSearchPaths(); + void installableFiles(); + void isRunnable(); + void linkDynamicLibs(); + void linkDynamicAndStaticLibs(); + void linkStaticAndDynamicLibs(); + void listBuildSystemFiles(); + void localProfiles(); + void localProfiles_data(); + void missingSourceFile(); + void mocCppIncluded(); + void multiArch(); + void multiplexing(); + void newOutputArtifactInDependency(); + void newPatternMatch(); + void nonexistingProjectPropertyFromCommandLine(); + void nonexistingProjectPropertyFromProduct(); + void objC(); + void projectDataAfterProductInvalidation(); + void processResult(); + void processResult_data(); + void projectInvalidation(); + void projectLocking(); + void projectPropertiesByName(); + void projectWithPropertiesItem(); + void projectWithProbeAndProfileItem(); + void propertiesBlocks(); + void rc(); + void referencedFileErrors(); + void referencedFileErrors_data(); + void references(); + void relaxedModeRecovery(); + void removeFileDependency(); + void renameProduct(); + void renameTargetArtifact(); + void renamedQbsSource(); + void resolveProject(); + void resolveProject_data(); + void resolveProjectDryRun(); + void resolveProjectDryRun_data(); + void restoredWarnings(); + void ruleConflict(); + void runEnvForDisabledProduct(); + void softDependency(); + void sourceFileInBuildDir(); + void subProjects(); + void targetArtifactStatus_data(); + void targetArtifactStatus(); + void timeout(); + void timeout_data(); + void toolInModule(); + void trackAddQObjectHeader(); + void trackRemoveQObjectHeader(); + void transformerData(); + void transformers(); + void typeChange(); + void uic(); + +private: + qbs::SetupProjectParameters defaultSetupParameters(const QString &projectFileOrDir) const; + qbs::ErrorInfo doBuildProject(const QString &projectFilePath, + BuildDescriptionReceiver *buildDescriptionReceiver = 0, + ProcessResultReceiver *procResultReceiver = 0, + TaskReceiver *taskReceiver = 0, + const qbs::BuildOptions &options = qbs::BuildOptions(), + const QVariantMap &overriddenValues = QVariantMap()); + + LogSink * const m_logSink; + const QString m_sourceDataDir; + const QString m_workingDataDir; +}; + +#endif // Include guard. diff --git a/tests/auto/auto.pri b/tests/auto/auto.pri new file mode 100644 index 00000000..fd8afad5 --- /dev/null +++ b/tests/auto/auto.pri @@ -0,0 +1,18 @@ +TEMPLATE = app +DESTDIR = ../../../bin +DEFINES += SRCDIR=\\\"$$_PRO_FILE_PWD_\\\" +qbs_test_suite_name = $$replace(_PRO_FILE_, ^.*/([^/.]+)\\.pro$, \\1) +qbs_test_suite_name = $$upper($$replace(qbs_test_suite_name, -, _)) +DEFINES += QBS_TEST_SUITE_NAME=\\\"$${qbs_test_suite_name}\\\" +INCLUDEPATH += $$PWD/../../src $$PWD/../../src/app/shared + +QT = core testlib +CONFIG += testcase console +CONFIG -= app_bundle +CONFIG += c++14 +target.CONFIG += no_default_install + +dev_lib_frameworks=$$QMAKE_XCODE_DEVELOPER_PATH/Library/Frameworks +exists($$dev_lib_frameworks): LIBS += -F$$dev_lib_frameworks + +include(../../src/lib/corelib/use_corelib.pri) diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro new file mode 100644 index 00000000..af06ea13 --- /dev/null +++ b/tests/auto/auto.pro @@ -0,0 +1,21 @@ +TEMPLATE=subdirs + +qbs_enable_unit_tests { + SUBDIRS += \ + buildgraph \ + language \ + tools \ +} + +SUBDIRS += \ + cmdlineparser \ + blackbox/blackbox.pro \ + blackbox/blackbox-android.pro \ + blackbox/blackbox-apple.pro \ + blackbox/blackbox-baremetal.pro \ + blackbox/blackbox-clangdb.pro \ + blackbox/blackbox-examples.pro \ + blackbox/blackbox-java.pro \ + blackbox/blackbox-joblimits.pro \ + blackbox/blackbox-qt.pro \ + api diff --git a/tests/auto/auto.qbs b/tests/auto/auto.qbs new file mode 100644 index 00000000..8dd301f6 --- /dev/null +++ b/tests/auto/auto.qbs @@ -0,0 +1,21 @@ +import qbs + +Project { + name: "Autotests" + references: [ + "api/api.qbs", + "blackbox/blackbox.qbs", + "blackbox/blackbox-android.qbs", + "blackbox/blackbox-apple.qbs", + "blackbox/blackbox-baremetal.qbs", + "blackbox/blackbox-clangdb.qbs", + "blackbox/blackbox-examples.qbs", + "blackbox/blackbox-java.qbs", + "blackbox/blackbox-joblimits.qbs", + "blackbox/blackbox-qt.qbs", + "buildgraph/buildgraph.qbs", + "cmdlineparser/cmdlineparser.qbs", + "language/language.qbs", + "tools/tools.qbs", + ] +} diff --git a/tests/auto/blackbox/blackbox-android.pro b/tests/auto/blackbox/blackbox-android.pro new file mode 100644 index 00000000..7aca99e8 --- /dev/null +++ b/tests/auto/blackbox/blackbox-android.pro @@ -0,0 +1,21 @@ +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 + +DISTFILES += \ + testdata/texttemplate/expected-output-one.txt diff --git a/tests/auto/blackbox/blackbox-android.qbs b/tests/auto/blackbox/blackbox-android.qbs new file mode 100644 index 00000000..ca7d429e --- /dev/null +++ b/tests/auto/blackbox/blackbox-android.qbs @@ -0,0 +1,21 @@ +import qbs.Utilities + +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", + ] + cpp.defines: base.concat(["SRCDIR=" + Utilities.cStringQuote(path)]) +} diff --git a/tests/auto/blackbox/blackbox-apple.pro b/tests/auto/blackbox/blackbox-apple.pro new file mode 100644 index 00000000..1a009e22 --- /dev/null +++ b/tests/auto/blackbox/blackbox-apple.pro @@ -0,0 +1,20 @@ +TARGET = tst_blackbox-apple + +HEADERS = tst_blackboxapple.h tst_blackboxbase.h +SOURCES = tst_blackboxapple.cpp tst_blackboxbase.cpp +OBJECTS_DIR = apple +MOC_DIR = $${OBJECTS_DIR}-moc + +include(../auto.pri) + +QT += xml + +DATA_DIRS = testdata-apple ../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-apple.qbs b/tests/auto/blackbox/blackbox-apple.qbs new file mode 100644 index 00000000..6cc946d1 --- /dev/null +++ b/tests/auto/blackbox/blackbox-apple.qbs @@ -0,0 +1,22 @@ +import qbs.Utilities + +QbsAutotest { + testName: "blackbox-apple" + Depends { name: "Qt.xml" } + Depends { name: "qbs_app" } + Depends { name: "qbs-setup-toolchains" } + Group { + name: "testdata" + prefix: "testdata-apple/" + files: ["**/*"] + fileTags: [] + } + files: [ + "../shared.h", + "tst_blackboxapple.cpp", + "tst_blackboxapple.h", + "tst_blackboxbase.cpp", + "tst_blackboxbase.h", + ] + cpp.defines: base.concat(["SRCDIR=" + Utilities.cStringQuote(path)]) +} diff --git a/tests/auto/blackbox/blackbox-baremetal.pro b/tests/auto/blackbox/blackbox-baremetal.pro new file mode 100644 index 00000000..32789346 --- /dev/null +++ b/tests/auto/blackbox/blackbox-baremetal.pro @@ -0,0 +1,18 @@ +TARGET = tst_blackbox-baremetal + +HEADERS = tst_blackboxbaremetal.h tst_blackboxbase.h +SOURCES = tst_blackboxbaremetal.cpp tst_blackboxbase.cpp +OBJECTS_DIR = baremetal +MOC_DIR = $${OBJECTS_DIR}-moc + +include(../auto.pri) + +DATA_DIRS = testdata-baremetal ../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-baremetal.qbs b/tests/auto/blackbox/blackbox-baremetal.qbs new file mode 100644 index 00000000..18ae588f --- /dev/null +++ b/tests/auto/blackbox/blackbox-baremetal.qbs @@ -0,0 +1,21 @@ +import qbs.Utilities + +QbsAutotest { + testName: "blackbox-baremetal" + Depends { name: "qbs_app" } + Depends { name: "qbs-setup-toolchains" } + Group { + name: "testdata" + prefix: "testdata-baremetal/" + files: ["**/*"] + fileTags: [] + } + files: [ + "../shared.h", + "tst_blackboxbase.cpp", + "tst_blackboxbase.h", + "tst_blackboxbaremetal.cpp", + "tst_blackboxbaremetal.h", + ] + cpp.defines: base.concat(["SRCDIR=" + Utilities.cStringQuote(path)]) +} diff --git a/tests/auto/blackbox/blackbox-clangdb.pro b/tests/auto/blackbox/blackbox-clangdb.pro new file mode 100644 index 00000000..6e407517 --- /dev/null +++ b/tests/auto/blackbox/blackbox-clangdb.pro @@ -0,0 +1,18 @@ +TARGET = tst_blackbox-clangdb + +HEADERS = tst_blackboxbase.h tst_clangdb.h +SOURCES = tst_blackboxbase.cpp tst_clangdb.cpp +OBJECTS_DIR = clangdb +MOC_DIR = $${OBJECTS_DIR}-moc + +include(../auto.pri) + +DATA_DIRS = testdata-clangdb + +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-clangdb.qbs b/tests/auto/blackbox/blackbox-clangdb.qbs new file mode 100644 index 00000000..38118309 --- /dev/null +++ b/tests/auto/blackbox/blackbox-clangdb.qbs @@ -0,0 +1,24 @@ +import qbs.Utilities + +QbsAutotest { + testName: "blackbox-clangdb" + + Depends { name: "qbs_app" } + Depends { name: "qbs-setup-toolchains" } + + Group { + name: "testdata" + prefix: "testdata-clangdb/" + files: ["**/*"] + fileTags: [] + } + + files: [ + "../shared.h", + "tst_blackboxbase.cpp", + "tst_blackboxbase.h", + "tst_clangdb.cpp", + "tst_clangdb.h", + ] + cpp.defines: base.concat(["SRCDIR=" + Utilities.cStringQuote(path)]) +} diff --git a/tests/auto/blackbox/blackbox-examples.pro b/tests/auto/blackbox/blackbox-examples.pro new file mode 100644 index 00000000..70d5b641 --- /dev/null +++ b/tests/auto/blackbox/blackbox-examples.pro @@ -0,0 +1,18 @@ +TARGET = tst_blackbox-examples + +HEADERS = tst_blackboxexamples.h tst_blackboxbase.h +SOURCES = tst_blackboxexamples.cpp tst_blackboxbase.cpp +OBJECTS_DIR = examples +MOC_DIR = $${OBJECTS_DIR}-moc + +include(../auto.pri) + +DATA_DIRS = ../../../examples + +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-examples.qbs b/tests/auto/blackbox/blackbox-examples.qbs new file mode 100644 index 00000000..77d32636 --- /dev/null +++ b/tests/auto/blackbox/blackbox-examples.qbs @@ -0,0 +1,21 @@ +import qbs.Utilities + +QbsAutotest { + testName: "blackbox-examples" + Depends { name: "qbs_app" } + Depends { name: "qbs-setup-toolchains" } + Group { + name: "testdata" + prefix: "../../../examples/" + files: ["**/*"] + fileTags: [] + } + files: [ + "../shared.h", + "tst_blackboxexamples.cpp", + "tst_blackboxexamples.h", + "tst_blackboxbase.cpp", + "tst_blackboxbase.h", + ] + cpp.defines: base.concat(["SRCDIR=" + Utilities.cStringQuote(path)]) +} diff --git a/tests/auto/blackbox/blackbox-java.pro b/tests/auto/blackbox/blackbox-java.pro new file mode 100644 index 00000000..d297d9e0 --- /dev/null +++ b/tests/auto/blackbox/blackbox-java.pro @@ -0,0 +1,18 @@ +TARGET = tst_blackbox-java + +HEADERS = tst_blackboxjava.h tst_blackboxbase.h +SOURCES = tst_blackboxjava.cpp tst_blackboxbase.cpp +OBJECTS_DIR = java +MOC_DIR = $${OBJECTS_DIR}-moc + +include(../auto.pri) + +DATA_DIRS = testdata-java ../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-java.qbs b/tests/auto/blackbox/blackbox-java.qbs new file mode 100644 index 00000000..6c56b3d1 --- /dev/null +++ b/tests/auto/blackbox/blackbox-java.qbs @@ -0,0 +1,21 @@ +import qbs.Utilities + +QbsAutotest { + testName: "blackbox-java" + Depends { name: "qbs_app" } + Depends { name: "qbs-setup-toolchains" } + Group { + name: "testdata" + prefix: "testdata-java/" + files: ["**/*"] + fileTags: [] + } + files: [ + "../shared.h", + "tst_blackboxbase.cpp", + "tst_blackboxbase.h", + "tst_blackboxjava.cpp", + "tst_blackboxjava.h", + ] + cpp.defines: base.concat(["SRCDIR=" + Utilities.cStringQuote(path)]) +} diff --git a/tests/auto/blackbox/blackbox-joblimits.pro b/tests/auto/blackbox/blackbox-joblimits.pro new file mode 100644 index 00000000..85413473 --- /dev/null +++ b/tests/auto/blackbox/blackbox-joblimits.pro @@ -0,0 +1,18 @@ +TARGET = tst_blackbox-joblimits + +HEADERS = tst_blackboxbase.h +SOURCES = tst_blackboxjoblimits.cpp tst_blackboxbase.cpp +OBJECTS_DIR = joblimits +MOC_DIR = $${OBJECTS_DIR}-moc + +include(../auto.pri) + +DATA_DIRS = testdata-joblimits ../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-joblimits.qbs b/tests/auto/blackbox/blackbox-joblimits.qbs new file mode 100644 index 00000000..857e1de7 --- /dev/null +++ b/tests/auto/blackbox/blackbox-joblimits.qbs @@ -0,0 +1,20 @@ +import qbs.Utilities + +QbsAutotest { + testName: "blackbox-joblimits" + Depends { name: "qbs_app" } + Depends { name: "qbs-setup-toolchains" } + Group { + name: "testdata" + prefix: "testdata-joblimits/" + files: ["**/*"] + fileTags: [] + } + files: [ + "../shared.h", + "tst_blackboxbase.cpp", + "tst_blackboxbase.h", + "tst_blackboxjoblimits.cpp", + ] + cpp.defines: base.concat(["SRCDIR=" + Utilities.cStringQuote(path)]) +} diff --git a/tests/auto/blackbox/blackbox-qt.pro b/tests/auto/blackbox/blackbox-qt.pro new file mode 100644 index 00000000..e17a04a7 --- /dev/null +++ b/tests/auto/blackbox/blackbox-qt.pro @@ -0,0 +1,18 @@ +TARGET = tst_blackbox-qt + +HEADERS = tst_blackboxqt.h tst_blackboxbase.h +SOURCES = tst_blackboxqt.cpp tst_blackboxbase.cpp +OBJECTS_DIR = qt +MOC_DIR = $${OBJECTS_DIR}-moc + +include(../auto.pri) + +DATA_DIRS = testdata-qt ../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-qt.qbs b/tests/auto/blackbox/blackbox-qt.qbs new file mode 100644 index 00000000..a25221dc --- /dev/null +++ b/tests/auto/blackbox/blackbox-qt.qbs @@ -0,0 +1,21 @@ +import qbs.Utilities + +QbsAutotest { + testName: "blackbox-qt" + Depends { name: "qbs_app" } + Depends { name: "qbs-setup-toolchains" } + Group { + name: "testdata" + prefix: "testdata-qt/" + files: ["**/*"] + fileTags: [] + } + files: [ + "../shared.h", + "tst_blackboxbase.cpp", + "tst_blackboxbase.h", + "tst_blackboxqt.cpp", + "tst_blackboxqt.h", + ] + cpp.defines: base.concat(["SRCDIR=" + Utilities.cStringQuote(path)]) +} diff --git a/tests/auto/blackbox/blackbox.pro b/tests/auto/blackbox/blackbox.pro new file mode 100644 index 00000000..42848d07 --- /dev/null +++ b/tests/auto/blackbox/blackbox.pro @@ -0,0 +1,21 @@ +TARGET = tst_blackbox + +HEADERS = tst_blackbox.h tst_blackboxbase.h +SOURCES = tst_blackbox.cpp tst_blackboxbase.cpp +OBJECTS_DIR = generic +MOC_DIR = $${OBJECTS_DIR}-moc +qbs_enable_unit_tests:DEFINES += QBS_ENABLE_UNIT_TESTS + +include(../auto.pri) + +QT += xml + +DATA_DIRS = testdata ../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.qbs b/tests/auto/blackbox/blackbox.qbs new file mode 100644 index 00000000..3f0ff959 --- /dev/null +++ b/tests/auto/blackbox/blackbox.qbs @@ -0,0 +1,28 @@ +import qbs.Utilities + +QbsAutotest { + testName: "blackbox" + Depends { name: "qbs_app" } + Depends { name: "qbs-setup-toolchains" } + Group { + name: "find" + prefix: "find/" + files: ["**/*"] + fileTags: [] + } + Group { + name: "testdata" + prefix: "testdata/" + files: ["**/*"] + fileTags: [] + } + files: [ + "../shared.h", + "tst_blackboxbase.cpp", + "tst_blackboxbase.h", + "tst_blackbox.cpp", + "tst_blackbox.h", + ] + cpp.defines: base.concat(["SRCDIR=" + Utilities.cStringQuote(path)]) + .concat(qbsbuildconfig.enableUnitTests ? ["QBS_ENABLE_UNIT_TESTS"] : []) +} diff --git a/tests/auto/blackbox/find/find-android.qbs b/tests/auto/blackbox/find/find-android.qbs new file mode 100644 index 00000000..de5c78d1 --- /dev/null +++ b/tests/auto/blackbox/find/find-android.qbs @@ -0,0 +1,90 @@ +import qbs.TextFile + +Product { + property string packageName: "" + qbs.targetPlatform: "android" + multiplexByQbsProperties: ["architectures"] + + Properties { + condition: qbs.architectures && qbs.architectures.length > 1 + aggregate: true + multiplexedType: "json_arch" + } + + Depends { name: "Android.sdk"; required: false } + Depends { name: "Android.ndk"; required: false } + type: ["json"] + + Rule { + multiplex: true + property stringList inputTags: "json_arch" + inputsFromDependencies: inputTags + inputs: product.aggregate ? [] : inputTags + Artifact { + filePath: ["android.json"] + fileTags: ["json"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = output.filePath; + cmd.sourceCode = function() { + var tools = {}; + + for (var i in inputs["json_arch"]) { + var tf = new TextFile(inputs["json_arch"][i].filePath, TextFile.ReadOnly); + var json = JSON.parse(tf.readAll()); + tools["ndk"] = json["ndk"]; + tools["ndk-samples"] = json["ndk-samples"]; + tf.close(); + } + + if (product.moduleProperty("Android.sdk", "present")) { + tools["sdk"] = product.moduleProperty("Android.sdk", "sdkDir"); + tools["sdk-build-tools-dx"] = product.Android.sdk.dxFilePath; + } + + if (product.java && product.java.present) + tools["jar"] = product.java.jarFilePath; + + var tf; + try { + tf = new TextFile(output.filePath, TextFile.WriteOnly); + tf.writeLine(JSON.stringify(tools, undefined, 4)); + } finally { + if (tf) + tf.close(); + } + }; + return cmd; + } + } + Rule { + multiplex: true + Artifact { + filePath: ["android_arch.json"] + fileTags: ["json_arch"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = output.filePath; + cmd.sourceCode = function() { + var tools = {}; + if (product.moduleProperty("Android.ndk", "present")) { + tools["ndk"] = product.moduleProperty("Android.ndk", "ndkDir"); + tools["ndk-samples"] = product.Android.ndk.ndkSamplesDir; + } + + var tf; + try { + tf = new TextFile(output.filePath, TextFile.WriteOnly); + tf.writeLine(JSON.stringify(tools, undefined, 4)); + } finally { + if (tf) + tf.close(); + } + }; + return cmd; + } + } +} + diff --git a/tests/auto/blackbox/find/find-jdk.qbs b/tests/auto/blackbox/find/find-jdk.qbs new file mode 100644 index 00000000..81d84fa2 --- /dev/null +++ b/tests/auto/blackbox/find/find-jdk.qbs @@ -0,0 +1,35 @@ +import qbs.TextFile + +Product { + Depends { name: "java"; required: false } + type: ["json"] + Rule { + multiplex: true + Artifact { + filePath: ["jdk.json"] + fileTags: ["json"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = output.filePath; + cmd.sourceCode = function() { + var tools = {}; + if (product.moduleProperty("java", "present")) { + tools["javac"] = product.moduleProperty("java", "compilerFilePath"); + tools["java"] = product.moduleProperty("java", "interpreterFilePath"); + tools["jar"] = product.moduleProperty("java", "jarFilePath"); + } + + var tf; + try { + tf = new TextFile(output.filePath, TextFile.WriteOnly); + tf.writeLine(JSON.stringify(tools, undefined, 4)); + } finally { + if (tf) + tf.close(); + } + }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/find/find-nodejs.qbs b/tests/auto/blackbox/find/find-nodejs.qbs new file mode 100644 index 00000000..c63d40c8 --- /dev/null +++ b/tests/auto/blackbox/find/find-nodejs.qbs @@ -0,0 +1,33 @@ +import qbs.TextFile + +Product { + Depends { name: "nodejs"; required: false } + type: ["json"] + Rule { + multiplex: true + Artifact { + filePath: ["nodejs.json"] + fileTags: ["json"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = output.filePath; + cmd.sourceCode = function() { + var tools = {}; + if (product.moduleProperty("nodejs", "present")) { + tools["node"] = product.moduleProperty("nodejs", "interpreterFilePath"); + } + + var tf; + try { + tf = new TextFile(output.filePath, TextFile.WriteOnly); + tf.writeLine(JSON.stringify(tools, undefined, 4)); + } finally { + if (tf) + tf.close(); + } + }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/find/find-typescript.qbs b/tests/auto/blackbox/find/find-typescript.qbs new file mode 100644 index 00000000..18ca0c5c --- /dev/null +++ b/tests/auto/blackbox/find/find-typescript.qbs @@ -0,0 +1,34 @@ +import qbs.TextFile + +Product { + Depends { name: "nodejs"; required: false } + Depends { name: "typescript"; required: false } + type: ["json"] + Rule { + multiplex: true + Artifact { + filePath: ["typescript.json"] + fileTags: ["json"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = output.filePath; + cmd.sourceCode = function() { + var tools = {}; + if (product.moduleProperty("typescript", "present")) { + tools["tsc"] = product.moduleProperty("typescript", "compilerPath"); + } + + var tf; + try { + tf = new TextFile(output.filePath, TextFile.WriteOnly); + tf.writeLine(JSON.stringify(tools, undefined, 4)); + } finally { + if (tf) + tf.close(); + } + }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/find/find-xcode.qbs b/tests/auto/blackbox/find/find-xcode.qbs new file mode 100644 index 00000000..bb6ee971 --- /dev/null +++ b/tests/auto/blackbox/find/find-xcode.qbs @@ -0,0 +1,40 @@ +import qbs.TextFile + +Product { + Depends { name: "xcode"; required: false } + type: ["json"] + Rule { + multiplex: true + Artifact { + filePath: ["xcode.json"] + fileTags: ["json"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = output.filePath; + cmd.sourceCode = function() { + var tools = {}; + if (product.moduleProperty("xcode", "present")) { + var keys = [ + "developerPath", + "version" + ]; + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + tools[key] = product.xcode[key]; + } + } + + var tf; + try { + tf = new TextFile(output.filePath, TextFile.WriteOnly); + tf.writeLine(JSON.stringify(tools, undefined, 4)); + } finally { + if (tf) + tf.close(); + } + }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata-android/aidl/AndroidManifest.xml b/tests/auto/blackbox/testdata-android/aidl/AndroidManifest.xml new file mode 100644 index 00000000..1d27681a --- /dev/null +++ b/tests/auto/blackbox/testdata-android/aidl/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/tests/auto/blackbox/testdata-android/aidl/aidl.qbs b/tests/auto/blackbox/testdata-android/aidl/aidl.qbs new file mode 100644 index 00000000..6e827099 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/aidl/aidl.qbs @@ -0,0 +1,8 @@ +Application { + name: "io.qbs.aidltest" + Android.sdk.aidlSearchPaths: path + files: [ + "AndroidManifest.xml", + "io/qbs/aidltest/*", + ] +} diff --git a/tests/auto/blackbox/testdata-android/aidl/io/qbs/aidltest/Interface1.aidl b/tests/auto/blackbox/testdata-android/aidl/io/qbs/aidltest/Interface1.aidl new file mode 100644 index 00000000..34fb0386 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/aidl/io/qbs/aidltest/Interface1.aidl @@ -0,0 +1,7 @@ +package io.qbs.aidltest; + +import io.qbs.aidltest.Interface2; + +interface Interface1 { + void doSomething(in Interface2 param1, in Bundle param2); +} diff --git a/tests/auto/blackbox/testdata-android/aidl/io/qbs/aidltest/Interface2.aidl b/tests/auto/blackbox/testdata-android/aidl/io/qbs/aidltest/Interface2.aidl new file mode 100644 index 00000000..815d44f2 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/aidl/io/qbs/aidltest/Interface2.aidl @@ -0,0 +1,5 @@ +package io.qbs.aidltest; + +interface Interface2 { + void someFunc(in Bundle params); +} diff --git a/tests/auto/blackbox/testdata-android/aidl/io/qbs/aidltest/MainActivity.java b/tests/auto/blackbox/testdata-android/aidl/io/qbs/aidltest/MainActivity.java new file mode 100644 index 00000000..e38cfa55 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/aidl/io/qbs/aidltest/MainActivity.java @@ -0,0 +1,18 @@ +package io.qbs.aidltest; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.TextView; + +public class MainActivity extends Activity +{ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TextView label = new TextView(this); + label.setText("Hello world!"); + + setContentView(label); + } +} diff --git a/tests/auto/blackbox/testdata-android/minimal-native/libdependency.so b/tests/auto/blackbox/testdata-android/minimal-native/libdependency.so new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata-android/minimal-native/minimal-native.qbs b/tests/auto/blackbox/testdata-android/minimal-native/minimal-native.qbs new file mode 100644 index 00000000..57015270 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/minimal-native/minimal-native.qbs @@ -0,0 +1,13 @@ +CppApplication { + name: "minimalnative" + qbs.buildVariant: "release" + Properties { condition: qbs.toolchain.contains("clang"); Android.ndk.appStl: "c++_shared" } + Android.sdk.packageName: "my.minimalnative" + Android.sdk.apkBaseName: name + Android.ndk.appStl: "stlport_shared" + files: "src/main/native/native.c" + Group { + files: "libdependency.so" + fileTags: "android.nativelibrary" + } +} diff --git a/tests/auto/blackbox/testdata-android/minimal-native/src/main/AndroidManifest.xml b/tests/auto/blackbox/testdata-android/minimal-native/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f61dc985 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/minimal-native/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/tests/auto/blackbox/testdata-android/minimal-native/src/main/java/my/minimal/MinimalNative.java b/tests/auto/blackbox/testdata-android/minimal-native/src/main/java/my/minimal/MinimalNative.java new file mode 100644 index 00000000..1464d259 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/minimal-native/src/main/java/my/minimal/MinimalNative.java @@ -0,0 +1,22 @@ +package minimalnative; + +import android.app.Activity; +import android.widget.TextView; +import android.os.Bundle; + +public class MinimalNative extends Activity +{ + @Override + public void onCreate(Bundle savedInstanceState) + { + TextView tv = new TextView(this); + tv.setText(stringFromNative()); + setContentView(tv); + } + + public native String stringFromNative(); + + static { + System.loadLibrary("minimal"); + } +} diff --git a/tests/auto/blackbox/testdata-android/minimal-native/src/main/native/native.c b/tests/auto/blackbox/testdata-android/minimal-native/src/main/native/native.c new file mode 100644 index 00000000..f49b4f90 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/minimal-native/src/main/native/native.c @@ -0,0 +1,9 @@ +#include +#include + +jstring +Java_minimalnative_MinimalNative_stringFromNative(JNIEnv* env, jobject thiz) +{ + (void)thiz; + return (*env)->NewStringUTF(env, "This message comes from native code."); +} diff --git a/tests/auto/blackbox/testdata-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 new file mode 100644 index 00000000..5dc5ad26 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/multiple-apks-per-project.qbs @@ -0,0 +1,6 @@ +Project { + references: [ + "product1", + "product2", + ] +} diff --git a/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/product1.qbs b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/product1.qbs new file mode 100644 index 00000000..c4a78a30 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/product1.qbs @@ -0,0 +1,34 @@ +Project { + DynamicLibrary { + Depends { name: "Android.ndk" } + Depends { name: "cpp" } + name: "p1lib1" + files: ["src/main/jni/lib1.cpp"] + qbs.targetPlatform: "android" + Properties { condition: qbs.toolchain.contains("clang"); Android.ndk.appStl: "c++_shared" } + Android.ndk.appStl: "stlport_shared" + qbs.architectures: !qbs.architecture ? ["armv7a", "x86"] : undefined + cpp.useRPaths: false + } + + DynamicLibrary { + Depends { name: "Android.ndk" } + Depends { name: "cpp" } + name: "p1lib2" + files: ["src/main/jni/lib2.cpp"] + qbs.targetPlatform: "android" + Properties { condition: qbs.toolchain.contains("clang"); Android.ndk.appStl: "c++_shared" } + Android.ndk.appStl: "stlport_shared" + cpp.useRPaths: false + } + + Application { + name: "twolibs1" + Android.sdk.apkBaseName: name + Android.sdk.packageName: "io.qt.dummy1" + Depends { + productTypes: ["android.nativelibrary"] + limitToSubProject: true + } + } +} diff --git a/tests/auto/blackbox/testdata-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 new file mode 100644 index 00000000..272fe55d --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + 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 00000000..18afe830 --- /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 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-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 new file mode 100644 index 00000000..474897da --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/jni/lib1.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void f() {} diff --git a/tests/auto/blackbox/testdata-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 new file mode 100644 index 00000000..de580ab0 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/jni/lib2.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void g() {} diff --git a/tests/auto/blackbox/testdata-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 new file mode 100644 index 00000000..94974895 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product1/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + dummy1 + diff --git a/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/product2.qbs b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/product2.qbs new file mode 100644 index 00000000..9be70dcd --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/product2.qbs @@ -0,0 +1,29 @@ +Project { + DynamicLibrary { + Depends { name: "cpp" } + name: "p2lib1" + files: ["src/main/jni/lib1.cpp"] + qbs.targetPlatform: "android" + cpp.useRPaths: false + } + + DynamicLibrary { + Depends { name: "Android.ndk" } + Depends { name: "cpp" } + name: "p2lib2" + files: ["src/main/jni/lib2.cpp"] + qbs.targetPlatform: "android" + Properties { condition: qbs.toolchain.contains("clang"); Android.ndk.appStl: "c++_shared" } + Android.ndk.appStl: "stlport_shared" + } + + Application { + name: "twolibs2" + Android.sdk.apkBaseName: name + Android.sdk.packageName: "io.qt.dummy2" + Depends { + productTypes: ["android.nativelibrary"] + limitToSubProject: true + } + } +} diff --git a/tests/auto/blackbox/testdata-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 new file mode 100644 index 00000000..871aadbe --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + 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 00000000..b3659ae7 --- /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 abis = Arrays.asList(Build.SUPPORTED_ABIS); + if (abis.contains("armeabi")) { + System.loadLibrary("p2lib1"); + System.loadLibrary("p2lib2"); + } + } +} diff --git a/tests/auto/blackbox/testdata-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 new file mode 100644 index 00000000..474897da --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/jni/lib1.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void f() {} diff --git a/tests/auto/blackbox/testdata-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 new file mode 100644 index 00000000..de580ab0 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-apks-per-project/product2/src/main/jni/lib2.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void g() {} 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 00000000..09ce152c --- /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 00000000..7b1de6c8 --- /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 00000000..92ab6dee --- /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 00000000..c524967e --- /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 00000000..110bab22 --- /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 00000000..e0cb448f --- /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 00000000..8b9ded21 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/multiple-libs-per-apk.qbs @@ -0,0 +1,84 @@ +Project { + DynamicLibrary { + Depends { name: "Android.ndk" } + Depends { name: "cpp" } + name: "lib1" + files: ["src/main/jni/lib1.cpp"] + qbs.targetPlatform: "android" + Properties { condition: qbs.toolchain.contains("clang"); Android.ndk.appStl: "c++_shared" } + Android.ndk.appStl: "stlport_shared" + cpp.useRPaths: false + } + + DynamicLibrary { + Depends { name: "Android.ndk" } + Depends { name: "cpp" } + name: "lib2" + files: ["src/main/jni/lib2.cpp"] + qbs.targetPlatform: "android" + Properties { condition: qbs.toolchain.contains("clang"); Android.ndk.appStl: "c++_shared" } + Android.ndk.appStl: "stlport_shared" + cpp.useRPaths: false + } + + JavaJarFile { + Depends { name: "Android.sdk" } + Android.sdk.packageName: undefined + Android.sdk.automaticSources: false + Depends { name: "lib6" } + Depends { name: "lib8" } + name: "lib3" + files: ["io/qbs/lib3/lib3.java"] + } + + JavaJarFile { + Depends { name: "Android.sdk" } + Android.sdk.packageName: undefined + Android.sdk.automaticSources: false + name: "lib4" + files: ["lib4.java"] + } + + JavaJarFile { + Depends { name: "Android.sdk" } + Android.sdk.packageName: undefined + Android.sdk.automaticSources: false + name: "lib5" + files: ["lib5.java"] + } + + JavaJarFile { + Depends { name: "Android.sdk" } + Android.sdk.packageName: undefined + Android.sdk.automaticSources: false + name: "lib6" + files: ["lib6.java"] + } + + JavaJarFile { + Depends { name: "Android.sdk" } + Android.sdk.packageName: undefined + Android.sdk.automaticSources: false + name: "lib7" + files: ["lib7.java"] + } + + JavaJarFile { + Depends { name: "Android.sdk" } + Android.sdk.packageName: undefined + Android.sdk.automaticSources: false + Depends { name: "lib7"; Android.sdk.embedJar: false } + name: "lib8" + files: ["lib8.java"] + } + + Application { + name: "twolibs" + Android.sdk.apkBaseName: name + Android.sdk.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-android/multiple-libs-per-apk/src/main/AndroidManifest.xml b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f184a8f1 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + 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 00000000..56fd0db5 --- /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 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-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 new file mode 100644 index 00000000..474897da --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/jni/lib1.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void f() {} diff --git a/tests/auto/blackbox/testdata-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 new file mode 100644 index 00000000..de580ab0 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/jni/lib2.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void g() {} diff --git a/tests/auto/blackbox/testdata-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 new file mode 100644 index 00000000..297dfa7f --- /dev/null +++ b/tests/auto/blackbox/testdata-android/multiple-libs-per-apk/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + dummy + diff --git a/tests/auto/blackbox/testdata-android/no-native/no-native.qbs b/tests/auto/blackbox/testdata-android/no-native/no-native.qbs new file mode 100644 index 00000000..7aab4e9f --- /dev/null +++ b/tests/auto/blackbox/testdata-android/no-native/no-native.qbs @@ -0,0 +1,9 @@ +Application { + qbs.targetPlatform: "android" + name: "com.example.android.basicmediadecoder" + + Android.sdk.sourceSetDir: Android.sdk.sdkDir + + "/samples/android-BasicMediaDecoder/Application/src/main" + Android.sdk.versionCode: 5 + Android.sdk.versionName: "5.0" +} diff --git a/tests/auto/blackbox/testdata-android/qml-app/main.cpp b/tests/auto/blackbox/testdata-android/qml-app/main.cpp new file mode 100644 index 00000000..e7cf5e16 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/qml-app/main.cpp @@ -0,0 +1,21 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + if (qEnvironmentVariableIsEmpty("QTGLESSTREAM_DISPLAY")) { + qputenv("QT_QPA_EGLFS_PHYSICAL_WIDTH", QByteArray("213")); + qputenv("QT_QPA_EGLFS_PHYSICAL_HEIGHT", QByteArray("120")); + + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + } + + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + if (engine.rootObjects().isEmpty()) + return -1; + + return app.exec(); +} diff --git a/tests/auto/blackbox/testdata-android/qml-app/main.qml b/tests/auto/blackbox/testdata-android/qml-app/main.qml new file mode 100644 index 00000000..45ee20a2 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/qml-app/main.qml @@ -0,0 +1,9 @@ +import QtQuick 2.6 +import QtQuick.Window 2.2 + +Window { + visible: true + width: 640 + height: 480 + title: qsTr("Hello World") +} diff --git a/tests/auto/blackbox/testdata-android/qml-app/qml-app.qbs b/tests/auto/blackbox/testdata-android/qml-app/qml-app.qbs new file mode 100644 index 00000000..e91a1490 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/qml-app/qml-app.qbs @@ -0,0 +1,16 @@ +QtApplication { + name: "qmlapp" + Depends { name: "Qt.quick" } + Depends { name: "Qt.android_support" } + Properties { + condition: qbs.targetOS.contains("android") + Qt.android_support.extraPrefixDirs: path + } + Android.sdk.packageName: "my.qmlapp" + Android.sdk.apkBaseName: name + property stringList qmlImportPaths: path + files: [ + "main.cpp", + "qml.qrc", + ] +} diff --git a/tests/auto/blackbox/testdata-android/qml-app/qml.qrc b/tests/auto/blackbox/testdata-android/qml-app/qml.qrc new file mode 100644 index 00000000..5f6483ac --- /dev/null +++ b/tests/auto/blackbox/testdata-android/qml-app/qml.qrc @@ -0,0 +1,5 @@ + + + main.qml + + diff --git a/tests/auto/blackbox/testdata-android/qml-app/src/main/AndroidManifest.xml b/tests/auto/blackbox/testdata-android/qml-app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..54279482 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/qml-app/src/main/AndroidManifest.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/auto/blackbox/testdata-android/qml-app/src/main/assets/dummyasset.txt b/tests/auto/blackbox/testdata-android/qml-app/src/main/assets/dummyasset.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata-android/teapot/teapot.qbs b/tests/auto/blackbox/testdata-android/teapot/teapot.qbs new file mode 100644 index 00000000..30cfbb82 --- /dev/null +++ b/tests/auto/blackbox/testdata-android/teapot/teapot.qbs @@ -0,0 +1,150 @@ +import qbs.File + +Project { + minimumQbsVersion: qbs.version + StaticLibrary { + name: "native-glue" + qbs.targetPlatform: "android" + cpp.warningLevel: "none" + Depends { name: "cpp" } + Group { + id: glue_sources + prefix: Android.ndk.ndkDir + "/sources/android/native_app_glue/" + files: ["*.c", "*.h"] + } + + Export { + Depends { name: "cpp" } + cpp.includePaths: [glue_sources.prefix] + cpp.dynamicLibraries: ["log"] + } + } + + StaticLibrary { + name: "ndk-helper" + qbs.targetPlatform: "android" + cpp.warningLevel: "none" + Depends { name: "Android.ndk" } + 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: ndkHelperProbe.dir + files: ["*.cpp", "*.h"].concat( + !File.exists(ndkHelperProbe.dir + "/gl3stub.cpp") ? ["gl3stub.c"] : []) + } + Properties { condition: qbs.toolchain.contains("clang"); Android.ndk.appStl: "c++_shared" } + Android.ndk.appStl: "gnustl_shared" + cpp.cxxLanguageVersion: "c++11" + + Export { + Depends { name: "cpp" } + cpp.includePaths: [ndkhelper_sources.prefix] + cpp.dynamicLibraries: ["log", "android", "EGL", "GLESv2"] + } + } + + StaticLibrary { + name: "android_cpufeatures" + qbs.targetPlatform: "android" + Depends { name: "cpp" } + Group { + id: cpufeatures_sources + prefix: Android.ndk.ndkDir + "/sources/android/cpufeatures/" + files: ["*.c", "*.h"] + } + + Export { + Depends { name: "cpp" } + cpp.includePaths: [cpufeatures_sources.prefix] + cpp.dynamicLibraries: ["dl"] + } + } + + CppApplication { + name: "TeapotNativeActivity" + qbs.targetPlatform: "android" + + Depends { name: "Android.ndk" } + Depends { name: "cpp" } + Depends { name: "android_cpufeatures" } + Depends { name: "native-glue" } + Depends { name: "ndk-helper" } + + Probe { + id: teapotProbe + property string samplesDir: Android.sdk.ndkSamplesDir + property string dir + configure: { + 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]; + break; + } + } + } + } + + Probe { + id: teapotProbeJni + property string samplesDir: Android.ndk.ndkSamplesDir + property string jniDir + configure: { + 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]; + break; + } + } + } + } + + Group { + name: "C++ sources" + prefix: teapotProbeJni.jniDir + files: [ + "TeapotNativeActivity.cpp", + "TeapotRenderer.cpp", + "TeapotRenderer.h", + "teapot.inl", + ] + } + + FileTagger { patterns: ["*.inl"]; fileTags: ["hpp"] } + + version: "2.0" + Android.sdk.apkBaseName: name + Android.sdk.packageName: "com.sample.teapot" + Android.sdk.sourceSetDir: teapotProbe.dir + Properties { condition: qbs.toolchain.contains("clang"); Android.ndk.appStl: "c++_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"] + } +} diff --git a/tests/auto/blackbox/testdata-apple/aggregateDependencyLinking/aggregateDependencyLinking.qbs b/tests/auto/blackbox/testdata-apple/aggregateDependencyLinking/aggregateDependencyLinking.qbs new file mode 100644 index 00000000..a65dcd02 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/aggregateDependencyLinking/aggregateDependencyLinking.qbs @@ -0,0 +1,37 @@ +import qbs + +Project { + minimumQbsVersion: "1.8" + + StaticLibrary { + name: "multi_arch_lib" + files: ["lib.c"] + + Depends { name: "cpp" } + Depends { name: "bundle" } + bundle.isBundle: false + + // This will generate 2 multiplex configs and an aggregate. + qbs.architectures: ["x86", "x86_64"] + qbs.buildVariant: "debug" + cpp.minimumMacosVersion: "10.8" + } + + CppApplication { + name: "just_app" + files: ["app.c"] + + // This should link only against the aggregate static library, and not against + // the {debug, x86_64} variant, or worse - against both the single arch variant + // and the lipo-ed one. + Depends { name: "multi_arch_lib" } + + Depends { name: "bundle" } + bundle.isBundle: false + + qbs.architecture: "x86_64" + qbs.buildVariant: "debug" + cpp.minimumMacosVersion: "10.8" + multiplexByQbsProperties: [] + } +} diff --git a/tests/auto/blackbox/testdata-apple/aggregateDependencyLinking/app.c b/tests/auto/blackbox/testdata-apple/aggregateDependencyLinking/app.c new file mode 100644 index 00000000..ae414324 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/aggregateDependencyLinking/app.c @@ -0,0 +1,8 @@ +extern int foo(); + +int main(int argc, char *argv[]) { + (void) argc; + (void) argv; + + return foo(); +} diff --git a/tests/auto/blackbox/testdata-apple/aggregateDependencyLinking/lib.c b/tests/auto/blackbox/testdata-apple/aggregateDependencyLinking/lib.c new file mode 100644 index 00000000..9ef95547 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/aggregateDependencyLinking/lib.c @@ -0,0 +1,12 @@ +#include + +int foo() +{ +#ifdef __i386__ + printf("Hello from i386\n"); +#endif +#ifdef __x86_64__ + printf("Hello from x86_64\n"); +#endif + return 0; +} diff --git a/tests/auto/blackbox/testdata-apple/apple-dmg/apple-dmg.qbs b/tests/auto/blackbox/testdata-apple/apple-dmg/apple-dmg.qbs new file mode 100644 index 00000000..b3d39fe2 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/apple-dmg/apple-dmg.qbs @@ -0,0 +1,81 @@ +Project { + AppleApplicationDiskImage { + Depends { name: "myapp" } + Depends { name: "ib" } + dmg.volumeName: "My Great App" + dmg.iconSize: 128 + dmg.windowWidth: 640 + dmg.windowHeight: 280 + files: [ + "white.iconset", + ] + } + + CppApplication { + name: "myapp" + targetName: "My Great App" + files: ["main.c"] + + install: true + } + + AppleDiskImage { + name: "hellodmg" + targetName: "hellodmg-1.0" + + (qbs.architecture ? "-" + qbs.architecture : "") + + dmg.volumeName: "Hello DMG" + + files: [ + "hello.icns", + "hello.tif" + ] + + Group { + files: ["en_US.lproj/eula.txt"] + fileTags: ["dmg.input", "dmg.license.input"] + dmg.iconX: 320 + dmg.iconY: 240 + dmg.licenseLocale: "en_US" + } + + Group { + files: ["*.lproj/**"] + excludeFiles: ["en_US.lproj/eula.txt"] + } + } + + AppleDiskImage { + name: "green" + dmg.backgroundColor: "green" + } + + AppleDiskImage { + name: "german" + dmg.defaultLicenseLocale: "de_DE" + + Group { + files: ["*.lproj/**"] + } + } + + AppleDiskImage { + name: "custom-buttons" + + Group { + files: ["ru_RU.lproj/eula.txt"] + dmg.licenseLocale: "sv_SE" // override auto-detected ru_RU with sv_SE + dmg.licenseLanguageName: "Swedish, not Russian" + dmg.licenseAgreeButtonText: "Of course" + dmg.licenseDisagreeButtonText: "Never!" + dmg.licensePrintButtonText: "Make Paper" + dmg.licenseSaveButtonText: "Make Bits" + dmg.licenseInstructionText: "Do please agree to the license!" + } + + Group { + files: ["*.lproj/**"] + excludeFiles: ["ru_RU.lproj/eula.txt"] + } + } +} diff --git a/tests/auto/blackbox/testdata-apple/apple-dmg/de_DE.lproj/eula.txt b/tests/auto/blackbox/testdata-apple/apple-dmg/de_DE.lproj/eula.txt new file mode 100644 index 00000000..7ca2bf84 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/apple-dmg/de_DE.lproj/eula.txt @@ -0,0 +1,6 @@ +FIKTIVE UNTERNEHMEN +SOFTWARE BEISPIEL VEREINBARUNG + +Sie stimmen zu, dass Sie nicht mit dieser App auf Atomwaffen zu machen. + +Sie bestätigen, dass Qbs das Beste ist. diff --git a/tests/auto/blackbox/testdata-apple/apple-dmg/en_GB.lproj/eula.txt b/tests/auto/blackbox/testdata-apple/apple-dmg/en_GB.lproj/eula.txt new file mode 100644 index 00000000..e2b7adbe --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/apple-dmg/en_GB.lproj/eula.txt @@ -0,0 +1,6 @@ +FICTIONAL CORPORATION +SOFTWARE EXAMPLE AGREEMENT + +You agree that you will not use this app to make nuclear weapons. + +You agree that Qbs is the best. diff --git a/tests/auto/blackbox/testdata-apple/apple-dmg/en_US.lproj/eula.txt b/tests/auto/blackbox/testdata-apple/apple-dmg/en_US.lproj/eula.txt new file mode 100644 index 00000000..e2b7adbe --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/apple-dmg/en_US.lproj/eula.txt @@ -0,0 +1,6 @@ +FICTIONAL CORPORATION +SOFTWARE EXAMPLE AGREEMENT + +You agree that you will not use this app to make nuclear weapons. + +You agree that Qbs is the best. diff --git a/tests/auto/blackbox/testdata-apple/apple-dmg/fr_FR.lproj/eula.txt b/tests/auto/blackbox/testdata-apple/apple-dmg/fr_FR.lproj/eula.txt new file mode 100644 index 00000000..48a9af57 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/apple-dmg/fr_FR.lproj/eula.txt @@ -0,0 +1,6 @@ +SOCIÉTÉ FICTIONNEL +EXEMPLE D'ACCORD DU LOGICIEL + +Vous vous engagez à ne pas utiliser cette application pour fabriquer des armes nucléaires. + +Vous acceptez que Qbs est le meilleur. diff --git a/tests/auto/blackbox/testdata-apple/apple-dmg/hello.icns b/tests/auto/blackbox/testdata-apple/apple-dmg/hello.icns new file mode 100644 index 0000000000000000000000000000000000000000..b8ff0c53b28f4f166cfcda689cffb5aa553769d0 GIT binary patch literal 1393763 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%0DxIAgcdn3GMPU^r@nHjyBb=F! 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

Vs`Zjv47LGh=iS=$j@GAElrB-f=M9 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
ISJyj@C<3jRxtE}pUO-Wf3)m2C)-%^3I6V* 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%0AEYh$y82 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
*kU@D<~=3aoJ-Z>Ev7 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�C$fjONzM`rNjh@Q#KW; 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%0A5V(r|rz6%T&e# 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≶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%0A7< zFxesF(Fj-G@x*2;1R!uET)PkWJMrsqUw<;{Q95fyT1jWg=~aN#ydhKQU2FGCvDL||&7$ZeF9jYa zD&)2mLx(54kQ8=Q?6yR+ZyY2K4R0w7yx|lQ^Q$X<7_N#}hFIIuAql|CMR$O5>^TL;!O9 zNKx>F-O3)Ver=bcZ9`y}{bK`BgA|pDoYHMesVa&gAP>`3s1j?_;H6l}ilo>ojO!T3 zc>=q#ChQmgk4H!`e)dypu?mY&(h><|F?qFNrc{G%r$QDfV32TaEcM8a0hr)~@dEw$ z9~}zt_KM0Q!h?dMHf)NKU80plSHQYBM)2}7gdA+^RiO(ZDY<}Iq^PxAj9S1M71wWm z^P9&%rvSey%J#)#FY%i>5;A;t+v<_1Tw9Mq&+}8%=|4)nZiTZmQV?>bXPk@o>Vj0E zmB3iLAL+Hc6qVPO&WnF3SW3Z8Ph2n8&ODu#z;x+4V9wH+_V!d1E~?sG(6SeGtpO(( zuh?6fmv@PxW)WB=osH~L7g#PX5)43*wrHix4wmqy@K%y-f$Cwgg`o(fXIX{wQWD@g zg^aw6@pB=E26Nf5?;md>p=xDch9)Gk`B_U9zt&rCBlG>a2=(eH@u|0R>%}l6tK2l2;cPq zz-J@x(sZp(PeVy1Qmv+Hr^1?`wR5x7%4iN&u3(G}4G*N^4X2vbS&zzQr#EUa>6!)% zFp;|1)*UZFr9yAKUVZO{=M53=%N`G)YCwBZ-o~ze7A?456@ZGOLaR!+bOa^}l0BC? zA!92$odT&xIth#c6?^>brJyK@*jk%;R2kFd9X_5c$k{7n0Gu!dTFZs4TmqFL#4r_U zOYi`YMNx8zQapU^L$VOz0PNKO-^roiaZUY+=8o`6{=AOAn@G|+mAVF5R3(c_5USHU zBMf`UFkIzjXCcS|C@}z#bVy#gtSm$>uX)~2I4AL3SHG<2pS4BOkRb&syE2Avo|sw8 zO$f`%I4V2;XlvyX3rSFY#SZ`;7kvPzWx4RnBAw!`#@?1*8PyL~Nex{6?DQI`mySo_ zyfN6uTrNniYl^Z&?puGtThhB#4T|^B1LmqENrT1Q$^sKi-nL2=0)KsL-t;_IMd7JO8B-(@q8PahM4eVrZM@B)BhlT5q!w&96Be8#HXjna``6K3 zJjL?RLiMZa|_sxnwq=n`nQX(%Rv zEY@0K3g9Y`f|k8Hxp3-~_^Rmf!Zi>nxWrP>O5GKwW8gx*YeB~eY^^rrAxkQId0|Kx z;LfCQf2aD`2LL2$i%`Z|Mk}49D&(TDGb#oq5lCStS$G+<)04JVWwQX|L5LQ4cHvzD zThG9E{kuCqe{!pKfbol@b1Hkikr*sK&t#ZtGYz_0wbDVjxbZbi0 z!Z@Fu5W`uPV3k)rS4(rS2B4HNJ0u=b>e3-$k%&>vgibIhfUy?Jvh}1nF2|^{2&ZVl z%4-WyM-+`GfcXN8Q%I@*XTNZ=nQk9O=1AC%i$!uxw2^X^wZ*o@>uPVszx| z@Fi6kW=4tBPgke4vK3%UCv;5-tfVH>fmVs)c{hRZT^Po^Vyl@VoL-`sZACQ%0K&`V zCskhFN}c`5)mVJ}fEI}m%#ylr6jFAIq;yU3g6XKHY`Iv3lS@-m3cZvEd*$8EXY7tS)NAoTcU^hPfGzD(^60&BPi{A-1(( zW$&mU@japw7-pO)ZY3}92yLwbNwfjF^mFmj8!W=WYGx!A&t7?IsI~AuX}s${wtZLX zwo1a&X8^fm$<bl1i#O zdP9hlw|3;E^z8|qy~Z9nFI~SO)l4f3PUMq5IGptQ86L6U*#FJTJeRzY6jY8l}3G(>&VGsX8tbuq% zK#r%pLMTFFfMFrFVA$m1g<)(mxeSw6VO%xH5*UEdYD>ZS-WO<56AwV&)*)w6iHxPD z$6q_u(7L=;A}~wqn0W^f$Zjk!m`Sbyc>R*<`5zmr(<^WP-q+|h@MWhQ>0hQqsYeCS z^U~WkTgDo!&f_}0JOQS}UsU9=WxwhyVA+KqnO+_svdAb4yis&2=%hrH0f z<_b`3BnywtI!k@AP#ED=8^D&(S!z&HFc`TO2%u|H8MYK#015_pMzL1Lkc=!D+3PNH z#>lM!fblfd_%Yx91m7OcCF$u?0>MD#Hjyamr(~{{Sa!TZ=)8D%DZK0$*o9{Y&Rb<* zUZQ$QpfeJIqDmo_QWSm706+?1BvXL1vt;xZ1ivgH-(FoO&quGL<7@&A#OjX&rlc=L z#t#WoVNxr=R=-v-gf&uO04fnXBUn+^^iqq(DI=sr)=?deopV#_*>8$1AQUg7EQFLIj zwp;|v%9zfc#Y#d_!ko6-gNX%LV6k-7nu(C`#j3%!s+PjG+E_9c(l*+yl=50d*%EI% zp8q&n$miBx>7`z?OD9Bk)(X?zwqe+#UFEe82EZ78m50~<4uEhWzDj)cFKjaY@WT(* zYADyrYpTw3S$MgurqrM?b9L(I@KwcI!6)!ox*}F@>3kR-VHrQRdO7!INcO^INl^)D z6pt$mV?9=07DiEYM&+4FjjNe4e3$@qF2V(G^0~cblqIdL^vD7tw5+SfG4J1NaUUpmfoekR~ z>sdBC0h4z}-IlRYgiMF=HGyXcz}tg*^y&x|y9)7CQaq(&VNaLcHj*mIZmpiX@^F$F z!BA;Gl2JKkzW8@XJeK#W#k&z=+L9WM6kv5B7oj81!c|^°2`_D(M_;kAt_Z~z61 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%0AyR=3ltujr?n2g9yVWU5t zQcLSotjim0TB*f}?!K{B8z_99yPJJ-8Z0|)y_%ao8|rZUFd{89La<-)sXL|twEW-d zqE>OdL6&7Gy5n5eUhksAnoR86L$P;Td6>1fbOMm3x`(m>U}uAY(ZSLxl6?p3S}O87 zL1}Yu6L+ZZOm8p0lCHAbePA`<{NjkN7`=*o!DW_zpyk*omE(i@&f#KS)+k`~BQcFVgKD<1kn#03mdb8iktCuyKQ*{n;{|VL1>N=iG~YM-ut_~>0h)&FDfxWML)stv^BgzA z6r%)IzTH+ErZRF7e%;kxalH>V$3G~TEHP%*I7|RC=E_lw_OqBq^$Rf;1ETIiMxb*l zM1R2Zy@qlT57Vj;)ZbsjZ(&wQ6IT^>SSXwqo3#0Pi5;4AK09}2s%H-^jkuF>DNmoc zu%O_3Hy54XG>ks`bpag2E7`rmc80!?XYXvA41?HP9X-hDc7knL>?g4me7R!G>EO>Q z+(>Gp=?8O<`TFvo%Z)j-x_jD>^}Mw5)?h{^8NW^Tm^t-?T@y4~dy(ria7gXtB#suK zoZ6~(0Zlqz7vjhp)VQdY;Geq2OG}Y+ahov z{TT|fK)3V6Uc4#J)y@K7-a0(gZiM3}Dgan^U>EKg?bEhd&+O&-%Bf%N?Y+La!`7!U zO^S69g1yfwCUy7>+uYMHl-!}i4-+im zyly@}<)LLjx4;B&Xaz}gwov)Z9kXHWGs=90c5;}e;ZufIpCx*9xchR)sClNJ6j91d z(LQ~))jg(x^&+zAp7AfXosN14H!w(&9O^H$*q4nnUNx?HI@OK78l=klWQx0$njUBd zCg>wMxK*Ze(p9;3sh}^1Us77coZDy~*%^+NJ<>VZu8pRKsNh6dp|EDMT1c;?TN};W zy-eKJnXhq38x*<`BGV@=&t0z^N_I$@0rA}Rhq19zBq&7^;b$7xE@DMCZ#DP9D>b~G z(`tKUW4X;U%7fXcY8H8aOcq+lel{~`K?D%8rzlsGyUWIdW6RD}#WV()>L(F@t}!y- z-Moi`;J;sA_)*LqbFbs{rMf3M6sPpdH%=bVzgrf5ni5g+GnF>i@u;r4NPd6+j!csX z#Dg-^Pj5Ylw<*{}tPo=aABqE$ISiGt7Z9qP??umoIj%#AUK;}bVt>&%qQAUuao~vD z8roQ>=&A(cUL}HVgMJzWtj_i7H3oL(d^!mo{kq-IjcIgOJ=L7+1*j}jUwj!*>r^!C zY!FL_6O&P4E*AaSMN4BJS|X_J0H3ReTIW!&hjyv`06AgRzGs|jNjwGIEDHTR-~H+a z{o0!Y*C%3BN7R&sh9j8^0V%dL`incVYB*~x$GG^&&@Aw_#~eI|vo0z7!Wx?GkYBrz zu)$f8PC(}aP5Hc->4PD@IlS)>%=X0(Z=9pVC7Xj?Ul;q|drF^8z5haF=rE;L#E=lT zljGcbKDnyoHIo;c-4qGk;L$=jlPF~=w7S@q7_8{j+Yy7Ypd|HbOrlB%zmSxyp{m4j5*9CHuYu`SSSp}R? za^sW6f`vcUd2JjFukusIpdC?VPIQf+m11I^4PhOeqjyR+bfvw=l8{~ZawXCxZT3Bf z&EQ6sEyncyUNbEH`n-paWa5}b_XAd4F@EQ~x{cZv$6cutIm1zR!oNZj(;QUGMoSal zr6+w+0}d40)a=DZYETGCFdrRyGIF+j-*&@pIyZZ36FaqG4tSzYxjH1v@@SxOxZQ@@tfm4@CC~JZ*ZRf{)6JDi5hgkUNqIQB z*^9!1@J<(wz>gG;iaI+YM;8;u`E%@i0u^qd@l)>4vXjP-$$jN;_6RV7=#A-%zJYdb zP{~pGV)Qr_iOz7FgG0-!DdOf})?TwHj+e)1MM;~5ta!i8XQy8*rl~Vj9ifBCJ&_$@ zHRTnkCE&Ya;e6Qj{s%ODbCeuWWnA{};LxJ9`LjRwTFVrlZ=@UU{-y?}pxo zf|Bbua>FEy0`ez})5OwZD_EuXe;RE@ua84%}@U z3b46<>I3k<{YABa?ZBT1QAdU{67)r6fbG!i2GA|m1KOD*d7lvh8T8;`mW8<|c7>nq8>%x=fot;y^@ur7CFQg*pK+8Q+ z8%~8#RuM8zN{k_J(c$x8;-78Le`I1i2vUb{U)*x_s|lJ{us(CHG5aL-n??atuMl;; zm|7r-zs%d!J8|hAwaHrqFH9%V<|Ix?vP{%>k{X&(JOj*)?x`7>=~3!c^!i{LdyV9K zL*{OeJ3zO9CPut@_w9p7MB33eLqrFSOu-!_pIfZfl1Y~oca1sJYVDolM`PEo3SQ$X z@Da3o6Ry;_R=(#`pII+OobJFY8>WuQ6|0MkU*etD{#Bsk0kSB*2S}mlo`Dn_Wuhjr zL4QIx$yAj=A#r3du;Z?RO(|{|XYM2oni!X{&KF?_JWC~Y@OvDX_oB%xKTDbbj~-99 zq-|Y?M)nzIPYj|nuJSj-X7QA&7A3DWtO9-2ZRnfu0V%wBN1hd}d7?B%s$4bz9j-ykFd`h&&nx?F-ZUAKn8bG@gvaQtd^Y9v4|X82){zg-n*5 z7BFO^YQTs6jh^ld125y>)w*aaTU_eAGv)-x`5XeCx|@lzSen$JTqL`I^M5Ar2Yrhk zWiJb3a5OT}WVke=hwPdbX6w@ccR>~F);eW=p4#oQWbF(qlqbS<`s{w4tEqW8zY!!e zXaG5eH*Msm`cbGH$eulcwoNxC((nFHs*u!gHc?{(F)EUEu&OiRW+_t%&yDdGcqMIkidjrow`@v)*ULblYeZ zWS?YpR0`OoesI!X0V>?TlGU|snnmcSm;99Jb7C!GS{?V;9~&~t{6a@4AuUo>w^_rr z{7nS?Rj{$1h`dQt*F)p)sirXLIBX4NHmXG>(3%&+9Hxy=Yk82VuHSzZ4-P1mg`<(f zgn!*p2Z}5@ZeF|IKuD6YYaT+vjK81Ro6Ls2@75%|^seITg}XDaX@%%JC$BVJ70ssj z@bh0+79V>JoSyy`$Kch$RHsaAMQegIM|ioJc@IiunxmHAc^(hJ^$fv-Zet8jaE{fyBZ-+dZaoL{p#W%Nmn~YB$ZL@_{BS|O<$LTpmAQT$J z_votkJiSiO{J&9|(+mJT;ERmdt?11aW6`(m6n*z^U#vR466P-gw?5riNVV&~)nDt{#9h6k9~! zc1uS^kNeb*oJr_wBn$C8BhQY^wPuLG?>Aj0?9KL;yUq$5^)vHcF0!8^9{eh!c;^(b z$4JxBS#nHdrpRRrI85)zVSE()0H9%t_us~Z)sTT(te7hm$z9p+*8ow zDk$~0;2oZidm*c9H=kT@bl)6)JY+G z8t`BTSC^tMYVj(SVLY_>;sBpx_vsH|sIoQ}i7A2Nn!R0n*q>9o`*}4%?^15yRF-l} z8OojI2gX0?dU^UHFt3qGx(3gWQ27_N_f#QuE^Y*~QGs#eJR@^t2fX9g2&i{g?-%<= z$G<F-366_s+rW zqLu#3a$^6Z>Ae5heBVBt*tA^NkwYMj!|mXR%+Ks>{=Bg#E6Pk z)vi^mMrzh7s;Vgcln(vo`QiB!?$;~1ulqc&_xm{PQ`O86FZGnn>W#C!Nk4LAU*kyE zR7NY>h|Y9e$WA+;&>%$+z9HD>)^vD0-S1V!&>#??0X@Eahk0!Mv)>SIc+7VMB;Dv0 z^B7My_b1diPX{r`Vsl1Y_jC4q#&Z*fr|fVDXvjRE8f!8sWiIWQ5EMKTYczD}sFj?^ zN=Cr>)#~!KtUqdwH0#wv+lZsN_TP_+V}^wB`n+E#Wtdj9Aa1fhv(|RG{Cwz{}$F9@%8Ju~*9m!LDP` zBd7IrnAdzzvu;uokqlsth~tTLN3i?Ydpc!@q0RPPoQ77jvcosx;1PcVntjigKFPtp z^`^!|@ebJBmMB|y5z~+xjd?qw8kDuunxBoj`1sne8XNqB~xdX5hN^tw=;MI4u z)w$m@mwZ8#BRKY{6`IwsxlMj96(SQ}x7vj;K0wdm|ocdm-NQt(jNrQ^3w-c5*#!<$S%yjaf|2~yaGxH0^ z@oMe5B~k}e2m=8|Ite_5%tcQOVNZF37I{Cpi@fZcv}P}BT%Dhfqc)fYN5pjJ62hqO zc|hDOkwqHr!mc*Du^hIKuCPob|9pgJ6`c!p^{*4iIFI%BdW!{6pabi1#bdI@JROatd^teth(lU%a1X(%Kh^ zIo|0QxRK@K^1Jnwb*7~w3b&DL$9Dofs}^~x>asYJvF0U)j&$?=!IJ@KXrWxpJXpH_ z!+VI*`+O#20eyNjJ4R7S4V75=6`DlrBbQkZqT7NR-4y^m%n8?TOPBx|cDjGRgF>9Z z)61L&vq0YYz!$0ZZfKwJm-jM(;?CL}h_hdNW4VNsu_3#u+@t%iF_h&ZZ+hqgGjHRg z2>Hd0n>I-}HXvFXLm`i^Vu=cH3X)b$PQwuTU=&u+m-stueiPRc+Zn5}IyBRaW&mWk zI@;|RicdMoB{=dnw!o0IB#a#TX{{#asWBC z`YHTm0Iy&`p~BG6Uh;`BT-7js0F>R;YP*N0Pp_yL0%We?8wc!&XS5-xF^e9xok9Bu zF?n_5&6XD0Y=%+i{y?b4jrW^R4S&02;4;<_Yxfx;W{4o=LYf$wgclS#vbM5VJ7?b8 zxXD)8p_(}1IP>%m@p7E$v}yBh3UzTI#LPB;8`noAa)-Tp`r+qf^r+X1wY*l}&`Q%$ z8Uk-SgY&oFLeKDEEo4$0y20pfQf2LgYwfGoQ)Nwu=|}`CvwG4t{^1-g^5*%u9$Vk; zk9LR9W&H?xA{Qe?R!DLPdqNtYsxZ4Pfh9Cl)&bhwo9Aq(0)_jTgUZa%I6NHi>~=U9 zrIj;s4cOnTc!+nhdC-%*jQwr=sk1@2(71p8zkiIZR<>zKIv$!&I5zH)On~cJqF$s4 z)bKxdi_~J{7Y?NFOP~H+^s`aYnX;LX0K$lega#8~^U-K(>*tE>f2qi zxMD;DK~o>A+a6($q+jfddUg0lf_3r=#uj~&?)v!H`ADCj<|b&^Lg_4`CigaA>IEZ6 z^}>Ch1R%04#iyus#|;`td7G951V0a@e^p!M(B_nx{_3)aY!5j0wV?EMFX-DUS!u-E zCqX@*vJBZg-J=Nijp#Q8?eI<$m!#}}%vZk{&wmluktyfm$>3Obud_ycDS7<8Q2A}l z8z)_LJ~RY4`%R`RpWo8I*#hy!Wa3huaYP|F;j;efv$lM*i%6!AhM^~vvvv0v!j9~ia@y;K~elUK)W}N)e72R`-N>}-dedD5HwBqI(3_tI!CUm=h*Wm*m1 z2HJ$Qyj4>#5i@+n3uI{(^Znfp`Sa=H-{u^`tM8Mc+|ctC+fISACD~n3z!j77GZfw> zS*-DTSKuh!a{*(Wg*MZ5&BTjiaos9X?#%w~Qe*^B`M_dh&V+R$nrQ|=h{fM%Qlzzy z-6l-tzZh9L*IDE%q3u~d1I_ds*+nO0RxO#QXr#H|3!_bke=^Ce&Rz@mE)|UI*H7}=L zb9HXi^$gs}e zMFXd!Pzd8O5Bw+~DdkvK!<2rLF>=(eLLP+(p8W4eMT>kNvbUnQ$_e`Y`Rjk;j*g5l zI{&}d9Yb3s(H1bDuqov=Jw^J>gO72$KaKpW+iRn~4(Pnp($dnigSL7f_^nZ+qJ1n6 zzGg1W@th7{IF`fLsN=6R{f*u3){Rk+PyZ}I#SJ1=%x08c#luy) z;~(>P&oQHx2{FNKAtU1iRulQ=KlPc5q6<>i%GpbXjL8?0*E_>Gf*INUEqfLkPA-ZNg*D}=12 z&LJ*Ca8M%3Kr^h(T{M>QuSwRCB9#V&wn_`auU=5y#;w@cS|^fF3)5=!=JyPmO(i61 z?Um8t8*c-OmiDW7VXU2AGfQ8jOi>lUth0k$mq!s=SI*nnqs|d;T&@RQod#3iJy1|U zt@s{AdwLCBy;&|2r=suUv5!`^@%cN{X!Ht`| zwLEfjwF*)Ws$KHE(Fu>u#XF{P1L_Y=4~0xv=0{U?5;N{ziX6=zmq4qF(pZag_Af(V ze|c`RgZ;#z^>H+EaiCNzss8y>=EDIMI@(5hS-k0J=X?_?HsOJctrL%nFYu5aWu!HL zDVa{Dtf6f2ja<(${Wh0#KY|9#8yP;zi}8Q4xL#$Kz3$ycO+=%lb$;qdgZYj9+elXY zYgPoy~arOE32UKt8&9);EvOPE^Hx&pho|X;JiMoL$|_*O=}B z@5c>~wtV@Kj37iB4GB@UuK4CsPPtx?Y6!iAuQJ19R`nb47oDIiC*JA5T$He+OB^qV zm6k}VK+g-hbv2=DZlvdJ3!})7vV>=?B|{Bu}A- zU-Jdc2U65iLz4eCQV|7l8MSyEM=}B`Tv~@6q_*FjKFIg@1slHNvT~=iNC?*E`Sxum z64d`<9lOa=TU^Lf%tgF~~!0>hm+{Rh>$dG_o&Y$K@oRLTIz_IZS} zEzf{0U;8%J{yBp=i;qF!4bFeCn)d6=F@WX|hIZuQC=tlXAV~fv3d|>2; zFED|B`&L<{k_A=@T(_~~RlZw8KQ+|!`_<@CV5sm}bu`F?bwiszVB*m=Z7~yAU~Uv0 zE0|wGO1P8Hc#11y}jhlj2Gi(H2$EU~lH<+j2g~)5(6UlX( z7!6}Ek}qO_d>MAW)c9Dxj$DvUz!KItH0{ya*E;EybW>@^f0H&B0_CP=?7BqPzJK88 z3qqQc<*b?pR03^Xx!$y!+bnwXJf!D{)Amyr8QO9yU*I{HiUhaym!H^NLV~n( zBpSDBWT!_>5N~XP=l?87OT-ccu=sg+Ad?IuvzFB=U~Ijr%vk1`h;=G!&y$yTS!2;B z;lC6aV!Pn6y-x2PbA#5NTbX={9ic8tIRTc;;L;ZTPhx2u?0qvI!d1R75~IYAH~3GP#A zW1s~}u$VT>oE_oN7vg4F?DMVQ_Ib+gZEz==oJVI6|5GY1n^2_iWEg$&;ItQ~x9z{5 zgFYe*SC#Y$<^dNP;?NjTZ z)hm0i8XjqYAy?w;YR4-`*2Ka;i-Y!iF_j=mDoLm@jUS{oK>N{IV7C7lLMuwzxFOF`r2nuzb2C3&I#; zc1IPXjU&)hoer=RUKF}M6PYa!jP^5%bKdYI2f6YNdfZ<4L(!wu)1MA4k9njsz-i3j zNEq=Fv#2y@%i-}ydFLvIq;x!gxRgh@sVyr*PrTR%` z!+nN*L3}EBx(4B2Ms&grVYFTr9fAVS;JM<5M^t7R=5=X)t$(%nXO}gZF~c_luw_nX zLw=M+xHoT|C6a(upwj_}0Q2Othz3>ss-+(+Q-v0UF)B(N@Kmx;bG4S?&uAq18fayS zcW1_Wm=oe@_k-?_YEN_B+P4gcJD8P`f?URkE|oKU zO~3it;*#+cfP_k9#iXowOFYO*4Sf^Txa&hmO)nVyVuvjRG{U61Y}b4CzWP1t4Rc@0 z%7D3Hpy=3n@V40`v!KMBk)pZFcnBJ7N$H&~zQPTg%Oe?ta{=oOU!Q%l=rBy2FY6h~cF)nYQ-u4brZpAK<0 z9ia-g;^~rGrMNjuShe5qO9VdpAxwLU_#VYG19z_c@7eVbsA~`PXOL%om7$rF;=dF7 z&Ha!?`C;6eIW0y>Rp^qf7bx&{9<%$vwF~J~>SUHwI^dZ+WF#_3L+?zzQ{*gXg9KZK zL>K!L@dG~(5_aeO>9Y~2Q2J9l(Zo&97d_nbOy0VVthtwUml|E}<>Ld3CcaOUt4Y>- z?^mW22jS1}+}(mRh==M^{oi8tX?^8MBds?M3p9w2dGf_z<7E&7VYXw(&%wEWcI7`t ziWZ6Ost++ja0Hnm$_;4Yt#|~wfOQ8iFy+M$VU2zj9*c1g6J>h=>x5q%stADA1q^p0mFBMcU@j1>8CnvGrg7t2l^b z?w{fe9g|O#K#p`tKf=(%Cs89k{3b^&QXyiMF&3td-d8K{qJ7#5)i5L>m77f5P2_9Y z4CC%X6ah|YsLPl5_WfCSDb88kBmPNyurM82XG>T?LGhzWKWF1J`M~+SiUv>GUA$K? zt*|7zL-q|yD}(b7QsPwNAu+!{bf3>HSp7H6dDrd06M_=ZQ*LS^9cV_FRYw@{gtCW$ zfuDL>FHS#NWgt3@buh>_4R}Li?WX1_bQ!*;Yp<6m`UH>6IN%?}j@1Z@Cmx>r@hrn* z39QFB^0pN_lz9}m0mH;z6Ol!s>c8&(C9N2pz>)n1yftH4H5Ug}nW}jP_lEd^*bIMD zEN|qDH}E1H5*Pgt^4=)#6_GNca5UcBzsRPI{8jQgF7tV?eZwgZ_2ApVAMa1f*F(-Y zeEu$OE9lFw74?ycMa%tdc%rN8*URFsIZcNAqu=0Mu`>m6mQZrmFBKa4MVjhvUGK#_ z^4L8-6PY(nl~uTHztPW`#Sx+dYX0JRN<(1ATAB7R)4y={L<@X=g>_1_FAqiOs{3vAk^P9P`M^FV^Fl5P9eCne0bA=_2qskHdF|P71 zqbmcj`jL<6suQH?OXR*y+Sea|**wxzo;0c__2LMC&x%i zUj_YaeDzA84A0V=nQa!`5 zTP2Rh{6MM3k?O!?JM%c{F>C&9SQiO8%Okqy0<5^L^z=f2I)9OvP8PXN32PoS*rBF# zLG-p-!rk9_l0ASy0FjvJ+jNt8VOY3>nQm$Bi}?N9(h6}jFMDz}5Hak>^b~7)g(C1H zaAZ!l!3%|;AQLy8mEv_?`uf-(#x*n)(EY3KgI_P4GX*R57cq zsLzz6=oNo<)3-lkle58RZ^<7L%CC%?yZnCc^`X+FV4x_LS2eaL+0k z0H}qmx%thD`6xwvX#s;IBxtscN_C9aX)ui5=H*pM}91)rP(!3$k*HCLbJ6GK9Wzi_cc>fAxB4p64x} zCfW1Qo}dJi)zM)!b<2E}P(s)Q;BH4q8QD%fyes@`bdG(2->rDgVl*|@xHMpgl6Dt@c9*ry=)bAXEsNBt4 z(0UIy_(FkmIxj`Okl*$D(*DmfmXz$2R)oXP$TgX0z$PQ{zOv-~DAc{Fc7&2-bt4G9N0i;oGD>Xr z*>CS5dozEpb3HD|=vLu8u4c6Djn|=k^WJo=!v1w%r~yxi1%cD+uxB{(ucB*im)`^9 zf?3fi!41vWe%BlQ>Z^*kR$q+?Zm{0K6(P1LZzE$t_8Lljchv(E0XesB>e9o6E)U-c zEsI{%HOgWm;Ycona)1}eO3$`kVCBRO}&!8@$o_F3D1ax``7U08$ zp{%G+J?^lf`8!UQ(>|;9cgDP{ibfvqIar40hm=ke(Q=Y z#jv15tyHBvs~gYS{-;&S$}d>VE^XNkUyvFOfsUC-d|+W0y&sx5UU*~tDZa)N5J5_$ z!Juv1nu|tFM#^qT$Jpj4eNBgfY^xxqkgV|pjwRI)ZAuXvYV4^39@ts7U6MqSjR119 z0Lql3wTXSKT2Tupn*`NYnU$@Z_@%&1b@}dtTZa6cWrl3Uy0w*N0cjJVEN)P=6l|^< z_vO+{UpIA@IzV+pU{9)74(80mV#-M5acs9x`Fk+L^F}g#}aC&>(~bTQ7d-F1}j`L-adg87F6cFu}!`R)740l))WLjC#@Z zCOIhR&{LF{p^U#dt4B^a+NTF((PFK+2nW=g%~Dpd*R#ZvQPyjB@V@Q&hS%5guc392 z1|sSO{TDa(0j<7MQPh08uG%Gvw9yS&KS z1ITMf${F|7=DFK2SzRC7Kc+e>U831shO=VZ9^7P-#J7pPGX6(q?{S~DIZ*--npuX zv;0!5oQD+B%%#9#J|sk^s+&;uI^((&G;l^HX0PNO(&5epab}3wOFJ?aubHV}sYHvu zOx48+x>U(rI^Zar^4Y)r{g_O@p~!DH9^1Y_k3>|;n`aa}y0$l`r5UaDAY!uqj^Wf( z@1?=|*CBSbbV_$+woZo&+zTwF%q9X>^Gy(>BSt0#hX^DWuQ6)h1OU+$7GRW$d0R4l6KavQzT&jZh=sz8zciag5VeG+u0wcqUH^3FF?I*5_o{X!h>k za~ww#Ikm0Vb0A0g=<-t4-2gEjR?u*_s{#Uf>{C3?X)Ccy9 zFrs_GD|yT3etjRsrI=!W@T&tnsG9Q7SmX8@VF8#H+Sd!~ie+Y5bg6cpt|J&^l%x#; zD9EsOhTN7uG~UC9GM?KF)%)*j)eV`u#o-8aT%}!9qN^;5J>C!>5258u-7%Zazdoxd z1pe|?q~pT5xuA`cG`_%lDtYfbuX-RHhl8_V=;phUKzC=HgK`8B?2>PM&2mK_)kS>n z2-8pYqT%`lx`LlP+=)CazycF#7A1m9g9+GQ`GxOI{OX>#Cn%OAafqAi2!WVBBMU{| z2lF;3Wwgktfh1Vg+VXgPHC2}L#sE+TnGCVh7xp0t9lD}nMV((DrC5)jb@>Zg@ss`? zLNK0ZE>%iS4vVcZgG2<>Wl^X+R-%=W;D>ADOC(4cQ?+Yhy)h+c2V@4hyvJs0LIx>tf&pS#yP z6$~Zy+oFoCir7?T?+^XShMQarp{M8!8QuHs+}g0{#)(Cg8j05j6@LYh1w)lfBRG`QlD*Nqwv_FAI9M{hFQ zQKUhW<2k|GVBvyY=}l3NsM5hrorMM9pRr@FDsZ;gI*f@jK5m!V{|6$XJ6EcwO)`>d zSR}g43rJwIb*vNpw_BYIl*?(>4I4Etz;);S`*b-Kt@lhDDC_NX@k%<+OP&hUY>6QF zVerzo^1_tY65|kc`yJ%e8kDtCBIX-|s+ioY zh2d9X+o3hBs}y|_81g2i(8#>51pO4?RWDp#ksqHZV8$T40KraXcRkUQPL3`{iLIczoSvf&%Rll1^Qoa)G}VMK=_>lCg~4(-a0U8Mp5pgomkNA$zJ4L*v+BdKS3|mo0=(}8&Xil`E`Am* zJL|Hn%3Q6SZ2c@}6cnPY(4Sf7>ROPB?!F?PVNiXypMDHMXfhZj!A%}mHx4X*@>0k! z0dy;wBq9*DY5A-{1(tqx1{>_7cJm<}pnBF6AT;tk1`wUBVarzE5L}Vo2JHdyd+(twiS|!kX;+);rg^s2TV5 zUAr$R()D4;Rq~dwu`81<^);07HJhY45~OREfQtvN9H)r+m7XBeJ#%_40|~NFl!r6p zMGPP=F5@KA{HZ1OGGugV=Xmppwg;GoS-&#TwMblfERxE4nH6?*Sw7g1Txqzvvg3-a zU$$d4-=b#DIQu3#rw*(a<%r?U1bRNir)%?C%hPDIzPZgsw52-cz4N1;6-lN3!Ot$) z%1_kUb)It(9`i2L^iTv)Z7#{j%vp`l!oE?#4L4sm_OabiN3A_g4qZxExgA|3`Az*u z$NnHq!JMJ=;FiD45z2faY{UfhR{rmDg6z5f{+^>R^J?l>7OuubFW-d-;labVIk!3k zOTC7_d)0S_C-^a5F--U%K!zq`=dL_|Ev2 z8d{pxOb{RsT5Xl`ojwG>NH}sf-j;B9`KD+wIfpAaV)^=$4!+vHSx<;GtE#1hue%A! zw?0v*y4vAFcRK>^XtJ)_5O()EYCVO$|Nb1$OU3kWm@qIzl35;0Dxg{@?j*IpeG<3$ zCVkQ3j^Jl!R46QWetUI{CO67{24)IOZdbY*hW^AiQ=4IceSPha!Z3ayyFn`4JWJE) z2vF`ZNq(bD)q*fa_pjYfGQp611u1Vn?o2Z%9!Dq|-*-m5spOk48bZn!THOxcK0SBm zZI#n06XIm1i@Vbi)C5xl@3?RY)$!Bw?O262nJ5RmdotH?U;xs(QbdLWsxMB)f8|t? zqMLgExR0tZM_gmEoqZ;7(cLd{Xf12eEC&5G}Z0C>C-Sx(N7V*U!j zK4aoc^p>o@X!tNB_AB-`eGl9Bl|sHPTjGx!QQZs71;gIdz2d+jre&oE%3g5~?iBc4 zv27NA$GvN}`-U%k!}o91nAU_7o}{Z zejVF6Hut6RSM`N*%nwLo?} zsl{!gBDVt=8z^GV_0yui@E+Z9i?@N3#S$EU+^aBF^?Rh#N38y_)6Qs(8iR4`ci}8h zwy}hm+qk1ODiNerAKmOL%Tf4*w zWYPuEFLN)#cxXZ{qAXEq?8N*UyKU1}c#}7FlqXD892k zn9x?vU_d_)R~o=^@ZCBB@2~cSxn_%6w``Qk+AI)If za=#3Ax}RQlR7T^~EOpq+GykTJ8HC>TS8`UGK2H_mShnZ@KH1NYI_lwS-XqsrgqE2Xs7^c=8yx!N6t=*-dcM=fqi&>+3m5?b%G`^;qCa(&F)QkM_TZAh433-`3Psl*hFF(60pKq-)pY$4D0uh5uQLxOlQyQq zIRA9BzVRtvs@|Qrl!rZqd@LxlDj-9>_}CRA4$}pqNzlR9F!0=B*Z>*sMf63m1aK>w zbo?jXTQd?@EgzGPm3LCxwi^0;{yetpBJYgoxW4{^)4fx#MeIN#DTi3ZCUJ3b;}K(9 z{~E&nY*&1dE=W`#F0O7n*OhBUt!!ua&q4 zm)bt|gI&cv_EiA2WQ1WERRpBB%|62Jpad+R2cY+rCgr=p%x-Xs(w ziHd8DMyxq8z_bjY+8m}}P@yOLqYS(3MSx+Crrf{AVQ&R+LZNIK`sZ0u!qMD+of`HO zNa0RDQuV>rM1n>`gKXSm5}Qz6c!;{2=2uO)6nR5<_w0GOb(M`2iyaX0wpno2POYjX zN-fX`66`yn*$kAeQ$?8-XipI@xmoYVOE%j3$Y;Sipii6ZZ@oukx9Fu(s9bQ93fv-7 zR{U-7d2DUkRa%84YHv@YovSZEM6uaw$?@H*GVkLTU!bP_Rx;NW%%N#5qty*OV{8I7 zLwvM|g(tCSM;K`&;ZrZt;|{KKH&V!rX4+c2k9jlqZKq`YTxP-MrZxq(tR z&T3=S3LMKy;18MfLkoPf8nBn{0XF&5FNrIXbSQYC*c!WE3Mdnw3;8lz$zI?^4)JCno?Y~NfYfDihCh`aeDHT^sOs#%?Q;7d*^zh-l^)@pOB**I9HC2m?(^JReA307M%}{cb;M= z+nmwv$MW*?;LnHrgDL+j;|@Ul`Tf`g(5r1SHZ zLPKH*HEE!r=Gr3*T3v(-=rAfg3~n$inlLAN35LATB4HdLkr1grq?t`VW+yIWJl!fm z%9>qt&jEqnV_#GPYT$AP4MXNKrT3c6-P|nbr*3$GY&9bwWNle^bM3RXv)7g&YsxoJ z8C$&oJgT*)z;N6{D%k5Xoitpy;hb(nppIHnNes4`n@3_mJ!u2q;`DW$pl)G0pHdW) zR0KtfW@NW`e=z}VP#FVd>GVr%DRC2}K})CGEg>bus%ejcK&VchGJzQn{*&v>JT)gzNZL2yyFbNp8K9aYOB z(%iYN`hX|1%yOEf%9ZmU-m>62!<_gzRS!Qt4{<~VsTI1g+ehpOhkm^Wq>bz5g*6V@ zzzMFd5F^AOA7>KJflUBJd%)qgnMYxI1=cbbf#!f7qgQRVVDt=jHi@&h5tBDj62Y#Z zIM#>vp0#**n-`K0+(As~9_Gs0(D+C!=TwTPcN~Wh#|#1{OS>P@(9S}O>MKbahW=uJ zPh>Wzy?=32=IXC^JQe69}4Y-qHM?XqfHc8&< z((^#4%mG`xRbTrCj{6f9&#!LdqGxtNhPl3nCH4X{v@pasm;vN|8=2pB!}ptJ`h^pV zMLNNdCvD|*j8nOzj#Pxxjs7#VBAo;v-BdNbSHX;<>D_pl#3xHu2@(U5qt#F+VVxZ@ z&`&$(AC<3&N&jnDiwDu?@>3MYrx37Pz?^v}j%?)d!=1x()x0Fupa|U=l(*e~|D9rp|$VBN~{r5Hz#_L`R3TWvOtmeg}Bv2DXyP5{tCp0u)Boy#omj%kU zpZ`lZr#^dY^Cyj{8hdej-%aS1gSp!hI^vvPR9qnO#pagYV>ut+G@vl6D@(o$dp)rQ zA<)#4HLd`aQlyaWGw$_15i5zUHqJS^#2`o)W`Y2npTx?4{Eacaz^+{x=#k`ha}Bj+ zXl3ow--`HHKehYNCbNnM4?rz^Z(?&@a?+u<@#Ghj-us&{0{>nWw-;TsaxLRo(thwS zt@1-;k+fpaXk$Ko27QPCCH%|fl%pG}L2qUot-_PMzJRKM6Ai2HoCy@yFEUPw!0P(> zFkuFCr<}HK)glFu(SUH|(Rbgb>uN1|$1!E}pt%j1_-XS(G^hfS!Rri#u5of3!vB*h zy*%@TSUAGMFcnkBa!{tCqO4XiAj!Z6}-fTbd&xFY8#bN~9zP4LimTXn*t1Va1b%#+75+ntPMirQ)+;OLH z02de(T0YNG2B;5l@IB572f0d#AdOW&MbT7+>6Sbr>_ zE!M^i?1FCdd)?Ig2dx%@7%p%KLu(nTduV6MIpvRW(r2X<(Vc8Op&tY+cM4t8ED+wIJ^5~a z*usFdJ9lkofv?5Q4=@TJFCowL>g8kD@GyOk^iUn$q%jkIEW#xW5!Uh~-jQFl?a6EF zl1=+bq?V=E5{s8f~VGX4ieV+dP78)b$5XiQkiPf|acZN=GH zr7rD6!us%%S$beY%|Gn-^D`ZIM};*KRj#}_%=Y#TvY4{TMxrCD5E7Er_@|4N>W{9? zFib<$NHrx(?2eeMceGrcfuA^LV)L&=Tg zjJoxWU*j47Q^n#OhgjiGRycdr-QXR-yny*f5c%ZCTcf`P6tSZo&HtY6fB609>I2=? zC*uELXQp0ie2~caiMJK9T8h)u+U~z+lQBu&S&Y;?qc;fd9@3ZNKuw(x+NHqJ4YzeS zNQQ%N|NHuB_U4GojUR1sKZsQCr64-05nf(VG)HPa&2+4}%MJ^Ow4LKh{N#>}we0`7 z2bMbo&&?7kR@3ujSbPCK)BdiDmT2QAIgf7#7o2`A1GY?G8EcVkWua&wnw3sbn8PX` z844QC8lyg@Z0G;5@Q5FIEE;?Ez%{HQ4cFz(haQ(BJgN(mPj;@yKDT3Zt2-r%yWwjY zIkNbdOJI8Zmmy9(e)UMPX&_&};snhee0{*Z@vcdm;i$7yQil-lb@JQJY&`!+31}Pp zgK>x8!U(sY~nE9ygE>R@2PMKy+Mt@F!u7N9M;Z$UP6U105;hBJQbo z{J{dyr4CE0=Q9T(pF}Tur!FWc7_tTYTc-qwK*hK_hT+~XJaBMIU{L|S9U;>=zVqX= zNpXo`YS2&%QYx*TMAbm+G+YV*M{bHFBsj!h{uuG4tcKQi^a@N`f`9MgFRMN+uzO_t z#`JQ@yInWR^kJ1Q(r~4`GnL!V>#_mR zhI{+T-FZ8=?y9gW~DM`{I0j7wz0|4+=-ctm!D!;D>U>T7u7JHF4rd9G}>u|UY?O3a3#V)m-MXGbF z=}VgD!UmtV$?MX$tFT(BA7`jRBNhf2G)xu*nt78b;}=d;P4?XjXoyKlS8RU{(qDc7W`aSq1A9IojvZ7eA>bjz%BP~=AQ=(VVR;H_t)?69&&0YcAbdulJ5k7aH zC~@~xsSyOH$LilY^?soT-fV$OQR5xf6bD#4;(qaN`oG^vtKGbg9xcKLZ*a7CmH(uH zwf7o{g0Yt)kYGaH=C;jkCp_Wc*`NI@|4x}7`--+pJd;q7L!$20>`P4nHbX-*sL5RtbFRlwJ&#Alz$kEdKvf||n+$_<9MQzI zFZAuM9tu>Kl-DKmODEjE>heuWVEgeA#*kokV1fPN20iR|mEMMIJPCTv(|#O&NbB_A z8FIE<_dA6QLpb>6AKPDAR$Zd~W(~GEUe=coVCGh#FKae`(vLk70;k~r$egg8&ID1m zlN9G71j(|}%}ef{m(e~-@zOCLh8cF<=;6VboToPZp1!;o9ucDO@;t`v#JlwTWT93--VsvllMcX~3_za(1coZ3dNPR9``H&ca9d%DW+ z+a(B&98&ClzZ8S0J?&zEv#pQk>7;hWgm%jQiBRd5jZL==NBQJ_vf?L{g4zP9{qU=1ZrwK173-Pq{O64;)@Z(vhUx@ z&b5VJQ0OU>iy<5KJsisv`1n51IuIOr!Oi`AG@N%fc`&cqE^;$_y!FgWX7`Z>ErC0X`YR#R~>Zd*gd#`8VDCw6w!rIC@ZG9vighTRa)sOYvU)@!!8O1qVjH zif0H3e>(mBsz1AnS11IWGiE({<5U5;0ujRw>>0_wgGmj29cV_rxY=iLfGH$Cg^qSp0N=EGYjMz+XL%UP$w0BQ1HjkL#wYmh<~TeGskUkUzp zRg=ZoT!}LnfBk7v%eIpgbWRz8p85kus5q$UJnwYyS#s(A+bv(3K3bO6yX6uQ@T&I@l}WoU&RWpi#t0_!L8{X4zFgeSep^st zDF5}@q~JI6M#jLl@w}iYP9$F5A%#>2$Z)C;L;>ezQT!-crv|Z0Vt@oS zynq~eqkcgTVba-eZ5-Mab)*b)zWZTq>fC-mUJTKIx-E_V=9+!?H$e2Wi`&QzHN-6z zHF)F)^s1I?(W=R<{#d;TNO=QIp({%py+jGWV9p^u+XJ59|F-9qs?>;cRM~=0GZb}_ zZ-!ztCY?XG2W%U@VWj*}8LWbrSf=clf76b8CAIC3RZ^R}`XZVi)b1%*Dh=5-486$3FyZOF`?tDkYUXQ#SKE0+m z>fBRu;`5U(%$`V)o0YvNf}CM!Lpmqi)+C1kWw zS?Ihk?Q>a)6cuXD*Q5EG{jMGa*X~mFxSH;j!?6FqLW~SvIXQyTv{@ zhGwC_T#btgbBY&s%GO67JO-i0_IeCNpBYg?%;Qh~6fAxDs8#x;$sQw(Hw_Q{eXkut zm}S#Rt!{VTh8yiCHyz4l0Bidm%@Gq{U!yD6)4`F+eKY;ymS!NCjTlV*G%l%Be$D0h zN+Cb|6|cza{}`x|7>MuQcUM4Xz?1(0>p&F0+WBKNH-OU3;5ppOdtf$!368-pgY zS4&nPZ3RuApUzc@rjhw03tJ$vRn-Plh1|bj&^>h0Qj~&&zUKgU)KZwHa$`yanIkYH zv$ev2!2o(z1Qq7lTUTEUF=amRn!;+y0IQ&(=t9Ys2b3jS_y3@GEH0hZD z8l|^lYNs5CPizY`Nnif9T4aK6B&ke1K4j)ut-PbvNQ=5y2?V~ADmQ#GJlg`6a`2Vd zzHuDCpxk|4Jl@waVYfC+}J zY}?x5BRK%{n!;o*69xoEN?~K$0`XQ@R0WWUQ+6Ma!K$rFM_KDa7e}}r=QR^1u>}-b z2{N&*i&NLJ39GFmL1Bx}3g%Tc#1ZW>G38}C43UBc!S|Lob_6 z0C);mx!4vHMyA(@ArdIXSr&P`Zct8Ns;`MTRH%fB_||>izpR{Etx)Cal;BMslfzqx zM{WROw=MYe%T4b90U4hR$ttuW-Yf8q)Gi<^yaTq?`ZIv?Zlhzu3yfS(2mBx+57G(#rwurPdsrJ?5uTf+(kW#qP-w!)?zuEfe*3p|I{-0NWs*)zmF zTK+VujA|Pr@8A~HWS-B_wI%34$Ysy`Or zZ#f`+EgB<3-m>xO^Y;X$oTctS56D(|XRLdSv-r4SIye%rrF2FgAKMPws#hP(uEiEl zTyRUzlT~-voYnwd>_iGVP$CJGU*YE`C0>@Be?+E)*hp5g%&npmDGX#z1F%)Xp)SO5 zJh97ZU~2=f7)Y8z%C^^|c?fKWdG?a` zylV8VK98Hy3_ZTgBb$Uj0Y(>Vfh;?pH26)m6i^Ee1O+C+*Yfk@6=~o~q?CwrFPE%j zg#b;4ff%c*;a@vu*bEiIWU#7UHcT?4`*eE*vLcymwN{~{b)J_yv(c~cR-dJIzw!yP zf>$2U(Ig%g;F$vcs@F=7r#EEJS9e<&P}@{MPc462zfM@w&{u^KZ7GYT@Z3Z)d@m1K zK4zm44VG0V$ZA`=Kq=dB4%0Z4>`tM4P0xP^;550RtoAAqaJ^xv+)KHjdJxDqD?Pwk z3U943>{U>>-^FJrg{Q;_f<8Z;7UO46=9nEIpjIg1U>>rRNMb9}0PsGO3TZk5fvJ=O zNW*IvuqyGhUv1HZ3ctj8u1Xk+YJq;zbRr}%l3UJ9kemqwthKE(8r3iuL9Ea!l)xMU zs^XE26d#$Mm<->moS_s_l^E6?j!|U>8j(-_wggOSlsH_9NrhcCfg>Z=H#b`;T?%-O zq?*LD5Plu!&$C|HP6Kd=1ZbLb=I>O*>tc*ef~Pm9%hwTh`Lb|s1jSnP9O(%J+h!g> zDzS1Cx7+8=DKV@FZu0X$6I+>E1r0I4)=CVBs`8B7St3{RYLK?+VPXCX8>X|@5hLp5 z@;i$ZP>CT$lK_+%ViaQL-wd|Bfq>wN&p)PZKA2V2w3V|VrFzGR;bxfLgk*X#DP;@T zG67U|^B6u>o>JDfm>nVgy1Ox{hP1-^X2=tuZ<2SgNbA6>(au&mAefX*JRd3eT>AI6 zCkE=?Y_JzNy%NK>bth6C%|WsPG-`5L+b%)Y7txvce%|qXcy>|o%(<~uj;G;Ziv&ay z5;q~eAZ|8*21Ab#DbpALW3}aJDl365$e7Cc2r|)Nt!ILJUc3?*FsIs{cyn6u9JI>; zejUnM6HJx{&ly>j?P8>+#2Qud!@;&xr{G2wW@Vc*6UyDQ3n{g}vZAdOCg5E6o*-w( zk0Ed^jbybA=U^+k96YjfO&_31FH1bU**5m;0OPI74Q3?z4x7nrX)vCbc6mB@h1Le! zBpf1@^G`3UL>Jj!Crs}HXUfV68is?Aif=F}L*kK5&H=Ec1egnAN-IWF0b6PO{!lqa zRT?GmEj^Qyg^}X}Y^7Q$`8T1l3Bs+X^6RNYBUMVm){W7TF)LYWOj!=|1T>;dELCX4 zuv_X22mdyuCujg-UoReO{)-dVV&WUimI+4WKtDq*yeQfU zBmpah88)1ht@$F!RB`Juz%}ZVRMU7EEN zH&|qqPx@wn1+2T!#mdLz@%gu67^VR@k04}0c@=X zCa$S1jj$C;I9Oqm=vWrxC0T7%l!Aa8rNC1POng(Nw-yBaVI_$@FAq*fq!cWiQMqaH z0w%V#3jVqynGmarK{6p&>p+F+6O$>#oMYU;BqjhvjdV}Yj~1kXt#6~mWJu*%(G<2t z&PFwl$@wUha=nzuwXW&DqsW*mQA=4>p0<(OVqohF!fDEeS>>iR4XZb6E9+Uv*2Ng< zD@Vpe!pSm;WE*b_vto!5o!7zXozAJlWP*&FTSD5>b5LR{;Jqhh37805hPD7b+-l3v zHiko<$eib+wHDSK2-4ISPcLOQ4z>)d1q|RI^qMxq9qN#(@?18bX6AsmB1I(TjQjlo zFWWYr%oaHwqpxi(fQA92tVNo-Xq0DLST=$AmAhwEogGicT#7PJYgOY_DQQT`&VsDO zDqEEle>XX|T2yYu3*bkPZ3eT+R@g}J&Xq&$daOH3pFsl{wL(KSu&uxobMWA*LN-aZ zFlH0#D`JpweCt9 zyKC8CE<6VV(=>_N9s$M|uT($XC<_8gR&^oMWsLxQDf)OTy#NGWm6##DZ7H(0$K@WR z@n9M=cfGd8fK|DKeL#>NWzM|-=N`Ymktc2d4)`!$G+~YKz_Mrh@1HyqTV)djtU9W4 zfy9xCCje@Sr?hqtfD*$CAPtaA)#scNj%tkwTuB> zjU;2@Ao1OR60>#Q^P1cr)Vi4R(Vnijkv|6)DF{a^!rvaE5WI`4~10& z*y3STNGoOoOZ6F$GH30&7Fc#z3EU;X#5~0`7<^rN0XSoJ1eiDn?YtXOs=BK)_b-p0 zEREK}Hd&`o+tw<@Jdn~FV{HbELbI8q;nO6b0Xh+%4iZTgsNHb>vL)&)@|UM*~&pm&=g4Xq-2Gp=WKmRC6v1jGFySK3Ufxe(W9v^V(sW{waXlcnF*et z?#KQ6du`_azDKG*?3oQ9mcq00(2Ym18*o>>cx-c~;n~)zwsQf3+wFOV9FA{$>CaDs zTk-i4SXIsjU>gREf$6`P1ZRa&B^xj&LyWa+mjbLv8V6K}A?V=zD_1LJOSz^>h`WtC z!hn*1Y$Xga)xt$3#BFO-N2y3vrh+lT)^q3MjHT#hX>84dwS2;4&s+>mfxda^XOx7Fv@7Y?O>E+maYvUfsm3w$Z7k&5Q0S*n5gV>T5r-0MWp zacnD>B2eNdLZ7vd`}g+v`w{=AKm94*Rv~?>Ekh}wzyl5?GMDX2Rl)QCO%q}&F_Jsk zSzgb4l*raj2?1WfdMS4VPY09oj_~=JMWZREa$=w@alu?P40G5#Qfn0BmJ~{H;FFT9 zT3caj3^JIEr<)hH_(&R7O-rC01Bl#qT{N;HP}Z6ieY{3Aor^QRNjj$PrakVr7cw93 zc?WTO1RSc8rEK8S`}^6!;AKk<*d|M9B#{Y5c7YVOR;gsW9Kc9@I^Zrr$9*1=89IV6 zGe$7PQ&s{O^`fj@WZgH0D$&&uxIKn_?;&<)G~JgfR%DsuIl#^ziJ`I9S}j3_ znEWwDqRHQO1JEe<1DvRtv3>-8dkn&{kD5H<(nQ8L}e7^HD{gv#Pc< zy1|FU6kAso~^p)_z6a+${?c1^2pf6?Hxo8B8m&La-c@3L$o^8|6 z+bXPfRShpCU@a)!rvMtGUbEeGaZclAC~c+8i6qA00VFrP7E+U$1St!ow*>?ZY=tX< z$(jeAApw%ONo<>3uN)fe9Pj(BM9GQ?CPP4BXO|t|PznibBsddQtZW4Rw zHMJ(AA#Mfm)&hQGnzsagHLzNzz<0p9;~U#HjqU*}vBA1hKSkhdiMbIlwcQN>7y(V0z+9u-swK~j8|xw3Rey!lQO)kXke?7 zeB7tAlVPkil5!9srO-Bg<8`h%lyx+`V8}<+9+R!sIrj(}pz*A>OzLjC&?5Y!JEf?v zj#?7{%&mAqUO8e%CxiY#VXAfJhCd2X#2Ml_gIEievpI{JSC zzJgOm1S_-JRkR=BkWqC}b-`G0iX!ImUQt)$^ay)37JhQqsa*v{wv zM_Bny7%hak$z+myL}pbt%pC%m6|tW^8kh;Cl4BRMO8;9FV(o3hfe$R`#anGRrQ94D zrsSDkiG!^14bF8@iZkH)*oR)WEnW+t#EpM$Ip2;mnjX`U-VRzg)Pm6{JGO~GbQ`V3 zh_GrJB*sGlmTZm88$>DaLJM-dMux@_^b`bSj>wdb+4l0t1V~~Ig^BS%y_wKpQU+8h zF$I!mCKz1?`EUWGdkM3UWCVcRwyd-ZAndIiDcy^P173@X36Adjm!WsVVO<36gVYEE zJmsPhZJV-xb=1(!Qt}5daT>NYW(eoN6BdE9-}l!jD||;;W4}tWRpl@@?XVq?;#z)s z!!3W@pG|uFnC?#8Ww~uSCQrFN8$>`>)jQXlCm9gdWn1B+yz|dfw2u-8Wd7;X^eOi_ z61Pt#e2Prk6QjLZcpxkjXk>eqxNF?v=aq;xwi1m&HMRgoHA9-+*9(Nx_uWfCj1*>J z_+VgGj=2Fv>yh2swt4!*{IzR0v_1j3X`eUunS?V@T7+d#{=`EYvK#2VX&04U~$B-;Y< z{P9Se(D#M+#lyQP4O>+~2FvY3JO&W);f_2-uS!2dN~BqpWQ@>d5M_9tw|J6jJGh8r z%O{hOG5rBYU?RR)FW^Io0UPxvA=k^qT^sQR!U(Mv8Y;UBnonosTX%$aVwhY2bFIfd!xiU>nu~6nI_S)^#VG z&o6;4Rpj01jDU^H5SarR2hk45rV#Mq0BH1dB2OAv$q}f945I}(E5`7t9rPf70u`qY$YM1ufKf<6dL z(`62ha;vViF1JwC$SuRN-fUYwY(0Qp4iHnu1KdQVWX**Iam&y5@BTYO4h{<-0BTvVwyJn9WX+t0;WoyfN%K(|| znt94fLStRBpm81hD8aq}k^KRNaolybe{PlfI1H*&f}-2yjaP%#BsHF56;`#Cyh^9kbF4gq6ZWtCWLwc_X|cYm9TU@RpL*#l0hthB%hNQAIWjHqKFRw!YXj z^q4($p2WSm>$GHibK9SlL3l%n`=mIxQb4seBDL4j4v}rhGg3<$)dG{I5|WZ$bdeg% zn8;;7OD256^3mNd2_IDfB*M#S%Zw7^I{HFi9|wv4Q^GMs2* z@ZmRWbW{>r*2R~h>^1F_#?KqQhj3(`2BTLN_(*p~rm5G<_7KQqZ176J%=vpsL(lpswDhzm9alwKpP} zwehSpx`k2~3AAd<5D3-U;TBNEIGV`8NM9z58I3-WoCajvvhhYmNJKiB@3Z6`c z0h{<&k{*Exl3uXwS#+6e93=rVja-Q`lNs*&rqmh+2?Bv>I?oNVgDH3cc+bqsx-fD} z>LO!KiEK*ka)6S5)Ac&)Qfh&>qfN#zftvz!ESu8u)AWfrvW0cYWaXA&66juT0a)h82s|IX_#V^FekimD+Mjdl zr6E>jsCR~~jcl~(N*cBBuPQeSz*nl89;2(3%6PNVDDjs&!h%2-F%0||grsk-%l6-@ zX{UFb7$6gsfOog5S{kJK>N{ctfr%cCI3`2bp0K;XtA@1zeEQA+f>`MChV*Fwp(-nd zQyQJJV^y}Iy8ueb`USE*nWuRjV0y1)oZf~WyYE}YbYGii!kGZB%GQ#Y0Nyc%`G8ro zt-WeH=yL3Tn%pvdMd8U{!Ejm0Q2Pis%|sRHapZ;qNJGgmWd3cG?+IQPWj-|`$%s8m z05+5cpb5wxgxmBClgVw8iSI)`dhsoWNe^I@b}xp^8w3La5IF<7S_9L1G^-Ni5Q!Jz zt^f-fqcJjp-DKpM^qo6%yed8iS!Ao^8I}5RgE8W`T)k`re=Pyi^*WLPBF%WrS@8zh zG5Y?kgFo8idj#(pTO-c!Qq zi79Qu0k)eFj<7emO`pHBVHPcufZmab{b4OJGJW|6=+=~YvvPyuo1Pm|6~hMu18(%o ze*@5Kq8O~VXO;UOjy!AcWnnwWe$iNugP>JynH=DbN}0!OP)kXkkJ5X{qQ6L>14eL8gTxduu*OC+Y)SsT11o_fYZlO4KnY3Q2t5B9 z@d;$nkrHSJ;%>nN1DnDJ#yA2rddnE0-LVWLZoQO|l#V$Q3mB7wE?5^nx5NoN+8WQu z?^6R>`Xe9EIM1y1@@|oB8d)#%#g5)eRVc|8Yn-gaHf5jVwxv{RdcSJAQG8gVE)0Q2 zZuE}ZA%Ia8b@99?V|5bdX0FwhcdTY zxD;R_MoX_mb`8#5@CMb3Y`0{b0pEF95Vs_mE)0SF+Xv%FpgRBCcYbeUV8AS(eE_`R zFznck>T(@bT_We(agvrXVMo2S6F9b2Bu%n85NzR*F_b349dla-cv_5qBl_~(31=&@ zc2{s;fAa`1)UFHQ;CXkuIid2HT7kvwy=KEpwX~If5z;$($l`MnL z1O}>BvO_W;e|&f8j$4mOOzrg9#v@~DcNDNp&<>6wf*d~r5+&l0W*($M; zl|;5mY%2l8a8nXcb~U`A!l0CJEA`1T| zv$G%*c4#`B3ENxwe0wR)ZPq&l<}4HYy!ZeZdDB&k9u3Z%?EQJc+kvLJs*|;we4qAr zYSvbDgvCt4ZQgVmz$`cNyYy`_vBrJ7OiCo^U2$8a_(dXc% z+GT>AYjG^JU<6wkDeDC=d;l7~cp1}QCSp{(LR(dZMSrsd8Ud(^Q@g=1hDp0oCf;=B zeEQUse1dhY7uf>sDJxZ#RWS{9^c0TVv{+%wq?A>KQkaYw)1ERO8FQrf`t-dpaT5*d z_nHzs=i1ZoVaNfAX&<;2R+K4#(UzxAR->*A1Mfd?$7>O6b@e~5$MfVy+`r#y5C`GD0wc)8 zk3pDzU$t|82oXCfnN^Ff>v4Fta{J^)V@l$B<2^fN3*(WLcdw6w(IU@UawY>TBg5Or z2h^-&T}@-*_=V>`y-70dy9iASbPE|nwj-d|6j_N$O#4@Y+p1peZI};@mYBKG7dAm^ zBt8RjW-Lo&%62J$rh3QvN2LxPUU*mmSY z023JJ$YdMh`@Hb!mGpLOdgG7)CRy05s*$p2N!e5INn`Z;En2%OO+#&HieQ3A%6x1n z`@xZkWhIf!u^r^VS>pTWzFs7sY>jZ#(sQL$J)O_zd$%}=QjksGN*UHD7b$<>Yw$@7 zP3Ztr!p#L*28cH?D|x#m%csvswLR&p2D}eI)29Gz^hRtDWz%KVqFXB!jvQc5s*yjL zqa2@`oC$%Uj$+gMvROFuvT7~etB`2CNy^a?11@0xgeu7Fp1rvc0?IZUxn6CEg&p-cMf4_MhO;YYDVLiD63p3J?Y3Y(O zYntXwjATxqPx~P=ccj!yW8<07SJKszWCSuk86fyH5!r~$DU9_Dou@xq7|WyB-ImcDYS} z=9V*5YD7w;@KvPA+0mc7Fox#V&+kyUM*u`40J1c-WBSmM7Km(t_Q4~y8(I}m$^^*M zb##&8!+2Gj(8KpDbG|cVc$y}{7FZXoBv=X6A~7GJ_40|A1IRRb0dBIj%dB!vUyBk_ zBXORY=mH8@9L)M*6il(m0x4)guSA z)V1rx7}O(lUs)jiBGuSu$}kddghnN#eC3~)Y&0GaDVsuu7m=zMC5CY>>7+N#`S z!M`^&kun~RRBf@uGDFoqKG*4LRVf|i_$e`hrmFUi#aOY&4Bi ztvwmNMpa6nxRXDl_io5~Wf$b81qO7zRsKR%sB%zkN%~qGYq2D84#aro^#)Y6l--5q zdYu0yf?%Q~2P!o;y!IL$o0UGL(%YgD(^JqdYS}SB253>WFdUG#05?ZbOIX#h_U`N0 zT;Tm8QTAMI-V?#PGNBJ>56HqXrqRvv&s`e(1XYoVGf7jgE}-13d?NK~**^p-39?Px zhWVFi03|Ya%u24Y9cT!0Q$nitS0LXdjs7qE>ILws2^@tk0U+yj1O$Lo@_>!&D+KqX zG_W8u$P=ws2`O^!YL4fx(H^vaEwn$F*{y*E4Qb&`9qH5Rf7Dwt%B* zc)*c>o`V3|r(Q<^%US|FyL(52@=OC$`exwCDp^T{wGSNkJ6cB4=n|Jc|0R1Xb*u}+ z!H6P~Ko=(c^*AX;PKJ5)2_oOSz+|SdwURZ`bOrC@<;O6mbl;pe2roJU7SqBI)3yOv zwNiRk7QnDwOP`T1xhgE|kb^Ffk6gT^uxy8N%gkI0hi**(%YrOtf|y$5s(_&JGTH%0 zYgN5$ne&7gmaj)c8z^qy69D- z_~!D#I5Gibns#e6vWfA!d_es=)>XSw)y#X1Zh=wCq=~vj<^Zvu9f} z%S6}$N_b4aG#cS(itNb2s@yyd4Gd`XN^R1hE=w=qkzK87Syy3!WcFN(cGbGFa@@kL zWCv^4GI1JKB9cGSp8c|=#|TwF6wKwWuY{}@22?2ly_O^+XnHyEGJO~z5bYpq7i^LF z&dRr#qcASV*E-k&Ljsd=_4ETPSmK>L@nFU#rxFy{dqo^ z`z7>ZTDI^uVX_^&y zeL0ESpOvbTWlRJdMeX^NW8`0d{p(-XUr$?RL18UDN(IJIwYzPkF)&R7bfv!woNrYl z0a0&CSs<-pbcR=T=2k-bgSKP$VGd|P_6m+{t0EKd@y|B0>F;iiwQJehWXL|A^fk5; zWe&{Er3A?6<7IkTAbUVc?K#UxEf`s}Fe@d|bsB^ub8Dnhv&tF)WR1pQJROpMyi#rf z7!Z`cH(_K}t?>@R^I6{XHgOzdI?S+Fs-@mAK4)_qRMo<{$pwA3U*fX>vOD%}jv}$S z%pJ8b%yTQ@2^>|sa!eo1NDftV8z$%qa-f!S_mV{|c(G?RSG1~jph$qxW#Q@Dil)W{ z3^CrVqZZ6;&%EzfuCI*}B~M*IM;aItfCu8c!oHl2)3aj2+^VK$E2mLng{K#-hr$0? zfI~cuK`G~^WDo}o40MH*iTgo2W4=Gjbx{JX)V8KuZy7LM(6FL`nZSX%a!cShvR}|S zXU+t;!_SgRt$}R4tkMvO#N|xj_*?%7l(;bw69XnTw7c-nTrOz#%gRGB=@V4t?^nl; zIV1Z4apYDtvc>R@#sLd%t0Em`vnWM-%0)}!+dQ&;Fg=>~8Xb}L!~o;zQ`Q(Juo(Yj zO76m^Zw1mUxfa<`HO5iEOgP8_)1Gk34dRJ0j;1TI+65+ zQoJL7y^SVw6a-)G`A-au>Hc(X3Z@O~mC2NRJ@EF)fnkD7(#YGVxiZ7Z{4r!q@IDT? zUYfwxXmo>=#N3qdjsYze^MDO1bH@AIzebq&y?eIWU5ZSML5fWD0*&VIY8(Y+SD;B& zj3k*4Kmf}u=|WJMr!arW*q+^?ssOwR|2v2cM0kP|q<%;h|D&~BM{{)=QXhQ8f^ zE{JESl*w88I=2jdQ?xk7Behq;1K~V#+ch1FG#mg^wu34%O?QVAP{O`u{WeddQ6vce z$YeF*YjMXusji6!*41a~lOm36RoG%KrM+jN?ESk!Q&`pLD0Fp{V|uhXBfI7%I`S83 zWH_t;&Z^l0M)dJDYs?JseJDPTJK_9v2rIRfzpy$oB+y>_M^YWyi14IGR6OJB8FIUq5zqevNO&pa_?FhHDxBRw21`@%TV$R1gX_42{=f!eJ` zS*eL+1O-_R9h*z+sN}h78Kalxea2qCt2G@K95@$nYv6&%tNQsJZjIV=NMFY5#rG62 zeqwwJ*bv66VgR9~xyY(y{vx83XM8ePX);O|_sBh+7A1l2>um}wklY;QGxht;#CHUG zL!iJLr`PEG@ofDg7Ltz-TK3NAJ$^hAqrEOIm`dGRl`~TAgT^Ci7D~wg3yAgv@Rha| z6OFYr9nODZ?Qqqwqpk##)g{x&1hQ~!H(-Otbgx=iC0_0dcnc#%>uK)@^qAhT&&V~A z9lKpSXk>!vL7J5b!H$W^FuiTxC{wgESqbm~`FRw8Xg8il2<*1EE_0F3MkZA%}o zB$!x0uyyPrt$}O@dT~{s#4{AH7eHRiGo7u7q-+O(06urPk3c!A$?}wo1U06S0|cfd zkYP{Zak_pBG)R>gFVo~Et2U()+w^zB=S?t_x{!dZrJ-=nfqm1ArW9V1AihCJ)#fUh zn=^+@GAF~6(a0QRjDJZ_HV0x#j4TK+UO=Y717$u5gkH}PNxxm| zdUsONl_%URdDD?$Wc$c`v;bzo9WgZYqAFf5Qq+rge*!#!r?CK_1mFR=UR`AHbv?gL z_6KLY5{;_dP`w3Dza9EWU_l@PcwMkYCf>~viQ$}sl4I)uO-WxVLwJ!PB~Q4`RVf?C zJh8jP@W+@)PX^R(d0TbF*Trx{M=IebfK3qutnf077l1*6bB!2-FsewA3@_mM!v#L) zN?Nqf&5g(!O6jKaOonV6jgrQ_yiFVl$^jZm;pko)bZ>dCDQK9q9wjiJKo7D7GD$Dv znIMZ6u8OY;U@FOuLdgYn`Mlb#JuzuEm$=2+Ax{ihEd)*PbN5wO5`6<{4ANDjOsqt3 z_V_-jM?f0dYh<{KkYOAHJbQ2q>wQ82jZqTf`f^3)}EU#0-lb5MlwEn zVfwWY6xqs5RFR1_Vg!&aA7EXH^H=I97ns7a5sp7fpAGJRBnCfiUqeSbj1Mqml@e~N zy{Z?}l3p`dc-I zn~$c?kswlLl3pg*IJ_3olm^kXDuB^0hbfi(yANyhoy4Tab0+pDa{uIs&p(rn1OhLB zM(b5|M@P$VI`YbY{}E%ZMtqz6`o};1;oPGf%lr3Z{o05`{WcJ)NK{7l&oIa zv8RLq0xj`M*Z%XwN_9ob7_($i7eP!_M{DQf=15gjF!);Z+E%XA2w1@FKXEO5IOuXD zb0m=KRT8Zvi}XMx$9&Sq^8xb73e!;++-F9kS2O}2(DL8$DmM!mwtx5XGHeSa0kbtR z2S8Mkk%?)FWC^SRY#BJWlwo-{YM(s)uGbi2w-)P;*=|JseBx{GC_9QqII@yO%K%EU zUfE*AN*mPo0h0jegCM{V%b0Ei6yrQ{+Ywp%e)dyUyG`HbrCHDi@qBs;uABZCs4v@#Bh_5n1S%B z9DD~Fq$KE71%Cm=1H`6u%$A^?Rhb}baqMcjwF%NO2S8xN3^BSAxVWQ2mtW z9PBoeZs(|!t*YoErsETDpTuj#(@&`?9OG+m4K2uS6xK)}9EByDg3m_@xRTEs5_F`` zN*2?@O6_n=?~g4*(IfPAx=rV|M~x}vj&f7v%w;&+Kv-t$D{ zzFzt?`-&clXe92qyYERPZbJ@0z{P7G06D6c*D@RsZ zeC3dRrpPwQFib|!wrzz^&q_&`7|1^dSg6XncB0T39Bg38>cV4fhDg_xrWfODQmNRx+_Ea;5aLWonT{UDJ%j|Ic^_ z5wGzTH&khnt)rDFYb+zJPfYgxvu9Z%D4TRSpyB;dmZI9$u)DfS(h15&T$?bFZH z6`pbJnX=c*N@)NV2F=+KrZ*Z7{IKO;sX=8sXgr%xLs+@{r{IMVW-kM6MkyTMr72qr z?|L!~rpI#ppP{SiRl~Jxv}LwWfUO{==?eI)kYRlHw1!gOBwau}4Q+RUgKb; z+GA?>Sv%5~{ev|(YxH4djsfazAHBU2GG$%+3bnvCBomo1Cr*!~jGy3T$W-$wAEWQ% z08KZS5+(*(!>UaQD2dyBx^k{1AJ}d*^c=4I{QM$~u2C1D1d!n=E!;5;@*NG=l3};V zQwhkYeJHuf=K4&Ti^dV?6@ZVG09MrwH@Y==X4_)g3FOv*b;-o#_|{8?Va|jDaklmW z1Zm_*Jm3n% z?iCL=5op`};nxcwl{CtMeDYLn+X-kG;(LFCb|VwvmaOqw9`i~-U1TZol^j)dSdoku@R-(gau$Xd#9H zzdY;J;yd=T5z*>cd1>FW`L z3v`4L002M$Nkl4P5ttUI`eTO=}YdH39+VOjh zmeGhW#*o3f@aOVHOTebWg=4cF;Ra`iXAv{865bMcm(MLyMvB^%ih6Z9Hl>NOqf8$! z+l|cJ_>@(91&k5B0^ryh4fRoJ$7{i;`Y@1Hk==-<-?sIV)uj<=oEBKgQCJLh z=3^`VaO2=PV46Y$IQA*PtSAA&s$N)c?Jtg$GgQ(GpA~N;maSogaX`;fsm*03{dL(t zr6|SE7Q?S`0DcQQs+udd50XG2S<%aaKuOH`ZE1?EN>gtwTcFRn1u|?3{T+Dzt#kg4 zfY95Nv*P_kFm>T!4{0pB&&;{-*G@g0gd_8m|kAXn7ZgK(8O9O zr&mqi&|3DH_Pa?bKrA{ktR*XrY~?pF-HTzrk+skZE5Ut*+6TGbZA$|SfCC?!T!Zsx z2H+dmj~jt-xGL1s({=|uzfN#qTc-lv=&MZaE3IZ z5~d?TBtRe8*QQl7nM`}0DVNE_j%49LrCuKIqs%a1nLJ&@6$p9_Kf?HyLBf2zu*EcD z&d58IgFz|#o+QQyO3@~ApwY=>q)aULIC=8NAk>baC1d++gv#L7HOe`iG%LKwvgvUp+Jq`Cd1a^b1CkRyEmn5*#m7lk=jG&|u zm`bhc*a#X-KQHH=-m}Pty$&U} zsz9FvQdJgwB2^t_j4T{A?tQzFug-27LzA*8wYPS?s(rd^OzHCmC<~30aMe6>!xTt? zjBiOTzDeB(pTm@n$TsmYY3aj~X|ioGC7{1u;Tw?vE;`EDZVEB}-A zZQk?JQ)VKoV%p)%``)e%wd9{tqv*di>i0rCFNd!!71sKs(D?rX*IX+LV}V6UXz+T&5>el?%9I;_%DV{-fHB%*s&i zg~{~dzxJO$BHNtI+qx97_erKDH;skf`NwF?!I7RoJDk#nKzsHfYOiEaR;oGR^K4Z) zFtVd1-xdorrS?3Lr5Jwcx3j)Y6{BJ-8B+avDcUaoX-9z)FqN<>)Uze8SBo05X?hkK<)t zrn~U8Eq&(xEZ~zB2G3l^ICAJq$WUD2dFpp(d~UMc{65czMky1yqug}>&Za9J>+hck zcs@c^DRH=4BUQ;PlUp)az=h}1r6oRpE!X>)b4~*YH>GqnfA4YxdI6-P_8OTxwm{B~ zf}o>J69#HDh{h+v)@b>B>Ae)z!EBDNMQzAlT&CQ-ZrCdLfNVe|^R_Ol1i8IOy+{k> ztlc1Zv*7e(JXVCLsgT>0erYzBaDajYRMrzPs|obNr3UuyNFECgH&bJ z6`DRWD^uE&1HqyNcGN{FSRny=jeDGw^GvYY0khJmnsQzI;bf{c%Ay~BKXvdPQ@dk# zksSr#;$6gIe9Aqe(sl^E?VG-mDu=Z+rIxeSy^&*$V_M!a__eE2CVq38WI4#T)$SIs z(z)1uXBa5}zd%60bYb#I5BunSA(dcCq2yQ~I|j7#$z;5_uvfwZ^0)hZF4-bQEk+RZ zv#IK1@^3q090g2C@V-B6bPj-VaHAPk@~I&W(#yw@l@gg_n*4Jn7BQ5*D*yiXzcJkf zBe>E|70(?@|j?dXp(_LS* zz}o3^z-L%#dvd^t7!GXvZoF%^SPpV6Gjv&EFOLBPH_=fwC4oIvO-aMndd3m>len&y z*$5;|Mw8wVo-M1GrqI;e&dbTnQbl%5ESnO!#mLTwdVi@ZiPn=1YD6o^u4Y5Mu4V*H z{;iZJ49Kc9l&+9Zo=T=*0Qt^5Uym3^?PB7$SJeVaegB!~hRjD+)s7eNMpQ~k;8>}< z$7lgWy@49#dh?VMSA`P*Y<S#mlBHW0`cU&4g znNVVAN{!tSDJ<6hu+g>1T|DOmx@b781;e~*&Uf1H)Y`gGS&;#Fq4C!rfBfM;I?KHs zHN0IlWVuPINXOT1q8;vaRpZ75 znsr;x0y#7q=B%YEJvaF^=|O5VMO8~mK%){~qhmiL-9^bq(0Z_zrgV{dVY4z%lQWDL z041>YHpl2i#ygVbgSSS`$?yUe3}IcvHM#=Ewgh@Rd~O=-H^qXBp^%>)?wA`&AK*XWoaUa1=){Th|0$2-~(@I(TzgNS4(9D5B`IcJDu zl_nnop^9t`S?pM&V@g>y&wL2PZ905PZyT1C+HD$Oy)X?yEjG-s#>OE5EyS+YCiy!e zYll5Z)%0l?GH+0GeK9E60-H-rStD6k)+h@CkwXHcYFLSvIcnF&6gJ$|beEJQF)H`QZwz328p#()M zAp^RYkf~xE8=7rXJduF?o>A>flGv0+G^KV7&;q$J2NI{rKTV_CCk=)G2BvgNg$$NGNC8tYZ9JMbk~Py!l6Uu0O9PnH1Bxlj2Wszi+M)C}I4ZQ+A^aRjt%#ifNo|Ryi4G zVI{p7=2-y+eVWYm;*n%2589MHE^(Kaa#K3lrSq`WILYGMFSffk* z`rF_BR-vDbq^IM16M&jVwh$nT-5l<{8b%<9HTG(ba5C93WlO-h`L8^9QWChoXfDua z5JN9Ar&l67KaSsj^AywQ+~m5-`~|WFWO(5yC|j5k=4RIIJ4uN~iyTf6Bhwt0uLd9x zc2fdUUdfr|$Ui;orbtH_e_PK2j$&Q^4J@O58naqEE2RWTBOLvOa(aMXuPWS8Bhbs^ zMFPsi17u19qbnt{(T-UW0OELke_tbws8Ng9mGQe&y}FtrC)SuoSJiz7@Y(u$7}`aE z^x9SVXv|Z|Zz3cgCP>P@T7YP%Ot}nb5r9Skyyg=pONs2JwZIupLjaeJ?#n~mHS^SB zy&5aQ9SH12Li5pNm&1tx=5vC^)j<5h02ASg1X-_;Q zW-@}@BK@&Qv*)^_PC`-^y;~1^2c9%#fIh>0G5ntCuOPYANS2;+Cd=9*Wg1__TI80o zz@`8l%bDaL7qwH0E$J?fM(_vx0Rh;VSXb}R+iF3^9Esa^ zs0-37H8-oS-}0C{^Q7VQwZrX6Z~wYvdIBZRK;MK&6M-^8wq$k_wIFp_-f!1JpCu!RNqG2@~5FoIh|GplRI2jVdYrS1Q9|Kf{ z7W!6!`+orzPfzT^Ofa6?B>`g56re9e<{SVTjV%*cSI636ChbXF7e*-2lml#YmSZ#$ z_-4p(W0vMPfJZ05(t1&}@ zG}-1O7j@ks=Xb94@bue_KwOXWyzs=49ebQ!EN2dYDw!_5y*z%UWBvVAOF-|KUhbF) zo`63dSQQYeiCe%?;3)wm;i%fS@NTh>wHEw(b#-hvYiHO*rAFYj@X7N|JE`wR8pqbo zMBZymRwFA_KU_7+c+TA&#t?`A+vdu81&yp)o}iNB1OXYTIv&>AZ>`zQ{ zn|=dFcT8NBzCr03=E*^{zf#9{A{l;{;uA*dYP}5#Bw)2Jz&MPX}TXAnigv-8Pnz|F$vb&8axYC3lr;VB4+EsV*B)?x+Uoa9~zkhpY&Q<0}!f| zV*m^vTg?sAz2t#<9kpX~Oo&`p(@k%~F!bm1r z)mX+?(#uUaHU*di&>$pD%ZKgX6=WJydLLEcm?z+vAsJBD-XZbyj>-0UrN?_}vW|Q( zeMW%ecALJ<9d)Vpp|pTwT{JLqFVK;=E+hJTKOZeM>Z&~rvM)9tjeR$igd-)OBx*OS z*bW3rY+F7VjG^pd;bgh##lvKCqY)SZ;K^P1CTj8CbWz_AUfN5e@TB;&N`poxSYbSs^n&?^R z{ZwF-i~#VWamVrUErwq&te1nJaWr&ytg8j`bUW{mL+jNQ!%CRK0VC6Bl!aq+lfm5Z zl$ff?ZZ*~j1lJc0&}3@ z-%;K%GDBp~j>Po+N78QxLouoXrW6^P+e&~J^DIiTFGKgLr8m(c2)L;##mL$#Mdk^2 z{2@bfs0D5r#}?i(L&s)iMOJSzgFIRb*dMT}N6?atzR+H58A>L856Hy$T4YL(z&RgB zgYXTNd8+!iLU?aQfC2iyVv(Rhrt~WpnNL;X7GTSyH&=pLQNn#6(vyi=IMZz5_c(FW zwNOU73w&)3>1b9ZnchkrGdIf`bI7oq58UH>@Lqu7HdlSWjoj*OE^L-|xxh5HJ^27u z@_)0EK&cVPt<)^Xe%Nzp8P1flMxamAdc=+lVfyz4xY73r;U@mjY`S)zta}NKj-0im z4;w`PUXUOSftH@Am8RVD=G;W$)^>zP0>xeA^T>(Eck~E=Fw_V;C&0kAYHr)Y0@>BN z2))~hs_(s+xCLjR#$+@YnU$< z%6u?dWCCJcaON1_1Xzn)%wIc(rmC0ksC^C$>1l{Lr*upyrvwN@Vnh3wQqpWOm<+#? zUR|c+H6EAmVly{aI0`^7a6QhSIDi&FyCrLv@tmjEVwUJh@i4NMd@z{@{l98J=! zwEO6sZ@?ZbU`jZBOxZQI0V^8W?~AUau_^feYSIFDGB^-s2oyXWy@(^sBxgrm_IGq6 zSs=@6x%Amw$tGG7IEt{*0p>6bJ)C)q8Qq)c!bIl4c2me2LCj>wGwe22Z66H4%AjUp z+?1O}{I-h8L3UJa$uzL{_YNAX64=4GtQ^-4i1D@56))?>C~;^L*c3cKpjuSjy;Ku4 zQOm?VF9!_0pz#f%w>(ThUuovV#jNNXifV>rnC9y`cIQ00nGkq&oUZP2KDPTaSxE~M=EUKSm}B>i?N&8HHa~JvircKb=na>k2RK^nsSZ*SckE0LQ|3yU?>CX=!O^w8kO$R)9ADrse z=(3c(M*9$X22x!}p8JO9B`Io`9S(AB+~5k5U)Jv>WCQIcXZ0fRfKb z6;@(sC;^OlhdS0s-2OmA<-TL_@@y(3`o`6iLRa--g?_@*a zOdK<8CAeb_ssXl4M0!~$wODQ%k(ldo65J7QnfAfwIgpPL^8xlmKL#n)_`}4YHdNIz z^MEeXwRnMU0SiNkJvdA$9DSu70iIb^DkJ;R-iE}xCLp?qUSaP0woDw&1vG|%zFqXN z62M2bp>?q}ngCD|fD+{{l|GZR%Dn=aH9AgU1l}gZf^#^5jFdGpH_I^-p#9};S9L87 zMG}a7md$N+&SAZl=@Yccm3;m}$U#*gnb7P~jv}#k*wB<@o>Zw9b2Ls#@2@}q{F7Sv zGJ)yZH#GA+lU0qUZ$pA&3zG>l zqirH{29i~6JsQVu-Fot8kA0+;#>p4~ z(D*=!`%=DcN{(dqF+w=jVsmZ;GfXLql;_3okENVdRamdkYqTsJRn1b>9*=R;8s0Ok#3Lk;R%?MTpExTA>z@5nan2n!~5s|2{9c1Kr8 zgAtCZe(!g+uA|qHepVWhF+ef5FLC~}C5?ayfcb1R#z!ogOIaykE5IhEzjIqj$xrdF z*&yO(#nU+U2)YC&Wj9iN_k?5k5PM0??K_Q*?U^T@6(x;oGPe0Q2<|xEv3+`TvPw+i z*Z3Z*HfZA#Sca!HGW2&r$4FMW1?YupZppeSJ%C}7OiLF;3S_tTJ=6|Y4K#&DyH^r` z5aBT_G>V;6B0+O0Rt&{4b4C=zJLbSWzVwQx@FNDO0C*Pd+t*YYHW;Y##+ zO&4%(%L7VPjnIzYCipNPrH)M@>-zPs1ci+BWM9`0Rl={ z3F+~}YeyzX`C^;U*GOilBho9)jUKNmVA)(COa^4K@5a@hWTIFL%$!~qu`VSbhHV0x z(|pG%Mas>RVWC8$-L{T^5(cg}!!8w{n5-+*-iR^WT1hnl!$#zcY)}h02Fgm>DTRw< zrJaAzNCxZr{9gget9PO97ax&eK__5>>v>ZB@iO;5woEVtWSl3Cairv7#aZxT`A>wBk(<$JNNUPlDJ0Vwq?69PpC- z+b0=*db}QJ>ye&;R+k9``708H@hQjWv%Q2o?pgr+z(ui%Pr7oU*T`1d7 z`+B!+dhPkh3yq=@{a%%uz%S;E*HX@hroY=E$=VIsT$(V@EXNJ)qt9@HY&%ZLZGs$( z6L61ae|TM)v;z|nQ?3gJd=>(31cn$c16?X}S;S;smrXXprI5bQmC`p?iR^luZ#5rG zK&Ibl8pp^o@p`!-Mct|`PBjNspSs&m=H%2 z_ZyXx0AH_a6SenT#%Ku=+)(#+L6X- zp~t6b0a+;FyI@D3I2lr8$So_!R_YJ3^s1Dw5pZO4F`OqbOQXz0NdS(8mPkh}lrjOs zJTp&5(})}*d;d)E?N3>I`l{FR^Aj4AL&}*u8tNr8p#)^+NG%?u_F7=1Fq$SkjVhl^ z@N6q3hUqQul^iwNArnBU_LRg(;mAQ1?h28Zj;)I9Gist1$3`cEfj%sc?5XpV zg%StLv`=J5InS4by8@t5#I)+jmd`Uc0)h9x8)Ped$?anSyu6lH$~i+hQr9N_khR7Z z0Gdk=^gYyK7N#oz7?CIQ_*IFKdA4K=^q6pN+C}4U_iFpCN z4X%q1CQ_GwSw-0hR&7bCMetX0q|}I^NtQo>YLTAvNcXA>P$f%4Ffh`f;%@i(TM=fq zKv+wQHAoJ$5)B~6(>vmGZp!icJ|LwFx>uR3x$@S_tu7f)2E3mOJsBS*f(C_~rJCVh zrj{lu*;7_!k~2Y7y-g<&cv*?K-5djNPFC+}`+fu(Bm02xlmZ!+eNFdnRTCfuWn^19 z#@I)&%^8{Dtsnd{j=c;J-mwk0Qi4Y8g(I~DwvY8pA4$K3<0*U9mZw}V=3MXlcZkfJ zxZl*O-9oj$Qn2MPafZ!8`YD4=Ot4veycP4JD_GxIUm^?b@ei^;GZ(U zs^5C;fFu^o4a&JLhIy7J%Z>ilKflHNu;4k%Mx-I&pR9JyK%;41XG+yb=42RFNEsIN z1OS1zqUjgi8UcYPQwlq7kX}bYAPAKB=PVO&PBt<9aE+~G4IrN~hLVpv>dNr!bpNdC zUP`rlF8|{nC9;%EbP-U(?;7tha~i9%a#K|R58!#ma1hM9LQ|?{l0a^5Q_eQu|51Sw z{aWxeJ$`=>XcYMXsvT200xfoQ_nYP~77VqdL6W7(IYxDJ{V%B;dpB7q5hI0O*^#Fb zoB+Tu7xcY5qwyiMeHn=~K`WBfERE z+6Mzrnu71xxR}Inr2w*Vl-g6uy)Yn@dakfdFfgA}zT+@_gyTk6k~!A`1BM1*K`+-X z$86gvfvPDpsdpMBvl^GcEBRJn9Gem@YZP!lCbcw$xFe7z0B1G9vAAU4#$<9Mxb+Qj zMDB0@@CLaBPl3rzuDzF6qR$6z9Hw|1$MBp9t7g?0M$-6g(_%_Oi5nj2C>O9zWd6NX zykolon6_mC*N*Rq3FohxKucoBtYpz9TJV{N@jJrr@i^Wo4EHAlpIW%tP;?~F zC76h2Rcejys6>PB)?@`A4lw@OoKK|21djf)2y@65AgGD~9PdVFrFQ7nm1d|UwyG}r zdQ-wbf}pJk8j+F@ATsZ2u*fsq?ch84^ChX#AmAP6sJF&i92?rW+}1@yup>~{56^5z zYT?Gpmrz#it@JvQ5fm6lf-irN^r`b{llluTbwuIEJD zOz%)p!U6;w4hJIM%dF}qN0V%8I|zL(O(C-BKvky2ELR#0Lb-87G)kMRR~1dr6hkq^ z?I6bI6W1lh(S>6~RRo%12f>swWFoAz04UYV*Lx|SFN9>9g0TJJOll&2jV4A&GeIN4U0=`?-h)FpGu_O4q)tEp5 zLuf0A-asCI95_yMS4vj9abn_1nAsjyX#{cifC=OOOtNTs4G;;P7N!uPH1VFK##0k?ZXORVkY#jl;JyT1Y`U^>+3~Ljp&x ziVqR1b{3!9rZlmgmC$1HZC;o3YM(QTF$Ia&rAkoDFBTx1j?8*e_g2oez}sQsjo>f+ zmxDAbD9v77F^lj%P<-M$xW*PU9bek40=^RgQzJfwIMPa!g|ikTYdY__LrSSl2UD8w z;N6wDq1$TJCQ8qkdtI23d%H|5CDqwmMtX^7Tp~Q{wgt^^1jSB@Pz!P(AFkAr5Y}7)ezR(6 z!id%_DOlb=FrT7SBb(sY>I;s9v zxb<29j@EApHR3axOb{(KNCzbAq1z+`n5HzoV8>KtaB9XeS|IFYCeHT;j%Kd7AO+m^WO^VqW-QM1IxW;;U98>q@*q-7QanDeYQnL8FMqy(%zrzybPM5(9gI5d+|(n3(` zhABYMaQZ(3ZTi*z^}qc<1PupMmk~_?vdBC_hoUbfCD0ooGSC7H7$Tq+hc_aN5Kg(T z{oV?M_HPg$0VjodBQO(O0JH$GQ#1lXTnVToZ9Q@CdE`F1swQd_6c^wKVn*oZiMR)Q z3R1w3;cbACrqG`n zP|5`K;W#z&Mraqf2MVg>NFq~kzp6tMJF8Jc$16jk75 zeVo)=>eG>eHHT|opAWCps^%(9R@_9$ zXWj2-0g}PHipWL?cvZNni4lB_2;h@C8#tSW7&X_^M|W(c&W68V97D998@gW{hIKG$ zV^kTO6n?2vaTCSNM7;>WRv$S&TmVE)tyw_h#8sp5Xms%5e>GKG!Szy05Xi)E3N7HN zR0X~(ou13AOQ}nczC*>3dhJ8jT!??a+>s&EiQsjFv^);Jho?mhZ!xjOm=9+ob_4i0 zCTd}VEb-dmwuR_|FdU#%yD2o&nL;31r^{F6SqsCN-fVn?mWKmPN3;LdmcpxI)~HL6 z)edQMDRRs6QVKjm*Hv#gg{=pXB16R8K-1y6fUZ}7z#~+xu?>}w?VpZViwDPgB8{_H zYoG~Al`>sv!pRn63V7mZYZRxT%MI|l@`egnjcB@R6k9B&sws2?uBba+DdFOMz%Es1R*%o-WGAE?l@I}$UIQ5m?kMz z0mRKHIR(KQ-&3d0cekw}ZbLfSrCH)Q8v!XbPC%zq;4coTg{)MS7bmEMJX4AcNuM%< zXR2@_do-k2cn5ER8qCYj+5-j5dXVLov{N_ zuzXB3vC*zhl@U}Tjs_=75RD18n2C|`;mBIHe=)LPEeishQru-~Z!2K80kN3T(>F_r z)#zv*Q>!Aw5iBNp%-!Hbmyn0;NhMo6zphi#(u(o7a15owK(2rZC~YhmZ& z?%F_~puWm<>D`{C2)6RuKm3nB{vhR*2C~SitQ}|~uu{HF1iGr`T{X}V5PXy@(74o! zF%30nB9tu;K@-D!Mbjjxq@_k2WIlzhEoHU;_WSR@W4A|R)OH1$iKfy{{B1wKJqYS8 z(50HSibiQ~p@15nel+cFAI`KeB5el23=dIV`)T-I0TFSH<6t&jRf%;|#b?qrsedBp z4PxRRkGLrXx8rPx&nr1v$4Ms<)l3>K93L(&sHJz5zCD|wiqoA7+_NiEVrGS>kPlZv zPIFU$VxFO?mPTmG9zAfl5<4UB&=iczs)|)ZhyX{6kbqW+oAVp{mh_siaQ}CNEh`={=`fFFxPQ;*AD#lE(S->fa zEsWsJ>2zZ4i7!BFI$Xee4Tuz1HE~lKR7ylZT)-puWh!-P78bZtI2s>w*8*XsZ6(g| zEx^O$z{NJ<$@X$~+pP*@$kKuyT*!_?hb)i7Td6&P)L2#gTHGN5rV=mKQb|fYgGgy= zXHxMFh10kY9vO`$CQXFI`8bUz*4w*hv@YZ`7WVLB~U`9X_naYYGK}MgRFXvaeLEg!)TB_3nFdR zLUg$@E| zYqxn5^)^)6whe_#w+|_C>5l-DY~q!oY0nyg5VJ?nDY~@y=CX+|#q=A7QwRvQNos49 z7UKwTA`{U9@hp1Kn5=m1QUqp0hETvy$Cp?-JWa}Iw%T!89>4E=YM6}3_)-LjUm zGvi88$fP#uHXIWTqA^)I5X^#bsk8LN(SQ(LPKTE1LZB=l+qTin67*+0IvQ~@y^|FO z>iyQwKk>C28bLe)FhQKE(VD9_u$`rFh?H*)vB1JgmZvsC0VGSUv!@fG!**~S$7wwr zALp>Iuf_4L5E-o$;lrWN99`@6Rl1okCDM zZ!{AC)31~WM8M;iD@J2DtVY%I+kmop{#wZA(>BhgfGSY3#F=v+A+qIMoD!AOe_x5*o_baATs;XUlC;a-BiDSB6UHDBg0$J)@T7d78 z_?;K06KPK&vKU_}%}$ZmLR?IQXDJt#ZdM#ATr8m6P{ICn3PDw7B93iKbw!i9H&x6O zl-vn&`y(_$HIB1yD4KK}xN~8`EyJ%grhu)AtV`N-f$3EttXXs>1vDjur&bBBvGw-Q z@3b=x2Z494IFvms2tx~O{~jy8AXx+@I*pF3G<}FF1R-$B3nl_dw#6C>l;Q;PJ+B^z zsuJIY@Fsd9aZ=bm0ys{~G^?#bcG!(W7KiT#WoQrb%af% zkU_SO0F76QUrZsg)c#7z!W&)Cse6veer8-X2pgiMN0S=m9=g{)ny(0J(`=a*!@*>F zDP+=G+z;|(FY%luLslY)EMRaPUWxBeL(+7duEH{`7++QDHgwg(!x6H^O}rp5<+|Vk zQh|DDqQRN;O#S}3NUDTi3%^vmO%Oc#TmL-7ZnbTZS+cxX3v(43Lx4?$&`}COlLC8z z*N)(kRRZ=1v`jD^h#kZNG`E_dcIGE;_q-hlc4`4IJk4EHivcMhd+pK0(Zq@1+u4uy zol9y@Yx_cWUGVh7#S{{;q$xBzV3(-`)QST?V|~#aRx%rtnCPP^R{@Aq`>~YWigMbWH%*7ffQ>i zOU8j$ZA5WWdXeE$xL&by#mUDp2**U4Ll7X7jZb8Oc!U;nsPuccy^~U_(x-!LA52Iq zH6jmZoi5py|1pA2Oo2v&>s35st6r3LFCf##32GDzIUS72!fc=Iz^2LOrxJ?erUxpF`P$^AkdB} zt(Rv&{7Qldrq|d+2Z194d2wA+lWve0KV~f)+qN?88;87$QVLI5ni{XAZN&yL0nIo{ z5#X+?5lw-V(nJ%p9h^c00ZUvXlSXbOr2+?U1QDFBtJel!N)S_3+#3i7BIn<$>+HtW zh+xp3xR?$#3atlW_#wbd%HUny2*@-`TeT+(*GM_>E+=M}lUg+GXsQu3s!jpe6oFpk z_kd@u`&gY+BZ6u}r=X=6hX_8VZTtFCZ7xoGwv6;@ABUiY6{HZNeS|~(8fM#4!=wgb zMzF-gldZ~25qR7Bm)ByF_>c(-;Enc6gF9ykn7|%{biel5A+W|0!k1bzve9bdb)10OvDg93?3)W^FiviKqF#W47u_Rp|g*ATnmX zi6A&pjUirFf3|_(W6nA_jv%kMcFYiDRX!xaz6Wg8psHd>gkrwkCgqAci)C1+D8?ym z0gc+Xut3`81rQv9g3Ya$j?0G&$TGN8`%WjOlN6r_KF!5AdaWl2FP6@bS*c)x0=@8% zN(~zSM_1%Ar4(Lrcam~Pwy+an^2i8XCa)0~L7;@@7l}$7X&Gm=*A3`C8(rmMYnYpesr$@ezP+^0pHJvb8|OmZZa^V!n5vzW|Un zYmE>w&y*~qi%!b}#S~iJmD1^AkppW-D*$GY1$0zt^4imH1aBfHK_+eg+U+A~4ZUbQ zemWLQ6M+Hwssbhc)lmw^^bo0e=Z(N*rgwZ*Y0Kjatb~s&NF?PBf)CWfBNrpU5jqsm z!FfnAf)YL)vo(}9y&a^iamb4yQfgMxn-oNB&oPNe$ueYJ1!#;~E$OF*AU#vBArNm| zy`6|GK8g}cTu_&-j0Wx$Vp4%|>W#CVjcEB6Q*9s0^+GZT4tuAIU&GX8kbS}-1k)7P zzI_TXYjj1md+mV^o}u+YNG%!yS%O_Gjw;_=jii+NJi?_)_#t|0!97Wu9s2XuU-U zT7Y?ts$xvO0JtfTzCaM*8hc$$*BA)zig+=w-4Gas(sr{(Dc`lw)D>-kbh1QfqG^E( znBYfTiW6Z$y}%H(7qL%#foFk zeUZ=*n1Gp^)d*>d0Ah+I5yW|^DSbI_T!Km_7UN5aY)|b{ro^Y#8km97UW`lpd~n8B zrQg{tDbR@DJ`fhUDef7+HJZrdG!&B|R0_0Jd?m8!q|f7cIVreLjeTij!Zj-4q#3d^ zBE#9%P->!5)yVu1e5sa3pFaSk+gg04p*9UYur9Qt_Us{2LM}xYop&mnf`_V|Dd9vo zTi?H1l2Qmqrof1+I=Q>hNQX^XfYTY!ZpY`Rm%rP<-;@Fb?P69}0kDZg&VFB$W~w;W z6BwFNQ|%JcNi#&OdM16}ML}v8`^0g=H99YE7NnQJEO%?8IJ~(y$qKsgINd;NG)`kd zLlFdll;h_;9gSjAN}acx(b92@Yaan-aXJ)g-d;RXa2&o8#3o{|jV=KZ3azKN1I9;I#X*3m@<3K90J1Jkid-*H;Qsk;&q`=D z@-6?i06QJ8-G)uaVXAEU;xqVOR2SY=Ak1_j-0t%+wu};_3$YIoRYz7;>Py7XWR=7X zO^4T23|H+ATU+X+@b-_^#6;SJU#YpO5X^!Nm0DHG>q5>j8H9&yAADq~d*&@;J+yX) z&|yuHrp(9hAxdfD5RV{G;(<&f6ZER!1oCF_Z6#=2B7FBBA4gT(DWpuyG|qsUlwMDp^~5TQ$1sk-nzd1-cO5fqviZW{jZFTk5-Xdjh+zZiZU7z4Z{p%{2i{?>WOA z1c@0E6S-yDgr-wyOj-K&HeIO$Qc%^77E_}VLW6iMO3oE1bs-rhyR9GBVgv=4iR0jt zLTXj<0=MMqlz=hC0tCx6U5eHWIM6aK7Gi|P%)#xc%Q&7PBP1@+6`w*S%4pi(p}^)M zXX&@q;#&BIVm1*X?z);HwGvvr5tN#%DrVZnEE-?TtCs2&^8{-V+tzN$rkMgrmy9C&^h9s3TWZ=dLfHW)V6*w|Hs~0kkf8~d!`ZCdGsxH=@Kw2g~5iNG>nBqiglnVF- zsy&W2A)*wL6n>8nZd?-)SRxu@N*7x|W1Kb-qcw#G@>*IoDSpoh4X|tknK+0r8fh7CA5v^mGuIs$M&51oA*ML(_yX zDI9{2W;a2c7FLpC3OceKq|ua&h`<*#TD-s=*3x2dvn(TEl>|mGjrgXwSfI3bzz&$* zCxTw3<_eI3eQI#Tc}w<-8j)3X*?|#Z#S>{09^P5v_bmjBK#*y#dX>&q*V5^L0>~_! zCz26ta~{sRXZ!1_1WZ3KO|Ss}EB5(kd?(sBq@OqAs7C03aB9wCRtx*JNuZr?y~aV% z(y5vhA0aYAOoH%^BY=l=3P@}4?ZBu1LlEC>sCLq%S{`#`IBRt48Q0%@?=nfn(M5sb zFt6osC9@brWBLSHIyA*yP6P_QXmf)~FZAXciQs zAvD6p_PZ$sI6{r>CQZKu+U=}(El%HQtwb{>A4kw=DGoxnYOc7o5tQQdQohDFfZMQl z1Fog>j=!Nw3_f!TF+!Z;#39o75PZhTT z4Z?3{95HX1R+S30P-BgQu+r8}87LhM7$0Jy5&U@rzW-L!4)lQ<3ox4qJpTrOL?03i zZOU!$Qo0E9s3>iKk{Aun#JbSB%}&@tF^wS3fbY8XLP*hVr7a+^#L&X8#~ zR~#+8-FPG7pfOcoQ;x(<5kkfxRWBrdD~TD+Uad}q44Imi)ZnZURh6c*S&b-7UyRRd z=hvbN;k|f9m&Oqrx_xv(8mhf&udB^#!O5zM<2Rb8hAE)@8Xdo%`OZ>mA0wJVN`UM_ z`1BJLLn3(MXplbp29>G;X=-n)7T^(hOdJf81hy{V8GNghWr3cq=@AuT;wzr^*6TmTm##*g4yO-Vc5cu3M_AQQAZ?kf6XU z2G1&q5&DTPFcD{b`e~wx8G=TiN`X>aHBL1Jh{8Q)NMr5Bl+YFpt;;hk;-n>(hilk;*WEc5}&0 ziVx8UWRM1l9Z(lF1P@LLs2UP85W-ZG5^LO9rkS#8OP-$ZootQBm9{`bmBRUsBWP&j zrVmFBZ0G4$O0YHDgVgVh-hq?_4iX**0pgdR{TTpLi8$Y4aSUw)T&&cK4MdP8*m^!_ zrtG7KfXrv<6gS9XG`nOtvR*U@oNWsIKuEonbP1T=4yX$gqGUI5n+rmI`^P{2q3ko+ zFer7a2pYLl5fw8y-+sDm!0?GORV5xGAl~4X;F)$+LG6Ob_(t#7qjp7h7|J#B3?D9N zoJM47+Ica8t=dP5EJmwKENJaS@Y#{Rro|eN=2ik`(!|pNLYUFIw-`ixE5}uW`~cyq z#tFz$>){pi_$`wN@6^bqBiDt~8iH;>;Jj0oiur=1DBw`=0`$>nd${ZcCXx)aLonngxO4&Bi7`|Cay|fS> z$hU?rEqzSP6|44`5T@3{LI$P~6QWC1i@WM9>)J%f#3_hPsj)TE1X6G_4U&o>^bvq9 z2KU9Mz3B}V3wTT=zlh`10@uRm@ZEfxrbAk(7a$Q}2NZY~$Q_Du?-g45ZL8WrS|DqCC>+h6ocd;ES-Gmg+H!U@ddjC zaZEVSL}`TDs4Zv#96=zjtG{+~7VAx1BN|V6H?JyX8dK&OO)n~3oCs#G1Q8>WEtU!( zy5{{yMDQMTyGb*ekQ5+djqD75e&MiW?G_MkmPWKRZ^y??Q;yv7$Z&{3Kyi3I(q1Yw z7Ki|p;zMv+QV>V53l!Tt0&i6w($`3H3D(QgOqRkq^Y=GPfnEfol?2WXskfU?Eh$sd z5wPuJ!+L=NEohhWc}74;XKbj6kTw~TAT#+Pof_;}Yf zzy|Tnjf2o)^#T!)1xf;BrQ)hmA8iwT$GQhW+ol|jbV-U1p&z|m_}uT7VRU(h}X$c$7| z;J|#ri=zQEG3$V2iMS1j-tbDiR8^ON8U&|+w_YY@IGFK2lb(OTGt87bPU3o5xWHRs zC9@o#ch9;KQ#Di!#~1UA8ZUMEINEW54Q)zYJ$m>ilIr;;l5*rVB4?cxhY>+&F1%~3 zaSKZs4v&u`cI_Z9Dym(hmh)TfW%sOOeS7{qp@Bz zo%cVR@7uCV}a?z(FNT|(U3?HgkrEN8tIos6Z0%ML2*P%W{s){Ipf2A=S0(2cdLJXP}R*6WUHE%zl)|}H>Rk1;hcM9<_GV};lCLw z=?eUpi79Ed9UG8UfP@-|feMZDW-_jJ2@G57HpPohg-c#%Yp%5d7}g%K{C(gGLi#-t zZ1ZpZdu`1NxbcnnGR{#{fW7G%#%IQ~Dlo2Ap!arYrx37I2eI=hkoPK8{B{akhf|!B zVaD5&?MxsC=dFlqp|MwZxHeD-K*rc@U zQ{-_JwTp$qMyzPUu^D$ybb5+@&_!{We`SjGksaa+mD_G?Tb~&rB2?l(z-Qa?2gx*K zrTd|pi9O1&#PVv`OJlC!81G(v*vv`{>(>(Nt)ATJYyr?L4lg_s5$ZRGC zCYTd__xCbn5d)_fr{NUpTbxqD8Mo7sTdYP|6?%8*6h$~~qzLyAn8F7OA1SE?Gl}ko zu`kILHxd02ynTk7)-C7WmLv=O0vlu6#mY`Il0@!v@pZ+)9b)xos=>FDY(7x{$Be4b zmn>sQj__q*es_BOfY^;Ik{9^6{W%V6+I&{xG{s~t8^zu5j}f7tj;qv_eKUv|du@MLc>j7`c=Fuf>at9Fl_LR3&XKHa{THND0_l_5l?Etwnt>yEW}GI8 z(*u;O%&E>?ZMBTA`7%@l2fU-rd-`A%S4Otd=oQE!pWWNOyYlhFTfLBc-16<@E9!3P zBt>uU_U0Fe%g$3dRe_zPe5i+-X2K+7vJ4Tka0kfrvslM6k+i{=NKIC@sgfqGk?aS^ za`ocmx6Fc6!hjlbI~-l2)&ZLsi6LXs4=(oZ8-PW;aHaw5aHls~k3!8}IraH?|GMXh z24)e1d3J@i*Djy21&Ot#0l-U(>WH)8`f&wMF*_xDQlo!L1l z74zWNCfby0UY|$GIahsv@j0hIBYb4YEH|AsnCDD#7jc>WH!ads%3%V9rEA;6;**P) z?U`IfQ@6<5yqDt?^?wR9hO2)2HngFYV>rusPAo^}2cuj zhMFfr@!OHG`MOzcMCk8tJNnyRJ7x>ecUrRchrj-&VEZ}alsl1|p#8(h&DU1|zss`x z%v1F_W(Ivtl5IfG8^ja}yg6}8<{+>K(9%BE;6e9o`&t9#u`pC) zIr+oPeg=vge0ZyRztcdU7zX7y*IaVqB7JVZN_I@<5zB!Yod6fDY{tUD*fL!Rx z(G@}1+`yVZgwhpB@Daxn&L`5YjUZ)*He<3HD&?fdPE?xei2M`c3!Dfs-PX=jYvjkd zD6?hGayL+}G@$u*?G_8XQteiYLwrTjiOGIYHX+1nmGB&?PH{j&8+d!F2tW4| zN^t4(#_m3Poz$UR7z44{JmI+*^z3lO0UM0M)tZRnmlK3F5)G32C(mADDctKd1Pc8z zviPt6^ya+!K@mDT4DAe~uSTVz`L&f3wbN;fC`xEx5$_a|`4p;RTTMs|ESc2W8kHID9?^cAd(Dptk{UQJQ-k|P5hxp8NY zj`)QxuH?l`+&SP*|1zfLN>mKm6E+c&kZo@*H!@1x$z6}SC<=ZoS7I~n7Y;^Pm}n$3(xln+@CI+0Vg9`pWsbr7b-2uQ&&?py z;D(ZxRi*nPaWmml(#V`=u8*f_r6q*3@hmz^3GsQ6dMcmp*qduwExxENEl%xbnjbkw zc;I_3>{lz$vBVBF;?Q#4l|PQ!D{<@-!iVVC1di9L}7PV>QeAI zGQydRRZkUnUGsbj)g&5U?TNC85d;P*?j6vroRMzVa8@-9|2h4tkN%Ki;V*uq!V#{- zP2@zzx;RUq3%?BOi(hUkInZ%L3Q&(6f7PD+D9O?-i`~J&S4kGNet}zR| z@;gBhXH_x8al(Ro1H##pjCB;~qRYVz|8Y5tS17h*T%APsb0`~W#-9>l+@IKATYm8g zAJBYb=2ku{c|AiOZ0)y3<5fm55BNG<=&M0H7O`DZ2pu1xuE3jR6O%)wd}^wtyCsNwv)k&{wU77jbq#gToY9*El0*#>pWs|Na=7}&XcPva1mfP{^G5Ha4 zDk3KC0@cg_T;9THSsRH;iwB8o%q3PRn>;%B%3g9U72rJv!4Ft7W}5{n%S_v~%g6C{ z6(QSg(GRNl*DYN5UhslpCUP4=`(^sH|2j_{9yfnOdAZKGQXiGfW!+PsQGO}&D!l!# zzpZ%rD0(PyX@o*+bo*;U=NByUSxQ3=&R?Af@-) z4N3r9Bh^8*bMWm;pVvF)wGF5nUJw8Hz65pzo7(HI2?YPAL{yQ6hBP2*#1Boj(rXfY z(`wIRf3?W4+2Ov*YrFS=TN8uESko16coS$^ntfB4rj*w9Cvqug=Ky@1oT2ue{gv^XZIcSQ*r;ncV} z!QUDNJVMYJ?$+t-%DLJbpf?>bh|@UQ2eCs5KCE3MldnB!Y9@KfgPJ^mx67`Ff?n?(1mU&4UERo6 zn5P)4s?|b6Hwo7^&g1~jPaz%Y_Y0>|f-8v}@$74KDtfqT+z17kt}B9fpI4D&{BIuNDCq4GlpNhb#~(&8FuR7sjXvP^ z_QdB)?gdgj%+058uHA{*gw%`3F1yJQ%C`@*x6a+MjMoA)t7x3&e%x582QJ*_ztpSj zqQBPGM5`UkQX;lK@suqaviQpQ7IF*T=F`2C`SbfPTBIdW`ayn`4mwE*Zh0K~?I(PG zzUCBO0nlF;;60hMY5^?UZFli#9!WfYExm|sPsj*<_!bHZ#oMV2P!X7MFZn4&+z-a4u<+ijPz_y>y(G6T04? zYb?oLn+3F!A*|>h$Q38bM96Ddnl*j&0Xj2>x7W5S{QK70{pV=-3NN#;L&@C>j?Cxd z{(49QrF2ee(*&tv&VHV`A@2BtQFhG;$ao2OmzA4Y(6B`(2Hr8R8gylxv_iS}Hm;^w zEfi{Il#0YXN`pn+d#h(ZbXy{F{(f1cV6w^V?pbX9kSBRm7rcB}&K{8NRI1Xe@9t;< z)*#A!yBy+16U{ z2R9~Av2O!oaA0~MhS*PFt)t)~$_7xM}Hg->}C1PQ6i+lOEH#4Ucwc&hTEdxrnj z;kelamv z7s#8jk2IeYL&~vHxD-((G@JjbJn4}X#;ZqM5wD4%C3gXu?fsQLt2X`mt2z1RWCYA`~9E}*xwq7=4fXH<97E+ z+KbGsXdBjGd5GVP#>W$gJ=~+2*MD2nGnCl{rO^^KoN`_m$IkWQ`eoKDGkx0`<#Wk7 zTSRzIX!1!2Fg2}zAmn3FP7hx*oEwz>L_{^&TRSP*Gs7HXef6HRZWPR34iW8}ns`F= zL7iLz>(_dmfaC@RgtzZ*@m43K;2%UWu{+kFn`fXAM*-=nh_xiEG83*}p@ZL7f}1Be zn%@?>elL1@d0J+*#h)dIsoCY*Caw5&8Cq}lYPzh~=!RQg(*HuHdFKA)cL z+r2!i)LYsLwJF;Mn>IL~ybFfr`u2kBN!!XMv*GJ%AUU_(8W{VA>>c5+$Asqk+-Fge zeYYs7e0rcC!oeDv9P@gA{&t3SdRXt*`^A06L^Q-6gR>HwS2{-|pK z8p^D?UdTP5ckQ1l00x=+b_W^5kLG(MeLjZ?BwaP*W+;_ekg2WiZb5ZBk)ejWUDk7c zHqz_*P&S3aRK z^4y!B5&&v`zxR;+XFG?b#*?o9SVlxl&O6TZNzUrarsIg?jop3Oz37|l=og@;R8Q3xYIt{ zFWSho*sR0FsMlT~LFt|HL&F!f>p*B9weVSpTRN|g&pub&@Vhu#`ZD>3!zlCH&7$j` z?`oXY#*e9f>E;u6FN8SGOrVrF5$tzr^Y z2*M`5I0wryw+?5|w>4(b@p;mYPA;*>-=y$1d>oXqa{-4wR5R2gu6$T{{&)u}FgP9_ z>hx54Q7_!mhqkIV% zZV(G3>5wHN-z6E;U&c#V0MZt2TvNOE7{>M& zxX;bBDZ^mJO`$L?tec9E4sVmU{zYO22jMg2-5wP0!9jAfE^$Y#PTakU*Re(CK!^dW zxyPZAJRUM+>}yy$9q-BE4E+rmjN{ZNqMA@#t4e+g!SwD>yKJ5P;`OnQ!|hRe9N~!O z50uCVM|f|i`pFgQk*U%Q$IoeHdvhSDquZ-l^x?jE_ZMt$m7w z$bSNUojf>OnY{}x;jOx4Bn>ufM?M%|nNnV5IFru}P)8Bxu-hu9doXB+v_h(2pXeSk z)xu;9de&0ObW9=qw9K1D3J4>egNQq#N`mxnvhQ7o5mAoNn}#M;C4OaubA?nyGoix0 zJ)YUtF)J>ePO&8CAn^jJCtI#wK|o)l5wZy??!s<$`0i_(y^=zRpiTd_{;!#kz|*1r z$4*ehs5%f<1T=fI1*d%e+?f5P_79&u!aq1N-oANX@AwSWk&7ck%U4%XgK}8F`B~qK zm6LFpn+f-qn{^|6xbW3Jbzb*kxrz2|*`@12^X%!k2WvY8gKy_ra>E6M>qDu{FG5eu zy_vK^5jXR6W5;}@A-d8#y1ctQP4onj)uzN@Nu22WH3Bcfyc4o>!lEm;th9M-`PL+q zKg_7g;WI1!&!X~NpE+=h(32=}7>PHIJ42%64Gcjiov3EcG!yXdoAJ5j4kQwc05v~^etF6K1FQ*8?4u;FPbm64iC!xCPRUO7fETzZk`PS<*8(M;Df_ zW+x8s*m09HN05fXt%`Kt6kU>gAyep_@kfW-&RW*xtcF85%K@30LRW!px<9b6%O!2G z77p)a-!!e0-!U071&@S8DX+gFsYi5bYU-LE4OuhoJoss>4;(36Q9Iw6r`sP(Y%k#_ z$5AZL7+1Qdh0IdXj!(b3O0@h+m=P>3DJ86buBP80Y_FD1Z21(J>uZJyf;&Uj&mGif z52P6nt$mMcsHe~8lkwy_aklywLuzg=qERrolgT@uA3lQ-(Y9*}7g_;qO>K#E>4zhl z1N&+82vGWPW?M=RySU460)K_r*J0rkE|e5*^;757}&ANe{+E$;CQwOy3)voQHIE!S_3A zIh+Q<%k?ES?GPO=V7g;Jz}3fj=eW9^w??xpqZ&eCa+6(y>zCs1eL9;C37AoID<7!# z5{~;lvL=`D^l`Lc&B61La$EXz$>$t)o7aO2m3uRIFUBDuz)FGt?5y)qY=ja&QFgHJ zcD?=CiHZ-D>~pQ*!Rn`mcE3w>9N4dx8>)MD`!59ro1FJprk<0S?j*_`IZT0oO;4iy~iN!J7KJg<45?Tp*f z87jeec*-6(PCv!3IqQFbMPrGfw;ld^%s#Ctr>fR+x_KYjV5oc?`myvU@?uWQx(_}^1 z9M3K<6z`%5Sux_Wn3Wq>@2-yQz2rv(_=$}6+bRK4H z_lK9CFm84l2~G7MGgw_cZ-eDvg{ahe4w#PD41i-S-`d?dp=S&^_KId5kYl$ye+f;x z+KBYpwYI(THH#hk9Ud9+f(*<(S}{PfMQP{PPe%NxG)IlNK>ZB)%69XeozYA0G}7jDAb;)x8?6)u^^?g^nC_q;Rj#o60y1A1jQE%~9Y`rC))R|xkQ zQCo8`HhCW9HB9TTv=Fq*4yY+dqSn!bd9(i_s57LP-grF*a`4fep$bZ#HeTV{Jx>G~u}&tKf+ z1=46G$FvB>>^4i)fcNxf%@B^ca9GsG`ZupbJz69d80*c_5MPo~ULWEFrxQtbKNghZ-G{%3v$rCUj-tI-yN_N9HoSuO81`?#fbo_|{#m@Lw{|`knb}&4H=ucI6Bs%0 z>U*z%Oy3N4ZeV*>uRYL18YIukP@V?pZCL`)$)kMD`)c&_O9E~>?=<_+h+Rj{sXCc% zy{&?mYhW~A@mVJIQ!@)kIUjD%Leq>FxGr&(YXqk01=LDi$llUO2HCwFHkJ@vbUAPG2VUp;iSvEKEGv6-y;OM;7sCn7hK9tYbe0d%j z)~DsKo1Az`UZ%68S=xx8{yqSVt}(c)3cBl~9^jzt1jBs(}_ zfm&>tbUXJ$uF=WS-Q;BuMn>Uq<%{xP3m&84T1bXBk^<(XZEnB6pf(dXw5ER>iLM7h zdk!TGcx6)Z;GrVH7J^-r)k>1*g%JVm^j$P7M^E0JBYi>Clt7g$lR_RgpO3#6g+lsq z_@9dw%Ah8g7vjH+7q!}5C0r>fqK6R6-0j*ngsXwu_`3Ly=7EoSDnN;tdq4<_;lW-3*3& zFOIWc4jiZa2uVo!U2@A`MvzvPvb$D;>J+Zt4uA4ci0Rtn#VMBKzg#<5m!p>~`V`7h zo%sC8PM9Ts7kp1RN$TuLYfiLgRYkSO=gY2j+yWWAg3KvfftjyoOm(bBCC#K&R6S%2 za{cxK+(gez(`N6++?5*)V81;M9)M5;5UhO-4$Q+g>G*$a*JcM>qY9h9sTu*PXw`Vx zB{LOO$oF#b_)kp1QXPS1a6*zQnGi*$=pR1HDGyLKH~NopsUq4*P!G zKAbwH0lOglNegl|{`K+O*WEhG(0)>X0Z4KLs%ZVHe&Gpv2=PiI>06h(;qI@hA>)wY zJPA88y0=}IG{Kr_*TE_Aj(t}5`-eC4p)ALG2|Ls74O;wrR>wV! zQmO)hbEiH;c|TD8hJicYOW1=Ptueb(3iD4e3S@iK?BHBBD1`?%9I2W9>w`j?$Nom4#XB`U z1-q@q-va9>|RozTJ|9b4$6Ml}_it}T<1w4-J^ z#`69;L=SQ(SANTs)o>5Q!GuMPXg36s|12Z(n+YM!CoMkRA2qr)PeEnK*&ra~E} zpo^0sKBd6HL)I=!t*fkFaz5d)W>!46^PIcG`#8=P>?Z*hfGRU>NGpz z2@dz>C`k#mfE+=j9S1%aa<1LeAX(I;Y9(yk4)tx^Rfr#VG#fN7B$u6! z-Ih6m%c$`n?P`G>XtzO$0j8{^$v@L{Y@%fU^65f`ti_DXl0cHkVy(I^!s|C=e>Mq~ zmuY%Mq{qfC@UZa7(VT%@n!$;XWM4jj%j%zDHK;@M8#Q|Lny` zidogE?;bKJ2L1lG++HBJ945k87B2w2$~3sq-^6Qr&%`;wF{}B4mBgJpcb-nBjC8b8 zd>b;jxK98208>5w9X#IChr7B0fFnU2A&o_eULd$bmHL;7=6z&k?uqj05brpZ5l}p; zNRq6SE-q(;-@hc)+|t!8$KR7G*63*IK#Y%K$Y@JjL{=OwttXu_<7 z1FwbO%A!@A4Y8y^G9-X51u$$w#4}8k(YUbP+4f2==nY9E2yFR7Y!1V^ek&dPEnu%d{)qAGI zentU0`VoDJixN*hZ+e{N^~)~+#%Fs3{St)PTZSb2Rne&8(|$A0`NXuSClxv$x(T)@ zt7`C+s`k(pTbR5OUU4E;O@pn$c_V`F^i^0qL3ut(pkh+`T!dfgNB@uMtE#iiJMv6h zIh#VO>(1E+yHLBIH z5aZ7fB*0s&p*)e3q(HzAe$*@XYcz5oaX0kmFDs$k_kZP-qy|zJe)a(qU8y|Zj^;$d z-0Xr{3>sS~gUU)Jg2uVT^60Q8`NkIUq*WDdffjU7a547DWTCA!A9-Nm-Pb|)@w$P0 zVKkM>iF&e{`*~!5Ku6AjKG~C&Qt~)ff3p0TRpCw$#NeC#1Fd*gyVCjG#=s;DsZVf5 z!Pu)_ivaMX&e~+aZ=7SnqFD4WzMn?@(t+v%W^SE*v>ecrY{oqaO9OiiP&Jl^H=R|% znB`S%ea`%K=KGz1U30G}ou09ZDkm^Y7Kk?HCY>9KijS8yr`lDAmyz{^YRnhzpcr3?P~X-pxBz~#G~b( zS0JB%NqH8d>x|zo)YJ${y(PFOWB`{10=+aF?##?_h*qZpSPmHe)rPTA)P*`3A*^Y0 zi%<6?vbL{|NplbNxLYlIk@aD3S+P%rE-Y~DAiFc%Sea5)l6YQ(YQ8iD0$+AwdV06j^uO`!JD%~=$z;m2& z+fHm!OG=x0W@Sc~?HA9T#PBTQg2zJO?h;McG^VpdvIXOo-Oja|4cvyf+Ksilm`>J* zbx?Cc#sQ5P13(&9w%(*k?$XA*Yl3c;D@%HYk@n&AjUGmcezh_x^8O=%T(nf`L0McJ ztF}82c=R|6nO3KzX*P-W&e1Rx8^SjVZeRDmE3YOc34rEnK1!8dI$R zm|1Ao%cCB}+?19y&kc*okKus1Hs-`_X{MW%QI5(PJ+@n(26l|E)=nq&`hVs|n5y~5 z0mI+CmS_wCB`WJO%0j)ET}Y9I9t-Hg>cJjOIGN~jZn0wf;c)F$d4t4YNeo3)D%QI@2U=U-FFgvB`{BA(dq3 z9gXLkxo?Xm9t~WSeq;@2^k*6fcwiUPf%RO|lZeRKe*j3ElJZ`>Ow0YVf8Mg=?KJec zjte`}Qo*I1M$&h#Y0ni2lDH1m(Qd;s&VexEuJ4Z)louvvb1!&0-}w;vU^V8wwwH8L zdk(s`A zq|_BHc<4rU1jY~ABm)%$b5_-vxvZc60OpTZQ&X2e`QkywbTDej>XUo4Miy!#3Oh-( zW%A{}HH(#ho~INrI5qrnbXesXs{E!4fhP)w5r3Xl&hX(PZJ&7FHCHIv|J!c)TW9&g zf)3^L$451sF>uf-QTYpL89U~Xf(Y<$m9|T3amjv7Ys}K_CmJ%l%8bqY1C+1Pt+W}X$0M`vGmpzdY6|7N9~iOvH; z7Xw$mOqRPC5aC{!<^A*EH_Fd2&$=wTO2m>V2fOj1H8zb}IbesC@5r9rbg1^jR5@ z?CB>~y}}CSh8vL|)j^n;4jz>y4aPEm8+@EU6R@NUui^)mG_C2q_qZ4 zc6ogj0E}ao)%%wC)(KKy<3x!<7_~s9S{dB;s!^0Fy+<)^|a^FY6?A%9~I#n1Q zh~=D1s_;0;IsXCg{2H~hLecZuqnwXFCiU`uV6Y{}Ka@g+qp(x}xV)-;-TtuV#s?$; zo!GiB(eg>W=T%tAQ_$%Rm6U8&IP+o=i!I9d36Y7B?$7Wkwr_xSC~tkm`Jbo;vxMDy zBhwcKE%hs5pXSC9Ek-r?n_>(%$5dn*zs}rE{NLdUf5rqK)VO(pZ@Cn}rgU?haunch zY98-FC?M#`u%iMaa=p9 z;ndix`mN}Z~$c#Gh^+=Qi zn@>vwW0DQC`?zs{Ou&t$F`Zm*t{KrlE?8pOa_-|+FU7Fc`Eub+Y;zZl3{iS1SSb=hP$=xYKQQ+j`DZ#52CPW!vv}b!ViKP zcO?B)Jg~NvJjx@qg^-*2ylOL-J}_F%q{O%iSkp8ZPDQv()k}_uiyRx{wCBuKE%q*m zyyF~okl<EJ}qi+|uwbkzc?neUie0ZK5Q?r8^I=-Mt@Crv#Mw|rOG0&5H&^Rp$OqI+O-Iw|->3Ej zY8Sz004Q33eW+?lAXJoCiL5S2dpfjn!t5{vcdHce30OLZGKO(YGD{q`@XD^~^K(cM z!l>L?t+)SxS?!DGwGKS~iMiobKwKT#obCN+kTh*`#`P3XnDx(mKyuZ*VRU|GiL;t% zQJ_p;%D4VQbGpuB^Ob~!^df`k*>C=p0i7N#CZ_!r290PGpkEDAZ{$^AVBG!YaLa3T zyb(vd$@W!za~a`3@t9>uwW4pO(`GA^L3xbo^mhFnjUt5z*fnxzvF)KE+uzu!_X(YJ z@Lv+W?|!l;yz=>dLpFyk!(l)010}onOGS0hY4YeH&w_v`R2lRsE=m83?xxW=QxVd%%kd}hjwf@p5<7nS*7(N zoq_*n0Wg|O!$!eR+WW!RZdW*Wzsw0cu^v6Av5xDcrFH)h^R&;_E#jLGDNkGOLzYE=>A8t1Er4;@id*^CEWij9`xm2GzIP@B4oRoX4EJM4FmgId zt;e_Cx7XBv`pm6@y^7at$sMZ*z@#@zvo#u_Yz=n>1Kd%!KtsE?R4NFUuW|x*UtR0s zR^4oW;B*P~9)|`rF1X(nU2k70>r$k)b>3nW$F|NS5Li40n`&OY;aA3Kv+u!9gx6b7 z%SCqEDk<;O@_W`LrN_v_%>qXE|8T253(RB{Ri{J$0%Q2EV#`m<$dNe{2~T-ELL5n$ z_bduY22`h?_9s;+>9}%Jl0plh9fHboz|ntVtwu3w(~nfskU5r{4JwX^nu`D_lIK&8k(|9GWKk1E7{@pv9dOW6QgZQa1OInl07 z(a{^ZV6Sb8^7y&Rd6N0~uZDb28h=OwD-@SF>$@Fq$qwckyrXydM23Px&l!Vk%IJ9! z#(?UUZy+V(%_H(o(1!8hDTw`gS^bK^n1^DgTB_2U?-k7CL&4F{BJ=!h%VQJ*`POXtL$JmZDZ7L zxYUQn_DnH*R17JHv_#plboCkdF^j(Hb31D3U)E`XR?9**s2^z+G|iMkX1sN6N>Acl^SMd?S3-eaRl$P472Q-Em5z>{oN}*IwiW zZqfKHg@Nx;9b)yeGjM!;n1dZLn&lZ09Z^vT;2p)MxC<~ca!1!fdcFx1CZgvYrH3fI z8!H@mbA7HVu{Ddy$-SKPqGGSD_ICBmF6rIIb#Gk02oeVrzh4r-m@}8GEh?r{ zQJo8@ryudS#y|dOj#pyS6gH^8iJ(32GtGYgyY>uSF_GRFyGLRDm^uCQ+9vmN15<#5 z-I!_Eq?qd2>C5i23RgC(UHTO_zsGUAoI5jsptWLMRFcuO0y+!8uuad52SN&83w|!h zj34RqxG2#?^JJ?4N^J?+XX#$8!V(Hx9)xWi$P2u1RjknI!y4$S+^@D;_RBh=RrGR^ z!*diYG- z0W#UzQ>!si6OQ+Y_kLdVvlBL`yX*6w7Cpw)Lc_>Wj!UQ!#fzN_R#ComJwHQ-$NBez(R@ zb&e%g`!1c?uIh>A&x{YKjY@4eoqZRMrWnInQJuih-8Znq(D&vSzJGS{Z@?HaS zL23U3lh;*)Z$K$;cSI%W)Nf$MFRe>LZ>Bjvwyk3Uu1W-88&h^?@+B`DU9{w_V^?B- zrqsa;UT-M>Jp4$P)S7QFCY^dD@lN7~WxcKnK-OjKw<-YPqyq51VUfa<3t6b#3kq{< zG#IO=ctMBV*X7=@P?fdK$RI927Q&Md5YX|ex0#c_%2E^(;3<+kNPgXyu*9I!%5<$> zmlGglu^-(1@9)cpE_aL#!Q7U4RX&eXQpb_h)8H}1jYpxk9% z@gCa40;6-0JnIKQ)4p!=uYZIF4ROMG_{7ApTmC`ltV`v4*7tMC$+u2D+b* zCX6h^Xwl^~(g*;YTBCHe^I}Z7qmk$>1HX4?C~!m1DVaU5Vt%3B=Yd0v^>s)-O(?-7S;o67 z1F6W8Hj!;yJ6h$Oa^3>!meJr``u5k}W(oB2Pf`R}o}qcCwpmdgaPcuJ4J7Y2f<}e< zTn0ISOy6r4Tj%Mp+hn^k46mY)ffyQ7_28V9T*G(DYk|u?0F;CO#8=FV`TzwhF4+=1V~;Xn4}QI>SN^KwLNKWu~UY zd+CUX2F^wCqg{GcLRbsP@+l^5rCs&17VGM%wgj#5pprJWllaAhu$q*j!EC_v#iVS@ z7t1HPo3@g&;%R>MEP$E$|7be%e<*%?FGMUo{u(J;f9Fc`ATAR>t@ zS+bR7$d;w7V?)aKFy$I*;=>-lu3{)TY1R z&92w#&XNH)h6As?aj$=}l=)m5$L;W$N%HfTfZO?mAkA_f1qI})_+OUUo8wG6fM}jd%UvD^G^IR&jha7Vv||e-bbV;1!Vdq8pbS z;I@Ri*K==yBjUc)L+py#?H-b?cC>RTr_Quq@;X#2*H6}>tTPInV<5nOrSlp?G4#rQ zu*H%V^jd%hPlbyp`~)t=$*ql1P|qe=XOH1(ug(e082@nVJNf>IOtjlekqQgEclHUp zDaEfvHcF)!oW{OTymFH6MLI>EDkJ}oj<9PyOnT!f?cj2O%51O?{Mhv9@K__@s`3M< zs6Xd+WmLJN&pD4#BD!NOFSmz98EoD9v%@~_6&w6$Lc6k7zMntB3H$fw)axVkjT7>< zXlWTOIs?QH2X*=z=*PBC+`WtUf{e;Pte;_33B{%9&`HB13{reh%KsjG|I5o0law zj&=!i{sUY>HEY-$Z`_`IX6zLK#-JJKTp&e`{{xl&iw@LM#p=IHEFQN0aUQXO z;x_vk+immm*&LOui36+A*&8TATGAF1?+MWydux7HXg-}6qzvN6du!LW81)*JH4@1H z-q-;_f3%7;E8H;O%t8yEbdTw!m<)_(3YA>#xFiq6<~T1DPfR&IS~O2vx$2xkTxOs= z19wL9#0=+OW&7U^aOR%YFHp5pt`nar-|T&&@^+5%JR$x#h@$n=d7(S1uX6s(=C zvnQNoD?R;rex)KK4yWynn=zwTkw?n}L#1%O+!1NavnA(Wxj=W~PfsKwv*@D@B(w%_ zlk$?}toY*R^*CK{K{`jr#Y?3(`FhtY{arnpC%{9`{wR+M6pWlye=b_9Fe%eux^j?5 zKuxMeci_l_N@{lZXD@&KK~XcCl831P`^~shgqKZ;-qq%_N@|2yLx=ZF z7p~;chI$)I26>K(+GusNMW4bfN}SUf+kzrO*{40FgVwKS>n{!E2O9>$%0l?)Vc*#I zExL?_SHOqQHm5qcNn!x5i2(pLA)B&kc<-e9Z)Hth@cc$#^C&4X*CLAxB!wj6f>!WG zpNAE2k~u(J&?%Ar8()ixN_`oSo#MA61DS|wYDM^hE=Sb+bLgn$QPi!CMu-7JnQVIwG~BY_G&s)BI~6Hp-xKD}1u*q{B1F*^ zav=T#0M~sl@`U30RF*3J8|C=;EUQLzTVT6N5B>l_(K%Y$1up57`qB0Kw+_~~@}+Me z>j21GZA)L|k@*c2?G928{(R=BzKMQ8<|45#M!q#PDNJw)IGpdV#=Rl*85&_Ix#6rb z0z6^C%j$RDX`lQT=2tn~&2G_{Rk2-sPetTLOR~<_s@%k^c%jO9ZKF`7a}|&fJeVIE z+URYNEp=@&0$NG>m>a|ba5;LP#}DS;dJLDG59ZrJ2Lj{kSmzR+)vnR9)*A3;#yxVsY8* z4@#EXX_J`h^InAeUBBE(lWB(Ti6EJ78vURM7|0$w(rNgIb|@%4`SppH#ybEr@F}e{ zD)gy2Q}24Sw0dZr%5qy@`|AX)Y4VH<+c@Rv^Fuqq`^~LGp{zJEk{u1{kOd?vawuvNn5~KSkR~gk2@tylOX4V z&sE3k$EFLNuDL4lCLCoWs;-aa%lZf|7PB?jvywrt#XF7HSm5et1u3Rex#QZl^rB0* zrf)MXZ4IVhf~JA3rEl!%T*MISxg8 z^hkz_cZ#TW-1P=F2aw%PwX0RQ0Bugr$ihKc)g<%$(=l%3BW_-1ZRCz_1x?hs=G)hv zgK#V_xs_crYSAH(#a~2yfRLf}4t-#F!^^(8RZte+y&kJEJ%>@Snp;@cxnwrHiCK<+ zExDOvLR%B5n^k4E@G%vJ#6RrJ&tMs5RAqT~>lS3gBSp_63q4hc6GSbgGdb|)+Hq*h zWIU>^aNE5ef-7>rJ)zAN!UFPb^$wdhLtI7W)%d+E(n%kS1{h1Yp-3Cm3`gK8#%;#v z_d30QbE*&$@LrfyY_Jff`PQ$40+RL4r-vZ|g~@J8XXI>=MJ9>f0kcb(jdo0_hv@Go zRhSZb+^_`M^RtW>r26hq)9_J4?2@_WHwk0ZXZ0W@g|?7sT?%9*Mh`p{{1=dGY` zyKuS3pv>bw-Wk9!t0X+pMjBFUm9ZaFgyb8g5#AnZ_HA$w8eaF{*(-D9fHo52wtN#J0KI%T5vsOFCTeclY&;Y@Nl}O|AN`%eVZ>y z=QiP@Ebom6=X`%BX7_)|;;s6kGN+9bm5kF;<-lQ@11*+K!S+h$5-sZ!s*JIs3#SXe z?zVE2-!83J9;Gv&Y#i!Q%eJgxU>Txq_GNXwwWtB3V~*(b#^WvL3|7>qIm|Dgc>PK# z`Xk~Ig~w>=cGo-!aX9lK&56ef#K%j#(DP>xrWuh)W?*cpLxnFXI<5WFS5Id4?n0W& zoD`7+B=wQj-!_WM@K~@RQ?=xH5aH!A@fLf_NJE|#wdPIp5zy548jPi6y4}H*R)xNb zIb?m-d#L#oYmM|$*yMH`Ug4bg(yVJ3%B-ZmKnITcA8)i_`a>gQ4I!!?EjcRxivH~h z5!=XH!%8Qt3;2C189hA_!w)7#hSINcaY)K3uaQh!JJe`VkdhSqSSVfhrf|GhB&kK| z)(#3SQYOOLWXSwj_}W~N=s-!lpbQ)bz!~cv@_p-nqoX&TsJ0NYHpR3S?RD+7PoM2e zoe%5OflCg17H)q2chvHz!(6hip`ZtYkn42mWUC^hKR9w+nbf#uYXmP_6N80(ek{g_3i(nC%x7s(NpVif5!Aj0G&gxex9*pR}4ePo$kyZ;+c!S0OrBMgvPs?8ubLiU?M7zgL&8OqIC{ueDeJA^ifdi(aX zsk}?J1WBr_9c~(_QUtG`EAq*BWEI3Bl8tFIR_{40NQh-nf1M|e8xyg-isg{cN!&0# zkucO6wgB;~P&I3oYnc7%^RgzlUMnNpAqvOO*$QiJm>~i^6Bii%qj({1n&yv$^fFQ; zmN1}y@yA1vBS1&8g}YQvAPg)R1u9B#u7BL(_a^+=9EJN4NxL0ph12n2S7{Hy5TxYv z?rX&^$P;ysS;R$u=4893=C7TL%Vp>K#kYnUib&!X0Dpw?kEymi16>N8X$Wyn?H|@C zby0Xqh8cRccI>L*fP|T$C7K{*Xmn_1f+KEFK&h#jX{I3JOt2} zt-pbYrGw(dLwnWumB?Z_Y8xJI0=7vxH(q?>zyAPSA@lW+&vzc!fag{ecoY%NC%@g8 zI&1rzFbs@B)R9VFP213OyIVYC4|-P2I|3UrUHCV559_+uy-92h*|7^lqTq=X(Gw4^ z%Durf^^v<(o5qn%RX~5TVOlY=Wswal9xb6EBzk0VeZ=?`tdzIr80rS^j2FO*?om$QD0`@e$|HAfa@xU z#MiVpRb9(MEwlRC<#U7-SRt?x6^@9Lx>Nnh|4|9+(4yW$?CEB(5>4Z=q23@{`BKN?F{r6 zsP9Ln>|cE2p49Wb77!J>@^NrRFI3;VU8Zw1#sL4zOIoh|$yNhw8T4$OL7+eBqQ;S# zWEr)1OErU7LS^rNCed+)9;NR3M$5a5UP$kikBx(js^IF(K20Smrnvm|=W>PwMmSi- z^|2d6I(jLwlF9b4N&0rT;Rq?@YVs_;zTO6@=45V`$;(_SC2FTA#?#Wb4pJm%= z%1{VY`l4D@R!%O2=R=uIDw)$w{!14HgUA8jbfqgp1{ro|{ym!on$mVe`n`6Q)Z@Tm z3ey;mfe$rRkzJb9>@K)`KJg&4;Kfy;#U4zTgW`YDIboVnN$ZXAZ2#z+oug6o;m5AG z9e~m|)B_D=GhgJgpc%4#Qc34q@}mk90E7I&;D(Uc>qzzzg_a+^sZPy;W_b!74MM-w zhg3Q&@y!_Q$w6DR&6kao=R#z{u`3HjtIL+)`fRWo+i>T{=e)x}&D&O>M#PP0BOX#X z{&_Wr?gtFzP9vhWRR!sb$nN2i9fgN#dfWqfPlhk#L-M)3f&aO&*q@eHoSXnB36lFW zQMd!4yPu}H{R;EO-?;xe<8(R4(pCt@gKIbYL^x8dq{bG!)AZnD_z?$m7$3O36tjp2 z7Kpe*tE?wo*NZwPEePvs@o=#04{)H5)$^)}%oJ|m@|EgSTcgF`B)|C;*?GXkAjswB zr|8#G>*&dr2f1IM=a!8WmJN#fXr5oTa?#5EB#AcV6cC z{SOb^?0t}IjTAS6_cXM}|GCk-r^VvB6_f6M>iNd?lal~BsWFD8d z4pHo(nhAR2enJ2@58qHGw?=ir2~;nCj-Q~eff}!oU2o*4_CuAP&lvtDkj05^VM(uz z{T^)C%gFcnj1cw6_e~L^&YCch@#`8e_K2U@r?t#NH;V6tp0PS%6G>d!@CAuaUm`r; zC+zYnr(N~UZSL#YHJq^u+*_@`zqu!U4=Fn_r8Jy3j*KHMVZlRpq32A`d5>}jT62AH zJ-7N*(hQw=s^yE14)L=A~10j()gxqpU!T>NleczAb8| z9a`ZFhy~<%TlWj|XT;U_sr+vBfW)EQ8HS)lgT_xLEVJyyJ8=OopIb}RZ%nxSY}UHh zbxo3v>+5{N%<$*=!u3w0=~cDv!&;YHHP=RR%=q&gVOSd$W+e-8P+hOZs}s*a@ziqD zHrID~MbTNSs=v-NA`E~?H=59Ataum0xYWDC{qG$GA@1Sf0RJLOLTjzM)MhLtHJQWr zGJ_`0tzTIe&Ocxregb>4hz1j_&&##Je8J-aI{!<2rRyS~YA$RuYXw}46wh!JO&YzX z4Qs6RR2piSm49xv3@B>f1!?e437Whkkx6~TA}(CXMJ>egS&par$-Dcr zSdAN|gj@#ix$}VZ=p;VD;rL;XERHT#tHgM?Y);$MwlqyVj*5 zPDroS#nXI)cfp-UzwQqgUM_>gGw|UomEU~>h{yuRx>q<(!bdU^MyjnFGwK;CaVl{D zqFDM)@Ga}oej~h*iGDEOS_~T}i=zx&H>7sToyw^C1_QcceSLEM?^9lyN6DJR-j>Ka5S+>BxUD%b?*)9 ztH}#=c3!A;%eB$ZfycuQWf`I){;~VL&B3K~5A3T1mbHUQoQssu3G1#(d{?iiNU0RG z36uX#!YuE$D7IF!`AyC|bSs+QBGkX|A$3hsGe9l*Of25$AD>qnkDu&}ey$C4u-L$KsbHmv}{%{wa4Yn_1LPPo81Ov>H10jP9GBr<-Ki!fmA zSw${tr`-akWrx)Qq4wRvK}D~_p9pAO5|IqBh{P^ijdh)!I8k*=X~y#?m(a)i-@Wwo z>m~H(ld~&^Q0K}yFxBs&kXIce%NJsHsxT5eq|KiGT!{A!W}2xlw4L=;i8@%!;mlP5 zTUISgFr0Srez?YJ@>)^0POlXoQ{qVC`j9uQbbZERhRhqR_tA}zNHd;R%3~wrE9P&h z?%*%~W}H?_vYGp$`#3<2{R}~cXeye%mX#=${hbxR-}3s;@;MtXp$M3>f0-_GD9?#B zuQwwt)*@3&TZUvDyUTH&g}=v_vUWASvtkGRORTiZ9iz~_QThB4uYp%nQNqHS4`#|U zHQ|iPr~z+J+%GbN*Jj4zc3=^{_6$CBvFG=S{iCS)#ju{~lAgJ9JWGfQj*3huP_}^6 zd8<;qEALa$<$6X){wKY+yD61}^E6CIHBhw7A<*Izl6`iA3+5XxyH^@At>| z`SSg4fmd~ht|@_x&ES;MH_?l0Uv*_ca}J)I}b z5RENwVf*n?X~dt7Dq>_!|D@)65r@RB&>7U7t4LMC7AY6t!#`S95n@G9V3Q7b+)t@c z}%g?`c3OY!{weq@h4TAI>W2M5_H`kYj)Jjvgn9 zwo@chvx6a$4;ye8S0uvq>B165T#~+vYKB=Ja&Yte&&P2A zt}5;CoboeevgDwZQxqDsrgcGGRO@93w0g_Lll4<+w56T5m zypnsYOGjG|wdL%qUeKS0o?{wFiP}f*pvDsS@_WS+h6YUR?sYm`>!#+$U! z0pD+)3?gCq*hG9VWaOjmHQif7v67PgtxdrO7FkIqlIk5IE3ehI)00!Iy*(q+g&Dfv zCIa=n_9yQ1RBWyM=e;)h(){0_4ixCG1_ODJk2-#%~gQGWGWW8Y0ZPxJwFYO1u%*HP<4 zHVQ7&6V}pa9@wYJ#gX3%R388cfO{by$9;N`RAKqp$)84Rem)OwX-Jd{SAV;cjIvl^ zYEr%$7^dHF)fCbWRu4E{xzJzT6s|It9Tpz?)3^A(nRcAZN)>7^`O+T`%phdfVtEd4 zG^1afMS%Kg3-vQjrSvcRUQ|nlL{&Iil2&{g{oku%BIBqMKH%VIvK*M-&l+3;5^X%6 zeO{dq&9QWKo(T!}Ptu6n6QcWx-d&0CJf<%;_deF6U$7yDabe`tG#^(spLt{v-MC!q zWtUJTa^=khC4smWCb<(!H^q$co*0;hP4{uD2$L1BM1g_K8Kr`UDk%H#976#;&Ll$! zN#>;Rb-8X? zMnt~bwcm5_YeK+myBU#Ls~eqgmT=j(f^K>W5Q8$5uR-G*jAO%&eKK}xz02ThtF3C7ln0JYJ1_f+dZ;% zL460;$F>3l*U!6tqb$}%a%hZKN#{}CVQpGJJeV&j0_HfMOH6Y>-&4G^ioJt?V+&~C z_;Q-N_jro#p)3tB`q_4J{iE+Nsncrn6e!L5_fQ3FK9}&|>LBBsL!k@*JhJ?<=lvoM z_HE#%nrbBz(>&;!NLD$WN%2oay&Q38SRjyhlf_r_SSJFHXRPaD-q`IzJ}jo6Hv4TGGgZ&4G&W66WzIrW5OP$mf{P&DdM zl#F`~*&l5KZW1t}CfuzLN~Jo^c^rK-bGYH#O-8>0^ZP2`ND_yKLg^yZ-`cQMc{M`~ z=L4rcNX>gyV0O~Op#`@w$hxgJ?v(mNDo++I?VF2C-^1;(NuqdyG0=Xv+dzH@+LOTr z1B+2*dPtRm+Tp)KStZx2D}e08Pz+r}++24xsS8}dAEfwLR4Wz#~?2k=i(EO`^T)=Ux`u;f33zYk&1F) z7Iy5`l^l>8c0fz{SaHI^f2TjpRyk#7w>n&?t@*P5DOh2nvH@l=h>e7Uf|pWc8y;U`f3SXY zYGo?0F%gg`JXYMSsbwQj$g4l$QXgcK9oE?0XV)VDxf9;%c|hD(2Pef zRFo_F{{a5WODQu46)F+mSj}5Yap5P}QA&Q+P#g()+&BzZ&SI?X8~%1{yr!z$xXN>^ zTVlO#{%e2*utX6HDG2Y4uXELReoFq9rq$*u7>TOT=@(yn`pb-3Nc&AQ?rIJRCg4(b zD7J)&_V_@7p3(cU2fu$eYSqBNyO%!M^w>)F$zMgQpe9r$ZG`OER3GgvU32$jCHaw( zh(V=qTNuU~?{d@LAc>dtTsRl0GY zKh!RS0`UcBRszXs|2t*5*PS|UJKpZ)HA%Vtq?oo%QFSGM-#ctS9W3$LQqlG}qvW&3 zm-~jh0#>)};y8Ze_iK?@UB{AKnBj{LbgmHVcLTjAE7Uo-!1R-wPnDLfXohC=UGxo$hr}x?1dmesOXs2P>aJfJ z-Ff#^cK#&a9`?y+H)*tl&yK$-SzI-m5DX&^4~TZ=H{2sw*Y?hOc>H0nvMZC2^q&e( z^nc{SRmvyl?IWrod&hP@DwYjPXnAxA6_NPUG>g6VR3%Rr;Yr1<Mh``n1@4wYfGSkJCTO*L4QXT7fZv5iekIVNS^0XkO{A)fc;$+Y%^miD zrI!pud z)w3>j{K9v0KAUuEs#ydi0^m-#Z~3S4QmJ%>t~c6}BsQ^F7Ujbt?FZVeYKzU5cBZqsAK!9ESw0WZ&?Ag2StqsB%o6}{e15FEBKdAmt)r`*dXvTQuPh=rKfJ|m0 za?pvr?P^DwZBiShxVU4S`}HTnGcLCAz4_PHSj8fqk-u{_deexL;Xx6S-nKd(3LoM7 zko|=R?J$tj0?SOMFvymb`8c{#4HRj%T^fIYFrzE90y!XJR`AF zeX@a5Z1NUaH=uX!%iSvrVr3g7N3`LICu;fjd0Li?xF5+>vyZyox8et!1%giF$}e+U zPVnTNFw0BPN|KCWvFG(USB{hsjFxVqo}8$1!~oRm)+^7~WH0)#a2$3y_XNxIkJt|W zdt`_S1Xw!wd$N4ie1qeQ?-=?QnWGo=y5pWn9R0abY^vjS$=PB_tzGcG1pQfMCINwb zZY5H=F(`nz?|vP=6gKkiRh=2V6u27?gbOuNzJ+w{+)vrGm<( zicp7NHFHtp&2&Ys4Nz3A@@p3HNLU0bv-{W$YYbIr9Mh$rK$}N=R6M8&vsT3$%UR%t zi9kLKjVlvE$RDQl3!fcuWT4_xXdNJaP43Tz*?na6l@rzrE&jF$f9?^5Vswv%*6J{j z&(>2LWJM?q^&~9L-6th}*X{guOVVoOC4kpb#JBR-of4iK{BraQ7pSS0E4)uBnJ+&Y z=p@Ms5K7{|ofb|$Dt!!WdsdA0=p6>CCxdJ%6P0l+z3`|)Ltv26lO^Bo*w+_azxK?ay!N`eJ+Z+PmRE1ZQhzb z8eEA3VwZO_IHvWoa$x&nf0yo_No^cyUmzXUuNIY>l((DNd~iWzVahSPf&{VL)0xfe zzUkkuEAX<&-WtCmcfU_HRmyuMnuqg$hx*~%$BfhVa?vc8jox5~tEov@@ic}s(IUO< zt$7-}P=ePCcBXCh=_?I0hR@mMh6muy8wo@YbzBs5zWwazwCmmIwggRP!Op2(%n^H@ ze?($8#&LY|>MS!Wq=(23N*wAIe@RM2SqXYxUiRN&2A=lIzxzbS!=M{Me37N=)#{N6 z!E_CWVcUTECa=D_p*8T%T(Yk^?=!g7pg@~i2ArjeR8JQ!&;bKiDTJ(UZJXM67!o=n z>r>(R(F-20Ttab{72Q(`yzFlWUN%Y|uoi(VE$JTBn+FzgC&$)i_Vgwz7gD^v02MB@ zl)7!ri{zKG4X_V#&z1O$+x!Qn*v=KD$AXOlV-+W7F~=Z)qXeIAo82%?E9i(y&nE%V z3gw(INb6{(!Ajm;VH<<*q4TwmURUmsh6<`xNr?YwRH@d|pp zQms6W=1*(p#OEWo9Pr5@nI-b0F6Y{||5;Dst3F-c(y8~M#DE!k~Bckd`%L~NC{{e~p?#`M_yJwl$JWw4$ zcXMUYZS<~8c{-}^AbWouDGvSosh>s$?As5|W_QJFR4Gelj&jWx3G;tC`yC)F5TA>8 z+)FX(_S;{Lr9NWPZCv8^Ar@m8smjC!idO;kfqNVK=Kp6A`Omo;t`d5`LA>K;!N!Y z(6!rvca{l>j-CeoY}w#>r%!_y2CrM<(Z1|NPxSHQ6M|wwL+=>E;^LUy@o!`lC8nSY%SN?1fi_IL?HnMHYqhlBZrbF;k(veqYeiUr8HqWQAtDZ$n z$+?Y7zq}MeXgiR<{^P^`3K=~)nHcKura~BlcK0@24N+ueiQ(pGX%g&zLu)Fi+rW8Z2EH{VyHb>)u=ACqDYr^gzF zivyAsB>N#QTNgnCL)Sc#+4M&-p7coU6sNn4Y$13N^r*s1a$=}x=xI{uP>NK|1*yfh;+{70!Ph!KGhP7Wh=GgPQ zVOB|&KqO{m#HqE#3`0sDcSY^6j+I-9>Z`dmDqpWKAIm-)wtD(F;8C;ZhtzckiZnmN zD>D}?eYEl~V8Ok-`Ci?VOX0s>QYYOOr*35(SHkG(3#1?5T<#Y6#%;eo@Dqi6>T@f# zC|mKF$@kTcrr&Pps--TXk5t37~f&u#-J^7GE>Osi51>u zA|Pvecy5*4$uYv`EA&j8SObGp5XbJ$G!II zYKBkc%amCs2_!^Oa({-J2sW2$tbY;Df@2I)XbsfPpuQg3Tmmg9N#Db!nF&JR4WwBF zHF>G~QjLicmgR_b-(`@3~|sKvZ%GkF+m^X?aA$-6*&K&*tm9dP?>qpwnhurA=s z{v4D#{PR`i!=;@wxdEfhKi3hI}N3u)}P5exR~+&k&Q~l?26gQJNT2F6+i3R+UCyS9JHb%7fZf8>$|(VTb`7g zR7S1=5){L|joc&0Za;gHMJ*AfTYm7=J2{Tw(n&vy3QhzVUA+L$?=X2G+#H`P9JZc{ z$F@s0Xr(AU7q&=xVm68wv$vokG8+Yn8DC@yyOyRCZary~1rJK+%A{?!T}iQVFDKpj z{jtU=zeqW{(53?VaAtilJ@sEx45f7;Rf=8x(juS7p96;{vbq0ZNjh07@iCGjUM9G?_s~E zyL%Q+qft_u}7m(zYUUy%d%caamyCQW{al^U9-1r-oi~(BX#S zPdD_(nfP3RUfjkT_i&Ib@9NUtt0z!eR;L&hV#Z?tY*|=s{{CmH0nIZkI+IHD2I8=- zakwm(R-JYQ(2{8xaT$wU&6KM2phndFz$~oeiyz$vG3XQs@Y=N1d)ByuS;q`SlfA59 zd+o0P;Ery2zF-{?(Vyza`=R6c{1YFHRb9vgn1Ft9D;Dl2SJ#kJQSXP);LlgUmMo|N zj+#g3L?!2SjiOvDG-2!;^O*R+vgf0Q_7}<&DvGIEIS|^0^)c?GyO`}$%+%cH1mj!SH zcc>A6JJSgGeqmo#GKHKTfD8@MyHW8@gz2dwcl_s~n6_3gxYi~80F%!rMz z^U;n0gov47iK1k5#d9$;nxQ9!E_aXS!M7(pQG;f1I6c*Miic3Lx!dck8OuK2ifg75 zN>UA;H*b`J*4{g2tGrtC@P?aJ1>ukAx%mCTS?*uK7fPA_rbl`dir4XwM6=fd+Q}bi zoKA`Gbk@!=`WWqA5 zdX&^FOVoO3(l_xi#;6@#BeX#4{e$&W;`t>UR(Xl~tn0?KT3vJa0jgooTx#c$ns&xv zZF}uVtn#KN1a-F($K4qWCbZSeSs^aF9Gbqvu90TgsTW)lcP&8lTNb#53WBEXNOsiT z|FZzn;&MFnB@$hhXx(1u_=cKT_rCc&wt(ZEljDP0iP#T=`Wb{b3Ci9d@5FG@L2zaOO-TFU15erzOvVZ3 ztkikUF^vljo}XjBR86WHDVotfqcNcwqk%ImVNxQrH8^zew#=p>PVS2Nxhb+P*P2#{ zZo}(Y$(s68LKb#2vYK#b&2->b3mMZ)d4XtB*Xi*`><(ka&$BrSk2IPNKSIx-`Ws=4<9d0Gl%vU>;WF(~ zUd7(&O;X-(EH4m|<+ne>O#Cyp3T!g%c6V=_xi^Jh|p$rh^S$GKRz4gpdymq;@>0! zLOL!FLR=kje70#p>~*l=yHK6RJ8Rm+8RjWGEU=m8cm3ftODrY=q+ivMKi8il0#2!Y zBj03dC6bb|#3_jqe%nClWj^RkKt;5yCz5?D40V!#e|WBrc*p^+^R+Ba*=gYnA_CCl z{vGMc%OG($i1B#2>9t(1t2L%!`k!zyhY|Ddl31CP2Vc=*J+7BIkFN$89EXvcyI}9a42m)!+PwF(pT@ zD&gplxh|2icy?#0zrzS5BKPzU%mw4-0_VxG@EskK$P#JZBm*6WZQ_l$uC?710vcPy z!8IshZH+nk>sO`GjH*8YA0jYvXv?dfiqWTm%eHGqp`q5~Tuqe9*Ku>_@1I_Z zZ&Sr{QdBAr4yGza=!G<*mlr4D)FsB_tWWFYifT0**Il7K@A1j^t5175il;BF^|V?- z?edQ)X)35Zq4u+JV`--z*JXRq^{Ud%XSJ^fK4dz=xg zaHV$GQbI~CI~$rAq7hNuf77}bRZ>rFEXwPU0rWl_vwqWXTh%oz@#R#_ZQoW8VFqtb zCuSii=FgTnD)d`9%Ho!TEfI;&@;b|6cc}!t7n#<+79|U9jjYps$+^?@=R*F2;OGE7 zC^0rpvpEUO9JR1>)F^zZWu~Lr`v8u=|+ZX(|eyQVN zr_`FxT`!D3wtwp}k;>kVh|VU7`S|_l;BioesO9G~C1J#sG?vRg&QW6Y5eqqM7Q|N{yCsjTzq_m)CN0)%4|0FodB9 z-HNvfK0OdB${VLf`9lx%4q#2c#QWy^#t};>negeiV}X`Z^cj3b%ap3I)N|$ZM{)wg zti_%J0f?{Tj3{(srby$hsG48<+)wZo?d$TUV+8}PJ$GMCSPT0t`(#L&g|MqWc~dWr zeV6v%kDYSKF@E>(4MPo9_`&)DV;>bEuchTS!+>0o`fc0yl$cI$<~xSE9e;crb7Id; zD?^O$Rw~7l>eJckmZ-OVBg2&s`k`jvOS+XJ2eAz$Pi9zAq>&*FslA;pze1F{IwAB~ zu966O)L@bgVk2On^De9ID_Y$sGNxDmU^o2vqO#~i-HRhel;rsLS13O)zX`k=uV9HN zUObY}XV|I|gIlqJu>Oa{wAi|(XP8>8@@^*U6R)Sd8n0#5l8SE3l|E`$M0AC%Jx-BL!h<0sIyioRtu(yyf5x;1_2C(lmU^$V`$nWb4RG*#1-hp zvWn`@otQ5TRI#2KmBf<`XCoRDX%S1f@17r< zeq5YSMOdi|{KOn`4!z|5vtfB}D5ETk6Q5{%wn(xg*Kpxr@RPfhsN`zY`CsQbf15Q5 zk)`D_A9ncM`CC*W_RdwQSXa6G#rvC0v5s;C-iL_XBhjdi3yQpc+MzGij%!FH`lEJ+ zae0~}!%ZscD1Qs?%+FIuI8ImuR1flhX3Q3Q5DW;hXtAb|T# z?w!^r@K>BSTaKZET#809@&CP?aDACWt5M4dJwc3h@frd_V6~G za02Ha>(ZiiFhkuqQqP2P;`*7vU6O-Ps2m|9A%SZyzuO`pO5&Iz!d4LL{_f|~{kvB} z1Fj%&Nz~##_^=q%V#26!^n#D9CmND*IWL5^<@M>uK8j4-5YrXuyo4;f^J7%4BpY@2 z?T^1|BtA^-pGuH5TmD3Q<3d>N9XbDi8&~$CODk{gBIV_>CGBsSNb~xAUlJ)y@D zZxJ{79KQ9T(_dR2d3>hg)t)|;l9oo4a5Qaoa0DwjLk%++z<}(03$C7CCH~>iW$9}S zY)JM^UhTYeSq=2=q_YqCdgV7;_BVcAhd&G2EI&7ARnlc%Lo3 zd^YK?i<aIM1m=Nc{EbO2Z-k?iS}mRg2(X%bXrD~ zh+hH@Fvsh48KAIFwP?MhDa<&=BDTKtyC zENSg**YYwrw&L&Jf5917H@p8pClu-jy7**D`9!pBo;-rQhO#;B$+`r8T6{2&; z%QP=01&j`n1-=Q4Xw$3fcdHdv#kWKd*5qjyR1JsTHB-enruXU*3hIt#((d}*n3UH% zi{e;he=6B^lIT!>Bf^}a(Qt`Oi4v_t&>Om=aF!&OX}z@e9+vBNSfN(uO^sRq72f3T zj&nvI!9T}hGpPT(U12Zv7LBx_;t~&YO5Qh7=L(6Lm({e zx~?;wFs1qPcc4GZY`70&JzhGR*H%8K;2l9@wGn!b-zKlEt|Of?u=C%Kk;}qLR+|=^ z7A~q)nHl-$0kgn9BZAt~X8F7S&YZyNVXwvt1H9zR0Y(ZIV#(R&oKt)jytiP)Dg1AC zKi#%r#>pg3W4Y2-Wf~^6F=;tmRBscRO1FWt{o~W;73_w?xu&?}B_JYrO#6UJiYZP6 z;{9Epd;SSLridW@?DlW3_ro=^S}N=wDdTn8-$*g<4ssu1S3l^b&)NRkq)0R5Wz(F* zYE%6XY#3Tea%Rb&!Hr{)nhlFY>B->c>##%$cX0C~8E0#* z$YK2|O{YZF<<=O44k?R%29RR~G%o@;xmS5TlG90_A&JUZ1KxD?L+0BAYUPKX#QvxB zB%uUsjXHBKJ8Y=C&A960=~`9ZUwm?!T>0I?J@KDUg7MD709*QKE!gJNOs8uZ0xz+;f<{@t4w^D>u@%IENlq?z8F)0k*BWuHuvIJE7+(I6=H>p4eUH)Y+z9d#oyubBS zI3$H#Av7E4di5NSGzuimZ6UgT_R~JMos$MYx0W499e(_J2xnK%g1eYax!#(ymZ3$(bh$A?j)xnv*lsvn}@@Hq#yqB>-$PShVDVpb*uMJ7j&$|QR2AsPid=f!9!p# zb_M4UJ?&A=>!~cwtxL3K`CRg=%;Z-R*qnMJ7gOMDs9Fw(pvsVPc=o!*1FoPWLyVdq z5;0bqCW{rCXjjX@mo}X4eM|oL>}E*lL(43MjnE#ZuFAfIjZG%8_nwf)34_B zg?lAI(I5q%+uW09UyeSp(XE}Dx0O2}YN<#C3JiirFAk}v2;#q!;sLGr$V}3}u(KlC zI?-P**b-|jh7F^G=?-dVn?>KNsfE2$oHkYM(fO{yY;SN^0L5TUc%S7eq8%Jc6G z)qU;;lQ=#Uv zEP9pmEvaT{ibw`x~p{ z!bPxfduPKu%@h;$o0_)jPCw^8lna*>Jk%XnG>C%;#XGhAq%dLKCv%pB_dexEERk*c z>~93@k->S@@%Wca=)_u~s1XkNIS~XcD74NgF?UM&3Ollv77X+guMq&8PpWO&2BxGH zPaDBms~%3I$xyW-QQ{!r(3dYnTO}60We}7B&WV!mw?$UM)h;GsVs4lq-0gnG)t;Jy zdVKr{%xQQ7DI}#oYS#9s8$G&DMbj-iX=>^^9RLQH+}{!zaUXN&s)$?HupjKcFO@Rw zQQOZ6r;jM5>7-f#?HN#?PTQgf1gvnYol46@{E-C`TY0FPPlb(vg2|4S*B%zzXBtq_ zG+4W_l*RBsamW1o#eEZiR2HvWIU?sua-Eo#3jzj|%Pr9E@p~ng?UDuZ22_x~To=vb zo<;!P{&P;V)bsiIZrCH8aZnU#dG!-=6kc- z9@~hq#M8?bz8*+b;5{;#5!u#bLy3pBZ%TC*oMG&FSA1T8?&){(%X|8GieoXdw0Rg~ z*@t?4jO>;LmbiYe`OD;6$BTMt=s1oHH2ryTJuX`vWpU%ESf~_ub zZPkumxSrsk3kB`!N03l;Zuc9$bPsDosJ~np+Y&mkK2OdYpioNPB_`A(=5LD!WixTy z^B?C5Ub}Jfx!b9;&%6K5sbx@@i+dxZXO;!DFD-3_*7>@bSKSEBjo@@>%J2IaNPykj z`Ju<8G2+8R)&JR5cc*5()rsMUUj0L{?` zT?-oSA0TqH3oKEgMnxuyHgwJL%a@ne?)>|6i~YqVAkp%z$mLt_^r(bKqTJ=4x#~9; zz)LD+t8^qGZl>5xv2Op*HGY+x_27(Q2{8 zVG+rQZ|;egDp!z4fon4mv6he4`_*@#TC$_h>oTxM?PV4^ zh6{CzmapHXcW1RxtIFeTQaBgd9m5*`XZ^yhoFOAGo_9=2Ns<(~!J|hV*Cmy-d2>&h z3q~!AqG|0vD_k}QNuu)V-tI$x+WpJN*`IbM>q6KCnS{5j?&7~s*L~hD)D=@tI9}n^ zw))Emn8KQ&)4%+g+M+y1V!@1K5#1^vju?uQ)ERp8vxmFfmP#dsTnB7Y?bkWS28t|9 zn?(@$=?zi37N*$ouzZpqPKu<=IgpMZj)*WLkIi$;);=6yQ=R-10h`m}7RZa)AG+@#-5G^?tA^IQ(7P6q znHXrfWf_u?J!z(2%8e+kw^yp^m|-*KTf@l!aaxxt*u$yr+lU~0dCq(3ZsC>;l#`|p z)0=Fsva%B1GIaV4zA|I^qyFr%7!IE&S7i!kXet*1^9dK z#c$~36Z&}W8Ckfkhps%2ox6V71)iE?cfCzyaX$a<`#g-Iqot38le4za{*C8?OQ2D5 zE7=C2%INfx*Wsc1|r^ADY!H;>GIXClqle*5S0y&X!yOJLdfi`qSri4DO zr1m4yy6Nqf+Zmt5nhbdBJ>V9C^FJLTnY?T0_ypX}3!4y@1Ug!Oj;=fUi5ZWXRo78F zFB9O3<+?#2=F{QEW~)FtbMNUWhC!IE0vKJn=PR{rXqac&v@-lt+j1l*p_?6y2WmU zk~Fp5^@YFA__3UKem*H5=p8nN&j=C8mD{)0r{+^{PH$Cn=0H|~i0cTqxaG02N?4>ny*Arc4E3K{(lkoXtiyfijRa<>eaPo}F7 zhL`NH)oSLBR^(>5vD8d414Od&6n}xkzRhpr>iXeT&S}m{kt1M&EN({&@cMAq80>IH z&B~7!*h(17pf+DcnWDzx17}3MWjFW!I}Nv-2fekO@b0V!2sXH=5UOIPXrcp%yw^ss zTrx{I7N+ya@|KL0k76^Ydb6zzvs+NiAyk_IT+w= zWKhAG5e~op2H{Q-1_r5e2jVy8a{5R6lZ}6Q0wXz=c#*|TVsfZta9%nGC}cCxjPXcB zQUWHXg~i+z=mqQ+MO;M_+^X|(#hQaBK-EpUrDl$f4GkdW#fhq2e*1Flm?3nyyl9<& z%JO8%!+?26Y?o&^@2&k@W?o?%byHP+eAWj%A~{`TrQ3{o*}z+CRPM!Ii;v`!)XR;f zqnKc}Rkc1Kbhtr-h7V!pg>)L*e{MG({|_WK#ZAoEI$0H!h#-bPAPAVHkOJ>6uVzIH z!wdqXij~xBH5`->b!TVj9*TA3$mk*puGFpf_FZn(eeR+M1z~zSS>(73W}*6TlcD{r zQ@~;;MpDFNFy)bJXF*t~s)NaT2A*~`znPwoIqzh?;nyg69WaB*m)tq+TYYzK>9$M`0ozg6m z8#%8OTI;%tF8gN>ISYLBhk$A|yxt4i1M$KIib0&OvPK+E{NILY8B(f{ zbni6n{4Ze5@}HUb+1)x{c<6<@3=GaQwj8}*5i&z4=8BZAk5dizCHoE2Ar_!wl&6Xg zUj&gXupF40=2G<8PTwTmp~!&~<)hvU1PH=HK6Qi5C{zZBpjzv9J1LEPA78=&RfJRA zmY4d;X_(yKo-$ERvQtB7v!3jQU&L48@8X7j5V+Mp+Ep@RpKzFQ8#Uk0eAMN_XDi!G z#kE&=yep{$?srL0JFM0I34_4ZdjE;OJ-3lr{E;b}bnl1SN%s9bY`p~uw}Z+BPNN8~ zJVwbnqbGO+JsZZ@yFY@vFn-LzsI&T`s1F%nYU`H3CXnyPjj!zH~sCb5b#sg1eBZlJGmV5Fpqq{ z-k6n>nO|VAm#BNywo)MpZ8ftzAPh>qU{q=EynJ0D<7?XRB4fEs>5(YQz4Jm%B=R6> zMxL4c?EX8fhCpHI;nG+Ej$veVFwmIe*DgkJ9?pL1Gea8XM2zsjG-0yHy@7HAXW07H(0_K6AYeD&HQz?HRlYIR!kNT*Jx8Ln_`P*ef_N2WUx=;XX(E**=KuG9VHM zsq#eDHapL8#Z7|abQH@U;9rtjo1FGWKVLl1!C?A>{ii~8J$`OpzB03p-hdZxxh4;EB@+=Vg6>~ z9Ud)YwI;({VLOJzGm=lY(Ef+wH;q6Ubsvi9!}|00ew(ssbjc}AJn*%Tm}?~XItHv? z5z6&c`TV+MW4@V-1S6dZTp#j{steznyd}|K zTQ#D?S-=tOc>5_q;9PysbM(a{pkC&g(R0vbE9Gybb>}$=2rkiE_Gyk)^ zuK`hawE7&-?7Z(fM;H5vH7OC=%1{>cqeN6VokeRxo2YR28vN-S+fJ?YU>1~4idu>e zpnq(xw@TF!*^E~U6&!AOsdx5$Sd80b1&bkXKbN5WLFUktiFSi1t{a67_!rt86#jZy z%tY`q(ATH^^Bn znV-hECA@ClyTrA7MJaFLgsZzxo?;TAXq&ShF(=tBmSB-I1({2e%wP#ey)0%58-1g; zm>2q_X;sH?bhxiofZP97Yx}Wvi>VVxQ(&2eT+EdFY54JCO9i!#3>zA!bd`42!&1W7 z!+b&VJ6D|KL5PcY8Fd^^)2lQ7CNM1wE?jRySUYa7DV~;heoRwCCM<&j@8JjbpX`1A z@GNC0SU`P+{_85-q)o$Nv48j8D698-peDFbDoY^oqLXtC!utlyHZ*sihL~gxoV#0j z@xKX&txJSgB#fmQGQ!#g`N~7DMN*7)*CJsV&oCb_1!0;WHUSW5D4<0SNy(?9$4x;x z#dqI=#xj{x1W<~pWUSuvBczbchDjskoBE)3j&glIZJ<~E<_(V59{~4!ONjWPGdVyu z1qb=C!%yigD{5h;LTyo7h}U%ck*u6@cthaw>-v|jL29}@suD2Lcm2~s11TB(B@Wws zAT3Y0HHJ!ujIr#WaBazqBFqI{`t`xIM*9d$-I^;-|M@Ln$k2MEqUbQ2=LwQ(d_7c` z*DJD(`C(8{Io0Y#$I_lL7qmt3jfK2|Qrs%e>Rz>D?FjID$7Mj13l`8*u+KCqZxpec zb>)8W4QoDt<$YSwjUAb{dd;)tw4(R@6Fjvij_#rTMm#SylXJ6LUeK1G2(|!GPwxD( zXPs-w1eCoJ0xCKZA*AKcgD+&Hg z-+9Qphdx$y9DMGDNF8p(VUp_hISy?9ItsULovFO}3VI~BD?FO0UYDmUg>6V%9tepC z!6FR5ul(p~Oy41{ekVc3pf5^U{hK9I*bFZ(O0Hc zbZ(cJG(^NLVudO>TB!mg_a5(Ux)PFt84Rsz)#5NAwu?o1v0JRfZsu0yxxY=P=u?Eh zgD-9MR*`6RLY_hwD>jazCod>YoQwseMA92(k9n-^h~flyUc{lh*$6YD}hh*PLgB9C4Q!XAM-El^272I zn6>57b7WVz=C!ozG<_IAoFxG3ronfCnOo?8`jGma5v&c!#H%WbrvG!}blHPZFKAd> zXw-u9C{em7EF&fja0!)6K^?{0V@l^ki<8p{t}nU;)YCC=IxMn=j*EFInyCM-6HSmW zL*GQ8bt{6#-98Zla0+fcqU)fyT%Y~|4xN4$ow+SpI_E(;WfQNy z1EXELyF+WL&G3a=ZXG(`MxvC#QG?IU;^;{VfWdrV+}5dj|Lli=+_t7L90g9df;Yy9 zk`fCKQ`RV?K>zBYkJN=9W^TrZU48Pq1*bQD|9wAH*pvHa2?jlwQjo5#*Ur8EO)4x? z2UfjsMeR=rCLhm1W2t3KVDfx=63*;Aa)T1!jkF~#?Z`=s610bpZi`wQ4Mdno@K_|b#H-52jA9-IZgsUVSw(8^mYPqP5`TU9GIE3uAN|QQ*g%@M5vMDPpaa_j z=JF8TBDQqZQLGt7j(cznh*obFM>^IyPopp|P^k@~>_H@q{o?#t_rGKP!u(Hxp>S>B zY9B4s(334Mk=S>AJ7W5md4Vu_S}#msbHsr>V0iwbG+GgoxJ6I6Y8Y7~cierg-tJOr ziGYx;`Fhy1#6-m-?SD#%4|0^}*@}(pj41VV6C+Ko@e1Emn-IqeSZtG|vwZaD#xwQ! z0fqGZM{KH31cPT5QoWv@(|pRG7QC--%dm~xOmyGZw#b7-^5&uga8p?`5ZjAN2;12- zn3$G-MXUH;8=P|y9*dydB))9i{IvxzDuUU~fj zO4!tRvC?^EG5PNYnh{|7PmphORwscyAon-V;_d`TwC06x%3`00_wV{{cRGDp9(L-Z zF9Er&nwq=WgpLfjN3XW3A?0HB+WNFRM?ShMn+g(7Z(fsH$NHIxWJF z*C0hGB@+lD%H+aLj26wU_35YjF1!MPK|OU94}uCKgH{m7lM2X;HWqv~%ka~qCBodc zaHJ2h^8k%`@cOu7*f5^D`d%BEe|ioSk{_@RqXRY3-YD^MibrDNw};gB(zV<5^KtX3 zcLkIXZm&y8t#_vZBPNU~ut%dVyHkl*Qssb!;%}UqLV6`f&IQPb3TpGBnw=0@$A z|F~1{*Kp9c>~f4gRWu7Yb0Ao1E~zQgwL;M95`nfWR^OO#-=M%~r4RD&!&HNR?t&Gp zcox-kY#!dd%Fw7lO;y!lYg`ns(m>m@*4JJdSVwKOlPs(}>bihibMy^d@3;Mg~}W7vH4)iluAzVK>_@Z;BRagzd> zJLe;$=yg~A94DhkAAl2Y*|XH%$nv=PDId>J2pq?ETuxVR4|n}L5y-9JEb&yn>_s*j z&Z(jtsFl~6{e$>DViz9l;a3vly2?ownYM1Z)2nuw;ag}M9ExA}XeU&6-xs5Cg2h%TQIif_L^X9{!xr{rG-7S2_n}4UpIw~;qiqDO}o+*4M ziB6$+J(QgN&^o762nXUcrfv11u+M-0!e(n5uBM6@) z9e!ugQ9aZ&**wMp`_P)SC$^ryp17s-qYHP~Rv+-R|LOf`D3og_; zm;rc{e=fc-V5Yl#6!S!Bo_R|Kkis9+rr7?yW2-e-;;9&0w>2!JdEl8<&KsIpwm`nJ z5t6@!!;qz%KfVf^Q!t=_XZH!y<3K*iGcAu|_MpCL_&%a@VlUk_gzU)c)b*dRHVAF) zh3vzyo)gyb(R97RhdwX7wf3=1GeBI=cwMVHb_mRM+toww>Lf-fG~;8Qmx&aM%IOhd z45%tz-(sd}hWtxSWOIFS9rp4JF*L)QLq2F9q{4jI;VCjbkWa}syZsj3I>>k$L}FUh zS+Vp_ER4}xW|X35i6(ci4jHn6Cm1yt2!fts?&>_Dv9O(ZkA9lP8A+teR%S=TIeGbR za^x&mI-Ci9|9kHHPTegA-P#Tw=I`8KhqG5+X=wd4n$ zv*H0W*&$I0xwSsjNY+KQX3l?;*2B-mw5ykneo$lO7>NB`S55WTRjyYN3~zz?#kdH2 zIG7&&wn(4};rouVbYu8cS9H4xS$I(aSvT}#v#Iv($Mgq7&2x1s&qSh`(ln-oNx7GY z1!b6xn&xAM@?x}zQyzmmDXTgbCjBrD!gFc3BB+H~apfZ;J;|~NIm`C^?rSy#Osq>w zx-Ln?!FpO7OxI=&Z3t>UBO~*5-~*=6(fL-i-wIKayIWX3md|}`e(b^_Bo3Dn786Ds znsD%Gt})6`cJwQ^MSfFe`TDJt=94OV5;=(9=;IT#t49pkss>+~^t$biS1fW21E6c_ z+phR5g$oPv##!*dO{6zprDGn#tp}x5UvazFe#`oPON)jk?Sm~^q`m0z;_f1&6pv+| zufrqvbhHInx_)=HPeh`WaxZhuGU;pIXvTDF9^(H*HgT3w2w1X!=MsomLa%=<$DIrc zKeFWXpB&pgsu4ANGqOxD{%FaAZ#C z(H9|*CC8*m{bEUc66@H~a2PZ3%zvrc(S2p45ZPq~|M47t!p(CKzJ+fe#1QVpqQ0{5 zKKsUOwc6Q!u7HJ`<`$bYHWxz+Ciy9g<0{m$+IXu#(z{aLv*mC!WG$8nEmvBpHKJ9_ zC6Cw?5@dXw*?Ax6LKOiB=$*H5U{Ze~5QHzinvbACScAEAb0XW~P>R&zXRH6ikv{T( zc8e#@QgOw6Zw3&pSzri>_M*`DP5OhQ^!(oqc%$DP$-h5H^95%hd9ylt{cpLBom<>u zQ(2i)&||G{(NhFXxg4<=v6jO+7an&8oEJu-`Y%5GQT@>e&?MeZV<~A+-`0S|DOxC< zQyQE-VzJ%Skl-ytK3~fOo8T4cG>&l9;7dfVqNrCMAN61q*?@2SQ-VEp*Dos7?A*3i6M;g9y!lNZ_Oxe zh(c{_Wj-l!7S8+2b)0l-XE2^bQ=9}hL1ma2wD5O*#XI+PQZR~y<*mh7_00x~*OM#rkDc60Jm$1rvK z)N0bna2_;Im|N+3s+O<$4~l29#dokU6}~;H_NK(*jd&z)T>nexj?{h&1!F#sjVg2q zEFe<5^i9l-xUA4gF-@Kf)r^N(7#9xmSB_iYy)%W(NXy|pT&0{AZva4Vp@2rv6F?3t zn?tyTUi9OvyF$xl0d;;1>`i3fQ-{rwJ8zkw+Nm}^3{Wp&BoC|m4?I@4Poh$lnlE9} zu>}b6LP#dn=QhvjAM+M4QWRlHh*Fp~j4V`Uq$VVE3-igBl17h`lFpDcb1fHb3X=)a4INjImlZ%e7_ug(B3R$k-mKmXi3P4lFI~yz<7W3lF>1fF1 zZ6S!`I>&Bm=ba1xiY`_KN%eFyl|Q189Lt#orN_&n_ceqHkk7ObW&t!U0?b!$`M#`* zw#hAhN1Y>y@gj=14NCdXm-|`B3ZcLKvNi_5(I;^|Phzuf7mghaz0tj2@(>>lW*Sj$= zWz)aMdP>YQOwe;_Bz?tg7Y-F={{Vy4K)}6c83|YEwDe!AZ2?#OakO?dM)e33vIT8Q ze!}n{_HrzI&>%$m2xsmHx2Rensx=~)?}(stXrsFXW2bJ)*I%x;QmCYa$mVx<3!^el z4;2g*u!~F+^mTRy-i(q4IP31)ON)pi;8$(_pu; zu{4`$Hf-Mq8S@`TPh};Cplhn=AjV?ie32CnIugWo?y9Of)qYntHc^xj5xiwhy5vC9 zcJ8AlI@F0w^9PrXllX5a!3`)v)2bW6VF!uYeI9Op>Le0#PE$4<9w9Fipm69qqqu)o zxKRPPO3WUYU3qCQff1T=a=5zQQlcAp#JAv_ImBbPZA#PXZW+M zhe^%~=~_eV6-vbF6;cixJXG30R~CVRe4^S#~Fny{z>Jt;(}fSE|qf6et_LHq?RD zg4N1+ZK}dRwBV(ZECKmzHUFyOkL7bN?~I%(n2Q+cu9$i9ABe}e{Rr_WAEEg(-JT#P z7y#-XjQ8>$9w#+AGs=PtJX4o?$XdxRaG*vs7Xd-aO@r|6$aBGR6%YMFE zRNrC53*&oaHBGLvXn&by4{LcGp`%XWZb~{hEI-U~<4*l(Bn=)wlwj?%eP@<)_3Jh6 zzKfBq8>*qcrIli19sv}~%u3~UKfEB**3p+I(^q_gJp8*VuJqT6=&zGiM!~BR1CF5= zBgRZ}n4 z)9e!mP5~s8TKCd=ujynDQ-vU#|QWU%Di~sGPDNT2z7?P z42I8TycxH#o^i(w)W0Z`n);?5Xz^X4d2H!7y5r}9(+cY6X|OBFpch^fPWBRf8>=OW z%O@wC{!JNue-0*sv|IGR0L2-%-;qH9XU3FMG<-y15+st6l>(ib@lz*&vXdysrXLNrz42U5oT^VCyHwL5yM?~Bd)P)Hmo zY(tdbf2W>jD{W}ygEp|`->{bYfe>%OK?r%iJhk_4$H+5q_g92@Tnq4^JiDO2B>LFm z%T3bQQr4B)+wC#`z<4TPSWP8nVf*E8Mszg(bt7l(w@XL9R2Y|ARAR*0tpF3BGHVmv z0D$}mxO}{*ybAk_&yQI7Z*!XpkKwfmd6nV-Pb{Ybkvbjv^aFj)Jq|9QVy;N*OLQ|1 zQz2v#JEnv@UD)}pyf}X}IY22em)D+wE-txZj1u((^)EP1zC-zmFMf3v{rI^0WmWgo zBeIX5<2)g%=A(c1LyQ73+7Hs%dqT$wOlt1ePI+1o;;Uqak;k#M*X1Vv%=Y3WO zm3*w*M}_@r@n4$Ltf=?p)$!YNw^A@ACtC_C(EcOuuZN}JKgB1#CN%8sKy4cnN0PV1 zl-}psFw;O35!MNhq%X0Yxz@*B^UzfuC)l7YI0$T(`QThdnGd6hu(-et`@~u`d;}SyiEmTq7W{t}fRPh@;lskiOo~@X zL`8JU;!?sG_0fi~F(^9RoDLo*0HFKxU97raj3KDD*)~{j7>_OGk3p5l&VjyI$dI&1 zfL_j7Vd<@JM!XSMjn*!(Jx;T#G5k&Y9Z>olZ}s|C24Gm*^DMV>JOYB1q0;W^^QOZG z`{ayrDsxPtS6r4KtFq?DC${smHHAD{h-I2J>f@?=JlmG~;(krucu9MH$O2u>6Ce)E zrgE(RDDWiU1)9C|X@G9eRxPplSBB>W1{)|#=cta-@9(yV6bjEM(gT-$MD9%j2|{^Q^7g%KECw_zjLApPY+MNj;HG^96ZOzN?v3t^%* ziszdMvK6C%N?q~|^Q}Df#X{B=#AQsK787%Pv3Pw8nh8oB2u`6ajS1^sUMiyT+_K0K z(6R8o`8f`uKfx>bR*C7#^Iwzx^RB)ZW!}%j7fD%XnIDLKL+*cJ8R=5|q??SGpZ(jb zhrng7dyxP8;WTC%opTM+$}sIulkHk23paAdpaw8$_HePslEtoPdk9a1 zpCxP_4FUa7;)~+z?WFuKCmm_+yCGlBkRi>LHy?kkI)3I)U4n!#c4ISqOCIyD0chB@ z_jkQ*eTVeR7JCnEeenv-hUXn3w3%`$3Nqzg39`s=L|j9wW04H`e@^s+x^b6n;YwfX zS`e*p%n|zJ#jTDQ*X#_YQ$e@;cme%UpNAUF`4wmA`?L#FOLw*PSi(udAjqK#cx1j# zc2@lwd-O-TMWM{$CxDCu7SMW5W)%XH6Kqbw;UDnkigBH>AUUp?`N9*w56z;p`5`!8 zs3I|?p1P-J&ED|x&6QhzWR*}^A|?i2!i3#fO~nvU$`sEw2r z+epz~)DnCgfry09i(@viWn6p=)Xd~#|MtyRy`_2mLa6KFOvl$+$ljCTo7H#L5p!t? z!C-&3lUmkGO!(YUsHg=+IB?aF6}s%v$7K5&0ywO;S-He>GCZ>QDw93*x)0Jvp5RHj z4+Ec!r<{At@nEL_$h(x~s{Z}K(f3QFiCRKK%JH?J66wa4loV-6oe8_)H4kqy8KO!* zC(pzK)jW7z2S-C*k8p*e`sRO;G;$gRExU#LTFz$y$&z9pa!IX=_m6)4V3Scje|N_I}P>?~M{SuISRfx_ZMSpx8BtxYee~$)?yF{q(B%@{2JJ)lJDYIuPI(@ecWU z)i&``DmVL05mmp%=AQ;1>FVQjk<(~^h`nl)8n7F9j}{*H^U8J3Fo6VB6l(?n4;sOL z&{Mdp(+*OS*>cDXlk$TEPox-6tHOFEb%M#M*K-J5F0#{&vmcWGY-#p@PPq3lhgl6_ zO81o*%=@eo8sW#QC(n1kKV18B{JC3Ezh{FQSmzYoe4%4n%-E&CF2?0Y2;&P2u+LuF&$0|`uCMdYm=wd0W0@k2*ckT@kZ&=e zD*AoS_?iMoQdTX;^50^a3Lf0TcIk!VU-U|4Q>J&mM`IP_3vN%j|Dyjq(Xt<(wnbNq zz{ctz9EzO=?CD%r$YW(-IaXC)`#z5QZfx%U^hs{M(~KSFGDx7UPi?75_PRfr9nHcDe+@TM~*7VER>roDN7_tRz9Z|oW6k1Q4SC9lb9jC((Mu{?l1 zd-k&P;}+86tE3O_*?;%t4K#lK({#0GORTjZhNE&Z4>D^g;Kk6jtYUsQr{RD{=LyFT zCEhKot<5UCtVX!K%Ov+T7eE1Fx4)FP$a-#@YUpS@!{CTywwoqDjPCu)KFnQuhM8p9 z7GD$V8iZ_y%_+4@4oaQf}Gn(7#h=Z63mr(zE9IOc|sO_o*K1P znhQ~7`y7sYN8G-|@dKVZ+Fg$p_BianB=z#wAPxoJYEqSIRpUQe(T?e`K6gd8c0OJy zT_^JyL*J9Yarm)y+VYJ(W;5V`PU=DtbKt+B_k0;R!S%J=gR|j_TU^8;-&b<+Go~lI z<6vo3b;UPbpJ@O*D%){j%}2Gag!L^~Md~;11@%p?{G)tF2Gp4YDj)o@dGyBbE2((l zJ0hQEA&&RkHj6b_Yvv!NTg>MtQNNFZd8VhA)%fWSPY{+qKipkBBpAt>1TL?e7h;|~ zy1+e{UYlZ_u*~K3;oB!Rm1PB%pfISaqJ%NhYzp~$_N^Qc71tBh?l-?Xe)Pxdvq6!g zMX`fvN!Rn2#n^J0>wc94$f{}4-p(znv1Hh1P<9H#vT?Z*xu5n@y+3ZqOX9ho7;Jtn zcxk_#=IM<#(}8hboZgG?U11ARgTKV+;QyRlp&Lj;v3k8O0DA5lai?HZk&HminbT8d z`|8#Cg%P??RMpc2ug<43ALN3+2SZV{P7ikv;}1_hJo7`D4bSHuy*&6T`XP&UWfZ(M z|6_5!E z(Tn~`&3Pb0;TsBK(p9u}h_E*zOX@L>;#zA^?j^ihh7KQA6WSOZ7y_9TcA;om*1_81 z4O=cX404%%PGv9`90qCjYUn>Nl;INHoRfiv!{wj4GL1w9!BzHw3GkyWv8H)LR@@d@ zAp;E_NRrzf$Z(HrAA{;#y}eVxeD=#4ISPfaBpr-65Czt3VR50G96zGi%Xz556OoV1 z|HZH+T9gjB9!GJ0FhRHataU5Dba+w1v~}Bh@|yNVEXS zXUc?##5*Mx4s^65T>j8he&yn}!A549C)=UYRRTFm7opE6TL$7X;KEky{sMaZ5Xqh3 zeeMW#<_O}jd}EqLBgV|VGp|nLo^?Aefayrplsb^0qmW0PkiK%p?!8(`r`pS%Y`09N zSQ}76zJNBYuyM@9am%)l4}cFmDO=y5Q}vU`XyJ{+mP?`z7t2_X*L8@uy0O*Qr)>f?phb~ z|FPlRe_z{aU4i6+Hk{>`8(=Oru^Mq%Z%*aOI|!Fo28!3EUugNT%K5PpdYC*Yv#Jikb# zVsz}-OV|16bss@;lbz{{@r5(fAS9|?l^t^NQQvtKC6hKnsY&$pk{c}y1O2MY+c--CONM=F?$cPp#eBePUdF?>*ba>Ili)SZ`$_BmT zT{c%*&bu_PNZ2)Ak{D+uFdDUzw}9fO&u;V+w`H6nh4X_Hbq)tqRP8vKp0a zoAGOh#RLfr!@>GlxR{@n7}Q<=%1C<@_S5%mFMUYo)`iOIw^Hs4ZIA{k zn?~UJWm;)icp7N%8^LYI=jgvmxkQT9a1P=rU5L|EV>HE&P9Y8AVifWf`fA_wBGJGn zyaATyauVH-sDnL0z}i4!{7k=Y0{@E5eLeA{2fdiQWt`Yp{&3+Z=$RiWr!JV--nbs* z!Kyy|o2reUrAF$q=Wrq2LXq|6NpXd5;qy+28RwKf^I-?xpm$|leL1&#nMx7bOU~Z* zYyy=;n4YG)G8VN@%Qb}34sL0BUPBHTbMW&ja4@!4Uy`M)QH&GU!6M5$oanGssOWM& zi?4rksI(^IR;nTZGJ9#$j}F(8H*&mPxdmY{ejD4fyuwk6Y56BoJ9{fD-hKCx(W5APO;orF^+ug34tQ*+PN@ z&2{!~w?An1T^>mdF_vpv#?h+7ng2fButp&-ly@zqbeeFQ`cZT{eeY?}mG5+7ixgbe z075yYhc~cl=$yXs3KmQ4xm%6>gzz;k8msviX%!3I-nK(j8MB^t+d|}{atRETH}=L>=_fEzpxlC&471Cs#bT+RcB>Q;9_o( z*#~u|)GdZ9I}<(Zdb{H)m&V!GWTAqW*YqKW*OA26p-l5uwEhG4_57!_U3i><{*&yN z!l&C>r#bB;X%}^J z1^&xZu3xPoLUZB!7gvuC6)j_-U-ck0LtPV@%NGrGO=IOmI-Jhj;bgfZNRQnIR{)|h z_u|Nzt+WK;Ausp@_WX!8)AlRwC&gsVnJJFczP%-_O_}KulZCzsWM=nn)&HC8?wI<{NW_Q|>rO3lC|5qt&=q$* zRH9d84}OLHG&(A<+s*==M^W@pr6E6iQxSg*N%|T5>j*S zdtkz!X#tE^KIE~;QPNNfLUWL{4!bUGbd6)*AIF-30)a2w_e%CIahDWpSdB5kXcn5& z1A$Ir%iJ#{?gp7u&%-R&pBrAA>{^mj61!|9cPq>(xFQ4uq3|rV+`4+WFQwD00;nd!%X?AlQw?QQ+4%ihC39Es1)9oFGUXp+j(U+Rz`nAZG->Ex>V8 zjj3e!E;k>G?PUqvqE(i6kH@5($S*ki8}I|gJ%Ahl>eULIW^0s+Vb2aozO#?e!XYomCk7a-ABlt% zuwOR|QQpp@cB&7vV8TQXEx@8lJ_8a_Rj2YCPO5J;}arfDbQt`~j)vc^qmwSL3s z{VBLwd;Z+#v+9DA!yoqp=0L=Em92ZDzjXh!aiWc;M!NJILE@>U5}*0WFfT~#MhAfC zueJHU%ovmq%)*?Wyb3Gu2$8V9*Z5o}kA*yb5clU5XONFS16w(?XmVhWV?)GW@qIiA z?Ihq{WOu{%)?izf^SLx{PTBi-?<3}tDedO_4#}AG9aAL-?OTVAoG$drVH}12SN}rn zskXR9thhvRsYibB<1kL2n_f^>mS^gQ*0Mm!=TF)POsgn_d!^TzF7;yY z-!>it%N|9|2ec>fEGCe$y)8@RJR{@HysOJ8TIfUnV002O6p^+-cYekw&akiMn?>VHrn6Fmk99Y_kfc(tmOb`@H1PdW<;OZZjbH+Kw!t5s zG5v(eN}hO6kOTf(9CF{&G%HS@0u0ZrzCWQ%R=8ijqw%!gsC7VZ%sd!~5n1-!T~_;t z|ez%_Kw)4ql_uUe>w$<$`ianO?=j=27qy`-*2JpZ`X2@p-JM z75`%@%3_}N1TL%OiyP%Uet+J=Ptl;H%|#Pfl#plU#IT0@@|$Ns9KL80qNqa-r8KLi^)3eQHdIAG8M!{ug# zAy(1K-*@L+2fnTc@oRKPD0`Fh4D_{Xu@nabaklDNMeWl!S`i!qHTBtlu7d&q2_rrg zs>^rL!g4%535R!8Wc3j`9VK=0%BBLMhrUjfNB2cZ0kiU0?gfqdGTj+`b%T;F|Q9;{(IolXC0gzxVBy+AhuAfdK6c zf~S-k8LLGJK@UgjK9G1E%+`O6AIfB;X(z5Q6DD_40Wa%4;$M|$4qax#zXriNyx#uh zzhoN`t9gA}f_~NY(k#ApJ18}nVelVo@r(vpzIr=LZqJ*E2gUJY>0i$`D&==_ABfZ% zD)wnM-&?v_AW%a>2ewBtuhQx1h`2L6xrqA%V!f!hia6`eQvnW$1I8_fZv0fi4w8!~cG|k9FfR zg43Vt%kZ+C?-NoYVw+4Az8{w+-e=9@VK@^|!`SRMA7`x*tez zJ-&G-x+y}h=;cQpzSC1K&~xLaPI;YS0WyRE`p^%5Jy2@47~(<=7;;LR@h9vh-Q6nl z2sY}QkVVBHTX4bD!EZ-BnO=)t+!AV}^3Q2~RRhTm;{%hY7*iWD&+GoQvzb#~aYh6G zm!Kd%W4Y`{b)bWhj73lU>3bWH!xahBOsrg3CXeO!Um6(;Bmh%9LdpaUe4YV;JDO`N zvX-it`L`sMKo)mExiLB+-={4Z^PRS8DhBNZ6$4+%=5m~`zFN&4eX&^%lVFL1M?C zXM|nzmTHLmXDUtUaHwJyc(`f8yfq?YkyQAA_8J`E{b$IHOX<@6 zQKUvHSt#BRw@&3CI}ncFJxkzk#d#)BuOuRk5}ONr#^$B0Pxm6W_nJs1KsyTn&GN?-l0Qv=MmZMRoYOcn$*O2|G7U0}g<5bb{z=&w0BBj=s)rs{}z_s&@ zv`H6JO7Al_MIFeMK=l*X!z4Zb(rOL27BqgV&&=MVF)7IBom+X;ZC|3liw~;c?%C8N z{j#$cQP9WqTYKj&LbA$wV9cSxlftSxY#xWf?R8GpFFd7ND6tnGqlsiwJhgk<_YG`D z^|q$v%#*09sZnmLX>AOIMd7yd$R10HRcmR(?4_=^x$@)Kt;4#tU68;s)Iv>wLEE`W zH}*I~S1}#4n?65K_7xKH_nhW!&swE?!mV4iX1{vF@5FFlrX3rKd`xg;>N;lDJ)x!& z7_FXOY%PT*C|&Le(e0QKD*2Gh!f?+(-yvZ>mS*+XB^S*&XwN|tcR)_2iIBv1{Cy$l zB%ijN59;w|)Pz@Y2rWbQIy$pU81)likZmFSd*~*KxXT~tbwP5yefH7?XW~Ap2L`2hT-& zj?{7bo^uN*@*);>VE^H#|3#P;isgHgO>_Y;6zD2kqM7jq@fK=#q%7c=qTAN7m!fG53XJA)ENy z;JB3+xu?VZEnniC!(TF~=4QjWO^UPemnmKhlNQ(V{Cp{VK1`}04g@J_?s@6|j? zA~mp^s9J1V&h6`p&6GamqbxnBdXb*XOdeqQqJ$5Ev#U3YW`|B0MhD z8oH15mr+N?pjoCP%XtRpq9+kG$A+8|T!I7m03K_reZBl-PD0;hyp!I;4V>yE@AXW+1QZp>u0ZgNW zAyT1CvR;b=jc$?D16=;^${C1`vaAkTV0Jo}WjJ3lj_f&;#q8TjU=ZO&Nc1kal#@fj z0+}S}_~MU1UgqDXKUd^RBUGyw6$@@iyHF6)WpK}Hk3*m5IY!9q$S?}ByWc2Z{sie` zARStXua!In-L8xfZpkFdTk}#x+FNpp?bY|?)LnQ5SejNwO!H|wYLkaEv+vl*?WMVd(kNz016qiC()Ia6eArkzV z^Y$;xV7dOia6z9N+ai)$87pMg#xpDJ=SzNSfHNmQ;!sZHBq# zq=@_(mfmrJbAd+bw}M5D5Q0mpd`u~x=_@jD7#p804T>*X}TPhQ=m&$LfU`=F1U zk8mR_ju+m{%uz#U$aobY(Q4<_Q9(LfR4$)JHZF~+7HZ^=MKccyz3~(gl$fdmM!4t3 zQK4aN0LK%AMfjQ?j8V`g+7V%IU2)^Z(3DOXlzAbRJ{=dTmmSYCygRya8uO&8M!NW+ zv`pr9y`iR6CH?G`O%Ynm3C~m)Tm8}uM12O^OSjt4=0CjJoB3cp%noYB=>E?7IqiKB z-LNqne9~&6rN)*!1nl3pzbOcf8$8wG>#ltPB8YT&OLip!u!DUt15GA3_uR?H`rEJu zq5ek172DFAPAy*#H2Nt1WQ;Ws=_+b2=>1zroAr7YgRpE=enZLj9wn6Xi- z&b4Y|o|#39*~-Z*R20D8S5XF0oc{><~}K@msB$*WLuQ;}gCSs98hm4u8~L&An{?OLHg?6VxUWsoi9r z3>mwd&_eNtaB2_xz$1b(d(b9li&yi)h|}{|-Je-EgUynYjMs`<1)F;ZIb~@%lkqK8 zHxSVpHB@Bz7hI%}$P4(8=Mw)S0AS7VvrKBbNBPY2Lb$UAqZuG0%dGKMJ}YpTk^{ry z2LWOzgvd|7bw`SlHI?9C3h`FYKYN3mELd4<-p+53|8+JU^)X$IofZY!vEgt;@(ysK z{|0&?Fb?r^dtRfwpY$~})I+0loCdDJ<_J}9dBo-gdqj->5sT!%UOsX}dbsc@1Z*&6 z<6#ATuKLs^2b9&xFpl+xo@eciuF5yPfDiiPPOk4wx2HeO0bpP{GM;cQ;`t(mH9ruF zlKpP2`etQDH`K&+$`M3A7k!SD$@!~Sg-5Q1=c-o4g@^8=G@3IxW8*{`1 zd)02!eic@^%KS5jDt)WTos^ac3f+(ji_Rh|);CqlnS*2EKm7x9o}`#j)x|6}r!yy1`Le}6WwPHu07 z4yy30?-|}ffB7yvQ|0Vyw^V@%_oPrB;LO!9neu*A*<`;lyrB24oCTdQn zUeI;2behr#dUQl!&7?J_n4{CSQP@P$OfS(kc)-<@fBHG@Vr|}tp9oFR6~_U-=vA2U z%cB#RH~hE-SBsOQ2j-x{xClK>h{-J6Pkym3;6Xf9#jwnj`M-7*JCDF6V$7+qt(nOu zB&-|j;_T2hgfjmoe$7Ygg4yEp660#KSr18spnDKK7EsqGy8hcZSo{%DZe*$AFweaYHf~%{ z9r_9XEAR;nh;$Q{?a#f?sIGRNFGpU2zp8E)4#6By*j^B}my0T}z7Xi1apu7k_5wA& z^xbW#RAqE@x0)?xlqsEz>kNx@U)hZ5jii%Z1^Hcc91MjNE}hCBZ+1_63{)IT?gEUA zcWL#6rLbdTj!2oV$B(}H+EG0nK1R>OQN-#5bgl0j(XGd-hJJ32Xo|12C^F^ophLej z&TAAOBP|rL9aLE6^_dIudQsdEsTb!t>8a=WM6-dd9Na0?vBH@o(8P}kAL{g&<{Vrs zOHVvwg*oUu?CWFU;j^2GfR;wIlcbngjFclPNJvgt?m+aK*h7)Ls62~fQf_~d{Qvb@I-ha4JG@m`$$|`l5#mwPUS8k11kj{?#BW0i*JGW;{ zWvBOi<)BdnBo-rwG%D?jt!$EYX}bJ;ueN^vrWin>)-sPxJKMxJXuz-f>DAyiR#ZYo z!AD{ZB}QU18U>Y1jJd5|)M)Mb&VYYR5^7aqcwS4Yzc-6+K6)f&WGFr4`aZp7dC+L_ z`C+{RYa+hU0zMC#RiVUfsUt?fPpawyWU&W=Jya%cqwV;V4A7H@-J3skQ!cZU@0sw1 zocNX^mPdLOjY}Fd3pFGqPfyeY@GrmW^2-zq?k90V&bn^B`tg%Ve@_f3hF;YXdhNAO z0_Sj7G%aJ(8irOayZ_@x$`Y$;$3KUEz7f8M9~taJQizFNiWt@lHXlU>^8V2H9KOPn z#XpqY+|R>g>E+QK-z6_jdNht{CBA>hJya;f;llbDG4UkNf$rLi%$qC86f0)zOrTTDbhapwBp{}fDDxz_(9uM8W}g^-}s(6#&a z@2*x5qr+$I(WHdvfXO>JDXFArBn6BxnwGGg0)Ly-W8R8#g{h0YqTB) z4fCAVrImcA0#IUI3DWb(7CpW3xgYJieB&uz+;KO}qvK?rlw^xvoIkhC%q`okCWlJw zOFUtAkCL(KqbmGa=jGh?wD?SJ z*)QVeG8@zSn&k1o`)|KC`g8?pp8p-E;qt2@-Wi{fa$_-N&tSu!htYvQ6+bG6l?5^z zUanL9hW+w48KTxHGAYB3a1XEYuk4Ph!~Ka+TPq)~#%=yYhU>GlH7N(5QR)o|OBu%V za@QB4*}2tpWEPRQI51fbJ1`3J_b$v112L>lO@59^e(j+F=J7!wv>A+l=W^tF&$&Xe zX*Hbb!{gh_vmZF$OED7muRhrScF|2}f;=N~#zc7^Gh3OI@0kGBH<2fNySimk=iBk~ zgQcJWkOm(5ZECG&R!yWu8`F>dj?aGV*~l^tRWSA2h*iUmctUTMlmBK9Iy!%De+5n; zw9>RqHr$@No+%+Jj^_Ol^)BdJc!Y~Weuz{lw^g+^;a_7Lkb4*FwqYfW;Np$HAWB!9 zut{Odl}*26C1a)P8MJP&;{Gg6*%S+%aowRuS6JnE!Wm$72<6 zm$rP^G~^n7hAcKyz>*nuGCZd6kzLZ#LM~HugX3MYd98V(jGO>IiiDHxMOOvvobeoP z4Fi(Zc0$?pt}aJsVQWeDqqpomiV^Ao2B@wR-bJqQwUOC{%v)%vHEl_a%VGP{_2&`9vF?)`r~D(n!T`0~Yn z$JgtU`FYJD2}tz1TItih$<7CvzuphH4Q`O_5wJW0vR#D)9$w^pZ`z7Y%Vgmo2uEt{ zQXOhpl|1E;LpJst!0fTNtNKJ`2f9~*D9e5w+>24-Q5ee};D^YSw%=~=3F@C1?@MfK z>o#LnsH^OKp^71$>DQ!5*4$i;NMCC_7#s4}_HDZA%!2*1fykr&FxX#ek4!!AL~>h2 zE&q-djPxPS=jPdy_AGT%B190po%k^Bg;5_>5$?BVdTP9LFHjvD{ z8n?3z5zk*~nHeRW32$0Op@JR_P9=A12F&y%gw&cEh~5zN7egDNgh3Mj`h20?b}Hz> z*(0Pk;xI0J?|ss^zY`*Hv!VH*~z~ zy=U)5>JfTSd??G@kD1i71PpeCt)4S=JY{83Z73@-;Qo+}(UeJF7a{-pf|L)T&=Dc; ztza3^c0Z_B&vL5*?zy~(9lYdLs~l&BG?K%+E^`d?g+MjBAF2LCG2fT@pWm??awg1{ zW5c=tFu!0K7o>u%)s%+|cEj(MICvNz$ja3`{n~DTf)%7b~xp_O*W-s6IpqK>F5Z60fp6B@U5{K?iOXUnBv4;|pnU|*QznoB+l80)?7As&2ubOo~H01BdY#VEUe`9(??$?e^`y|hhL z_6Hdy5m?()WGbWXWNnv({#?>{d~bUSowRhdRGU2rJq(gAZ!+m|oA?SuMb*6cn|}85 zota?Pz7tSF!!PJs#+Fz39Q>Qr!-h0@Gg5Lu(D&G9)2woq4>|bGlA^_%D;^(!s$-$q zNwm7_AQNv=G}q3{XV*Pza+Ns0oSDE3} zFsop&HCpH&?^ykHJeC{e=|9XT5F#${1Xkt+TbL7>@W~$1vvpAj;m;%EL_8F;m5aqC z*jm1~WP#5kErJ2Si_sb@#2}eyh^(QHfN)@KcF$V=T$@=eLEsv4W z>TDNp!|CiI^!LGYrdsvjQq*Cgd>Hz??T?TD{@zvLf363^A3XhSkT`H$`BNUDm9f5I_q&d1czRq2-jM64OQVPSIXEM|b`_Y9%1`c%2 z{JCVrgwx#VhklkwkE^^(CRENDZ9}@5_e-^lC=uy?vRgY&!YC4g%EEzi0F~4NN!e_& zdtWfOkJ{U_FglM_j>D|FzNJ-~!WJT&L#>k-NsGNEEy)!h$pDm)ycgiR&oQiKTrwtx z;PPdg$6K;@_!`ykSb;FnA{=2;)j~jCdRiZQeKOTSko3kS$f#eS@=K3zjCjTBl)_k< zecI!?PrG4P?sXyVUC0UDz!1A|%s!ID`CHg-U5#PbaU9$uS{BvsZcm?ow+gsLoKU-^673*oTtm42mT zINkIWmN)DLsm+r5A6TG|uNg#0uM3o;N7@TRZ)k@OwZ1uI0_^Z-gH`)Q$k*g|iTj&U zV(}l?=ZUwLRWU5HSu1UP3XdE?tUS$uJ0QmhL64ale#ec}tpxI76EEnrj8Asy_g#m; z9DqUirsr;ZR$z>oL!OwddQP=2jZlA-9OfOX_2FgFdsbu;FX zaG=EH7s0A#xUr^Lw*FpxqtlKqz)Y`0P>68rI@_#Nhq5|W#AP}j5q$0xIS^W<*E!ak zoX_3m!m4ux|LK&WD(%7VPZ?JwE@5BC=iyy5g8oJc7RdFtaa4lSmws+0Q|fWVg*RFw zk&rn+nj}M=eB56@#4Ddk>C=|${+bdNd|Z&d?!zU*kr^IQ!zsR@d&NjDMM6mPpnpvw z;n7DQF}w>WUz^L}p6z*haAA_u;pg~nmc`7WxP0?@x^_0sN~WlgA5<<%rn_I+NCLjt zP-X1PNje6b)8bpE^?r2pa}So22Zouq-JTW7pqVP5!_o~C@cf+Sd>8Hg;=}DLDZ8BE zApnHbMO0h{xac_+Nlk}*cBINpSrRIk+Ow?4EZgJvx#A@b_X-u7X+mx-qIq_%uk$m8 zy9K3QMc_B{x9%sRfE)Ior#Sc83G}VO)yucxCwWB`mvkiieM?3F4wTp7*ex@V`OV7fq5(C)xvY8fh@I;iN4yAByp(x=2go!?aO96+$eIjGe zqfUPGw&#q`CF9Be<7RD)wYsQJEXjUmIQCan^&N}PN1`FMU(Xqc1tGwZV^To=>Xh%{ z4}B>dO-Qb)Ez~jTAT`sqek6{%Ah-aRHRK$vYbD^;lPH4=@CI1{=ylGVY4ro`}90=&8dBJ;M$(uEqa#LbEAHL z2MS`D1x@3l59wR(E(atEXaY0ChZ~t3)wnG;>n~|E*{)YbftJiI$Sg_A;ZCi2G@^^w zCQ*q$Cgm7fwq_{No<=(L;gd|S^vc4Su*TILS`WbraPleU8D(_lYGKeM9(CW}?$ z;e0UnSOZq}GO{3coBD|nA$~p$eZo!QAkCcld{bXHF8dk546&OzV&;gDXdo7*4~WgM z_CE4wpRTS1RM^c$?(1i~HzYnLGG>aaX0LYop}k@?*Jg3`+ju}H3w7HM|JMlh7^hwS z9*@5;wZ?fsWHQV;oDQe#){)7mHpWJsOSyE z?Wu+8S#HYaX+X3<{=+xvIy*N)LrU)|Mbm;>C$ee2zou zkFxp<{rB207$luHtoBlL}tN4?u1JH^N)Tmp>+=jB4xw<0WH0xKyckh zna@7oyeyMow=@M9LknN8gUoQD&$3a%p&f9v_}xk?o`)ObACGrPQE zm_5tO=~N!ks4t!N(r`pLj75SusW&F6WWkTQnHMQ>(=TBrG;^qAv!QbX?1hq(>c4yn zvL2U6WF1g^Y>luuoKx)f&x?{x@(oyy7fdvHeY93I;|deJY=@weI2p7Taf}T!N9BC? zq%1sFKF(2c2XyKRp+c^!)-W$3KQ(misdXdyj(j@XDX$Q<1R$@Ubi_w@i#v|t1_)^J1;iT(j2|DbNT2m;nD*Depr49u1qN=?4>5gIpAG4 zn|g3zQ>R0|NABNmT~l2jjx26d+e*j!s)G$rlREMlf){2EKLgeNb!j%&P)B%m$;xKxu%WPn66I#(>=Y~a1tQx>96Sbw!r6fDaC>t3OQ; zb@f&Mr010I<;~n9Qn?f&+;o7A-qS~Hw&reLui3Kt{A*lhT&%tT&iT6~rrz~(|OWiFX0nOZ_4>Ov+-F34t zw8(h{_WJ$4=vYoPz`WQA|@uHW~x0=Y<5ASfj! zzPNzZ#`UDsr~J1%&T0onhtxTrdhtSqDiXJ4%bX<_vCj8br)0-d-L;0U@?3ZlB2qiG zBs#Y5ddkpFqlpz8usrK$H$WQbK9L<=zfl;r(rVFtaBFp#Vz7iaX>hw63&(c?L-7^+ zpz3nvIFVfC0`}PyLus)*;#rCBLB@XqiCve25-I?(a|>Q^Jel(S(m^s}%uv(9SKYk+ z#>wh$#$pK1J3p_Cr_A^<=ql7KwE9UWrNaHOXoNUK+S&K_XiX8vWS~J;WDhsI4UF-E zYG*9)=9$!Jnex~7NV8VXE@rwD0ZJ}|%=V{4ey=KrWkti}t%wfH5UqC6WT&XUQTq$9;nv!ZhIGL$6YrV{NFs+xRRU5L$~qx#UH{iu?! zA5)KXJq`SI%%p8TZJI0yjp4clUeqj9WU``oDdtu?c*3kX?@D?mU`oc<>n#D)*JYrd zkOLyY4{lhb5<1P5pKy%w{9ziRjCZT9coqul*Lmv9xrq4o%x}1f`-61v7G9b6aZl zArzPhbh;u|(=S}e$4Exh=MNdwoYRYIA( zHS3#`PkJzhl8^VBIW}8)6tLDAN03{yr3xpuG*eg=MnAV>jTe?CNtv% zJJqigi|LW8a8NyoZ~$Y0QJ2*C`SJ=c*RWqIVTzsZ^A@hoo8@d6xn5VLUFV z`pDnD3meX2F228gnUL1BKgBhVH~UWYCIFaz0gLq2n4&D#I(v}!%9kM?lW{H|gV%!J zjS4k05!|(X=k|N59a;|_YrcH~YXeaA{f0IqeF|3?FTV_r3_emy(=tLXi9BEwxavn9 zy5KCk7ZtCO5_DL;@NZ9ID?HcYi9O+cJ|D2?4v}O6-SDF#_!u)l*Sprkd^*l)o9L#^ zFH;Q}@3)USLtY7bC>BX3{yTH;F~?rJf!k9db?ScXjd&#fx|_1}R^S$}A);d#p10h{ z>N2tRg3knh##cP!m%hwM{Kd2Ty{j8Nz6Z*pbN&XDizBfEC zkNE9e%fu&&2wM51X7`mhGObMLSkN4CcfUjxjY<8)l}8a&)h%iWUPDTQ4f(1unuO?P zwZl&A=2Q|{>-oe}NuaufubXyUI0QbQKsgKScOAaS!PPAPUGZbYCjZ~xAOFp^h_TAO z<23>P%mb#s(t?0&-za>q7vzD@Hs0|N;T4Zh3VHtZOx-m&z`!>MJnoXQWj#MjFsyfT zD(25kpbP~>NVOQ(Uj3DyNlrH-EuIW1u*&tq=%A2yiwj0BticnThtfgikuFobtZNd5 zWmtf8(04HoDh%8^8Z-qYX0}LH^GAKsPSBjW9*eH1aR{z;I8Z`PaPqA8tGM?+)yWh zdt*Ud^)at$zY%<}ZpVgaXF0I5N3_bkdvtG`6V+!0) z3+K}d?>l1dcMZ~xr=7HIJ!wRS+`IV#jZ9onqMB`_Tf}oPaqQ7QC_YSj0N4d%YgZ0U z48J3}6a|Ug5E;AO`w6#|K6s`=apn;v&!Me?8k z-jGIj+tFjH&siW}@SYN;$I+OyfY&rE6!>rdmpou`YHQ`i^V=qNT_Siq{!FF=9W{6< zt2B$>f!Ws#xP~@dd6j0LO$i0QwpOPTDpJpPof_S^BX6EZb+>lW*J)IJA~rz)DPk$J zL!v&u3Jd)TGRpz{EIPF{AJp}UNwFL2&Rrt=iZ>J1B#u)g+TE5{*(|V(}q#^QST|`mNV50A5{QuyAwn2 zV!kPUd=)iGZHSNxioSU6^gbhYzUnD3Nb5&X)2!qYbShfoaLW|+c z9`@oqJQdfG@mHd$%yKt6<`O2BX%3<>-H%NbkCRpsonTnjN(E#+o#% zP9mYu$t79R(~^%a7*?G{)2+N)zW|A4w|Qvc$3@;BNo)?Caw?Nn80?Pr|HW-$u(>7R zBA7dpV<<9KV_I?pvL=F1A0P!Ttq**qd_HqWXm5;nCde#b*?lgs0jh;~XfZR8955sN zbsEf>@kYz&hy2W4ZAi40p|HJPr;nNIZ_k%9Kz_CI(=^fPo_HnnoCvINHn&lk8L)~h zvxj#@Gy>lrA0CS!EjK<4c^3?c1m25v0n&L;#B73tI;HRO3nHLE${!(Zn>IJ^tKPHZ z{m4Ck4Z`XI@f9WQPGTJkuFsmAdSgJf^@23%UYFK0&NF*Hkp<@iPx_9)_iD|rg8KV=`B%p!Q&}$O^N7K3gGyVR5oY|OT z!<>d1i<(o8wYO8whdGatIVR_`a>${w+{3+ zPq=Qk*Xz2Thx_C47^$=?Fsj|Oy%p$p;9(r556CIhiln-eyus-Qzao+*j~5C#esE{? z#I66cn-)epp*2~>w78wk)ElM>sI1!)#>C9_CvZph23o@qDpWH|3b0*dtWsr zyqcT$IgObe!s^H}X&0uM6V{DwWew(1fX&@5Yiy72T+xkjP(*Rg^5 z(5v|p#l_Sf-*d$esDY2N?nur_Lu8w{*@F4mi0|X4FCk6A@865RpI3ZO4p3u1JLtkc zng6D2X3kRM8aU3T$PU!`79WnMQc-8(EONPD$f%%II82MR3&9kyOZka>_n|rR%fmAF zk~t}fCjN#GA2631!dh&DxUXTOh5ZZ@9z5niXm|wrze4`1{)M3%tt@p{(SrjSP}h)A zX?k^?{Ku}!7&mFCDmO;9TEr(h`&p2bZEs?HX#`5c?Bjo-aviQZ+t>WC#Pw{r1ItEP z-K`NHl=S!djd#5Z0A)_=W-0a9D`9Lu?hL(JbBz+2$vIO4__J$@YhDv}f;2RO#(fIhxK{u{(B`CSu-rGJoS*c$C|j1HbnC)j1=F z`16;U>p?&TOsYt=KFl6(e2%HuDj<8)$d%sseB+Tw2I19g-;7~5I<^}4>=pLEk_Uc` z)7*^?7;#6VG-AOe-31Ph__G|`j~p9Y!`Xedkb1L<*&5Bn;hwyjik$k}eq1!Q4KC+A zhOYyI8u!ZCG}|o*gjR(7HD>8f`R7t+KlBetOBn?tf%G&sZ6sk z>uL!OFs+PEzXiJt)-?XR3tztTaPZ#B-6AUd2Fy%;ISKIe^LHSpKW(jqhn;`=k0;n` zQn9!J8ha>p=j{{bq|D*WOgwOmCNISDx%a9CvF2ydu32)Amz=&NXqi)zXIWo1*64k0 z1&=y>XGRkE?FGB5-}%9c-YJ~%Um#%V>Q2SrZQ%`gaHY`$4b5lxR~z2`_?O{t_j7Ml zwb3@Z@GIhK z|Khv3;T|##$$IOab+X9)blnnk_%>z{e$^)4p>TEp*nLFz4hG+QSqi#qi_92Ft=5wl z=^a>Ipk-s(J|$NMW%wEucvnjtu;pv<**k9E|0M0R?)M4tiInUe6cy8&8w~Cxf$3@Q z3g;|9MO{ZlwcIm(8_#3Y9v-RhIT5(sD(15y)!;S+a2pU+Zjr|-=;V|nDUg=ax8|$l&2EvBEUaMo zui#@aji&HM>?p81r^6avk9LA9-dV>0Wht^Q8?}e8Bsz(~5S(CS412ei3fci{w+_=r zXj}KvI~jsynGe`C)REc<$UHtLd`Sm<$a096GPs3S0SI+9m6Ltn`VkX~1a9=9THp1| zvb%Fq&qEk7V~q5SFI@x$i+@;4g1qT(jZ!f^alvn)qRq~yEwfp{z)pbSCMpD1VE|l~ z5iP-ruYTr^z;sgUB#PDR<;by#&`rWz9Go0XD$y|oN?tAY@)MAsZl7OoWDtAD^mf%6 z!IcBZRc#+7_^ulMN>73tybJtn8uYet_}=G5DWXm6+tB1m<1!nvb-zFr0IPP82i5xh z<>`pt$fJ|8<;kU-E=Ccd_fYM1{ZdxeAkMmMDP7(cX0xVnKZ`DHu<0WUh@o-xG~ri# ze^0+cL1$;DP6@%c-&D4sZ;L5N!n7jpwLNCqT(l-x9f_5N(l(AD)Eb*7zR3HSU99XF zm!?`@PccmHiyFKAR*b-9ty{yN-(Tf!xyhTE`(m2Ux=tzvL0sK1Eko{nH|@MbQr`No z@!@PeXJ*V6=s=EbfLGxV_eC(lGm*k&CJdkGLtx(rr&~b}m@>0NBS?aujEFi;e3xUv zZPxC#eCB%df^e?UuT^Y0Uw2k>vI9bgq>qoMl|ZI!Z9~|t9RidHBdKeSeaR z)+rVHq>nsx6dU$>!_p^MS-F7UKgRZ*1x*>b01LJx3S?V2eH_61(e$(Aoz`Y*owuItEtw(-BqeuLRa zWKQVV8{|aJhj;Ka7!XW%ZeLytWySdpnb>!B-6R&6SQ*s69qE~cV8n?qLtPB7XSyo z{t7V>9^&`Kk0@KV9Cc{zWMle@U-i8>yD~d{V>=cF(H=#v8@Dt)rkke;lAbUug5eq$ zPv!W|Q8$jrT=t(QRzgzq_OIn*fpO5#n_W84*goi2^;B|3W%An!$@>ReLjUdD9;FAe zbrc#CFM%>s7J6iq^IEl{F{=ij@VFh)Ha)OYZ%pS%#w>&cRYz}_Q*bi%VnOJKO~RM@ zyhtCh1=5V2%wU?|Mn1&WWET@-&|?WaYJWQs7QeGVo9_z<@J$fk2{!0}x}pCC)UTW0 zl%^YZG;Uwbft%5`Z+GB>B|meIOsiiD;(xe9;chy~z{M8OVRo~8YS&C5ft@sJzP{cq z)t%_-XRp`lzdh1vI5kz%c{m>j)-!!QK5>+R2V=!#XL?o&FU!#t0M5~mRqWXzkCS<| z(IU?5kedD{<~~lcL*?3#Z=9JW*BOAu-_bV974;_>*|*+OwXwq9aZF*X@F-9)Bm3hk z2K6s&-acHR@9dMn!JfP>lN~%6Wh&rda0#P*dHHTz&gC`s%i!SWd^@l=0q=IK17pvZ~vCJ@)qQMsCXSHX{NcOHC4wuY8t;?vI@hF zyZsDUY~Erb_OYzKsR6Fwi+>;Fs#>K=Oo!;G#vc;45V3J;uAeFdYorflzg`ZuBjNQ%2sge5Ocf?mccmF4myoOWhf-DlGz7* zx#39*7h%^YsS?A!8t~L|yD?fYd-+SkVZV>~he$kU{N_Xnj$EU2S744FxH^#bMac;j zsm*?Q90>asu+7A`^eSgQ&hvN2H#ZU0d8UaVvP>4NRa$+=@0N?_~h0UQQlIgh3c z3=@7Wyq*+`xWPE=1dzNh@NP}(?pYz2C`+=i9Qx*4wR$*($$dOjPL^a3(m)2EqQ;wS zKAUaluO1q^6fnvrNFI2j$r9qOTUYJbggSWA5A&gI(ndh-Qz$J%>o3Pq`iqOyEN9He z5|m^-^)=f+UcIE(>pGD`%4GQ*_A>S>QgUDrR;KZ?}SR0dP zun>%aGH7*T?Dt526SL?RzlmO31UVdK0#p>9u!D?9vZZ7O{R*MSSoHLA1YR(xLG*v; zjr;J0aFh=?iHN}_+560&df$6c%urEL+;i&-NtEBrREq0fT*eF{b+7;R--9clLLU=c z%GQt2Z*^vubiMz>OO;g%EqzzN3XIWiaxTq5zNmQYsvP%`^?H8-q)EbM&0V?&v@eWD zfXg1s{OF@6BcMA+&>WE%Rm08KHQBux_H;KVnK3vca&R^M*=^u9X;IYh(Hol+O+L%X z+9nD1YFK)9r(ev=Q8hy)%~QFf_x0EalQ8`kdc1S-`?vQWg;=ULmjp$^N)$*a)XVG`C(6y4Ykqq@K542_fJeR|tB&AV;xR!TQQ-ZeZg z`aWIe%e7flOnD1)rmci9Z<&3@>bxj=aO`KWc97Au?ZjfsqV2RBTZ0lHQ}$6_x?nA+ z(!Pe}@rOD$)P5K?;H{SGM+Og(wYw4`sIFE9xfkp;H=by7$a(H!k$yhUZ*=R7eW?7n z+Rk!1JL3&8p^V2Y9R(96+>OJuI`LkMvzO(gzv)2sBq@p4G9PeiQ$g~e3h*+K!Y^1X zIU4*Of`{vs+dSI1l(vSbcV}OXxP-fWp=SHZ!-#Xkq{tbxu}^=57$G3tn606h|E}p6)P7X_cRcz4*B$jYBqpdlmgHD(4N-WZ5<4@tsq^)FRK(N# zJ_XZ|s^!i3sC`}wdnq_6)m(rYlHW{G1r0%_=VcKy@1&{Bf9*eaT?LuZk|jORAgz$i zWF|47$t6r%Wd*4WC$(#=9exf#URzG3x`)uH*W#DPU?ib=8v6|4IO`ULCvjHJ34Z^0 zVgyruG6hcV`^82!7-N511;`YNJI5GUv>dwlhYo&ne=)HndQ>5vpSRAWJCtwi_)hG( zx@)}^45EgEIT-Vs4Z&Ad(%4W7w^RMFg((B(fY6EgJwK`42fZ>)@XQmH+SMa& zIkLmFSPIkM7@{=oz0t zC-3FC=U%mIza&e2luU6GHvp%EG+FarR%pr_2R%dbAskoNN7bo`j~*I7vltGQFy)`* z((;t!x^sE47kuz@_c$UA#%oxS!F4768Ug1t@r1#`{y-*_lf^lD@IXp@AuXBPf6;0B zMHO@@2Ty=#f;8j6dmSRS>_sc781;e-1QP>CeX^)%MPjdJk@1 zmMY`-6t}#!e9Mo&Jj2Qrtkq?(1fxJN?+IPxG7D)0Vp`Xf-+>8zyARD<~!+9zK0f9F`)K zmNN>_{aOh?%0>wfr*iWwxp@Q0JqO`)xIQ1{e+5#oRD%x|$gQgz5B%~QvXTQ0{Nr*Y zFe;#z;o(32ICDG3ItmaD#Fm?^Yj~&0pM>Ze!+BeUVGg^0wH=EJ1Ta z%XXeP@FtvYungpX#4q-TKLJV{wLq8BUdP0hSpAM{%6oZwrHeP>_>vKu*E9gN zJ5aSMJgECuBtl#k1YV6;!E6f>^2d?p!LsZk!}dx0^+5E1Y0>!8Qo*~#ZG{zk)6u4O zjA#Jk=W2e10&V;ZL-P zRN#i6VPg-z$u2v14pJapX8VV&+|@b9JbTFr(f?KevS_zFNutNk;5g;u_ILKT+NWDz z zF5>}fF+SzvaLMcV0U`DYv+DT42BZCoFhzp8Eq67 zR5c>)k&juU{Vzc4U&dJPErXO3yz=6?ll#wnCp(yC1?`QaLboS<6SBH%JEW+0W!dnHO{YO^54{T6!uNXuiE+->$Y9mOYcRf21 zA^N!K8n+4`g-OJV292vnPCM%7RrCGR=GXSXbgdbgq4`Km)WU@_eqcl9CYuip$gPN) z?9~M~|M#UvL;x)2UF7vq=H<0ksrhIF9}(>nxNf+tr#6_vuUw3Kr(88!*0&M*!kIN~ zV3+%hZ)Uf_Q>qbrXA}L(($6mG*aPwCCnJxxx230I!oE>xXWn=j#tyBow2U-|8wXW% zojeUClk$9^r8_tvD|+L_PuNqr>GO;k$DS?n=1o+i>|X9oo)-q2=ix*_`*vfErSvO{ znqZ=`WG%7yNrHF(d$7ANXN>~c2feiB>bdhs2Kd^6=MCo;d*~W)@{v*yWBJB%2mg`~ zEB~GYkM~PRCx(z9o+OkZwxd@uMmjczc)LlY1@?V8A~GMAFy&TDd9&$6NN>HGGeiJz z=LnMbj5gdCGG=v%<4nRIxqm|MCN9*q zh#L}v5LvV^<2Ac>Us;kH_=6kttj_=I6`7-%{Ub5n6RF6H;N{MzGm)wvYVr41o2aCg z+N7xK6a+H@>Mrs%){g8>KDJdW(4eDskGYIKJwe_CENK^<0dL4%U~Ow(j~1fsphNI#rKLj$~`=UAVBip2_bb3L)F)r}?!Z z4OdfK6|qQ1y9@5myc;0mSIMM!UPdX8DE!s3ax0$&wmG50&VQ2VgKut-#?Y5}Ef1xF zKh3N8Xuj_wPvEDfZHxV6%|d~wC6~`$O=BTo&^8Vk8Z`v$q(6Ae!q5s;feRyhYiBhTY7daSXz76`6Mj)pd1UNipnNJJOV&0Az!mn<`-Pfw2mA zt;FQqAHQwO3s-lt1{hL; zk!j}LFD^l;@VDy4z2yg)t{nggy?oh>*r*_)z}uX zI4G~TLTyv=7M#@h5vSz~ylugl=(^4$tepZUwAx^`*u=in#Qe=0r@~K^yiUfh4QwoX z`*DXVc}rfER&hsim*>j#P4*OtL8V#m#z?xF>!DqhuZvjU>eM=!qac|A;=g=R0g$M5 zPa60KO|@us-#b(CGnAH3iYR{@K~cY#s#wb1NliCS7q@Ss^!BmeXvbauM9Nzk*^o-zZW*}1Le zZ`g6RnEF|_e%K3gWRU$Z7H%F;&^F3c@UXf4hOLL5leBR3B)Nqti}4N=Vcx z8BEj|4)wL;)0%r4az!W`IsQF5Eb;fArEGqTEP*KRK;GlG>y1dHcLZP~L*BJItHONP z03Z|$1kDJDhj-h2!x00-U4ysAi$0&4biXxrOboh7?lvOE{Zzvalaeb;3VL?Myh(=) zDU;Q7R?dc~P86mo0lll{tLm2E*o_!n#H_jTa9f%ObaK`>$s~<7w)vicEj2|O!fgz5 zz09lynD$#yIzO_(9l~GV&n})^ENRf*-gWZ)MNUOxRZ?*?(^e2-x&D|ozmb~BDaD{r zu+q5%XF$+5x148=?@9DB$6J*7dJ%2at^~3;Ujd9g$=5OopvQ@^tCuH!4-|2j5*dCeU9|5ewZC_c~n_SA|L$ko5@*6l`@HXn2&i< zp5tG7<1P67bC8>~JKhF)e+yy|$EVl2v)-~!A8&fb`Gc#oiY{>CxTYprn^}Fwql!%t z*swYsll5~IsK@Eg5l`=-@)`e>91wL{kre^J7t z!U&WDdOFwJy|1RKD&pxzI&9<4Ajb*CzX~J04NCg{O28<@##0wAmO_fZKm@E)Wha)TEE3b0hXLc{P z8dC_GJRW{jng^Xv+1{$r_zZS=J1Q)(1qC#PwlT610wLFoGTdVh16&EtVy}@al{P;D++oEL7G5@ zb0}x1_QAtPH!&UN=Z)m#`HJFx;?>oCUh*>)SfzZJQd+CZXn5U@@r_nKg^A_YJZbp0 z_4k#>C){0;FUdbw27VNvs<_jVBbSHGRYkS=*9Gp#hM)r${#b5qj&8j#>*v_@c@LN{ z*c2DP3b1FmNsukg@wrBzXZkA8lQjG#(iA{WgjgLEfpWuyHy6%X^G9jB*|kBeEhZsa z;gqw*gH}wk55Rt?{=3Rn`^5ZCw3ZsGcm_Of#5nOQ>Bh+qy`gz2SVicy@{iCW{uiQ` zLKuM$I+1IY2);c~s<8Ic4XQA0*P4IZQyEu53CEksx3CiBhGymzz4x&ThPOoEdNAI6 zY9kMC6_)@5wO-QvJ@llCEoSzs?-`EFWy&cYd|6iX^(zgy_zp#3J(DN4_`Y62R%I!y z=8ssuoA%3gDKn`CZshmx1C#!?5_b_eGBUq94}dZosOBXX+$c)7cKw}k_@IPD@%Xnt zf3?oar{BKxVxSy>f4A!gty8`jy67VR=Zxj*+c%rjQ=Vx~UEmcPUIFH+mY%qy(#t!u zd@&e_5+Tu~w=kAVKcuG)4sW@s5k1+=-5aLv%BICux{y_*=u;+MTL%enTAAksi3DVP z9XH_D$LCelE4!yz7a4wVMZDNa5U>At#a6MZEQZZMjXFhV6Q`9unw(eHc`~WzF6l|3 zano?$2q@9=yvSW&pX`O-8~O|tj}b;*b_%zd%L&l#ow+`1epzXywSS%0^^ z$P9YH?e*!G7m1-n#-iU%{lnKoBo>tQxv?S%A2GSd)e;Q;ObY>OiLa}6U8gR{IvO)5 za6L`01s_zX!)~Lvw8;i9OQ(Kz-R9k(KlJM#AIS23U^jb{6l{BIb->*o`w)GcfBfUZ z)2a0(MoX>vZy2f`I~1+ef>D<>M z{D`GzN9hyW8HlNn_!%>B`s66mg_I9eYQG7)IfjF!yjQ?R3xWyVa(Pp*&DD>_)<>Ez z+F}B4@!U*a(6&U7l}R#1OR{1)-=4!g@(+9PNxBqq6Z%s*<@VitE>I97V};-daHcs} zZU0}&x}W)5ZFy@5JQUw?S5-xlzJ~C^c+&ixTn5*@9xIEQD!?|u8Rkxdy14FFid8ec zsZU>Nz>@L-snsnKAmbI`lo=S#5u5?gvs;x-=z~XXKzS2g&jP<@)Cz32XKL-(9fwk@ zQiK{7r-10L$hT}nN(fu5%tbR1=8aK~P#UueyM5^|z`q0h1m7HXt$t=Fi+GDOM52K+ zE5&}0j_>2!p`ZzkOB%E9tO9`Ne(@AcYhv>5Mv$CTIyl8>qfU<2y+aJcd=Me)54=Qp z`iDxC=BRI7=Cvm}L3ZpWX%7*)Kw+q}iE@&ZE`N)u6GlyM`OnO>h2vJURE>W0Kjn@; z)SWQ<$Hk<&yBk20Jz2;kmmVDLVERFCvg-N!#w&@iND0(We#jiZ*|ZzZyG_ni0!8fe zR8iK1>2~`|vuQrc=fqHR!;S8Mpf-`H(WX)M<=i5Dr}pWi`=Ds#Id1d+%m}7KO-jf=Ak0ht6ZV{aG$<_dpzLda z&owOm$;AdaQ`KO}FT7T6-Wh-ppQaOY8xDY^4bRJ$019^V3(@mkug^7Es0qg{<%sR8JZDWA zx8eP?J3EbnzquK;8>x^%7{@UuNN;%fnT97ZO#c1xED2G`dZoMW^Z6g^L5yHUxFoOP zq(nx-UIs<|Aw2(UZ!S@^BmNhZsPP^w%jC|AfmA7K4@MgOg2SKtx}}-<>x!HP-2ThJ zkmYwr6ecoC+W(WE^7qhOXYeXEL#)xwRMi`zfC$tBTaHNuV}lm78sSvyY}`5V83_+M z_AOgeW35&YnTK~TXg2;q(V>ju--%3_F)lPcCdT2v_;E~^)_3~t>-Rh# z4dtak^$xQfV%l73=(5-A=;KpwT+<%3>GFS9m>Ra=hb23xe14&&vZqp!I}~B;LPEtikiKeX41irB~*)wr`od^I@IMWrYD1q zHv$5cMUa5e4B?#g+?-ghX&9M~cSixXik*D(ra~2X_riCiZ6eOk?MI65Y{3(Z!tC3} zyP?Egv90VUo+Q*49pY-sFFM(nyDbhK^SIp^i&t9JTSR*c{XrYAARL@yE#KC#!H6~o z?Fa80d4OqD^>gdUy|hI%Gs^{s3l3W=8!m4)rLiBGg`W;QVk#qI)kYWtp>pB7mfp4r7|(6X=j>9$_iS@(pB6{ z;{4Bd`vzT#7g)5Fqxw=96a9SEaEvhd@f*5v{EO5IZT(<|2kaxhlx4kX>8)HY zfnAfvm;@OuWK9s>&}_FNL2s0P zv!z578jasIN_dT>bClThTX_VxdxVSu&GhEr{p8G`PD#nucUz0FhV4P1)Buw+mjv=k_WKI9@6>3>pW&ZMFE3x~KQ;RBPpgm- zLc|>=Gw4*$kD%Aaz9++zRt6HTTCp=pk)C!bo1GWCGDezpoW1;2%>1eOOsVp?qn-8L zPblSV{cd74&__X5Qzm~!Wih?+oMq~pZ+wOijT{VOIbKqtxgL9ZiHLKIWW}=+Ys9oJ ze-T)Y+pC!mL7NfFXB}lREO>uq+}e?{7$t_BIz0j1>bywpb{OsPfsJeUPb7uZQD{w1Xg*$D+Yg(=+L3T9jVQJJSTu)j%AT0RQ@L1x@4 zo$n#ZK5flfb`oAmx#wKJDzJf~3J6QVr}v?>k6Af%Zd3UNgDZsr*{k*hWlRL0Kutd@o>VQ?a`gJ^5RqxynoiIW;m~e^=Z5?YOui{oQ5u*LY@L|YP47Ec|Ci5`uU3ZOd~Y(ywB+Q30EoT zlfX5KpFb0_F+)i7VWn}|8)#FgyXpWA7_8ueG-Jaq5aXkDz-<}qv80Y#SS0rT(C*}g z|1!0BpSGI$CTk6ry6cLsWi*udUgFW6*mV_ec@^!tF)I=HfYExo)-G338-EQxypes- zk>lGldpSV2>5)!rDMq!e<3gJVi?%$0VW1^8ws^QJVaR5yd!a_n=Z-5GV;b$tC%pB< zyu?1JvM=r{T3+#S?95zRY|<{PR5F#_R6q$oHL^2twTxMyk^4C)VQ)J!Oem*9OQqOM z;dx^mjYa&Ig`I#!I9c?@swxFC;ClCeVb!WTCD0`7ZoiJ4=jC;-3L2Wv!rPd7zxw^9@rvD;gsYW|Xn z$WZ&#`@tOBt+r6@pZPz4{T8dZiCL52-+;mHHsAnk!}}5Ihd^)cVC4oFXa~di(5pr- zE)3If-$MUT8k7!Q(k@_sD&y#{%IkWFGx&kScw>Ld#Ld3*_!#h z85QG{Ti)me%L~ygduHI6Thk#;v=72=!1{K+uWV!ukA@N_cCE1+s^NZ@w~0{-_iztn zOW$#+u)%u#)`jGY(xKL_Rp5y)Ke2CG9=Rs?Cwi+FDc&!baKlSIMC{n3MpX+K@ScX} zIl-AcFMaFmjR^DDh?Z@rS_T%>fYEMD(4}#VfPW4+r6>38{*Cj|_~mk7xjn)!t+jWx zIeD?oW~1eznL%7ZX>Dq8bauE^$6|?zp9SwLXM)>yERWt`(wP9s%x7$q8~0t}HEK;hUn~dp9yi?yEzWJfQRIEM#qrg`}hA~@F*nF5X zxawt*P1hd0dM0dzo%qZd+iwg(d8&qdPk@^X$#yM{WF>K~VK$JR+M~nLJa@ z5z3lB-+mcWqY8@2YoNf{{okBQl<5A)o|&7Z28+2vABZvh+LP??cT0lcDL!ob?Mhol zOE5|&)i~MDlrtY(&!2=*W-Mx90yXztgItokVaho7D)yuMyH?-E!^cd?2w_@`B&VVs z!>kFAMf`Ik^GA~Ah1*H|SnaNNnOR$oVadjw{XUFX*#RVk%)O)pP;?qFElokBBg2NY zz$R&46XC)9st{A>_9owgi8HV@^hJK{2)k03Tu6j;An*NIiTi0X4(*r=X9j;q>4h!s?jM%esg0{F}}xW`k~&}`^vtFHy8#QS?8anQx2hQDb~7ho#BP^uOtp8 zZhC_=mjsO}|Hv-&w&AMBgO%CAf?V48CwVzGrnC`G{Vt`^2!x-*vfsXowKz}NLCoC^ zKNg)Nfg8H>BLFAQ_q@((=c+U9q)3oU#2QI`Vo)yfSA{XPu_9CilSx8;nxsLSx~CxX z|Lm4|RIT1!W52|Nk?w1HbwkThAcSmoSJ@rK8`=`A41P}u#y2^Kq)mWjgFVts47H>R z8&0_Wi12y`@ldv%rL|?P0*oVDrsqhOzbmiV6^VE9E1HsSze&&jZa>a~;diN(-O^MF zHF>8NoSj3l$Fa@3{}%p89oyi1s#uY~T+#B%W}72Xh~>(cFa^f3sI5lrZW@UG2QsU} z!fEvH$@M8Ux6TcwOtW!mOVU`BtW2PUgz~S+(j8dcVPx0Y0Gy9~2W0l7%WpX%DC8SR zF~rM?$LQ%Hpz-&|a*v(2DK(YuRT)Q|5?Q3~X9~0XtI*d+SAb?JL2)5%zNXgxQHA-W zkDB=u656zAWJ~i>FI0f$qkPUC$rA4V@as+u9>Eu+;65o_M$0*a9Y{R$XER+N78HA2 zH@%Hb9kqPS6njo8Y=YTltr13kTBl5QxUBq>f3O-gQA0W;05+gta zW!_IJ6YoC^^9jXny`9O05%a$Mq*nDdHWyc82oQw@nDrsUzN)~z5ap1TrN;`D z*^0j47|BIFg3v`oA-t$`(_`a}HDv!vel!i6nP$DE*F6>MvNBpXu)##l(4W&J$XXl2 zl#0`3eq8EAPWGRQY%kUa;k#OE(2O-wxXNJJDI#;2KBvxR=GoT2;Ye(tLdo?mf(DGV zzS|_4G9Y{flwi$JONUk0oaWs!R!;o(9V20lTeMAo=jbgo~=4 zNJr9he69H832w|WyBVmk!;QpthT03;JKJg})?Y0f9h*;qo@>vA%b8UoA<8j}TS!^`(&lh&-o1SHDz<{@ zY`!%V*E3DUYy;^4=|yOgs0`er4X$o-@rs#tL8-IkutRD1239qwcfp}r&+EQ5@aVlk zsdhms9P0+KUUsucG30G>U#-FOavJ99%T2p&IXhj~PWrlvO%8p&7zcraN~RaqW_oK+ zUy;BQ=QsZX%|(_j>C|Pl%F$UI8UEi3Kvs4p(5?I)>%Lh7=y_zTnV}@$;#m`JInS*V ztJ2Kp!FEq-!#ehaSU6GXs!A|7!+RoK#%N&rO3PoDx+-7bBJ`aluTh$$^R0Kfs&!M8 z;V>(d+D2x3X9nPW8594Wnu;j-q$r&`C*q%{!j>7-F5VxQ0VevceFei$2`wIX*CkT@ z+5Ywa&(cZ_+4?K|bCu8Oy`Vd>T(;41w)*NLJtN+WiAq+ zuboC?w^^#+e7dHO@FXWwR>LwCMe&S)m1`)O9lzC3&^pP6xMNizm~IrUB_XS@k=lRh=aiHHc-W`Pc2vVHX$dCWH@z{yIIiXL4be~(W} zhAd-HBV6Wr`nuqC(KsKQ_`S9<+RWGQ7+yNs@bk$QH>Y*9_y&R@b45(Sn%^aDji%Lk z^v0or$K0p0gFX4__TuHs3hvH>stEUY@JsCEZ;l4u#x;u*`Kv+~$q8sPtAMzsXNZib z$h^OL^JB;)`Pb7_V^dA-rXPvdNR;kd0g;7N8s7zt# zt}Y@IE`pU1QK_Q_@2ylhaSrAUBlBM}xkY4LrD5ppnG`|AFAH(BkfNjInL zzZ%WKA_nFZQX0u^NRL>(H4ZG%ljwvv7Z4c4Xk(O#jLd~g+%ad|%wwS!1%xxw{FmJA z^T`;s9Xw@-stN$&S!0hUj#f>s7qUx{tc{DzJ5dTX;~T*6JNi2=zjJMbs6e{Haj>c} z_iQh*#;a-W&L@}5tDo>_Yzu=Z>bl>{wrVkNA>c~`?JY^Rg23$kW88?)kF?|WrQfpt z6vx4f?{myj$HNf=S91XMISo0ZGgb5G;Fp_xH1*2PIv{|!v2m@*`M#?W0r#Bs%-6+G zQaYGPd9-hK=-^q5t^l$8;_`{$T2gY;ma;Yc-Qossj~A7Rjw2qCcQN<9s#~PS`1E5x z{CC~uSH2d3igNwEf(C5q^@5-~PZ{P;V*6}_r}U9!j0gnOTaS0rD|uEEI1p=4PdSER zL7@d_L1iymCCDobm1C$kCN_O_!IMb6N@FV6Fp!4kn-4O0?3OF zxyvHD<09B3Di;{roBqZnyp?YsB3|~3yQOoQ>wuBMDj^y2*y`@icWFl?c#x0)%^*sZDDF_>8Qd&h9p|FQmN$@tr&OhlKV|K|B$?nGyM-LINQ8Tdj{-=^gpsgxMJt1ryqqd)T`o!+Fv%RBPjgj8wdKIimiCp4 z9|$|UVUZ8ysg|3$ymt+T93@1I3fww(LWyqdr7JrW4!IH9KnM+l4#`jxHD5dOWX;0x zc;oAx?~1VLnhNyy*tucWh^#3Zb;?yzFWZg?21)(g%HUUXT#_kdr?fR!@@(m&CrW7VQeueO9W{^BO=RGuR<n)L6JVz*qqU} zLOnne7t87v3WL)8zyWL?%t*Wk+-5rd^j#E6hv5dx^7q8V%Sr6SjN z4hG#B8vOt^vqyPcAorOuFnS}%I4L#vurr2w{h!1`!IGdjn{J-*cMyzayb?x>R}4(3 z5u4_^NuaFm$f3Va$#5&HJ_37?Lw4~|X6m=?EV1ZHC}-k>hbZsvie-j1Xr&J$zYe4a zfcqBzjJ))UGEj4=`=vZ2tJ5JK!B7}K31-wcGnl{hJmWngTO6R{J^LmP(baUjt4|30vD;&xnOQxF72uxr_bXfs>t-L_4*9 z{M~>B6%546J{%(EtfW;SgZWRZk(rV3I082z19%HZJv1L595Ny!C-nhd-YKJh%H08}SA|TsHQzuaJ*vM^ zCx|cgl})zbHB7$QKu|h3VFFAbzP3Er;^?*Zq=IE6Jp*oNvAlY?w7Px}dnJvWnml?2 zxhQi^OUFBG51rg^Am*H*Q1lJc9-)=P>L-bCUEf!CMh^JlUitQ)(t|js7rWCNYc4k# zsGg}?{EL*eLt1n0mWIoc_0#`FP2c&s-rjU0sQ-O${>-gT8@(Ir(}Z(&ocozJfV2H9 z(j2Q@loQz$iZeKnc(JOrx~BEr7WOv^#l`ex>9EcD@eC!VwU*%Cu8PI?TYZC~Izk(P zTefS!@ri1aY3%nYZ-_y#GlSQ@Q#JH~>!!p)C|GfcYk;zgtarn7SGUj*W`EOEe~2o}OSjw0bQt&Di+qMAX^OZ_>^a^HA}p0zJRpf@*b+K>@DlBNT5xyvkLw(co^!GF~VEzqYTTKZr%FF|Aeq;bT+; z!u_Q0@VcmQk*93b{GBL(($eB81O9D#w-3-0izMXh1R?cQ&iNm6Vg@0H^Oxe^2jMG{ z?42mw-#ztSI;dPjGU`^?vlsB}IO(=ag>)I^ziaz=k7-8;#Wp@j!J4?D_2iuK)q*Db z3Hzlr?;5YTEPM6()>^zl#?Jwp+|MY$FQdi87eBAI>k-C>q=^hAj0)>V6FBznU8 zNGzb&E5{2~n2wZ`_P}`cT#7eE%Y5BnK{HjQfVyjIrI3tE535q%Ce;I+$>Dx+Ok#A3 z{c86B%2ri)N&B8fg#nA9x=Gqd)Q0ybp)oR6#W$IWTZYedb-y(F3GLpf%hSh``p0&N z43%E(B!se@C$>vlJ;W3*$B5V*?ioMdiy%I%mxnui^rTLF5e6wdBG9@!ur@y`OqEmaS*}!nkdQNhJ=J)-MuUjHk{CR4m zhG_^X3O{lFgXmxTM@EBq?;|}y5SEc}BNnl|@>VMMsxsGO2J-*Wbl&f5zV91Ph&^g= zYHw=9*1qi$vmq%`YQ#=ZqgAD~TeV5ls#P%}R!EGZR&A<@ng~Nv0`QW@M)fG7&)o!1EyTeq%eaZ zaC3AzdBzbMOXUr`LGqnOi>No?P3%*4J=pGyQx_H*mCy^}j=0264OY3Vls0_o{{t8_ z3Fh9J^*(=Fw^mE-v*Db}V?gy#H!Zb6D!~T|`+L9W(Y1Pk1$eMkgf8|nBaD+^>L?I6 zhTZM;D4^j2=?R2L9R%hSv^@Q3Gb@f(?eY*3x%(<6DA4fA;h!c@Q0xymHmjVwdP}w5 zAY&Ew8v)f`Ry8JKS+v{l`vk>!*ec>n<+m3*9Ns6nQRXo9Ut#^JGo>0F3HHmk8)x^2 z)o%~Xv@ht?t7&FzE7`iJUOkk^W=x=uiJMDryduJ`7N4Oleg<@UBK6g_%%#eKLqg{#EJBxd+`5|1zM ztStEnAU*sydG_g{mVG#}OEz!&#~bXKNCiKOB4?$- zfHVW4rrJD#N)pV8aqG*uf>5F8(J{%c&3YG@Xurz({zsg)Gn-?|F`FwXPt?R&V&~!N z+7r>ZZtOTm>Y;qhkH`zx2v`#B!w2a}7VSKpZ2FZu-L*S!HFv)Wd5^b9bhH3IKkA-!xmU2%Jx^l@bb*>_VQoUmRq{anxq3%Z|kMJ0vQIn zm;S;1cpp*Z0TV}wAF0CL9*DvrqwX7lG4o?X2@(~ka?2ToXVkn^IRx;@bX&%x)fx+9^noND7QNJ%?5I^ak3A3!hxQ6CA7HDj8Sc{%k zj3A25rNpVa%j{DK50>R=JT8wSkG5!M2H$12E?)h;Cow!^0}3yodQ4~XDG;VwBWD;ftY`Bjx1;*D)7UaX zLay)>G9Gpy{4_#UI*vww5jkv7sY@f&CSUpQYwgQxfAqQjdwCqqSX6Di=S6#KD!ZU1 zW`0{VY&vyste1(6&%UAkw2qgfmkX$FzLNc$um|nRVlsMu@AE_s(U^$61X>GNn<%)< zv8+FEJmyll2YE|nA7Li;Qs5ia%;kOIKpUSI)r>!Rt*feLGa77;^zSia8i)1`1l4DA z@u%CB4x9-v^0orvxT>$8B4KZXlk9$<#w%&Mz@IoTMJl!AV;DmO?f3XN(cCo#$y^dN z8_Uu*Kab!*#^ICjI)`h0!S@cLM1$^h@#KE?V8RHoTzw|`%~^)(g*29!0X$+93Tc6Y zUKnULCR4nR9>>5oNgok4RrK$1CeNQ}eYx#F#~)HipU3{khG9I0lN}LX$|2CX{mAS{ zI*`u)|qbhLuZJwUh>*ses?HsgI0*kMhXtLYb-uao!=7Ahf%8#JfhbPx!ei5y84`S_#Bir zW5k#vFtBq0O=Heabn~v+c$9L7ATNF8aQn*#zK5GkzW7b`W8v_}p&U7|a#3#xbTha_ zg7Jphw1He4M*@TBc)KjUZ{HqYn8J`oq#x&tO^K`R0l^a0XmJy--^H&DjSCX5JnX7R z^R;tbRodA!DNp|9+^^`v@I|KjCLp87XXJaZaR;4Mmspt9moHSJL8Fu&MH?k=Ejicc zxrzfy{1{BjOIU2h@0g%aar}3mVG9ibr$(u8vC>;#6_|9`V>v<;fX3uEWr3w@a-1f# zfhA$GT1`%~VH-bwpg2WC5@@F9*MGbeUgau%S1J6ZvYn60QkDbGjLIXd=YQ!!%ZB7FmYYf2Mmt)BmXrNQqhFucb2cLMB{FAfcv-_ti=B%`AZyV_TE3wO z?>OOu@n792WY)`ljM z9tk<$;Puab3BD}G&Zhkn3~Q1az{f^4q-Q*@|Gug}%|fY7+^f4~fKMDy6sj`N5PsH9 zKm5wN;!#Zt3DFM;uaPDy_fAfSyJ>n!iFXuDO0$X&a^;B+W5WFWOr(^2nY%fn<2ppC zXnVy3>87)L@T9PVgTZEB8hcJhvsc#HN{`=((1}l_Q#fT7)S{&5HTIZYg`D(+hk(;r znl?)>tBsr)s%t|@S2S8bR<)VsYB$K6@-$PbdFvTbEPlj*oAC*}v(x-y-)kTyF?Um- zq5D5IJZk)TjA9Kd(#f@Kd>^gt!p#iN?O!At1XUKst{*8$H_C^M>qlF)IRyQVd>GIQ zZ3`=ce`a5&|7pC(B^oSO(&oyj*hxb*v6PM1POo{|dNdXCM1vaTxy&f>n?LbVwOmFOj#H3eXjFutS$%6>L+j1M zJHONtTA2IDOLJ2D)3UD_z1=kzsk$!5D6OWttyT@IyW9daJ)P?JUaE!>eMs)Y%>t-y*6l z>@6y-F-IRICuEOLO1H6?;Uk$$c@I@QupLv|KM26l|qDpE|5TF@01G95X7FLfASd}i7$(q;fK|jldT~Xq6 z_jj?e#q+rAUELFu@ClB+;@lj@&y|+W5v4@&&CT9jmb$sxXDJqWoD4u4@%M?s^q5wD zF(W;aR%%l!wb)l9JL@068M=Ig+{u&~uFH6*?{DFd4nBb^extq{td4BWPKB~PcO|@W zuO6{*Y`i_jHK4$*$$l5yTm3I}W>G>xEp^YBI2VXS4nz@95Rzt5-7(^8jV5 z6iu41(&6FMb+ci=&1#=B>jTgIL+nn=Mc=L2?(kl`WtEqT*veK9ka zKeACq=B0QX*G(89M%a;k11g&Lu$XbUU{1XwqyX zPxjg^Pv|G)yM=fA$Az43i?G&=>WNN#H8JSDCu=Yz#8~FTEJ=mw`&E{HWkWqKj;AzN zgNe@`@|6C+nK_d=U&xJ)14UYSc0~&7@mgb!*AncM%3Swta<#7W?%bF|jVYYP%6f^= zfl;wHR_Ssrt`R@{(1RhD;rE<_q20nhuqZbd1K4W~uOdt>?TJO&t6RMBIavA2pfH!C z<=lIfyP)oDvoQ%t*AcEgyo6cWm;xQdidIs-&INVPBJWNNdF&dETJ-`E8AW?fZJJ}Za5Pu66s;Z zYRyzdpyU#Iq27r=mD=)uh6Q(SMiqOZUAkdyEH!53sDOh|nQg!O;r=7e0r+cDR#VKe zyfc?=YHo8c2Jx0Vjb`Z#cG8Y|`dyA|uv^8n=kn{9T2Eq3s2`>dn>m8rFQCH{%L^)g-mHY@2C{O~ zjWivtJ1rj2+z=Ua7`fevL5M|;=Cy_7AjI3K*4%=6*7T-FB7PcKP9eXV(99?K%EQs8 zLF8dL(GR-x*Uk5gSRii&uzDATK*o#dHOxIVRq7s-QT9#Xkojhut7IJ+_gj@}&|F7P z;k;8RR9a_j^EJYwR|oiX`5@}iGksMsbaz|}v&)u=%W7gd@4cb`YSFaKLzghY_z;OP zN_MXW^2<0%F?C!|mdm?;{8K*}$uE|Uw&FYNfCr|mIc(9<-&a2^3U%AZ!->F(l~g4+ zkvOQiE$AzIUftb#)C~36jYbG^L&!nO;J&BewKNn}q;180Fu$~PrqX&8s_-sDW{9oW z#L8zZZSj-3(BU_>^)F(nS93+QXPB^>Hl|AKITA@OZLyzk8XuxSf$qjWSV3r6wF)9~ zvF_|!_%I+}vD?`o*p6gmwR}9V!{zTD7`(;q$(-ybN!lA(F%2B47(TlGEtg zkd=k)Yx4`WL3!PDc_ovfmEU}AO@(U$29RyLyH;2Cgoh-BTBO-n?k`0(e4^Var3f^D zcmS2IWG}=rgrC;lBL1P{R>l}h0p>o@jQH}un0p82RJHv!rDP?ux?7etR3PYV^+{dK z$%7A)G3zL)^-S*H|DNlP!b1v6?7`Vg4;a^Y^{-D2|DHEe>Vi>BN0w;akaV&6TC}k- z`Aq+6!1*#Z$@BI0hN*okHjC#Q;#gZxXMtGuZ$`Srxdc>J zoUuKSQ_Hvxfpd8OQQsC|kuxq3k7O;}kbbB7x{{k-!)H`U>1CHhyf5rwi8O+~s~wLz%He4-r&Ap(@>`{15u z8%BN7w|p<_)((X~anMJN@$+TWjD7t+dgWT*PC`2_C`7yR1f){ps!1)HoJGY3)5yCl zzx5+=gOjm(ra_jkAV9ES67==fqXw|*_*B7{kIJ57IfIJ~9cD?&EG4x01Z8A-4S~hX zV1-NwS(fusMV^@#8x&j?uKE_KUV?pS==4Q#CSZ}CG&LZL=w~dUPsA{emNGj(eB9A+ zbA}^0MJ(nR7xq9s+MvZCTdX|`H|_+-8)vcvVVA$>Vhq}kx@Uj;Zf%Ui%~RUZCScrqKnHX5=mCi zQZrK*J-x}edB!m659A^;dSy{PYY>`A$SRgtH7}O)nuLi7zWe^tX+zHpvCqH%OYA^? z_%p||0dPBdq2RJcB+!yX%7y9iPY@eFK^RQhT)mPDCAB2;clR4zY|cIRZS;G|ugyp! zA%*{Owwlng4WG1ngf!ArbbMx1x3IDcmPKKAd2+fh0|stX%%+YTMak|%h8&;1Hjd;; zxJ%}LxwwXq7bPC*-3oe=^2H?!YH*-nyg2C;ecwE>T#OLmaZ6pnSbd}J2*dJ$FygDL zBYKi7_NqkC4uchsnKAF>V@;?O<=~$0Sk1*P=1)5pVVcu$AGl&5p1>?&C*j-HF zD6~4A15D6fOlVl1CEHyI!{+;ElE5xvTr)+^23uhW5<>P3c)q~lJ;4kS^P(%ATnN^P z*apjajvPL3CK;RclMB8DNGYY@RJuauRc}}cC~jfY&cTa?Zd5tkzxy5U@ZH>Zcogq{ z!~R(weDt-9j{J;&5yHS?wJ)@ePF{6qaaTA>W21rJUJD3~48Vr7!-K*<8q7pPl9y=K z$AM*@(hyYLTkFR=LxfyM3y34{0pO51e=hTHaGY)B?Q4=xb$ooWFBO5id#dHsn$1`P zJ!{;M4#jo`Ux1FMzLJ)Ff@vE#136PY$Zmo6$Hx1^t zE>&%Fr`LVeyv{&q|3)n=v5*ba`R7*ggMP82MXI@ut$)$KFx4jzR{%-&<+yAal;w0g z@%xqS`Dp~M*3a-0m2E+SY!_8>EVS%^JOo6{m%yU?cm9nrJ0>&Fhbw>7Ukz8VCKzX z75Xrh^slFZHzu1wi|*{9s)(1>PiM(o{4a38Y!Aa~tb5IxnbN45j9mb26B_n|ughe| z@((3HkHIyi4?g#|A4GKnwEHfKwMc@J5=Z(Lvf}FF&hEzwWV&s9Eiv{{KbHq))|N5H z*7mvrGo2HwK?SJ^*)>2$om!x_bIS-J5Czwd`7rWDLkg$rN5wJtY~XgfLC;Y2>Lu)G zB<6RlGk{SCoKaVO$Q(NWWmC>(lh(O;LZFDa?9AhaWBDAc`1O8#QFW5U`y)pl(WgP^ z$rpa!sT@uUKFhZ)JoR?k?yBZ1hZ-lzHAi1bmweXrRWtqYPuRyr%00)q`2e#wHJQRZ z3@P<-?tj0s7(8DgAkSP;YafC~JEkld)SLpUO4*ZvRQhhXrUQBvHEBacEimtwy1@>? z#CnFnUL3!!qsF4Pd<@9eF{}u2eEfFy@1ji1AfF z7Y7f$(v=98R~*1GDxI;V=T@1^eIBI4oVUvY@jvi3+zR9_2I!s_F7N2(5*3E1;2?gK zcGG)V3;l)>tZvvmH#?f3mMufbC9G4(U#*Q4my0IcmL+2d7~bi->ih=h1nqS%;O0F4 zqOkPv^V1AU1l*h`;EvjQF$&SicgR5q3Q{{%gVayTUpZh9aPaC1L)broHKGhVQ z27u6ei+#u+Euh`Mw`q(yD~ojqO0s^f|!?eD=Xx( zw~b?p)Ot+JlN(7*p^erSY@wuo-|7S<^lEiIE>|@yeO0nxQ9pkV`F@a8{Xm^ooq{df z-;~zlu#5X*GriW)JiZn*O;%o$OhyBXU;a#e^e?{X@(ptS5(V2myPf+l}J< zq)YiH^`9P-Bhp4go;A^!6bn9%x+$t1x}qE`O|Ckrq&jv##TaQWOB1GaySVgg2PFUm zCoD+`pLh)FYmTYQdMTxPwd~{2Bg>4cnnyRxfm`izO{DJ{`xZEvn0Ie*b0f<=MvC*b zR$@`s&DAr*&vsC19rM+e~Gv_YZ1alIJ42Gz5m)6n;qbRLJJqGAs5Lo-2Xm2w-9x?Iqi$&ui( zsJAophyW_5wz-y7)x{e(E@s2zU_`J{nF2`{b#qd)6)LCtK|W?9A5Av^up+dNRN9)A zsxT`~S3C1MvYBi2NDn*{VN)=;#vJ`O*8U%hQ_VNis*hKKQVrya_bxo;M=Gc}I3*_~ z7soBpMxE83#e1)1_XkDoy`SA7L z+5hWfbS3BE45(MArH41-E_|a>%5$zqD6A%!bs*SUFsA2w^Z-7}Qj}BO$`iKCJ}W;w7qS9olVGm8GZ%3Pd;7JZu3o7a0VcU?hYVlR5F zV`65U`0t+`j7iPllhors-);v4LL2H-mpaV0zfPD3N6Kxvl(yDVU=agEZU?-m>Vp!g zM(oSBwk#Jn^YzRR`BusN6jPlW%McW>GrR^w;cfBqExw+}tbvCIP&f8*!C*#K0M2Jn z_Zl)qrMq@!%)+)VdW*QO{Qk|;gQrgk3#Vh|`FR?pnHu@G08Yi_|M5^;;b~9`Pl1tg z$lIyxtSedTpn!8VsE#eCCr4xwk*{|G9`UQNA|Dr0S>Y$yKRh@esIK5MO_7iEeD)-u z8jhr4YjD{?c+Fpemc4frkl93I)&H&s@&>5IQ0H?ucMk*(YDU8wIdxu}6BwI~M6J6P zJk&0OjJ1q8N}MpcoJCF84Uu~paM<21FQ<%|Db*P;RH3~xst45OZVcSBAOok(mWTdsG3^FPG(AioIDK5 zFeu|WV~G*lx&t10@SynCWbzO-9x1%yZ|P0aDE#(qAgmg@Pt^*?j^te(MaRzbKeiNL zkPuk-`~yuA^_}E*J>aJ;VXjQ5W8D;xp`C7=cv@y&V&xxM1Nm$uEq+{e^5_)AcW~mP z{8qjfsiQuU8{(kC>U5jxHJHw7&; zjb9|8%q7_>-x=nb=`BaOO=?(s)-v9G-du-eSVUo)M;9%C@2g`tJbF#gUBmQ9lJ@6A z;ps*a?)E|#&|JZI01d$SdunpLrdu%JX5#hk^Bc|h72~q`a_5|W>yvZ=k(|&Q z`ht}@b*32>lw6{iPEQ_bYCad$Z}|`?kHI6m05233+%~R(e+7v>%XcHxir11}h5rk9 zc|m4`nR4`>0b*Os&}RdeAabGPqtkh$?4v9~RH3@a9S zN^e4rZ)Y!G{vllTgXQ0}R+)?sEo^{Qt{X<~>tEdV?F0>szSin-e+?cz78?FEGCdzB zTB4tWC4Q%(hULeWny=y6)6Crt@NyeaovA~S%dS5PvmeCiT`0#aoJUrH2~pdq#FdkV zdPW*46Tv`;(``8=nrp2I5w8Yzw|JIDNk<`Qgk>k= z-Nf6)B#8^zmiuEDZrY9kbkCrazX|_R zghs@U-^9j;6@em_@dVHryyBClti-+)Vy@$MB3C26zV)HX#hB(eS%=M1?0mCtXkXCV zroGBd(_-?2n3Wr1m}*#{PB>&dh^7#oxJaJRdCGw4k(f~7MDB_o_30J7us(;}VRv;S z5C>GIjtDCJ^7n%8Du4`N&~gf14Mfi+pM?Vr{9G+>HYe>Ei26@R-jRrK5#V^^V89dz z%MW$x%pR@t2y%!_mH!z-S|SIqDAI#^AJyj-UUYJ+;)kCF_!LTaJHQ(rt09AyBH4F) z*mwpg^{hM}{#ie^sY3o_e$6BB&N6J5JaUpBkA{744G^epO^FXUEVPDkYDUo=R^)Y` z$%PB$7ZYnLB0U%3aqsA^u+bZ+$i424F_IJ5uAE?*IX{s5_17v(>OSMCL_gVTA%y;u zc!0uN^;o^KTU1C{8pkY12blS~SBYp_kGjmOckTDhYgiL4+}@`6Q^TYF-km>vH{$=^ zumD1~EUjU3bN=_|UfK!xp)~kRh>OEfFMRUlZSvA&A@Ra}gUfk9wgx|f1^f-`RT<u+iv&PIpWq$AY=IR)E}g4AuY};*qwA z!}OFpr)9P~Svv6xz_i)v6r!(WS>^hW8XZ_kmFK`%zlz!Kg;xKh=*Q`<`92eLwV!cT z&%(yN?x#iEHJB=5>VTO-DN8KC?&jd$bv5< zyHnQDGpT z?+#IGa*gR3t}N2wY->*F`s4EB(MM<`H^i}*e`n20llz9~_GL}8<-2xyDx@@H76`3X zb|CnJ=DK14OY0J4zTgSbD^^%DG!9&sl{TBQv8t&lQVDE{fz7d3K0VDJ&yAnc2Suo( z^x@<0y3lL!zP^mS%Ohq{>+e3LnkyiL)9elPZ6UX=Y=V>dUh! z8g=9YP*eiGTv-9=@z@at`;Pc|VUaYTeVcCNIm6RHzhdO^%FdMqI5wmf)5rqmv-Qb% z>g8j3WQsR^Lp8fXFSL7w&Cw09l>uoTJDEx1l9 zP(=>7OWMSSU1Lez^Yz%yx9q zG@YV;9<6g|vTJ8uTqR#%mNp`JGP~x3M3d7-2P6le@WrBjB9)o5>4&ZU|Gw%wRna3J zRhoAZuqFB3yQ4Y^9+Q}!M<2m(H8E2+1Tv*z>h;h{E#jKflMyuc*&>H;!d@y|KU;K= zr?ROFS2Az$!XF2a8yz@n{>C7s67POQ%XCR`#J*kDgasT>=2PA~9dQDLRHS2RS|QV( zx>UuqtFYOy#g5(Pp#gci#hp8_L~=UAZ4m}K`CU8i8}~Yu+@i0CCzlu_r1f4MYr2!* zhVAkXOt%_!<*nS%e2~ts@CtduN)z`RoU(py%q#j{d#`!6q2v|+-M{Fp`Cn0VmA8~P zpXR5&uf5Nb)LNI&Jf*7@2WFwRA9UpA@DzxTKhK=zTXM>{t7qLBd%hZrx4gp^zEf z4c$`bHdTKV<+y9QKJY@pJ|vj|l9H=e;lw&9%T5f~2#+U-Yu%SqaQdiA z^#iuuY!>BBrDp^`ZL0ePk^Y3YUZ_U8LtgawOBtv;-tDd`WpgX<>?5qZ3L>2DmR)=r zt8!-Ue*oCLQ5M5xn+S&;2FZIB(gk)BKF7H)AGrIvO@bRSK;4Eq+k_HDP^1R4RcRxM zhb)v3(V_)BZpJF7q0gzO8ae90X`~RV zU5$4rTml}&o%>l4nh(xaF2`UC`_AB3TL}c|duAI-;+|u%e6~(XQ!Qj7lJVWA^Rzf7 z#3JN#F^mk(|2WQRZ}FFSK@jc{;~%$IdSaz6IsDz~^p8$lkOKEk{37tW*9%1s z0rph_&P3g}Kw71CBA#6XS~yO5TM(P!3J8ssyQHSUJ+|2Up>TnHBl%F6GLX`$e(5}G z4m$J5)k^;RL57my_CTSdz0hCV*PdK3IoOm3_4q>tJ-YcEMYrm*{Po7u8AUmTzJ2Qj z^K-f+gv5SHlS>ivg;9NUJDAS$_xq%(T(0#fF)emzs%2^CibKG1W>OHJ90^;^!O&th zx`Ze75p_NemOYqZK|f#z}k zGbqzyhVBvzeF8+UEt?M!b;IDp3>#Ao(L9TfIt&L#Ku93+2r8??gq&!)P3fscE-#on zDVQaHOM_CNNK@f{V)5;Q%DDU^8R}t`&G9oKmK3sP8bt?6B;?Bz9c*0~|YJ9jb;tiD$I_VMlRg6SdDV7g|m8J-*^SdzJ?lI6INW`_NG>|ic&P63DQZ$$kY z+zN>L8~=sKOmkI^6-L_RPAUZQBpn*RxZ%{?3)AM~seYwJQ-7=>nl-ss@$lG$eJcc-{@121hVMRL4K>o(1nQ`RNJH z^8xjm%Pc|;psVLa+%XZ|Dq!KChRQD~_;r)@1z~jW$ml}oXv8^RlG?YR%$bT5?9*cH zcA&a_$D(QwAic47kp20_++`SUdx#AXtuFii17$RpuTg%U$vziA?hcg>pw;Fq?eX!N zyB=Eb!)e3=1ygO+$YwyR6W|xjITwj4yJn+L2}+_T4$sDU{8HXcJm-y6LWbtly>_Sw zVRr37s`5+c6#OTR1G+`_#sa7jG@OQjnXS5w0cFW$QfZB){ui#s>YArty?*?zOv&Ea zq{vq93x9o7M*K#3?1;AaU}g+mkR?^LS7{FcGMAEBGu!1;VYx6#tN*4|AT6b-PyG^j zkcrOvNvpLhj~eW-TodrbZ<}U^t|$f~+F^NBCZ>C-K2crT)!noDZFQO-UyTt2mEAEj z+~?iMp|auT-HFN>XOxwlPs}D{C>NLNy-!IJlNK>XnKHWkeo-?i9`N=^j8>qMUw%(O zPrK7Ed99KqeornFD16hu%-{Zf4-^klq*sAbdj||VR}thV#$#2J5sUD-EEb;lak^j~ zm%bIpZD|EZ?DMl5Dz{*xj8*S(Z_Y z+Ixo+L+=&Zy@KqxCHbr3Fb-a0jrUoNrqG13j6w&jV_9hfo(KCY7+2#0KTU2?)Ug4SB4Dm@`n@FKU1Ib&GoD zfsjHHks#x>%q%vm;wxbvL}Gg!RU!WVwGG*{^=u58;Uz2#r$I>s-w^xdgIiP+@eY0Mhm44S z4T_6!UsGqAt3X*FaW@hnb%<{2C2eU|NSca)qUpOj1QrUt`y^4W(A^1!+5E1bf|5*q z_Yd+|86@CUO$t+&?L9U8JpH7I(=S`UE~vpDdwYRw-xKG^_G}U{1g1>-@(H6y2;S8y z`5gL{=t=NJVPn3Ziku8wq3{xp`RI?U*ESyIwo z^Z0aLKmudu{E%{sY>_Es!kPW8ZNE6Rss}j@;B;6ZXNc^|={PLw!)u&hsJZ?W%1aq= zSD<5#b-qRMtRr9KFH8?l1H9%_iaADtoq7VM0QiujuiwfB*$|$?k1vM)`|B>Ch=Pd% zVn-fW0pyqpt(5i$&b;xpm{Kb%yPyKArOMb?O5R>F*4z)dP3aJ{N(Lo_++s37XRNuA zk$k`~|A6ynUpAo>S%rX|3AS#{-9eC+Fjfo346TliAE(G;`-9a!c%m*AoK@kf3fttW z-cT9z@fmi&(e{|7RSYTI_}}LQ7HZ!Y+R#L$qAQun&cMPddvM;?BZ=g!iz&BkxqGG^ zmItDwo?&s|Pa8CX;vC>4+KM4tt6kOCSL;%;wSS99G`oJyksBevCwr;3@~`nhBit46 z+Y%j&$Uvya(4I&~lmJ2+LO?q23K{1nVNp-}rWPvSvtNemogk!gkSbqg0JXrGcnWX21?q!fo}XwNXC$K^qHWhDY~WP}w9kM)rnyQA}1 zn!(p*asIy&OjItO?Jnw`2&k)LD6j>D}j`Rfq5`Qx37{9k5HnX4H}H$ViGrMnxlU^tz5 z%PWOnnk+!eV5du(gQKg4D@NR<;ct+1)Ar)R1LVav;PB+HbbO<$z@_t0p^~*y1Rj4Q zgBy_P*d2=s?IQ&;M`aYk zo}u93DbAMpjP_+DF;#$SQr`G)NF)qpst$Pbd(ZX>e)YhCq9Hddum}_RmL{D^L(qNy zN$jI)TpL40A3P1@qfpFTZK7Ez9}uYkbaL@sx8c4x;opSud%sz)e0LMCZNGY@lfX4i zKJWgT7s=08e{1LLyshM&AoM^Q2vT!pTz8A;gw5LQyJ{ekT(u!iwOrD7F7zep`Xo)0 z%wXmtl!tXDd4D{@^|*w z4rU#}5EwJL36Z*u9I8#2Mz8V)7&j27e*adZOyDES|1y~Klh5hgHd96-m)7EEX>tU8 zSdGelwJ^$gBqqBHdo2g@Nval?xv8G2 zRNZ&25Y7AVj#4f0CHr6|`)obaz55j$%R5a6!`OA~mq1(yoJaxHdi`A%T>x}g6%eW$u0@23! z>+|_KJKl#KlcmA!gkySw`FoXdx!0%d)=5>o$n+hVYb>qBtl3ax90*xkj#}>O<%=e9 zayz@n?bKvxJRP{0Rh4qhyyQ6N^MZ^l^!26a$Vey!|CqV4dAOt1x$0eG)1`&PAEM4& z=~}SU5M_Mn{n0zU&jg(bDzf}L4Fa^ZyqFY#(|WpL(W)465yvZX#1*Xh%gHrS<3YWH zrZ!>H;c~v65&~$i1{z-ID_5&Yp}bluS-&4uy&uqD zt2B2qqz#p=AM)`d@2o88L3!b-=eY>UxnSK z?fb)zbyZSRhxHro8m$1&w5g_BA_Tu`}_Sh2jaXccsr7kGm z{dca}%1u7=QpwVWoW9@Oj1jh1ccwCf@7>f+viV&LR`390z{cZGk1T-&5c#TEu3Y>hVSXr{CM2GL5YYh6=c=UaT?|7s zbQDiy3M3yGEbWF<-(6|UK>sK++VuLRBeKY$pK@zgsTWo)34-k zJJS=3BND;H@fml$xohx-xb?V ztT3VakxmWfFfR4O9S!I!0J%(fp9@m}`lczGYhZRKq}y(QXn{ zn(0(afACe)eYSO>Uyo}gMZKpN-HG2wiQgd9OdhSVaw;Gc8Yrjm+~Yg zmSPz{1lB5Ey~w&)ey+aQ1&ND5h+ZY3&{@f<`f1mfiRk`GQ|bo`1Z@Yeq&y;J9DAuw zj%ux%QT62q6nqh`#PF=R^&b&t_&y`Sf zEWoK|YhjSQn_y&IdeX-@2r{pn3J0T^N!H!qpzvovSFq|jZhB( zhN(UkBYc*>KgskYYum2k!3a6J%58YuPUm*@uc=YS7N|m1XGnAD^L0`CP6H znIetbBbUZZ9ZZAPYOu(HeNZM7ZB8npY2*;Kbzv4=?p3fA@?N$zS)Qb?uIuE%IiQ&& zZD{pwCBh*AoVorIux4|6oR(>Uhno zvoM>#M>0K2-ay>zh-mv-L2%_GCC$1(0E4u;yj}&)VLe617Yn1`?|`ZPURKl{bM4jQ zTBY1rhd)atYB6LZAO>OL3fq$lhDc9lhEU%mUCnl$8%#*_xAMI$<}D2xo2o-C6$1(7 z2aOM91($gq8^hTUf@;Qmr}7g$72U^p)ODuXX1zGPGvK8;)*w}6=0|D9*e8BnB9=&y^Be?nlHZpkygh0IKc^6uxppf|G z@ubjsVWt`Ps!HP{%I$|fS&&?Gnuqg#oEAru{Ja8;th8KUBayPFjc@4(BW~8Zcp|gA zfzBFg_$JQ%zJx+0V*C`cx(JcjJa>ysIWu^rwoz*G`{ex1zw38(%GyK#-B<>RWdC8jp?Kev3*i-mN%Y0yk>O z&^9WtZRRN{-KG3e*y#mOs+^;sV2Q)gG?l{lfwO-+%OeybM%tHumO>zut1bwts86w*`(zQwkZV5M&9z^iB=LqQ};*ADkh7Lj~cKRn=#M ztTR=(Za^@B3}8qT6(~A69Ek1A9b)jxTZJ}eT(XIq^NPt5Py74Yxv@pP8)l=Q6`f6X zJT2uoI&;{L#LT4O>5IaUn!aG33F->EB4?f}Q9k;%kZSH22PbVhg_T6D3}>sV{sp|0 zXW$8K>8^47-3I1RILAMK3YT0S6O8+k)7pe)7JeH0Q-0t@dDWLY{7V9JnVjRC=5k)N zNlq83)kZd=OeKaTc_MT&fLUiw@eT1J5$m4faHT)lDbKR0|`@?pU1L1r)qAf0&3*`qc`eeRdXdlEuBalWaA$re%q> zmJ4XS!HXUHo+r?gn=waXt6*T}%8b9f61tWDsrRH+n0!g4X_cby0qCU?Ke5rT}_b7kz+rqjN|U$R47TKGuIGfroX+B zGYt?zac?r*FA~yCbe*FTRJ*>Ty;^GFEqvw8sqf7cF}6P!a9&3U-(b&MY}AbazmQ4$ zTq}l5G3MZf4<|CxmJ&>!j0mZLVyZ!@9>7Vf3_c+Px|a2vHuY7lcSjI(<3ggtTf(iJJeCE3MaOgZt8Gl)WIbb0LFx4Hscn<-lmA$J zeYRlDpX+8v317g}v$Hp1*st}=(=HNXN`1utN7H%#Q~CdY{5bZ^?wHwSc5E`T$3b*B z$13xf$3bM1Lzz*=IkFOUtm7Qx=o}+tWo2cBBcYH)M4`Ovdpvr94*X#9s zKJNF&jkkaPg&B3c|M27@tvO~BE<5~tBXQg}-IcZwx{nF=a*6vSn;wKfx%zQgZX__B za^~>g=5tL1#-=Ow;XFT{DP9)2;Hz^v5V`kbm(h6!B7f?x7=kRgEi4OtB7Df&z^3(= zX(scz?!p|Y=`A#_(Mfma065_2nPZjpSxHegPbyj1HS}y)dMBUnJ1_1a-98NF0YmC1 zeV`{619I1-zj6`EZQ)3HI;16F?!(ThSMa*cbyefPf6JwJ-O8nMTj{;!e;cx=bjswB zHFGc#>C5K+kF+n?N;`Q!P*~&`TmTA^QWn@6i1KuKm+jgGgu>h*()Z=t404+~qL+Xe z4LtfM2_}m(YbRn9LPc0X($xQu51f$``pIeC7f=FYTET{J$+G(aRbdsFd~$1np9faJ zwHNC5dL_>O!QNYEtcP@}oCfxK$e>=P!Z=*b-Bn#8z=k!!JI`>=d=umYyMfCiJg!jR z;I^fTS-hY^(;qz#&j%g z$kQfeU^?D?^wKq;k+dIRNi#}0Ke(D8#@8FBpzXWcwr-1HOaj_*7G4&&R_cdhLP0uy zF>4d7UPZtMZn7NaOS>_lm6sWd87A$9)7u0nK#4ys6Z&dXp-n;FN5|y*hmjv24!dHl zV}T4M&ORK7KDhEqTI4_;_Cx*$-5U0)l%OBD7Fb%-Z8geJGjApdF^G`&76#cnejU3Y z*ty87!#;h!L)4dknt8tt&kL&KmJ`I+RDFbJvnHuKZ{868UR2?hZ=0PCI~l^?RLwQk zg~s7vccT_+p5MaKeN9WrN-4Md!L$lsopd>2q?q>`8OyCP9FVjl$3>drp2|&B5T(WL>(I8eDN;T;w9r1?=hQa5q8dxj>Z4jr)WyqgygVg36w4f zQYP^W6d^bEP`URe0-TDE)8oiiu3Ziy9X%9-2@iVrBOIclee}$kYb{9MP`jSTxDcUO z)~g`kpcgu;raQ`_nv=z`WzvWM7thAfn^{RBTCEJUa1#- z{iwrxrc1E^hP1plQN7ajr>RN$eT&L5c9D04cFGZAvt&7eZl=FW9oI@~?^$4o1h9xs zyLqOCLwu+jOKyw?9u9)5C>cN8Qt00ohZpLDi4fkHsA9LgzHTP*#(klU-794^H(O^K zPEwR>@L*M0X!F-^R<8Q!6h^Oxph7breFF~#s?9Vy1Cz_i% z_v~A@22OM^>9uZsre!9Q`{%Hp5AsxxWm!UTm11IQoVSFxUIjx1IMcP zc`0h6w|-@ilzVtvcgl1E@^ zFS%khVzF6Nixm*}B@6hY^-@hz$3zHa?^NtAFXOus;4>nIikk?yar#w|)?*@Gx3CYj zJ}4kZ>k~y+hHO!Ycr9v=U4beO2R)`^RPeMkKL0$*&4ng9X-PvlFaHTq^y3wFVVw@v zOOMpgK;s)5h4Spu9^~6%oC9->qb13e=RC99a_rn~YPWP9M$|5ct1usGsNUF~=RF0X zMZ{gaG8qHp7E<;Vr@2*2ciGd18LqPZZ9jV*w zO1pOrEZv7ix#pJZ1c0ZQQsA=Wu1^_nuY>U8*C>0*lBtWX16QuE2{UvtTrAuMy+&RZvWVU!iIz{?*w~-S>y840`RT$CW~3Gqij0 zTWc@P3m%?mB|mrD$pFHi(6`aSl_q4{yb2(x(0`wW4r0nC7k+ch4S_t}3V4`L(uy`6 z``X{p!|@gvTg4D+a|?@-g>}oO*RxNQy-{ott{mmVy&KW8xX5+3rZ*zjSI9HRf8iyG zh`q zaU9sIILgIzi*vOthkQY%aE#df1q3nv)MOM~$}|f|(Oeef4*v65qhLAqV=Yv-j*-w$ zr%9{CRIXNP*_Onr4J|lT;| zA>iM6oN!H54niQXVs=Y>7+yynI)1jUFjsdOSoeneZq!c_WnPTM^L1pA$_k~2rXBo z4KgeZW#mKf5b$`RB$O!Ft?X9@rpx&jxi|FXV@g?b6R=*2uL0%T%lZ8ieWXL6?6d)s zL?|bB-PfVOJovj_fE=kuf46STG|V6L@7_w|uq&MX^K>VwlC?ptAP#OdMVsd_9&_Gz z@+rHZ*6@_S5REPI!;Y}YdsA20G6# zGk`Q-_V{iKDNG~!;-mL(Xy|!}Ut|Sr_#LAJoaL@)DFor|+VJK6j=|vUr`bPyuh*j( z4&v#a-t?aQGajvhsFkR3k}blo1Q%FUDht_D|7tIZ%wN1ws=h05h2!&rzQ(m+<%1z^ z0k<8L{eMxtnTonHgjWiee-`+@T%&iw!jcaEoo`?FPF8Fp*ED~(&`AojktL3&^r(}Y zWd(n41<5+Bezr^HKR1wgcIUUnvePz)woiW|Msa)^KdoRRSKykD03_j*Z5@@p*dOZi z=>doC=>KLTe-XDWt_fJjq28NFrgKQMn2~KuGq&DDU3LUrpdWgXaW-u`&G+)Dd#c^@ z>CaEOmaHiHoc;~daGfAKV!lScz|6RD6^ex4q7Sh`6IxO@y46AXS!BIFggKKE|4j3; zqOn&)zKPm1VIZaFzmR-a#!SDg>NUF0h?NHxKk=G!sz3G?r_xrLl}Ao(bAl#2J>NP` z7Uop-SjF4!#hc;`uVxpXcAbHoO!3tu1!>|@s%C4vTU?)13ck^3thS+WCJ>?2niBCF*!pdC%wf=yy8R-a04pX z>_2{o|D~k5%nnuFVdGLCC(`vp(LdbWQHo(iXW3%fh@fjJ5C`5ZcK>9J{rRtL&rqz~ zHsprw?{QR$+?{GnE+ThISqZ7b-jpRu~ZyluwB`JsjV`yN35e z<=uO7K~}H{Q_o?xLn+QX8E=|X4?A5tB<(VtDy(YLeZ@Hp;5IMsPciWn4cW|J^Is>!u zNfECZ>1IEhY4=*uI!41Iz5ea*j!yD@AOQ&$4cj!5A`YHp9W)(kr_{z&ouZJ|k=i@s(Y5$?m#k9{B9noy&RDG&X zd~lf~N?xL%r`f!p0q*Ec$lvcv5chw@+thu)mpya!zu2iZ0+!cvcqED$SAOHzhZaD! zIhwwLYY}+#;)zA*i-7#=1pN$6pv&g8o1>Z$#Y3ZS`U>#;|9J+?cRBRV<8q)Ws^%w zIw3djr_fu*zga$u=sj^Y@-b_2u*)~nu6&_HV4tB&sqvHgid07Ai!QXerHPeDUIP9e zgq{IL*yv<#eT<8cF8g;i`R~!Qy|g2c29mn6Gr6u3W=;WOG{2N{Vt54^mf1sNjV~GT zp0a_8Bb!#C-FueD|LP zn#xQgt)IMHeipO7JSZ>j72$wyvJ5tl%%+Iv47 z*1!>a?AR4w63uZ70i=dLdW|?OY)ud;37J)WI&HgkBM((})b8m{PVKNV&7)6$jS6MO z@w=fMA4&32%!}_&KPQSb@9~KKJo%3y5XZ6e@UOpNQwneTG7lhF1YIK(0we##AWwug z{+bp+rzJY+5J&15JbRZZ&zHbW`%3QKULj~GuPu(yyPLKI%B4jJMd_F|f7E}+wN4sS$QJ^atj9}sV zRMk_a#;%(61#-nYph0XQgfd1SM92+z*r;zpc_nlu~G!WQ!AKqpqIQc+?u+os@BD7LmskPg4}72zS6$0C|L-BL*g#auv`rU z!b=f~FXycgq0~A<%i``32981$h`RNyW9?P;gwga)f=$DzS+S8A1vJkTI55&*}e&+X1Sbn3Ry?t9# z|J~V4?P}%eJ})9l#E3P;$r2w8^x?Qezeb0A{TMG`7>3yRDbt$tJ>{_qLa6^u{Jf7L zBK3ND`eGKC3bcY*$(Jrm{wFb)1lIsQu6mGgyVE@_jdzJnK5ZYQ{o;WI-l&NDrftG5 z%fd=_8*uWAKtb%|_u)6S3`}i9Mdlmo8SUkP$^duL@}0UBwui^?R3_qw+ZH3 z=Orv_*ODsqIA??}sE0Xy?zJ)s(QoJVIa&nz>>WeU>vALTh_0UN%VLR^EY3dUC=2Aq zc+`&PSd2i}#W9q(oq$_DUbx<7(a9DJ=V;MH69hBY(Jj0Wg<%bhx%5B=Mx|ZNLN!|% zW}Ms7*=ra=pysk1A0-j$wvI~S3Pg{E{ZL{nn*{(1d(Z9OUMl{#bNpYX_0QZ}L;=2E z4lbJ^n?^zdx~{px#r_#%AK@VtH!i0U!hHu1j`z&M0HFeIX{b$bOF!^}(_ho(J?B4X zJgFD0$k)_t0Xi2OD*SUJJlJJHbeV(@`m0$T{jC^3;N38l$URZ5++A5ODaN%4`j^!= zWq+GUNau|eDH*}pW}Ukqg;O-f`2LWmVoC{K4NnLWbhQ~-Q9S70NQarlQHZ9C+^LZ) zMaor?IW?sE_wswLpoH|(RT0jdi|d5tAI5CxabrZAfr{rP`P1O3>JmYEjja0qT+5)h zc!g8Y?S_R!H>?92wQ)1}->&S5l18w;@OJh0>)$2!w=og9`uyobtv@A%r-G?@-yej- z?2tcQvII6n>QkyS*X1ybwwuJGXZkdM@&)G(0*XS1BVu7Y1xtD!p~SdZ!-{Qb zYB89VgBFuu=VyNz2~i2(zu$^P7VEA+{lYn{ zg*ce~a>AjAqr|$v!&F5mPVxckT^~O;z8B(I4nN zeVb%>L>V-J!5bj{*x%Ppv)lI>!YuWNb^=*WcmHNwKbL$)8lVK5TLPkW!%j#-3OtBL z*<50O<5P9jA%%zUKV0Nnm9a_#SJbpB%18`MLB@E#&})*|aw2>#JaJb`qa5qHGX^A1 zlKz>#7gMq+;n#kDF|9xMFdabms`9&Z88doj~ zR{UGe;f;cSqP(Iad2;dVR+=ts-kClBJ}g)c-Tvf`$M-02cdHLyBRv+qmgV0ynkdI} zGYs$ElRD*gH#f9Fy)7z}w0rx2q9UH>a zs8wD6;L^Q&@joxTgYqsaXrm}w8S?q*X3J8H23t=V1Y;WxgAq0>bZ@Wm*L*Z2Cj|I= zU?Vm(%9yQI>L^V2rFWuqYde#X4`oI!8>F0-Ro!f@d=X+ax_V zoX<+zh0)2ce%`6epO0O*(PydgH7N34bidtOpC3reQDWz+ zu@&tXteoxmnroN~mb8X2C7-?Sb$te!E6t(kkDy^^Hya*ch8411;}j&Rcu6 zA&6t`ZdrP!@SJn@@bKwPq99y%nJFJy>Xc3XIMq8wwb4 z%W1P)(kJ>6Nvy)@SyM8(mo9j46b3%I90{p)+{9T`u}xN~&iNzn}Jur5~yrYZy8c@ILJXTy@P-$0_K)hk>(%4U4ih;4K zOPBP!-8w;uFPzS4GJJ;&wjo&GjeN^Hnx}S;cEr{qkDd+RM5;^AYau=u%&vN;F7~1-9N-tv@eM2svN{?7EaM*-s;t9v)+Tcq>Aw!TLC>otUf1f9}`r+#o>)ATo03 zZZ|kY(L%u(=VoNZTG^WSlS>;=K^lz=L_418uY{8LRV#yyvv_pp_Jzx1e*F5!VN{t7 z75o?tp$0#ZD%$(iuc1FNYeo5>FLVnL`m!k1lFmoxX15>iyjx$cSPi0Bs6Al3h& zXc5T|Tk!buTlhTHnIg&BMNcCFac+H<1O!zziq@yq2q`MKT!Z#qtzS#^aM)_nTYIyg zlb~CMS27hNy9=}srJI2xfsKdTcG95Mp4|6~;GTjQF;|!@*x^N6BC|GYu5+hjpxtAc zA&_`sR@4*vht-|1G?1}B*u7Vwv{jV_JpNS7$~WPzXOAAH{|QvFA-@M;TFLM5%JJWx zK>gS|gh7peue$w5N3Q}<);fBJ#ln1y3D+;S4%_&t&_GdkHD+}o3mTP{P&djLdPQ_f zOeTvp;NH6pi|_uo@AY$Id&AZb?;HZw+4MofskBc+gv;>)^2M_oCkn*eHxJsE7c_5s zrk{BQVw7luOCiC0{0PCvx0fXT8k2!|05FoAUR)(L==Z54kNR-hNHNU(PU_ZHmJMq$=LGT=@YyEp|5YBz(ywN~Pj zv>NjlMh{j43uLho29=w?VPC^=AzTaK^>?4{I}uA*~W`uL^iPSFCqK{hn&)Ge=r^a3PrtrNxb2fWB7|^hWh% z+ZtK^6-D~$*;g(UHVS(adtUDC!U7#Tb0`Cli{` z!gtiuQB`JEu5oxZ(0GPKz%CPu_W7#_aN@4Pa9lL3#V+gphuEL}0}FVaPpl(f$}Z8r z$B>*V71~jDL`5grH*~%BTC1^x6{NXN+@Mo6JF|?j3uJK!(+|iUgeyEFaix_=sty&+ z$kwV_PgT1)o~8cqGnviZljxkAbh9*aSdMsvcsuQR65L1JCm=3YzI*fjp@MfhLTHSQ zLa`+FYap>0ji-u$uPW!OLUD5O!@q-9WGZq^RH_sOo7SAvP4x zU})#k)L;OoADSM~npilyd!f9V!BjBi{t0M{NQhvdDB`KAD8~q~DL6WFNh2 zYmT_D8GN+JR(j*Jo3LR^eMlq=RROan;m7)m1qT|-p6@U58uT9sD67JhwMdDqLVOiC zjM620=c;Xnq=}uG&Ac3@O&gOV3_}TV*Og$-W#+pU>;FLN~F-52=m-K6diz z*sISM^f*nW$C+N?zg;4z@kZ>M5-Lk~5!iH(qbFgI+bNdt8Z;|oWQp9gmxdAawJ(`Z zZUn5=Km+~9;1bnJy&HCOlb z!!o9@rLB>wj{{V$2tjx>%Dyq>`5=+S0=y4x2^ws!JZp4_n9z#w0NwvWha21U>R$DZOTST*ZzDPrmj4fPdGCxP)!wnWE;%$ks`(c`D~=$uNlSl6EiyQ`+o zS9}(K&34jV5rGyxCZKTGp^gKq)N@p|23ssA`lp?>#2jV*sX#iE@E%+u0qzwySi%F8 z5lwy17}edC3Jv_aP_K1_jf!-py*j45DwR{5OX=GnUi7_s{IZ>BpDK5{1h+)`VIoB#@ zvbUJtjJ@>x-KMA571?Oz4k07&R!wXVD(g%`k_kTZp4~k_ra8?JX=?0vi)5(+It#nL71Hq2))dfhCc_{<@ zX4Zn}-IfWV#_XZKBuNc*zDaO}8govWD=YUFV0wCX`l2^dQjhLVzM6QXa`Kbd+ovN! z_sCm3V|tZJrfLn+E~=DZ)LUqO#Y2>8oBBrspm@oAGk;D9ulA0y@4?{6GM!NLX#5+e zc2zawg^?nx7dyK?^T4hT)eBt^%lwi0NWL2HF2%<&yjJKf#R@)Cyts47$}zi#H81@r zLt*p#!&;qf3)?`birkxIo09&sef^(x3Q@>%!TYD#!Zlb>+3K<^P#(rg{kwB*|0zV$ z57xZ|s=8=kH{8rribWGETi_LPeNavEZR>ir^~S3OkBL)@C2Nz@aeDOyBW352Wv5WK zsLO&Hnv9FQpsUTaj4509ZhVLBN8@e_wx+|gwELt8MUsePT|SwJ=S(Ls6cX_aa!ND; z9+F1yhG=MtXx*E&d1epaE}wG##k5763-#*fQ;tKh&c$9+cVXW-nuRb3q}Mw7scHsP zUpMyQ%SzQ~A~DsJ@<;z}cBhRWSzrJTmktG6%ss7&rWnbdhJM385~*qNR-+w?rbH;Q z6r$LA`B$cH35xmvn0agiVi?0Nt#_0D3gIa~fqivcxA)Mq_2|xi_LZZXu!l6rLfhUD zZu^Oc8V|jFPrbPxc~fsgZfE2;p0Uo}8<9$2psa8N`ANNj-%VH`A<8@@ ze{^%Z-$aFlQ8>}>-;Z5-(ah)9=q&&FcfX#_lF*kwAE-M%8XrD`#-i%Y)o1VCzbAYx%)X ztt7n3AqJt4^>>BUE!FR&Di`1y#Zf%=(+_CxnZ_0rLk{eG_P%|hBj3|sZy+_+dxz*< zHNO4^0K~zX2qf7dGZOjD@XJ-9scqp{=iJUhH%0ztK=5Isc5+d?>+qFP(fYJ2)FReu z5t~o^pch{s;p%-S4YQX1=J1S4wu#R9JWv`i{A3V=P+vJed!>M`HMyCET{Li+5M3JQ z9CBneHhKa9$S4KdIe2aYJ3N+Q;x8Dcx#Z24qJ%saEymJ!grow>bPI#Aub>}3T)vYA z4S4j2;vV#+dJPJ%*!lOXDfvH}FhTzRPH?X7=AH9;aoyEx%kFx`H;)gfsP{MNT_?c} zss0=}FPXe1-2lW1?(2MLk8FF=@j=wJ3KBat-TL3JuaV|i4@S!yE*=_Uj*=ud@9FtZ z7W*6FKh%W-kzVRft8gMO=u4vp*^e4k66tK`tN1o=F*~U2aSQ7l8;3DEeRXWiRjW6Y zP(w*Yvl`5}u^X;1@D-yY23}&jnVxBrA}+SORR+m>(}9@Q)-IdkytR~V4l?_5Rl@RGNvmt zG%$)!70a%bB+^)^3B7{oPjsPaCbHt5rc!5D_>q^>hF#`oVpPzePs?F%IzHFDIi3on zJLCQWb<9zw{shKHj|jWwQ?klBEQJ0Z*sYe)JL31q-`-~u#=BZ{yN&__8p6n^ELF_d z2bizRMd-f(+Ocf*=>bFWTHMCjr+lost^N>YAGE;`^sDQ@ARd`UgHqxTYcr@D2`fv{ zGf;A~AX&wn>18l;BJA0dKKNDUZ)X2>IYCDnwcL@$0B+m^N18sKa9;BM{egi*>9{I` z-zb7nqI5wa&?Sp$TND8M`1C&C#MnO?H4kwx;0uB-Kg>;;@SK|*^#^D#$CZP!Mr78%w*RKk|ZKn); zmr1dB<1Lu>g;)!y8%8)VVL9ioh;vS$t5O>yF&xDZKO60YZ#q zy{N`JRbnrvJB^z%9)ojm47=Q(vkje^!fO2@{>AV;3{Lfm8P?mqYtS0qgTc&e4D}4rvU@YPNsm!8b;~xrS3uml+z0< z;FkjZ(E81zYSP{EZ`&88aCg~tCbS62`j&Z^-X+_l*(o-M28dIrQ_6QqHOW96r!AVF zDNy_`#!Z6GJ#_mY(;S0K+1&CRuj3tC6>!Uj6v-cIl66xJN5>$(b>T9lpxW#Q54r|W zE#NU7+5Co{qVK&IVb~oOL*>XiV*!+SlTJag(wgE1YkxB1p4iBf>{etLDpL5V3$e(g zd{v4@v$VV3qmak{P-Ye@9SXxBdS3wf=a`mIx;F=h!@k>`#m2)EEggv{!bN=v#)m;8 zX1)ww#Gi1-I)t2JHHr$@XkRC%}_pTwch0=Xg#3^pQ8! ztJnfx#L>i-N8+{dPAm;!R|Y6E7!_V{rCgKx6hwlQ)2EXbaxD)l(l|K$a#RaEu@9`u`~uG?N+-V-`R)6^>pPvV$yRDSc|JaN zl;#`2^QA8OLAk7N#e-HH zs;;2|!UCori+HWd?-SQPUH$f-cflg8up>^bE2IM61(TFO%i$QtEt)Ro_T-j+8=Hy& zYJ(1JN3Ufrm&8Z`C7>6L9;$y4lk3f}02G(JgpqnV94TZr_rLk2*AlL-T2)dL#E!oz zS*0Fuf0bGpS5gC$U$R(Ny$~hp(-8Yg$bh!sFw?VBD$Qt(*)RKhUP`mRA0^kCIgP$hI^ciF7?`2?(-K^s@yOk2- zU?hF;?K3@v3@fB*X_YGYERxAA4Z%S};^}Y}Ci7Okb*n{CthD<0a3>wwVsG_}-kWn* z>ve68aR&rYLu8fx!YgfA*yzR^D$gSgTJ~&B#nlqGr@^|6-}H=mu^f!4;(ZX-=N=FjY~jY6j_HKz!46kNgr7cj{0Z4l3R5QWD@LtL@_Pf%~oPFuDkx3#W46z>&BEs?z%-(J>!Vx{Acv@=E$g<} z8SEUX^dGcsoJ+3acqYmx%8{)skE!E%#p7ahgcSYd| zng2D0lqIXbRG@q5_~t<4Pmc{qRduy!h)u*=pt5P{pn42%!W;P2)n?vAz#w<;ab()U zAPKE0+rwXA+BD&z&f%JT3=}3WE2X11NTL@hlH7Mu!rT4CG_he*r>LKAy1{NfPu1|%y6s! zOHy84!{_#$6i#N1VPSBtd|Cj|eZpD6y~0A0o8kMiEyL+^6a7lY={WR_hD2?TEERI) z%?Mh_0=={g;}`FEchgSxG*G-^PHjtc%!r)j!fn(T1ixoV!t7%pnJBW?X52umJ3tyj zCn}n;nxQ`AOgKCs^t=)gw=G@7qqhjS|$F%-H_@f=Q+2r_zPW5k&bKaZBLC1@p<= zF^7)6RiO~ZBssWiRkH)^s{7s*RX7y`cW)r@Uve_D_DU*g0^2}Xi8DaH9^7`j!ELr>jc99&rZ&H0|C&CT=7u7N`i7ED?Y!j$ayC3TDc0C;q_4Zal%8 z1^30Je7=Qef8a;d<5%pyr6ca&PCjzuqcK}(AZKEG>s_H{No#LdHyL=(-?(7MAko1F!r?*45gHv57@NW zdEI`o8oUVt8tGOtdZq0?$74P^A7E~Cb=BDriQVQTLp3M`zrw2Y*3Qw@kbzv(KSEi~ z=1|SQ?k<-~XsNw?f62{!Qw%apIAD+`in??UTk>Y^9>yv9;5Qt{1dB@59jsdH{*7sT zy%9gJ$JnM3oaF0*dZnS0yvyaz-VpIFnh&qM*!kJRi*jw_73Y@6``{%w`?nDT`BVZ6 zm=u)9$>j8-abome(r-H(rY{w!kBv;9!sfO{3zbwOw#qCYsyIAZm6fl;JN)>4@R<1k zo-*2DvM5xRi`pjd>0oP}-pVjZfcTo3Vwpg|6@8Y5vLM*Lgs8BN_AXo014i_enR4%g zvUyG3cB8}$sRf?K1VuY_ch#V6>xVw^*q_!fxYS+Wx~yHNOnjY$F(!7pMj`Zfan$E&0%p5AN}Mi(T{(svQcD10GhwsGls*y z_4>!cJBxo14PvrwOh^unLVt<8c^=(dCpi5?xdkc%W+j2qu25(lZA?oUy+%F>a)r(I zk|#eFm907A=F-ZJwoLQx8FJlt22*TMSppo7{8r#mXGZMW1dVh$L-F52j)LY*SXUk# z-)CBAO(zSPuM@%jt!;6AbdLBDPg>xh^ zTmHWH5uT-@PxcQb!)~DY<>_*xqwr?#u*jOKmt?u=(tuQ`w>n^>dnk0^dsMuJI4}Bf)kPXgUl%+ihTrH6fVHojrEb=Z$D%$(KnyPx%YcZ72ObnYL zsA}xa`HO!bdrH|9C!FK~&c$57Ncg`2H-yr9nzyXf(h_Vz#+3d+s~itFc>z?pxC2~B zZ-Z*#-dD0lDZNslf@tbAx~{mKn?zWVq@j>3T@6aVP?Yqc$RBi?MF<4_?tov!T@^GG za~%yGub#q$n>m%&84>O5QT-|4np3zX(H+oGC7{PJNn zLv;-Us_eBlu03ny;w%+jpqr}V9s}mOEa=f1B>t648H87snD|kPh9~rkn5f(=oeUE zOd>`gf+}Mh7jM6Ck1&Er4Cs%>;%`feYhg_wz@_Wg&q!ZlBMj2x=;GFC7S)-P0n)ug znsM4mpu|1ebvJhjk!VI~itEB=)m=9@AEy7q zuwgUxi6CRX65iyY&SZTrl{Z8Tk+2l+g{6MoXF>e;tBE2A$aSN_Z9{3v=C`f~>+tLz z4A}xyH5gq_B912&otB%fw)lP#@Hss>B_tjLAN;T&F91iGuYyHGhzus)+kRgx{ksuy z1v~)!T+tYvb%;r#DHKB93KPI9td|f4?Bkezd$1VE=_pq90Wg+#U)SbM09bIE$uRH~ z1KPy?S5ZZpx-l{h|ZECoL9gQ|4(8ruMz zYw-6*3E+7FvA3Bw|Fu99I76&&0VeWi;b~bIC~fBxwI7f&5?&fp*1Np)!Q0vLMOVTX z-U#V0&mQcfQ6@Sanb}uGaz2s?7hkC31#nE z=Q9rNW_2go<3i zh6K>Z7qWBS3E#~cDPP7(l|4$+n)W&*Ti#O6lk zKo%;7J{I(1+B&MmuKYCyzxplrWz8TYWHgXvZp%MS)kI+>*E;KWO`8hN3l@5Wxx#C_HAMq?-Et?(FJ*^zKA8${aD2E;2KQTjHNIgUK-EePxtLPrAx| ztRe&`L0!aF5EeYbN>_z+3WhjFI;GPHz6{shZ$!4<=I$j+ z14vKr8U5l+R4z9GK>>#VRqd^$mgBJOTuUvGs?jK0W5Vq~@IqUVJ1uZg2A_o^(&4VS zdVkuwoH#cnyG>BIvo(mYb*uiW$o~1o1A+_op;6mb;PFu3sFRdzEuYCWBNi%=F=n?$8J^t1&Jj?KS&bggW`iYNVVH4}v?mA;8y33g0?3h_oHl zUcBt1mS7okrc4mkj^$x&YWU`X^6#V2FOx*(mr8aYU=wV!g0op88c|YwY{dX8Idt*`yTCzCroyfdu!kEIf=vpjnS7rPw@+Mr_ODPAHq3ie2#+HD;zmAm< zT6jOU0Fl5AX*URY%JO!qncaZrWTZ}kOEpVXo0mqZSUQ=LZ&?FyAd zs*7Gc0+<^?`2apw9CVP&tQux1{X1EsU<=10?xh73UvoR8&{|LWsK4o$zR8I($kFR$ z?3xeZ2gZKLwK;~CsqE1AQ#(Z2fq3-dyYme+NTt-)O_}b9ys>3ncM#W60s%-+WxHKJV zJmNr8@8N|o<*&#+HThi3WuauM3IxxZ#p*`?m${EtiK5)Ek7Z%53XshM&)|dqH~ks2 zIhWMs<9iv*8Ky^gBFI&C#E_$qh$@*IQP_2xiP0^_qdTb!G3P5jtEhI%-slS-UbVFj zzVcSH;{+_u%o!LLSE2e+By;Y10A>B}S8otwWjG~7Tb*nJ`d-WOO=sH9;J!CT?2h(~ zL42-Y#}j?XAx~fG-T7Mn0o}CU)BTF`qn0k1F68b1(RA+rOg`=(-^uxqh@2|taw>-* zhvX13$HvAihcU+)MGlD?<(RY0IfpW*Z4P5oD(4)Q)7xQ=l@yXlQAwxoK0kc_hI{v8 z*L_{D*YgRok1Kk;uumTCX1wdkI>tA&3+Wpk{4kvIuT`N;#$M}`S;(X8iJI<>Lh)NH zN6%T@{p!|`?HkuTL-TwWS{7GTE-s`{Ic=&B=T+ZkH^}bFooV~1Y#%@pAGz^aUKFfr z%R$RhTSoSJZ&9?HNf9XnX8((N=y9xRMg4PYlk7+?IIAxNmCOm& zFeTtNrp(#JxW`Z|wrHfOjZPqI9mI)H%bXSDZ+x@#jb>+0Bl^-1+mi|v87aA)lR4$X*`{P&&-i?yn z!Wf+P-pSp4>L4}p%(WWbB=lJ}{PTf2xNo zfmnmXFeXvF5Fx2`Q?n-R3t-TW(hy3I_x0KsTfU&gL5wPYg@|_OW8x@jwgC?SKG{e2 zpyC?RP8oMgp4wCH9M16YHz1XmW00O}w>ydtfD4dL-l5}v*7#FL? znRzmTX+qI^fhQ4x%armqTJJuc|CzHFBJ60XA<%K@0Bs@LAK!jMZv z@Xon`+ZO}YTt1&l`goGKf9x<~%W?lJ;ZmvBMd5*tjg1Sz-0@YPkUfHIv&Riy6o2yS z6XmvX6l{xG3lcPJQ9P}6^G?{4v5Cm*5Mj@N)o-*0nuq%b^s|@>@xS8pjXK=L7%DJP1?`U3R!9Kh8i!FG9`mM&9o1sD zXGqjjQR4cku5HnR%CYl%AF@)XdD2juz7)1vxsWaL=RUk;WkCtTOb_3zoUm>f=KraF z?6*Gx<0E`R*E|I>E4qKHdrN{_g~F*1N_lV)F4T}q)X5M6>(}qcQV6S*#?ZIPQjIUD zQ97&>=KKqPVhS1D`=l2FrZn~57FGdf@BOl*VU4_{&L^&xzHei>Hwh=5Lec3jV$azemUy1XFeES`ILQ2GTq;j&9NEtyDP@{!L1NpwojoUq@pkB zhx+7T88E8ZzEnyALMEn-lj;xyZg2q()Y5FkV{SOQ8O4II2SP9v`<}ru_lFLu*E)R~ zRLbSyoVvVck*MXkb2@(gn&Lu6MYGo*UZTa+4f0JSy4o8x-}hI4xCmX|wMfi?U=4FZAxMre&Ba zoxE90d|+|EQ|ZI&l@v~-9&ce9JM@;L;oZ{heUNzC%-b6cJbrEU-1VmWfu(M?J}J6C zgb<+M=d^LoGglJh8QvwPxcWPW^Ab#~4ax{A`L=$|oN&W*q}dd@9|>9t>lYizwQi(< zLosbfAs@2dt7tzx%QDaje(q9f$*1XsiuAx~>X{!Fp{iY#k?R;SY3@S3K$B;Ul4zyt zI?4o_T4SEaB@fzvbZbccW#v>nHe-8uo9tDb@DIytdU@I{!zbQ_!RT5KcgbR{FH7bv zU&_?}JJGh`^ynTQ{hV|3hbM^|0-Z6eLq|T5Ge?_VmTH_*#Kgz=*RzibE(}fHpShKc z0l5jBCwh2yv);x?nxMcdFLpiV&YC`F6Pp1Qy3Ql)bteG0mS3yg!RPBq{yRhG{MVV; zwg9pF9WAX4b@HWle9Dij7|O7a&GNmFpFXcArEg~aiwBJLyfu38#^YAMdX6B4Z2v4< zk1@-28~x+Mw803?)T!HBUa~O*V)U(6`9Adp1e-EiXen0ci6``jBsx}0?3R`+yaSJS z^9n9ODZu?6W6`U3>G-zsrvFY9KYm@jlO!1CteSJmr5QRLrt32%G(Q=h@wgZJG-fXd zFViMIkzw7l4xCxnh*CE_CB77^)XKLx?XI10?28sz?<6-?z5HUX4$#2f?Na*Hnc>h; z+Rt63pv?8~EfAh7QA5XIX#vE9o-lL(J6S8L`>W+W5q)-EXvY|jupmGn&f@)08zeJ0 zBv9XE8ToNG)zjlhQv_C#cDkskA`;gm)AX^?b1SorTb z`z0(Ly2VLFn;}1tV%77j@JvY+fu-z}>weY@+5=bJ7SdRqpJ@Bosq7B6zi=^>(+MiW zs8W{9LU`P8LR!bj=S9+}xmgZ`R<)AaMwQezeN46mOn&)h;((q-R|O;Ox?j3bPPMFD z8!4e#SmG>7o*Z+Yr}%G{b>DlA<0D~{uO=f_De`f1TKL!B=M~vO%Gd|T6vyp<3}txq z)-A3NO`fSsZ2k_A1~r&^bE_NKeKt`ctFdeVQWIDo@KCNpdc*O4?qMmZ;ciB6M1>(` zpqA_R(P8z=@5)>d-YOhaT&-PCQYP`p}ixwnELKQ(Dn4X08gK$h54eJgyfY}EEEV{B}BpQ-(K>+FmdvH zL+G43Q^BOUobTwOp>B`p##o*9zr$}hCe|JC|T!Y2KM30s~GU^Io$XI z3mO%WYuHacw8YLgs%cnVR3>ytv}mZC#cw_`+Ck_H3S5FI5Y&YP4oH)NZ=;O*tPA7nvx6o=+&D;4N=mZtce)H-(lJ{b`4bNUSx8* z&1#+LGS44x-w`8olE%l z`Z>j@2w7b+Qu^k>{hg1Qu@_@JyGRwHC8txVT1JH|cBzvYEQX?aT{9tlYry=SY8p#y5OP<@};*_Go{tMJLjG%dPMXpjmPHEMOTZ?Wszd?lt zcix+mRW(l0pxPn)R0PgYUxZ{0c;i&vMcmzja3)KYVxHTT-6HZY<4s{c47k5`@}dx6 z=9rc>=nzsW5jz@Xlm)q-c`Pf3L~|Qvm-V+DDRpI_x5oK(>Dk(By;a-h6D>C7{lw3T z)@uJrbN)29@Jq2)K1uz~7P! z+#g;OCnTEqU6|V;iTkgy(z-l?U>iQ^yOf)hdgiB%YB1Rfplf|`3^jRe@J z#tIjg{Psb^Xj{cpj|SPV?W=iQSC6CTtINsnoOO=DPa@ag`%Gludwb``4K79F+pF~b z_}x6F#pOJoz4@>9lde#L#khjM4(_>{YFQhxNAlC}lv2A5lv~CLzMs)iHV=#t3>vi2 zC$9p0RiN2;IZbFgwcylGnDk55f}}&fIJps{8o3nx0syprbdBL4T=3RvqxsfqN-GSN z41C7+7dNUxa85J7)MHDz394VBS{toC&hkTL(8Z4}ii>OAcq+wFFvKP>lOd40A^2XO z?o~c(Jva~Aw?G=FCdpW`j#7PLRSjykq2ylOE51)}*~vN;{5}6#j?++_c0)HqT86v# zbVAik#$?-D$e(xz;2np1y}6>ng$iOLqVta2E*>$O9-Ld}jWeKXNdGgQ4Gx&)fhQji zj%Vaqezx=FVwF>;GPDd%2^H%u$VOQEDu5;PYzrjsmU)EBWp%~&nm1zXg@>o!PiEA) zE&T^DSLG#6`vn1Ti1@ERjtajs1`&?R3OdUZhF0_*KFi;`ws*bcuRs|3IaJws-oP4h z@ViVmay0wy(MFHGj4ury(#wQ{(P{TRS=)2c z@Q~zU8E{FzT(;-q8Y`LFwU^{P_IqJLj26J}6EK zz#q4k!_ki>iCTML%plAYwu)aNyENTA-=%w=uc$TmL>D0NhnEYSXwUZ`j!MOolEs;| z_|=k@tj&}XpT23W^l&^*+;9%mbT9N`!itBRoyfG>3niVzo#ka3KqoWUk$y@{xmX1# z=}X-QxevIG*V%vzdS|De{U&nI%BP5wMrV>4a>*xDd*OBG#QjUb0Yl~X;)(qprgveMJz=@;=zfWd!*AT{+R1qrs1B#Y}NiK`N5*a>!JO~in90o7sGYLe*b)+=sL%2 zftgGims*WclU8Q#+{vA5=61ALIji&5FU{-vArx*Sjb4QTunBuZ92O^Kj>YF{`(N76 z$vF>wQE=eASfAv{HV>Gffl`(iJf)XHuFR)KZw1|VC5eg@ceX^NO0lmr=E$16w0;#+ z=wG&1SXnj_Xca{gs*aE8%ie_$!D9geK5+jNP={4(?LtWo&+z4&&^K7QJWyS!h-UkZ54J7IVgXuk4MD^D=F z_Z}`WYffMNx!@5+p#ZxoX2b4+?=WvZ+2lgkoqqG$dlys7gZVS5h{6iNoJ8HaqO9d@ zgk3=yV!v<3LNjIb$*@r}aTr!u8~vOTi^AeGhf4<}zE8SwT`GNc?RR#%8v;p^iaxt; zpIZ!-1dD(racdBa?_Ver$Nyt(vIo-WqmxX)_*5IGIs$Xk2?!fL1ToYv^woFdU;kNs z?v0-SBQ?mS9wV)aPg5-6 z7x0ATVO)is4W5rSV(>AQRa(eIvi;qo+T_KQvdBF}?YwK+DSWkgpq9{h!cDV}V^gv5 z+Uv)zCByo{db%r{q9IR9(t!!@DDX55A9?kCQR)Zux5g9NbbK;odATlPbR8xC$0^9% zS}-?VZySHd_W7bdVHOumtEpuQ=_}Xv*r3K*5G2js{oc2(DF|3V$AuV2 z$bD z>f6d6=M#yIEzzKUS)W+$PQ|f16QPKOb+KT^3-9hd+zxa(Vtv|{yh5V~%Q{pKFQ^Su zApv_bScAIZVE*z0me*tP~KG6=A`JCPphhjse=8O=4|z4Kc=Lb6l|B^Kr=J+I}- zWgoES6Pg_L#s^CR4EDRI@evnYqcCUH*9D#UY9@Ye+aXztzl8={+|F7m4Qe5EQpO&c zBmmC&a-Xp}Vnef;9D9>7;Urg_j|jsi*9u8Ft;@k_0I%wzXKpW$e6-e_$pUkCxYIWDl#V7F#70ia>g_eIqzwk>Tvq#G5RB=bad-xIo z-^ZlT<+Xou&!2-HY!S#`Eh^x_+Je883aLdvNocM-L-r!oFwfjR~zp;@QbkHpl^ifUnB(^RtWMXsFk6OTQ-X6Z|; zJlbukcGRB+$@09(K&k?VYdM3ckTWj8OaO&_RNJmn`_^ZP(a&E8Y!@-h!iq04_1{97 zSOqbYB}vJJP|U;{L+9j4KPAL-4_HLYnx)*=I*7iS|CMSDvIMkc@(_9FcGV59)v$&F zm6UY8kN#TYvmSFtt4@K{QOF_4v{vu6@pzc9%#riq=rMct34|1?#Bn&zo!tI*%jBTq zMwd)dri@c2F$anYQx*Si_}Myl_9LlH!hT@FB#&-^SZK*+@vYJhN1ka{;Xwh*WD;`(j38(7-S>gracQSbF(=-GPkc zS}+j`7DbXiGV%`#EB-wjyI6Tn1yry>x9)4!+B5lrB~wYK-ZbkPxbv@oq2fKj21vnZ z#yL`?I{9%^rB^=;5{Rqe>BXE_X4Y-}*h9Q!C0?U> zKINp@U<=kBx6YbgE?vwIHcDJ54N=cxvlRjn-mCi`pZE|Bm=W&h7Pu-MFPGHOILdov zCYwIY$bDeDu-=|;EB}q2wJDlQ1~83-9kl7?o;qUxnz(iT8#POUVjHF3HU6EmSa=^2 z3`yZM<*&{_BWnz4k@C)P`OFS7Ff*o0`tL9Ae__2HN*Aopd;IGrfB)9Alo?=1pj~3b8=R(i2lU}nDtF|5(I=ie2i%K>f*C|kG za+HnBS??^Y>qeoX8+J{kZ$XU--UJ=~Y|76%b89EH0D8M`nbs!4<9+ zNux-9HLiS!7V(M^v}n#z2B5f&9yL|=Gg9ObPGiV{$8$n{DE!yV%<|omO!2t{nFlf9 zdOadb{KAU{1`cMV58R}lSQR23;ZJJQP^TZ@;>TTFaON5$gq$gN1;A13dIfi`#Ya-l zVk~lv4Um#66Q9(c6`p*`&(r%@#Qa$gh5r5G3$Qfz3U%O*`WvoVHwF0D4dyrd=x`l# z94HLi7)pZ^qN;K(A&28{lwaib)-?F9e8D42?Sstu1SH$y=9TDm{x3VJ_KgcnSnuaf z^^dT`%7b+kKyn3_1K=S0s)#DV-XnUO>VQgh;z3nmO zZsU8>z(7!`!vyl1;ut@Op>ECF+(=U<OBiNOejWEyI_ktmlO{7nyY#3*rbr!9w>7aIIQ_jII8N)bq!kP~gQ z;6_zL6XXJw_jgh)m8SthxtLd~HyhyjJ|9(ODqsOhV_NAv&Sv(9bdYEq zXnzXc-+>bC2}okc&4N_n+&;pKKtDiTeGB#Ye4(v+;1z^>Iu$W_hJ*Ie9tlX=&~W8L zHAp&LidJfIT}u$@RB)%gQ4 z3YFJt!@Gmr2T=GkL`F4{4K+OPW3SsD_m6aH=+`2T#~$@4kNLtaPT~t^&Ow307yI%g z8{7afZ-YhMmvox1is#=+`fZ20#TtI-q5g#u?s^z2*Z=CGTrp9nIDGMTX2glB`T;+yq4}D+%6OhAy^h=2o0HFvB1K zS!s1QI(C3RYgDvFIQzIKu1#tf;$U>)B&!2sR>QIv@y!*|H zu87<}IjZnW(dl^Y%n#!C5ogWx9Z|9#&vC0l z#4C*N6=U_33c(V|dOh_kH&e5k<2~&qU0hVGOpzw1o`1hF4(`gCKYGBsU=PAcw**uK zqDO6n|0t!6HhR$fG2Zu|kI`)&bKciMVKEtSgUAznv30Sx;b6E8Kqvu0N2yLMl=4}> z!M>KIGvrnjoBnKkdo-$+x?XohxznSm8#_muto{reI0?aiJaZUxjc&$Hk{)<>4FoBnff`+MS_?8>j#@V+dT2y)H9FRpc@ z0QE}kOKefqJ^EtPc%ki@F7ON=q(x>pV~?o&?;oJmm4`YhU4QPX=0r9-*W(9rxb!NM zpq=eZ61B(P;ZBop66lb|Lk7IU{KS|3x<`YLMlzn3T zlPz(2rbe*GA+Cw}p2uciE_B#s_ih%#O?v35ZiA z15u%lxGbt;r!{jxN+11Wa;XnhO&y-*V7YIrV9Tl-_bDV%{4ECbA>X@~iy$FX^VpG~ zQhS&Wgb@D8?v6x-O+!r0cv|mM^@}gy{nW4BO@R&0BPtu|ZgjnWNSNv>U1k<}zVwr0 z3#bm5{L8?y za(CYZ<8|A=-#>0Wgl#l>dd%SXLw4R9@A!0MpGXhYy?bv~RDI|3d$ZolkpcDvTNMDs znj4ov2c=#w+eC&z^jCZina45%?Q&`ODk5#TyO*7yWA-GJY370`KEi!x%yfIo53q(6 z8o{T8fk*T$OZ{a|as(6C`qKb|&3#&CDHk8!g6B}#>C?^plQxYyn}JA8b_5k*qLwS;jnby+>l2ZPD5>H2AuyBc9{VcDx~g&a8>Lbs_YON z4<>6ixZNYsZ~nykB`?RSmX*K7y|_$rO+aAyw?iNU(`mbu-^g+|P&0p!hmT6Jjr23y z(S_vCI(#3#WMP?Mq9tu!o~l{7W8G{Pa843sTQkcf8H%mjH6)22ekAZg7uWiwj{@v( zTQ@RX&E?Xj6%*HwEU~jgO(rVr5OBvLsv76pPDD={sZ(xvC&enV4_7)L*YuTL+i=vB ztjT#wqxu2L^uEH|U?2GHUzJOd{lidFQPwC@eu|}OpR;+C*r)!35C`WA2_*LdvMvd# zPq@G^DZ9qe$;dqPxNsAaly`7L6MjvY2JJcf_C?f&Dn+p>dFe6DbUTzZ!zp2HTrOib zu5|v;eHATDFxy^wP+ZqQ)w+O27t3o~^E(T>YbOoDLNyWcSpq|na;=EVy)LfxYH;K{ zjC0~y=RmezX^a$#jY*%}Z|UBGfJ;y5Hk}yU$a`%ce&w34+|`pOPs-CsC`ODyZa1S| z>3UM}V5AZKY~pF^abVyocUz}=n`)kd+!Da_^EE%f!^Frz18#!`k0E!wcX@4X%OquC z(d*ZQsP~inZ!dag``{|j?uO7W;cP(s$9(aC4p`_GuYIQkZo90`OPdIqo|X3_DlzqY z%-m*t#MdinU&BcDu4@;ni&(l|=adz6$oRcsI>Ui&B-F*1{$z~*AGc5S9CQi9Xisk8dEgsKtdlgXvusfppE2@Q_G}@gM;q9 zzgjdZ&Z_OzK%!Y8w^j5$9#GXf_!ml}!CD6K$hW_vh>Wk)U-W?#r^32!edgFQOyrH0 z77ry?H0tttx>N8rviW3!=5dn_?0e{P4JW+w2)i zIcD{*dEr-8spxxyPhSVNzSz4`Z+x|R8T(XrLH3NBvR$EOer?rJtwcc3nMAT^%@sR2 zBRvssd1aMKt=IK}Wi=5>X1P=IiS|AX*?>8916Bf`Pchb?riNB0*ESJHg(ATaa=qUI z9Frte8(w^J{$|-_~&6>GssAaA_g~FcUIR$7c4K>>qga4-evI_JwjuUWQQp z#I6DH@85i1UgEFvPRAn&D}G~%U82~#vx^?-(H4y#8hST@EV!imWPc~Pq{qku&$#=e zd!F(g8ru~;BY};T6e*{XeatjfEFY^QL@T{GW>uKrWS|lnjex(J{S7o#wBaUG>%8@~7F z?)=&Et`_#b@aebdMSl7qFZzlf$x6xO*a3A;m2d== zL=EM-trLG2rQSOD%q6)FQkZ|@a~Uk2-u{nq;IHLSs%f6O@LGRzE%b- zB?=X$*q&JW`q?5ND1R3cJnTzNvTb{{Fmb^T^6zGTuY#ptV3yuI${sdSAkB4pG%&M(6g4h^*rU@do+Dr`p0I?={q!%4lsv~o zgOke7EE>LnLal^})}o5ufl6WvVSMg|-E1q}Hv0CPmQ+%`vhj}h9Z-ew902$npX6X_ zj_CAT8FVJ~NY_uY>I}qM^d=6ujq-@JsPVUBpF7}{pl-FFg%u<-Kl4<1uwCQX5lJGN$ZLHMpo}k4JBl`u9%c|=jZ$xHa zta@#b5-g!4jilt9oGutxcJYFaKjV)>0X1LRVN$=sefGonPON#*p1PuS17OgIs%kZY}ozP{Xj=Y=pQ z1;h?fT61_z%(mav7V^12I(cz`dq#~ocCXvz%$IzhgE@;?v#c5ZIA5l0DKlC!uBIwA zoi?`T8)sg76*+x#s-Jz^HlHD0vU0r5pG!b97Ifhl3~eg8m%G+x8=8lIgTREHRkEa< zgO~&&2BZ#8fdMsIRP0`2IysH2$C)#M;I3eN=Mf(e7?7%P$KloUQxXfh$&^xRqletB zDqNO)kkI?@+4ruz(2rDg$DPZ`XUZa1XE4%0zr*!U=NbKh#kD(mu~@9%sPPuI;kX;h;z^zL?8h>8wy5 zEH$%TBlM{hZR486Y!SjxUH^sySyvz=TSJ}kLq`f8@(fq)rtI@XP2W>Iq<2 z1Oedvl-2(&+5eq-<>@=sSB*`*E}lE7B_`ajx)&OeJhx(Eni%>t5tw*n#z5Qu8A5XO zkokeP*vxb!#kfe*hi3dKBTgaC@K~y;0C&&Ki_)?#8|9MSqs+#dgOzk`rq%k1RQJO3 z%Dw#LP?|Qwro?HSWwpA$)Fi=cx4HGIit4w{9IFIhKYfAyqj5wls^+LRn}5XB^XG=Z zRW)vRKC^R#u&T;6$H0@6vtD(#4YqyDmP`4ZhdE1b)}kguIjB*k_2d2j=3rt+D0FGF zkq0|Ja%|(XYJ&P$-FM4(Y#4_9tJ|~sfjY^=hlU*=m4`nBQ$2;LEac2_n--6-5Il{3 zqDDiRV7T++0AfZ*CJC1DOL*Ph;6hUP{rcv*&kh}Sfg!I>2Zj+XLbjWJpmHGz5A+F( z3C&Z}P{VRbup*wetH6UZbE)bIEx;W54)m~3ij`=k^vfG;P@q$`IvZiSz?c10O^U=tmrE8AhLOmS2!&IFA*wvZ` z_kfQ4{wD*Ksfa+$mm7ghex|87z1gfG_K?beW>dV6#YMzof(J~#d`mW#t5}ha&%P7q zt=#JMlk)KTd+*!7wmX)$Q9V%%{Vy3y?EJiyhi`;O;;Lo4XcM3xZzskY-2wq|yX;25 zIzl^DCVeUzET>)m6Dn9v;8qR2uT>GS5X$j$dtc>L>n3+u+MFxJ+2;|egp|8fUiU8w zgN1d9?RuE3U0G^ztwL_nzgse@Wdu<%$MPJ8D^|B%G$spvg0E?5*g6Ld{PxZJ>wS@xwc8@V^6zv*J{xBtzps?Bz+9wZ=nVy#eHuN~BY<5oO zc8^To9c3u7abP<>d(s5zai)gvbjxb8C`THdCAt)64-KpBJ(K9*FN(6yGn5@>;-4Q| z)JISI*4WqksYrPk6ZISxR6@rbFi7IJ7enrtD>U(1)veixChmgOX|@(M;^kWgElPM{ zr)(Qt#dTlqU&T5)*3&6xm`CXpIp1(s03VqZE8F*dT;_9+iqSJ4BekA(d?J7lW2-5cv z_aVAPkcPPK_@;*mD&(p$a$RMzW4PT9vKBDuv@`Kko*iwb70U_Ac_Z`Bj3KD*w zYS-^xW}ac4(IytvS{xGmwenYNu--*Djl@fr|GaClLOh~LgN>`*8xR1xXEsaEN(NXG*cuQ*Ee>97SGo*>Fl7b@QpNF{FrogeY$ZWUd@;3ZD%mt%7LVeifPulM*=Gozk zcH>W{E%TGB{%rq03jmb(>1e0E=JBs#d_=!mxBEAjNboJ;6PH~)8~NR`dt*WbMYs4gCRIV;~8AWv2P?+byon#s+L=_oN+oR3bKyJ5hE)k85oKy z7|?h%`6Aipuoji#&K7|Ay2V(f|q5aepi<7`O_* z{og0;d4*ss7rmNhDo*C`k3s~^Fo1B;qf%ire4>}9hB)r>9^M8}H1t3?>h-e$GhGNv zRFPK8wfs2xl_dA)1NN-wz??-oZL+TsDF@!dl|?>d3u17kgU&&?6W-C%Itf-MjJq@^ z%W}aNlTLVGvwYm~+W6^A>|7Oih5C+kMH zhinx;$=(qQf7!@}g}yDnEcz}zX4jqyA3*-6g>-MM=L%dI-T%WsP3(~f*Frz)QxHP? zsJ#6a0ar~fai`20O)<4Y4UP~3723DLXi#K0mwA@&{9t{b4G_^AZ0`sbOML}DZnejh zd=jinEzf`A3K((l$y95vf6CZ_K9s=P{3mhE3QwN-_`0$cGjwt47uqV>S z>_(~~*qW4K8_du%v^=4zoo+0UVkYs%HqnL>l+S&?RqZGW6 t9;v6KZN8@JxAFwI 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

QV4pbX{oc;5#y3@pzCm`2Gllg9ZO#Pofx1tzeP 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%0D{Y1*o;^!PMJ*T^J+}$zOqu7ZHQIqv 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%0DP&{_M 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

wHGBb(@H4)E${rm0w-yUjsicg z(rZkg&`L=e(M=3g0j4N}rvA0)#ZM%>Kol&3L9XmDn5WY$1j4K7h!KK&+iEZD9mFX~?{qEOQs*3#6SCmPbutm_6+uQO7Y)^uVGP$5s6cD5rGay0(M zsU;eeKT?dyi9hMe__(7Mn;6y~*3wB>UD5&nWPmr)j{e#7I|VjWQYuA4qp)&IOh!~y zO^^U7dNiHjU=;2E!gMrENn~j1UYO<7_BwvRQm#`)K^P)#^Q1T8EQm-)`yhiO_gXBk zzp{Elx1tT>O!lP_dxcu$bn>qP2X`OUP{egd>95mI$`glI9;nS)@JJfdE&fU2={s+%1pVSfdj?0Lb zIo7N2|8qF3S)O)=?G3k+J-1hqOV2ulFDzUF(j6>B`0UZciZbwpmZW3;v`BIDey>!49ark29E>Z400HkG zc+P#vi7zyyq587oVt$G7^F$-n)V-%f_niu7m1L4)MMg4!n47#;pcU^O1>QdtL(*e1 zi0eex?sa8qo^C~;maG*DR|)c`axNLjsv`2GW&&$Dt8!DQR7OFWM0tROQZv0NRFR`( zU;qsye6bUfV~AsXWk)2psuD1D)`0?OC;ADoY2afNiOG5G!YU~8u*O^j5HF44l|r=< z9$;g}p(L(r9XQt7$m66RvIvwPl5a>1OYvc|0VJt1e8NZT*(+p87rF{0`p=PbQ;%YI z547e!moy`F#ClDufCVZPtb<$c(W|L-$hA!Ic$@UnS0#!#%jfAUCba19%WAI66!LFV z=Ck{Vf zE0%K8StQBU8o&<|DpCmzwyu%yKd2;7D~*(W$vH+`+Svp@Rt$d8h+7&CnqEqEfil}d zB%7p6F;@inDG?<8rImG$LXWM&y>{BIh@%IbiYlf=VhrJ)umm#jt+={o_=4yV`YVc( zn6RgZQ`vG0G$egjP9g+t;L|Q#)K(W#sIWh2fCsnW5sDYfQ8mCaxJ%Rcj-NJcO4|;>qwix z;y#zL*%%@c^-a(+GVn0~{=T=y%3lHrbn-0CyxCv@5enIDPsHzS?Xt`^>}*L1+rO_5 znqxnBe>7QBo;*rSzFx<+W#04Ng=h4pT^hY22hzAnFgnh__uDljT~09SA{FRx3Z!#Y zR4=4s3=rwZS{|GdjV-Y1Q z6!2+0ZnfP3O=sW%AX0^+Z~r>ivR5q0UBg@2Qt~#$ys2}PDLF7Wkl1GSrA=NhzPmac z8W`+Yl#E^QGIq@?YT96*`I~3!>>Lh*u9CJW&!=K zOPGx%?dyt6KA&6>gvMd|o?jAvoR7ATRZpHjOY>$Oa(3LOA2bBRYmB_X^_M9vz5M{ii>rUbMf^^n_>Mve5w@Ls={>Uj26}#NCO#j z`sz~i5z_Tt+FDFahZ!FwB2YFZI%FRnXI|BZ63S-WL_FfQMaa+23U=(ONC2hTbHIAh zOI>mLxT3Yo#r8u1^(LrE3C*pgjG=MBatua~JACNbZw(y-lMm%9t;}Qcoo*9{qwI&E za&?z@54j5{5;0#U#NJa6x^?GLUZT|u(zi9_IaT@2eAIm6;+S}Ux(RH7s_%N7lDKp} z)SfftnNoQuI#-tnQTowM$h`oMUk}NCp#Tyn0nKH~qMtwmI%ZVEB+YrMBGn#BZkeId*-7xaEE>hT}iR8E0=8W_f2m86TditUI95>q$p^81S z+#)D~2e^{RKiI54%TmKZw<-a)v-rjp!#IcOq8u{%r~|v9_{x_t`ib-cF5rna3LFob z*DGTVwBikx$VmP_YLo73%S@phLbT#9#6fyS2Abgb)=8F(+$OyEhqymbp zWuMtgN8p{u3Csv2W`HS_E}IP?D}NmWOGWax3rq!`N)m|r!y3!Psc7-0X~vAtg~ zAHwjUdNzo%A`|F+moYXbucj!29l#3oN5Rn!e)$H(0tH+-%C2hc?iDw@f4l(D|7|7bcOBn24bYBlEl;NUlt&2gxd%n!$#t zJ66DA&pMciGPr#r;D$=u$-tu@yNqEE`*DZhx6Ras6tnrs5$VXOrxT-BJz@=r`1!Ah z^T7%hL6hl;y9OMaz)Z-j47yCq;KM3>Ue072fGT=56RQlWrX=#Y8yI~UiIzh=GsEnC z9s(beVC1@_6*WszLhOdEkj5pcLqCF+Hh=?7I~5+2J-`B(go)emp{^ zpubcrvQJIbThX# zry_hp(He(a7=lov&XvSlJ_)ug5xh{N12?KdNWict+XyU-OGzCk*cayoyN}sj-nxZwyKTIil&~CNRn)Rj}sjVtuhemJf3)$wUbpZiO94akSYV*AK?Kd z>s(dw`mrCH28 zZZ%2JxRpyJ)q9D3>{t~8I4Imq7-6?HM1QR@jL3R3&%NU^8+xyd_f_@irUlbp z6!N|Vx~+izlPq`9y(K;?{~UvPr@-awtC>IL|z|Soy`4z>7_44NU9UK0*}L1|pQH=gBO% zV?G=m!i~LqV36)FU_(kUG7lE}_>3z@x2ipftnSH8QopQ?3A#O*Wm>h>RUUEe!kuZN zlVGW`p0BgBnZ5;4y_e!Tq~t9TT58D%C3P%BLQpkSoSo2s4UvjGm_3xgxs0JmEAkT? z0u}STvVry2RLIF(@nJprQKN+0wn;<#-(Wot;FzQ=LZ%UY>ok+^xf|b;D@TdSojy#s zDr3=60qGnxls)QgPL@MZ;C@bA7gRKuI<_{jn`c@g>?(zpB4q`g+_hSCyy494-Ll_9911sX3M*yMS{bU<MZm#$%AIj z*aM+je>%=i30_nn<>KgDQ`(WXp~N%AwsFWyNwR9=NHnFBE{)~Y(l#}+uo@gphCem7 zN8Iyzu4Ita!NnMiK&C-viD~+qmUq{?AyqNlIo*~fi!RC`$cXtT zn(`H&X*0lF)tiA;wf58daHJ(>ACHbW%}Y~S;HE$U`iGv z5Ch_P085hh02uux?6j~O!8KNiNi5O|x`4UpeUf;Nk2s%Q>&XpHmgrW6JT8;tVm{*GNfAd|+;57adaMcMjnR$3yoLOP!x-_9eJYu2;GBIGlJ2I=j7#YU;}* zl|KQM#1a#__?cko=x$fE$cHyhg2^v%U&(XE7LseJ?;owhh(!fg0ytP+#XfgG3lQ^< z)ZG(gOp`ABVsFs2JdeK9fIDE~C0Is; zrNotct76<#xZ-?CMM6QBmgv3r3IF06Y3C${@iX1zF=}h3c)n`xI8Yn2;*Hia! z&8<@N6yRI?o*Z4GwQ^ifBmcFo@NBJd);R^&kbWNW8wS=ufI73;i^!X)FCrkykJTyZ z8P}qqx38UFyaom<6R_J^bZ*Da=pSxVZ-@YR3b4Zv18o)!v0QHMeSLXrH6i_k zcgnkH<~~sIc8#}(*Br~^k#W{|o#O;duC8<3m8Tw+iJJE)ur-Kui8H^7e{{WRpjfKs z#@&2s2Z@VzeY?JJ-FD9>pYhzfw>vFm{ErB=aDM1~l6zph?mD!iHwjs4?C1jB=U(IMl1xJLuV35 zY&JVJnn7m}IV3C$1*$!2(Ah*P7dDO3Ah0;>nhz>{*dlbAbg~C%q{$@~=@j4*bCg*s zfLI;ABX+7&01=v87Q6wD#$*ym?E*zukyIvi$Mm9=XRpApc>87}C;`0yA6fg&1OY?C z#B;XE?6xHn$y#5!NIU0c9h_iuml9Ke&GyXek%H(8H4qyj9 z`n&KqvM!v18H&+cH&LBv28UAOfG^${&qudKg3$9bI?QjmQlZA**q%P!|4)uY?&-T! z06R`V0MMW51G0m>2@<5}vCt9@xF7D?P@%7YD-46CNQ-2AvS1Si^S1}6sFLlMYoXace$vZPFrLvVlw>7%NW$nv>p5~Pg5s1i{Q zuIuxICBG~jQUNs#Y6Oh2=p49&vJTq(za{89J07)-5@OA=4XZZtvs5&_E`T%2GKZ*X zKoeBDbt)c^OH!2iHzI5tR{?-8;uB1OPMkjQqzAo$8KTXyaPrqseNM$JliB??JV>-q z&BrKx8v4;FeU^qmjkSP#qzW5ss~)bZSuh{A`Yk(FXpD3LBoe}hAI?!qNQ~FDltARk z%u>Vw#lQ_F0j!Bc-i)s6s#Oetb`S`ZQB7C`@+Fh4%J!npf&{WRQ2QQ|Ps*eXhB!*5 z9Ef5GM1NwXk^COWJtz`EdpWK04z^>6jIgN*vA+fZhHAoX~s!p7&g62RB5YOt; z#Ln|- z!-OAvI01EEI>i7*K)SyjG&YC@TO-8~=M{kdEr(3Ilr`q4579$o;1%LNweZoIi4RTf z#ioy@6un)8766Nx&>hBXb5+p|Y+wYtrZYZwAQDe{2oQ{`gx;srDz-k(Y{!)+{!U%P zm}2hQ2}0+jS`2%=YQ_|YxwkSzNdn?c>e=!DCjA4`St?cW1p_(O_J`rQiZSoZ(K^yZ zwV&t!Zz!ysGR0y))?#FFZ|)+JCs094A<=B*nm9ES@K(v2rT_)qs(>;w&W_puSf_?E zC#AIpOS&D2X~DUtbMCsyE9D=pC8Lt#Y=NT`LR0Z+D6!K!4V4rkMe#B4IMbq`U_&Hd z1flJ}=DMw70*x=uS$Cv%r6v+-(SoD#>l-45`M@j-m}^j~6NzIcq@-~u2z=c|7mPm9 z{DD12x@*anI&w^c5_M~$52n+0sF(|zZfe?RJ?82@h{Q=>2I_6MsWOAhWK|z+o#7Dj zr2I#-*a1!i{+1M0=HVRLmOu#ZD}+BGV_eZ9=n>L3QanJKGH*OG?m7~t0#1qD3M~h4 z%fMGwLt4D;AB*+ItU_3!Q0qQluBJ7`80ywv$t+on`JYAd5ltQ8ils79s4>;MP-MZ| zg{l=5rTN61UZL#_C@_1ExwL)Ra6VlKGga>SoQfq*IaU=XwCEMK~X%mT-z+438^hqwcEa`5q)>J=l9%G0%7O^|G zab?NcfgiA($T%o>Dx+Q5R?kJ=mwStTa@5YdIMTN2THlsxt`$(NhSU-)VgQ;p7`gX# zj=rJ>ya=|LNz0l>nPVQTl*${_mR)x(3kW&WZG>h(-sTfNh+I2;i?uhr zoawc)`?+&-+Pna1S0%CrHryqlT|3vkSN7GrP8KoHE@#;~GR&RgI+`G0b(m*kCq1CM ziy{!!yCc?N(W>=I?nNq}TJITTZHRU}bjSHXB$;7)C`v4`18p;ql0V&)Hi;s+%D)jz zZ;PlMn!-X4v7_`6>)IaRz4iS8b%Y9qWQmBiv*0Pf<@U#^qu}+_D#kjRx?V)qZ zmKw1Qtz?pZUO6^EwD%3$w3)7O@_`)FNh;XdPoC?0d7Qin`lHkusviz%U@K)-A}!^( zTP^u=Yg~!oTbH9%b^@)TQ2NfG7jeFYbCr=>60n2&px)VMuCjs`RhUl|&`uDc>V{3@ zMkwjwPzeVI;un%e+C>0oyje8$@oH{5-!Jsvk4&&il2k{czitvyf~dePfUoAdO9dJ> z$RhtPdS^zKBFy47=VbM39ska7(g{2gF=Ne*z-z$_t=VfCP{z zaCGWAWkMR#sPwVs0-M90_pl7H$_Al-tkDzp>hyYHi#-)!A zi%5-QY?4P*G)!)$i;8>0wo~qUQLTLl0$}4}fO#c+#15p9X3kR26qTqU0BAh#?5b*S zUW8^mHcdDH7 zL_DnWpf6~eW43+f@F8k`@k<=%rkFgY&L?7KG~Nk@1S~ z4vJSzVFqyw@eYX~s-Qfl)hBV5LPh$~t-C0WF((Nm%g5~*;qtz&#KhtLdZG?nuV|fa zEUF8~9psv)A|kUdA0MIq!ATz*=w9mQG_eHwHcGNZDSkud=9ChBA}oa!Zs0yc@|sVe zwI_OkXW00II{6aq8)<$}uAHDFAi4?8!~#1N&-{#o?2ysk)dNKj(cH@mqW+KMnP~7Y z&cOH#bY)HM)@qy|k*=9>jzBFeuVhlw&}SCuipk&#BdPfkq<*O8y4|8CLh{^a%GRRr z4sKDp{$lnlf>g`K7$Yx8h!ecYCZ<5qw(&`^md|0h^|HHQ0sD$~^UMi3@&S=*M z%(91K$pY+Z@+O>u#X82OnH9=dHc?#Ha7=nG*$nFv-o$R8b5v7CAhFE`jEfv9LFq?^ zbdoKAN(l_SYE0(|Pylj(e(5ODrmWCq3cSgu6OtSN&xqlwkTgh6^hd;6E)q$xdVEGV zI_zgi&GxDj;!|c+n=?{~Q!?bEKK~Q2d(KeEh@zp7!4HVbI1kpN@Tx4V(8>g`Z6UC` zBcd~;9*1ulq2+GIr|{5nY(;dDG*B5D4-`9xe*5uKU8;iC3mqDVmlp?SYGVfHs=Gc; zj7{Z?_pM<1?FQ}Y;xn`uo-Qyl1Eyz0py#Q6?MVX6(sLuF+>H3dT+D&n}VBIk0Ij3hTtXnxT+(s3I4<;>+Oys;9dyEdSJ zaD`&A^xHINO?38|;?QA;PJ$;_SmPk41aEOEQ2>M>dV_PF0gDKF*I7WFJi%Z1^!9i1Moz&w6hzBuJ>+Fvkvcrs&X<9v3))iJfNgF^Xg; zI|Dx5<3C$%yd+Mygd>}Px8|zm{YuNvNCqK%DJY)Nsa}w-wZza_0xYMh*t>TMgy>6Z zitL^;iHJg7kZv3yp{bIR%Z|poDiy>4u|OXyQe4KRL%5t(IAY`0S|dUPXIEIn1_F-* zI?{M{0JV2{>k$8fEnfm6ZIA$1X1Yojb&7DIAPJl|NZ$RAu0ilzNbaeY!(={}=0MRd zyC`LD7e=}{YT?wRf+t2Rs^fabA!v59kyx>TlK>woQ9BRw6oM>Ut{RwwbT0YVNXXvV zuOKdU*oX=F3^6DqN&G{zqY-B&E{*v^b=RADG@i&XZ^W>l%1sFF!+qmqYEnNwIDwFe zI`*OWm%{E|R7I5R5V0^jP{ruWGTm}{I6SHS2B}|cklzWB0b*7yAQ?#ZBXv4z?A7QC zA{dyU=O`s{a{E*MKO{RE#@XI)bQ2>G-X1(|?y^5orQ#li1>ye|KZ@ylq(& z;0~TA8ui8Ti+}`62l1rmv1m0xYwMJXFX%qkCyr~CWf*0q2=S>G`JM^ntl$pkr$KB~ zX$FclKINGHkHY<<6_gZ}ptA&jd0Qu23}zH%jWS^&=W!IRjd%?_J7)5+$@p@eF)xytdGX`iE zQp=N^+B;U{Zs2LK^3xHMI3I@wRHSYZ7xL`9Vx-b3EW0q|e{fAhA`-Vl2f;}xxUojx zgQB0@JBpYExA-%Wt@&?K1RwR_Cv+KP7xbVlcZBjkGWP_0a56LME0>b77;;}j?Faxl zawS%LA^63Z+a6$0t$TIq0QNB;2qItlK<^ky2Z-@ZnYD}1!#HFD@|j3e@y>^Ayh2sN z!d#M^Q{w>+!wtI_suVmfa*%dV;50(-sg8Gpf$rRJu9!G;`0r+hF zV#2AXt9Wj2+@UPwKMaQjZd+vcO9p*edwPAyiVlXIWFNYI)=9q zOGMDq*D!!&e|{5xNXs2v*vGa;*jz^iXuDI;LP6eW7ErhdZ(=*+5;iK8Pp(7sVO+E% z0;C>(%6*YipWAgF2;O0g{fo!F&Frl$HjhJ0vVHE^oHaycRXvs?o5b9sK4^YnY-JFm zUEUG_%gc=Di?2>L?T5Lh^|XzQOx#JZrp8>{kF&W_SG2ArA2&CHNI6w|@HGu)Al4u+fNjd;d0C5Tw zMim{40VOgR>_Sr=qD3K78B9U%S)xY3@JHQ3KKijr-_p=5#z+XYSEe*e6Y3dTjlM3D zne;-a0lPq^keWpLV;!f`Vb#d&l1V%RM`biiWFnUgl2q@P*+hP0OOjWyP|Ean8GD}q zFSAM{rc;*zOk^<&P$EN1z~G;{82v82OOVZ`mipzJ4;Z}I09rh*mTAV(<7X21d@ql0 zuL8Q08ur))l+sP-5=xZgC5XgVC^z5?lH=WsM{L<1J$gf`q+X-+N+y<5bbM+*n@y?& zsG-ezQn$E?i=2MI=u+Of0P8|B`LKuT-o-&c3Oe#54Jyc@A_)ub$Rf+a1fwsj%Xt4i z&e{HwERl31$+@aS_~N+nI!w?YQ3P2C04~#%q9lgYhKL@rGR(6g4!U-p9@27`B%w#l zriLuf3H;WfOj6#Mfim*E`6{SGhJ`jLyTsC|aOxECAIh>mh$xSWFx?>2Bk3BVZ!40M zM5&Yj`o>6-Op+upvqZ)}ks2oAI4LRwx4D2Dtbze_v-K*avv>rON@vW}@E}smeL+yD zGuW3QO%#-ZLe5gmp))f%{K3#*1z^iZubLQ+tJTE*08B4D=~>0IVlf&`ubqqlzf$lL z`Ox!(%R3-Vk`YfRN?ZbuKa})$Z>JWy{>?|Sy_m%$i!;31A&#IGBOa>rUe+MUgri}f z%JNdhQ_A(8u1fc*`Et$3LseM-l`;6M!cE!)vqy8g0KT}4`W(e8$|9F=fOBo60Yy~x z-yp+F>C=j(|3CpbP#f5&**EUcdGeb|2AvYc`N zB`V`)!9z5DT7x8(ess6p>RR5PEz1ZW1x6rxelq!EVSJ4%JJq04=ru4P~gU!u#C z8;-6{NH)`2p_!eK%pn?LQ#7K<4btIIj}x$z+;sAnqO8eEyske|wE<37Sz}7O%t@vT zGXRa}Mw4)lGRbC5U=B2#We3xVv``=)D2^e^O4PU1ag8SKD6*8Nd!@6wV1z8)U03B=9bD#@L=69;({XN^5Txqo7=$=9ERy(~JnEo_e zAD7{3^y6_N%{oAXXI!~MJxsj(mqZ4%(7QPxi*5rS<$g&UdR}$THCent{@PYzcTK8D zhBD)@`(JzRCXJ;(A!Qc!NV5WG?M>&g=5Ws2BR)row0}V$2zq+nGxQ8Mcqr)Js(HTy}sR>7nJS(_9Ndb+LU>mO_0N z($PF@Z+wI;#CB22L+U#PaC%2GM6=X#%X3kvf07rC0!XuVj0f4KH|1tm5^(lYAQg@! zHY9SJlDlqCd89!@RAY!C3~BGZ!;{hkqnImcSBE`(x~J~*iE6!7uAP<^M>w^S6s7>B zIEO3uj=@n;LTQX3^D9*%N6mZlITHmFC({1rCIJ6}kN#OL*r3;k)Cx5UZOM>GDJEmt z2Rze3W=9E#0Abk{dS$)ck=W8w&zsX?k_CN{^?fPGaZ5kOD5k|F1m8(H;+hVTV8@r5 zzF&!|KSZU;Mfv)o(ff*J=veJTc>m#KEv|&v8SW>kW$VWux6&dMva$QkPf&N4cROE-`wkMm& zp4?(ouK+C-p;ZMaKGCGhCM9vu)pJCi=CaH*uc@Pvh2sj<9Yjyjrk0XIxi+e-x|tG* zRkFyDPpgw_g(>Y#$pc3Es`+D$Lw^FS#3^=13-##HYk!2`Eu;ZSy}Tr3q$FjH_3_&yK4{vQq*W; z>}sm1z!DNW@$zfb#I7lkeNRom00XeGo3T~O-kpjsC{Rni!_>fi(!4zVDedx&I2yB) zh|^o+!}Y8x3Urs3xK1mLXvOH#!Ze2qf`~%%EmW4|N2c_{6jZBampdaGxYrldZF_2! z8B~B3Ms}DiC7%u%s<^i?ZLgheu{e%SPqF-pnqeI?u3|?LW(QU2>_MVzRHR8^_LbC< ztj@AVZoTRCP$uofWC`rg5L+1RBRB@gL+bTJ@|J#{sQtjqB^6lnXz}W30yXEtdH_4+ zJ+A%;$&A9}kICdm&(>f&SiXyzSTcVwrG8iojtUW4pO{!qegGC~7&JI{lB7%y$a-Xg zO)>Y8W%&x7pcOruqj?49?N=;?c}oDeu14cZ%u!^9EJy5p3{&(pQ^ttAouWgytE4U> zCi0cxf%j_ZWz5i0J{N#GQsxbL{>HBOh@JYfa<_>Mo%v{yUMB>`3E^B*&1C9H?syO=fjsHC!pbc4B$tyurID9-+rX38Jn# z-!rMW{xMp~+aud|9-pN8jHmG4GU@U_r&-yFHN8#bS3}C|M-frc_b~uz!dRiEyd*Ck zL&<_RqB4b)z%{03MK%ha_=BF=haZrDZ1HgNLblN)XpNYu4yOBZwy0IEl4iOqV#ndU z)(;-u>&%sD9h2l}1?M~J3y8VM#SS0iXUQ~*ds84l`+w(Px37&@&=aWqBecwdD6@%h zD~1_qj;abvci&kD;>@k;)|KnW=%c7of_hjb`0I&};cBYaC(^K2a1 zT)DuW9a2N96ZxOxzp#RfCKv!8kqI~BIu_ySp`*080xCWrpNH96p<9l*NCGPXm%1qx znnRN~DWr+xJRkV~F5>?mc|STiQHhI}wSgC-(wu-H(6`|Ew9@*B*oGuH*NZa;t@w|@ zfR?GDskNf-9yo|4>rkXhx2wRj6!VU_>N$s_@2Y`GBjf}!+Jv%dxvnUKGfOD36d$vT z)2qsFj?qst#0$El4z^qYiQ-`&p>d~6y@;CfE_xHQVfUL!)gMz6A%KvVd#t?~&oO*2 zxtvKWlr%oFFScvXIBR*G+ifedsyUJCPMlv}ll3Azf) zzL8tixDqMGY+9N69=_7U6! zjYI4Q`28LT0-ii%p-HJMLi7M6UVswy7JC1zhyt=yhMIyWwsN^h`Xsnu0u16b3lUL@ zS)~WcgCEOozm$h1N(!JeTfy7(HuF$AIx5B5<_^NEurcp9yltZDwYDT1!hBB_qf$8# zj7Xs0MQZt-xC9=XWSw%?G%1!K!JsO-NiT$#!itqjX{oaE3PD6II0_n`gg(AVu*lek z7R({Hn!ySaHyP`1%R_WJak(o&jJpz0MvFDa zT4j*KuE6Nh#n7KByYCy6tSNMX4nuQ66se67w|!%BCoZsRUuVKmmrV>WJ)& zk27k3I%U9GW=#~jp=uf?TuqzYSs|R^wGn)(6EHnAeqb8w z53%|@gO12TydR0QsF1-r0OO4*yvta@MIleMsr5ghp3&2H#+cF28GB9&LCvbtp=jkg zIf(30lj8j!1!5*S}13++xRq%f{ znWB|&lS3GyREPsM+dR!3S4jdEwL&Dkgm|+m%u&SW!V7M?J0hg5CzNq##l-Ha8yj*4vH3u7#@vuxLLm59E4O-Wr(Y@r-Qq&5`q&rk;mP~V_xp_xT(R`bOt0NLF z!IBiiJ1MCJ5V(sOv(l>?0f5GQkf8&Y(d^Hp3301Ld!iJ7*MN_{<0>NQagecr%1Tk0 z>FaH{!fVP~H}NPAv76OXV}NTt_TLd@>r9qe)-EEu|}5 z{75}66|}Q}6A?tbo7fQ7v>U0bM0XZ@dXZbtwh(=;G{8c*x-k*mn@QuCN!Kbu!8L;6 zEBkp9!1k|jecLoIwU~uRAbXO!CzJFEGodS~SxvbuGLPe*vs--DxWqF7s8cem!h}wh zu^rdUrN>#-Mlz*Ct)??t8ZcRWu-w|$)vR1S2t^X164F^CiM3Bi05YzaVl*GY=8-L;p9MdSb&(IadATeP{TeEia7?aRb$ z-S~@19qOX|G2VkRD!Qe&nv9T(UDLwfWqc$O_8BAc@&G`{WN5zDYIBB>WDf zX%&qWixiVZs)8d7MA^|a3?K}~L2J_5L@eI~5Fv_w#eME7#O4#hE4+gFM03!Nq=p>v z)h&~GsuCyQHVL4jqAI=g#dP^J`~HHB!D+_)YCIiY}|7kj$PkqF>pek3{VUxGlUJun%214MC6rc_9w z+C$vD9SNmxn^KY30KAR|N=$`n+%7BH_B^m0 zr8iCPmI0^_@hgq{{F6dp&0Q8UJ(()aoXKF~SqKJY6MXgS*KLw#d8&Cmm!DqSG78*|lZh#^RVkCq$fb3Kb9SF7RIavA1fCIdy zah@#u3wvZF-?hhTq%AqV3XPqu#(_tF{^i z%T9?wsNy78e6e*2&6)mz_&>wo*m7SwRaRfFv0GEr=S~-xo66lA;327mjW77}hieT3Z#8tZf zHTy1BtKFY`9Gt=xy>Ov0G3`JNoq#L?hnVeVX>wl_9o_^k*#k}7Z8kh(v_*4gnglPR zo~_>jq+Ge_-k}|cg+93TDmoRtHT68?e8}q}q-Zi2UIAuY3OyQNu4!6&>WHKj{&KM7 z+H4sGixGs(r~|T6>Qlr=kKsMzy_@4;kGEPDomK*xT~l5ILq@rCJMrDsRTW^JdtU<{ zxQY;9w9H9*exjSmMWj>4faC2X%EH)sR;ku%lWd0Te&Tc)dSw2rhp*KH&TIa~=yfSPUIaM&BWUYNQUei9p)waJ^c zzYT#_=I;p(dE`873G|Cj>kk$F89}cHXFcJalrGfhei7ZcOUnaW1BdAXe%(H5mIPX1 z`~8*#6<)d+mU~?{)NWW4SPguO6WSY1M&nlrmujumhvl;hA!oN`4sVvyI`vo2F1aD! z7_yFsyAzYDE2YnwU=4lFlfv95O1Qd}I{-8^(b?QxlFU-Md>n}+C67sz@qBbZu5;ES zSQv;8((aDHrI(7(%8Hih;wK;r&Im(g8{=6bF58$K52zhj8H zJUsLbnd8`>Zki2olVYMyj?zGnq&lJsB15mSkY^-1-yMdB(M`|Y+*1l}=HPx;9}11( z>75L2s)exd^$@%U&eF`fXA^xCTrtYDLn*BY#?Zl0&5rCAg~43a&&7pFe#xMt5UD#S zFlLzHo-MNhojz5EPMjreB{{&cKP$kz*|WV0M{dot&_n3#DkbS_4_VK&{5H=?ua>Z3 zg4~Q3kt;#t+m4~z2(CHk><5ph;>jlff2oxB9H_f!H=914>&QdMyu$4NE0casmD8Z@ z+gOZ0D?+=c1oSqJC3sPmBALTL>?evvFGb zEF>F?dv_Wr(R_5g);}0K2*k@6vOIz!4MhBV0R%G#eBC^7*-1Q#7J0MA-*sL_&wHLv zMS|e$+WSlv=Pvk^hts&~be}S++gJkLqeuDRvyU`gIRM&noadFP(8n0HJ*u@1G|>{e zq-gAf3pd4!Qr_pt+GWv6MmMq>mFq;!9=o}&vBrU~C-4-?}uR102jmh{G2gP&Ot zuJ=@DGi0xlWxSAV{GbDM+RPZ}fB@h?2qXRs0)#=I@JM7f8~^~sU(q+*R1X)4zn_t~ zhz2(N14v*%I8=5!3WNZkQm`zxKOL06W>SbmGD{MP%%8H5B=$2j0ZF1T$jo*>8Hd9t zKp9MqPYQ!lV3C?c5>r=%PN!9X6)GbNk;>mukWH2<`;OM8K&xbqV=}Q#z%Z$374LV8 zf#Fe@{kjuTsl?_N_x%=d{-w%n@(?tB`vS&Tt?$_$GJgh+!|!oACJL7ky3V8bSo|t= zWdy!8Qas&Kn*x;TpBG#h3eP%`-Eh>&tQMh#&Q+lk&?J6St+U;4JczA=1($!`ZBN+G zN@oVvL9yUCWa{el@B;i&l%!zG10wv4pUwL$msDuTt|$AJb(f| z<+tjn0InY}N=Ec54uU%3KIkL1og=U_eucP7ycqK^5IPXlLx=maf2U2`@Z-HL^e~3L zu!sQGw`j~lj=^jR{RTql%NW|Dt2+Fypo>amsX$2LBFLp|szSS-?K;egvTzC%ytYbm zgzlzIqK5LONXo>9Er3$a`96q528+U~q5UzZtukcdByLJu3pelzmgq{2TVUSB@ah!C zHd9Ib#y@BBQyEBTbZFF7=JwS3L{FiuU7S~&;#12GHcBy%T?$)vo6@GgVf=t(^5w-C2%BH z0Kt+G`r<*3BIuVRZ+kr60948v;wko2i44t5LL*TCc7gQ@SM^Ouu3V_fxaqfdq_zMz zc6);&pOO4s9N9Ej2AwSF<(hvui#i^W+?D;^DWdC=6+pBJYL_f0Y$OpU-Vt7t+;p`cl6(1_p1^6I#t*^Kyy z=+&BAU7(8!R@%A19jzibh|JgmCri$KlCEsS*@orSHGQ(wuw-2PZWL1vm#A8?^^?%{ zPF9}l_FY(s(re2`mZWlJu&G566-JHVhx1V%acAVDgIigCC0M$a{2{gN)8qdjR0~uM zc41orWrW!0>N>_!cj^U}I`#)VjnIhNGok=VtU3`kPPVHSD+Mj|5bgw_rKE{HNkHm+6sb*tWF~H4bdt} zN2&k|t~qORzQM7xK$X!_q#=*=pcW;{ZB&8tYCsjl ztHsh|8IndM$Qc6}q&jp80+mv*neaRmRFBmA_;P5i6Q{-MMA(P{EKJOvk@6T`Pzvw> zBXS}gH}s}}3#eQtXpO625R(jKM*t69leENE-bm7qG6aQjLbPl{6N_n1>8WzR*6fan z5d&Q7@qeOKzPVT$DNm#R3%61Q3y6ZhZ(tCHKEz!ZN7;;bNc4)3$OPkH+&&>LD*<2+Rp=N=c7O zHosUE6rp0`kPvi-O6SodT*^0ih`6#AQ)IZBSu2p|VmrA)dZf<~3rUbok`MI?^;$#c zcyZ0ozjsbWSTdD=s!XM#IF%R(5n^``&0aP3Mga|Mx*$c?NFuh9;#W-ZAjRcjAeMGP zltUc=PHoUnCWg_TnMrrR1~rhhClN`-hO)Ux{7#=ptjQ3FoIPuS zd1)r@h&o!SR44%xfK#n~oeBchSh2rEb$rH=xMX4DD+_>B29ndZRU{ngSAPfLuC4QK z^J$|rh)u)*Dfka(XPGUthEBgN`4oqp1*2_tJ}bj#;7Z_97gO<$h|A{v4*((1Zw{v0 zBBboT)DtIb&7}CSwZ#yOWe9hyWCD^pKVe7XXdiVEtt<2c7AHiYbR+fa)MnK{$?V%C zX32IcL%d5%%p_SgCY!CW3q&FL7qjc$%uvFmH>L@-X2z-q`b?A_-Tmvazdf-P9$o5}rw*Dw3xRgrr zdXV~hkJvLGi5S>^E|h+&TASHHn6Bm`Gqy%&rFnfY(c)x^sJke%nv>B<>(jKO9-md` z0JIvo4^=+%*b7Czxh-j~vU`eXi&?)B2++G4ngEFW7D)KvK0;EYx>~`%U5U8qyj6?< znLMaNI&(4-Vz}_G0f)iaIuEJv(n{O&9M}*qJ!Q(c_F-=|ubIvf@WfAEUO|=lJichQTYW&kcCo zEruV9*y;idMr)WOuJ+AhQhSLOBCPuN@hZ3uYC4BihQ zkD|`Q2gHpkE+54{+{8M7LQ@4oBKQIxiOfDD4`lbI3}%Zcny*}!CoFA)YGa2#20{d~ z=oX+ZNO>zj*R4dg4OZ#Jz)(x1jQ|Y;0>rTGO%RT9qERN-Y9P9XX$T8Aj}TxXs~Ff! z6$=DxP^*dniqKVS@P!Os0Bz<$jJ|nooRiM(0E@{2OYq1?j}ET9yJ)PfP&#%e+{(t* zj}8g|rZ8=Y1lK~iQm7n_afZuh)K`okC!|Lh&JI`WPG2IhWaL8-XF&*}8fh)A=rNxZ zkt~LBxV15|$Vzlj=2>sZu0OXxlTG@MEE*t&mt|nd<)>`QaJHU7Jtj)0P-9kgQo0G3;+?B zZEbWT<4{SBO7)BCk7-gtDKd0L{=zBVy~BwPPyZTb8hfooSCPL@Jb(Q2uO(j-Qo0KP^&J@St>6q z*Kjb3L;_H+%Nk9zb5X4KNu3~UVH508GmWzPN}T_qs3~ibo9aZu%4Ya6Q1wULtFA`f zOWxQmK{3M;wahOuQrQtg6)-~XiNYBw!pj6H0Q@jR1c;_0twLXGvWZA$&rKwOhrRHW&kOUH}hIr>8d3zSTWC>C{L$bF$Jqd0S zvMUJK^Xl&_P{>1S#3E$^5;}tt$XAFHEsZ#H@KBuS5MxrJyCvNi=oEi#RQiPY-g4&f zCWzTE2SS2O(~e4g3qF_6qX!6PE0D_=Ej*sn=2aeW!%#ct|fV%=6MC}Nd#*%9%bX~}1p=+{Q$*zu1!ix|vP_O4VG-7M>)Rkf? zHbe0}Z2)Jmz?Xs=%CM}~&bc9UV;JtQ6b1gcRA`A5YS57m#H}duLGwjS!vV_-b}HpG zZ9ZKisv_&CiULCsL-0f}b|FH4nb>!T(gOk|@LG zRVC74=7|fz_Gv8Af6YlZ18W&0>k#MY^>V2@1M67~cJ(DyB5u12^Hoe>4mMDTTkELO zB$Bi;8bwKbha-1O)ySkI13EH@&4tcq1Kl-3fXo02It?0uZ-|?1%BIfTqAEEateFC% zK2z;h%aCY|mK^^{OfZ43W3MX6D!`L+PCp5JyTVer=>TJlO(?Q=GwpiIhjB8i=}?Ai zP;nI_=;svj)W~L)C25HB&dgtHCPO887BztG2&7g8)NO`NQfCInLOS=W+{jG{TD1HC z%;i~$JmuzvER&rU@p^sK>Q|L)>Q(4-3Eqg*!IFGq@O zLC;hp;2U%#nNJVvFw>+)#l-0};|j~A4{so6Fb-{s=+Vm%@CiC=Gb%xn=17*_0Bdns zOHVGhJqqqXDhaw$^7}Az#!_#?Vuij^u&C3L>s_J!IRFxK;?Fq333-RkJ8IUM6$ZO0 zDDaiY{h$R!av>{|y5mQIMUJ^_N@;6X5m7GRH}|zU_VzHAhY+-d8n7CrvLA4f9$7PO z8M01j0%TfC-BOUa01{L#!tSioLpgHEc&4*9E~yAr;sACI<`dRCc1d~S5W>;lPhtrG zs%Z_7jKKB7U?KMOQkQ)~wFmIvAJjxuD*}85IPDUqFIZf?R+kac#Y(P%iv~n~wGRw# zY@))Jx=ZnBF52YO^6iO4cT(nnQDDXoO?@age9Vx)7gaa*&5DvoLdE|DxA35r{@ju} zvzDJqkSt{EuqarLD^ZB+tB+F{DnqFz@4{B$Fz6tNopI(WH}0tj^WeU+3sMY!ERuR` zher;?{co#K@XoPD7REy;6xi(m&PQ>0b`2$UDlM4^0OC4Qb*d~#omR2hwT8AJgu*-l z=u#wI zR1#AkVfZgA8)5BSL$7+jrpYmfQ*A&H+qh)$lSHQS>hgjNy@>W-4*5J}M)$RpIE7IQ zb02G`a>RvKn^*L9E_hrQotbZhyO2!AYXs0r1!?9kHA;lxE`Z6@*vYmZ9TkHz2yQBs zky;D1Z?3->u<}L3Lg8|RwGR)9X}6#*>}jV7EQJM9>IGr9#h>Z~8~SY4a6oG1esMI_ z3*z$ZYD90=2DH+#qmvdP#UprU6FTqJhQt$K=f6BdiG0-S09q*)XYpVSc%l(bA?|;q z2f*0l^do7airIj>Rr2^12iRL5d92mpOF#5a~9|ydQQ!BYm?_NKN&3%8K8qgu|p`3Pa*KPS(7yhek`RY zgE@61Mb67OSEgu2z>+?2}E@ z2XLuoEVuSr)m<^52cGr{jF(q`6U0f30Qjo0hb!FqY6O3$Kd(pGSwm$}bbP+0q}N28 z0W9Qw%V#PsPi4X$rZ(*O4$7v;_S2QCdQ>I=t4{a^PVjf{TQ}RWGvjL|eN%Ofri2Z$>uDVNMYq~nRhkdU!S>S7 zp69cDx@q6XmO_)tsfd$he`~0n>#~}5H8Rt2R7rZk@TphvuSOBx$*g(JlW`+V)iMZj zGHmNJm}mg?6vb5TXoX0j=VLD@8bhfD)iY&c$u^J`s?@uZZ7IZ>x`oaMkkSQLt5CAt zuGw#g{f%cZppg~OLa5J0Qc`WKb~aNTxVLLaoKmQ*0}`MBHddO?()q3M=sQXxyAz;! zIlsBX%=HwoliqzAwM5zUy^a$2&h>$~$C`M^T=q)A=d!GX5}GJjn{#4NMcjDt&zwg< zw*@|9nb1e?U8`4iAdpeQb&AO%Qunl7T#VzF4Y=gE&;k2*rFiKO zz(jR>GlGB{9#Fq~KK*RDIoAJyf%RAU)?sUJfHVYaC;F$Vg*vCRTBUiN$sl0U&Y&oi z!a5?%dou?vxU<+BJoh-e^Up45Kg%W9P+k#$`F0=I6H|DVEc`Pn-8PQN zv$2Ih0Dx9;b0POC{*-N+#Zd?S0}O?%Z|$Kl#z5U-wDWsecTbsEha@|^w5P&F#F$kr zv&mw$>tgdJLMECnIpe<34@?6s$37vMRzWf2*9t%|Rc%38_8(Z` z1+8E0karI_X)DP{&6` z0Q;-!Ok1>C#iToTNUE94AP?vh{s{$xK>+ZVlO8S??=?{w(ZmA%qMCJ5o%)XxlrAR1onqVU7MxDtdu_zR5 z4SkHuVU)`}3atN~#Orhi{7#uIhhb>b7tC%aeFH~daXK|Fbt1;us}*|Gf(sd>T&Fo{ zWP?{##DJi`}yGYIeA(?7M3Uio-QZG;F&Cn63rs%6V2t2ltWdP)- zdU%BnnQs90Wbj>_G8doRXkYT()w)Gpw)$ic?sYP|FQjCkviaO*H}#aLND}UYp~%7@ zjjTt*x{)jB6Nv{fOZq^Uy385<1prA~%LlMc`i%|2@7f}QGRM41jk(CnESiX%S8x-yota^`+AfmE3*{Rq7Jk2l(6s&gn}s%DekVgNji4SDU7oK@*j!# z1qUmuTmIK5$$KdVr!D);=Pf8CcN(Lts`{+63EXK6OmO>e534dlD4!%!vG@ef@nVA4 zJ@9g}>@&|p%@(~0YF^qPh}+(bpsn+4-?hndQl>K#!hcD{3B#2iReCB^P zpbX(`U{-!N~A@0;%zxnuDWA3fY<_t!oUaeyJ=G(9o7Np5*hn> zANGR;`=&8Gb8I1tB2wcabyP&7$KV{g0jw3%NlxSY%kh42~Vi&>`h0(}edw(IQO?ZReyJe|>D>!l&9$1cIx{30q=BrFKyxJ^z^l00e zJG5Dg1vMwTd^MD(@$d+_u8>7B3c=OL3E0oQ<6SvZ6y+d`pjY4mhe$nU71g!$`V8}S z$;#0nAei2y(N0>fQ0^x%rv>b$&o%3I-1NQsq)ERUExKDTeoM3fpI*$&efcfzeyNO; zwZ5eKcbe-ZB06)4+G06OZR3zbj@nqufkXs}l9e~6)ft-2N`>_%wO9Ve3Ni=*kaYwV z!b)nKIVnOfk$9>oNJI&0Pff~!ViA>q_P_$bBP$G_sHMn9h_YNF2QiYhHxzY`;TAGS z$*~gmskv<;F$6@^c+QUt zsDA1(ew>zS03q|>Tv4olzcyF^N<%L~=C%@@RZsvU!2V~8RNKAy3mD1#NOhy}dYbe) zyw^h-A_XDwxC5Oe68iE?%xv|LVp_shaXCh8bOAVpilCt~08>D$znB0gIiav6LSo{o z?p)|Ol>jq#*d*H8m4|$hE;dah$fApNQRZSjBqm1LdORzuE)Wl<8byd>5H!#zm&*kD zcmN{dm{Srh7^0aaiLqI7a!O=52{^Z;EWm`W0jSG)3j0VDHULJxCZ6X6L08MsPLEXY zCU_$Vhv6B3==8kGq^evenI(~r?k7n~9W}`6MgVg#l90uLLr+l_d9f*vkppuyopFVH zX=Ydy$l|Pu8#8IM@&+$e>mgNH6lfEj4a?bTv`9PoSn1JBPDdX1iAzeAOaVD5)Lnnv^K>Jqf0|&Z_Ki_9?q=q6onqx z=Gtr^L^+(;clh9x-4n65HE$v3xn)&jZI!i1+p_e)N@9w6wS(x1oWmsS&S?;KHLQ-P zQV3*;mnnRJ~aXivol&f+(J^%%Xi>z(>xA-E-WxQfK)@}f?$|}YR8~Qi&vf#0~i7^ke z7nUpl=_=C-=Np9ev{n?JQbg$Z==l9vt0;>oMtVQ4NzBQHu`AwZ5~wMfF@>Z4?oqG{ zk7Q5+!{o+MKKmrihughrl+4Rj?5u0D-YXyXg*h;c>sjK7=BJmArk>JEoE+;bB8XlA zyg3q1^sAu&t^#gMqB^5jG8M6vey!W({S;zN3Mh{GnaxlMrELtpU(BJ!T;wYpY}8_$ zvjq#&7Cl`S^X0A0A~=%Va6azi>r)R_mS?6xfC-Kxb$8OilVh0mW=11QFB&6H* z^(fZMKtkkC9U=IC3$VV%rPbC|p?QE&pLBEp4OP>s-49EoD#X%+$7qmU!p8GQC4x8P z0nbU0b25JgF~lXWkH$*XTvXn;vFCu{iwRB7i3w>7A}@d@fd84A4!Xn%z#95bOSyq` z>Ah8e8G?{yitIFFcuPi*y$xA)p^{h&5Tg`}UeXN4*)EQAmDp$*$lzh#=X4e-Yf`HT zcbxe&R=%U*j!&`)8pL-xMa_Cea0WxVigl z$RGgOeJZ%_rko*D=U-&4J@5t=L_E&QUt3cIkVay}MAiWA$heR*-aeaRiwLnS(zv+cY^Tx2oDht-bB84mcrsMd*!u7Pc0SMc;I8+M3`nM=6%B4C$ zx5MP73immp*eIc55JF@r!_<&Cu`wJXr0MF1>qZS7?gfKP8 zn7&(Iy_1PJIoE&?%sJCHh)WoVOXMC90>nU@6YEVqWDCL3IXz0`zMHcMWLJxz*1b9) zqzMpw| zyMmSkb{5K1N0eTVdeV|uleNK+4|!Om3Yo@m@)!a5n-c$vsp=$ZIjxL)CELuN0FJ-$ zsGI_UM=0VnN}LL-h`JcjA~P}-VOdF&&n6l+3andz3>*x5S`KuWCMpIX%w{YS2&8;W zHS{W~v=P6uJs?zXu)>@Z$b!jug^EHkFQDY1ddxT6eLMlBsH0p$VjjmK*FeylCIYnx zvu--GRjJbPM$)V+bEK=gcpA_F5JD)DYHdE~e;S05#F93o5RRo0#~_fH35cc`pqz}8 zQ;4vaw1J{NR0^M3T#q9wl7Za`DsD!}a}DCzOOq-q!k3&Ay-exkNJ+wgI>ZbqkUSh^ ziW%f0iyz1dI-FD`I4Q21EWbK)LBztvmoZEV>14g+8L+9DG~_@$M5zfBJ3XSao8gc| z3a%PK*q_tm$H@ndNUXy1ya?Ld01H38XxlFgaG4w36Qi&jAp{Xi;J`4Fw^Y2jnJk(@ z$cg(#P1+nTQoR`xM^GeuN?UX)7?P$$RJb6lvUIM!qFc?RP`m?x7{vg*fwMQH1ej7L zuF2$(sA|y|%R0(oNt|2<*n+=^$q1VTovVTskseD5FdigzNKF|S!oDDE9#2!uOgcf( zIM>S43?hv2N%@Eskn>77p{NrDl5B*sx%ZBt}>#*KTillyjvAm%+H_9hC3sw5IeISI%d#*n+Yz~gm^5i7a@ zRwmS5p6eI6B#A{eH%_#Y3!w_OaD}KXG{h1cjwA{zbs{DTCa(-4%~C>%d;kdJ1GqvW z8o-M~b5bIDJ2M%GD51VeA#=4P(?c}7H#(ZD$yUnq-X%qKu5jxL?OT9KSS2Y5(wslk zVb@ojEWHGoNPRXaIDgSB#f#}Y(hO2R;IT|)cQ>fpHN|2ow3Ss<3_y~vCi}&(Ad-$) zx-}4(yz1Z4f#W)4qNHg&u^BwlNdq1hz{{$ zS|U%c45uYs6id^RKP8V7T;i*AypqjH4ZPi?M1C*yxi2;%|eP%Htq|NzRLDPFKgFjl7VWop%%|y_L(|I*Zf>OlE z*MsUkoK8@3NfK-L+cdGXsP9-)Oo&Nkl*GNbsa6=3cG2R7vhpUHa&AF*08A3$&+}S6 zGOH`v+^-B#-OH54E!EMQ#*O+hS6Y?Wjncs*>(5+MS;C~MMWxTXh%^*|$!ZDPZ1dVB z*ekO=Dgt1;HFeXg=3i6H#p!02CDe!W)U4D9NJ=yeQhP`J@GEr(&P1oY`a?It!cT>c z+}(jejHM`Otk|R+trLn+BHK8XXeK4-TWz`!d9xGC?TN6RhPEIy^|3h$qQ<0gfI}TZ zgq@{TfWW&wAAHW#5;rN!%}NDn91!2Madt=DsVAXXRKyFik(xy?m$cLn2f_8vIHoXK z?iM6%B@FlCbI@W)izpE1L=CE~`8g**gDiy2QTz)UbS)!Q+?~l{GTFdfwFb%+2-RhS z&>>)1((RS3mNSz3F=|OEVj8Vz128JFvOL5JsbW{u6vZ^-rNGf2dQltVdP?3n#4~*= z@RrXz$6ZrABivlA5%-_P;6t1=4f<|bPAa#>9jdGe%k_MSO!L&cPBGFZiX0p&LM^Ql zjN=+qz_vOw%RyHOIY`RgVza&5jNRKi>An)GO<_7zL^i?%QaYkM)I*0|ei#WkCbZNj z3e5wWj#og8WLR{bv;(|j+Ls^JeN|i6vr1>#bW2Fn=_OQN%;=roGSxAl)QjwSQNeRh zjp3I(&O9tl!BbcvD&jHNlel$b)5@0Au)fi1Jjmde=ct6t0kT%mepM@t)q!)?TF#d9 zL=J@k!rZZ?z4+6`D7sh!vbv^G%gHd|(8(?le=^QbhuU^a9hh8TkR6KpF|C0<6@pnd zDm{U*MVg^O+pghxrX(8v7wFc}qNLYarLDM_-a>~c6KbSthPI8OOo=~-rr}X|u_*G< z7=!+ZbDuhS#cQp8jJ16oP8&d}gTW3C&<2XsJDS^l8&FPfnnW%`_HUYu=D{rKVyPY* zo8C8~%UvWE-BQFsig(e{T0{$vU`6`m27E;v96r&F;#h+)GR`60zz6hT*8OPXmb}1P z)K7MP7R)DzwZapvan~APJxIchZq>YK(Mjn!TTT#Sq}`oMk1kAIYI}(;N#C;W?77|y zfD?yd*^FA$WN8};?S3j{sEQ;&tlvn9;s9%0npA389R2yxMeosL@`UxFV*wA9ldA+xd=SZ116f$5%q^zZc{2=Mdh` zVy6HZ0hDKzvQyerNJBv!20>Js2(}gtC8P;ds&{B}PIcVFs1}`!5tr(gZ}@Vgoy*j} zKT~Mf_tG*P_Cz)6I?*1ke5XzMyLtsfc6TIAuH4Ux)mvSuw=J5grgrr;PwyBVbnnnd zO>0#K2}(27Tb4W|$2prX3@X?78Wib3p^4kGOUscJXG_LfC3=(9ZeYQ+Eqh2lAg1j1qk=fkz{!e9Mp6ALW@cqTf5I-Iv)04CN>n&? zP>hi<*LZxIajKbn`M-^aXWVlLAts_}U=nDgNhp`IxYrp6jO-&~q#5z*aws^Bm~w&z zdab8Ti`z8gF)O#M#aqq%S2fj7MW|v^8ehyLHkx9P+sq@G$xOuZzb&0#+2KCEWeb}% zFpk3$r;+CJ_XvS~9~X76Ob7r71pk6TK(KHGCHV_}!(ku*3`QRXg2X^DC>#H%hBw{fsnm?vcD12I937SD?a`?3dB?*$l;cv-2VviQ3 z#ANg6Oo}5Ihsk2J=;a=t8mPdc(pa?8a}lTjEVeKV{yjLK+icKE5C&;rq1)f_Xb6Bb z;6PX)5)Ar;0HHt_WA+>Xh(dr-00cSv1_D2$Pnh5;JsN*Q05S-m4ox77MkG)9WP(K$ zi$tWrIJB|;2AoZwlex4~eGrjEqLe6v?fFBAQb18?qwbwAh)-w|C}i3tBBws;@X8

_JeE_X4V12Mx<0}6k8Esoq_gR=p|;Y3x-Vd z(>rVTc@T_esz3?14uiM=;-!;1Hx^kKiD$EbK6C#>|HM)vaS4nnXVtChC9$nchjS*t z$fO&&H$#z3seocq3eCs8CfiFkKzNlJNS~xn+Omls5GpW=Gyp;E1)~cy_Vlf8Q;NXC zObV0%I;gU;#4l*F9EmXwiV%u3$im=_Hh?P7>mkqh0GY9fGlun{3A93mLrQWf6GjfA z(x13)tagJi5R|$|00V}v_>om7eE}H{SO-W;3jUtN*6q2(s!W{3h=j8wZ%vALk zBqYp2jW|UzH4Q^mC;YC!SadU{uDr8a48fyF!m}O#c7go4M^BAbe>hcA2#_oBLLD0* ziJHucEQ`91h`!((=R%+DJeg+zj$(GiSTh0p3SE+o0{SV}vG}r1vuXVO*sKf6$w@98 zB=F7-YsSI4u;n)APVx8v)3ouO;O!!kV*KZ?w{9~pJ~k2mYdWuUw?SnYgjZt1@)Wx* zs*(;T5Fn8v5p?HhRu^a{&`YF-0Xj=yekBt%>mKLF)Q3A}?He3_sL&Ffa3IigWm;u; z`{S9+j!gAyp;)wiu%F1YC1zH;dy$7y$W+*!q)Rfh1fvW@nLi>A+AUq|wzDM!uq)Mi zf=Q6Jg^B8DlFfr;ZY*K~V2E;zjA8QVi0z|$WudqJX@B`<)4~i{L>CHUh+W7f} z`Hj-KhHRfaF1!wiLue-9nBh{jWPYg3LdefW(}%yEW-z{%nXwI9BA(u;0DiB1em*n8 zU>3mpYei}Ivx8dZiP>aZXLXmXhBWjZ>*ImMG4sGBp#O*D9XjSLlREVnq1}-fglv$E ztq4@O-*OmG?rj+{H^%mf`|)S4rSB9{cIk@ak41y^{0(@F-~#g&V} zNe{H7Z(yXIi7A|j8RE$rkzAd4(e+}I1NRil>kT>0fSWXRa6ysC`*Wpq&p78wehPG1 zDWy2FI>Zu_37KSs(b;$;RrvxNna+*C43Q+n#91PoGysTQ5f7(Cv1Up=BF{E~#kex9 zS;9AZNolJYIf9qe`AR|!nVB_rLQ4+2TmX<#9ROFx!cyu|hYZ01kg2Z^Z(D8v+G{}EUQ%k^}Ve9~Mq}f{4xbb`F^;87|4eMo~!hV%sL1_1C)JTt@lD(rQ4JCFz%B$pVV1bUj+3*Z@Cc(;gah zF2If(E?F%tV!zCC^lvp9hcErK03^i0tC4|R)eb+&>E z3P-2jmCflka1>^OUP{Y_b96l?)*Z6P#kYXl1`s*MNk-D^dqgpPdZUn8nAnrD`U-XV z0FzG5;Y51rwDalwT>@9~iLQE@Vx6Zr3I zUP|;U6yyRzYN-e!%Ef0D2HFv*s!!Bfw~mkG+7d#JOg{^ZQsY4Y3uWc=(jGY5OeIQYjMHy^C<0;_%+ z+VUg-ZF%1@uBKd^6j^3Ed;Hmu;}HEDE{co_dWLRBV6oH+$)4HDmGgTEii{o3Z-f|n z_San)-4POXkaYf&x0V4Dq953?k4Du-T~oNy?THayt*;lt)N;D*Yrr2V-+JcV^;7|P z5%)qyiiwKJwc8;v3aeJSz<#-kp`pePk^pK8TG4ldAJOsNHb~dBQtgr;?EIV~My0SR zj)Idb>NG5v&5A%SMI_Z@mYh@r=u|t=S6=5%X*kDEfHf`CI-KVrda*5Wp~bVWCXMJs z;M%7u{a6-J-vCP2(7tuIy2Md!Fs3rlTU*}}G7YO&2~U*a@L zjr5TL^mm5#GzonAtdzFR(w}6mUTC6eCs?gxPAJUQz9`hGW^klP)M;oa_)Z4^E2{e| ztdfV;qiU!o!pwRkq${LWs}9EJ%;r?4uHh?Kbqj2}%>tStOhF;%2qX4MjW~@-D3C^6 z@gM}ZmEra@_V>f#XdLG=zw4CK!?08Ka~NJQ$w2*nQ~%#W}D2N0Q}5ZCK$Vedw@qGs78 zL|va&V5|D88?F_^phh&;ZAmU2IvJBix>jeYulsL{`4NPVL55h(7a#rd9 z_z^ZR3b3he>WYWd^k^t#LX@sehP_5&*lJWzC+`#LklF0;`HXVL%>;zTvNlOvyDvo3 zFv@x)#I>i!&CPP5?Mj&Ns`%g!1%fzsb;A#!a z0fLf`iM+gRI-W~%ArQz=D6%uhD%FXC!f?c&Z=_B`K_db9o@jXCO6FwoP-BhXDKj*D zf)p&pwtojZ0!r~Kf{h|Yxep|vDUX*E4S3tJdbM%09i$|%PCF$@&>v>N$Ia{?O}yxG z3gyD6oX94|=8Z8-wI+~e_bRN&&WgYksS#x`O~Z?4>dzkSjA-OB%(_`C5_+nN~)balRpQPuqVxy0c-d&LPakJy692|NbORvf&iv3qkfQlt{!Q zV;T49a6kt3%T` zs|PNu?u9O5e1j99!9%KdDLk~V%F#o5q)--SD<;PS6AGmG`b9v-wCPIpBz!VLJVT0A z6bLjG6iVb%1rZjasRcmgpi|JC%Wo*bNSP?lVr=slTS}KDgW^$f9<3u!GQbc!PIi)Y zSgeq7HZbuxkO4pK3dL$ft!nKgm7zk>G61io7_SVjYmpUbDKO7ch)B5_D(>(PlBRK1 zH?UGT0t_g@xn$xn9#&NTX@a-p_%H`~1+nr;iq9kUJshu0edbiEkz+u0YM4()AJWu1 zX?VHBoFIw2Im*QTg*-72AaO35g^2{d#1J$Q>M&?cUg{9W3f~LROg=2L2R0{Xb#PE5 z>mQM(qKmE~2Qtt?vU96mvX>i+oUZ@RRY^{ecb+q|QC?g5nGzhaa zbwOflTW{?9N_F10&lOi8&KFOwR(z(zz#2 zfMRcKn6cGBR+1k?lT?G_879`ztjyCd&cLesccJ)RjG(n~X!ZhqA%mS7;q&uj?mh#bS)df@8Fkuh$e5r4SB804dsY?*8m8 z0(vB%zKdGECDhlE2T7+4!?r{M@cQUV?xO?4Bx?;_k%)(t9au9v9kQQ|Yfn5+ZU77oEZ9oZ zH}duI0|6iqU{X6q&;dmDZ;#jN0I-arrHa64)O~Kp5ODyigZ7I!6pjZf{TPRK7m)4) zoj+|dw@L)fWchChV&#_#47hibE3ufG zcHwxz{9AG6qjiUq*{uYIScWTGN+tUB$hk^NHgdK4{L~zNQ?hwSnLKRkbSV3lAP!U0 z-0c-o(X$NHG<3R@w|x%0*Ng_FL@!So1&-4>lbLOFwI0(s*Q1F08ILVE=!a_fR*|Qo zh{3KT2RjwEdsF7h*(p>25bSDm*sw!SG1$V*n0Tv`jCxUgc8*_W&o986e@?3 zz8FViDIDjc+Ie>qt@a0h=RlK_e|E^c!!YLNEz=<}5^U#^if zjbfs-a-^iBjft!_L>eQDoo!jexHiQSdoP5Pa-1VePo`dWR(mM(e(C#Bu*T0>E|mkx zz#-YNkC4|8DLO3HYG^v@$+T1-2oo_HmV2`d;>+fjISF}+$0$sz7PSEir+I)3T!>|< z0_?`Y3Xr|XnGu3bNH-T_nR}zL6w@b2I&UVaYUQq!d4hGMISQ#CjR|7+4tuIgu=qy_ z+KIgu_g+%L4AT0z13+=F#f(+D0Tygl`v9LN*pke`Ur;x8Xj3(kfVS!d6MLM&kk|mm zRZ>>kz12sZjc|Er5^M`Pr;c8;tJs6|1!>JcaH*z}k-da9Z<}J3i%w@pkiT*c%Dk3s zVAQF^CoaA<6iii=#!WIsFX<~&tDnNVo?3aDmEe3juBs)rLitS$XTy2OrbRc2h{r79 z&pDVVy=ey=p>kpLq`2)V96yT2u1e_GZ_2*8TFGKjft!;I zd;KYR01UVC^_6G4VoOTeRbk5KD{s#CTUF5T?tajN!mi6o#MPa^Q{$G5t7gx(h1;)D zX261l>BF$iVpfkIQ`C#x4ur|}q)a3sOPy->x^lN;q9}O^i9JfWz@wLT+5;i4jkgp6 zvmy>*ZZN%;3> z-LQH`MO6cl(g@+00r_p_lVir&Eilb03F%#8spy!c-NIQ>PNC46!7IbcmzY|q0x1Y& z3l`^O(N4AL24X1C5GmU;J{wy&qD6lE!$jEIE7;yUPVat*3+;2#%$dtZq55}_f*xo( zcY{5GCq;rZ(-Ny%=R!C-^eJ{}&B6YEYVN?c^6Z4lY*>PT@P|ElpXtUz~cM7VOwxfFvmxpmi4GGKQJS*IEge7gN1&6l5Tz>i|c4FmA zG$Gn&lQ^q65yeR{u2oRMdJ}S>~1Fpe??&s&|^O*ELMHr;U0Jj+Qb`3NGNT)QLOh!)|piyG+JBSt@2g5|_mfGEL0~p6* z@sQenHvWwgC1q+l;Kq z|Czl&qH+#|M*#7~o&^+YWz zE{&rRPz3`#tb-=W$Iz;s2q%p@BJ(8>I13z~(xdK>H1T>oyD1S$2$Ui42nQ>?(QC4q zKaiLODL>I`V;xIC63UjwY74bFy|Dy#iLS@R11rq2eEm92kRnL)P2dE6*D?u0JiN>x z8Xmhj5);W5QK(%K>mbxh+@2*#T2QJ(Xl&S{p$PMRKcu0^O9v7yDNGxSi~{h@ZdIxT;$m#(Jg-*M99J(Y+LB^QDwSqOA zYKyuzgA4<&X~+pksi?eO-Cs6A{eQhPqJcErDCCgGGBnhGmsih4cmhoGfB>o^%p03L zHfW21Tg%C#Gh4Z{A_$!5pd4a;G;c(HyCo0GH&-EPSQD1Osq5&*Na-e7Y}lA0B8TQl zma9|Uxb8RrYA5Rlg`(<8{=Yyniy1Z|dPZmfQ_)*awnPja)QHL14H;YG;3ob6@mFNb z?^hW?`p@e?3}0j8cZ@-JT!-_cac8LB4U1k8`aO#`%o;I^OlTJ;#PzIvQKi?>{T9~E zvF{(uAuF@Y-&i!&5}-twjjMcPSxNqT`Cwm~ks;{x%eg|-rL2@J=)-)VF5N=oOma_J z@QA969Ke1-C)?!Is2hEul{7J?2Nmo+dH0|I-Ir2}x74~DVj%DYt<5QCDvmXr7ZnUY z*fFIcEg{IMA|l0&8qH*b{rxjyTBOJ0>Q81(TfYDXveFaOc4Sd(lP6r`Qo8|Chy9f} zLhMA|=mkIk4ZI!{K=9hg1T^l{^~2>jrrWA*MedBAAm?bOS~9(BsF~J2lkfnKGA}bP zpGGg;9hbCJ<&;Q4x5BCF_;*feJ{w zih-`dwUC6;X~h@ufaQ5J3!@FJbdZkjr-3TubQvBu{E3fJK8QVib;uaREjiiXfbzyMh3a9vU@> zN@UJQRC{K)JedQ%^=ga5(xJRyk<`6_GB)C8z+iOW+kcE zeN56K^@!PnB666V)B*ZJS;-BVPb9(2y58L3vK5B00N2H~4KS0Tqo5V)1t%rsi&-;p zeneI~Jd@F2%|Qk%^STy7Q+zk8YD&7;ns*37Sz$^bA)=DImy)I zUfDEji0yk1wq(Vb853gBoX0;xWO8VEkAx1%JW?_e&B!zqnZ?C~Mdp)ns6nJ6?(U+n zl$l@Fy(@jnZh%N6Otq_A?@>lR!;jUDv(5CbdJU=9OlKcNu8V{&1;wr~V`e_!Y$S^WhoiV$ zrrpYIgij`mG;2?GKD8GaO&UkZk*oj`@3g8VI+{T_l-R6< zO@(~xqj4K~pe>9}Yiow?eYqoe(Q~*$fh!XJXOGmT=8?P_X-rFxgboKePP9V?n!XOG z#Fg)tX;|hMv36R`Fpv_4m2_+Ll(~`o`O=A?*wK<1WE83zxXk`#-|LmY2!V)0arVWO z)DEFC9;{u-hVAK$(PedJLex5Ka7k%0r%L(GK59T@+$9Dv?@c1etEVR2lws92D&CN0 zr#>+`1Z?%r>TNigaw#4pWmkUUm`MFit|ehkNtZIVidh9uV*kzX>Y97LAi!9uMm& z_#`ccWnq+c6+3y=po^Acs$x0Q&g8k}0Z(Qj8W#O1l;iNZTWmrk@01Dz|$ewNKHOj}*(o3ZmJ3b?Ro7AX+30a7b zvLDluE&EBe%XE1(HV{(u8q#;D`CnS=7n*i56{D+`*{yN7{v`l6WqUZP0`IG5t#GYafb67dWk4y}G^0P?@^Cvigh$G)337!BV zQwy-9ppv>P*_b|IbF>()8mQnUGxP{(;jY^(rSiZvAOaF9kU1;;H1UHvv0OaMTAFLa zB(Ry93+WEZ5wkHPD6qJ@t3)1~2pF*8z>^8GilG)M&^i+8LTq`3cHaAOgh~ zNS44D*g{Z(mPnGh$*Pth-x{g*jVPSF0DqT*KRHAWF%!8vYX&|d(Fj4KymIA~OXHwS z00-MPyF1FW!+N!`7nvL5l+e_f8P_`?sW)JM79nnrfQ73;E*nz{mI{50%Mc5TpOqXG zkgE#00wypCG>=1+#5n0YYf&FN%AMevK!Sg&92`G5m5v%$Hv=yT;IobNCI``~vaxrR zp^OcIGQvzk!DE3Iu|2?2={xdy3DK1!;)N_&*u69YsTh@Yr}ycunCv0>~*fpDMkD54pIaVtMHoIGKrvvw(ItX@WG`qE1Fbh z8d9$?sM4Eoo17$dF^ejQS&Jm|A4DNsI&_6J@cB3iG$QlWTmOYhAXx!;E`6F9JFgqW%=fgfd%>K7^Jx%L0+% zY^q`ZM1p)s$dbJ=*F?j!K2eA@GE1(Rt`b=gM6|pS8G#_Xl(eaA6RUVl(8o*wtjk;y zy$b0K99D_C8jPG*9C|^dgw4z(0u}SqHJU#?15dVNPdF-U$UGlL5RykolCx`UpF@NQ zyVQvjEgWkkvZI!!%S?!Aiz%_}iM-1;NQ5b)D6pu}nt~{`+@X(J4=|gXI7vB8doG7X!?#g>&C~$62+PFM~?FZV&FsMsVi?~NIy zF5IfkF~YU*0hfcl$vJ|i*uzeXeY&9#nj2sh0RXjw3anDaLwK`^lc2~EAV%8cfGh}0 zczw2!J;-wWIdV)Hsu8x_cr-xLj(QbPd1b(YS+SUgx-BF`{N*`2l&*B)7)*Ny@Vi4{ z5DiM(5R<)@ThkL_k-OWbO5?Sn+>?zuRue3{!Es^5V6U42715fJy-KPF)J0Q45g-AX zA{eSn6W$zb5Uz~?k`(bW3$Z z)BN1kqgsg)Qzdcq7CY-Mr9{x3QOHBsN?|gc`4!4rf}}LqHN(R=Py?7lb0T5@G|?5v zk|2+fu2I<7r~>WNfgq9;C>To8NGpGb{cby)>crFN(V)?!@Zk#-IEm$JLb#Q?-DkQ$ zfj#8`L5xpRl;=xe!%yOTsj}oe^^iCN>5gpR2_<4K*P+^g-1p<*r3GN=MWEPbqr6!}g0bGb|dm*deL z)pt)Jz5o?9J82@=^(P6io0_Y(6sUm~UAfvw%)uGswS&S#sq+zh45DdFYnG83&K8ViZcokH0$2-{f*K;JLxPepzynF!wy zly;!NeIEGIn6b|uOum>+i>64<;2Fw9o9@$#GD8@ftFRNkX zj*8@4n`%}#lqEIR30qa0WOXWipkq!)8B%v##FVaC`W!O3QT0jG#V0F7+%Dpg--rV} zO_re{M8_Uo+HeD>F(Q}od(g4()Aa`95QyBnT}YkRQLxeB0s!IUa0#YQMgRa*nA5PW z*N%BBsTy}2#X_S%!&G&pSmMPMFqW7_cpjR+HVu)CWEwb$bC8vPxrw_5h$| z07=X+ZLN-d|5lDmzlwJ$1bj$FXe%%r@n{Wh?h)V(2GS+I_rAmO<+v-0kz_1NjaHViVIO z32ZXv9YNMTlg5ca#Z6;f&9rF)?bfp2X1)t+4Ka%0AHwl$!41y2rO~7v#6F(1I;lBY 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

Zur^gN1i+;7huw8rrS9Ic~W6ew&Ac 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

D|DL!u`70rFY0 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

y zHdX}KKjkwCJdRH(zCYhRdL+Y637p?;@GOQ#VK|1%fc(8CUNNMS!0%7$eG*4gxN+mU z=xC(8eoyYk+N#%q4H8#f~4{qp0_LQ>{O&7s`{T8Lnx|t z=EX}R>;gQ|!TzEua`MoFq-VVeG@+_86+q63(upx2Xd5pVDDL#E=BBYS4*w)-)fFUB zPLe?LB&=EyM1V626AV15bLfF6j*23YA}GU*f~oY|@{mht5>k|;w6!ROy{HtP03@k} zCa?erngu>AlxrI|$MBj8w6-qj!ToA?2#=dK{$tVlh=mnA7Xg&MFC zOrV1x?{e;*W9;*lQh;c~{U;-|-~(&plw4gVHIo_}A>$b+0ItlZ>p2Z)XdBGZH<;bM zML?9SrsmbCpcDM3$F>#wWk*V4F`=LLQTnV|7L23-V(A2}2RF6+r?H=z)0naC z6kPdc09QlI-~bXbI`_G9`gdAEwR8lxAIko(xvXk^lN(Y-u34%JKm4ig8!LO7;~vQB)>(Qa*!hM%5*6&5{3 z(c3R&J1l9eU53eZ)N@5f&|&$}XDAkCm>+T+QNsWd&jRytD7J5I*L@=k79u`E3am2k zXVR;}RxX5x@d!(ku*p0T{OV3SZH$7qEfDiN02N@e_+TE3QkG3!^ZEdWkqH$w z?C+%#4r}XD9WB-yA`b(>jwBTaL}QjsPf3xC%&|TdgXBbIL3t|k78}JBY+BdK1c)&a z`oa3{2!+EZa+3oUdZcF zF%wPpveFWQ+`+gou0dC-5?v3G6#*ZTiWj+QgCf)s5kSnYdH_O+G8P;VAuHekBslum z6=(u7@(|F^w#f&G=~FaQr4kQ9;YO9n{9eUOnzhA~4pl-;D(k(csxm;3&-95P=2*zB z#0dRj$%-WO?F%dhts`1YM1yAr6i=mk@uI{wnQ}T&I+6H|l+v+nBql;S868;Vys`#qWjp(t_`v=c`jv*(Sb5#fPdgz&jWsEl8HW#s8Dfu9Z?IE!$W19F} zvyE=f7Aw^GlQ5;xW4%BP1zJkcaO#V$zKjf{$`)|3NrS*?%4F-JB5JteyS5>ujFHll zeroQ^!9&P^tHBb=ol>1wQD_l*u4k-6z=mCi)+-(^0xWf8^CZG^%#5Y1sG`bw^<8yt z8#c-fYOr*$jW&ZLkrv-*NNvt-YVpQC0zq*URQv!)w1Vi2e~4v+3D68Uj>`vCmvZ;9$7hKrRw3Cnr-xk|K^hEc2^pd#Ga^7I z#cxBi6iZa(?*r;-qRunEr`M!z1Y-B=7h&F{-ivgMUlM}0^nQkOkwz^$x<{(XLtuPz zaGflgDnLMhHegTKAO;NogaKhsxL^_-{)j=MKoA60EeL#olcm{WzdL~I1hh`NZ%lA zBw~qIu)eL4SvCeob%ai1QM$fDc?8D6sF7%u3N1vBKQB;eW=e51gTgBDjSXfaq?Sme zcknz)dljHUB{UeW(uqz4a9;d5j}7lvt3#s|eY6r;yU4|7@*SoMqne^bCOI%3heiR? z#c?!X6sHdk#LGA!j%)YNSlmc^(Y*~51PT0TV*sz?)BFX%3Ui*fxga8z`~fP`2#%x5 z^75H13kodSHsAtM+9M7!B=xOH3k--QFhc~QFn}^%*rhFN;@GKe-~g^Q=$f39CMU#f z+@I)_6qO+lTyV6a31dRex=l=Sn@FGxTAi(@>$vHquEG5vAJKD6$t$Qc*7Hay3lftf z3!)&zyYeKi07LSs_=7~MstGY4(&W^P$u7IVki3!{jJ>F6gVz-R4O$S;z)CzInYJkl zvh5>l>KQO06NCr`QiqK!wYf+{sPaH-f;TrGuy6-KMXXG|g*gyg7==hk^H2c1XsWL^ zx-&v|K1t{_s!D?_HBUDqsUc1R~ZgYeaxb78Ntx6?~}^vdLy9kEarXET>|PrShxY zDUATdfD97HOvSGH&VpY#-9upH5q$Q8FU$IxhG3`LGKtF3Tz2j#T4(_9vQRE5g~v#Q zXpPxwb&mJ|5i6}PEHU))B-qkhpqZjsrGNmW%68g^+lXa5SLMiJrD#y`bKj4E4BDwR z;=C58MMgMVxWv7AwLiipEE6h-YwS*Fi8hz&2^UP+7ZGKe5{3c-iup%dG{ z3yR^g&83pGQ8-$cid84zym~gyCGGnj|kBBRAx_c}OZ!J8H?g59Y?lNU20Niip8Jbl_15G8KI71(g$r zK#X5Ie1c@%q^|NNciVX>dI>m+urLs{hpNeUFXgMhH^=~>(SJ||Sf#T^Jp>`72OAMA z&p0vuz)K4-hv}Inox>1VSu(^dE9?M1@(g#(vov+B1*sWku<{g1mT=CsnIPD{>G? z8rTC{s;mN<6zo#f#7yUZre5s~$!U7b9UU3pcrQxm-2Xa;5>;EA~8FjWn^k`k# z9+gK_k)d+_*XKL_B677Wr3CCsT(dYekQk{#wH!X-bSqhKxS<{OK#9}j3{Kz)XRB9Q zpC6zEZzJwKHWLW*mrKAVvbF~g>GWX~0qt#cy zw=fA+j1fX!&|UjU(mdg&EFn$gZkf^bNcu8WpN~y;@>&C2-W%bEQIvhagFpAFyqr7jgEi%q0?<^DvvT$~i!Q zDc_=r)$xzR1!M>H5~Oft#F11>Y91X-0ShfNB?5@MNJ^+7#t5e(Q~(0%$)`|cc{0Gs zWYiK#p;}AS{xe7}d&!LQ07W_K%5VcO=AAhpI30#5ckhp;O{NmuDBuITq7 zVOyyuEYe;8R^NKDOz&@G?ZZxdQE6qApeChX6q0HfK5(k&5+P=Rpdm;2K}C z%kTg!@cefzErp|kb1X0eYo3>aS6`R3(&hY_k`fe$OCn;@=A{@cH>iwJkO?d&umzat z+Nokt3v~!~Wqhez(ATG0i=~o$llUFD5T|d0Kq8&b*yM`XHx`76!U#hr5RPW*TP*Ro z1CPqQ!4he70&EH+pU_n(Ge_ALBai`YZ2FZn8Cxq7OHsy_ktpB9=cKJJ14$-ZDJb$3 ziDU@&UdD%3*~8nT;8fMtIde9D8OIr_PI9e!ZtB)t zYn4EhEv_=Bm+3wsS>KwTDL2w%{unBt0cf@iPM7MxAuarE!N}YfbCoKUr9ZTwY<#OB9gq%AVZIXJpKNP|To`NdhpI3_kz zcDv&oK2XKE0J_O_Q1_`-(vzs~d?cIL#Ep!YbnoV1Z@=Hy^paY<;a!%>39XK zz$QJ=#t9Igh;iY&bB6#m4N#9~7@~LTA zy##aQ(!(sW@@WXwET+qa7CVGop}-E8<`ydNgddCn08JYGB0PU9MkegO;%X+ff&5xX zX5#`j>(7dOuOfd!Mm=vxMCm3YZA9nAm_5$?DaGt4MksA)0RRUGoyCxmC$j|Yw*G0$$(JFqAEt-JBFx;id-iT{G-Lb{jK^Sh0>kv3~lJ>Aj<}d0raM$$mSx%LGVQ5 zqr}$@ivuHEAI5I2kP?QX8g!3Bfv@f+WzJYC!0zPWSZbDuEC_o{%;8XCa}O-CM)*_% z$lC;3OvX6o;q=z4b|h;s0L`LOfEXn!f&c^%MJRsPZfM312(HjL_zL`Ws*;86O8iNN z1R;z3G%Ng5sIDJT67z>rI;_iFMP$1AwBLw~<$nJaK2N?$)87};b zjIkGrx@BS*tx2lSQ65+@1chWOs!&S}D4fYgDxC1{Vylq@Nl^wwrcli6neQ;z%1}tl z%>2nTA&UbAQO;H6l1FJN5agE}Yyj%d#x`R9%Mp->;)EqDeo%(&*C%qCE(%AHLcZc^ zqCzZL!Vp?eUjFc$drXHA3D$2yY9z@J0B_L8$HN#RCe9FI%xtv_j?k1XeBUkFq=Z)k zjPi405Pu^r9c$*Li-xfU0wKvHM$XodA~6FCgj_HVy+?dBBi6b~tW7CKaEw15VfG+F zvLeprR8Ste(xzM~NO#J1H&>;@IR7R4j$73L3!d2&-Z-%HPgN+DM%P%o+90yD= zgqr9vK*p`IWTg`$viM0d{=Y>?M{X=e(Jar=q#tB{pw6BFsBk-q0stvh08>B!PxR}= z>|&81m`-sGak?dpo=TBuFYm~Du4fOVT<)viA}WTHgu!2kQ21I-8y(LtGLb;k=}6INoW4)wAkHqWXt>f5-yVF_^H#ZDn7 zv#!Hxl&ubafaK)oiqO^Vn?R0T0x{nMr_4%B=^d+7QE~9QM}l+nNi9ksAjok9=86F9 z5?kqBktNbz(n}vi9-1qDhYXGYOt6zNmXeduf}${hiz7YlwIlQh$ELoH=xIEhFU0rK4!+4?nkWKcE$qKW-$teu)5v`Y z^L;<6oHmGNT_l<|gqo6#;R)yt<;7n|Z2r4MD?d?j1IqOl4sk*wxTYw)rDSmQQub0J zHv)xRR;yN~&=CM-K&na>N7(Hmu6%r)fsv{Oig)pz}S`G~fhswvIOxhw%QSC8f zaBL&dEgHn2y~M{20+%f=H4nlnwiaLj?jGEd*Dh}d8sGv%sk~!?uG%E*y+q)WjkiN7 zf?7+}L+y&!u>`dykzW$>*VGW#PB&KKX3qs{JSh4hOHl!XjLz<0vMbeVDFZwbK{rAi zB`~}yNVtUL=I>9$B0;Zlte!pf!in?xk_I@;q8SZij+mrgyqv(9}+4!F;`#osvpvIVeO1hf=y24t!oBw zj`pU@Wu*l7NbBGU6Obizr^@-M0#u0Zyy`Soq@X~9$ZP2Al`Uw3QBsnEh=&9atrR;d z#=dSh15_Xck`ScW)5ker4?t4cA9McLcNR#(a?jJE{Gs^el#oyYVzmlz-IhqQG-{?K zM;dv>{gEE_hCd=A{pie^3G;B)7I{cVy>Dt3GYp0&_goFdY{|O%FJQKe#md zA@f9LnIEiH0K}R^;+#Pa!Z3}tw$lR~>WE^r-huZa8~6Pj?dwvqAs_~+d^Yp|qkbWj zp&1RY4B_;KYCd@&4JKE0WpnX_q8MJ*E+B>EJ1>?jF)f_q@cWgO6*qJ^*u!6j>K|q$ z6*enZknKw^+d?Ht2!ZuT>=}NtB9&RLTno~hIpTCK0=-vpWzREh`Z&^&$9XFKrt!L0 zsS}%65e6uZP8fd?(AwpfD$<$;^SOhc*j!jExaTF5X=W(oVj!Tgy)QzYPb?y<`l~E? zB4`AYA+e^^Bk*{Q+>EOuFRD}kCw!~1mgCadNd-kPPH-Yv?s@-;u;4Xq z6)?mxR|dY)I|D4Y9wGTCq3o`*dtP_GO(Ii8#BBUCwT?uWi7&88vazQ5So}pvS&eg& zx%z1)I6L_Oj$(HFB85S7MLLx%@3GR@w@sdf!u2ytcP+C6HEM*}PPS>YUS|2F)GU$; ztfWw4V}$@Vlj;CNjIIlR)Y2yc$OYx3V~kwnHb)O%MW z5k$8qW}^jN(e9Hnz~!G<+{zcq~N3PdiN*!rQ^99r=6)J z=NqiPyt@Y-xp|x?v{Voenpz@>G@zDQ5`4OE5m?Z=5!o@4f3VTcB&Y8ZORI+YIT;v- zgX&i!a*v%#4OK{va0#ifr5B%~og)MBs9O6R=3ctaymu;Q?SlyzyS%T^BU}iau*Tm1 zTbT0kJ#Begh&j`SZc;|jPUwY1nsqir_lO_sen)(AgqS z664?x!$e|h_d@QZ3AW8l--A-Rse0H zzrryVyIa3<;wNgQhDpzGg9xzGVM{Kf!DF+P@><=dsXf(O*+d_HmcZ9J%JNm`AhsjV z7WPgq4G)vVFa3v^OZR|T+sG4QJLai&)8QQ3;wCzGy<&HQnM2y{9#oI56LoWHed*i~ zJV1z6l8vb#Kn~k>6C0*{VunWBEGOLvl5xDOY!Q?sbowki#>6!tm|R5I+$2j5{dPV; zKNq*ZT_`EZydu+UquU;mBZ7b`Va2I3M;#|{6TIc?Lay0BQ1f!HL*Eyd!|3vUPRUgG zmTEb+d&`_Q~#gwY&l9-Azyn3f?d9Ap61NAohA zI6O8W02ky9`vrf&z>t_b`~eMwfgf=gEA#~whJXOEm&|Mj9f7|ivJf0zE&-E5QXHoeK#q~{z&!*6s^j;@HgVf=$ zxeSsUO??6E5&4|@br*-l<8P2nt`}ae03+bbm8L^8h0wq@06W(CdZNoBaWHNUy&s=e z>y?QmYCAr(!|1>&q!!5)s?sM>Ty$@9L!d6f7nT__^F?F=6hXKtMxQ zhNsc2E`PJJ@_g?w&3Y>DFeobUi>FPS#_G3?as+{*>(nZbqv?w}n!*R`Vt+a*)A*2| z6C;Y{AhHBZexc{I+Z;V8VWsk8^y8qEO*oj*(ws7xv#5INmMh9yGX7EZlTj63hvmMJM3)po zo~Vob6n{UFQWpRKY1DK8H4v?keZ|vh?8D&bU7n1%i0!p}qq1FPO}Z$%)g-H`Za<1v zSe!{8FNdm?a3mmBdap~_JyXkDS`05pQq_@OvLS&}IejK!M~ zLRhGk%qnCgMMmrkMyTMoyT%sofFU}01nF%%NGL=}i@#!N-BpoO>fFcs7JZ6w%{ypL zuSertILh$^r~~{~T|13n>D@0r6e@*R64-=in!%Auu2nbBoCDZNKk&-^D z7xSP(lQ|C3$~;JzBeZwyD4!Rk;6IYZGj*T{&au*FT!3MPc+WLXHf0F}Xn+ACZ~dt* zGJf#@0^&wg$O5>MD-(zGAvF#NeYJvAb;@ZeB}DRsw3=A18rcbJ>`t#gQZz>Bm;+*M zp+?h40UqXn1Yu9z?oXMIA5X&%Ah1B4L^Dld4vkJRN_>yMcO49k%LjW^?Y*Q{#(NA? z$VU{iy~qj}-%^W)Q1g*?5m{3No}8X`Q_Xh6@*46VTjgaIE=rR)yuDGIqIW9b_A18Q z_if+7o8<^&#ItY!4XecY+Na?RC5W=-d9|0(QNBJCqB%d%&dkF>=Dfq; z^lyqpzTnR3JWne$?}RIfXe!4%KkjTIBk1DalE$qxsU2;0)bkHQh2KhB8(&jo&XBAE z6EBn-n^e`#3Ea5970FdzlrwV{<(N9nCSU}T3m)FWDZ@yNQXghlA$vzWKZ}WbdA;u- zy%p>3XbAc)drOIPy@E|_PyAGbHK=`NN~2T@#R(zfdrvXa^j;u?GL1@Z*vEojEi3`f zpSDjGy0_sx>VvS3XYk16QvW97e8$sccRx}n2(e{$AU~EKOs)z;UeR$gdsp=6)ic=B zDa(92#o|k$*R6$-QZ=X+0Jh4_e36J98eqkyR1qC9AC8S|0L&S&CMCg8V*8xT>b2u- za0Qe{)4$M%I|AG#aQVq(Y1}G<2TA!>KCG!7RqTX~m#0WZVdM~xRrf5Xd)*DJQpGV? z0a-kSK8&FX=OJhkz+f}KU*AiYs`jLb(aS*Fi3xh4G7cXPE^tU>qNao9XI8T(&|B|a zE>Vz({%@zKPpJtHrHHA86vCTT%JMjU_TAsb_}md@y0oJ4O~)-rX|~0gk~3V z#i4ysX^DY=!qwumE5HT8qwg>PPKf#>A@m_6eVWnocxQAuPPnN@K`G4w!w2zt9)32B zX=iDFWhBubNc&n3=d_EA{fIs5oSi>@5=840Zr+`emMQ)NmQ}+`;0{W(k=8qU=+N}` zh`9bf`QI(~w0t^Q-jK@MuN(cr4u6Y+{JMC=?suh5FC3bp!Wx4+%X+*-ls$J<6Zx5l zx;TFQ?YS*-!zsxzX`^0lc;fa7#tjI| zrZ_majEP<$;`cjgQwh>E9x;tFLvS0DJge}M4vNbqGxe4$)Cqfjtx(K7vO|IkY2r8{X()Pj#qL|V>K)R?V zOOBF2y$pJ0uYqF3wlI8nkp)}e~1|sK7n|VBuSUJ zl`={cs+)5-+Ju>txtO!ACa}$i%N&TfZAS}BAL`o`la`xnl}6Jmv19kLcz-?82pCiw zo8bneY1_IYc_2x#s)I})deJQbMa3%3G|~d1BvdWAiNt}4rHmIy8qFmMToww(D3js< zBOE<>`W>`gLZWGvf!iYreKd+)$3m@|o8t)D`Y^w}$fvwL z!@`k6LZ_B9H@Yh6vz(_kI_5*66*_90!qa@knt?0|ZyuoI0@s+rVG40Qm_CKud=YXE)z=?WR=M3vnJ$SFp2{jp>iUmVn2Ft6622m6fPEl zg+6o)l;pCE>GuhE$g3 zOj|8C(At!bsxDXcgMx$P&qjW~z`x0}UI(1OK?&L|>wz0#|;3)R11phHyustgjN`dlai5f~9! zFwsRYs-XxPy2?rwtD>R}!($HgN6&}>K0J0C8mBSH$ez)BJfii(9(9i%SDb@`{MUeSLkg7M&yXh`<9<;PGO^lAQ z+;yIO2f?%-B~)m#l~zYwAPfq7$Vw_aJyACCD5?oaJUu3?TQLA3`7RU^Byg}uQFzCw z%!_=(tvhiwj9jCcgG{gh#lWyp;x#lSWmLNzN3ixmS_q#tEuJ&v%j)VPs&r9cf01%v z2-=*D!0#~pi6jAoHZuOCL!}CARg?n$hP8$caV0F(Q!>;2$a$nQxkar_KvtjwxA>4f z^`=H3ifB7!K;DtpcYjGObT^vlRV5B*kd5000q`q$L!#P!ocb*sly22EUDj2u%W+ z?M=K?oj}2GH&X7T+f%Mu!LzNCmNBMR(I*!acfPrTG$V);Y>6i0^9Wp+qgjn7ynnha zX3iq$y>J5)y(NdyxRE6sqg|uNYKWQ*K8kBm2ppQuBaE{`Q%?&XyhF3u3q>9=Ru)+` zBt?!veHVx0(F@gTmx=e-O+H*YPt{vg#uR(ORBj~dDv|VH!|Nc4dY-eoIMB;>*8?as z9Mm~t*a(QuqUixC3Pce3?OM!TsVr1UgDN3W@TL722+D5|{p&FKaWo=llk8=?L^!8u zX1t?gAxUW~4LnbJd9rB%T@5h6lHI0NdfrWTF8}~Npn3>ejXeo?j*9W6!v5c!Fo z;KAR|z0pNHid(rPC$-I1K$jD{Ga#^yqtM(MB~noFKB{sOlJq`Q9W1{RPu^@izPc2j zo*)Wo7ZKqW71AGBRYix|5V#-#&+RU={QxK!11pumjU&&}VPO_Cwn=?QWPd>mlvuzs@-a>m1cqRlPZo2sp1;EA&6Y8JxVqN|B=%*rti& zk><(Uw83#a5U@Mc7%Dk7iQ8L zq4ZZ3xrnOo=wmxegyD1Gd3mLO>XWX-aq^H2)`v0CNyhsvcX3wl`Q ztklJnC-Ul$?sO-aq|2R_-Hcs~RX;HOJ)S}8W=mT^hTM^3NB{{}nZ$-Y+#Txh+zgJ3 z00x5WtHY%YQGhgwRN@h6#EOVr2ihsi=z2rHdaKiBJv<(KA@R2nQj|>_pj%4;pM5qj zaL(J!e`{-B+)GSL4iCVLYhOfrNx481rCp@_WKh0yrO~7}U(VEQyWT3B=PTS*YCRt52GAY7m^rr;V>R=M7PHaRXkT23@O(;I0FSE_buJq= z?;Zjjwpca`qAFm1V$EIvGT4=V6BLEvUv8A+I>6_Eg5AMhJiM~x@!P~*nhI)rk#U?9 zg^01WywtY0E)tR#2DGrVOmH;+nr3I7HZE+Z9Mkc`Que$rGu=LPCCMKqwfe!zif-nl z2aJvY;Btk_cMmGMf4Pn`7dX@=O_L{SBdh|JDA4z*VTV?J7UphPKC9(eR|se6+m!n4 zUouZdexlp8mle4_$JJ@(BgIEMdW(Ikhr$1s5XCl!1~^*4*f@O}EZv|i|21n^K$gDW zD3@-{)z^E1;$s!9e#+INPwwVh-y}$?_=tWiuOygW@8n~umZ&a+gldeuYXQk z*3aXFzF|S0s&h2}GM%LKT?l!cG0h-ZX2uZBXV!S12A`rYSx@&GAg*4JG5u$^^_({Q zeffg(EE<+~rO7PSM+)TJ)F1&K*Q}?db#9CpKu4}%R_IlQvEIVHxNv;#$lP>B-SymC zQuU)Kk6Q^ei|*6PU-j0LR{z0+4i^D**6vNW8J?Ea`T#}e8D7$$GB=L%9NDW76=xuL zJxpC6z}gSIl^tHtMWG-wyPnse#OITh5RPZ4fulIEB&~a$JIsq*!QvW~h>E2M`Hr#9 zFXAfDD!d982g)ppv!j}9sELW>UQyzP=3bhj+k8%k-K(8F{T|XiOg&2CgAeS>tmXta zLt7mB_hs}`JPZ1nfskm$s>}fKk(w!8zhUI zsFk%T3_bu*sA?3WI*`ID%h5>1D;(5JvIyqE^lAR;OUS|hH8CiZ{+Fq0V|3uUkL3)3 zNz5Ed0lw$jEaL$$5CF_6G~xcxIkQ~8hAZouYf#jS^w0oJs5)r{Ei$4-0svI3~A^UZ? z$gPOq&DPuH003|1THoIw9RBbCHHtSW0J)`MN9VZn6<0Q_nnISY88NM_s@Qr4f;0+* z?21i@FcGrI3WW%PA=Cg-K(4rV z{K9?k4@D}M*o%U$ucoaBRTTkFdHC*h_x!Shc6p|h)5snJPbZ(N+B4S9D1@Ui05I;x zextY|aXp~Y1-yq>cjrcb*iF|ExTKv{Q9mTCO<0L4{W5FcB3MKboGB_b;mK-Ey>A+J zwy+#K<#fkUcDxr7@ffKtaljP77{Uza01D?VEtCWm2j=_Px~YNX3A(=F{B@P_Qb&Ld zNjj5mSKKnKT4cQb5o6M~$TJ2)5ONq0rgrs5+uk3r*tb1nI7MOe2q;Y`l`<6GVqB^* zH&9_OL}GBa2vJ{i&B5rB2L|4XJLP4mz<*S@Ix6vD~M>y4i8xr0_0&vbxKSC6?ibp4jR2=1NJ&b z)m+t}S#vYxF+q@3u+(7bb5JXKNGyeH`COtofMhvm#7RJ+3M0;U$heC^h!TY&>Oo|r zg)b$ftj~%w5KfG_z>r1|bV`&6lFnd#97GP6SOn7mi0(2FNHA`iqvQZB880caenX7| z2xLy}a?AA8Z{Xa+TgiF6%tH=(o+@pXa*^jDk}Sra87q)u@xP)-86JvbG*ym@QbI|w z0-sY6HnaqOL*?Lah$1d8t0GrNRXhQjX#O`#D2OBm7*Ej+A!*TsgGrJ)+en*XS%?0W z&64O(<}3@GDb>;uiQgO1@i<`%2(k}C!t}rzR7es0q>{Lxwny|4ACmG^P*O1@AM|)% z&QXj2dD^`W#U^4TIt)*;lIsZr9Y>@5-5y#_$(sFRldu+2JqSHM2vj7Y1tO%xpae~8 z3yiO6RDTp?MBid;p-himw~{DY$`_SumQdTS|0wAy4**KSkT5i9U znm-vb-lOF)m=D>|$ll8)dlDdj8}&|FV{=C*bE$FwOIEQG#1$xuN^e1eI}2fHUr;tS zfsq1MSIyL!d@AzHxk&p{D>X$vj7hhXQiToJiViviZ2L^v>kO5lM2FFtYb6IEbRUyk z0Icfu07zQ+2t+Je?3%+!b`fB&;ZJ3D9?hlk_>wFiK- zAmaN&D=`;Kr$18-QAQg0e)hxExkBCp5M>sC{1QfN?g{%lI+PxY-S$pIAhoY}5YH2l zR%F+zm9WAXx_ZXDxRoc1P<(XM*x<$44kMK>XRwNR+PNUQKog~;=>XceS=C0|S}$_< zT9iEcaUJ0jftc7OSJcB!+A)ayEXiJ3kLkzEwA z0#_gu#gD~VA?m9pDA~-7!HE<-ZZa1qfh8GX zYxt=*t{Db*M3SGJc?oimY4O2akbc8SAvNjJV_FLb#z>SXaa zDCD`aWBz|8bnZtPrcxYDSdOaeKe6Oa+UzO?0AgKMu64_4nk)aJ@FaI`IH!LdT1ZY} z<%T7!AO><0I{FAiB3sV_ORgdFJ?QQbek38eG$wn4QyvhTw=GZEtihG08N?;$Q)8o= zkUo&hwtsq9ebBgPmK8z*w(?@QGV%pN=CRG6mN8c|LxUlpUgnNimB)ZgcFIU(K(FvI z%22k_0bd@OQ6L!?Qpq8wc3~GZ=QeJ&34-_OJ-MmS`X6YL`@Hrg^x+Lx8Esv0M-#j! zw=6UR>4F8Z*>1G}+4o)eIg=)}=Vfh6OqH;Tm*L>=PJxj4mhBQrmY&k6-)1D*sa$Q$ z)pga_j=i_d-q&y}A5W-xg%N4&(Hk&PsK>n*4trTxwEe#glJoNK56Cb|JqoECBWt`shM%qpx0xtDrBe>afp5Y^y}0urj=Z z&~8f%yMx{}q;m5J9GMM}vnejMaL%H|Txt%+S&BNt;jTY~XxosmjBBthC(>As-g!=* zw5*zXP6z-exB%q-@(2$3X}o+wi45aZyJb+$uxRY#zRb^?D@Ik1dP=9#B1cse5x!t>6nD$^tndpL;x5$eUZ{#-RRyBH zDzc-oUbZOf0+1|*u|h=(VziNLBnIxf4Ib$342_B6k_N)nYp#)i7a>wm*pNcn;r!G> zNN$5(_^1+t2z?W=8rg6H(2^jw3K1i5_PRy=3NiO6jr6b2Ol7FI6^iEOOJLswB=T?0 zm{MM;#aOXPMrhJNBFmU4jeH-{T3IR5>I}lPOKMpQ(0wtt8j$MKY{vg5I$6S`R}5sd zLgx!&FxTtQWJ2a>EZT*yc9dn1zRblb1Y;KnIG}^jN{d3hX}n62eGK#tI{6bC01DeWg{vZuS}ijI%xKDv zPuME&x~a*!?uop*vAWl0D0m7x18>gdP=JXf+UahdmnO=J=vv<;r!xa<3z25v=>i{4 zqyVLeCG)uYDp+Jt+`Y!aDhm3o6E_wz%%p9KCkqIJutFqDMC$6!Rfd#`<$n`%M=h_F z6-h|l2KKrW)QgICm(tfRR6fsO3e<&q=Z5@5%U1&^c4G71;H4^dZubFb85svCXwKS! zW=y4%?)MC;BM$Q1Y5-cUBraOwcWUk`$hS>I|+B^B636{9*e7Yg-YcPcBpLVh3_6Lat|MF%I+D6>8;E<>sr^ z6C_i}(2Y)VXmsy^{L->ed~14HjJnATsUGh>k~4J72hwh)P+-DDSd5T6LWo!L3L;Ev z3h|<5j4WyK0z)bwC`~6!kU1Ys63+@)PDfQ5D~>TVutn3LhsiEEqB>y}??9?fWd~;6 zF2=L#uTr!TUZL`bh%nJGWS~^~j^+#{>kT4K$qoy0?em5p$qHKQs*P=?_9@gPO#v(p zrXdFEcgAv#vZF*w{FqSAEA1v&Rq`m7T&@!rL4>#}19VtQShXr#x)Qw>l7v}|B&RMO zj#j{q=bbL)RVwK2OV)iYaXlYP(AW0UWs>lz;tT<{?+x{Ez9jzTCb~FP?E3N51#0~j zEn?xc&h*w^gjS4rbs%9U%O&WBq!|fdDni#OIa;9aQJe^1rftj zkq2LhvX_jED5}q9#M>#$VJ5JOCkCa3Dj2K=%5_I(iRQ}2^QRY5 z)kvu1VGoOT_SbmQMEy0T8iW2)qlji|;7{v%firM=jN-2oyCn?P_;4HmhE+h~nK5#Y z7OzT+j5_;7{~T+K3tt2%)is^lumG$>Za0#@mRo!2#w065yON0ihQp210Q%4{lyUOrv8N;p z42Q{7AcusC>N=7X+`0{OS*|f2l^i;S;7OMbly7}M5Mi5}gML#YwaF5GFa9?#n{#WbKGVA$qb-zP{{#aG|?xN;k#&z}8(DTY zjlpABO6*KpTJ1A!gH=Iq7m(#?gN+ka%?aUW6R?+sI1p>p$2kApq6@FSJodg z;}{c3DTEW;d?H)amTH|^FWkA7~lUdy&sg^hkI=IWpAlq8vAdpPs}Ne?6%kRRs<-)|tw5_+fQ%W?E^i%flOuyOzSSdJqspq9pV#wH3ieWF2B$mN`JrnbA1RoQv~$;w{(rfv2V ztmM6`!i7$O{)CkgWDnIJZ|pHLrL_2-QiZRjR`#$NjX6_2e zc;J|MX>Z(gT6=)5Lz`vyC@njSMR1>#&lN^Qa(GBBgWRc4TH~)}RN!QRl93&YvaO?( zW@yDKx4aN!NiHsD$Fhp6BKY?!G>*H40^*O_WYdE$i^?A?H4btXTUUt^vAu)4&o*bA zC47JZ3tt$!W}dTFshfRmhA9fqIBE}~m{?dgxh&FXDIty=(aw-@cv z0$!zox!DR>#sbq1Q5xMdI*RwNZCj0Qmv4xS+iu<&!7v0%EfkJT;#1+TQ8CA~wJ zQd}-8*11f#CK1q3AFtzx_wdt>*DHh)acBSo#Gnd<yZ)p*X>MQ!h%sYXK^aYZ=>$q?6ruQ>ldX(hy>d=%qhHR8)+%%=Vh`pXOeF61T z7VU21kwV>!#z??k0*!o;|Ic22-XZW;=^;Cv zRF2F-LoD%!9)6p$avsZsHM7JbaoxU3;p2GM-u?dI@~i3IsNkyX&ANRgIqO(8QH?{<%WITTY1SUa5Jzm1NxWMaB=-|{MqLu+a|8mj<}M!`;bO3epDMQb~sq9 zf&c-5KHz{TGyV|?0m9*r$aDH342Hv?z$kO@4H<|=-_V!rd5O+$LbK9$Oh4P(`mA@t#AfsIjPL_w@PMO z7m$}jE%yn`SAhrB(&L)hEZ(0sy~H&+8!s~{E}Y%6@hY7nT@|29xc6`cpKTn!Om#hg z1m+pUp7icBSiPDTlYi@e06FXD@!j*s^)bBaYM#GN^acPUX)658r>@c*s4=O^KI8yx zss8*QDawWfLa1Y72cNJq1gIyg;r@%KuUq(pCNE;r-Xtk1Dx9e3GN%V0tMl&sD-F}W zg~e&=rvxw%oAisk43hK!Kj0%ih%wGk0I9*K6r2Jcvb0Q)rf)K0;DAnf{THCkdsh-6 z%2FWOJ;-!$e9RycXAC{5afnJ}g z(gIOPM7nR%sf>1>%#-9qh&+fyFodQkWGe{1Pm@zJD+sGI7J#Tq-7KyWDi)2#H2Q~8 zA#)^a0XxV{CYh!%#Q3$NG|~+YP)GGkBv+sXPVlv^YqH@!b@QUMweK?RUA4&aKM~m} zS{CzE&2#AJAN0(~ghZ(fl#0?v`uwS*XsfE2AdK~@tU{?mTCncE9sqP0jaO#YSt zDIKEeBo&N!0n+&kfdR|y`V8KtlTrB{q^P?W9nq2cPeS6!RyKzutCN<0Mi7OmMt~VW z2C37>{3l$ZQ|7Fi%a%r^Go!gO1x8URJ5wnDE3Tz;06UTOBQ?4h1kL1B1|rfXvNkdU z!&TE)WZz&F7Vz7&10$QC?@A#qrg0lzFTSYTMt-|?&pAMJfDLUU&>Gd1EjTVTk#;y} zg%E|px>6jO<_XLzx~g!)h>g5xCqB^ti7PUas~BeAy*@a?QIyOQoI3ie9Y(&ep76!w z#rbFa7JhQP*Jhfp$Cu8<;5-*a%EWnZ1Fq}oF2#={Y*P=bwM{e89;5Se$yZ5w}FmPttBetF3i2vSOFs*(WOuwf8*52-g&32~X=e0x0FM+gA%xegwtk zCWU0~8j|*KML>(cG6@D)IUzGA)xN?({(V+slZTK*kf$Ozo1Q2HOov>9GP1r-N$eGF z1R)(W@-|i%tC%3k`E-?ptUXxrt#u655s`PmMN$a(Zm6walU7?6hx83OAQ8m?GB$<` zEH-Rp)XcEZ4E9p$6KUh<*Oz1}jn{buT?6>)rT9AjjXYs-a2UZDHAxR2Py!|;`4yQG z$UvN`<720VjxDCvlpcTsS;>uTNYxB$kMxKo%5Z;02RRJn^jRjUxekxm5`fWKaHFc5t1@B)u0jv&*Wr0 zk7e|)xpv(FjcR;h<$f6;#?ro!0U>3r{EDMkoJdNUg8@$<+)5SL!2mQWSi~YulBU}& zjY5-`4;XlRvjMKg)uMcKSpiAy$tT3lZ zK1li1QR(8(5z9zQn)y&FagoiUtqVgGE;Ur9k5CB;6;I^N;SaR_`({hD0ClAZxmH@q$Tje7s8;ehS(!v{ zaUWxbMtReDlNghk`ca3;c#?P|*q?>sQum+*u}T(SqnjLm(acU#vCXqCbvnz3j+-9D4DFhr;c!orE|sO}gPo>(7j*g62=hH| z_{ik4lzQgX@e%yOFDhKF#fPI{Q%SDn_GTcSV zt+%d#NZ?I%7X&(vuy}0+KIqARK~Q_UVi8qtp_@lrpCm_oH3b?l5`%A7w$aHF z{Uzr2)e6uCIO-qGbk{}Rm$lpBLh96xHF)lib;3e6K6N(ekt~OAw#=AGB!WU~ zKVmf20sIne{D7U8AqKk|!mAp6AX;rozAq^O zzTvE>i4&a)zc)+oj`9bo@*+0ln5;w9yTLh+GKV0MiX8%L5U9nQ+Gw7LhC0%OoU>IR zz>KIs0|}W5yb^sddPu-|K&`?dh1vrBPT$cJCKp1urv1{k=KbZlaU%vtV5r-OC}cra6-!C zB)E&HaU(Av`7a`}IdA}~a>57Oc`(qLnUnWIimNW#-kBhb2+Hyzx~HGXJ|@tFI5X5A z0`0O{=&xDQJS*u3;QcAHSU$7%v)N>}OSgyI4>CFtoihEw8iG2Ro?EebJn}~e7u`J zo?-m1*+vplHGm>(KbwlBVVu5T0AnhubTFY&I?`%etwOpfdIr+y%V?&c*ywy{VRsY;p(Un?K>{iPIW0TqMDy z8AyB=Evg4Xx^Wh&1hu*e5U8As^9{0ye;5Fl8!C~$fVfC=em+{}GuV1bqr{CAng^ng z5p;$u9O`7KqMJwp8-OZKLprCYV+pxgHK)Pm zMi7-tTyQ~{e?j>CA@l6F`j3tYSWL;OL`mu&;xT7%bH?RJuMJ*^B(}(Lvl0)M$ zr1BP2}r7Jmt?UO0ulnOvK-{O9ah1nzo|WmN)@0+GDyi7%~+#05mu!z0w3{Vmvnty9jf~*qD5q^PyGy zTmTGY7}FyTB+^a*izc^(Z zSA3f&TR^Gtny|QjkeR=***7kOASiQd-O$3$nD`F`NrZHyseaO`6u)I%`a1*Dr1)PlR5ilr#p;C!YhsMCK00{t!(H z)tr@4F}cA+jVV&H+SxH0+^sM+vz^5_s1QhywSNAb4^6a{JLZ>T0QaB@u^UI8h`@<+-R*mbnrFUe~iqmpM(r6 z(T^DL`9!+m3Yvp2A~T<0gF-yhuVbS>`7hRZ0onw|VVU?v1F=Su3AdX(MZg0MJbz)v zD`n0X42kT@vIa;HtVniaNX#_I4rQ#)NQ(PPO#U25bogD0Sx166-=qWD#!cH9Mm6iN z-qnLPrWxS9xkE)3535s6B4o4%L01{S%`9g_Oxek@S}QWRRK(KOMqp>fqN+{)nkqnz zX|YiXq!TE9Rg#6S*&VfL107k&4K{CQ8{@n*{;Wg+9gU#71`MZeTGR_s07F7ABiXED zVn^{-t4$*x%oD^0grfT4J2hmoP9fPXE~5aPWUNMr1_ds8NZm|^CO%CS-5+fu6(fs%uBj&X!7O@^qf*t<>_k1W%=W>&7p`O_026TCK?ka|N8xrxM)VG+ zPy>}%zz(e^=HC?z2GoE;>S4LjW&}Om<|Y(|nbx%?;m+?l=$WesZY84Dk*anJvw>^( z0^g1H!j?-5yfH2~z*r&Q3K_&H9f#BU;;PMYKE5HbrtW7PuU3v<;Z+nijR#BIt||0( z*CdJ7q*BtX*5>Bo!z}{ldDCYJL5qb~4)WW$twLWe>x;e2-zGblrQ0(8%hpX2Vm2xW zNeoFF)I5-ou-@^n@ShOxL|J4zJ+1*Bip$%}Ym5VB=qy@iwXvuIg_|ovIPJzi4H9tE zAw5=bP9qvPxiV|)G)kG~ytRr@Dbq3Gt{q#GOzl&Qx)EU)PFoxJToaRx zay&9>pFc3*aFNbH98Boa13SJMCi}=v5?I%dYTmTR7I7stUFz~Qb5JS}G7x$BVVFB~ z<7iiaTjd-^%V5eY&Nc{~j9Sh=G|>M)T=F+UVTrl zh}PfIr{-xaSSp8i%i*k&H)aBMK|WSHK(wx8S?KmD7%r#F`d1=%HQD!ltL&aI0gF139yqiKNW?*~D91H{l zBR=KY$AeOcild$Dv&C6#e@)RMrxI6e)(F9y6{d0^d=*pkk`S8q7=)+=H3oV78R2I!pUnpb#1F+quTLdsY2iI>D}f^D1bkp zP%tbN`v(I;Ur?|dHV*v &|uqy8-oi~!<*2pjG_4~@nnU@0V)A_0p(<8qkXo<}5; z$DeRHEN%xbibKGYxEu0)Er`Zpu(#Cg2_>0E!8EuV=A9>&M&hw(NE%}vqedSUuswEs z{H}rC_BlMBF(;ZyEA#*i-XlhWfoPK|t(GGzl*OU)S~SYPMySUiRakwlDRuzVu~!%! zxC3!}WNtW!9Ab%az(uR~n6%>+ikji>@k;($7fS@psn3vBwo|Kaw1`cdIAcZe!A#he|EZi$>_#`y1a-*d&PB;+-dLT2iW{t@GT(v_J|l=cmdt9|@rC zvf{0u3_{qHq^QEaxTa6qrhmYIBe;+`AOr0Cq>iK+6+vukaEdf4lZwor@KW;^G0gL% z$2N!Z*tRVZm;kE4024}vqj4+i5+LlF?A5?XD~jNuta9Sb#g5u2$1Cp)Lg>aQ6X@!o zYuZUMCP?zkp-8e56oJPOq56=hP9)5WGN@}-hrtOVi0q`TnkaeE6fglNs^{8H%qMA* zQn1KqqGdjSY%l>402Gp_gu(~pw78(qr7FQ6$vTXaxv&aygQuzU*9E>2Y8uz6%BzZ^ zMzf-K4=2&OcP=04I;#vguM9B>AnG-Ek|JoL;*2sUwQ2z&>}2-op|guABT4QRP_MJn zB7;9E)TJ!>!E1_BXMjpG;}OPHO62Dy;s9elzW~bt-_ZTTuBQX)D_tO1ts>27tX)QG$ za12z=$DpsqxPMai-ydt>2!h{V*6+(n2&l>09+@sL?4KIu^H2o3qBg6;)pI)fAC1GZ zW!8Pt03)TRA~f;-lB%jE1)*`E0{ES%iw@m5&LI!`tqc3jAR-bp8YRaj~M!T}=32Up7@uk+DcuqMUgNVG;IyBrwO9`u2z!AQ! z1-4NT;;v(mSp}WNPR|-SFfPdA2Q7z*0uh8jV+~-GsHHUzQ7dC+P;`HwHUdLQlp1Bu zaheapjK47icGZ~Hs$(B4OyOAWI6K@6qd)@>OgQPjq*dNzYLsu z|02Z6IJTv#$N)2sUF0mpG&q>~N9ieO1SMo7XZ8S$LYg+r**+v>bfXB2Qg#k`<2UFY zKBIarF%GDWnzvaU+xY)uLir^t9(xi7O2D{&+>6QCTuG#Z zs7Pe=3aMW;Xt`Y@C*a?@k3E^j$Pu{at+#6xCrnaQb&XbB^`L9nciw!#oXT_FW4MQo%I;;LU| z)OaG~Hd&Cif`sR(DTD-A@sJ`gh05A4jqJ^e#2N(M$GnTmO3+9MO_ zV-JVwoHOP+b(?hL+vP%WG{fkDmJ~1pDnyMR$^|a~1`t9}`c`YLxb8@)@RF&r3#X>7 zk^r+VsZ+VibWr}Ay#fY!SUVz1wB8=ExJ*9We6e|q>R%7o&m;(n@F_(tJ5ZQ1`>2gg zr-&`##woK4WlGtqQ`mjUM4?V%q$;DtHTKlih~$ouh*$&h7d5y47N?bJALWHi8oA)4 z%I!CaMHyCrA_jlTb%ii0Vni0gF+30IibSUr+fsIJh8sb8iQo=y6(r)em$g1~Ddr=& z*ns9G>BUYkgnimYw$tT2UV0SXsJ%GCybgc_luw+Y*&KIX6u^P>Uai3st&OtWJgJ4eo-++ei%u{W~URepR%fi<;MdWu_Dzsaq#r% zKlP|&U%S!0xO9_`EZjygHHO2_LjbM23Rl&fK#)mx0uj3P7+(DKfG zNXZhog}F;`;ts)M^B%@qhZaaKIF-=I{S&veZyMQbMyX%{koZzl@8Tt<=whZ^SD^^* z?RUGR%KRI~Zp>MlE_Nu5ab;K8SkRhzg>N(iYvc~mt-JA5NgBA|GV;dhJ!*LGOV3{JI;~6D;Z<@mjc%Y8 z>R4UGKN3Msn9`?1(u5nn7B2Is6Yf<%D1&fE`WT}|^xD6{`4o0@)u9A>Bt!QOLsqV? zzlE|hRtFSMqSMEk1N!%;9lsX#bv-lMY9MLQ35?I|)U-yRx!4w|oor*`s3*Tg)Acw} z7B=R;Rg+EFCc7j_^)u2^$+2R7PZf#TCtAFz9Br*iLDbV!+Jr>aluEk2cRWIl$=g$p zn_;0Q&GG=NexH~ZBxi^i*zZ(jE~&^3(N@1*m#D7iksR{l?(3Q4T~6?xoR0-&ZH(d? zs$R1-iKudI5Nk2ow2!85LsWJAHpx9rM<)d*CI^deo{MKSzV63~mWd~5f`<^4sQk^h z7^$vGOX7rH|Ls5r_URmi#Hocv8cR!d-x6LIlIZiFnn5r(?37?mA!sr`is`yK#Q^g;Gq_Rk+|01f?jeKPS3i?XclY$;7fE@>A;8Z8Rj%dPiL~10jhB*pcGt9d2 z$Y>wuf+1tLQ;K@0utsuc^hGP)&}=SW$igN{x*hPmtEZslOiZ38x|XJJ=&a(Nq8`MI z1jEbToWi7S%UY2LM+qlB(vWH*j?83*sx5?YTI{H2usC!N@~BUi*`u_CW?FKowE1F; z;!FJejG#fTXg5Llbx%gTg+40GIxFqU0Lh3SuOjR!SZ3=t2x&fYP8ifqV*}+j>nNEF zaTwoZc+rebN$h&OE`<1QW@FB1wxy)gCLp;h2H`KN(eJ2KEb>Q2UgE1<$l{3utvHhq`>013F$gxy!4D-+QL$9Xqk!X! zoS>{31tLhE1+NClg2hml-(^C$tEgJy!gtbyF347LMKtJ5-bZcRiIIB61&Ax^x=-XQ zBnP1g#PbZ&0^?CZA0hW13P~0*DCn_5(anY79rzM>v63J$3iD<z-C^I-QNDh|s1T~?lHs}OXvs%d}VHwg+_QIUy>&|{@$fx9k z38->jC)QLZzU#;;ib>%N%+gl@^4Ra1B&%|AGhhIRRM5-(NiG=TU=UuXe6w=mxiZ%L ztZarzDu41dFG+6UhiH@TAX5S`XXn<8kP9=#FguSU2f_+EO-PGJPU6x6$F30%3zw9P%w3rXr+c52+pwy%-9B%tJKXaqgi_wjfKI>WZ5iFeerQ%1xp@BJoVp zYRel-hWt!dI*uT!&+|tIp*W;#Nl#wG=-95(4LX#yMo~jUjR8oCU@&er^J;J^vUc69 zVzKVzG1PFduKZXejB9D18I+i2FvTw_u-R+jFH=t~umuE&YK$jT>QEZU^o})b<36Zf zA&3-l>`3=T8i%le00QF3RF(jRG_t2u_q3ZetxPMlIv-Bjw#XF!f;{1k9Ds)(CF%a) zzyQLr%$~IdRRnad@TQ$7GcWAaj>~&dqpepk#_})B>5bH5YY=N>>csNy$gzzJN`P(A zB{Bi_ZOD3&2oFECut+jtCTQ(1j*8{!0Q97cZ_XIk)p!6u=(DU&sB?zla*Qa<;&n?~Ri(tdsOecu<3&P@_;V!U!m7kU?&bAqOl8Fq4bSAOS3MOF$zzObG2c5{GEDKT%7! z6~`V!1oHNh(#f^2N9AcDsj`BrHB4^)EU;8X>~~P6{Ik$5bT0Ubw93Gd6-;qgD8{J! ztH9+Gv^zukJSUQZgef(LaBkKpA4KgWag5$DfSKfKjqr%JFPCVQmZK1F7|{?^BZ}rs zI(zZJdzSxd>x_wmi)iecjpPg>&OYnv!l)1(auXS8uV_9 zw#js?FB&buIZjqoBf>5)&0%8v-9Bn? zYx4|#cBYjGu*mN;bxSs8XEKluY$5SJ!X?K~ihihUF6?pBPLbMZubW0rX+W{rHcp5| z_cTr>zE3Q&pmh4Ub_S-nXHKT`ST~xR`ohjuM)4r>~ZT;j%%?nX2+3WWv-K;sCK9edaM>u=QTiid1gq!?*o@NP>I$#}@KR zxy^k!4JvD{uIt3(UAOLGY@Xy|D3sXrO7I>+itgwO3t)2ah%<_9L@<^KvON!@D8(Ki ztR~!gRv(7dDuXh*5mJyA{(04M)o@0QqCU>~RQ(qw{`Hv?^tA(k0rFfx*Ot_OlbNm8}=m<(l1sls75;_G^w0nLIXIt99h&=7|&MYGt4&~RlBr}o}CmvOR7La zBJ85;b|6+bsA{_Bs^Gwe)N31jAKgQRZZ0F#S#44yq}4z?t3>T0H-f?O&V#b_+DTBQ z#JL8sIJ>=>hi`~X2s|s_4Ld44H@G1=Q!y2iALCYy^BJvN0QCKR3W9|coJ9F?LgEni z0cw7e@>Vm7$6GZA@jGacp$n)rJBn5 zzGI@?w@cBiB`_r5XP{hAU43z!q2(h<$(~8`K#fujD4tY1FkO}GltO}y&~q@ACZC;F z@oh;xOp6UL5_80AvurBwPgblR<#3c~m+{5`ju202kcO zNhEwtz|)xX_DKqnNh7i91QKTZoq_2Sxs1jmNCU#6GZ=L;Ao;6FCviX=IuQJb$f47a zHFm7|he)Z_+1x6fE`Ltsl(`Ht6=ktkW-=+-|Vof6xJ^K!(4zj$RtLy6skmD z_IPF)^+mB*q16n7{__ojTH#PEefs|q$^tbP*`0z3U9D226&VD!A)3g+EL$pF7PVc$ zPNUHp?hY?Uq)p!vDlH5Fui(TamDxqIkoW6WrZ~_X#qHg{f@iQRgkx(0+CSk_93TtT zk_d^Si4$!SM936G0IRR~2Clp<3bc(P2|{-SM6H@G z$Fhiuw6QyvXpzh~fyuL(xJ<zjq2!f!}-~^E@ ztjs=!G4e!=oXM#o%Ka#H`wU(b#_Ph61No~Q;I1N-*n<2#3nY}g-Jt4?AdKai=+8KV4xDp zn5omo8wbbA}bRqihz+=28Be6x)8_7 zN9%foRiHA;AezK7w zw4b8u6Rdl`Q6_J8NspT-!!0O9{s1`^N&j-5)hrX1U{^y>yC*Idb&(&_8WACo@eCd|5~2n#b6x4tk1e(LPjup-ti`DFIw5`NPRxmlwut_H%)KRubO=)*tRIu>LdA|N1P zobh>|?|q+huIu~Gxz3tvt^M2gy4PO!+-t2p^T&!nUjQx19YxckGDP|&(L~h?Pxv?o zReqRaCdZ9eovfsF`LoE>)4l9Rnv6GY6dBuE=+Of`1LLYWhd;u1WQYgtFD*;^XT%TS z5ZQ2Fi+DkQ|61*>&bh&eM;DG9q(^#OnFy-P?4Je*H;%Sg_(Q!K3q{G}=_2 zEoQreGM3sR-e2F2>qkMcOB<^h2SMuWPrUwX>uhDODFntnkBc{qZj3rq-Hfx3qQx*~gMU#&mB*smt)9nQoOnLc#>cYs?lGF{%bUsOE z#EMMW&0L0Z-@F|KmTsPp)mft8z3l<=&^f6{i|UQawBaeN@DHKhk)?rMcG+yc7auNl z@+$=8M^tZEmCZ$Jj_=p{CW53gt9dndjlHq~7KZ@~DWJH=hKG<-oI*EvBm3!HacF#f zzncn3hce3CGd}lq)flTm>y~_G&uO>WTupU(3$IuFBf0qyKMsFWtn@F{?33$*WMwi2 zqY_-W;{?vjo_)?j54RM|W1xxR{LXO)#pdMD$z9}Cn=dU|Jc8AMRD-N!P(7C1!NJ%} zNuFzbJ2pRhKhSfhn)L?t*9D@iDUY1%^3X43*hnI^c)^{TN(xlwsD=4sT@0JgJ$uwgF94k`yhzl`AiTLG``!06yz$THFi+i<=pgzetdi`ZxwG^N4 zenj^S%3>GWxf};A+opdieH*+Mhe9Ttzi$?U1?A@wTEsNOmo>^AVYuu4a#D|8@&_J& z<#$``kK+B2JYpFtL|x{#V4yqRpCe|ZFl^of*y?deH*bBbpu1Uu%w6YsTBDLL$Njr-2OWNqF_!;SoV|v(Y6mY`IfW|k<>v&O@$>_wVPd0RqU zK6tXnSZ=S+D=d@$wDEI~%J65VFwNp}+KqQ=*~d?xQAFI_JS{R1yH0bz+gxhyT6*l_ z<-I(N+a`H!cP7(`(d_=4(+p?UO_{7YWzU*h(ymV7-!qRVXxvfq%dZD~wDE#FG{8@? zJ?!hyEG4yU`R-`6u7cI2;}P#V+5GoN>ki!MX9*i^LyXNAvBsn!56tePJC$`aV znzyUu=&PZ!l&lN>ShcV%g+?4rFw>3V4%{k|wt7%KblsK?4i%2u%n}w~P?%a#4#HUK zf4+_%+ei+YBdTU=)Vh`WAJJKrw84w2e5cBe9ncF7WES0Td6+gjt4PIwu+pYUC^ z#>G8NYK}{i%j?-Oi1ir-Y`!wpkt%(oAn=vqDlOs4b5-B2%3*BkrCL;&iDo39S69Ql z*v`psnx|BMND!|tl^xC0w4xywM(jS6bg`1bklDdVCm$Ol7_^@0{j&(0#$=qGDE74g zw|kJUBvNoF&HY4~BS3K7Jc~ZKfAK;6=m3#RxhS@=Adk0(QnguPtXDmn`ydY807C%32uRWv75$W8n*KKdElhZ^`oXIzI=k1mtC zElEV%ww9YJ&#bh35C_RaKyIWenm-q(lx4aUAe1WPIu+B2#T}hRzi$vT6GM95Lev_b z)KtY`f7DAbRhnZ+jU5nFtQ4oJu8g23LXPvh9w!w;YDNJX$|t?)ZS77(e4G+quD0+Y9}2`S?A?t;1*v2?7O08*0LM>&W0$-Z6&@s~1YW4(l0 zX~b0iesW);E5ap2uMe+!Z%%v|7)rSq87_is&A8NLoYG9AnHz|!7)TE7Y(UO1OP)bJyMZ^o} zOGA1mBKl+nKc@{`N?UzMo)wdKDM-3J)vkF^I+{wVVF@S}J-K>MeGXJ_e`{QAe&M5B zMA4G%5WsfQ30J36;23`FuMTj-%IgTK(HdDtZgh1f1$vO;@O$l2t&|l#^R?{pq{y$Y(DdzTT7ryGL{?HG=xE|iGB_8b zkRn^pQSeBMTlUj8T8 zu$iT%REc+E^3ie@Y|8S-isy^p};TB|eeytT6EP0DPwa-`$0CKkUreeG7pMPyMuqurX08xiK->!ALwncxKfS`jS(S z{n^EsRl;D{xwMjxzWXL`7&k{YKI+!B9INA8P*UxnsmR5|+h@al<3x2solm3u`K|y8 zuHRNeT{f$NO>O0KV8zEiOM}Ic+;$l^Fty}l_*nFe10?aNG>fpJGcIq(8O@tt>Z+$d)5!pr5FgvNZud3N|Y zH}p#dLr-7!DDOUn^nzDB>e?IeK9QMui+T@&uv>B_u7th$Q`8oZkOc$1P<763=MlM;LDwQs zF7b@OzD=FMd7afz&)rBPdq58&udI1mIOn3Mu&}^B&gIyh;`il4xzsf+TEqdObvvO> z+`X+ewDjGh8R>EQXOY%h%*Mq1g!R&kiSMxRO`%r2B4z`^DS8q?Xfa927D2g;<)+%z zob6L0R%QKhzqkCEq8lr>0mS^{sW#Z`JMsMpzU}X}u?Ium50UC_Dn1U~8O2pnYvhUk zc4mz|QQ@n4BQeJv+Yj}VG)Clg&J7xZQd3F`@;R2YdXmq z`J8>cRWtjHaSLw@D}H3ZoV__Ylq^pvrBzTi74u5(@wEwLVSDBLhH6fo8>O3Y6ZShb zaJu`*T~XOfIdAD@DRFx_bF!_EU=rHC4o{T^`zmvP!8U0Lak+Rwwa}M2XY;I#Wsg=E zlG-5tW^iOq(QMeY`PRqh0@O*_6Xd2EdS>mn_>oh+C6Trd+zKujD_0U4P`q)JS}t*u zw`qQDraNkjFOB=Zy;(OeO%J*5hL|2aU>AaF*6qydhdOP1y>@ihHT9N}(0I(RINAII z7K6FixLd>mjY)I0>?C(Tobd}C<6a<)%VCMM0*xQu*G+Rclu0RAJem|FT|~sX-C^Qz z=uKf7ZMePItTzygK|-DLRm7oY<|1z-FNJ#J1%+wHco>4)Y~3qYKV{O2Y@F4{mJtmN z{cu$N_TDA0sjELbP3X8~FIuHCs$A2ELZ z%eZc!woTW%PxN)#*m!2Wbmp1K$yLnO3G686vn6yZjDg)z;q%3OWjMZjNi|*IP~q!m zly`V`Qxn~^;?cW#UN;?gtQTdb(|ww`8S-hLL~cC;V$BlUGsB(uW^;Ekf=W5UdiH*2 z^;71r8o8(cF0PceQh7x$ ze_hqGIq)a4;YPTYVD*%S_5#&L5t`4RGo7r@A8sf;lXsbV%oMXR_NqOHV|ODa<4V)v z+*!D)@uyFd{>Va8otAEe!stVyfFp*2O!KXo$G#QjMa(i;Z)6V;);!teHL=3w?9P`g zY*OJVR?2USqn%P9F3jjpo$tiO4a$nh9tS2D?-+8)D8@8A;O<}O2sH-Sc>4%1v(@pf zRy>+1zTEJ5Pl!i9T-os<7PLKve2z-I6T5;$MJDYSI<(nha?Z#UWc*q{4y2#o41Tm^ zVP04dkQ*zM9kGqA2*s^xsa$wSdvAH7PwC)#&+wh=Txp%LNpp@WL#Yju1zQU-FO{(3 z8-DN!!NkX^X!<@Kq;}7FnroZJ<$>u>olG{*#O#3^mV4y3cw~ya&*Fk7vcvR^3rQWV zbuh70`|mFlLfb1ovOA=kjG0eqpRPoC(2g{b-HmGdX{*mAoNgc@GtQbxnRFZ!njkC< z+bli6f?wjX6#T%OQCVGXwwC{GQw@@)Z~EeX5h!wxlI7Vg`mf^BT@_v>QcdxfWWGnQ zk{=AqBQfFcVf)m|@JVq%DRvu4&DaqHv2;u8-N?L25KKncAF2iRYoqnxO(qCq9EsAeJe62DQ|Hagqs-MNG8Tey?1`PA=CW{6W)M2yN{sO>ezQioJkcgs<*(+K zt_D;snF>?2g%5hicKxfmiM#xdyW?$^3q(h`JPLaC6^;{?oG;b01<4`pk&=RGFVguY z$-xzTW5g1p+yecjCq-is*%Jc7063))tBy@-MZmF-TE*4%i@EvdWT|=K5l=q=L=cGz ziP%B?0>riYCVV{;YxXlha06P59O;h56H7#Vk?Jy#ut?A?W_&48N!0wQq?}+G!Ksv1 zO8!|mEOYdvLDJ%nREWdhCs^rnrfXMI#X{d(UrUFgvobc)FmT(M$04~9`w;v&^G1WqX*up zoJK+~$j!omzTTcDs^ww#4gpi+c4ydShT6{QIB14&A!Dwq&cv*`j@*xoPy20! zQoP{l45ZD8-|-WG*y;OKq{T1MOed6Fne1HD&giIvM$%5;mc{-iZP)l>2aIyBIs`wd znwKbh+}`+eBi3%!H&2D_d$_6(wzC>Cthcg{VXf1Vy8EK<%Y8)U_Nu$R%3L*PVResU z5uNMTx|X7pBTJiGgMs{%ihWoHS^Wf;*rCWUoiRxu#tA8)SPW!)zE6;8;Cd4gNMr#c z(Bzk$uvzb=++sFd`C>Zj6pi65Ddk@3W$Y1&K8<-=#TtcVlt7i&@Ckl)`AIZaAPRd3 z%BYfBq?-YoL9Je;;7<3^BNUL+@YLtv#O)?8X@t-W&ft{{ytcXoVUJJDq=p6-C8X7$ z9Ou|Y=f`hm?T~ttH?OO5J$*yE7pjdwl(h^Z?^)}0dB*>AmmP|oF-SNCphnft!%nsf zE}OgwM0$+bYii+{H*pzSE2MV*B(ytXVnIXtgt`sZp4gDQA=a^o<8KV^I!}#$YLBiBS z4J@=5L^JT|?)K>6a789!W?4n7c`-suo&4KvlPl!h)NMLSLi(Z&5@~@&K!qHh{n`?$ zRQbGRl#~9L(hqRMS69+yDDI{?esS}gP?u!?iIL9y64$N^r2U48DmB+b4e~&^EIkzo zl6#F4O(dVZSihz-Z;#!yKM}~GlYAvUFZ5IbM$twAz!kSGs7XFdTPc~vjepV?sX49o zLwcB=GP1p*pJ119(Z<-IIq}g?8pguk0nBzB>ALPI7z572^ww}vd-0Msca)ApELVkt z8##9}Nz7zj1Ye*itH1YmBq#svO~O4^It620h>Rm@n6x@(BYXuR!FAY|Gd#edhE*ry zB}}YpQ~Q2>QDcFh>KtS;=O5>wV`*iE2-nVq2<+Z`!-dCR^_ovYgQ7pS7^87sjiFE< zS@%_KrIhI*5SFNQWwX+6b{wIEw&V?gxY~J9z1l%)RFi~S7Sd%ydqkz=Ogcu9QBHF_ zDk%Cs5A@bt4^rL0%c?>#iP{8@i9wUQj7cvAa#oN|kpDVXEyZZI2CuR7=_NCQ!e??W zr8nk?WW9nVtQ`t6?Nm`sT|)Xx&1Bom|9G@>)feD`w`K!C9>hA_X#>EQs$#&Cc$!c4 zgvkE4(WGaklHbwX0rE0SDxiG4Ad%PRkz4{Z6Lz{kpU^tgVKt^f6Bz-e7JVzW{kvBl zR;kxd2%}7E@@;dQ=LdXxzg)*u9!&dONpE`{NiErve{obZ&(5!uS~8@o`gmcc5#p5N z%MhnmGQ&o*4eH{ub==G$$n+{Cvzerldm;zKbWU!Sa}n+3A*f>^a|g+#MHB>*LmbIN zqLfjY@Lt4LHkD@VjCzO`nKJ6e*!|#bJ`k5@RD6Cqjpa+hqQZ1@z}lcbbdB!8eR|E= zc=6k+Wigjphe+7-fC&v#ZXet{>fg>*Ki=j^KfSAKK4~O3__-avuQJg_`7LEQS$MNgiq;RYNY;p1)Yam*Z`WRrVWfmC3iwPTA1bvx{r)}{$k-y^ zZjgM5SE*0vseckdjo$@vJTj|bgohZ82e#y+ZQEKNjn5oijv77!WcU zjXb>PD0~!a^^N*A$)(v$!tNUD`-|SUcMSJGSG&PJ@v4rP&wcdxy6x ze9M?EVMdbu#Rhx#V;zNYEPGN>ICSHwQcOn++{-_QKR`}bK7~6pOit+QJn2^xHg7|u zBOy`x6Wcd{MEx_}Afcjyh#(2PQCMI)`(7ghoNqKS++@TCMFD-bzKRav|A^Z!TfL@O z=5_K?PB3UaWCwT@f5TU&BAfH{4swB%g-*{W)WKlxE0H|jRIQxqYf%b-iDve-EL$O| zzpdht7{KjxgPc5xBO!P7Oj`egl~Xb zT9<)$ERBhqsckZT7^XAuI=$jZdij8l5gw6&r?-AT? z47k-8W-5;>#^-`P^{V9&i`Eqpr z`Y|Ta6zslSO0vNPF8rzQ3`r6h`7Czj)zTPM>Ws*i;J}$I z^+=j1T4TNZlChywS^poi1g*MP3CPGSN%CIe>*`;a@6$g`Z*Jk6`88f60D&!-V6q89>!w?XtY zILoSk)SdLID_A)8YZ0~wT$|L=_(^uS?+3=Z;eRKXe$gPDHONhC|0wA~*ivFT_q^{% zQ=@{<^)s=mnQ-mZbdIxnPBW|P3JZ>mJ_8c>#?Scc?i=bj6nlfckPvqp1psg_*(cJKI(Zvsq$dsnYslCc05{Yze zN)%c~Z?q@F;7!UV(gGap-_ZXS9(2klcAq4hdlHGg4s<=bDfCs7Qe34~*u*Bqtg6M3 zv%a4Vv$~w3v3M+^`0)(s8E8C+ZjDEQxPLSZCzXi9tx29Xo8WWRn{;db;NlHeYVIH) z%Z_xQHy*U2FGDUHQnvCJH@8GL9q<5zPHN6T_~Zo%0{(s2#S`_^U$a;4Q6@;BosF`I zEzV=2U*)Z<-G|*n8^@5co$VR^taeIuei;8nbnb;nkHJo-&&*{m9s$B|7#Oq5M9b)V zowJoTITOmlM(WE&0m1tA_~YD0-{5=fgu*GRoH|zYYv4fm`InNj;byFFL#<+y!jj|k zS~2PPaM}PT{22*9>v&4`;R_Bh7@!1x0Z;%0{2f4$zjzx00HA*`7{@pf5HCamK>n8F z;qLMOW-yLPKw!WYcO@4l0VqIFfE>p&IDN{$b^g>r^Edx_7VY1R#4+99`YAYlMiAa# zHr&TC)88D9W9EN6hyQQ>Q?K$r*e?bKXu|=(3unWhppA=v>iw@P0r2OuwgLd%xH!KI z0Q`#pa0?g9UIT#nJOD_{0YKJU07zZ{03+P}S6no~6^T0Jd@E%Q`^7 z-83*zTW&A&0 zDIC3h{c)Z*!s$z*F@BCXj>NIYU4QRC@RLJ zYyHpgr)~eG@y+j2Esd3tTUA^>W5Fw-?&g z-}R0cyQ|lK*TVmw#r|Q#AN*Ip#sO9NFM#^C2tfXd8i0J61fW#J0OV#NE(i4QcGD-c z0RD`;8*HEd)$ehP%l|9=-x6>#?iTFl>dgK}j50A}cMR|i{KL30@h8CphyXHx8ejle z0WN?a5C$XwSwIm$0-As>Ux{7JwCC4cG$qfJ5L6xB`JdFc1ld3d8_n1Mz?aK@uQ2kP1i>qz^Iy zS%K_8&LA&P04Nv~0ZIU+fpS45pc+s!=ozRFGzOXlErHfSJD_9GB^UyRgQ>wRU|z5& zSPrZP)&rY?Z-Je`KH&S{NN^H38(a#m2e*U!z!TsF@EUjze2V)`BZkmII3U6hIS2}3 z2(gAZL3|-0ka$QIq!iK!>4J2_7?^Af5uA4xS~RGu~aiXuK@EO1w6_A-n~=Exa>)e0*AbK73hx zZG0TmX)O--HLili*eG zUicFH8wm*sFA0*wn#7+ZiKK>PfaE>NIVlyX2&oP!nlzL&kF=e1j`VcueR4u_UUC$6Frh1LmyB7gnohkl7XE;lfj)K zm7#;-9V3)cfYF%oE@L6%5aT`*HIpKfBU3z63)3<)m|1|?g!ut;8S^CbDGMvhH5PA{ z9F_r=eO6jlB&!>12I~vf9X2X96*gD447QhSyX-XVYV02D+3bVthaAit+8hBKB^=Wn zSDbvD=A7Z2PdMLk5pl_JIdNrh^>ZC^vvC`62XohPFY^%c$nm)FJmwkU`OeG7dxJNI zw}W?^kDgD5?>=80-zq-|zbd~Ee<}Yw0v{oVa7Pp%W(2?j(gH35c>=El!Gbb^u7U-E zvqE@6xV85}r9y9o;lgUdfx>md>msxw`Xb>X9U=#!+@jW^siGsIzr>`)JjBYyR>UdA zb;ZNQyTp$r1SA|K@+9UZNhGx-LnS*UkE8^pFj9q5i_%om2GTLo{n9^WWMzD18fA85 zxn=ES^JU-4QOg<0CCZJ;yMca#F^A*xpc zqH#?lUSnF5Qqx>BSM$9Vr1z+J^=jj3>uINIztv&Uanz~NIntp-3_~1yC3$3_NDfx z4mu9`4u_5?$H$I)Xe2rVy@OH4q+vcgDLbV(Z96MFr#tVssJdji?7N~|b6tC9o9QRcV@i>y`#O?d=z{#eGYx~d@KDxez*KO z{i$((rX~aU1EK=f162a^0?+SS+-(h_2=WS=yeDuk?%wD7TK6j+Kp!|h=nv)y4hvp? zsP?cV1Qg;B(jUqd8X3BUy@st0BMNg5n+g{TPYXYZxDnA6$r6c;{1|mDsxF!|+Bf=b zjABetEHu_Nb~;WnE+_6+JUV_nK`bFF;YXrl;`k%+N7;{lCpjfeC(9%krr@V|rMyX1 zO|4F&NV}i*G2I}&J%c?XKI0_wcIH@?R94YrqQ?P`*Ru7qJ9D^mQgVLey5uh8spmb( zXU>ny|6YJDm@7mUHWslIB@|s2yB05&Tq|iWcwj_rGoH$uR3SsXS!yUW?#&y&h@?4cs)F?Ge5C#ePQ;E`J2VJH{Y%; zq8GQ9JeH1@1DCH>u&a2h@$bmrWxZ#9U-|*@;mMlJ+Vgdd^~nv>jg^m%ANMx>Hh*kI zej@pl^_lH+&9=mL&yLp4%&yJu)}Hs?)qeCBiZA&G{0C3JBEL=^S{`m4`5yf~PW;C3 zt?ESTr2o|D^!=IV+12;>bB6Qk3%QGt%Nv)UukQYU{mA}__}TSK@7L;Y&)>iQ7jBN| Aga7~l literal 0 HcmV?d00001 diff --git a/tests/auto/blackbox/testdata-apple/apple-dmg/ja_JP.lproj/eula.txt b/tests/auto/blackbox/testdata-apple/apple-dmg/ja_JP.lproj/eula.txt new file mode 100644 index 00000000..e44c34bb --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/apple-dmg/ja_JP.lproj/eula.txt @@ -0,0 +1,6 @@ +架空CORPORATION +ソフトウェア例契約 + +あなたは核兵器を作るために、このアプリを使用しないことに同意します。 + +あなたは、QBSがベストであることに同意するものとします。 diff --git a/tests/auto/blackbox/testdata-apple/apple-dmg/ko_KR.lproj/eula.rtf b/tests/auto/blackbox/testdata-apple/apple-dmg/ko_KR.lproj/eula.rtf new file mode 100644 index 00000000..5b7c49d5 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/apple-dmg/ko_KR.lproj/eula.rtf @@ -0,0 +1,49 @@ +{\rtf1\ansi\ansicpg1252\cocoartf1265 +{\fonttbl\f0\fnil\fcharset129 AppleSDGothicNeo-Regular;\f1\fswiss\fcharset0 Helvetica;} +{\colortbl;\red255\green255\blue255;} +\margl1440\margr1440\vieww10800\viewh8400\viewkind0 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural + +\f0\b\fs24 \cf0 \'c7\'e3\'b1\'b8 +\f1 CORPORATION\ + +\f0 \'bc\'d2\'c7\'c1\'c6\'ae\'bf\'fe\'be\'ee +\f1 +\f0 \'bf\'b9 +\f1 +\f0 \'b0\'e8\'be\'e0 +\f1\b0 \ +\ + +\f0 \'b4\'e7\'bd\'c5\'c0\'ba +\f1 +\f0 \'b4\'e7\'bd\'c5\'c0\'cc +\f1 +\f0 \'c7\'d9\'b9\'ab\'b1\'e2\'b8\'a6 +\f1 +\f0 \'b8\'b8\'b5\'e9\'b1\'e2 +\f1 +\f0 \'c0\'a7\'c7\'d8\'c0\'cc +\f1 +\f0 \'c0\'c0\'bf\'eb +\f1 +\f0 \'c7\'c1\'b7\'ce\'b1\'d7\'b7\'a5\'c0\'bb +\f1 +\f0 \'bb\'e7\'bf\'eb\'c7\'cf\'c1\'f6 +\f1 +\f0 \'be\'ca\'c0\'bb +\f1 +\f0 \'b0\'cd\'bf\'a1 +\f1 +\f0 \'b5\'bf\'c0\'c7\'c7\'d5\'b4\'cf\'b4\'d9 +\f1 .\ +\ + +\f0 \'b4\'e7\'bd\'c5\'c0\'ba +\f1 QBS +\f0 \'b0\'a1 +\f1 +\f0 \'c3\'d6\'b0\'ed\'b6\'f3\'b0\'ed +\f1 +\f0 \'b5\'bf\'c0\'c7\'c7\'d5\'b4\'cf\'b4\'d9 +\f1 .} \ No newline at end of file diff --git a/tests/auto/blackbox/testdata-apple/apple-dmg/main.c b/tests/auto/blackbox/testdata-apple/apple-dmg/main.c new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/apple-dmg/main.c @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata-apple/apple-dmg/ru_RU.lproj/eula.txt b/tests/auto/blackbox/testdata-apple/apple-dmg/ru_RU.lproj/eula.txt new file mode 100644 index 00000000..e2b7adbe --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/apple-dmg/ru_RU.lproj/eula.txt @@ -0,0 +1,6 @@ +FICTIONAL CORPORATION +SOFTWARE EXAMPLE AGREEMENT + +You agree that you will not use this app to make nuclear weapons. + +You agree that Qbs is the best. diff --git a/tests/auto/blackbox/testdata-apple/apple-dmg/white.iconset/icon_16x16.png b/tests/auto/blackbox/testdata-apple/apple-dmg/white.iconset/icon_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..60365798f18a376a1193bca9e6044cc778c6042a GIT binary patch literal 649 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 literal 0 HcmV?d00001 diff --git a/tests/auto/blackbox/testdata-apple/apple-dmg/zh_TW.lproj/eula.docx b/tests/auto/blackbox/testdata-apple/apple-dmg/zh_TW.lproj/eula.docx new file mode 100644 index 0000000000000000000000000000000000000000..9e48895acdf2d0b393d23f26a9c76f585370e93f GIT binary patch literal 12338 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 x86_64 on macOS, armv7 -> arm64 on iOS + +function enableOldArch(qbs, xcodeVersion) { + return qbs.targetOS.contains("macos") + && xcodeVersion + && Utilities.versionCompare(xcodeVersion, "10") < 0 + || qbs.targetOS.contains("ios") +} + +function getNewArch(qbs) { + if (qbs.targetOS.contains("macos")) + return "x86_64" + else if (qbs.targetOS.contains("ios")) + return "arm64" + else if (qbs.targetOS.contains("tvos")) + return "arm64" + else if (qbs.targetOS.contains("watchos")) + return "armv7k" + throw "unsupported targetOS: " + qbs.targetOS; +} + +function getOldArch(qbs) { + if (qbs.targetOS.contains("macos")) + return "x86" + else if (qbs.targetOS.contains("ios")) + return "armv7a" + throw "unsupported targetOS: " + qbs.targetOS; +} + +function getArchitectures(qbs, xcodeVersion) { + return enableOldArch(qbs, xcodeVersion) + ? [getOldArch(qbs), getNewArch(qbs)] + : [getNewArch(qbs)]; +} diff --git a/tests/auto/blackbox/testdata-apple/apple-multiconfig/lib.c b/tests/auto/blackbox/testdata-apple/apple-multiconfig/lib.c new file mode 100644 index 00000000..37dc6f01 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/apple-multiconfig/lib.c @@ -0,0 +1,12 @@ +#include + +int foo() +{ +#ifdef __i386__ + printf("Hello from " VARIANT " i386\n"); +#endif +#ifdef __x86_64__ + printf("Hello from " VARIANT " x86_64\n"); +#endif + return 0; +} diff --git a/tests/auto/blackbox/testdata-apple/bundle-structure/bundle-structure.qbs b/tests/auto/blackbox/testdata-apple/bundle-structure/bundle-structure.qbs new file mode 100644 index 00000000..db5f15b1 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/bundle-structure/bundle-structure.qbs @@ -0,0 +1,143 @@ +Project { + property stringList bundleFileTags: ["bundle.content"] + + property stringList buildableProducts: ["A", "B", "C", "D", "E", "F", "G"] + + Product { + Depends { name: "bundle" } + condition: { + console.info("bundle.isShallow: " + bundle.isShallow); + console.info("qbs.targetOS: " + qbs.targetOS); + return false; + } + } + + Application { + Depends { name: "cpp" } + Depends { name: "B" } + Depends { name: "C" } + Depends { name: "D" } + condition: buildableProducts.contains("A") + name: "A" + bundle.isBundle: true + bundle.publicHeaders: ["dummy.h"] + bundle.privateHeaders: ["dummy_p.h"] + bundle.resources: ["resource.txt"] + files: ["dummy.c"] + install: true + installDir: "" + } + + Application { + Depends { name: "cpp" } + Depends { name: "B" } + Depends { name: "C" } + Depends { name: "D" } + condition: buildableProducts.contains("ABadApple") + name: "ABadApple" + bundle._productTypeIdentifier: "com.apple.product-type.will.never.exist.ever.guaranteed" + bundle.isBundle: true + bundle.publicHeaders: ["dummy.h"] + bundle.privateHeaders: ["dummy_p.h"] + bundle.resources: ["resource.txt"] + files: ["dummy.c"] + install: true + installDir: "" + } + + Application { + Depends { name: "cpp" } + Depends { name: "B" } + Depends { name: "C" } + Depends { name: "D" } + condition: buildableProducts.contains("ABadThirdParty") + name: "ABadThirdParty" + bundle._productTypeIdentifier: "org.special.third.party.non.existent.product.type" + bundle.isBundle: true + bundle.publicHeaders: ["dummy.h"] + bundle.privateHeaders: ["dummy_p.h"] + bundle.resources: ["resource.txt"] + files: ["dummy.c"] + install: true + installDir: "" + } + + DynamicLibrary { + Depends { name: "cpp" } + condition: buildableProducts.containsAny(["A", "B", "ABadApple", "ABadThirdParty"]) + name: "B" + bundle.isBundle: true + bundle.publicHeaders: ["dummy.h"] + bundle.privateHeaders: ["dummy_p.h"] + bundle.resources: ["resource.txt"] + files: ["dummy.c"] + install: true + installDir: "" + } + + StaticLibrary { + Depends { name: "cpp" } + condition: buildableProducts.containsAny(["A", "C", "ABadApple", "ABadThirdParty"]) + name: "C" + bundle.isBundle: true + bundle.publicHeaders: ["dummy.h"] + bundle.privateHeaders: ["dummy_p.h"] + bundle.resources: ["resource.txt"] + files: ["dummy.c"] + install: true + installDir: "" + } + + LoadableModule { + Depends { name: "cpp" } + condition: buildableProducts.containsAny(["A", "D", "ABadApple", "ABadThirdParty"]) + name: "D" + bundle.isBundle: true + bundle.publicHeaders: ["dummy.h"] + bundle.privateHeaders: ["dummy_p.h"] + bundle.resources: ["resource.txt"] + files: ["dummy.c"] + install: true + installDir: "" + } + + ApplicationExtension { + Depends { name: "cpp" } + condition: buildableProducts.contains("E") + name: "E" + bundle.isBundle: true + bundle.publicHeaders: ["dummy.h"] + bundle.privateHeaders: ["dummy_p.h"] + bundle.resources: ["resource.txt"] + files: ["dummy.c"] + install: true + installDir: "" + } + + XPCService { + Depends { name: "cpp" } + condition: buildableProducts.contains("F") + name: "F" + bundle.isBundle: true + bundle.publicHeaders: ["dummy.h"] + bundle.privateHeaders: ["dummy_p.h"] + bundle.resources: ["resource.txt"] + files: ["dummy.c"] + install: true + installDir: "" + } + + Product { + Depends { name: "bundle" } + condition: buildableProducts.contains("G") + type: ["inapppurchase"] + name: "G" + bundle.isBundle: true + bundle.resources: ["resource.txt"] + Group { + fileTagsFilter: product.type.concat(project.bundleFileTags) + qbs.install: true + qbs.installSourceBase: product.buildDirectory + } + } +} diff --git a/tests/auto/blackbox/testdata-apple/bundle-structure/dummy.c b/tests/auto/blackbox/testdata-apple/bundle-structure/dummy.c new file mode 100644 index 00000000..210c8274 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/bundle-structure/dummy.c @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata-apple/bundle-structure/dummy.h b/tests/auto/blackbox/testdata-apple/bundle-structure/dummy.h new file mode 100644 index 00000000..50841a22 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/bundle-structure/dummy.h @@ -0,0 +1,27 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ diff --git a/tests/auto/blackbox/testdata-apple/bundle-structure/dummy_p.h b/tests/auto/blackbox/testdata-apple/bundle-structure/dummy_p.h new file mode 100644 index 00000000..50841a22 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/bundle-structure/dummy_p.h @@ -0,0 +1,27 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ diff --git a/tests/auto/blackbox/testdata-apple/bundle-structure/resource.txt b/tests/auto/blackbox/testdata-apple/bundle-structure/resource.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata-apple/deploymentTarget/deployment.qbs b/tests/auto/blackbox/testdata-apple/deploymentTarget/deployment.qbs new file mode 100644 index 00000000..9eff57b6 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/deploymentTarget/deployment.qbs @@ -0,0 +1,14 @@ +CppApplication { + files: ["main.c"] + + // Minimum deployment targets that: + // - will actually link (as of Xcode 8.1) + // - exist for the given architecture(s) + cpp.minimumMacosVersion: qbs.architecture === "x86_64h" ? "10.12" : "10.6" + cpp.minimumIosVersion: ["armv7s", "arm64", "x86_64"].contains(qbs.architecture) ? "7.0" : "6.0" + cpp.minimumTvosVersion: "9.0" + cpp.minimumWatchosVersion: "2.0" + + cpp.driverFlags: ["-v"] + cpp.linkerFlags: ["-v"] +} diff --git a/tests/auto/blackbox/testdata-apple/deploymentTarget/main.c b/tests/auto/blackbox/testdata-apple/deploymentTarget/main.c new file mode 100644 index 00000000..210c8274 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/deploymentTarget/main.c @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata-apple/embedInfoPlist/embedInfoPlist.qbs b/tests/auto/blackbox/testdata-apple/embedInfoPlist/embedInfoPlist.qbs new file mode 100644 index 00000000..ba23dc32 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/embedInfoPlist/embedInfoPlist.qbs @@ -0,0 +1,48 @@ +Project { + CppApplication { + Depends { name: "lib" } + Depends { name: "mod" } + name: "app" + consoleApplication: true + files: ["main.m"] + cpp.frameworks: ["Foundation"] + cpp.rpaths: [cpp.rpathOrigin] + cpp.minimumMacosVersion: "10.6" + bundle.infoPlist: ({ + "QBS": "org.qt-project.qbs.testdata.embedInfoPlist" + }) + install: true + installDir: "" + } + + DynamicLibrary { + Depends { name: "cpp" } + name: "lib" + bundle.isBundle: false + bundle.embedInfoPlist: true + files: ["main.m"] + cpp.frameworks: ["Foundation"] + cpp.sonamePrefix: "@rpath" + bundle.infoPlist: ({ + "QBS": "org.qt-project.qbs.testdata.embedInfoPlist.dylib" + }) + install: true + installDir: "" + } + + LoadableModule { + Depends { name: "cpp" } + name: "mod" + bundle.isBundle: false + bundle.embedInfoPlist: true + files: ["main.m"] + cpp.frameworks: ["Foundation"] + bundle.infoPlist: ({ + "QBS": "org.qt-project.qbs.testdata.embedInfoPlist.bundle" + }) + Group { + fileTagsFilter: product.type + qbs.install: true + } + } +} diff --git a/tests/auto/blackbox/testdata-apple/embedInfoPlist/main.m b/tests/auto/blackbox/testdata-apple/embedInfoPlist/main.m new file mode 100644 index 00000000..b3f36222 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/embedInfoPlist/main.m @@ -0,0 +1,9 @@ +#import + +int main() +{ + NSDictionary *infoPlist = [[NSBundle mainBundle] infoDictionary]; + BOOL success = [[infoPlist objectForKey:@"QBS"] isEqualToString:@"org.qt-project.qbs.testdata.embedInfoPlist"]; + fprintf(success ? stdout : stderr, "%s\n", [[infoPlist description] UTF8String]); + return success ? 0 : 1; +} diff --git a/tests/auto/blackbox/testdata-apple/frameworkStructure/BaseResource b/tests/auto/blackbox/testdata-apple/frameworkStructure/BaseResource new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata-apple/frameworkStructure/Widget.cpp b/tests/auto/blackbox/testdata-apple/frameworkStructure/Widget.cpp new file mode 100644 index 00000000..e17b9675 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/frameworkStructure/Widget.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int foo() { return 0; } diff --git a/tests/auto/blackbox/testdata-apple/frameworkStructure/Widget.h b/tests/auto/blackbox/testdata-apple/frameworkStructure/Widget.h new file mode 100644 index 00000000..e5dacdb5 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/frameworkStructure/Widget.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int foo(); diff --git a/tests/auto/blackbox/testdata-apple/frameworkStructure/WidgetPrivate.h b/tests/auto/blackbox/testdata-apple/frameworkStructure/WidgetPrivate.h new file mode 100644 index 00000000..50841a22 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/frameworkStructure/WidgetPrivate.h @@ -0,0 +1,27 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ diff --git a/tests/auto/blackbox/testdata-apple/frameworkStructure/en.lproj/EnglishResource b/tests/auto/blackbox/testdata-apple/frameworkStructure/en.lproj/EnglishResource new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata-apple/frameworkStructure/frameworkStructure.qbs b/tests/auto/blackbox/testdata-apple/frameworkStructure/frameworkStructure.qbs new file mode 100644 index 00000000..3c6d3933 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/frameworkStructure/frameworkStructure.qbs @@ -0,0 +1,19 @@ +Project { + property bool includeHeaders: true + Library { + Depends { name: "cpp" } + Depends { name: "bundle" } + + property bool isShallow: { + console.info("isShallow: " + bundle.isShallow); + return bundle.isShallow; + } + + name: "Widget" + bundle.isBundle: true + bundle.publicHeaders: project.includeHeaders ? ["Widget.h"] : undefined + bundle.privateHeaders: project.includeHeaders ? ["WidgetPrivate.h"] : base + bundle.resources: ["BaseResource", "en.lproj/EnglishResource"] + files: ["Widget.cpp"].concat(bundle.publicHeaders || []).concat(bundle.privateHeaders || []) + } +} diff --git a/tests/auto/blackbox/testdata-apple/ib/assetcatalog/EmptyStoryboard.storyboard b/tests/auto/blackbox/testdata-apple/ib/assetcatalog/EmptyStoryboard.storyboard new file mode 100644 index 00000000..71b6b685 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/ib/assetcatalog/EmptyStoryboard.storyboard @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/auto/blackbox/testdata-apple/ib/assetcatalog/MainMenu.xib b/tests/auto/blackbox/testdata-apple/ib/assetcatalog/MainMenu.xib new file mode 100644 index 00000000..07dd40b0 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/ib/assetcatalog/MainMenu.xib @@ -0,0 +1,4666 @@ + + + + 1080 + 11D50 + 2457 + 1138.32 + 568.00 + + com.apple.InterfaceBuilder.CocoaPlugin + 2457 + + + NSWindowTemplate + NSView + NSMenu + NSMenuItem + NSCustomObject + + + com.apple.InterfaceBuilder.CocoaPlugin + + + PluginDependencyRecalculationVersion + + + + + NSApplication + + + FirstResponder + + + NSApplication + + + AMainMenu + + + + Test + + 1048576 + 2147483647 + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + + submenuAction: + + Test + + + + About Test + + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Preferences… + , + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Services + + 1048576 + 2147483647 + + + submenuAction: + + Services + + _NSServicesMenu + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Hide Test + h + 1048576 + 2147483647 + + + + + + Hide Others + h + 1572864 + 2147483647 + + + + + + Show All + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Quit Test + q + 1048576 + 2147483647 + + + + + _NSAppleMenu + + + + + File + + 1048576 + 2147483647 + + + submenuAction: + + File + + + + New + n + 1048576 + 2147483647 + + + + + + Open… + o + 1048576 + 2147483647 + + + + + + Open Recent + + 1048576 + 2147483647 + + + submenuAction: + + Open Recent + + + + Clear Menu + + 1048576 + 2147483647 + + + + + _NSRecentDocumentsMenu + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Close + w + 1048576 + 2147483647 + + + + + + Save… + s + 1048576 + 2147483647 + + + + + + Revert to Saved + + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Page Setup... + P + 1179648 + 2147483647 + + + + + + + Print… + p + 1048576 + 2147483647 + + + + + + + + + Edit + + 1048576 + 2147483647 + + + submenuAction: + + Edit + + + + Undo + z + 1048576 + 2147483647 + + + + + + Redo + Z + 1179648 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Cut + x + 1048576 + 2147483647 + + + + + + Copy + c + 1048576 + 2147483647 + + + + + + Paste + v + 1048576 + 2147483647 + + + + + + Paste and Match Style + V + 1572864 + 2147483647 + + + + + + Delete + + 1048576 + 2147483647 + + + + + + Select All + a + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Find + + 1048576 + 2147483647 + + + submenuAction: + + Find + + + + Find… + f + 1048576 + 2147483647 + + + 1 + + + + Find and Replace… + f + 1572864 + 2147483647 + + + 12 + + + + Find Next + g + 1048576 + 2147483647 + + + 2 + + + + Find Previous + G + 1179648 + 2147483647 + + + 3 + + + + Use Selection for Find + e + 1048576 + 2147483647 + + + 7 + + + + Jump to Selection + j + 1048576 + 2147483647 + + + + + + + + + Spelling and Grammar + + 1048576 + 2147483647 + + + submenuAction: + + Spelling and Grammar + + + + Show Spelling and Grammar + : + 1048576 + 2147483647 + + + + + + Check Document Now + ; + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Check Spelling While Typing + + 1048576 + 2147483647 + + + + + + Check Grammar With Spelling + + 1048576 + 2147483647 + + + + + + Correct Spelling Automatically + + 2147483647 + + + + + + + + + Substitutions + + 1048576 + 2147483647 + + + submenuAction: + + Substitutions + + + + Show Substitutions + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Smart Copy/Paste + f + 1048576 + 2147483647 + + + 1 + + + + Smart Quotes + g + 1048576 + 2147483647 + + + 2 + + + + Smart Dashes + + 2147483647 + + + + + + Smart Links + G + 1179648 + 2147483647 + + + 3 + + + + Text Replacement + + 2147483647 + + + + + + + + + Transformations + + 2147483647 + + + submenuAction: + + Transformations + + + + Make Upper Case + + 2147483647 + + + + + + Make Lower Case + + 2147483647 + + + + + + Capitalize + + 2147483647 + + + + + + + + + Speech + + 1048576 + 2147483647 + + + submenuAction: + + Speech + + + + Start Speaking + + 1048576 + 2147483647 + + + + + + Stop Speaking + + 1048576 + 2147483647 + + + + + + + + + + + + Format + + 2147483647 + + + submenuAction: + + Format + + + + Font + + 2147483647 + + + submenuAction: + + Font + + + + Show Fonts + t + 1048576 + 2147483647 + + + + + + Bold + b + 1048576 + 2147483647 + + + 2 + + + + Italic + i + 1048576 + 2147483647 + + + 1 + + + + Underline + u + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Bigger + + + 1048576 + 2147483647 + + + 3 + + + + Smaller + - + 1048576 + 2147483647 + + + 4 + + + + YES + YES + + + 2147483647 + + + + + + Kern + + 2147483647 + + + submenuAction: + + Kern + + + + Use Default + + 2147483647 + + + + + + Use None + + 2147483647 + + + + + + Tighten + + 2147483647 + + + + + + Loosen + + 2147483647 + + + + + + + + + Ligatures + + 2147483647 + + + submenuAction: + + Ligatures + + + + Use Default + + 2147483647 + + + + + + Use None + + 2147483647 + + + + + + Use All + + 2147483647 + + + + + + + + + Baseline + + 2147483647 + + + submenuAction: + + Baseline + + + + Use Default + + 2147483647 + + + + + + Superscript + + 2147483647 + + + + + + Subscript + + 2147483647 + + + + + + Raise + + 2147483647 + + + + + + Lower + + 2147483647 + + + + + + + + + YES + YES + + + 2147483647 + + + + + + Show Colors + C + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Copy Style + c + 1572864 + 2147483647 + + + + + + Paste Style + v + 1572864 + 2147483647 + + + + + _NSFontMenu + + + + + Text + + 2147483647 + + + submenuAction: + + Text + + + + Align Left + { + 1048576 + 2147483647 + + + + + + Center + | + 1048576 + 2147483647 + + + + + + Justify + + 2147483647 + + + + + + Align Right + } + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Writing Direction + + 2147483647 + + + submenuAction: + + Writing Direction + + + + YES + Paragraph + + 2147483647 + + + + + + CURlZmF1bHQ + + 2147483647 + + + + + + CUxlZnQgdG8gUmlnaHQ + + 2147483647 + + + + + + CVJpZ2h0IHRvIExlZnQ + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + YES + Selection + + 2147483647 + + + + + + CURlZmF1bHQ + + 2147483647 + + + + + + CUxlZnQgdG8gUmlnaHQ + + 2147483647 + + + + + + CVJpZ2h0IHRvIExlZnQ + + 2147483647 + + + + + + + + + YES + YES + + + 2147483647 + + + + + + Show Ruler + + 2147483647 + + + + + + Copy Ruler + c + 1310720 + 2147483647 + + + + + + Paste Ruler + v + 1310720 + 2147483647 + + + + + + + + + + + + View + + 1048576 + 2147483647 + + + submenuAction: + + View + + + + Show Toolbar + t + 1572864 + 2147483647 + + + + + + Customize Toolbar… + + 1048576 + 2147483647 + + + + + + + + + Window + + 1048576 + 2147483647 + + + submenuAction: + + Window + + + + Minimize + m + 1048576 + 2147483647 + + + + + + Zoom + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Bring All to Front + + 1048576 + 2147483647 + + + + + _NSWindowsMenu + + + + + Help + + 2147483647 + + + submenuAction: + + Help + + + + Test Help + ? + 1048576 + 2147483647 + + + + + _NSHelpMenu + + + + _NSMainMenu + + + 15 + 2 + {{335, 390}, {480, 360}} + 1954021376 + Test + NSWindow + + + + + 256 + {480, 360} + + {{0, 0}, {2560, 1418}} + {10000000000000, 10000000000000} + YES + + + AppDelegate + + + NSFontManager + + + + + + + terminate: + + + + 449 + + + + orderFrontStandardAboutPanel: + + + + 142 + + + + delegate + + + + 495 + + + + performMiniaturize: + + + + 37 + + + + arrangeInFront: + + + + 39 + + + + print: + + + + 86 + + + + runPageLayout: + + + + 87 + + + + clearRecentDocuments: + + + + 127 + + + + performClose: + + + + 193 + + + + toggleContinuousSpellChecking: + + + + 222 + + + + undo: + + + + 223 + + + + copy: + + + + 224 + + + + checkSpelling: + + + + 225 + + + + paste: + + + + 226 + + + + stopSpeaking: + + + + 227 + + + + cut: + + + + 228 + + + + showGuessPanel: + + + + 230 + + + + redo: + + + + 231 + + + + selectAll: + + + + 232 + + + + startSpeaking: + + + + 233 + + + + delete: + + + + 235 + + + + performZoom: + + + + 240 + + + + performFindPanelAction: + + + + 241 + + + + centerSelectionInVisibleArea: + + + + 245 + + + + toggleGrammarChecking: + + + + 347 + + + + toggleSmartInsertDelete: + + + + 355 + + + + toggleAutomaticQuoteSubstitution: + + + + 356 + + + + toggleAutomaticLinkDetection: + + + + 357 + + + + saveDocument: + + + + 362 + + + + revertDocumentToSaved: + + + + 364 + + + + runToolbarCustomizationPalette: + + + + 365 + + + + toggleToolbarShown: + + + + 366 + + + + hide: + + + + 367 + + + + hideOtherApplications: + + + + 368 + + + + unhideAllApplications: + + + + 370 + + + + newDocument: + + + + 373 + + + + openDocument: + + + + 374 + + + + raiseBaseline: + + + + 426 + + + + lowerBaseline: + + + + 427 + + + + copyFont: + + + + 428 + + + + subscript: + + + + 429 + + + + superscript: + + + + 430 + + + + tightenKerning: + + + + 431 + + + + underline: + + + + 432 + + + + orderFrontColorPanel: + + + + 433 + + + + useAllLigatures: + + + + 434 + + + + loosenKerning: + + + + 435 + + + + pasteFont: + + + + 436 + + + + unscript: + + + + 437 + + + + useStandardKerning: + + + + 438 + + + + useStandardLigatures: + + + + 439 + + + + turnOffLigatures: + + + + 440 + + + + turnOffKerning: + + + + 441 + + + + toggleAutomaticSpellingCorrection: + + + + 456 + + + + orderFrontSubstitutionsPanel: + + + + 458 + + + + toggleAutomaticDashSubstitution: + + + + 461 + + + + toggleAutomaticTextReplacement: + + + + 463 + + + + uppercaseWord: + + + + 464 + + + + capitalizeWord: + + + + 467 + + + + lowercaseWord: + + + + 468 + + + + pasteAsPlainText: + + + + 486 + + + + performFindPanelAction: + + + + 487 + + + + performFindPanelAction: + + + + 488 + + + + performFindPanelAction: + + + + 489 + + + + showHelp: + + + + 493 + + + + alignCenter: + + + + 518 + + + + pasteRuler: + + + + 519 + + + + toggleRuler: + + + + 520 + + + + alignRight: + + + + 521 + + + + copyRuler: + + + + 522 + + + + alignJustified: + + + + 523 + + + + alignLeft: + + + + 524 + + + + makeBaseWritingDirectionNatural: + + + + 525 + + + + makeBaseWritingDirectionLeftToRight: + + + + 526 + + + + makeBaseWritingDirectionRightToLeft: + + + + 527 + + + + makeTextWritingDirectionNatural: + + + + 528 + + + + makeTextWritingDirectionLeftToRight: + + + + 529 + + + + makeTextWritingDirectionRightToLeft: + + + + 530 + + + + performFindPanelAction: + + + + 535 + + + + addFontTrait: + + + + 421 + + + + addFontTrait: + + + + 422 + + + + modifyFont: + + + + 423 + + + + orderFrontFontPanel: + + + + 424 + + + + modifyFont: + + + + 425 + + + + window + + + + 532 + + + + + + 0 + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 29 + + + + + + + + + + + + + + 19 + + + + + + + + 56 + + + + + + + + 217 + + + + + + + + 83 + + + + + + + + 81 + + + + + + + + + + + + + + + + + 75 + + + + + 78 + + + + + 72 + + + + + 82 + + + + + 124 + + + + + + + + 77 + + + + + 73 + + + + + 79 + + + + + 112 + + + + + 74 + + + + + 125 + + + + + + + + 126 + + + + + 205 + + + + + + + + + + + + + + + + + + + + + + 202 + + + + + 198 + + + + + 207 + + + + + 214 + + + + + 199 + + + + + 203 + + + + + 197 + + + + + 206 + + + + + 215 + + + + + 218 + + + + + + + + 216 + + + + + + + + 200 + + + + + + + + + + + + + 219 + + + + + 201 + + + + + 204 + + + + + 220 + + + + + + + + + + + + + 213 + + + + + 210 + + + + + 221 + + + + + 208 + + + + + 209 + + + + + 57 + + + + + + + + + + + + + + + + + + 58 + + + + + 134 + + + + + 150 + + + + + 136 + + + + + 144 + + + + + 129 + + + + + 143 + + + + + 236 + + + + + 131 + + + + + + + + 149 + + + + + 145 + + + + + 130 + + + + + 24 + + + + + + + + + + + 92 + + + + + 5 + + + + + 239 + + + + + 23 + + + + + 295 + + + + + + + + 296 + + + + + + + + + 297 + + + + + 298 + + + + + 211 + + + + + + + + 212 + + + + + + + + + 195 + + + + + 196 + + + + + 346 + + + + + 348 + + + + + + + + 349 + + + + + + + + + + + + + + 350 + + + + + 351 + + + + + 354 + + + + + 371 + + + + + + + + 372 + + + + + 375 + + + + + + + + 376 + + + + + + + + + 377 + + + + + + + + 388 + + + + + + + + + + + + + + + + + + + + + + + 389 + + + + + 390 + + + + + 391 + + + + + 392 + + + + + 393 + + + + + 394 + + + + + 395 + + + + + 396 + + + + + 397 + + + + + + + + 398 + + + + + + + + 399 + + + + + + + + 400 + + + + + 401 + + + + + 402 + + + + + 403 + + + + + 404 + + + + + 405 + + + + + + + + + + + + 406 + + + + + 407 + + + + + 408 + + + + + 409 + + + + + 410 + + + + + 411 + + + + + + + + + + 412 + + + + + 413 + + + + + 414 + + + + + 415 + + + + + + + + + + + 416 + + + + + 417 + + + + + 418 + + + + + 419 + + + + + 420 + + + + + 450 + + + + + + + + 451 + + + + + + + + + + 452 + + + + + 453 + + + + + 454 + + + + + 457 + + + + + 459 + + + + + 460 + + + + + 462 + + + + + 465 + + + + + 466 + + + + + 485 + + + + + 490 + + + + + + + + 491 + + + + + + + + 492 + + + + + 494 + + + + + 496 + + + + + + + + 497 + + + + + + + + + + + + + + + + + 498 + + + + + 499 + + + + + 500 + + + + + 501 + + + + + 502 + + + + + 503 + + + + + + + + 504 + + + + + 505 + + + + + 506 + + + + + 507 + + + + + 508 + + + + + + + + + + + + + + + + 509 + + + + + 510 + + + + + 511 + + + + + 512 + + + + + 513 + + + + + 514 + + + + + 515 + + + + + 516 + + + + + 517 + + + + + 534 + + + + + + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{380, 496}, {480, 360}} + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + + + + + 535 + + + + + ABCardController + NSObject + + id + id + id + id + id + id + id + + + + addCardViewField: + id + + + copy: + id + + + cut: + id + + + doDelete: + id + + + find: + id + + + paste: + id + + + saveChanges: + id + + + + ABCardView + NSButton + NSManagedObjectContext + NSSearchField + NSTextField + NSWindow + + + + mCardView + ABCardView + + + mEditButton + NSButton + + + mManagedObjectContext + NSManagedObjectContext + + + mSearchField + NSSearchField + + + mStatusTextField + NSTextField + + + mWindow + NSWindow + + + + IBProjectSource + ./Classes/ABCardController.h + + + + ABCardView + NSView + + id + id + + + + commitAndSave: + id + + + statusImageClicked: + id + + + + NSObjectController + NSImageView + NSView + ABNameFrameView + NSView + NSImage + ABImageView + + + + mBindingsController + NSObjectController + + + mBuddyStatusImage + NSImageView + + + mHeaderView + NSView + + + mNameView + ABNameFrameView + + + mNextKeyView + NSView + + + mUserImage + NSImage + + + mUserImageView + ABImageView + + + + IBProjectSource + ./Classes/ABCardView.h + + + + ABImageView + NSImageView + + id + id + id + id + + + + copy: + id + + + cut: + id + + + delete: + id + + + paste: + id + + + + IBProjectSource + ./Classes/ABImageView.h + + + + DVTBorderedView + DVTLayoutView_ML + + contentView + NSView + + + contentView + + contentView + NSView + + + + IBProjectSource + ./Classes/DVTBorderedView.h + + + + DVTDelayedMenuButton + NSButton + + IBProjectSource + ./Classes/DVTDelayedMenuButton.h + + + + DVTGradientImageButton + NSButton + + IBProjectSource + ./Classes/DVTGradientImageButton.h + + + + DVTImageAndTextCell + NSTextFieldCell + + IBProjectSource + ./Classes/DVTImageAndTextCell.h + + + + DVTImageAndTextColumn + NSTableColumn + + IBProjectSource + ./Classes/DVTImageAndTextColumn.h + + + + DVTLayoutView_ML + NSView + + IBProjectSource + ./Classes/DVTLayoutView_ML.h + + + + DVTOutlineView + NSOutlineView + + IBProjectSource + ./Classes/DVTOutlineView.h + + + + DVTSplitView + NSSplitView + + IBProjectSource + ./Classes/DVTSplitView.h + + + + DVTStackView_ML + DVTLayoutView_ML + + IBProjectSource + ./Classes/DVTStackView_ML.h + + + + DVTTableView + NSTableView + + IBProjectSource + ./Classes/DVTTableView.h + + + + DVTViewController + NSViewController + + IBProjectSource + ./Classes/DVTViewController.h + + + + HFController + NSObject + + selectAll: + id + + + selectAll: + + selectAll: + id + + + + IBProjectSource + ./Classes/HFController.h + + + + HFRepresenterTextView + NSView + + selectAll: + id + + + selectAll: + + selectAll: + id + + + + IBProjectSource + ./Classes/HFRepresenterTextView.h + + + + IBEditor + NSObject + + id + id + id + id + id + + + + changeFont: + id + + + performCopy: + id + + + performCut: + id + + + selectAll: + id + + + sizeSelectionToFit: + id + + + + IBProjectSource + ./Classes/IBEditor.h + + + + IDECapsuleListView + DVTStackView_ML + + dataSource + id + + + dataSource + + dataSource + id + + + + IBProjectSource + ./Classes/IDECapsuleListView.h + + + + IDEDMArrayController + NSArrayController + + IBProjectSource + ./Classes/IDEDMArrayController.h + + + + IDEDMEditor + IDEEditor + + DVTBorderedView + NSView + IDEDMEditorSourceListController + DVTSplitView + + + + bottomToolbarBorderView + DVTBorderedView + + + sourceListSplitViewPane + NSView + + + sourceListViewController + IDEDMEditorSourceListController + + + splitView + DVTSplitView + + + + IBProjectSource + ./Classes/IDEDMEditor.h + + + + IDEDMEditorController + IDEViewController + + IBProjectSource + ./Classes/IDEDMEditorController.h + + + + IDEDMEditorSourceListController + IDEDMEditorController + + DVTBorderedView + IDEDMEditor + DVTImageAndTextColumn + DVTOutlineView + NSTreeController + + + + borderedView + DVTBorderedView + + + parentEditor + IDEDMEditor + + + primaryColumn + DVTImageAndTextColumn + + + sourceListOutlineView + DVTOutlineView + + + sourceListTreeController + NSTreeController + + + + IBProjectSource + ./Classes/IDEDMEditorSourceListController.h + + + + IDEDMHighlightImageAndTextCell + DVTImageAndTextCell + + IBProjectSource + ./Classes/IDEDMHighlightImageAndTextCell.h + + + + IDEDataModelBrowserEditor + IDEDMEditorController + + IDEDataModelPropertiesTableController + IDECapsuleListView + NSArrayController + IDEDataModelPropertiesTableController + IDEDataModelEntityContentsEditor + IDEDataModelPropertiesTableController + + + + attributesTableViewController + IDEDataModelPropertiesTableController + + + capsuleView + IDECapsuleListView + + + entityArrayController + NSArrayController + + + fetchedPropertiesTableViewController + IDEDataModelPropertiesTableController + + + parentEditor + IDEDataModelEntityContentsEditor + + + relationshipsTableViewController + IDEDataModelPropertiesTableController + + + + IBProjectSource + ./Classes/IDEDataModelBrowserEditor.h + + + + IDEDataModelConfigurationEditor + IDEDMEditorController + + IDECapsuleListView + IDEDataModelEditor + IDEDataModelConfigurationTableController + + + + capsuleListView + IDECapsuleListView + + + parentEditor + IDEDataModelEditor + + + tableController + IDEDataModelConfigurationTableController + + + + IBProjectSource + ./Classes/IDEDataModelConfigurationEditor.h + + + + IDEDataModelConfigurationTableController + IDEDMEditorController + + NSArrayController + NSArrayController + IDEDataModelConfigurationEditor + XDTableView + + + + configurationsArrayController + NSArrayController + + + entitiesArrayController + NSArrayController + + + parentEditor + IDEDataModelConfigurationEditor + + + tableView + XDTableView + + + + IBProjectSource + ./Classes/IDEDataModelConfigurationTableController.h + + + + IDEDataModelDiagramEditor + IDEDMEditorController + + XDDiagramView + IDEDataModelEntityContentsEditor + + + + diagramView + XDDiagramView + + + parentEditor + IDEDataModelEntityContentsEditor + + + + IBProjectSource + ./Classes/IDEDataModelDiagramEditor.h + + + + IDEDataModelEditor + IDEDMEditor + + DVTDelayedMenuButton + DVTDelayedMenuButton + NSSegmentedControl + IDEDataModelConfigurationEditor + IDEDataModelEntityContentsEditor + IDEDataModelFetchRequestEditor + NSSegmentedControl + NSTabView + + + + addEntityButton + DVTDelayedMenuButton + + + addPropertyButton + DVTDelayedMenuButton + + + browserDiagramSegmentControl + NSSegmentedControl + + + configurationViewController + IDEDataModelConfigurationEditor + + + entityContentsViewController + IDEDataModelEntityContentsEditor + + + fetchRequestViewController + IDEDataModelFetchRequestEditor + + + hierarchySegmentControl + NSSegmentedControl + + + tabView + NSTabView + + + + IBProjectSource + ./Classes/IDEDataModelEditor.h + + + + IDEDataModelEntityContentsEditor + IDEDMEditorController + + IDEDataModelBrowserEditor + IDEDataModelDiagramEditor + IDEDataModelEditor + NSTabView + + + + browserViewController + IDEDataModelBrowserEditor + + + diagramViewController + IDEDataModelDiagramEditor + + + parentEditor + IDEDataModelEditor + + + tabView + NSTabView + + + + IBProjectSource + ./Classes/IDEDataModelEntityContentsEditor.h + + + + IDEDataModelFetchRequestEditor + IDEDMEditorController + + NSArrayController + IDEDataModelEditor + IDECapsuleListView + + + + entityController + NSArrayController + + + parentEditor + IDEDataModelEditor + + + tableView + IDECapsuleListView + + + + IBProjectSource + ./Classes/IDEDataModelFetchRequestEditor.h + + + + IDEDataModelPropertiesTableController + IDEDMEditorController + + IDEDMArrayController + NSTableColumn + NSArrayController + IDEDataModelBrowserEditor + IDEDMHighlightImageAndTextCell + XDTableView + + + + arrayController + IDEDMArrayController + + + entitiesColumn + NSTableColumn + + + entityArrayController + NSArrayController + + + parentEditor + IDEDataModelBrowserEditor + + + propertyNameAndImageCell + IDEDMHighlightImageAndTextCell + + + tableView + XDTableView + + + + IBProjectSource + ./Classes/IDEDataModelPropertiesTableController.h + + + + IDEDocDownloadsTableViewController + NSObject + + NSButtonCell + DVTTableView + IDEDocViewingPrefPaneController + + + + _downloadButtonCell + NSButtonCell + + + _tableView + DVTTableView + + + prefPaneController + IDEDocViewingPrefPaneController + + + + IBProjectSource + ./Classes/IDEDocDownloadsTableViewController.h + + + + IDEDocSetOutlineView + NSOutlineView + + IBProjectSource + ./Classes/IDEDocSetOutlineView.h + + + + IDEDocSetOutlineViewController + NSObject + + id + id + id + id + id + + + + getDocSetAction: + id + + + showProblemInfoForUpdate: + id + + + subscribeToPublisherAction: + id + + + unsubscribeFromPublisher: + id + + + updateDocSetAction: + id + + + + docSetOutlineView + IDEDocSetOutlineView + + + docSetOutlineView + + docSetOutlineView + IDEDocSetOutlineView + + + + IBProjectSource + ./Classes/IDEDocSetOutlineViewController.h + + + + IDEDocViewingPrefPaneController + IDEViewController + + id + id + id + id + id + id + id + id + id + id + id + + + + addSubscription: + id + + + checkForAndInstallUpdatesNow: + id + + + deleteDocSet: + id + + + downloadAction: + id + + + minimumFontSizeComboBoxAction: + id + + + minimumFontSizeEnabledAction: + id + + + showHelp: + id + + + showSubscriptionSheet: + id + + + subscriptionCancelAction: + id + + + toggleAutoCheckForAndInstallUpdates: + id + + + toggleDocSetInfo: + id + + + + DVTGradientImageButton + DVTGradientImageButton + DVTGradientImageButton + NSSplitView + NSView + NSView + DVTBorderedView + DVTBorderedView + NSButton + NSTextView + IDEDocSetOutlineViewController + IDEDocDownloadsTableViewController + NSComboBox + NSTextField + NSButton + NSTextField + NSWindow + NSButton + + + + _addButton + DVTGradientImageButton + + + _deleteButton + DVTGradientImageButton + + + _showInfoAreaButton + DVTGradientImageButton + + + _splitView + NSSplitView + + + _splitViewDocSetInfoSubview + NSView + + + _splitViewDocSetsListSubview + NSView + + + borderedViewAroundSplitView + DVTBorderedView + + + borderedViewBelowTable + DVTBorderedView + + + checkAndInstallNowButton + NSButton + + + docSetInfoTextView + NSTextView + + + docSetOutlineViewController + IDEDocSetOutlineViewController + + + downloadsTableViewController + IDEDocDownloadsTableViewController + + + minimumFontSizeControl + NSComboBox + + + noUpdatesAvailableMessage + NSTextField + + + showInfoButton + NSButton + + + subscriptionTextField + NSTextField + + + subscriptionWindow + NSWindow + + + validateAddSubscriptionButton + NSButton + + + + IBProjectSource + ./Classes/IDEDocViewingPrefPaneController.h + + + + IDEEditor + IDEViewController + + IBProjectSource + ./Classes/IDEEditor.h + + + + IDEViewController + DVTViewController + + IBProjectSource + ./Classes/IDEViewController.h + + + + IKImageView + + id + id + id + id + + + + copy: + id + + + crop: + id + + + cut: + id + + + paste: + id + + + + IBProjectSource + ./Classes/IKImageView.h + + + + NSDocument + + id + id + id + id + id + id + + + + printDocument: + id + + + revertDocumentToSaved: + id + + + runPageLayout: + id + + + saveDocument: + id + + + saveDocumentAs: + id + + + saveDocumentTo: + id + + + + IBProjectSource + ./Classes/NSDocument.h + + + + NSResponder + + _insertFindPattern: + id + + + _insertFindPattern: + + _insertFindPattern: + id + + + + IBProjectSource + ./Classes/NSResponder.h + + + + QLPreviewBubble + NSObject + + id + id + + + + hide: + id + + + show: + id + + + + parentWindow + NSWindow + + + parentWindow + + parentWindow + NSWindow + + + + IBProjectSource + ./Classes/QLPreviewBubble.h + + + + QTMovieView + + id + id + id + id + id + + + + showAll: + id + + + showCustomButton: + id + + + toggleLoops: + id + + + zoomIn: + id + + + zoomOut: + id + + + + IBProjectSource + ./Classes/QTMovieView.h + + + + WebView + + id + id + id + id + + + + reloadFromOrigin: + id + + + resetPageZoom: + id + + + zoomPageIn: + id + + + zoomPageOut: + id + + + + IBProjectSource + ./Classes/WebView.h + + + + XDDiagramView + NSView + + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + id + + + + _graphLayouterMenuItemAction: + id + + + _zoomPopUpButtonAction: + id + + + alignBottomEdges: + id + + + alignCentersHorizontallyInContainer: + id + + + alignCentersVerticallyInContainer: + id + + + alignHorizontalCenters: + id + + + alignLeftEdges: + id + + + alignRightEdges: + id + + + alignTopEdges: + id + + + alignVerticalCenters: + id + + + bringToFront: + id + + + collapseAllCompartments: + id + + + copy: + id + + + cut: + id + + + delete: + id + + + deleteBackward: + id + + + deleteForward: + id + + + deselectAll: + id + + + diagramZoomIn: + id + + + diagramZoomOut: + id + + + expandAllCompartments: + id + + + flipHorizontally: + id + + + flipVertically: + id + + + layoutGraphicsConcentrically: + id + + + layoutGraphicsHierarchically: + id + + + lock: + id + + + makeSameHeight: + id + + + makeSameWidth: + id + + + moveDown: + id + + + moveDownAndModifySelection: + id + + + moveLeft: + id + + + moveLeftAndModifySelection: + id + + + moveRight: + id + + + moveRightAndModifySelection: + id + + + moveUp: + id + + + moveUpAndModifySelection: + id + + + paste: + id + + + rollDownAllCompartments: + id + + + rollUpAllCompartments: + id + + + selectAll: + id + + + sendToBack: + id + + + sizeToFit: + id + + + toggleGridShown: + id + + + toggleHiddenGraphicsShown: + id + + + togglePageBreaksShown: + id + + + toggleRuler: + id + + + toggleSnapsToGrid: + id + + + unlock: + id + + + + _diagramController + IDEDataModelDiagramEditor + + + _diagramController + + _diagramController + IDEDataModelDiagramEditor + + + + IBProjectSource + ./Classes/XDDiagramView.h + + + + XDTableView + NSTableView + + showAllTableColumns: + id + + + showAllTableColumns: + + showAllTableColumns: + id + + + + IBProjectSource + ./Classes/XDTableView.h + + + + AppDelegate + NSObject + + id + id + + + + applicationShouldTerminate: + id + + + applicationWillFinishLaunching: + id + + + + IBProjectSource + ./Classes/AppDelegate.h + + + + + 0 + IBCocoaFramework + YES + 3 + + {11, 11} + {10, 3} + + YES + + diff --git a/tests/auto/blackbox/testdata-apple/ib/assetcatalog/Storyboard.storyboard b/tests/auto/blackbox/testdata-apple/ib/assetcatalog/Storyboard.storyboard new file mode 100644 index 00000000..777d711d --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/ib/assetcatalog/Storyboard.storyboard @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/auto/blackbox/testdata-apple/ib/assetcatalog/assetcatalogempty.qbs b/tests/auto/blackbox/testdata-apple/ib/assetcatalog/assetcatalogempty.qbs new file mode 100644 index 00000000..94361f34 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/ib/assetcatalog/assetcatalogempty.qbs @@ -0,0 +1,27 @@ +import qbs.Utilities + +Project { + condition: { + var result = qbs.targetOS.contains("macos"); + if (!result) + console.info("Skip this test"); + return result; + } + property bool includeIconset + + CppApplication { + Depends { name: "ib" } + files: { + var filez = ["main.c", "MainMenu.xib"]; + if (project.includeIconset) + filez.push("empty.xcassets/empty.iconset"); + else if (Utilities.versionCompare(xcode.version, "5") >= 0) + filez.push("empty.xcassets"); + if (qbs.hostOSVersionMinor >= 10 // need macOS 10.10 to build SBs + && cpp.minimumMacosVersion !== undefined + && Utilities.versionCompare(cpp.minimumMacosVersion, "10.10") >= 0) + filez.push("Storyboard.storyboard"); + return filez; + } + } +} diff --git a/tests/auto/blackbox/testdata-apple/ib/assetcatalog/empty.xcassets/empty.iconset/icon_16x16.png b/tests/auto/blackbox/testdata-apple/ib/assetcatalog/empty.xcassets/empty.iconset/icon_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..60365798f18a376a1193bca9e6044cc778c6042a GIT binary patch literal 649 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 + + + + DefaultValue + The default value + OverriddenValue + The default value + + diff --git a/tests/auto/blackbox/testdata-apple/overrideInfoPlist/main.c b/tests/auto/blackbox/testdata-apple/overrideInfoPlist/main.c new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/overrideInfoPlist/main.c @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata-apple/overrideInfoPlist/overrideInfoPlist.qbs b/tests/auto/blackbox/testdata-apple/overrideInfoPlist/overrideInfoPlist.qbs new file mode 100644 index 00000000..e70584ed --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/overrideInfoPlist/overrideInfoPlist.qbs @@ -0,0 +1,16 @@ +CppApplication { + Depends { name: "bundle" } + cpp.minimumMacosVersion: "10.7" + files: ["main.c", "Override-Info.plist"] + + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: true + bundle.identifierPrefix: "com.test" + + bundle.infoPlist: ({ + "CFBundleName": "My Bundle", + "OverriddenValue": "The overridden value", + }) + } +} diff --git a/tests/auto/blackbox/testdata-apple/xcode/xcode-project.qbs b/tests/auto/blackbox/testdata-apple/xcode/xcode-project.qbs new file mode 100644 index 00000000..fa4c67b9 --- /dev/null +++ b/tests/auto/blackbox/testdata-apple/xcode/xcode-project.qbs @@ -0,0 +1,58 @@ +Project { + property var sdks: {} + + Product { + Depends { name: "xcode" } + + consoleApplication: { + console.info("Developer directory: " + xcode.developerPath); + console.info("SDK: " + xcode.sdk); + console.info("Target devices: " + xcode.targetDevices.join(", ")); + console.info("SDK name: " + xcode.sdkName); + console.info("SDK version: " + xcode.sdkVersion); + console.info("Latest SDK name: " + xcode.latestSdkName); + console.info("Latest SDK version: " + xcode.latestSdkVersion); + console.info("Available SDK names: " + xcode.availableSdkNames.join(", ")); + console.info("Available SDK versions: " + xcode.availableSdkVersions.join(", ")); + + var targetOsToKey = function(targetOS) { + if (targetOS.contains("ios")) + return "iphoneos"; + if (targetOS.contains("ios-simulator")) + return "iphonesimulator"; + if (targetOS.contains("macos")) + return "macosx"; + if (targetOS.contains("tvos")) + return "appletvos"; + if (targetOS.contains("tvos-simulator")) + return "appletvsimulator"; + if (targetOS.contains("watchos")) + return "watchos"; + if (targetOS.contains("watchos-simulator")) + return "watchossimulator"; + throw "Unsupported OS" + targetOS; + } + + var actualList = project.sdks[targetOsToKey(qbs.targetOS)]; + console.info("Actual SDK list: " + actualList.join(", ")); + + var msg = "Unexpected SDK list [" + xcode.availableSdkVersions.join(", ") + "]"; + var testArraysEqual = function(a, b) { + if (!a || !b || a.length !== b.length) { + throw msg; + } + + for (var i = 0; i < a.length; ++i) { + var version1 = a[i].split('.'); + var version2 = b[i].split('.'); + for (var j = 0; j < version1.length; ++j) { + if (version1[j] !== version2[j]) + throw msg; + } + } + } + + testArraysEqual(actualList, xcode.availableSdkVersions); + } + } +} diff --git a/tests/auto/blackbox/testdata-baremetal/BareMetalApplication.qbs b/tests/auto/blackbox/testdata-baremetal/BareMetalApplication.qbs new file mode 100644 index 00000000..fa678ebb --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/BareMetalApplication.qbs @@ -0,0 +1,104 @@ +CppApplication { + cpp.positionIndependentCode: false + Properties { + condition: qbs.toolchain.contains("iar") + && qbs.architecture === "stm8" + cpp.driverLinkerFlags: [ + "--config_def", "_CSTACK_SIZE=0x100", + "--config_def", "_HEAP_SIZE=0x100", + ] + } + Properties { + condition: qbs.toolchain.contains("iar") + && qbs.architecture === "rl78" + cpp.driverLinkerFlags: [ + "--config_def", "_NEAR_HEAP_SIZE=256", + "--config_def", "_FAR_HEAP_SIZE=4096", + "--config_def", "_HUGE_HEAP_SIZE=0", + "--config_def", "_STACK_SIZE=128", + "--config_def", "_NEAR_CONST_LOCATION_SIZE=0x6F00", + "--config_def", "_NEAR_CONST_LOCATION_START=0x3000", + "--define_symbol", "_NEAR_CONST_LOCATION=0", + "--config", cpp.toolchainInstallPath + "/../config/lnkrl78_s3.icf" + ] + } + Properties { + condition: qbs.toolchain.contains("iar") + && qbs.architecture === "rh850" + cpp.driverLinkerFlags: [ + "--config_def", "CSTACK_SIZE=0x1000", + "--config_def", "HEAP_SIZE=0x1000", + "--config", cpp.toolchainInstallPath + "/../config/lnkrh850_g3m.icf" + ] + } + Properties { + condition: qbs.toolchain.contains("iar") + && qbs.architecture === "v850" + cpp.driverLinkerFlags: [ + "-D_CSTACK_SIZE=1000", + "-D_HEAP_SIZE=1000", + "-f", cpp.toolchainInstallPath + "/../config/lnk85.xcl" + ] + } + Properties { + condition: qbs.toolchain.contains("iar") + && qbs.architecture === "78k" + cpp.cFlags: [ + "--core", "78k0", + "--code_model", "standard" + ] + cpp.driverLinkerFlags: [ + "-D_CSTACK_SIZE=80", + "-D_HEAP_SIZE=200", + "-D_CODEBANK_START=0", + "-D_CODEBANK_END=0", + "-D_CODEBANK_BANKS=0", + "-f", cpp.toolchainInstallPath + "/../config/lnk.xcl", + cpp.toolchainInstallPath + "/../lib/clib/cl78ks1.r26" + ] + } + Properties { + condition: qbs.toolchain.contains("iar") + && qbs.architecture === "sh" + cpp.driverLinkerFlags: [ + "--config_def", "_CSTACK_SIZE=0x800", + "--config_def", "_HEAP_SIZE=0x800", + "--config_def", "_INT_TABLE=0x10", + "--config", cpp.toolchainInstallPath + "/../config/generic.icf" + ] + } + Properties { + condition: qbs.toolchain.contains("keil") + && qbs.architecture.startsWith("arm") + && cpp.compilerName.startsWith("armcc") + cpp.assemblerFlags: ["--cpu", "cortex-m0"] + cpp.driverFlags: ["--cpu", "cortex-m0"] + } + Properties { + condition: qbs.toolchain.contains("keil") + && qbs.architecture.startsWith("arm") + && cpp.compilerName.startsWith("armclang") + cpp.assemblerFlags: ["--cpu", "cortex-m0"] + cpp.driverFlags: ["-mcpu=cortex-m0", "--target=arm-arm-none-eabi"] + } + Properties { + condition: qbs.toolchain.contains("gcc") + && qbs.architecture.startsWith("arm") + cpp.driverFlags: ["-specs=nosys.specs"] + } + Properties { + condition: qbs.toolchain.contains("gcc") + && qbs.architecture === "xtensa" + cpp.driverFlags: ["-nostdlib"] + } + Properties { + condition: qbs.toolchain.contains("gcc") + && qbs.architecture === "msp430" + cpp.driverFlags: ["-mmcu=msp430f5529"] + } + Properties { + condition: qbs.toolchain.contains("gcc") + && qbs.architecture === "m32r" + cpp.driverFlags: ["-nostdlib"] + } +} diff --git a/tests/auto/blackbox/testdata-baremetal/BareMetalStaticLibrary.qbs b/tests/auto/blackbox/testdata-baremetal/BareMetalStaticLibrary.qbs new file mode 100644 index 00000000..56045516 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/BareMetalStaticLibrary.qbs @@ -0,0 +1,104 @@ +StaticLibrary { + cpp.positionIndependentCode: false + Properties { + condition: qbs.toolchain.contains("iar") + && qbs.architecture === "stm8" + cpp.driverLinkerFlags: [ + "--config_def", "_CSTACK_SIZE=0x100", + "--config_def", "_HEAP_SIZE=0x100", + ] + } + Properties { + condition: qbs.toolchain.contains("iar") + && qbs.architecture === "rl78" + cpp.driverLinkerFlags: [ + "--config_def", "_NEAR_HEAP_SIZE=256", + "--config_def", "_FAR_HEAP_SIZE=4096", + "--config_def", "_HUGE_HEAP_SIZE=0", + "--config_def", "_STACK_SIZE=128", + "--config_def", "_NEAR_CONST_LOCATION_SIZE=0x6F00", + "--config_def", "_NEAR_CONST_LOCATION_START=0x3000", + "--define_symbol", "_NEAR_CONST_LOCATION=0", + "--config", cpp.toolchainInstallPath + "/../config/lnkrl78_s3.icf" + ] + } + Properties { + condition: qbs.toolchain.contains("iar") + && qbs.architecture === "rh850" + cpp.driverLinkerFlags: [ + "--config_def", "CSTACK_SIZE=0x1000", + "--config_def", "HEAP_SIZE=0x1000", + "--config", cpp.toolchainInstallPath + "/../config/lnkrh850_g3m.icf" + ] + } + Properties { + condition: qbs.toolchain.contains("iar") + && qbs.architecture === "v850" + cpp.driverLinkerFlags: [ + "-D_CSTACK_SIZE=1000", + "-D_HEAP_SIZE=1000", + "-f", cpp.toolchainInstallPath + "/../config/lnk85.xcl" + ] + } + Properties { + condition: qbs.toolchain.contains("iar") + && qbs.architecture === "78k" + cpp.cFlags: [ + "--core", "78k0", + "--code_model", "standard" + ] + cpp.driverLinkerFlags: [ + "-D_CSTACK_SIZE=80", + "-D_HEAP_SIZE=200", + "-D_CODEBANK_START=0", + "-D_CODEBANK_END=0", + "-D_CODEBANK_BANKS=0", + "-f", cpp.toolchainInstallPath + "/../config/lnk.xcl", + cpp.toolchainInstallPath + "/../lib/clib/cl78ks1.r26" + ] + } + Properties { + condition: qbs.toolchain.contains("iar") + && qbs.architecture === "sh" + cpp.driverLinkerFlags: [ + "--config_def", "_CSTACK_SIZE=0x800", + "--config_def", "_HEAP_SIZE=0x800", + "--config_def", "_INT_TABLE=0x10", + "--config", cpp.toolchainInstallPath + "/../config/generic.icf" + ] + } + Properties { + condition: qbs.toolchain.contains("keil") + && qbs.architecture.startsWith("arm") + && cpp.compilerName.startsWith("armcc") + cpp.assemblerFlags: ["--cpu", "cortex-m0"] + cpp.driverFlags: ["--cpu", "cortex-m0"] + } + Properties { + condition: qbs.toolchain.contains("keil") + && qbs.architecture.startsWith("arm") + && cpp.compilerName.startsWith("armclang") + cpp.assemblerFlags: ["--cpu", "cortex-m0"] + cpp.driverFlags: ["-mcpu=cortex-m0", "--target=arm-arm-none-eabi"] + } + Properties { + condition: qbs.toolchain.contains("gcc") + && qbs.architecture.startsWith("arm") + cpp.driverFlags: ["-specs=nosys.specs"] + } + Properties { + condition: qbs.toolchain.contains("gcc") + && qbs.architecture === "xtensa" + cpp.driverFlags: ["-nostdlib"] + } + Properties { + condition: qbs.toolchain.contains("gcc") + && qbs.architecture === "msp430" + cpp.driverFlags: ["-mmcu=msp430f5529"] + } + Properties { + condition: qbs.toolchain.contains("gcc") + && qbs.architecture === "m32r" + cpp.driverFlags: ["-nostdlib"] + } +} diff --git a/tests/auto/blackbox/testdata-baremetal/defines/defines.qbs b/tests/auto/blackbox/testdata-baremetal/defines/defines.qbs new file mode 100644 index 00000000..b257a8a4 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/defines/defines.qbs @@ -0,0 +1,6 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication + +BareMetalApplication { + cpp.defines: ["FOO", "BAR"] + files: ["main.c"] +} diff --git a/tests/auto/blackbox/testdata-baremetal/defines/main.c b/tests/auto/blackbox/testdata-baremetal/defines/main.c new file mode 100644 index 00000000..d2d4769e --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/defines/main.c @@ -0,0 +1,11 @@ +#ifndef FOO +#error FOO missing! +#endif +#ifndef BAR +#error BAR missing! +#endif + +int main(void) +{ + return 0; +} diff --git a/tests/auto/blackbox/testdata-baremetal/distribution-include-paths/bar/bar.h b/tests/auto/blackbox/testdata-baremetal/distribution-include-paths/bar/bar.h new file mode 100644 index 00000000..49ffa0b1 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/distribution-include-paths/bar/bar.h @@ -0,0 +1,6 @@ +#ifndef BAR_H +#define BAR_H + +#define BAR_VALUE 1 + +#endif // BAR_H diff --git a/tests/auto/blackbox/testdata-baremetal/distribution-include-paths/distribution-include-paths.qbs b/tests/auto/blackbox/testdata-baremetal/distribution-include-paths/distribution-include-paths.qbs new file mode 100644 index 00000000..0fded6a4 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/distribution-include-paths/distribution-include-paths.qbs @@ -0,0 +1,6 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication + +BareMetalApplication { + files: ["main.c"] + cpp.distributionIncludePaths: ["foo", "bar"] +} diff --git a/tests/auto/blackbox/testdata-baremetal/distribution-include-paths/foo/foo.h b/tests/auto/blackbox/testdata-baremetal/distribution-include-paths/foo/foo.h new file mode 100644 index 00000000..dc510379 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/distribution-include-paths/foo/foo.h @@ -0,0 +1,6 @@ +#ifndef FOO_H +#define FOO_H + +#define FOO_VALUE 1 + +#endif // FOO_H diff --git a/tests/auto/blackbox/testdata-baremetal/distribution-include-paths/main.c b/tests/auto/blackbox/testdata-baremetal/distribution-include-paths/main.c new file mode 100644 index 00000000..aabc97a0 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/distribution-include-paths/main.c @@ -0,0 +1,7 @@ +#include +#include + +int main(void) +{ + return FOO_VALUE - BAR_VALUE; +} diff --git a/tests/auto/blackbox/testdata-baremetal/do-not-generate-compiler-listing/do-not-generate-compiler-listing.qbs b/tests/auto/blackbox/testdata-baremetal/do-not-generate-compiler-listing/do-not-generate-compiler-listing.qbs new file mode 100644 index 00000000..46faf944 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/do-not-generate-compiler-listing/do-not-generate-compiler-listing.qbs @@ -0,0 +1,30 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication + +BareMetalApplication { + condition: { + if (qbs.toolchainType === "sdcc") + return true; + if (qbs.toolchainType === "msvc") + return true; + if (qbs.toolchainType === "clang-cl") + return true; + if (qbs.toolchainType === "iar") + return true; + if (qbs.toolchainType === "keil") { + if (qbs.architecture === "mcs51" + || qbs.architecture === "mcs251" + || qbs.architecture === "c166") { + return true; + } + if (cpp.compilerName.startsWith("armcc")) { + console.info("using short listing file names"); + return true; + } + } + console.info("unsupported toolset: %%" + + qbs.toolchainType + "%%, %%" + qbs.architecture + "%%"); + return false; + } + cpp.generateCompilerListingFiles: false + files: ["main.c", "fun.c"] +} diff --git a/tests/auto/blackbox/testdata-baremetal/do-not-generate-compiler-listing/fun.c b/tests/auto/blackbox/testdata-baremetal/do-not-generate-compiler-listing/fun.c new file mode 100644 index 00000000..3b8c8f2f --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/do-not-generate-compiler-listing/fun.c @@ -0,0 +1,4 @@ +int f(void) +{ + return 0; +} diff --git a/tests/auto/blackbox/testdata-baremetal/do-not-generate-compiler-listing/main.c b/tests/auto/blackbox/testdata-baremetal/do-not-generate-compiler-listing/main.c new file mode 100644 index 00000000..2c3d7726 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/do-not-generate-compiler-listing/main.c @@ -0,0 +1,6 @@ +extern int f(void); + +int main(void) +{ + return f(); +} diff --git a/tests/auto/blackbox/testdata-baremetal/do-not-generate-linker-map/do-not-generate-linker-map.qbs b/tests/auto/blackbox/testdata-baremetal/do-not-generate-linker-map/do-not-generate-linker-map.qbs new file mode 100644 index 00000000..f0095a4a --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/do-not-generate-linker-map/do-not-generate-linker-map.qbs @@ -0,0 +1,16 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication + +BareMetalApplication { + condition: { + if (qbs.toolchainType === "sdcc") { + console.info("unsupported toolset: %%" + + qbs.toolchainType + "%%, %%" + qbs.architecture + "%%"); + return false; + } + console.info("current toolset: %%" + + qbs.toolchainType + "%%, %%" + qbs.architecture + "%%"); + return true; + } + cpp.generateLinkerMapFile: false + files: ["main.c"] +} diff --git a/tests/auto/blackbox/testdata-baremetal/do-not-generate-linker-map/main.c b/tests/auto/blackbox/testdata-baremetal/do-not-generate-linker-map/main.c new file mode 100644 index 00000000..58fe6925 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/do-not-generate-linker-map/main.c @@ -0,0 +1,4 @@ +int main(void) +{ + return 0; +} diff --git a/tests/auto/blackbox/testdata-baremetal/external-static-libraries/external-static-libraries.qbs b/tests/auto/blackbox/testdata-baremetal/external-static-libraries/external-static-libraries.qbs new file mode 100644 index 00000000..6fbbb864 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/external-static-libraries/external-static-libraries.qbs @@ -0,0 +1,48 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication +import "../BareMetalStaticLibrary.qbs" as BareMetalStaticLibrary + +Project { + condition: { + // The KEIL C51/C251/C166 toolchains support only a + // full paths to the external libraries. + if (qbs.toolchainType === "keil") { + if (qbs.architecture === "mcs51" + || qbs.architecture === "mcs251" + || qbs.architecture === "c166") { + console.info("unsupported toolset: %%" + + qbs.toolchainType + "%%, %%" + qbs.architecture + "%%"); + return false; + } + } + return true; + } + property string outputLibrariesDirectory: sourceDirectory + "/libs" + BareMetalStaticLibrary { + name: "lib-a" + destinationDirectory: project.outputLibrariesDirectory + Depends { name: "cpp" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + files: ["lib-a.c"] + } + BareMetalStaticLibrary { + name: "lib-b" + destinationDirectory: project.outputLibrariesDirectory + Depends { name: "cpp" } + Depends { name: "lib-a" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + files: ["lib-b.c"] + } + BareMetalApplication { + Depends { name: "lib-a"; cpp.link: false } + Depends { name: "lib-b"; cpp.link: false } + files: ["main.c"] + cpp.libraryPaths: [project.outputLibrariesDirectory] + cpp.staticLibraries: ["lib-b", "lib-a"] + } +} diff --git a/tests/auto/blackbox/testdata-baremetal/external-static-libraries/lib-a.c b/tests/auto/blackbox/testdata-baremetal/external-static-libraries/lib-a.c new file mode 100644 index 00000000..13401861 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/external-static-libraries/lib-a.c @@ -0,0 +1,4 @@ +int a(void) +{ + return 0; +} diff --git a/tests/auto/blackbox/testdata-baremetal/external-static-libraries/lib-b.c b/tests/auto/blackbox/testdata-baremetal/external-static-libraries/lib-b.c new file mode 100644 index 00000000..5d45b817 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/external-static-libraries/lib-b.c @@ -0,0 +1,6 @@ +extern int a(void); + +int b(void) +{ + return a(); +} diff --git a/tests/auto/blackbox/testdata-baremetal/external-static-libraries/main.c b/tests/auto/blackbox/testdata-baremetal/external-static-libraries/main.c new file mode 100644 index 00000000..84ef5e51 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/external-static-libraries/main.c @@ -0,0 +1,6 @@ +extern int b(); + +int main(void) +{ + return b(); +} diff --git a/tests/auto/blackbox/testdata-baremetal/generate-compiler-listing/fun.c b/tests/auto/blackbox/testdata-baremetal/generate-compiler-listing/fun.c new file mode 100644 index 00000000..3b8c8f2f --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/generate-compiler-listing/fun.c @@ -0,0 +1,4 @@ +int f(void) +{ + return 0; +} diff --git a/tests/auto/blackbox/testdata-baremetal/generate-compiler-listing/generate-compiler-listing.qbs b/tests/auto/blackbox/testdata-baremetal/generate-compiler-listing/generate-compiler-listing.qbs new file mode 100644 index 00000000..2596b441 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/generate-compiler-listing/generate-compiler-listing.qbs @@ -0,0 +1,30 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication + +BareMetalApplication { + condition: { + if (qbs.toolchainType === "sdcc") + return true; + if (qbs.toolchainType === "msvc") + return true; + if (qbs.toolchainType === "clang-cl") + return true; + if (qbs.toolchainType === "iar") + return true; + if (qbs.toolchainType === "keil") { + if (qbs.architecture === "mcs51" + || qbs.architecture === "mcs251" + || qbs.architecture === "c166") { + return true; + } + if (cpp.compilerName.startsWith("armcc")) { + console.info("using short listing file names"); + return true; + } + } + console.info("unsupported toolset: %%" + + qbs.toolchainType + "%%, %%" + qbs.architecture + "%%"); + return false; + } + cpp.generateCompilerListingFiles: true + files: ["main.c", "fun.c"] +} diff --git a/tests/auto/blackbox/testdata-baremetal/generate-compiler-listing/main.c b/tests/auto/blackbox/testdata-baremetal/generate-compiler-listing/main.c new file mode 100644 index 00000000..2c3d7726 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/generate-compiler-listing/main.c @@ -0,0 +1,6 @@ +extern int f(void); + +int main(void) +{ + return f(); +} diff --git a/tests/auto/blackbox/testdata-baremetal/generate-linker-map/generate-linker-map.qbs b/tests/auto/blackbox/testdata-baremetal/generate-linker-map/generate-linker-map.qbs new file mode 100644 index 00000000..854391ab --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/generate-linker-map/generate-linker-map.qbs @@ -0,0 +1,16 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication + +BareMetalApplication { + condition: { + if (qbs.toolchainType === "sdcc") { + console.info("unsupported toolset: %%" + + qbs.toolchainType + "%%, %%" + qbs.architecture + "%%"); + return false; + } + console.info("current toolset: %%" + + qbs.toolchainType + "%%, %%" + qbs.architecture + "%%"); + return true; + } + cpp.generateLinkerMapFile: true + files: ["main.c"] +} diff --git a/tests/auto/blackbox/testdata-baremetal/generate-linker-map/main.c b/tests/auto/blackbox/testdata-baremetal/generate-linker-map/main.c new file mode 100644 index 00000000..58fe6925 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/generate-linker-map/main.c @@ -0,0 +1,4 @@ +int main(void) +{ + return 0; +} diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-application/main.c b/tests/auto/blackbox/testdata-baremetal/one-object-application/main.c new file mode 100644 index 00000000..58fe6925 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-application/main.c @@ -0,0 +1,4 @@ +int main(void) +{ + return 0; +} diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-application/one-object-application.qbs b/tests/auto/blackbox/testdata-baremetal/one-object-application/one-object-application.qbs new file mode 100644 index 00000000..482425b5 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-application/one-object-application.qbs @@ -0,0 +1,5 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication + +BareMetalApplication { + files: ["main.c"] +} diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/78k-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/78k-iar.s new file mode 100644 index 00000000..25f0e2bb --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/78k-iar.s @@ -0,0 +1,6 @@ + PUBLIC main + RSEG CODE:CODE:NOROOT(0) +main: + MOVW AX, #0 + RET + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/arm-gcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/arm-gcc.s new file mode 100644 index 00000000..c7b89423 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/arm-gcc.s @@ -0,0 +1,5 @@ + .global main + .type main, %function +main: + mov r0, #0 + bx lr diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/arm-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/arm-iar.s new file mode 100644 index 00000000..0a13a5dc --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/arm-iar.s @@ -0,0 +1,7 @@ + PUBLIC main + SECTION `.text`:CODE:NOROOT(1) + THUMB +main: + MOVS R0, #+0 + BX LR + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/arm-keil.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/arm-keil.s new file mode 100644 index 00000000..f3fcd50f --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/arm-keil.s @@ -0,0 +1,7 @@ + THUMB + AREA ||.text||, CODE, READONLY, ALIGN = 1 +main PROC + MOVS r0, #0 + BX lr + ENDP + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr-gcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr-gcc.s new file mode 100644 index 00000000..4ba005a4 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr-gcc.s @@ -0,0 +1,6 @@ + .global main + .type main, %function +main: + ldi r24, 0 + ldi r25, 0 + ret diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr-iar.s new file mode 100644 index 00000000..49e9d476 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr-iar.s @@ -0,0 +1,7 @@ + PUBLIC main + RSEG CODE:CODE:NOROOT(1) +main: + LDI R16, 0 + LDI R17, 0 + RET + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr32-gcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr32-gcc.s new file mode 100644 index 00000000..879e5415 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr32-gcc.s @@ -0,0 +1,8 @@ + .global main + .type main, @function +main: + stm --sp, r7, lr + mov r7, sp + mov r8, 0 + mov r12, r8 + ldm sp++, r7, pc diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr32-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr32-iar.s new file mode 100644 index 00000000..c5e78896 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/avr32-iar.s @@ -0,0 +1,7 @@ + PUBLIC main + RSEG CODE32:CODE:REORDER:NOROOT(2) + CODE +main: + MOV R12, 0x0 + RET R12 + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/c166-keil.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/c166-keil.s new file mode 100644 index 00000000..394bc2ae --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/c166-keil.s @@ -0,0 +1,7 @@ +MAIN_SEG SECTION CODE WORD 'NCODE' +main PROC NEAR + MOV R4, #0 + RET +main ENDP +MAIN_SEG ENDS + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/cr16-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/cr16-iar.s new file mode 100644 index 00000000..4a14de6a --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/cr16-iar.s @@ -0,0 +1,6 @@ + PUBLIC main + RSEG CODE:CODE:NOROOT(0) +main: + MOVW $0, R0 + JUMP (RA) + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m16c-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m16c-iar.s new file mode 100644 index 00000000..4153f290 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m16c-iar.s @@ -0,0 +1,6 @@ + PUBLIC main + RSEG CODE:CODE:REORDER:NOROOT(0) +main: + MOV.W #0x0, R0 + RTS + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m32c-gcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m32c-gcc.s new file mode 100644 index 00000000..173c04c3 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m32c-gcc.s @@ -0,0 +1,5 @@ + .global _main + .type _main, @function +_main: + mov.w #0, r0 + rts diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m32r-gcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m32r-gcc.s new file mode 100644 index 00000000..dfcf42ca --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m32r-gcc.s @@ -0,0 +1,9 @@ + .global main + .type main, @function +main: + push fp + mv fp, sp + ldi r4, #0 + mv r0, r4 + pop fp + jmp lr diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m68k-gcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m68k-gcc.s new file mode 100644 index 00000000..fdde81da --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/m68k-gcc.s @@ -0,0 +1,7 @@ + .global main + .type main, @function +main: + link.w %fp, #0 + clr.l %d0 + unlk %fp + rts diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs251-keil.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs251-keil.s new file mode 100644 index 00000000..312cc968 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs251-keil.s @@ -0,0 +1,8 @@ +PUBLIC main +MAIN_SEG SEGMENT CODE + RSEG MAIN_SEG +main PROC + XRL WR6,WR6 + RET + ENDP + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs51-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs51-iar.s new file mode 100644 index 00000000..09cc6461 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs51-iar.s @@ -0,0 +1,7 @@ + PUBLIC main + RSEG NEAR_CODE:CODE:NOROOT(0) +main: + MOV R2, #0x0 + MOV R3, #0x0 + RET + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs51-keil.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs51-keil.s new file mode 100644 index 00000000..28174d0e --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs51-keil.s @@ -0,0 +1,8 @@ +PUBLIC main +MAIN_SEG SEGMENT CODE + RSEG MAIN_SEG +main: + MOV R6, #0x0 + MOV R7, #0x0 + RET + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs51-sdcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs51-sdcc.s new file mode 100644 index 00000000..eaa6467e --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/mcs51-sdcc.s @@ -0,0 +1,7 @@ + .globl main + .area PSEG (PAG,XDATA) + .area XSEG (XDATA) + .area HOME (CODE) +main: + mov dptr, #0x0000 + ret diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/msp430-gcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/msp430-gcc.s new file mode 100644 index 00000000..8e8a2498 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/msp430-gcc.s @@ -0,0 +1,5 @@ + .global main + .type main, %function +main: + mov #0, r15 + .LIRD0: diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/msp430-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/msp430-iar.s new file mode 100644 index 00000000..fbabe3ba --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/msp430-iar.s @@ -0,0 +1,6 @@ + PUBLIC main + RSEG `CODE`:CODE:REORDER:NOROOT(1) +main: + MOV.W #0x0, R12 + RET + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/one-object-asm-application.qbs b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/one-object-asm-application.qbs new file mode 100644 index 00000000..7650810a --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/one-object-asm-application.qbs @@ -0,0 +1,97 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication + +BareMetalApplication { + condition: { + if (qbs.toolchainType === "keil") { + if (qbs.architecture.startsWith("arm")) + return true; + if (qbs.architecture === "mcs51") + return true; + if (qbs.architecture === "mcs251") + return true; + if (qbs.architecture === "c166") + return true; + } else if (qbs.toolchainType === "iar") { + if (qbs.architecture.startsWith("arm")) + return true; + if (qbs.architecture === "mcs51") + return true; + if (qbs.architecture === "stm8") + return true; + if (qbs.architecture === "avr") + return true; + if (qbs.architecture === "avr32") + return true; + if (qbs.architecture === "msp430") + return true; + if (qbs.architecture === "rl78") + return true; + if (qbs.architecture === "rh850") + return true; + if (qbs.architecture === "v850") + return true; + if (qbs.architecture === "78k") + return true; + if (qbs.architecture === "r32c") + return true; + if (qbs.architecture === "sh") + return true; + if (qbs.architecture === "cr16") + return true; + if (qbs.architecture === "m16c") + return true; + } else if (qbs.toolchainType === "sdcc") { + if (qbs.architecture === "mcs51") + return true; + if (qbs.architecture === "stm8") + return true; + } else if (qbs.toolchainType === "gcc") { + if (qbs.architecture.startsWith("arm")) + return true; + if (qbs.architecture === "avr") + return true; + if (qbs.architecture === "avr32") + return true; + if (qbs.architecture === "msp430") + return true; + if (qbs.architecture === "xtensa") + return true; + if (qbs.architecture === "rl78") + return true; + if (qbs.architecture === "m32c") + return true; + if (qbs.architecture === "m32r") + return true; + if (qbs.architecture === "m68k") + return true; + if (qbs.architecture === "v850") + return true; + if (qbs.architecture === "riscv") + return true; + if (qbs.architecture === "rx") + return true; + } + console.info("unsupported toolset: %%" + + qbs.toolchainType + "%%, %%" + qbs.architecture + "%%"); + return false; + } + + Properties { + condition: qbs.toolchainType === "gcc" + && qbs.architecture === "msp430" + // We need to use this workaround to enable + // the cpp.driverFlags property. + cpp.linkerPath: cpp.compilerPathByLanguage["c"] + } + + Properties { + condition: qbs.toolchainType === "iar" + && qbs.architecture.startsWith("arm") + cpp.entryPoint: "main" + } + + cpp.linkerPath: original + + files: [(qbs.architecture.startsWith("arm") ? "arm" : qbs.architecture) + + "-" + qbs.toolchainType + ".s"] +} diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/r32c-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/r32c-iar.s new file mode 100644 index 00000000..84430681 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/r32c-iar.s @@ -0,0 +1,7 @@ + PUBLIC main + RSEG CODE24:CODE:REORDER:NOROOT(0) +main: + MOV.L:Z #0x0, R2R0 + RTS + RSEG SBREF:DATA:NOROOT(0) + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rh850-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rh850-iar.s new file mode 100644 index 00000000..8901027a --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rh850-iar.s @@ -0,0 +1,7 @@ + PUBLIC _main + SECTION `.text`:CODE:NOROOT(2) + CODE +_main: + MOV r0, r10 + JMP [lp] + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/riscv-gcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/riscv-gcc.s new file mode 100644 index 00000000..d0909780 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/riscv-gcc.s @@ -0,0 +1,11 @@ + .globl main + .type main, @function +main: + add sp, sp, -16 + sd s0, 8(sp) + add s0, sp, 16 + li a5, 0 + mv a0, a5 + ld s0, 8(sp) + add sp, sp, 16 + jr ra diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rl78-gcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rl78-gcc.s new file mode 100644 index 00000000..59510bd0 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rl78-gcc.s @@ -0,0 +1,11 @@ +r8 = 0xffef0 +.text + .global _main + .type _main, @function +_main: + subw sp, #2 + clrw ax + movw [sp], ax + movw r8, ax + addw sp, #2 + ret diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rl78-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rl78-iar.s new file mode 100644 index 00000000..1f00996c --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rl78-iar.s @@ -0,0 +1,7 @@ + PUBLIC _main + SECTION `.text`:CODE:NOROOT(0) + CODE +_main: + CLRW AX + RET + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rx-gcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rx-gcc.s new file mode 100644 index 00000000..501d4cd7 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/rx-gcc.s @@ -0,0 +1,8 @@ + .global _main + .type _main, @function +_main: + push.l r10 + mov.L r0, r10 + mov.L #0, r5 + mov.L r5, r1 + rtsd #4, r10-r10 diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/sh-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/sh-iar.s new file mode 100644 index 00000000..d8678031 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/sh-iar.s @@ -0,0 +1,7 @@ + PUBLIC _main + SECTION `.code32.text`:CODE:NOROOT(2) +_main: + CODE + MOV #0, R0 + RTS/N + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/stm8-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/stm8-iar.s new file mode 100644 index 00000000..674e20de --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/stm8-iar.s @@ -0,0 +1,7 @@ + PUBLIC main + SECTION `.near_func.text`:CODE:REORDER:NOROOT(0) + CODE +main: + CLRW X + RET + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/stm8-sdcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/stm8-sdcc.s new file mode 100644 index 00000000..1a552f4a --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/stm8-sdcc.s @@ -0,0 +1,7 @@ + .globl main + .area DATA + .area SSEG + .area HOME +main: + clrw x + ret diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/v850-gcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/v850-gcc.s new file mode 100644 index 00000000..3599a1fb --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/v850-gcc.s @@ -0,0 +1,11 @@ + .global _main + .type _main, @function +_main: + add -4, sp + st.w r29, 0[sp] + mov sp, r29 + mov 0, r10 + mov r29, sp + ld.w 0[sp], r29 + add 4, sp + jmp [r31] diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/v850-iar.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/v850-iar.s new file mode 100644 index 00000000..4ccfacd6 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/v850-iar.s @@ -0,0 +1,7 @@ + PUBLIC _main + RSEG `CODE`:CODE:NOROOT(2) + CODE +_main: + MOV zero, r1 + JMP [lp] + END diff --git a/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/xtensa-gcc.s b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/xtensa-gcc.s new file mode 100644 index 00000000..c2100090 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/one-object-asm-application/xtensa-gcc.s @@ -0,0 +1,11 @@ + .global main + .type main, @function +main: + addi sp, sp, -16 + s32i.n a15, sp, 12 + mov.n a15, sp + movi.n a2, 0 + mov.n sp, a15 + l32i.n a15, sp, 12 + addi sp, sp, 16 + ret.n diff --git a/tests/auto/blackbox/testdata-baremetal/preinclude-headers/main.c b/tests/auto/blackbox/testdata-baremetal/preinclude-headers/main.c new file mode 100644 index 00000000..75519228 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/preinclude-headers/main.c @@ -0,0 +1,4 @@ +int main(void) +{ + return PREINCLUDE_VALUE; +} diff --git a/tests/auto/blackbox/testdata-baremetal/preinclude-headers/preinclude-headers.qbs b/tests/auto/blackbox/testdata-baremetal/preinclude-headers/preinclude-headers.qbs new file mode 100644 index 00000000..0ded6ff1 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/preinclude-headers/preinclude-headers.qbs @@ -0,0 +1,18 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication + +BareMetalApplication { + condition: { + if (qbs.toolchainType === "keil") { + if (qbs.architecture === "mcs51" + || qbs.architecture === "mcs251" + || qbs.architecture === "c166") { + console.info("unsupported toolset: %%" + + qbs.toolchainType + "%%, %%" + qbs.architecture + "%%"); + return false; + } + } + return true; + } + cpp.prefixHeaders: ["preinclude.h"] + files: ["main.c"] +} diff --git a/tests/auto/blackbox/testdata-baremetal/preinclude-headers/preinclude.h b/tests/auto/blackbox/testdata-baremetal/preinclude-headers/preinclude.h new file mode 100644 index 00000000..6b68e482 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/preinclude-headers/preinclude.h @@ -0,0 +1,6 @@ +#ifndef PREINCLUDE_H +#define PREINCLUDE_H + +#define PREINCLUDE_VALUE 0 + +#endif // PREINCLUDE_H diff --git a/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/a1.c b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/a1.c new file mode 100644 index 00000000..b593e95d --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/a1.c @@ -0,0 +1,4 @@ +int a1(void) +{ + return 0; +} diff --git a/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/a2.c b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/a2.c new file mode 100644 index 00000000..35ab7feb --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/a2.c @@ -0,0 +1,4 @@ +int a2(void) +{ + return 0; +} diff --git a/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/app.c b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/app.c new file mode 100644 index 00000000..9814bfd9 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/app.c @@ -0,0 +1,6 @@ +extern int e(void); + +int main(void) +{ + return e(); +} diff --git a/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/b.c b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/b.c new file mode 100644 index 00000000..92df418d --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/b.c @@ -0,0 +1,6 @@ +extern int a1(void); + +int b(void) +{ + return a1(); +} diff --git a/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/c.c b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/c.c new file mode 100644 index 00000000..0c0e350f --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/c.c @@ -0,0 +1,6 @@ +extern int a2(void); + +int c(void) +{ + return a2(); +} diff --git a/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/d.c b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/d.c new file mode 100644 index 00000000..a3fc084f --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/d.c @@ -0,0 +1,7 @@ +extern int b(void); +extern int c(void); + +int d(void) +{ + return b() + c(); +} diff --git a/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/e.c b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/e.c new file mode 100644 index 00000000..9381e845 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/e.c @@ -0,0 +1,7 @@ +extern int d(void); + +int e(void) +{ + return d(); +} + diff --git a/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/static-library-dependencies.qbs b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/static-library-dependencies.qbs new file mode 100644 index 00000000..7184f47e --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/static-library-dependencies/static-library-dependencies.qbs @@ -0,0 +1,40 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication +import "../BareMetalStaticLibrary.qbs" as BareMetalStaticLibrary + +Project { + BareMetalStaticLibrary { + name: "lib-a" + Depends { name: "cpp" } + files: ["a1.c", "a2.c"] + } + BareMetalStaticLibrary { + name: "lib-b" + Depends { name: "cpp" } + Depends { name: "lib-a" } + files: ["b.c"] + } + BareMetalStaticLibrary { + name: "lib-c" + Depends { name: "cpp" } + Depends { name: "lib-a" } + files: ["c.c"] + } + BareMetalStaticLibrary { + name: "lib-d" + Depends { name: "cpp" } + Depends { name: "lib-b" } + Depends { name: "lib-c" } + files: ["d.c"] + } + BareMetalStaticLibrary { + name: "lib-e" + Depends { name: "cpp" } + Depends { name: "lib-d" } + files: ["e.c"] + } + BareMetalApplication { + name: "app" + Depends { name: "lib-e" } + files: ["app.c"] + } +} diff --git a/tests/auto/blackbox/testdata-baremetal/system-include-paths/bar/bar.h b/tests/auto/blackbox/testdata-baremetal/system-include-paths/bar/bar.h new file mode 100644 index 00000000..49ffa0b1 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/system-include-paths/bar/bar.h @@ -0,0 +1,6 @@ +#ifndef BAR_H +#define BAR_H + +#define BAR_VALUE 1 + +#endif // BAR_H diff --git a/tests/auto/blackbox/testdata-baremetal/system-include-paths/foo/foo.h b/tests/auto/blackbox/testdata-baremetal/system-include-paths/foo/foo.h new file mode 100644 index 00000000..dc510379 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/system-include-paths/foo/foo.h @@ -0,0 +1,6 @@ +#ifndef FOO_H +#define FOO_H + +#define FOO_VALUE 1 + +#endif // FOO_H diff --git a/tests/auto/blackbox/testdata-baremetal/system-include-paths/main.c b/tests/auto/blackbox/testdata-baremetal/system-include-paths/main.c new file mode 100644 index 00000000..aabc97a0 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/system-include-paths/main.c @@ -0,0 +1,7 @@ +#include +#include + +int main(void) +{ + return FOO_VALUE - BAR_VALUE; +} diff --git a/tests/auto/blackbox/testdata-baremetal/system-include-paths/system-include-paths.qbs b/tests/auto/blackbox/testdata-baremetal/system-include-paths/system-include-paths.qbs new file mode 100644 index 00000000..1f9fd123 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/system-include-paths/system-include-paths.qbs @@ -0,0 +1,6 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication + +BareMetalApplication { + files: ["main.c"] + cpp.systemIncludePaths: ["foo", "bar"] +} diff --git a/tests/auto/blackbox/testdata-baremetal/target-platform/target-platform.qbs b/tests/auto/blackbox/testdata-baremetal/target-platform/target-platform.qbs new file mode 100644 index 00000000..50be8e91 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/target-platform/target-platform.qbs @@ -0,0 +1,18 @@ +Product { + Depends { name: "cpp" } + condition: { + if (qbs.toolchainType === "keil" + || qbs.toolchainType === "iar" + || qbs.toolchainType === "sdcc") { + var hasNoPlatform = (qbs.targetPlatform === "none"); + var hasNoOS = (qbs.targetOS.length === 1 && qbs.targetOS[0] === "none"); + console.info("has no platform: " + hasNoPlatform); + console.info("has no os: " + hasNoOS); + } else { + console.info("unsupported toolset: %%" + + qbs.toolchainType + "%%, %%" + qbs.architecture + "%%"); + return false; + } + return true; + } +} diff --git a/tests/auto/blackbox/testdata-baremetal/two-object-application/fun.c b/tests/auto/blackbox/testdata-baremetal/two-object-application/fun.c new file mode 100644 index 00000000..3b8c8f2f --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/two-object-application/fun.c @@ -0,0 +1,4 @@ +int f(void) +{ + return 0; +} diff --git a/tests/auto/blackbox/testdata-baremetal/two-object-application/main.c b/tests/auto/blackbox/testdata-baremetal/two-object-application/main.c new file mode 100644 index 00000000..2c3d7726 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/two-object-application/main.c @@ -0,0 +1,6 @@ +extern int f(void); + +int main(void) +{ + return f(); +} diff --git a/tests/auto/blackbox/testdata-baremetal/two-object-application/two-object-application.qbs b/tests/auto/blackbox/testdata-baremetal/two-object-application/two-object-application.qbs new file mode 100644 index 00000000..2947975c --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/two-object-application/two-object-application.qbs @@ -0,0 +1,5 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication + +BareMetalApplication { + files: ["main.c", "fun.c"] +} diff --git a/tests/auto/blackbox/testdata-baremetal/user-include-paths/bar/bar.h b/tests/auto/blackbox/testdata-baremetal/user-include-paths/bar/bar.h new file mode 100644 index 00000000..49ffa0b1 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/user-include-paths/bar/bar.h @@ -0,0 +1,6 @@ +#ifndef BAR_H +#define BAR_H + +#define BAR_VALUE 1 + +#endif // BAR_H diff --git a/tests/auto/blackbox/testdata-baremetal/user-include-paths/foo/foo.h b/tests/auto/blackbox/testdata-baremetal/user-include-paths/foo/foo.h new file mode 100644 index 00000000..dc510379 --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/user-include-paths/foo/foo.h @@ -0,0 +1,6 @@ +#ifndef FOO_H +#define FOO_H + +#define FOO_VALUE 1 + +#endif // FOO_H diff --git a/tests/auto/blackbox/testdata-baremetal/user-include-paths/main.c b/tests/auto/blackbox/testdata-baremetal/user-include-paths/main.c new file mode 100644 index 00000000..e76e08cb --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/user-include-paths/main.c @@ -0,0 +1,7 @@ +#include "foo.h" +#include "bar.h" + +int main(void) +{ + return FOO_VALUE - BAR_VALUE; +} diff --git a/tests/auto/blackbox/testdata-baremetal/user-include-paths/user-include-paths.qbs b/tests/auto/blackbox/testdata-baremetal/user-include-paths/user-include-paths.qbs new file mode 100644 index 00000000..23d5dbce --- /dev/null +++ b/tests/auto/blackbox/testdata-baremetal/user-include-paths/user-include-paths.qbs @@ -0,0 +1,6 @@ +import "../BareMetalApplication.qbs" as BareMetalApplication + +BareMetalApplication { + files: ["main.c"] + cpp.includePaths: ["foo", "bar"] +} diff --git a/tests/auto/blackbox/testdata-clangdb/project1/i like spaces.cpp b/tests/auto/blackbox/testdata-clangdb/project1/i like spaces.cpp new file mode 100644 index 00000000..64d87d52 --- /dev/null +++ b/tests/auto/blackbox/testdata-clangdb/project1/i like spaces.cpp @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** 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 + +using namespace std; + +#define _STR(x) #x +#define STR(x) _STR(x) + +int main(int argc, char **argv) +{ + int garbage; + int unused = garbage; + cout << "SPACES=" << SPACES << "SPICES=" STR(SPICES) << "SLICES=" << SLICES << endl; + return 0; +} diff --git a/tests/auto/blackbox/testdata-clangdb/project1/project.qbs b/tests/auto/blackbox/testdata-clangdb/project1/project.qbs new file mode 100644 index 00000000..ecfc562b --- /dev/null +++ b/tests/auto/blackbox/testdata-clangdb/project1/project.qbs @@ -0,0 +1,41 @@ +// $ g++ 'i like spaces.cpp' '-DSPACES="!have \\fun\x5c!\n"' '-DSPICES=%T% # && $$ 1>&2 '\''\n'\''\n' '-DSLICES=(42>24)' && ./a.out +// SPACES=!have \fun\! +// SPICES=%T% # && $$ 1>&2 '\n' +// SLICES=1 + +Project { +Application { + Probe { + id: dummy + property bool isMingw: qbs.toolchain.contains("mingw") + property bool isMsvc: qbs.toolchain.contains("msvc") + property var buildEnv: cpp.buildEnv + configure: { + if (!buildEnv) + return; + if (isMsvc) { + console.info("is msvc"); + console.info("INCLUDE=" + buildEnv["INCLUDE"]); + console.info("LIB=" + buildEnv["LIB"]); + } else if (isMingw) { + console.info("is mingw"); + console.info("PATH=" + buildEnv["PATH"]); + } + } + } + + targetName: "i like spaces" + + Depends { + name: "cpp" + } + + cpp.defines: base.concat([ + "SPACES=\"!have \\\\fun\\x5c!\\n\"", + "SPICES=%T% # && $$ 1>&2 '\\n'\\n", + "SLICES=(42>24)" + ]); + + files: ["i like spaces.cpp"] +} +} diff --git a/tests/auto/blackbox/testdata-java/java/Car.java b/tests/auto/blackbox/testdata-java/java/Car.java new file mode 100644 index 00000000..06afd5ae --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/Car.java @@ -0,0 +1,23 @@ +class Car implements Vehicle +{ + private InternalCombustionEngine engine; + + public Car() { + engine = new InternalCombustionEngine(); + } + + public void go() + { + System.out.println("Driving!"); + engine.run(); + } + + public class InternalCombustionEngine + { + public native void run(); + + public class ChemicalReaction { + public native void occur(); + } + } +} diff --git a/tests/auto/blackbox/testdata-java/java/Car8.java b/tests/auto/blackbox/testdata-java/java/Car8.java new file mode 100644 index 00000000..941c7126 --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/Car8.java @@ -0,0 +1,28 @@ +class Car8 implements Vehicle +{ + private InternalCombustionEngine engine; + + public Car8() { + engine = new InternalCombustionEngine(); + } + + public void go() + { + System.out.println("Driving!"); + engine.run(); + } + + public class InternalCombustionEngine + { + public native void run(); + + public class ChemicalReaction { + public native void occur(); + + public class Atoms { + @java.lang.annotation.Native + public int hydrogenAtomCount; + } + } + } +} diff --git a/tests/auto/blackbox/testdata-java/java/HelloWorld.java b/tests/auto/blackbox/testdata-java/java/HelloWorld.java new file mode 100644 index 00000000..d42146d6 --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/HelloWorld.java @@ -0,0 +1,15 @@ +package io.qt.qbs; + +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Tach."); + } + + public class Internal { + public native void something(); + } + + public class Other { + public final int countOfThings = 0; + } +} diff --git a/tests/auto/blackbox/testdata-java/java/HelloWorld8.java b/tests/auto/blackbox/testdata-java/java/HelloWorld8.java new file mode 100644 index 00000000..d1ced80d --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/HelloWorld8.java @@ -0,0 +1,8 @@ +package io.qt.qbs; + +public class HelloWorld8 { + public class Other { + @java.lang.annotation.Native + public final int countOfThings = 0; + } +} diff --git a/tests/auto/blackbox/testdata-java/java/Jet.java b/tests/auto/blackbox/testdata-java/java/Jet.java new file mode 100644 index 00000000..38c00c04 --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/Jet.java @@ -0,0 +1,7 @@ +class Jet implements Vehicle +{ + public void go() + { + System.out.println("Flying!"); + } +} diff --git a/tests/auto/blackbox/testdata-java/java/Manifest.mf b/tests/auto/blackbox/testdata-java/java/Manifest.mf new file mode 100644 index 00000000..2da157c8 --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/Manifest.mf @@ -0,0 +1 @@ +Some-Property: Some-Value diff --git a/tests/auto/blackbox/testdata-java/java/Manifest2.mf b/tests/auto/blackbox/testdata-java/java/Manifest2.mf new file mode 100644 index 00000000..17433ea7 --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/Manifest2.mf @@ -0,0 +1,2 @@ +Some-Property: Some-Value +Additional-Property: Additional-Value diff --git a/tests/auto/blackbox/testdata-java/java/NoPackage.java b/tests/auto/blackbox/testdata-java/java/NoPackage.java new file mode 100644 index 00000000..84045c3a --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/NoPackage.java @@ -0,0 +1,5 @@ +// package this.should.not.be.parsed; + +public class NoPackage { +public static void doSomething() {} +} diff --git a/tests/auto/blackbox/testdata-java/java/RandomStuff.java b/tests/auto/blackbox/testdata-java/java/RandomStuff.java new file mode 100644 index 00000000..fd98b3f7 --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/RandomStuff.java @@ -0,0 +1,6 @@ +package glob; + +public class RandomStuff { + public static void bar() { + } +} diff --git a/tests/auto/blackbox/testdata-java/java/Ship.java b/tests/auto/blackbox/testdata-java/java/Ship.java new file mode 100644 index 00000000..3ecce635 --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/Ship.java @@ -0,0 +1,12 @@ +class Ship implements Vehicle +{ + public boolean isInSpace; + + public void go() + { + if (isInSpace) + System.out.println("Flying (this is a space ship)!"); + else + System.out.println("Sailing!"); + } +} diff --git a/tests/auto/blackbox/testdata-java/java/Vehicle.java b/tests/auto/blackbox/testdata-java/java/Vehicle.java new file mode 100644 index 00000000..b3a0f805 --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/Vehicle.java @@ -0,0 +1,4 @@ +interface Vehicle +{ + public void go(); +} diff --git a/tests/auto/blackbox/testdata-java/java/Vehicles.java b/tests/auto/blackbox/testdata-java/java/Vehicles.java new file mode 100644 index 00000000..e25ab980 --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/Vehicles.java @@ -0,0 +1,37 @@ +import java.util.ArrayList; +import glob.RandomStuff; + +class Vehicles +{ + public static void main(String[] args) + { + System.loadLibrary("native"); + RandomStuff.bar(); + ArrayList vehicles = new ArrayList(); + + for (int i = 0; i < 3; i++) + { + vehicles.add(new Car()); + } + + for (int i = 0; i < 3; i++) + { + vehicles.add(new Jet()); + } + + for (int i = 0; i < 4; i++) + { + Ship ship = new Ship(); + ship.isInSpace = i % 2 == 0; + vehicles.add(ship); + } + + for (int i = 0; i < vehicles.size(); i++) + { + vehicles.get(i).go(); + } + + // doesn't compile, must be a bug + // delete vehicles; + } +} diff --git a/tests/auto/blackbox/testdata-java/java/engine.c b/tests/auto/blackbox/testdata-java/java/engine.c new file mode 100644 index 00000000..3c292ed1 --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/engine.c @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** 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 + +// javac 1.8 is required to generate native headers +#ifdef JNI_VERSION_1_8 +#include "Car_InternalCombustionEngine.h" +#endif + +JNIEXPORT void JNICALL Java_Car_00024InternalCombustionEngine_run(JNIEnv *env, jobject obj) { + printf("Native code performing complex internal combustion process (%p, %p)!\n", env, obj); +} diff --git a/tests/auto/blackbox/testdata-java/java/inner-class/InnerClass.java b/tests/auto/blackbox/testdata-java/java/inner-class/InnerClass.java new file mode 100644 index 00000000..3e0333ec --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/inner-class/InnerClass.java @@ -0,0 +1,6 @@ +public class InnerClass { + private final InnerInnerClass clazz = new InnerInnerClass(); + + private class InnerInnerClass { + } +} diff --git a/tests/auto/blackbox/testdata-java/java/inner-class/inner-class.qbs b/tests/auto/blackbox/testdata-java/java/inner-class/inner-class.qbs new file mode 100644 index 00000000..c4840147 --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/inner-class/inner-class.qbs @@ -0,0 +1,3 @@ +JavaJarFile { + files: ["**/*.java"] +} diff --git a/tests/auto/blackbox/testdata-java/java/vehicles.qbs b/tests/auto/blackbox/testdata-java/java/vehicles.qbs new file mode 100644 index 00000000..8153efe0 --- /dev/null +++ b/tests/auto/blackbox/testdata-java/java/vehicles.qbs @@ -0,0 +1,107 @@ +import qbs.FileInfo +import qbs.Utilities + +Project { + DynamicLibrary { + Depends { name: "cpp" } + Depends { name: "car_jar" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + + name: "native" + files: ["engine.c"] + + qbs.installPrefix: "" + install: true + installDir: "" + } + + JavaClassCollection { + Depends { name: "random_stuff" } + name: "cc" + java.additionalCompilerFlags: ["-Xlint:all"] + files: [ + "Car.java", "HelloWorld.java", "Jet.java", "NoPackage.java", "Ship.java", + "Vehicle.java", "Vehicles.java" + ] + + Group { + condition: Utilities.versionCompare(java.version, "1.8") >= 0 + files: ["Car8.java", "HelloWorld8.java"] + } + + Export { + Depends { name: "java" } + java.manifestClassPath: [product.targetName + ".jar"] + } + } + + JavaJarFile { + name: "random_stuff" + files: ["RandomStuff.java"] + + qbs.installPrefix: "" + Group { + fileTagsFilter: ["java.jar"] + qbs.install: true + } + + Export { + Depends { name: "java" } + java.manifestClassPath: [product.targetName + ".jar"] + } + } + + JavaJarFile { + name: "car_jar" + files: ["Car.java", "Vehicle.java"] + + Group { + condition: Utilities.versionCompare(java.version, "1.8") >= 0 + files: ["Car8.java"] + } + + Export { + Depends { name: "cpp" } + cpp.systemIncludePaths: { + var paths = importingProduct.java.jdkIncludePaths; + if (Utilities.versionCompare(importingProduct.java.version, "1.8") >= 0) { + paths.push(product.buildDirectory); // generated JNI headers + } + return paths; + } + + Depends { name: "java" } + java.manifestClassPath: [product.targetName + ".jar"] + } + + qbs.installPrefix: "" + Group { + fileTagsFilter: ["java.jar"] + qbs.install: true + } + } + + JavaJarFile { + Depends { name: "random_stuff" } + Depends { name: "car_jar" } + Depends { name: "native" } + name: "jar_file" + entryPoint: "Vehicles" + files: ["Jet.java", "Ship.java", "Vehicles.java", "Manifest.mf", "Manifest2.mf"] + + java.manifest: { + var mf = original; + mf["Extra-Property"] = "Crazy-Value"; + return mf; + } + + qbs.installPrefix: "" + Group { + fileTagsFilter: ["java.jar"] + qbs.install: true + } + } +} diff --git a/tests/auto/blackbox/testdata-joblimits/job-limits/job-limits.qbs b/tests/auto/blackbox/testdata-joblimits/job-limits/job-limits.qbs new file mode 100644 index 00000000..221105a6 --- /dev/null +++ b/tests/auto/blackbox/testdata-joblimits/job-limits/job-limits.qbs @@ -0,0 +1,97 @@ +import qbs.TextFile + +Project { + property int projectJobCount + property int productJobCount + property int moduleJobCount + JobLimit { + condition: projectJobCount !== -1 + jobPool: "singleton" + jobCount: projectJobCount + } + JobLimit { + condition: projectJobCount !== -1 + jobPool: "singleton" + jobCount: 100 + } + CppApplication { + name: "tool" + consoleApplication: true + cpp.cxxLanguageVersion: "c++14" + Properties { + condition: qbs.targetOS.contains("macos") + cpp.minimumMacosVersion: "10.9" + } + files: "main.cpp" + Group { + fileTagsFilter: "application" + fileTags: "tool_tag" + } + Export { + Rule { + alwaysRun: true + inputs: "tool_in" + explicitlyDependsOnFromDependencies: "tool_tag" + Artifact { filePath: input.completeBaseName + ".out"; fileTags: "tool_out" } + prepare: { + var cmd = new Command(explicitlyDependsOn.tool_tag[0].filePath, + [output.filePath]); + cmd.workingDirectory = product.buildDirectory; + cmd.description = "Running tool"; + cmd.jobPool = "singleton"; + return cmd; + } + } + JobLimit { + condition: project.moduleJobCount !== -1 + jobPool: "singleton" + jobCount: project.moduleJobCount + } + JobLimit { + condition: project.moduleJobCount !== -1 + jobPool: "singleton" + jobCount: 200 + } + } + } + Product { + name: "p" + type: "tool_out" + Depends { name: "tool" } + Rule { + multiplex: true + outputFileTags: "tool_in" + outputArtifacts: { + var artifacts = []; + for (var i = 0; i < 5; ++i) + artifacts.push({filePath: "file" + i + ".in", fileTags: "tool_in"}); + return artifacts; + } + prepare: { + var commands = []; + for (var i = 0; i < outputs.tool_in.length; ++i) { + var cmd = new JavaScriptCommand(); + var output = outputs.tool_in[i]; + cmd.output = output.filePath; + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output, TextFile.WriteOnly); + f.close(); + } + commands.push(cmd); + }; + return commands; + } + } + JobLimit { + condition: project.productJobCount !== -1 + jobPool: "singleton" + jobCount: project.productJobCount + } + JobLimit { + condition: project.productJobCount !== -1 + jobPool: "singleton" + jobCount: 300 + } + } +} diff --git a/tests/auto/blackbox/testdata-joblimits/job-limits/main.cpp b/tests/auto/blackbox/testdata-joblimits/job-limits/main.cpp new file mode 100644 index 00000000..ec9acba8 --- /dev/null +++ b/tests/auto/blackbox/testdata-joblimits/job-limits/main.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) || defined(WIN32) +#include +#include +#else +#include +#endif + +static bool tryLock(FILE *f) +{ + const int exitCode = +#if defined(_WIN32) || defined(WIN32) + _locking(_fileno(f), _LK_NBLCK, 10); + +#else + lockf(fileno(f), F_TLOCK, 10); +#endif + return exitCode == 0; +} + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + std::cerr << "tool needs exactly one argument" << std::endl; + return 1; + } + + const std::string lockFilePath = std::string(argv[0]) + ".lock"; + std::FILE * const lockFile = std::fopen(lockFilePath.c_str(), "w"); + if (!lockFile) { + std::cerr << "cannot open lock file: " << strerror(errno) << std::endl; + return 2; + } + if (!tryLock(lockFile)) { + if (errno == EACCES || errno == EAGAIN) { + std::cerr << "tool is exclusive" << std::endl; + return 3; + } else { + std::cerr << "unexpected lock failure: " << strerror(errno) << std::endl; + fclose(lockFile); + return 4; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + fclose(lockFile); + std::FILE * const output = std::fopen(argv[1], "w"); + if (!output) { + std::cerr << "cannot create output file: " << strerror(errno) << std::endl; + return 5; + } + fclose(output); +} diff --git a/tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/add-qobject-macro-to-generated-cpp-file.qbs b/tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/add-qobject-macro-to-generated-cpp-file.qbs new file mode 100644 index 00000000..db0e2009 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/add-qobject-macro-to-generated-cpp-file.qbs @@ -0,0 +1,25 @@ +import qbs.File + +QtApplication { + name: "p" + files: ["main.cpp", "object.h"] + Group { + files: "object.cpp.in" + fileTags: "cpp.in" + } + Rule { + inputs: "cpp.in" + Artifact { + filePath: input.completeBaseName + fileTags: "cpp" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generatating " + output.fileName; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); } + return cmd; + } + } + cpp.includePaths: path +} + diff --git a/tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/main.cpp b/tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/main.cpp new file mode 100644 index 00000000..3f57ddaf --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/main.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "object.h" + +int main() +{ + Object o; + o.f(); +} diff --git a/tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/object.cpp.in b/tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/object.cpp.in new file mode 100644 index 00000000..b1b9922a --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/object.cpp.in @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + +#include + +// class InternalClass : public QObject +// { +// Q_OBJECT +// }; + +void Object::f() { } + + +// #include diff --git a/tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/object.h b/tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/object.h new file mode 100644 index 00000000..5537a8d3 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/add-qobject-macro-to-generated-cpp-file/object.h @@ -0,0 +1,32 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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$ +** +****************************************************************************/ + +class Object { +public: + void f(); +}; diff --git a/tests/auto/blackbox/testdata-qt/auto-qrc/auto-qrc.qbs b/tests/auto/blackbox/testdata-qt/auto-qrc/auto-qrc.qbs new file mode 100644 index 00000000..92b29071 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/auto-qrc/auto-qrc.qbs @@ -0,0 +1,39 @@ +Project { + QtApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + + name: "app" + files: ["main.cpp"] + + Group { + prefix: "qrc-base/" + + Qt.core.resourcePrefix: "/thePrefix" + Qt.core.resourceSourceBase: "qrc-base" + + files: ["resource1.txt"] + fileTags: ["qt.core.resource_data"] + + Group { + prefix: "qrc-base/subdir/" + + Qt.core.resourceSourceBase: "qrc-base/subdir" + + files: ["resource2.txt"] + + Group { + prefix: "qrc-base/subdir/" + + Qt.core.resourcePrefix: "/theOtherPrefix" + + files: ["resource3.txt"] + } + } + } + } +} diff --git a/tests/auto/blackbox/testdata-qt/auto-qrc/main.cpp b/tests/auto/blackbox/testdata-qt/auto-qrc/main.cpp new file mode 100644 index 00000000..53a33854 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/auto-qrc/main.cpp @@ -0,0 +1,18 @@ +#include + +#include + +int main() +{ + QFile resource1(":/thePrefix/resource1.txt"); + if (!resource1.open(QIODevice::ReadOnly)) + return 1; + QFile resource2(":/thePrefix/resource2.txt"); + if (!resource2.open(QIODevice::ReadOnly)) + return 2; + QFile resource3(":/theOtherPrefix/resource3.txt"); + if (!resource3.open(QIODevice::ReadOnly)) + return 3; + std::cout << "resource data: " << resource1.readAll().constData() + << resource2.readAll().constData() << resource3.readAll().constData() << std::endl; +} diff --git a/tests/auto/blackbox/testdata-qt/auto-qrc/qrc-base/resource1.txt b/tests/auto/blackbox/testdata-qt/auto-qrc/qrc-base/resource1.txt new file mode 100644 index 00000000..edf22a3d --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/auto-qrc/qrc-base/resource1.txt @@ -0,0 +1 @@ +resource1 diff --git a/tests/auto/blackbox/testdata-qt/auto-qrc/qrc-base/subdir/resource2.txt b/tests/auto/blackbox/testdata-qt/auto-qrc/qrc-base/subdir/resource2.txt new file mode 100644 index 00000000..b7c27009 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/auto-qrc/qrc-base/subdir/resource2.txt @@ -0,0 +1 @@ +resource2 diff --git a/tests/auto/blackbox/testdata-qt/auto-qrc/qrc-base/subdir/resource3.txt b/tests/auto/blackbox/testdata-qt/auto-qrc/qrc-base/subdir/resource3.txt new file mode 100644 index 00000000..6df9761d --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/auto-qrc/qrc-base/subdir/resource3.txt @@ -0,0 +1 @@ +resource3 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 00000000..3fe8b6f0 --- /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 00000000..c95f1190 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/cached-qml/cached-qml.qbs @@ -0,0 +1,38 @@ +import qbs.Utilities + +CppApplication { + name: "app" + consoleApplication: true + Depends { name: "Qt.core" } + Depends { name: "Qt.quick" } + Depends { name: "Qt.qml" } + install: true + installDir: "" + qbs.installPrefix: "" + Qt.qml.generateCacheFiles: true + Qt.qml.cacheFilesInstallDir: "data" + + files: [ + "main.cpp", + "MainForm.ui.qml", + "main.qml", + "stuff.js" + ] + + // 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" + } + + Probe { + id: qtVersionProbe + property string qtVersion: Qt.core.version + configure: { + console.info("qmlcachegen must work: " + + (Utilities.versionCompare(qtVersion, "5.11") >= 0)) + } + } +} 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 00000000..ac82ac0c --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/cached-qml/main.cpp @@ -0,0 +1,14 @@ +#include +#include + +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 00000000..f5f32f6b --- /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 00000000..57c8988b --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/cached-qml/qml.qrc @@ -0,0 +1,7 @@ + + + + main.qml + MainForm.ui.qml + + 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 00000000..3c19feac --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/cached-qml/stuff.js @@ -0,0 +1 @@ +function title() { return "Wello Horld!"; } diff --git a/tests/auto/blackbox/testdata-qt/combined-moc/combined-moc.qbs b/tests/auto/blackbox/testdata-qt/combined-moc/combined-moc.qbs new file mode 100644 index 00000000..acbed163 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/combined-moc/combined-moc.qbs @@ -0,0 +1,7 @@ +QtApplication { + name: "theapp" + files: [ + "main.cpp", + "theobject.h", + ] +} diff --git a/tests/auto/blackbox/testdata-qt/combined-moc/main.cpp b/tests/auto/blackbox/testdata-qt/combined-moc/main.cpp new file mode 100644 index 00000000..8fc13028 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/combined-moc/main.cpp @@ -0,0 +1,6 @@ +#include "theobject.h" + +int main() +{ + TheObject object; +} diff --git a/tests/auto/blackbox/testdata-qt/combined-moc/theobject.h b/tests/auto/blackbox/testdata-qt/combined-moc/theobject.h new file mode 100644 index 00000000..759eae8f --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/combined-moc/theobject.h @@ -0,0 +1,6 @@ +#include + +class TheObject : public QObject +{ + Q_OBJECT +}; diff --git a/tests/auto/blackbox/testdata-qt/create-project/dummy.txt b/tests/auto/blackbox/testdata-qt/create-project/dummy.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata-qt/dbus-adaptors/THIS.IS.A.STRANGE.FILENAME.CAR.XML b/tests/auto/blackbox/testdata-qt/dbus-adaptors/THIS.IS.A.STRANGE.FILENAME.CAR.XML new file mode 100644 index 00000000..6d8c9d19 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/dbus-adaptors/THIS.IS.A.STRANGE.FILENAME.CAR.XML @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tests/auto/blackbox/testdata-qt/dbus-adaptors/car.cpp b/tests/auto/blackbox/testdata-qt/dbus-adaptors/car.cpp new file mode 100644 index 00000000..5e4f348d --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/dbus-adaptors/car.cpp @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part 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 "car.h" +#include +#include + +static const double Pi = 3.14159265358979323846264338327950288419717; + +QRectF Car::boundingRect() const +{ + return {-35, -81, 70, 115}; +} + +Car::Car() : color(Qt::green), wheelsAngle(0), speed(0) +{ + startTimer(1000 / 33); + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIsFocusable, true); +} + +void Car::accelerate() +{ + if (speed < 10) + ++speed; +} + +void Car::decelerate() +{ + if (speed > -10) + --speed; +} + +void Car::turnLeft() +{ + if (wheelsAngle > -30) + wheelsAngle -= 5; +} + +void Car::turnRight() +{ + if (wheelsAngle < 30) + wheelsAngle += 5; +} + +void Car::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option); + Q_UNUSED(widget); + + painter->setBrush(Qt::gray); + painter->drawRect(-20, -58, 40, 2); // front axel + painter->drawRect(-20, 7, 40, 2); // rear axel + + painter->setBrush(color); + painter->drawRect(-25, -79, 50, 10); // front wing + + painter->drawEllipse(-25, -48, 50, 20); // side pods + painter->drawRect(-25, -38, 50, 35); // side pods + painter->drawRect(-5, 9, 10, 10); // back pod + + painter->drawEllipse(-10, -81, 20, 100); // main body + + painter->drawRect(-17, 19, 34, 15); // rear wing + + painter->setBrush(Qt::black); + painter->drawPie(-5, -51, 10, 15, 0, 180 * 16); + painter->drawRect(-5, -44, 10, 10); // cocpit + + painter->save(); + painter->translate(-20, -58); + painter->rotate(wheelsAngle); + painter->drawRect(-10, -7, 10, 15); // front left + painter->restore(); + + painter->save(); + painter->translate(20, -58); + painter->rotate(wheelsAngle); + painter->drawRect(0, -7, 10, 15); // front left + painter->restore(); + + painter->drawRect(-30, 0, 12, 17); // rear left + painter->drawRect(19, 0, 12, 17); // rear right +} + +void Car::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event); + + const qreal axelDistance = 54; + qreal wheelsAngleRads = (wheelsAngle * Pi) / 180; + qreal turnDistance = ::cos(wheelsAngleRads) * axelDistance * 2; + qreal turnRateRads = wheelsAngleRads / turnDistance; // rough estimate + qreal turnRate = (turnRateRads * 180) / Pi; + qreal rotation = speed * turnRate; + + setTransform(QTransform().rotate(rotation), true); + setTransform(QTransform::fromTranslate(0, -speed), true); + update(); +} diff --git a/tests/auto/blackbox/testdata-qt/dbus-adaptors/car.h b/tests/auto/blackbox/testdata-qt/dbus-adaptors/car.h new file mode 100644 index 00000000..f14b1062 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/dbus-adaptors/car.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef CAR_H +#define CAR_H + +#include +#include + +class Car : public QGraphicsObject +{ + Q_OBJECT +public: + Car(); + QRectF boundingRect() const; + +public Q_SLOTS: + void accelerate(); + void decelerate(); + void turnLeft(); + void turnRight(); + +Q_SIGNALS: + void crashed(); + +protected: + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + void timerEvent(QTimerEvent *event); + +private: + QBrush color; + qreal wheelsAngle; // used when applying rotation + qreal speed; // delta movement along the body axis +}; + +#endif // CAR_H diff --git a/tests/auto/blackbox/testdata-qt/dbus-adaptors/car.qbs b/tests/auto/blackbox/testdata-qt/dbus-adaptors/car.qbs new file mode 100644 index 00000000..34aab470 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/dbus-adaptors/car.qbs @@ -0,0 +1,18 @@ +CppApplication { + name: "car" + condition: Qt.dbus.present + cpp.cxxLanguageVersion: "c++11" + Depends { name: "Qt.dbus"; required: false } + Depends { name: "Qt.widgets" } + files: [ + "car.cpp", + "car.h", + "main.cpp", + ] + + Group { + name: "DBUS Adaptor" + files: ["THIS.IS.A.STRANGE.FILENAME.CAR.XML"] + fileTags: ["qt.dbus.adaptor"] + } +} diff --git a/tests/auto/blackbox/testdata-qt/dbus-adaptors/main.cpp b/tests/auto/blackbox/testdata-qt/dbus-adaptors/main.cpp new file mode 100644 index 00000000..0491719d --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/dbus-adaptors/main.cpp @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part 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 "car.h" +#include "car_adaptor.h" +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + QGraphicsScene scene; + scene.setSceneRect(-500, -500, 1000, 1000); + scene.setItemIndexMethod(QGraphicsScene::NoIndex); + + const auto car = new Car(); + scene.addItem(car); + + QGraphicsView view(&scene); + view.setRenderHint(QPainter::Antialiasing); + view.setBackgroundBrush(Qt::darkGray); + view.setWindowTitle(QT_TRANSLATE_NOOP(QGraphicsView, "Qt DBus Controlled Car")); + view.resize(400, 300); + view.show(); + + new CarInterfaceAdaptor(car); + QDBusConnection connection = QDBusConnection::sessionBus(); + connection.registerObject("/Car", car); + connection.registerService("org.example.CarExample"); + + return app.exec(); +} diff --git a/tests/auto/blackbox/testdata-qt/dbus-interfaces/car.xml b/tests/auto/blackbox/testdata-qt/dbus-interfaces/car.xml new file mode 100644 index 00000000..6d8c9d19 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/dbus-interfaces/car.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.cpp b/tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.cpp new file mode 100644 index 00000000..eaff5c77 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part 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 + +#include "controller.h" +#include "car_interface.h" + +Controller::Controller(QWidget *parent) + : QWidget(parent) +{ + ui.setupUi(this); + car = new org::example::Examples::CarInterface("org.example.CarExample", "/Car", + QDBusConnection::sessionBus(), this); + startTimer(1000); +} + +void Controller::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event); + if (car->isValid()) + ui.label->setText("connected"); + else + ui.label->setText("disconnected"); +} + +void Controller::on_accelerate_clicked() +{ + car->accelerate(); +} + +void Controller::on_decelerate_clicked() +{ + car->decelerate(); +} + +void Controller::on_left_clicked() +{ + car->turnLeft(); +} + +void Controller::on_right_clicked() +{ + car->turnRight(); +} diff --git a/tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.h b/tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.h new file mode 100644 index 00000000..b6492835 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef CONTROLLER_H +#define CONTROLLER_H + +#include "ui_controller.h" +#include "car_interface.h" + +class Controller : public QWidget +{ + Q_OBJECT + +public: + Controller(QWidget *parent = nullptr); + +protected: + void timerEvent(QTimerEvent *event); + +private slots: + void on_accelerate_clicked(); + void on_decelerate_clicked(); + void on_left_clicked(); + void on_right_clicked(); + +private: + Ui::Controller ui; + org::example::Examples::CarInterface *car; +}; + +#endif + diff --git a/tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.qbs b/tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.qbs new file mode 100644 index 00000000..a1bde7f1 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.qbs @@ -0,0 +1,19 @@ +CppApplication { + name: "controller" + condition: Qt.dbus.present + Depends { name: "Qt.dbus"; required: false } + Depends { name: "Qt.widgets" } + cpp.cxxLanguageVersion: "c++11" + files: [ + "controller.cpp", + "controller.h", + "controller.ui", + "main.cpp", + ] + + Group { + name: "DBUS Interface" + files: ["car.xml"] + fileTags: ["qt.dbus.interface"] + } +} diff --git a/tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.ui b/tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.ui new file mode 100644 index 00000000..379015bf --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/dbus-interfaces/controller.ui @@ -0,0 +1,64 @@ + + Controller + + + + 0 + 0 + 255 + 111 + + + + Controller + + + + 9 + + + 6 + + + + + Controller + + + Qt::AlignCenter + + + + + + + Decelerate + + + + + + + Accelerate + + + + + + + Right + + + + + + + Left + + + + + + + + diff --git a/tests/auto/blackbox/testdata-qt/dbus-interfaces/main.cpp b/tests/auto/blackbox/testdata-qt/dbus-interfaces/main.cpp new file mode 100644 index 00000000..fdd9fc59 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/dbus-interfaces/main.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part 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 +#include + +#include "controller.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + Controller controller; + controller.show(); + return app.exec(); +} diff --git a/tests/auto/blackbox/testdata-qt/forced-moc/createqtclass.h b/tests/auto/blackbox/testdata-qt/forced-moc/createqtclass.h new file mode 100644 index 00000000..b57df630 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/forced-moc/createqtclass.h @@ -0,0 +1,14 @@ +#ifndef CREATEQTCLASS_H +#define CREATEQTCLASS_H + +#include + +#define CREATE_QT_CLASS(className) \ +class className : public QObject \ +{ \ + Q_OBJECT \ +public: \ + Q_SIGNAL void mySignal(); \ +} + +#endif diff --git a/tests/auto/blackbox/testdata-qt/forced-moc/forced-moc.qbs b/tests/auto/blackbox/testdata-qt/forced-moc/forced-moc.qbs new file mode 100644 index 00000000..736f4034 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/forced-moc/forced-moc.qbs @@ -0,0 +1,19 @@ +QtApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + files: "main.cpp" + Group { + name: "QObject service provider" + files: "createqtclass.h" + fileTags: ["hpp", "unmocable"] + } + Group { + name: "QObject service user" + files: "myqtclass.h" + fileTags: ["hpp", "mocable"] + } +} diff --git a/tests/auto/blackbox/testdata-qt/forced-moc/main.cpp b/tests/auto/blackbox/testdata-qt/forced-moc/main.cpp new file mode 100644 index 00000000..e07c6298 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/forced-moc/main.cpp @@ -0,0 +1,14 @@ +#include "myqtclass.h" + +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + MyQtClass c; + QObject::connect(&c, &MyQtClass::mySignal, [] { qDebug() << "Hello from slot"; qApp->quit(); }); + QTimer::singleShot(0, &c, &MyQtClass::mySignal); + return app.exec(); +} diff --git a/tests/auto/blackbox/testdata-qt/forced-moc/myqtclass.h b/tests/auto/blackbox/testdata-qt/forced-moc/myqtclass.h new file mode 100644 index 00000000..d2557aab --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/forced-moc/myqtclass.h @@ -0,0 +1,8 @@ +#ifndef MYQTCLASS_H +#define MYQTCLASS_H + +#include "createqtclass.h" + +CREATE_QT_CLASS(MyQtClass); + +#endif diff --git a/tests/auto/blackbox/testdata-qt/included-moc-cpp/included-moc-cpp.qbs b/tests/auto/blackbox/testdata-qt/included-moc-cpp/included-moc-cpp.qbs new file mode 100644 index 00000000..1ed85ccd --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/included-moc-cpp/included-moc-cpp.qbs @@ -0,0 +1,9 @@ +import qbs + +QtApplication { + files: [ + "main.cpp", + "myobject.cpp", + "myobject.h", + ] +} diff --git a/tests/auto/blackbox/testdata-qt/included-moc-cpp/main.cpp b/tests/auto/blackbox/testdata-qt/included-moc-cpp/main.cpp new file mode 100644 index 00000000..5323e4c9 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/included-moc-cpp/main.cpp @@ -0,0 +1,7 @@ +#include "myobject.h" + +int main() +{ + MyObject o; + QObject::connect(&o, &QObject::destroyed, [] { }); +} diff --git a/tests/auto/blackbox/testdata-qt/included-moc-cpp/myobject.cpp b/tests/auto/blackbox/testdata-qt/included-moc-cpp/myobject.cpp new file mode 100644 index 00000000..de0988c2 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/included-moc-cpp/myobject.cpp @@ -0,0 +1,3 @@ +#include "myobject.h" + +#include diff --git a/tests/auto/blackbox/testdata-qt/included-moc-cpp/myobject.h b/tests/auto/blackbox/testdata-qt/included-moc-cpp/myobject.h new file mode 100644 index 00000000..61c2920a --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/included-moc-cpp/myobject.h @@ -0,0 +1,11 @@ +#ifndef MYOBJECT_H +#define MYOBJECT_H + +#include + +class MyObject : public QObject +{ + Q_OBJECT +}; + +#endif diff --git a/tests/auto/blackbox/testdata-qt/linker-variant/main.cpp b/tests/auto/blackbox/testdata-qt/linker-variant/main.cpp new file mode 100644 index 00000000..d1c118fa --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/linker-variant/main.cpp @@ -0,0 +1,6 @@ +#include + +int main() +{ + qDebug() << "Tach."; +} diff --git a/tests/auto/blackbox/testdata-qt/linker-variant/qt-linker-variant.qbs b/tests/auto/blackbox/testdata-qt/linker-variant/qt-linker-variant.qbs new file mode 100644 index 00000000..ab588900 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/linker-variant/qt-linker-variant.qbs @@ -0,0 +1,10 @@ +QtApplication { + Probe { + id: qtConfigProbe + property stringList moduleConfig: Qt.core.moduleConfig + configure: { + console.info("Qt requires gold: " + moduleConfig.contains("use_gold_linker")); + } + } + files: "main.cpp" +} diff --git a/tests/auto/blackbox/testdata-qt/lrelease/de.ts b/tests/auto/blackbox/testdata-qt/lrelease/de.ts new file mode 100644 index 00000000..5beb013b --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/lrelease/de.ts @@ -0,0 +1,23 @@ + + + + + Application + + I am hunry + Ich bin Ungar + + + I am thirsty + Ich bin Donnerstag + + + Please press Control + Bitte drücken Sie den Kontrolleur + + + No keyboard detected. Please press F1 to continue + Sehr witzig + + + diff --git a/tests/auto/blackbox/testdata-qt/lrelease/hu.ts b/tests/auto/blackbox/testdata-qt/lrelease/hu.ts new file mode 100644 index 00000000..dc45eac4 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/lrelease/hu.ts @@ -0,0 +1,15 @@ + + + + + Application + + My hovercraft is full of eels + A légpárnásom tele van angolnákkal + + + I will not buy this record; it is scratched + Nem fogok vásárolni ezt a rekordot; ez karcos + + + diff --git a/tests/auto/blackbox/testdata-qt/lrelease/lrelease.qbs b/tests/auto/blackbox/testdata-qt/lrelease/lrelease.qbs new file mode 100644 index 00000000..978aba33 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/lrelease/lrelease.qbs @@ -0,0 +1,6 @@ +Product { + name: "lrelease-test" + type: ["ts"] + Depends { name: "Qt.core" } + files: ["de.ts", "hu.ts"] +} diff --git a/tests/auto/blackbox/testdata-qt/metatypes/metatypes.qbs b/tests/auto/blackbox/testdata-qt/metatypes/metatypes.qbs new file mode 100644 index 00000000..bbc98c93 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/metatypes/metatypes.qbs @@ -0,0 +1,28 @@ +import qbs.Utilities + +StaticLibrary { + name: "mylib" + + Depends { name: "Qt.core" } + + qbs.installPrefix: "some-prefix" + + Probe { + id: capabilitiesChecker + property string version: Qt.core.version + configure: { + if (Utilities.versionCompare(version, "5.15") >= 0) + console.info("can generate"); + else + console.info("cannot generate"); + found = true; + } + } + + files: [ + "mocableclass1.cpp", + "mocableclass1.h", + "mocableclass2.cpp", + "unmocableclass.cpp", + ] +} diff --git a/tests/auto/blackbox/testdata-qt/metatypes/mocableclass1.cpp b/tests/auto/blackbox/testdata-qt/metatypes/mocableclass1.cpp new file mode 100644 index 00000000..06adc8ca --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/metatypes/mocableclass1.cpp @@ -0,0 +1,3 @@ +#include "mocableclass1.h" + +MocableClass1::MocableClass1(QObject *parent) : QObject(parent) {} diff --git a/tests/auto/blackbox/testdata-qt/metatypes/mocableclass1.h b/tests/auto/blackbox/testdata-qt/metatypes/mocableclass1.h new file mode 100644 index 00000000..020c1517 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/metatypes/mocableclass1.h @@ -0,0 +1,8 @@ +#include + +class MocableClass1 : public QObject +{ + Q_OBJECT +public: + MocableClass1(QObject *parent = nullptr); +}; diff --git a/tests/auto/blackbox/testdata-qt/metatypes/mocableclass2.cpp b/tests/auto/blackbox/testdata-qt/metatypes/mocableclass2.cpp new file mode 100644 index 00000000..bf538913 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/metatypes/mocableclass2.cpp @@ -0,0 +1,10 @@ +#include + +class MocableClass2 : public QObject +{ + Q_OBJECT +public: + MocableClass2(QObject *parent) : QObject(parent) {} +}; + +#include diff --git a/tests/auto/blackbox/testdata-qt/metatypes/unmocableclass.cpp b/tests/auto/blackbox/testdata-qt/metatypes/unmocableclass.cpp new file mode 100644 index 00000000..34330d18 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/metatypes/unmocableclass.cpp @@ -0,0 +1,7 @@ +#include + +class UnmocableClass : public QObject +{ +public: + UnmocableClass(QObject *parent) : QObject(parent) {} +}; diff --git a/tests/auto/blackbox/testdata-qt/mixed-build-variants/main.cpp b/tests/auto/blackbox/testdata-qt/mixed-build-variants/main.cpp new file mode 100644 index 00000000..141d7ebe --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/mixed-build-variants/main.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 + +int main() +{ + qDebug("Tach."); +} diff --git a/tests/auto/blackbox/testdata-qt/mixed-build-variants/mixed-build-variants.qbs b/tests/auto/blackbox/testdata-qt/mixed-build-variants/mixed-build-variants.qbs new file mode 100644 index 00000000..7d8ab1b9 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/mixed-build-variants/mixed-build-variants.qbs @@ -0,0 +1,9 @@ +QtApplication { + Properties { + condition: qbs.toolchain.contains("msvc") + Qt.core.qtBuildVariant: "release" + } + Qt.core.qtBuildVariant: "dummy" + + files: ["main.cpp"] +} diff --git a/tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/main.cpp b/tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/main.cpp new file mode 100644 index 00000000..b586f8a8 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/main.cpp @@ -0,0 +1,6 @@ +#include "myobject.h" + +int main() +{ + useMyObject(); +} diff --git a/tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/moc-and-cxx-combining.qbs b/tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/moc-and-cxx-combining.qbs new file mode 100644 index 00000000..f5463853 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/moc-and-cxx-combining.qbs @@ -0,0 +1,4 @@ +QtApplication { + cpp.combineCxxSources: true + files: ["main.cpp", "myobject.h", "myobject.cpp"] +} diff --git a/tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/myobject.cpp b/tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/myobject.cpp new file mode 100644 index 00000000..7e2e3ac0 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/myobject.cpp @@ -0,0 +1,14 @@ +#include "myobject.h" + +#include + +class MyObject : public QObject +{ + Q_OBJECT +public: + void use() {} +}; + +void useMyObject() { MyObject().use(); } + +#include diff --git a/tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/myobject.h b/tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/myobject.h new file mode 100644 index 00000000..013d3ee9 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-and-cxx-combining/myobject.h @@ -0,0 +1,6 @@ +#ifndef MYOBJECT_H +#define MYOBJECT_H + +void useMyObject(); + +#endif diff --git a/tests/auto/blackbox/testdata-qt/moc-compiler-defines/main.cpp b/tests/auto/blackbox/testdata-qt/moc-compiler-defines/main.cpp new file mode 100644 index 00000000..d3b8f310 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-compiler-defines/main.cpp @@ -0,0 +1,7 @@ +#include "object.h" + +int main() +{ + QObject o; + return 0; +} diff --git a/tests/auto/blackbox/testdata-qt/moc-compiler-defines/moc-compiler-defines.qbs b/tests/auto/blackbox/testdata-qt/moc-compiler-defines/moc-compiler-defines.qbs new file mode 100644 index 00000000..e184f554 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-compiler-defines/moc-compiler-defines.qbs @@ -0,0 +1,3 @@ +QtApplication { + files: ["main.cpp", "object.h", "object.cpp"] +} diff --git a/tests/auto/blackbox/testdata-qt/moc-compiler-defines/object.cpp b/tests/auto/blackbox/testdata-qt/moc-compiler-defines/object.cpp new file mode 100644 index 00000000..7ffcf419 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-compiler-defines/object.cpp @@ -0,0 +1,6 @@ +#include "object.h" + +Object::Object(QObject *parent) : QObject(parent) +{ + +} diff --git a/tests/auto/blackbox/testdata-qt/moc-compiler-defines/object.h b/tests/auto/blackbox/testdata-qt/moc-compiler-defines/object.h new file mode 100644 index 00000000..3221d117 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-compiler-defines/object.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** Contact: http://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 OBJECT_H +#define OBJECT_H + +#include + +// These were not defined during the moc run (QBS-1592). +// Do not use Q_OS_UNIX here as it is a fallback value which is defined when nothing else is. +#if defined(Q_OS_DARWIN) || defined(Q_OS_LINUX) || defined(Q_OS_WIN) + +class Object : public QObject +{ + Q_OBJECT +public: + explicit Object(QObject *parent = nullptr); +}; + +#endif + +#endif // OBJECT_H diff --git a/tests/auto/blackbox/testdata-qt/moc-flags/blubb.h b/tests/auto/blackbox/testdata-qt/moc-flags/blubb.h new file mode 100644 index 00000000..54e2fdd7 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-flags/blubb.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 + +class Blubb : public QObject +{ + Q_OBJECT +public: + Blubb() { } + void makeBlubb() { } +}; diff --git a/tests/auto/blackbox/testdata-qt/moc-flags/main.cpp b/tests/auto/blackbox/testdata-qt/moc-flags/main.cpp new file mode 100644 index 00000000..8e7c9e28 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-flags/main.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 "blubb.h" + +int main() +{ + Blubb().makeBlubb(); +} diff --git a/tests/auto/blackbox/testdata-qt/moc-flags/moc-flags.qbs b/tests/auto/blackbox/testdata-qt/moc-flags/moc-flags.qbs new file mode 100644 index 00000000..7cc4e5c4 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-flags/moc-flags.qbs @@ -0,0 +1,3 @@ +QtApplication { + files: ["main.cpp", "blubb.h"] +} diff --git a/tests/auto/blackbox/testdata-qt/moc-same-file-name/main.cpp b/tests/auto/blackbox/testdata-qt/moc-same-file-name/main.cpp new file mode 100644 index 00000000..1ca3fc9e --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-same-file-name/main.cpp @@ -0,0 +1,8 @@ +#include "src1/someclass.h" +#include "src2/someclass.h" + +int main() +{ + Src1::SomeClass sc1; + Src2::SomeClass sc2; +} diff --git a/tests/auto/blackbox/testdata-qt/moc-same-file-name/moc-same-file-name.qbs b/tests/auto/blackbox/testdata-qt/moc-same-file-name/moc-same-file-name.qbs new file mode 100644 index 00000000..461fec50 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-same-file-name/moc-same-file-name.qbs @@ -0,0 +1,25 @@ +QtApplication { + name: "app" + cpp.cxxLanguageVersion: "c++11" + files: "main.cpp" + Group { + name: "src1" + Qt.core.generatedHeadersDir: product.buildDirectory + "/qt.headers_src1" + prefix: name + '/' + files: [ + "someclass.cpp", + "someclass.h", + "somefile.cpp", + ] + } + Group { + name: "src2" + prefix: name + '/' + Qt.core.generatedHeadersDir: product.buildDirectory + "/qt.headers_src2" + files: [ + "someclass.cpp", + "someclass.h", + "somefile.cpp", + ] + } +} diff --git a/tests/auto/blackbox/testdata-qt/moc-same-file-name/src1/someclass.cpp b/tests/auto/blackbox/testdata-qt/moc-same-file-name/src1/someclass.cpp new file mode 100644 index 00000000..2f9cf8bd --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-same-file-name/src1/someclass.cpp @@ -0,0 +1,7 @@ +#include "someclass.h" + +namespace Src1 { + +SomeClass::SomeClass(QObject *parent) : QObject(parent) { } + +} diff --git a/tests/auto/blackbox/testdata-qt/moc-same-file-name/src1/someclass.h b/tests/auto/blackbox/testdata-qt/moc-same-file-name/src1/someclass.h new file mode 100644 index 00000000..cc5eefd8 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-same-file-name/src1/someclass.h @@ -0,0 +1,17 @@ +#ifndef SOMECLASS1_H +#define SOMECLASS1_H + +#include + +namespace Src1 { + +class SomeClass : public QObject +{ + Q_OBJECT +public: + SomeClass(QObject *parent = nullptr); +}; + +} + +#endif diff --git a/tests/auto/blackbox/testdata-qt/moc-same-file-name/src1/somefile.cpp b/tests/auto/blackbox/testdata-qt/moc-same-file-name/src1/somefile.cpp new file mode 100644 index 00000000..a2e46e81 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-same-file-name/src1/somefile.cpp @@ -0,0 +1,13 @@ +#include + +class MyClass1 : public QObject +{ + Q_OBJECT +}; + +static void f() +{ + MyClass1 m; +} + +#include diff --git a/tests/auto/blackbox/testdata-qt/moc-same-file-name/src2/someclass.cpp b/tests/auto/blackbox/testdata-qt/moc-same-file-name/src2/someclass.cpp new file mode 100644 index 00000000..13be460b --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-same-file-name/src2/someclass.cpp @@ -0,0 +1,7 @@ +#include "someclass.h" + +namespace Src2 { + +SomeClass::SomeClass(QObject *parent) : QObject(parent) { } + +} diff --git a/tests/auto/blackbox/testdata-qt/moc-same-file-name/src2/someclass.h b/tests/auto/blackbox/testdata-qt/moc-same-file-name/src2/someclass.h new file mode 100644 index 00000000..fe8a400a --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-same-file-name/src2/someclass.h @@ -0,0 +1,17 @@ +#ifndef SOMECLASS2_H +#define SOMECLASS2_H + +#include + +namespace Src2 { + +class SomeClass : public QObject +{ + Q_OBJECT +public: + SomeClass(QObject *parent = nullptr); +}; + +} + +#endif diff --git a/tests/auto/blackbox/testdata-qt/moc-same-file-name/src2/somefile.cpp b/tests/auto/blackbox/testdata-qt/moc-same-file-name/src2/somefile.cpp new file mode 100644 index 00000000..770183a5 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/moc-same-file-name/src2/somefile.cpp @@ -0,0 +1,13 @@ +#include + +class MyClass2 : public QObject +{ + Q_OBJECT +}; + +static void f() +{ + MyClass2 m; +} + +#include diff --git a/tests/auto/blackbox/testdata-qt/pkgconfig/main.cpp b/tests/auto/blackbox/testdata-qt/pkgconfig/main.cpp new file mode 100644 index 00000000..2080b507 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/pkgconfig/main.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifdef QT_CORE_LIB +#include +#include +#else +#include +#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/auto/blackbox/testdata-qt/pkgconfig/pkgconfig.qbs b/tests/auto/blackbox/testdata-qt/pkgconfig/pkgconfig.qbs new file mode 100644 index 00000000..87ec6dc7 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/pkgconfig/pkgconfig.qbs @@ -0,0 +1,23 @@ +import qbs.Probes + +Project { + property string name: 'pkgconfig' + CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: project.name + Probes.PkgConfigProbe { + id: pkgConfig + name: "QtCore" + minVersion: '4.0.0' + maxVersion: '5.99.99' + } + files: 'main.cpp' + cpp.cxxFlags: pkgConfig.cflags + cpp.linkerFlags: pkgConfig.libs + } +} diff --git a/tests/auto/blackbox/testdata-qt/plugin-meta-data/app.cpp b/tests/auto/blackbox/testdata-qt/plugin-meta-data/app.cpp new file mode 100644 index 00000000..c57504e9 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/plugin-meta-data/app.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include +#include +#include + +#ifdef QT_STATIC +Q_IMPORT_PLUGIN(ThePlugin) +#endif + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + QJsonObject metaData; + for (const QStaticPlugin &p : QPluginLoader::staticPlugins()) { + const QJsonObject &md = p.metaData(); + if (md.value("className") == "ThePlugin") { + metaData = md; + break; + } + } +#ifdef QT_STATIC + if (metaData.isEmpty()) { + qDebug() << "no static metadata"; + return 1; + } +#else + if (!metaData.isEmpty()) { + qDebug() << "static metadata"; + return 1; + } +#endif + if (metaData.isEmpty()) + metaData = QPluginLoader("thePlugin").metaData(); + const QJsonValue v = metaData.value(QStringLiteral("theKey")); + if (!v.isArray()) { + qDebug() << "value is" << v; + return 1; + } + const QJsonArray a = v.toArray(); + if (a.size() != 1 || a.first() != QLatin1String("theValue")) { + qDebug() << "value is" << v; + return 1; + } + const QJsonValue v2 = metaData.value(QStringLiteral("MetaData")).toObject() + .value(QStringLiteral("theOtherKey")); + if (v2.toString() != QLatin1String("theOtherValue")) { + qDebug() << "metadata:" << metaData; + return 1; + } + qDebug() << "all ok!"; + return 0; +} diff --git a/tests/auto/blackbox/testdata-qt/plugin-meta-data/metadata.json b/tests/auto/blackbox/testdata-qt/plugin-meta-data/metadata.json new file mode 100644 index 00000000..1377879e --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/plugin-meta-data/metadata.json @@ -0,0 +1,3 @@ +{ + "theOtherKey" : "theOtherValue" +} diff --git a/tests/auto/blackbox/testdata-qt/plugin-meta-data/plugin-meta-data.qbs b/tests/auto/blackbox/testdata-qt/plugin-meta-data/plugin-meta-data.qbs new file mode 100644 index 00000000..7fb47757 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/plugin-meta-data/plugin-meta-data.qbs @@ -0,0 +1,59 @@ +Project { + QtApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + + name: "app" + consoleApplication: true + + Depends { name: "thePlugin" } + + cpp.cxxLanguageVersion: "c++11" + + Properties { + condition: qbs.targetOS.contains("unix") + cpp.rpaths: [cpp.rpathOrigin] + } + + Group { + fileTagsFilter: product.type + qbs.install: true + } + + files: ["app.cpp"] + } + + Library { + type: Qt.core.staticBuild ? "staticlibrary" : "dynamiclibrary" + name: "thePlugin" + + Depends { name: "cpp" } + Depends { name: "Qt.core" } + + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + cpp.defines: [Qt.core.staticBuild ? "QT_STATICPLUGIN" : "QT_PLUGIN"] + cpp.cxxLanguageVersion: "c++11" + cpp.sonamePrefix: qbs.targetOS.contains("darwin") ? "@rpath" : undefined + cpp.includePaths: ["."] + Qt.core.pluginMetaData: ["theKey=theValue"] + + Group { + fileTagsFilter: product.type + qbs.install: true + } + + files: ["theplugin.cpp"] + + Group { + files: ["metadata.json"] + fileTags: ["qt_plugin_metadata"] + } + } +} diff --git a/tests/auto/blackbox/testdata-qt/plugin-meta-data/theplugin.cpp b/tests/auto/blackbox/testdata-qt/plugin-meta-data/theplugin.cpp new file mode 100644 index 00000000..90ce230e --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/plugin-meta-data/theplugin.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** 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 +#include + +class ThePlugin : public QObject +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.qbs.ThePlugin" FILE "metadata.json") +}; + +#include diff --git a/tests/auto/blackbox/testdata-qt/plugin-support/modules/m1/m1.qbs b/tests/auto/blackbox/testdata-qt/plugin-support/modules/m1/m1.qbs new file mode 100644 index 00000000..a4cad304 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/plugin-support/modules/m1/m1.qbs @@ -0,0 +1,12 @@ +Module { + property bool useDummy + Depends { name: "Qt.plugin_support" } + Properties { + condition: useDummy + Qt.plugin_support.pluginsByType: ({imageformats: "dummy"}) + } + Properties { + condition: Qt.plugin_support.allPluginsByType && Qt.plugin_support.allPluginsByType.imageformats + Qt.plugin_support.pluginsByType: ({imageformats: Qt.plugin_support.allPluginsByType.imageformats[0]}) + } +} diff --git a/tests/auto/blackbox/testdata-qt/plugin-support/modules/m2/m2.qbs b/tests/auto/blackbox/testdata-qt/plugin-support/modules/m2/m2.qbs new file mode 100644 index 00000000..c4730ab6 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/plugin-support/modules/m2/m2.qbs @@ -0,0 +1,7 @@ +Module { + Depends { name: "Qt.plugin_support" } + Properties { + condition: Qt.plugin_support.allPluginsByType && Qt.plugin_support.allPluginsByType.imageformats + Qt.plugin_support.pluginsByType: ({imageformats: Qt.plugin_support.allPluginsByType.imageformats[1]}) + } +} diff --git a/tests/auto/blackbox/testdata-qt/plugin-support/plugin-support-main.cpp b/tests/auto/blackbox/testdata-qt/plugin-support/plugin-support-main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/plugin-support/plugin-support-main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata-qt/plugin-support/plugin-support.qbs b/tests/auto/blackbox/testdata-qt/plugin-support/plugin-support.qbs new file mode 100644 index 00000000..c554a7dc --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/plugin-support/plugin-support.qbs @@ -0,0 +1,18 @@ +QtGuiApplication { + Probe { + id: staticProbe + property bool isStaticQt: Qt.gui.isStaticLibrary + property var plugins: Qt.plugin_support.effectivePluginsByType + property var allPlugins: Qt.plugin_support.allPluginsByType + configure: { + console.info("static Qt: " + isStaticQt); + console.info("requested image plugins: %" + plugins.imageformats + "%"); + console.info("all image plugins: #" + allPlugins.imageformats + "#"); + console.info("platform plugin count: " + (plugins.platforms || []).length); + } + } + + Depends { name: "m1" } + Depends { name: "m2" } + files: "plugin-support-main.cpp" +} diff --git a/tests/auto/blackbox/testdata-qt/qml-debugging/main.cpp b/tests/auto/blackbox/testdata-qt/qml-debugging/main.cpp new file mode 100644 index 00000000..7311f2d6 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qml-debugging/main.cpp @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** 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 +#include + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) +#include +#include +using Application = QGuiApplication; +#define AH_SO_THIS_IS_QT5 +#else +#include +#include +#define AH_SO_THIS_IS_QT4 +using Application = QApplication; +#endif + +int main(int argc, char *argv[]) +{ + Application app(argc, argv); +#ifdef AH_SO_THIS_IS_QT5 + QQmlApplicationEngine engine; + engine.load(QUrl("blubb")); +#else + QDeclarativeView view; + view.setSource(QUrl("blubb")); +#endif + + return app.exec(); +} diff --git a/tests/auto/blackbox/testdata-qt/qml-debugging/qml-debugging.qbs b/tests/auto/blackbox/testdata-qt/qml-debugging/qml-debugging.qbs new file mode 100644 index 00000000..8176a7c3 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qml-debugging/qml-debugging.qbs @@ -0,0 +1,7 @@ +QtApplication { + name: "debuggable-app" + consoleApplication: true + Depends { name: "Qt.quick" } + Qt.quick.qmlDebugging: true + files: "main.cpp" +} diff --git a/tests/auto/blackbox/testdata-qt/qmltyperegistrar/example.qml b/tests/auto/blackbox/testdata-qt/qmltyperegistrar/example.qml new file mode 100644 index 00000000..ef97df12 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qmltyperegistrar/example.qml @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $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$ +** +****************************************************************************/ + +// ![0] +import People 1.0 + +Person { + name: "Bob Jones" + shoeSize: 12 +} +// ![0] diff --git a/tests/auto/blackbox/testdata-qt/qmltyperegistrar/main.cpp b/tests/auto/blackbox/testdata-qt/qmltyperegistrar/main.cpp new file mode 100644 index 00000000..6c3920f0 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qmltyperegistrar/main.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $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 +#include +#include +#include +#include "person.h" + +int main(int argc, char ** argv) +{ + QCoreApplication app(argc, argv); + + QQmlEngine engine; + QQmlComponent component(&engine, QUrl("qrc:example.qml")); + auto *person = qobject_cast(component.create()); + if (person) { + qWarning() << "The person's name is" << person->name(); + qWarning() << "They wear a" << person->shoeSize() << "sized shoe"; + } else { + qWarning() << component.errors(); + } + + return EXIT_SUCCESS; +} diff --git a/tests/auto/blackbox/testdata-qt/qmltyperegistrar/person.cpp b/tests/auto/blackbox/testdata-qt/qmltyperegistrar/person.cpp new file mode 100644 index 00000000..de4a33dd --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qmltyperegistrar/person.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $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 "person.h" + +// ![0] +Person::Person(QObject *parent) +: QObject(parent), m_shoeSize(0) +{ +} + +QString Person::name() const +{ + return m_name; +} + +void Person::setName(const QString &n) +{ + m_name = n; +} + +int Person::shoeSize() const +{ + return m_shoeSize; +} + +void Person::setShoeSize(int s) +{ + m_shoeSize = s; +} + +// ![0] diff --git a/tests/auto/blackbox/testdata-qt/qmltyperegistrar/person.h b/tests/auto/blackbox/testdata-qt/qmltyperegistrar/person.h new file mode 100644 index 00000000..530c335d --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qmltyperegistrar/person.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $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$ +** +****************************************************************************/ +#ifndef PERSON_H +#define PERSON_H + +#include +#include + +//![0] +class Person : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString name READ name WRITE setName) + Q_PROPERTY(int shoeSize READ shoeSize WRITE setShoeSize) + QML_ELEMENT +public: + Person(QObject *parent = nullptr); + + QString name() const; + void setName(const QString &); + + int shoeSize() const; + void setShoeSize(int); + +private: + QString m_name; + int m_shoeSize; +}; +//![0] + +#endif // PERSON_H diff --git a/tests/auto/blackbox/testdata-qt/qmltyperegistrar/qmltyperegistrar.qbs b/tests/auto/blackbox/testdata-qt/qmltyperegistrar/qmltyperegistrar.qbs new file mode 100644 index 00000000..68dc8374 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qmltyperegistrar/qmltyperegistrar.qbs @@ -0,0 +1,33 @@ +import qbs.Utilities + +CppApplication { + name: "myapp" + Depends { name: "Qt.qml" } + + Qt.qml.importVersion: "1" + cpp.includePaths: sourceDirectory + qbs.installPrefix: "" + + files: [ + "main.cpp", + "person.cpp", + "person.h", + ] + + Group { + files: "example.qml" + fileTags: "qt.core.resource_data" + } + + Probe { + id: versionProbe + property string version: Qt.core.version + configure: { + if (Utilities.versionCompare(version, "5.15") >= 0) + console.info("has registrar"); + else + console.info("does not have registrar"); + found = true; + } + } +} diff --git a/tests/auto/blackbox/testdata-qt/qobject-in-mm/main.mm b/tests/auto/blackbox/testdata-qt/qobject-in-mm/main.mm new file mode 100644 index 00000000..40c464b8 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qobject-in-mm/main.mm @@ -0,0 +1,13 @@ +#include + +class Foo : public QObject +{ +Q_OBJECT +}; + +int main() +{ + Foo foo; + return 0; +} +#include "main.moc" diff --git a/tests/auto/blackbox/testdata-qt/qobject-in-mm/qobject-in-mm.qbs b/tests/auto/blackbox/testdata-qt/qobject-in-mm/qobject-in-mm.qbs new file mode 100644 index 00000000..3ceb8515 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qobject-in-mm/qobject-in-mm.qbs @@ -0,0 +1,4 @@ +CppApplication { + Depends { name: "Qt.core" } + files: ["main.mm"] +} diff --git a/tests/auto/blackbox/testdata-qt/qrc/bla.cpp b/tests/auto/blackbox/testdata-qt/qrc/bla.cpp new file mode 100644 index 00000000..5375b33b --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qrc/bla.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** 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 + +int main() +{ + QFileInfo f(":/stuff.txt"); + if (!f.exists()) + return 1; + if (!f.isFile()) + return 2; + QFileInfo d(":/subdir"); + if (!d.exists()) + return 3; + if (!d.isDir()) + return 4; +} + diff --git a/tests/auto/blackbox/testdata-qt/qrc/bla.qrc b/tests/auto/blackbox/testdata-qt/qrc/bla.qrc new file mode 100644 index 00000000..cad4c6cd --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qrc/bla.qrc @@ -0,0 +1,6 @@ + + + stuff.txt + subdir/ + + diff --git a/tests/auto/blackbox/testdata-qt/qrc/i.qbs b/tests/auto/blackbox/testdata-qt/qrc/i.qbs new file mode 100644 index 00000000..604652a8 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qrc/i.qbs @@ -0,0 +1,24 @@ +Project { + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + consoleApplication: true + type: "application" + name: "i" + + Depends { + name: "Qt.core" + } + + files: [ + "bla.cpp", + "bla.qrc", + //"test.cpp", + ] + } +} + diff --git a/tests/auto/blackbox/testdata-qt/qrc/stuff.txt b/tests/auto/blackbox/testdata-qt/qrc/stuff.txt new file mode 100644 index 00000000..78f0d32c --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qrc/stuff.txt @@ -0,0 +1 @@ +a resource file diff --git a/tests/auto/blackbox/testdata-qt/qrc/subdir/dummy.txt b/tests/auto/blackbox/testdata-qt/qrc/subdir/dummy.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata-qt/qrc/test.cpp b/tests/auto/blackbox/testdata-qt/qrc/test.cpp new file mode 100644 index 00000000..7da700fd --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qrc/test.cpp @@ -0,0 +1 @@ +void test() {} diff --git a/tests/auto/blackbox/testdata-qt/qt-keywords/main.cpp b/tests/auto/blackbox/testdata-qt/qt-keywords/main.cpp new file mode 100644 index 00000000..4ef493cc --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qt-keywords/main.cpp @@ -0,0 +1,14 @@ +#include + +class AnObject : public QObject +{ + Q_OBJECT +signals: + void someSignal(); +}; + +int main() +{ +} + +#include diff --git a/tests/auto/blackbox/testdata-qt/qt-keywords/qt-keywords.qbs b/tests/auto/blackbox/testdata-qt/qt-keywords/qt-keywords.qbs new file mode 100644 index 00000000..ddd93827 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qt-keywords/qt-keywords.qbs @@ -0,0 +1,3 @@ +QtApplication { + files: ["main.cpp"] +} diff --git a/tests/auto/blackbox/testdata-qt/qtscxml/dummystatemachine.scxml b/tests/auto/blackbox/testdata-qt/qtscxml/dummystatemachine.scxml new file mode 100644 index 00000000..6c751866 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qtscxml/dummystatemachine.scxml @@ -0,0 +1,3 @@ + + + diff --git a/tests/auto/blackbox/testdata-qt/qtscxml/main.cpp b/tests/auto/blackbox/testdata-qt/qtscxml/main.cpp new file mode 100644 index 00000000..c9a7d774 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qtscxml/main.cpp @@ -0,0 +1,15 @@ +#ifdef HAS_QTSCXML +#include +#endif + +#include + +int main() +{ +#ifdef HAS_QTSCXML + QbsTest::QbsStateMachine machine; + std::cout << "state machine name: " << qPrintable(machine.name()) << std::endl; +#else + std::cout << "QtScxml not present" << std::endl; +#endif +} diff --git a/tests/auto/blackbox/testdata-qt/qtscxml/qtscxml.qbs b/tests/auto/blackbox/testdata-qt/qtscxml/qtscxml.qbs new file mode 100644 index 00000000..90b968ec --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/qtscxml/qtscxml.qbs @@ -0,0 +1,62 @@ +import qbs.Environment +import qbs.FileInfo +import qbs.Utilities + +Project { + QtApplication { + name: "app" + Depends { name: "Qt.scxml"; required: false } + + Properties { + condition: Qt.scxml.present && Utilities.versionCompare(Qt.core.version, "5.13.0") != 0 + cpp.defines: ["HAS_QTSCXML"] + } + + Qt.scxml.className: "QbsStateMachine" + Qt.scxml.namespace: "QbsTest" + Qt.scxml.generateStateMethods: true + + files: ["main.cpp"] + Group { + files: ["dummystatemachine.scxml"] + fileTags: ["qt.scxml.compilable"] + } + } + + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: "runner" + type: ["runner"] + Depends { name: "app" } + Rule { + inputsFromDependencies: ["application"] + outputFileTags: ["runner"] + prepare: { + var cmd = new Command(input.filePath); + cmd.description = "running " + input.filePath; + + var envVars = {}; + if (product.qbs.hostOS.contains("windows")) { + envVars["PATH"] = FileInfo.toWindowsSeparators(input["Qt.core"].binPath); + } else if (product.qbs.hostOS.contains("macos")) { + envVars["DYLD_LIBRARY_PATH"] = input["Qt.core"].libPath; + envVars["DYLD_FRAMEWORK_PATH"] = input["Qt.core"].libPath; + } else { + envVars["LD_LIBRARY_PATH"] = input["Qt.core"].libPath; + } + for (var varName in envVars) { + var oldValue = Environment.getEnv(varName) || ""; + var newValue = envVars[varName] + product.qbs.pathListSeparator + oldValue; + cmd.environment.push(varName + '=' + newValue); + } + + return [cmd]; + } + } + } +} diff --git a/tests/auto/blackbox/testdata-qt/quick-compiler/main.cpp b/tests/auto/blackbox/testdata-qt/quick-compiler/main.cpp new file mode 100644 index 00000000..1fe8944f --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/quick-compiler/main.cpp @@ -0,0 +1,18 @@ +#include +#include + +int main(int argc, char *argv[]) +{ +#if defined(Q_OS_WIN) + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif + + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/qml/subdir/test.qml"))); + if (engine.rootObjects().isEmpty()) + return -1; + + return app.exec(); +} diff --git a/tests/auto/blackbox/testdata-qt/quick-compiler/qml.qrc b/tests/auto/blackbox/testdata-qt/quick-compiler/qml.qrc new file mode 100644 index 00000000..12ede604 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/quick-compiler/qml.qrc @@ -0,0 +1,5 @@ + + + qml/subdir/test.qml + + diff --git a/tests/auto/blackbox/testdata-qt/quick-compiler/qml/subdir/test.qml b/tests/auto/blackbox/testdata-qt/quick-compiler/qml/subdir/test.qml new file mode 100644 index 00000000..9c36e13c --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/quick-compiler/qml/subdir/test.qml @@ -0,0 +1,5 @@ +import QtQuick 2.0 + +Item { + +} diff --git a/tests/auto/blackbox/testdata-qt/quick-compiler/quick-compiler.qbs b/tests/auto/blackbox/testdata-qt/quick-compiler/quick-compiler.qbs new file mode 100644 index 00000000..b141c867 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/quick-compiler/quick-compiler.qbs @@ -0,0 +1,22 @@ +CppApplication { + Depends { name: "Qt.quick" } + Qt.quick.useCompiler: Qt.quick.compilerAvailable + + cpp.cxxLanguageVersion: "c++11" + + Probe { + id: qtQuickCompilerProbe + property bool hasCompiler: Qt.quick.compilerAvailable + configure: { + if (hasCompiler) + console.info("compiler available"); + else + console.info("compiler not available"); + } + } + + files: [ + "main.cpp", + "qml.qrc", + ] +} 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 00000000..aba40ccd --- /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 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 00000000..4333b64a --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/file.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +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 00000000..b8ac33b9 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/remove-moc-header-from-file-list/remove-moc-header-from-file-list.qbs @@ -0,0 +1,7 @@ +QtApplication { + name: "p" + files: [ + "file.h", + "file.cpp" + ] +} diff --git a/tests/auto/blackbox/testdata-qt/static-qt-plugin-linking/lib.cpp b/tests/auto/blackbox/testdata-qt/static-qt-plugin-linking/lib.cpp new file mode 100644 index 00000000..dd79cbe6 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/static-qt-plugin-linking/lib.cpp @@ -0,0 +1 @@ +void function() {} diff --git a/tests/auto/blackbox/testdata-qt/static-qt-plugin-linking/main.cpp b/tests/auto/blackbox/testdata-qt/static-qt-plugin-linking/main.cpp new file mode 100644 index 00000000..d1bca9e2 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/static-qt-plugin-linking/main.cpp @@ -0,0 +1,7 @@ +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + return app.exec(); +} diff --git a/tests/auto/blackbox/testdata-qt/static-qt-plugin-linking/static-qt-plugin-linking.qbs b/tests/auto/blackbox/testdata-qt/static-qt-plugin-linking/static-qt-plugin-linking.qbs new file mode 100644 index 00000000..97c04f41 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/static-qt-plugin-linking/static-qt-plugin-linking.qbs @@ -0,0 +1,26 @@ +Product { + name: "p" + Probe { + id: staticQtChecker + property bool staticQt: Qt.core.staticBuild + configure: { + found = staticQt; + if (found) + console.info("Qt is static"); + } + } + + Group { + condition: type.contains("application") + files: "main.cpp" + } + + Group { + condition: type.contains("staticlibrary") + files: "lib.cpp" + } + + Depends { name: "Qt.core" } + Depends { name: "Qt.gui" } + Depends { name: "Qt.qminimal"; condition: Qt.core.staticBuild; } +} diff --git a/tests/auto/blackbox/testdata-qt/trackAddMocInclude/after/main.cpp b/tests/auto/blackbox/testdata-qt/trackAddMocInclude/after/main.cpp new file mode 100644 index 00000000..aa63c20b --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/trackAddMocInclude/after/main.cpp @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** 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 + +class MyObject : public QObject +{ + Q_OBJECT +public: + MyObject(QObject *parent = nullptr) + : QObject(parent) + { + } +}; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + const auto obj = new MyObject(&app); + return app.exec(); +} + +#include + diff --git a/tests/auto/blackbox/testdata-qt/trackAddMocInclude/before/main.cpp b/tests/auto/blackbox/testdata-qt/trackAddMocInclude/before/main.cpp new file mode 100644 index 00000000..350c25f6 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/trackAddMocInclude/before/main.cpp @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** 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 + +class MyObject : public QObject +{ + Q_OBJECT +public: + MyObject(QObject *parent = nullptr) + : QObject(parent) + { + } +}; + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + const auto obj = new MyObject(&app); + return app.exec(); +} + diff --git a/tests/auto/blackbox/testdata-qt/trackAddMocInclude/before/test.qbs b/tests/auto/blackbox/testdata-qt/trackAddMocInclude/before/test.qbs new file mode 100644 index 00000000..0aa1b1d2 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/trackAddMocInclude/before/test.qbs @@ -0,0 +1,6 @@ +Application { + Depends { name: "Qt.core" } + cpp.cxxLanguageVersion: "c++11" + files: ["main.cpp"] +} + diff --git a/tests/auto/blackbox/testdata-qt/trackQObjChange/bla.cpp b/tests/auto/blackbox/testdata-qt/trackQObjChange/bla.cpp new file mode 100644 index 00000000..74c8bdac --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/trackQObjChange/bla.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 "bla.h" + +int main() +{ + MyObject obj; + obj.setObjectName("I am the object!"); +} diff --git a/tests/auto/blackbox/testdata-qt/trackQObjChange/bla_noqobject.h b/tests/auto/blackbox/testdata-qt/trackQObjChange/bla_noqobject.h new file mode 100644 index 00000000..ca45b65b --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/trackQObjChange/bla_noqobject.h @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 + +class MyObject : public QObject +{ +}; + diff --git a/tests/auto/blackbox/testdata-qt/trackQObjChange/bla_qobject.h b/tests/auto/blackbox/testdata-qt/trackQObjChange/bla_qobject.h new file mode 100644 index 00000000..840465e8 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/trackQObjChange/bla_qobject.h @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 + +class MyObject : public QObject +{ + Q_OBJECT +}; + diff --git a/tests/auto/blackbox/testdata-qt/trackQObjChange/i.qbs b/tests/auto/blackbox/testdata-qt/trackQObjChange/i.qbs new file mode 100644 index 00000000..34801754 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/trackQObjChange/i.qbs @@ -0,0 +1,17 @@ +Project { + Product { + type: "application" + consoleApplication: true + name: "i" + + Depends { + name: "Qt.core" + } + + files: [ + "bla.cpp", + "bla.h" + ] + } +} + diff --git a/tests/auto/blackbox/testdata-qt/unmocable/foo.h b/tests/auto/blackbox/testdata-qt/unmocable/foo.h new file mode 100644 index 00000000..a67438a7 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/unmocable/foo.h @@ -0,0 +1,2 @@ +#define Q_OBJECT 156 +int someNumber() { return Q_OBJECT; } diff --git a/tests/auto/blackbox/testdata-qt/unmocable/main.cpp b/tests/auto/blackbox/testdata-qt/unmocable/main.cpp new file mode 100644 index 00000000..e7a3f8df --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/unmocable/main.cpp @@ -0,0 +1,6 @@ +#include "foo.h" + +int main() +{ + return someNumber() - 156; +} diff --git a/tests/auto/blackbox/testdata-qt/unmocable/unmocable.qbs b/tests/auto/blackbox/testdata-qt/unmocable/unmocable.qbs new file mode 100644 index 00000000..780d27e3 --- /dev/null +++ b/tests/auto/blackbox/testdata-qt/unmocable/unmocable.qbs @@ -0,0 +1,9 @@ +Application { + Depends { name: "Qt.core" } + files: ["main.cpp"] + Group { + files: ["foo.h"] + fileTags: ["unmocable"] + overrideTags: false + } +} diff --git a/tests/auto/blackbox/testdata/QTBUG-51237/modules/mymodule/mymodule.qbs b/tests/auto/blackbox/testdata/QTBUG-51237/modules/mymodule/mymodule.qbs new file mode 100644 index 00000000..55495a95 --- /dev/null +++ b/tests/auto/blackbox/testdata/QTBUG-51237/modules/mymodule/mymodule.qbs @@ -0,0 +1,4 @@ +Module { + property stringList theProperty: [] + //property stringList otherProperty: theProperty.concat([]) +} diff --git a/tests/auto/blackbox/testdata/QTBUG-51237/qtbug-51237.qbs b/tests/auto/blackbox/testdata/QTBUG-51237/qtbug-51237.qbs new file mode 100644 index 00000000..849a943f --- /dev/null +++ b/tests/auto/blackbox/testdata/QTBUG-51237/qtbug-51237.qbs @@ -0,0 +1,17 @@ +Product { + type: "custom" + Depends { name: "mymodule" } + Rule { + multiplex: true + outputFileTags: ["custom"] + prepare: { + var theProperty = product.mymodule.theProperty; + if (!theProperty) + throw "Oh no!"; + var dummy = new JavaScriptCommand(); + dummy.silent = true; + dummy.sourceCode = function() {}; + return [dummy]; + } + } +} 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 00000000..7fba9c98 --- /dev/null +++ b/tests/auto/blackbox/testdata/add-filetag-to-generated-artifact/add-filetag-to-generated-artifact.qbs @@ -0,0 +1,35 @@ +import qbs.File + +Project { + property bool enableTagging + CppApplication { + name: "my_app" + files: "main.cpp" + Group { + condition: project.enableTagging + 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 00000000..237c8ce1 --- /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/always-run/dummy.txt b/tests/auto/blackbox/testdata/always-run/dummy.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/always-run/rule.qbs b/tests/auto/blackbox/testdata/always-run/rule.qbs new file mode 100644 index 00000000..7d61e5da --- /dev/null +++ b/tests/auto/blackbox/testdata/always-run/rule.qbs @@ -0,0 +1,28 @@ +import qbs.TextFile + +Product { + type: ["blubb"] + Group { + files: ["dummy.txt"] + fileTags: ["dummy"] + } + + Rule { + alwaysRun: false + inputs: ["dummy"] + Artifact { + filePath: "blubb.txt" + fileTags: product.type + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "yo"; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.write("blubb"); + f.close(); + } + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/always-run/transformer.qbs b/tests/auto/blackbox/testdata/always-run/transformer.qbs new file mode 100644 index 00000000..8ae44fb7 --- /dev/null +++ b/tests/auto/blackbox/testdata/always-run/transformer.qbs @@ -0,0 +1,22 @@ +import qbs.TextFile + +Product { + type: ["blubb"] + Transformer { + alwaysRun: false + Artifact { + filePath: "blubb.txt" + fileTags: product.type + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "yo"; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.write("blubb"); + f.close(); + } + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/archiver/archivable.qbs b/tests/auto/blackbox/testdata/archiver/archivable.qbs new file mode 100644 index 00000000..8e521ddf --- /dev/null +++ b/tests/auto/blackbox/testdata/archiver/archivable.qbs @@ -0,0 +1,11 @@ +Product { + name: "archivable" + type: "archiver.archive" + Depends { name: "archiver" } + archiver.workingDirectory: path + Group { + files: ["list.txt"] + fileTags: ["archiver.input-list"] + } + files: ["test.txt"] +} diff --git a/tests/auto/blackbox/testdata/archiver/list.txt b/tests/auto/blackbox/testdata/archiver/list.txt new file mode 100644 index 00000000..e300f8e2 --- /dev/null +++ b/tests/auto/blackbox/testdata/archiver/list.txt @@ -0,0 +1,2 @@ +test.txt +archivable.qbs diff --git a/tests/auto/blackbox/testdata/archiver/test.txt b/tests/auto/blackbox/testdata/archiver/test.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/artifact-scanning/artifact-scanning.qbs b/tests/auto/blackbox/testdata/artifact-scanning/artifact-scanning.qbs new file mode 100644 index 00000000..bcd23616 --- /dev/null +++ b/tests/auto/blackbox/testdata/artifact-scanning/artifact-scanning.qbs @@ -0,0 +1,30 @@ +Project { + CppApplication { + name: "p1" + consoleApplication: true + cpp.includePaths: ["."] + files: [ + "p1.cpp", + "shared.h", + ] + } + + CppApplication { + name: "p2" + consoleApplication: true + cpp.includePaths: ["."] + files: [ + "p2.cpp", + "shared.h", + ] + } + + CppApplication { + name: "p3" + consoleApplication: true + cpp.includePaths: [".", "subdir"] + files: [ + "p3.cpp", + ] + } +} diff --git a/tests/auto/blackbox/testdata/artifact-scanning/external-indirect.h b/tests/auto/blackbox/testdata/artifact-scanning/external-indirect.h new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/artifact-scanning/external.h b/tests/auto/blackbox/testdata/artifact-scanning/external.h new file mode 100644 index 00000000..1a2f366f --- /dev/null +++ b/tests/auto/blackbox/testdata/artifact-scanning/external.h @@ -0,0 +1 @@ +#include diff --git a/tests/auto/blackbox/testdata/artifact-scanning/p1.cpp b/tests/auto/blackbox/testdata/artifact-scanning/p1.cpp new file mode 100644 index 00000000..5c51a978 --- /dev/null +++ b/tests/auto/blackbox/testdata/artifact-scanning/p1.cpp @@ -0,0 +1,4 @@ +#include +#include + +int main() { } diff --git a/tests/auto/blackbox/testdata/artifact-scanning/p2.cpp b/tests/auto/blackbox/testdata/artifact-scanning/p2.cpp new file mode 100644 index 00000000..68d7d887 --- /dev/null +++ b/tests/auto/blackbox/testdata/artifact-scanning/p2.cpp @@ -0,0 +1,3 @@ +#include + +int main() { } diff --git a/tests/auto/blackbox/testdata/artifact-scanning/p3.cpp b/tests/auto/blackbox/testdata/artifact-scanning/p3.cpp new file mode 100644 index 00000000..58a752cb --- /dev/null +++ b/tests/auto/blackbox/testdata/artifact-scanning/p3.cpp @@ -0,0 +1,3 @@ +#include + +int main() {} diff --git a/tests/auto/blackbox/testdata/artifact-scanning/shared.h b/tests/auto/blackbox/testdata/artifact-scanning/shared.h new file mode 100644 index 00000000..91caaa65 --- /dev/null +++ b/tests/auto/blackbox/testdata/artifact-scanning/shared.h @@ -0,0 +1 @@ +#include diff --git a/tests/auto/blackbox/testdata/artifact-scanning/subdir/external2.h b/tests/auto/blackbox/testdata/artifact-scanning/subdir/external2.h new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/artifacts-map-change-tracking/artifacts-map-change-tracking.qbs b/tests/auto/blackbox/testdata/artifacts-map-change-tracking/artifacts-map-change-tracking.qbs new file mode 100644 index 00000000..87913853 --- /dev/null +++ b/tests/auto/blackbox/testdata/artifacts-map-change-tracking/artifacts-map-change-tracking.qbs @@ -0,0 +1,85 @@ +import qbs.File + +Project { + CppApplication { + name: "TheApp" + targetName: "TheBinary" + Rule { + inputs: "cpp.in" + Artifact { filePath: "test.cpp"; fileTags: 'cpp' } + prepare: { + console.info("running rule for " + output.fileName); + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); }; + return cmd; + } + } + files: ["main.cpp", /* 'test.txt' */] + Group { + files: "test.cpp.in" + fileTags: "cpp.in" + } + } + Product { + name: "meta" + type: "custom" + Depends { name: "TheApp" } + Group { + files: "dummy.in" + fileTags: "dummy.in" + } + Rule { + inputs: ["dummy.in"] + Artifact { filePath: "dummy"; fileTags: 'custom' } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "printing artifacts"; + cmd.sourceCode = function() { + var dep; + for (var i = 0; i < product.dependencies.length; ++i) { + var d = product.dependencies[i]; + if (d.name === "TheApp") { + dep = d; + break; + } + } + for (var p in dep.artifacts) { + var list = dep.artifacts[p]; + for (var i = 0; i < list.length; ++i) + console.info(list[i].fileName); + } + File.copy(input.filePath, output.filePath); + }; + return cmd; + } + } + } + + Product { + name: "p" + type: "p_type" + Rule { + multiplex: true + Artifact { filePath: "dummy1"; fileTags: "d_type" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { + var blubb = product.artifacts.qbs; + }; + return cmd; + } + } + Rule { + inputs: "d_type" + Artifact { filePath: "dummy2"; fileTags: "p_type" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { }; + return cmd; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/artifacts-map-change-tracking/dummy.in b/tests/auto/blackbox/testdata/artifacts-map-change-tracking/dummy.in new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/artifacts-map-change-tracking/main.cpp b/tests/auto/blackbox/testdata/artifacts-map-change-tracking/main.cpp new file mode 100644 index 00000000..1921f1fe --- /dev/null +++ b/tests/auto/blackbox/testdata/artifacts-map-change-tracking/main.cpp @@ -0,0 +1,3 @@ +void f(); + +int main() { f(); } diff --git a/tests/auto/blackbox/testdata/artifacts-map-change-tracking/test.cpp.in b/tests/auto/blackbox/testdata/artifacts-map-change-tracking/test.cpp.in new file mode 100644 index 00000000..8101b05d --- /dev/null +++ b/tests/auto/blackbox/testdata/artifacts-map-change-tracking/test.cpp.in @@ -0,0 +1 @@ +void f() { } diff --git a/tests/auto/blackbox/testdata/artifacts-map-change-tracking/test.txt b/tests/auto/blackbox/testdata/artifacts-map-change-tracking/test.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/artifacts-map-invalidation/artifacts-map-invalidation.qbs b/tests/auto/blackbox/testdata/artifacts-map-invalidation/artifacts-map-invalidation.qbs new file mode 100644 index 00000000..cd4e9b79 --- /dev/null +++ b/tests/auto/blackbox/testdata/artifacts-map-invalidation/artifacts-map-invalidation.qbs @@ -0,0 +1,57 @@ +import qbs.File + +Project { + Product { + name: "dep" + type: "dep.out" + Group { + files: "file.in" + fileTags: "dep.in" + } + Rule { + inputs: "dep.in" + outputFileTags: "dep.out" + outputArtifacts: { + if (!product.artifacts["dep.in"] || product.artifacts["dep.in"].length !== 1) + throw "source file not in artifacts map!" + if (product.artifacts["dep.out"] && product.artifacts["dep.out"].length !== 0) + throw "generated artifact already in map!"; + return [{filePath: "file.out", fileTags: "dep.out"}]; + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); } + return cmd; + } + } + } + Product { + name: "p" + type: "p.out" + Depends { name: "dep" } + Rule { + inputsFromDependencies: "dep.out" + Artifact { filePath: "myfile.out"; fileTags: "p.out" } + prepare: { + var dep; + for (var i = 0; i < product.dependencies.length; ++i) { + if (product.dependencies[i].name === "dep") { + dep = product.dependencies[i]; + break; + } + } + if (!dep) + throw "dependency not found"; + if (!dep.artifacts["dep.in"] || dep.artifacts["dep.in"].length !== 1) + throw "source file not in dependency's artifacts map!" + if (!dep.artifacts["dep.out"] || dep.artifacts["dep.out"].length !== 1) + throw "generated artifact not in dependency's artifacts map!"; + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); } + return cmd; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/artifacts-map-invalidation/file.in b/tests/auto/blackbox/testdata/artifacts-map-invalidation/file.in new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/artifacts-map-race-condition/artifacts-map-race-condition.qbs b/tests/auto/blackbox/testdata/artifacts-map-race-condition/artifacts-map-race-condition.qbs new file mode 100644 index 00000000..86255c6c --- /dev/null +++ b/tests/auto/blackbox/testdata/artifacts-map-race-condition/artifacts-map-race-condition.qbs @@ -0,0 +1,73 @@ +Product { + name: "p" + type: ["custom1", "custom2", "custom3", "custom4", "custom5"] + Rule { + multiplex: true + outputFileTags: "custom1" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "reader1"; + cmd.sourceCode = function() + { + for (var i = 0; i < 1000; ++i) { + for (var t in product.artifacts) { + var l = product.artifacts[t]; + for (var j = 0; j < l.length; ++j) + var fileName = l[j].fileName; + } + } + }; + return cmd; + } + } + Rule { + multiplex: true + outputFileTags: "helper" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "helper"; + cmd.sourceCode = function() { }; + return cmd; + } + } + + Rule { + inputs: ["helper"] + outputFileTags: ["custom2", "custom3", "custom4"] + outputArtifacts: { + console.info("writer"); + var artifacts = []; + for (var i = 0; i < 1000; ++i) { + artifacts.push({ filePath: "dummyt1" + i, fileTags: ["custom2"] }); + artifacts.push({ filePath: "dummyt2" + i, fileTags: ["custom3"] }); + artifacts.push({ filePath: "dummyt3" + i, fileTags: ["custom4"] }); + } + return artifacts; + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "writer dummy command"; + cmd.sourceCode = function() { }; + return cmd; + } + } + Rule { + multiplex: true + outputFileTags: "custom5" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "reader2"; + cmd.sourceCode = function() + { + for (var i = 0; i < 1000; ++i) { + for (var t in product.artifacts) { + var l = product.artifacts[t]; + for (var j = 0; j < l.length; ++j) + var fileName = l[j].fileName; + } + } + }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/assembly/assembly.qbs b/tests/auto/blackbox/testdata/assembly/assembly.qbs new file mode 100644 index 00000000..f7bd4eca --- /dev/null +++ b/tests/auto/blackbox/testdata/assembly/assembly.qbs @@ -0,0 +1,74 @@ +import qbs.TextFile + +Project { + Product { + type: ["properties"] + Depends { name: "cpp" } + Rule { + multiplex: true + Artifact { + filePath: "properties.json" + fileTags: ["properties"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.outputFilePath = outputs.properties[0].filePath; + cmd.tc = product.qbs.toolchain; + cmd.sourceCode = function () { + var tf = new TextFile(outputFilePath, TextFile.WriteOnly); + try { + tf.writeLine(JSON.stringify({ "qbs.toolchain": tc }, undefined, 4)); + } finally { + tf.close(); + } + }; + return [cmd]; + } + } + } + StaticLibrary { + name : "testa" + files : [ "testa.s" ] + Depends { name: "cpp" } + condition: qbs.toolchain.contains("gcc") + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } + StaticLibrary { + name : "testb" + files : [ "testb.S" ] + Depends { name: "cpp" } + condition: qbs.toolchain.contains("gcc") + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } + StaticLibrary { + name : "testc" + files : [ "testc.sx" ] + Depends { name: "cpp" } + condition: qbs.toolchain.contains("gcc") + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } + StaticLibrary { + name: "testd" + Group { + condition: product.condition + files: ["testd_" + qbs.architecture + ".asm"] + } + Depends { name: "cpp" } + condition: qbs.toolchain.contains("msvc") + && (qbs.architecture === "x86" || qbs.architecture === "x86_64") + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } +} + diff --git a/tests/auto/blackbox/testdata/assembly/testa.s b/tests/auto/blackbox/testdata/assembly/testa.s new file mode 100644 index 00000000..39e402ae --- /dev/null +++ b/tests/auto/blackbox/testdata/assembly/testa.s @@ -0,0 +1,3 @@ +.globl symbola +symbola: + nop diff --git a/tests/auto/blackbox/testdata/assembly/testb.S b/tests/auto/blackbox/testdata/assembly/testb.S new file mode 100644 index 00000000..845be550 --- /dev/null +++ b/tests/auto/blackbox/testdata/assembly/testb.S @@ -0,0 +1,3 @@ +#define bla nop +symbolb: + bla diff --git a/tests/auto/blackbox/testdata/assembly/testc.sx b/tests/auto/blackbox/testdata/assembly/testc.sx new file mode 100644 index 00000000..845be550 --- /dev/null +++ b/tests/auto/blackbox/testdata/assembly/testc.sx @@ -0,0 +1,3 @@ +#define bla nop +symbolb: + bla diff --git a/tests/auto/blackbox/testdata/assembly/testd_x86.asm b/tests/auto/blackbox/testdata/assembly/testd_x86.asm new file mode 100644 index 00000000..7b99c017 --- /dev/null +++ b/tests/auto/blackbox/testdata/assembly/testd_x86.asm @@ -0,0 +1,11 @@ +.386 +.model flat, stdcall + +.code +foo PROC + nop + RET +foo ENDP + +END + diff --git a/tests/auto/blackbox/testdata/assembly/testd_x86_64.asm b/tests/auto/blackbox/testdata/assembly/testd_x86_64.asm new file mode 100644 index 00000000..e957b0a7 --- /dev/null +++ b/tests/auto/blackbox/testdata/assembly/testd_x86_64.asm @@ -0,0 +1,8 @@ +.code +foo PROC + nop + RET +foo ENDP + +END + diff --git a/tests/auto/blackbox/testdata/autotest-timeout/autotests-timeout.qbs b/tests/auto/blackbox/testdata/autotest-timeout/autotests-timeout.qbs new file mode 100644 index 00000000..8ae4f3ce --- /dev/null +++ b/tests/auto/blackbox/testdata/autotest-timeout/autotests-timeout.qbs @@ -0,0 +1,26 @@ +Project { + CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: "testApp" + type: ["application", "autotest"] + Depends { name: "autotest" } + cpp.cxxLanguageVersion: "c++11" + cpp.minimumOsxVersion: "10.8" // For + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.driverFlags: "-pthread" + } + files: "test-main.cpp" + } + AutotestRunner { + Depends { + name: "cpp" // Make sure build environment is set up properly. + condition: qbs.hostOS.contains("windows") && qbs.toolchain.contains("gcc") + } + } +} diff --git a/tests/auto/blackbox/testdata/autotest-timeout/test-main.cpp b/tests/auto/blackbox/testdata/autotest-timeout/test-main.cpp new file mode 100644 index 00000000..4bb1ca27 --- /dev/null +++ b/tests/auto/blackbox/testdata/autotest-timeout/test-main.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Jochen Ulrich +** 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 +#include + +int main() +{ + std::this_thread::sleep_for(std::chrono::seconds(5)); + return 0; +} + diff --git a/tests/auto/blackbox/testdata/autotest-with-dependencies/autotest-with-dependencies.qbs b/tests/auto/blackbox/testdata/autotest-with-dependencies/autotest-with-dependencies.qbs new file mode 100644 index 00000000..b6447352 --- /dev/null +++ b/tests/auto/blackbox/testdata/autotest-with-dependencies/autotest-with-dependencies.qbs @@ -0,0 +1,37 @@ +import qbs.FileInfo + +Project { + CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: "helper-app" + type: ["application", "test-helper"] + consoleApplication: true + install: true + files: "helper-main.cpp" + cpp.executableSuffix: ".exe" + Group { + fileTagsFilter: "application" + fileTags: "test-helper" + } + } + CppApplication { + name: "test-app" + type: ["application", "autotest"] + Depends { name: "autotest" } + files: "test-main.cpp" + } + + AutotestRunner { + Depends { + name: "cpp" // Make sure build environment is set up properly. + condition: qbs.hostOS.contains("windows") && qbs.toolchain.contains("gcc") + } + arguments: FileInfo.joinPaths(qbs.installRoot, qbs.installPrefix, "bin") + auxiliaryInputs: "test-helper" + } +} diff --git a/tests/auto/blackbox/testdata/autotest-with-dependencies/helper-main.cpp b/tests/auto/blackbox/testdata/autotest-with-dependencies/helper-main.cpp new file mode 100644 index 00000000..b5a07ecd --- /dev/null +++ b/tests/auto/blackbox/testdata/autotest-with-dependencies/helper-main.cpp @@ -0,0 +1,7 @@ +#include + +int main() +{ + std::cout << "i am the helper" << std::endl; + return 0; +} diff --git a/tests/auto/blackbox/testdata/autotest-with-dependencies/test-main.cpp b/tests/auto/blackbox/testdata/autotest-with-dependencies/test-main.cpp new file mode 100644 index 00000000..382049ea --- /dev/null +++ b/tests/auto/blackbox/testdata/autotest-with-dependencies/test-main.cpp @@ -0,0 +1,10 @@ +#include +#include +#include + +int main(int argc, char *argv[]) +{ + std::cout << "i am the test app" << std::endl; + const std::string fullHelperExe = std::string(argv[1]) + "/helper-app.exe"; + return std::system(fullHelperExe.c_str()) == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/auto/blackbox/testdata/autotests/autotests.qbs b/tests/auto/blackbox/testdata/autotests/autotests.qbs new file mode 100644 index 00000000..4927a886 --- /dev/null +++ b/tests/auto/blackbox/testdata/autotests/autotests.qbs @@ -0,0 +1,15 @@ +Project { + references: ["test1", "test2", "test3"] + AutotestRunner { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + Depends { + name: "cpp" // Make sure build environment is set up properly. + condition: qbs.hostOS.contains("windows") && qbs.toolchain.contains("gcc") + } + } +} diff --git a/tests/auto/blackbox/testdata/autotests/test1/test1.cpp b/tests/auto/blackbox/testdata/autotests/test1/test1.cpp new file mode 100644 index 00000000..08d9d478 --- /dev/null +++ b/tests/auto/blackbox/testdata/autotests/test1/test1.cpp @@ -0,0 +1,12 @@ +#include +#include + +int main(int argc, char *[]) +{ + if (argc != 2) { + std::cerr << "This test needs exactly one argument" << std::endl; + std::cerr << "FAIL" << std::endl; + return EXIT_FAILURE; + } + std::cout << "PASS" << std::endl; +} diff --git a/tests/auto/blackbox/testdata/autotests/test1/test1.qbs b/tests/auto/blackbox/testdata/autotests/test1/test1.qbs new file mode 100644 index 00000000..8b078a75 --- /dev/null +++ b/tests/auto/blackbox/testdata/autotests/test1/test1.qbs @@ -0,0 +1,9 @@ +CppApplication { + name: "test1" + type: base.concat("autotest") + + Depends { name: "autotest" } + autotest.arguments: "--dummy" + + files: "test1.cpp" +} diff --git a/tests/auto/blackbox/testdata/autotests/test2/test2-resource.txt b/tests/auto/blackbox/testdata/autotests/test2/test2-resource.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/autotests/test2/test2.cpp b/tests/auto/blackbox/testdata/autotests/test2/test2.cpp new file mode 100644 index 00000000..dcc3d270 --- /dev/null +++ b/tests/auto/blackbox/testdata/autotests/test2/test2.cpp @@ -0,0 +1,13 @@ +#include +#include +#include + +int main() +{ + std::ifstream input("test2-resource.txt"); + if (!input.is_open()) { + std::cerr << "Test resource not found"; + return EXIT_FAILURE; + } + std::cout << "PASS" << std::endl; +} diff --git a/tests/auto/blackbox/testdata/autotests/test2/test2.qbs b/tests/auto/blackbox/testdata/autotests/test2/test2.qbs new file mode 100644 index 00000000..2ce6ea5f --- /dev/null +++ b/tests/auto/blackbox/testdata/autotests/test2/test2.qbs @@ -0,0 +1,9 @@ +CppApplication { + name: "test2" + type: base.concat("autotest") + + Depends { name: "autotest" } + autotest.workingDir: sourceDirectory + + files: "test2.cpp" +} diff --git a/tests/auto/blackbox/testdata/autotests/test3/test3.cpp b/tests/auto/blackbox/testdata/autotests/test3/test3.cpp new file mode 100644 index 00000000..2ce7e2bf --- /dev/null +++ b/tests/auto/blackbox/testdata/autotests/test3/test3.cpp @@ -0,0 +1,9 @@ +#include +#include + +int main() +{ + std::cerr << "I am an awful test"; + std::cerr << "FAIL" << std::endl; + return EXIT_FAILURE; +} diff --git a/tests/auto/blackbox/testdata/autotests/test3/test3.qbs b/tests/auto/blackbox/testdata/autotests/test3/test3.qbs new file mode 100644 index 00000000..34550dee --- /dev/null +++ b/tests/auto/blackbox/testdata/autotests/test3/test3.qbs @@ -0,0 +1,9 @@ +CppApplication { + name: "test3" + type: base.concat("autotest") + + Depends { name: "autotest" } + autotest.allowFailure: true + + files: "test3.cpp" +} diff --git a/tests/auto/blackbox/testdata/aux-inputs-from-deps/aux-inputs-from-deps.qbs b/tests/auto/blackbox/testdata/aux-inputs-from-deps/aux-inputs-from-deps.qbs new file mode 100644 index 00000000..6a9e9823 --- /dev/null +++ b/tests/auto/blackbox/testdata/aux-inputs-from-deps/aux-inputs-from-deps.qbs @@ -0,0 +1,79 @@ +import qbs.File +import qbs.TextFile + +import "util.js" as Utils + +Project { + CppApplication { + name: "app" + files: ["main.cpp"] + Depends { name: "dep" } + } + Product { + name: "p" + type: ["p.out"] + Depends { name: "dep" } + Rule { + multiplex: true + explicitlyDependsOnFromDependencies: ["hpp"] + Artifact { + filePath: "dummy.out" + fileTags: ["p.out"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating dummy.out"; + cmd.sourceCode = function() { + File.copy(project.buildDirectory + "/dummy.h", output.filePath); + }; + return [cmd]; + } + } + } + Product { + name: "dep" + type: ["hpp"] + property bool sleep: true + Rule { + inputs: ["blubb.in"] + Artifact { + filePath: project.buildDirectory + "/dummy.h" + fileTags: ["hpp"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating header"; + cmd.sourceCode = function() { + if (product.sleep) + Utils.sleep(1000); + File.copy(input.filePath, output.filePath); + } + return [cmd]; + } + } + Rule { + multiplex: true + Artifact { + filePath: "dummy.blubb" + fileTags: ["blubb.in"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating blubb.in"; + cmd.sourceCode = function() { + if (product.sleep) + Utils.sleep(1000); + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + } + return [cmd]; + } + } + Export { + Depends { name: "cpp" } + cpp.includePaths: [project.buildDirectory] + } + } +} + + diff --git a/tests/auto/blackbox/testdata/aux-inputs-from-deps/main.cpp b/tests/auto/blackbox/testdata/aux-inputs-from-deps/main.cpp new file mode 100644 index 00000000..d1640462 --- /dev/null +++ b/tests/auto/blackbox/testdata/aux-inputs-from-deps/main.cpp @@ -0,0 +1,3 @@ +#include + +int main() { } diff --git a/tests/auto/blackbox/testdata/aux-inputs-from-deps/util.js b/tests/auto/blackbox/testdata/aux-inputs-from-deps/util.js new file mode 100644 index 00000000..a37a8cbb --- /dev/null +++ b/tests/auto/blackbox/testdata/aux-inputs-from-deps/util.js @@ -0,0 +1,8 @@ +function sleep(timeInMs) +{ + var referenceTime = new Date(); + var time = null; + do { + time = new Date(); + } while (time - referenceTime < timeInMs); +} diff --git a/tests/auto/blackbox/testdata/badInterpreter/badInterpreter.qbs b/tests/auto/blackbox/testdata/badInterpreter/badInterpreter.qbs new file mode 100644 index 00000000..2317e6ed --- /dev/null +++ b/tests/auto/blackbox/testdata/badInterpreter/badInterpreter.qbs @@ -0,0 +1,54 @@ +Project { + property bool enabled: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + + qbsSearchPaths: base.concat(["qbs"]) + + Product { + Depends { name: "script-test" } + name: "script-ok" + type: ["application"] + + Group { + files: [product.name] + fileTags: ["script"] + } + } + + Product { + Depends { name: "script-test" } + name: "script-noexec" + type: ["application"] + + Group { + files: [product.name] + fileTags: ["script"] + } + } + + Product { + Depends { name: "script-test" } + name: "script-interp-missing" + type: ["application"] + + Group { + files: [product.name] + fileTags: ["script"] + } + } + + Product { + Depends { name: "script-test" } + name: "script-interp-noexec" + type: ["application"] + + Group { + files: [product.name] + fileTags: ["script"] + } + } +} diff --git a/tests/auto/blackbox/testdata/badInterpreter/qbs/modules/script-test/script-test.qbs b/tests/auto/blackbox/testdata/badInterpreter/qbs/modules/script-test/script-test.qbs new file mode 100644 index 00000000..4d043ee5 --- /dev/null +++ b/tests/auto/blackbox/testdata/badInterpreter/qbs/modules/script-test/script-test.qbs @@ -0,0 +1,38 @@ +import qbs.FileInfo +import qbs.TextFile + +Module { + name: "script-test" + + Rule { + inputs: ["script"] + + Artifact { + filePath: FileInfo.joinPaths(project.buildDirectory, input.fileName) + fileTags: ["application"] + } + + prepare: { + var cmds = []; + var cmd = new JavaScriptCommand(); + cmd.description = "copying " + input.fileName; + cmd.sourceCode = function() { + var tf = new TextFile(input.filePath, TextFile.ReadOnly); + var s = tf.readAll().replace("$PWD", project.buildDirectory); + tf.close(); + var tf2 = new TextFile(output.filePath, TextFile.ReadWrite); + tf2.write(s); + tf2.close(); + }; + cmds.push(cmd); + + if (output.fileName !== "script-noexec") { + var cmd2 = new Command("chmod", ["+x", output.filePath]); + cmd2.silent = true; + cmds.push(cmd2); + } + + return cmds; + } + } +} diff --git a/tests/auto/blackbox/testdata/badInterpreter/script-interp-missing b/tests/auto/blackbox/testdata/badInterpreter/script-interp-missing new file mode 100755 index 00000000..b92a0e9d --- /dev/null +++ b/tests/auto/blackbox/testdata/badInterpreter/script-interp-missing @@ -0,0 +1 @@ +#!/does/not/exist diff --git a/tests/auto/blackbox/testdata/badInterpreter/script-interp-noexec b/tests/auto/blackbox/testdata/badInterpreter/script-interp-noexec new file mode 100755 index 00000000..8a336b98 --- /dev/null +++ b/tests/auto/blackbox/testdata/badInterpreter/script-interp-noexec @@ -0,0 +1 @@ +#!$PWD/script-noexec diff --git a/tests/auto/blackbox/testdata/badInterpreter/script-noexec b/tests/auto/blackbox/testdata/badInterpreter/script-noexec new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/badInterpreter/script-ok b/tests/auto/blackbox/testdata/badInterpreter/script-ok new file mode 100755 index 00000000..1a248525 --- /dev/null +++ b/tests/auto/blackbox/testdata/badInterpreter/script-ok @@ -0,0 +1 @@ +#!/bin/sh diff --git a/tests/auto/blackbox/testdata/bom-sources/bom-sources.qbs b/tests/auto/blackbox/testdata/bom-sources/bom-sources.qbs new file mode 100644 index 00000000..4446355b --- /dev/null +++ b/tests/auto/blackbox/testdata/bom-sources/bom-sources.qbs @@ -0,0 +1,4 @@ +CppApplication { + name: "app" + files: ["main.cpp", "theheader.h"] +} diff --git a/tests/auto/blackbox/testdata/bom-sources/main.cpp b/tests/auto/blackbox/testdata/bom-sources/main.cpp new file mode 100644 index 00000000..de10a65a --- /dev/null +++ b/tests/auto/blackbox/testdata/bom-sources/main.cpp @@ -0,0 +1,3 @@ +#include "theheader.h" + +int main() {} diff --git a/tests/auto/blackbox/testdata/bom-sources/theheader.h b/tests/auto/blackbox/testdata/bom-sources/theheader.h new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/build-data-of-disabled-product/build-data-of-disabled-product.qbs b/tests/auto/blackbox/testdata/build-data-of-disabled-product/build-data-of-disabled-product.qbs new file mode 100644 index 00000000..d71facf1 --- /dev/null +++ b/tests/auto/blackbox/testdata/build-data-of-disabled-product/build-data-of-disabled-product.qbs @@ -0,0 +1,4 @@ +CppApplication { + name: "app" + files: ["main.cpp", "test.cpp"] +} diff --git a/tests/auto/blackbox/testdata/build-data-of-disabled-product/main.cpp b/tests/auto/blackbox/testdata/build-data-of-disabled-product/main.cpp new file mode 100644 index 00000000..6a0bac9f --- /dev/null +++ b/tests/auto/blackbox/testdata/build-data-of-disabled-product/main.cpp @@ -0,0 +1,6 @@ +void f(); + +int main() +{ + f(); +} diff --git a/tests/auto/blackbox/testdata/build-data-of-disabled-product/test.cpp b/tests/auto/blackbox/testdata/build-data-of-disabled-product/test.cpp new file mode 100644 index 00000000..56757a70 --- /dev/null +++ b/tests/auto/blackbox/testdata/build-data-of-disabled-product/test.cpp @@ -0,0 +1 @@ +void f() {} diff --git a/tests/auto/blackbox/testdata/build-directories/build-directories.qbs b/tests/auto/blackbox/testdata/build-directories/build-directories.qbs new file mode 100644 index 00000000..be7672ec --- /dev/null +++ b/tests/auto/blackbox/testdata/build-directories/build-directories.qbs @@ -0,0 +1,37 @@ +Project { + Product { + name: "p1" + type: "blubb1" + Rule { + multiplex: true + outputFileTags: "blubb1" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + console.info(product.buildDirectory); + } + return cmd; + } + } + } + Product { + name: "p2" + type: "blubb2" + Depends { name: "p1" } + Rule { + inputsFromDependencies: "blubb1" + outputFileTags: "blubb2" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + console.info(product.buildDirectory); + console.info(project.buildDirectory); + console.info(project.sourceDirectory); + } + return cmd; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/build-graph-versions/build-graph-versions.qbs b/tests/auto/blackbox/testdata/build-graph-versions/build-graph-versions.qbs new file mode 100644 index 00000000..da753631 --- /dev/null +++ b/tests/auto/blackbox/testdata/build-graph-versions/build-graph-versions.qbs @@ -0,0 +1,3 @@ +CppApplication { + files: ["main.cpp"] +} diff --git a/tests/auto/blackbox/testdata/build-graph-versions/main.cpp b/tests/auto/blackbox/testdata/build-graph-versions/main.cpp new file mode 100644 index 00000000..8b8d58de --- /dev/null +++ b/tests/auto/blackbox/testdata/build-graph-versions/main.cpp @@ -0,0 +1 @@ +int main() { } diff --git a/tests/auto/blackbox/testdata/build-variant-defaults/build-variant-defaults.qbs b/tests/auto/blackbox/testdata/build-variant-defaults/build-variant-defaults.qbs new file mode 100644 index 00000000..4015817c --- /dev/null +++ b/tests/auto/blackbox/testdata/build-variant-defaults/build-variant-defaults.qbs @@ -0,0 +1,16 @@ +CppApplication { + property bool validate: { + var valid = true; + if (qbs.buildVariant === "release") { + valid = !qbs.enableDebugCode && !qbs.debugInformation && qbs.optimization === "fast"; + } else if (qbs.buildVariant === "debug") { + valid = qbs.enableDebugCode && qbs.debugInformation && qbs.optimization === "none"; + } else if (qbs.buildVariant === "profiling") { + valid = !qbs.enableDebugCode && qbs.debugInformation && qbs.optimization === "fast"; + } + + if (!valid) + throw "Invalid defaults"; + return valid; + } +} diff --git a/tests/auto/blackbox/testdata/build-variant-defaults/main.cpp b/tests/auto/blackbox/testdata/build-variant-defaults/main.cpp new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/blackbox/testdata/build-variant-defaults/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/buildenv-change/buildenv-change.qbs b/tests/auto/blackbox/testdata/buildenv-change/buildenv-change.qbs new file mode 100644 index 00000000..6c0bcc73 --- /dev/null +++ b/tests/auto/blackbox/testdata/buildenv-change/buildenv-change.qbs @@ -0,0 +1,16 @@ +CppApplication { + Probe { + id: dummy + property stringList toolchain: qbs.toolchain + configure: { + if (toolchain.contains("msvc")) + console.info("msvc"); + } + } + files: [ + "file.c", + "main.cpp", + "subdir/theheader.h", + "subdir2/theheader.h", + ] +} diff --git a/tests/auto/blackbox/testdata/buildenv-change/file.c b/tests/auto/blackbox/testdata/buildenv-change/file.c new file mode 100644 index 00000000..4b74880f --- /dev/null +++ b/tests/auto/blackbox/testdata/buildenv-change/file.c @@ -0,0 +1 @@ +void func(void) { } diff --git a/tests/auto/blackbox/testdata/buildenv-change/main.cpp b/tests/auto/blackbox/testdata/buildenv-change/main.cpp new file mode 100644 index 00000000..b98fc3c2 --- /dev/null +++ b/tests/auto/blackbox/testdata/buildenv-change/main.cpp @@ -0,0 +1,3 @@ +#include + +int main() {} diff --git a/tests/auto/blackbox/testdata/buildenv-change/subdir/theheader.h b/tests/auto/blackbox/testdata/buildenv-change/subdir/theheader.h new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/buildenv-change/subdir2/theheader.h b/tests/auto/blackbox/testdata/buildenv-change/subdir2/theheader.h new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/capnproto/bar.capnp b/tests/auto/blackbox/testdata/capnproto/bar.capnp new file mode 100644 index 00000000..a0e8a0f8 --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/bar.capnp @@ -0,0 +1,8 @@ +@0xc967c84bcca70a1d; + +using Foo = import "foo.capnp"; + +struct Bar { + foo @0 :Foo.Foo; + # Use type "Foo" defined in foo.capnp. +} diff --git a/tests/auto/blackbox/testdata/capnproto/baz.capnp b/tests/auto/blackbox/testdata/capnproto/baz.capnp new file mode 100644 index 00000000..8b2fe4fa --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/baz.capnp @@ -0,0 +1,8 @@ +@0xc967c84bcca70a1d; + +using Foo = import "/imports/foo.capnp"; + +struct Baz { + foo @0 :Foo.Foo; + # Use type "Foo" defined in foo.capnp. +} diff --git a/tests/auto/blackbox/testdata/capnproto/capnproto_absolute_import.cpp b/tests/auto/blackbox/testdata/capnproto/capnproto_absolute_import.cpp new file mode 100644 index 00000000..0e8979ee --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/capnproto_absolute_import.cpp @@ -0,0 +1,14 @@ +#include "baz.capnp.h" + +#include + +int main() +{ + ::capnp::MallocMessageBuilder message; + + auto baz = message.initRoot(); + auto foo = baz.initFoo(); + foo.setStr("hello"); + + return 0; +} diff --git a/tests/auto/blackbox/testdata/capnproto/capnproto_absolute_import.qbs b/tests/auto/blackbox/testdata/capnproto/capnproto_absolute_import.qbs new file mode 100644 index 00000000..ee0903f7 --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/capnproto_absolute_import.qbs @@ -0,0 +1,18 @@ +CppApplication { + Depends { name: "capnproto.cpp"; required: false } + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + if (!capnproto.cpp.present) + console.info("capnproto is not present"); + return result && capnproto.cpp.present; + } + cpp.minimumMacosVersion: "10.8" + capnproto.cpp.importPaths: "." + files: [ + "baz.capnp", + "capnproto_absolute_import.cpp", + "imports/foo.capnp", + ] +} diff --git a/tests/auto/blackbox/testdata/capnproto/capnproto_cpp.cpp b/tests/auto/blackbox/testdata/capnproto/capnproto_cpp.cpp new file mode 100644 index 00000000..b9f72995 --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/capnproto_cpp.cpp @@ -0,0 +1,13 @@ +#include "foo.capnp.h" + +#include + +int main() +{ + ::capnp::MallocMessageBuilder message; + + auto foo = message.initRoot(); + foo.setStr("hello"); + + return 0; +} diff --git a/tests/auto/blackbox/testdata/capnproto/capnproto_cpp.qbs b/tests/auto/blackbox/testdata/capnproto/capnproto_cpp.qbs new file mode 100644 index 00000000..d7ee1b4c --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/capnproto_cpp.qbs @@ -0,0 +1,16 @@ +CppApplication { + Depends { name: "capnproto.cpp"; required: false } + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + if (!capnproto.cpp.present) + console.info("capnproto is not present"); + return result && capnproto.cpp.present; + } + cpp.minimumMacosVersion: "10.8" + files: [ + "capnproto_cpp.cpp", + "foo.capnp" + ] +} diff --git a/tests/auto/blackbox/testdata/capnproto/capnproto_relative_import.cpp b/tests/auto/blackbox/testdata/capnproto/capnproto_relative_import.cpp new file mode 100644 index 00000000..5116bd3d --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/capnproto_relative_import.cpp @@ -0,0 +1,14 @@ +#include "bar.capnp.h" + +#include + +int main() +{ + ::capnp::MallocMessageBuilder message; + + auto bar = message.initRoot(); + auto foo = bar.initFoo(); + foo.setStr("hello"); + + return 0; +} diff --git a/tests/auto/blackbox/testdata/capnproto/capnproto_relative_import.qbs b/tests/auto/blackbox/testdata/capnproto/capnproto_relative_import.qbs new file mode 100644 index 00000000..7c1991d8 --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/capnproto_relative_import.qbs @@ -0,0 +1,17 @@ +CppApplication { + Depends { name: "capnproto.cpp"; required: false } + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + if (!capnproto.cpp.present) + console.info("capnproto is not present"); + return result && capnproto.cpp.present; + } + cpp.minimumMacosVersion: "10.8" + files: [ + "bar.capnp", + "capnproto_relative_import.cpp", + "foo.capnp", + ] +} diff --git a/tests/auto/blackbox/testdata/capnproto/foo.capnp b/tests/auto/blackbox/testdata/capnproto/foo.capnp new file mode 100644 index 00000000..146a2969 --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/foo.capnp @@ -0,0 +1,6 @@ +@0x8a2efe67220790be; + +struct Foo { + num @0 :UInt32; + str @1 :Text; +} diff --git a/tests/auto/blackbox/testdata/capnproto/greeter-client.cpp b/tests/auto/blackbox/testdata/capnproto/greeter-client.cpp new file mode 100644 index 00000000..d3fcdb4e --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/greeter-client.cpp @@ -0,0 +1,25 @@ +#include "greeter.capnp.h" + +#include + +#include + +int main(int argc, char *argv[]) +{ + const char address[] = "localhost:5050"; + capnp::EzRpcClient client(address); + Greeter::Client greeter = client.getMain(); + + auto& waitScope = client.getWaitScope(); + + for (int i = 0; i < 2; ++i) { + auto request = greeter.sayHelloRequest(); + request.initRequest().setName("hello workd"); + auto promise = request.send(); + + auto response = promise.wait(waitScope); + std::cout << response.getResponse().getName().cStr() << std::endl; + } + + return 0; +} diff --git a/tests/auto/blackbox/testdata/capnproto/greeter-server.cpp b/tests/auto/blackbox/testdata/capnproto/greeter-server.cpp new file mode 100644 index 00000000..a7f482cc --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/greeter-server.cpp @@ -0,0 +1,27 @@ +#include "greeter.capnp.h" + +#include +#include + +#include + +class GreeterImpl final: public Greeter::Server +{ +public: + ::kj::Promise sayHello(SayHelloContext context) override + { + auto response = context.getResults().initResponse(); + response.setName(context.getParams().getRequest().getName()); + return kj::READY_NOW; + }; +}; + +int main(int argc, char *argv[]) +{ + const char address[] = "localhost:5050"; + capnp::EzRpcServer server(kj::heap(), address); + + auto& waitScope = server.getWaitScope(); + // Run forever, accepting connections and handling requests. + kj::NEVER_DONE.wait(waitScope); +} diff --git a/tests/auto/blackbox/testdata/capnproto/greeter.capnp b/tests/auto/blackbox/testdata/capnproto/greeter.capnp new file mode 100644 index 00000000..b9188f63 --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/greeter.capnp @@ -0,0 +1,13 @@ +@0x85150b117366d14b; + +struct HelloRequest { + name @0 :Text; +} + +struct HelloResponse { + name @0 :Text; +} + +interface Greeter { + sayHello @0 (request: HelloRequest) -> (response: HelloResponse); +} diff --git a/tests/auto/blackbox/testdata/capnproto/greeter_cpp.qbs b/tests/auto/blackbox/testdata/capnproto/greeter_cpp.qbs new file mode 100644 index 00000000..cf95b968 --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/greeter_cpp.qbs @@ -0,0 +1,32 @@ +Project { + CppApplication { + Depends { name: "capnproto.cpp"; required: false } + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + if (!capnproto.cpp.present) + console.info("capnproto is not present"); + return result && capnproto.cpp.present; + } + name: "server" + consoleApplication: true + cpp.minimumMacosVersion: "10.8" + capnproto.cpp.useRpc: true + files: [ + "greeter.capnp", + "greeter-server.cpp" + ] + } + CppApplication { + Depends { name: "capnproto.cpp"; required: false } + name: "client" + consoleApplication: true + capnproto.cpp.useRpc: true + cpp.minimumMacosVersion: "10.8" + files: [ + "greeter.capnp", + "greeter-client.cpp" + ] + } +} diff --git a/tests/auto/blackbox/testdata/capnproto/imports/foo.capnp b/tests/auto/blackbox/testdata/capnproto/imports/foo.capnp new file mode 100644 index 00000000..146a2969 --- /dev/null +++ b/tests/auto/blackbox/testdata/capnproto/imports/foo.capnp @@ -0,0 +1,6 @@ +@0x8a2efe67220790be; + +struct Foo { + num @0 :UInt32; + str @1 :Text; +} diff --git a/tests/auto/blackbox/testdata/change-in-disabled-product/change-in-disabled-product.qbs b/tests/auto/blackbox/testdata/change-in-disabled-product/change-in-disabled-product.qbs new file mode 100644 index 00000000..2f0dfc08 --- /dev/null +++ b/tests/auto/blackbox/testdata/change-in-disabled-product/change-in-disabled-product.qbs @@ -0,0 +1,7 @@ +Product { + condition: false + files: [ + 'test1.txt', + // 'test2.txt' + ] +} diff --git a/tests/auto/blackbox/testdata/change-in-disabled-product/test1.txt b/tests/auto/blackbox/testdata/change-in-disabled-product/test1.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/change-in-disabled-product/test2.txt b/tests/auto/blackbox/testdata/change-in-disabled-product/test2.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/change-in-imported-file/change-in-imported-file.qbs b/tests/auto/blackbox/testdata/change-in-imported-file/change-in-imported-file.qbs new file mode 100644 index 00000000..cf535426 --- /dev/null +++ b/tests/auto/blackbox/testdata/change-in-imported-file/change-in-imported-file.qbs @@ -0,0 +1,22 @@ +import "prepare.js" as PrepareHelper + +Product { + type: ["output"] + Group { + files: ["test.txt"] + fileTags: ["input"] + } + Rule { + inputs: ["input"] + Artifact { + filePath: "dummy" + fileTags: product.type + } + prepare: { + var cmd = new JavaScriptCommand(); + PrepareHelper.prepare(cmd); + cmd.description = "Creating output"; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/change-in-imported-file/prepare.js b/tests/auto/blackbox/testdata/change-in-imported-file/prepare.js new file mode 100644 index 00000000..fa73f0ff --- /dev/null +++ b/tests/auto/blackbox/testdata/change-in-imported-file/prepare.js @@ -0,0 +1,3 @@ +function prepare(cmd) { + cmd.sourceCode = function() { console.info("old output"); }; +} diff --git a/tests/auto/blackbox/testdata/change-in-imported-file/test.txt b/tests/auto/blackbox/testdata/change-in-imported-file/test.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/change-tracking-and-multiplexing/change-tracking-and-multiplexing.qbs b/tests/auto/blackbox/testdata/change-tracking-and-multiplexing/change-tracking-and-multiplexing.qbs new file mode 100644 index 00000000..d1215355 --- /dev/null +++ b/tests/auto/blackbox/testdata/change-tracking-and-multiplexing/change-tracking-and-multiplexing.qbs @@ -0,0 +1,11 @@ +StaticLibrary { + name: "l" + + Depends { condition: qbs.targetOS.contains("darwin"); name: "bundle" } + Properties { condition: qbs.targetOS.contains("darwin"); bundle.isBundle: false } + + multiplexByQbsProperties: ["buildVariants"] + qbs.buildVariants: ["debug", "release"] + Depends { name: "cpp" } + files: ["lib.cpp"] +} diff --git a/tests/auto/blackbox/testdata/change-tracking-and-multiplexing/lib.cpp b/tests/auto/blackbox/testdata/change-tracking-and-multiplexing/lib.cpp new file mode 100644 index 00000000..9a614565 --- /dev/null +++ b/tests/auto/blackbox/testdata/change-tracking-and-multiplexing/lib.cpp @@ -0,0 +1 @@ +void l() {} diff --git a/tests/auto/blackbox/testdata/changed-files/changed-files.qbs b/tests/auto/blackbox/testdata/changed-files/changed-files.qbs new file mode 100644 index 00000000..e5790d2e --- /dev/null +++ b/tests/auto/blackbox/testdata/changed-files/changed-files.qbs @@ -0,0 +1,29 @@ +import qbs.TextFile + +CppApplication { + type: ["application", "stuff"] + consoleApplication: true + files: ["file1.cpp", "file2.cpp", "main.cpp"] + + Rule { + inputs: ["cpp"] + outputFileTags: ["stuff"] + outputArtifacts: { + return [{ + filePath: input.completeBaseName + ".stuff", + fileTags: ["stuff"] + }]; + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.write("crazy stuff"); + f.close(); + } + return cmd; + } + } +} + diff --git a/tests/auto/blackbox/testdata/changed-files/file1.cpp b/tests/auto/blackbox/testdata/changed-files/file1.cpp new file mode 100644 index 00000000..51bcc546 --- /dev/null +++ b/tests/auto/blackbox/testdata/changed-files/file1.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void f1() {} diff --git a/tests/auto/blackbox/testdata/changed-files/file2.cpp b/tests/auto/blackbox/testdata/changed-files/file2.cpp new file mode 100644 index 00000000..e6d0ae53 --- /dev/null +++ b/tests/auto/blackbox/testdata/changed-files/file2.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void f2() {} diff --git a/tests/auto/blackbox/testdata/changed-files/main.cpp b/tests/auto/blackbox/testdata/changed-files/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/blackbox/testdata/changed-files/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/blackbox/testdata/changed-inputs-from-dependencies/changed-inputs-from-dependencies.qbs b/tests/auto/blackbox/testdata/changed-inputs-from-dependencies/changed-inputs-from-dependencies.qbs new file mode 100644 index 00000000..fb92d183 --- /dev/null +++ b/tests/auto/blackbox/testdata/changed-inputs-from-dependencies/changed-inputs-from-dependencies.qbs @@ -0,0 +1,58 @@ +import qbs.File +import qbs.TextFile + +Project { + Product { + name: "dep" + type: "dep_tag" + + files: "input.txt" + + FileTagger { patterns: "*.txt"; fileTags: "inp_tag" } + + Rule { + inputs: "inp_tag" + Artifact { filePath: input.baseName + ".intermediate"; fileTags: "int_tag" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); } + return cmd; + } + } + Rule { + inputs: "int_tag" + Artifact { filePath: input.baseName + ".dep"; fileTags: "dep_tag" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); } + return cmd; + } + } + } + Product { + name: "p" + type: "p_tag" + + Depends { name: "dep" } + + Rule { + inputsFromDependencies: "dep_tag" + outputFileTags: "p_tag" + outputArtifacts: { + var dummy = new TextFile(input.filePath, TextFile.ReadOnly); + dummy.close(); + return [{ filePath: input.baseName + ".p", fileTags: "p_tag" }] + } + + prepare: { + console.info("running final prepare script"); + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); } + return cmd; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/changed-inputs-from-dependencies/input.txt b/tests/auto/blackbox/testdata/changed-inputs-from-dependencies/input.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/changed-rule-inputs/changed-rule-inputs.qbs b/tests/auto/blackbox/testdata/changed-rule-inputs/changed-rule-inputs.qbs new file mode 100644 index 00000000..d11613c4 --- /dev/null +++ b/tests/auto/blackbox/testdata/changed-rule-inputs/changed-rule-inputs.qbs @@ -0,0 +1,40 @@ +Project { + Product { + name: "p1" + type: "p1" + Rule { + alwaysRun: true + multiplex: true + Artifact { + fileTags: "p1" + filePath: "p1-dummy" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() {}; + return cmd; + } + } + } + Product { + name: "p2" + type: "p2" + Depends { name: "p1" } + Rule { + requiresInputs: false + multiplex: true + inputsFromDependencies: "p1" + Artifact { + fileTags: "p2" + filePath: "p2-dummy" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() {}; + return cmd; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/check-timestamps/check-timestamps.qbs b/tests/auto/blackbox/testdata/check-timestamps/check-timestamps.qbs new file mode 100644 index 00000000..65c29ca7 --- /dev/null +++ b/tests/auto/blackbox/testdata/check-timestamps/check-timestamps.qbs @@ -0,0 +1,8 @@ +CppApplication { + name: "app" + files: [ + "file.cpp", + "file.h", + "main.cpp", + ] +} diff --git a/tests/auto/blackbox/testdata/check-timestamps/file.cpp b/tests/auto/blackbox/testdata/check-timestamps/file.cpp new file mode 100644 index 00000000..4752b89d --- /dev/null +++ b/tests/auto/blackbox/testdata/check-timestamps/file.cpp @@ -0,0 +1,3 @@ +#include "file.h" + +void f() { } diff --git a/tests/auto/blackbox/testdata/check-timestamps/file.h b/tests/auto/blackbox/testdata/check-timestamps/file.h new file mode 100644 index 00000000..789447c0 --- /dev/null +++ b/tests/auto/blackbox/testdata/check-timestamps/file.h @@ -0,0 +1 @@ +void f(); diff --git a/tests/auto/blackbox/testdata/check-timestamps/main.cpp b/tests/auto/blackbox/testdata/check-timestamps/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/check-timestamps/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/choose-module-instance/choose-module-instance.qbs b/tests/auto/blackbox/testdata/choose-module-instance/choose-module-instance.qbs new file mode 100644 index 00000000..5f414862 --- /dev/null +++ b/tests/auto/blackbox/testdata/choose-module-instance/choose-module-instance.qbs @@ -0,0 +1,17 @@ +import qbs.FileInfo + +Project { + qbsSearchPaths: [ + ".", path, "modules/..", FileInfo.path(FileInfo.joinPaths(path, "modules")), + "other-searchpath" + ] + Product { + Depends { name: "limerick" } + type: ["text"] + files: ["gerbil.txt.in"] + Group { + fileTagsFilter: product.type + qbs.install: true + } + } +} diff --git a/tests/auto/blackbox/testdata/choose-module-instance/gerbil.txt.in b/tests/auto/blackbox/testdata/choose-module-instance/gerbil.txt.in new file mode 100644 index 00000000..4722829a --- /dev/null +++ b/tests/auto/blackbox/testdata/choose-module-instance/gerbil.txt.in @@ -0,0 +1,5 @@ +I once had a gerbil named Bobby, +Who had an unusual hobby. +He ${DID} on a ${THING}, +and now -- oh my ${IDOL}, +now all that's left is a blobby. diff --git a/tests/auto/blackbox/testdata/choose-module-instance/modules/limerick/lord.qbs b/tests/auto/blackbox/testdata/choose-module-instance/modules/limerick/lord.qbs new file mode 100644 index 00000000..9a5071eb --- /dev/null +++ b/tests/auto/blackbox/testdata/choose-module-instance/modules/limerick/lord.qbs @@ -0,0 +1,5 @@ +Module { + condition: qbs.targetOS.containsAny(["Deep Purple", "Whitesnake"]) + Depends { name: "texttemplate" } + texttemplate.dict: ({DID: "chewed", THING: "cord", IDOL: "lord"}) +} diff --git a/tests/auto/blackbox/testdata/choose-module-instance/modules/limerick/ritchie.qbs b/tests/auto/blackbox/testdata/choose-module-instance/modules/limerick/ritchie.qbs new file mode 100644 index 00000000..1e3c02d5 --- /dev/null +++ b/tests/auto/blackbox/testdata/choose-module-instance/modules/limerick/ritchie.qbs @@ -0,0 +1,7 @@ +Module { + condition: qbs.targetOS.containsAny(["Deep Purple", "Rainbow"]) + priority: 1 // Overrides the more general "lord.qbs" instance, + // which also matches on "Deep Purple". + Depends { name: "texttemplate" } + texttemplate.dict: ({DID: "slipped", THING: "litchi", IDOL: "ritchie"}) +} diff --git a/tests/auto/blackbox/testdata/choose-module-instance/other-searchpath/modules/limerick/generic.qbs b/tests/auto/blackbox/testdata/choose-module-instance/other-searchpath/modules/limerick/generic.qbs new file mode 100644 index 00000000..2ebaaac1 --- /dev/null +++ b/tests/auto/blackbox/testdata/choose-module-instance/other-searchpath/modules/limerick/generic.qbs @@ -0,0 +1,3 @@ +Module { + condition: !qbs.targetOS.contains("Beatles") +} diff --git a/tests/auto/blackbox/testdata/clean/clean.qbs b/tests/auto/blackbox/testdata/clean/clean.qbs new file mode 100644 index 00000000..ce3a8eb1 --- /dev/null +++ b/tests/auto/blackbox/testdata/clean/clean.qbs @@ -0,0 +1,19 @@ +Project { + DynamicLibrary { + Depends { name: "cpp" } + version: "1.1.0" + name: "dep" + files: "dep.cpp" + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } + + CppApplication { + consoleApplication: true + name: "app" + Depends { name: "dep" } + files: "main.cpp" + } +} diff --git a/tests/auto/blackbox/testdata/clean/dep.cpp b/tests/auto/blackbox/testdata/clean/dep.cpp new file mode 100644 index 00000000..5997c193 --- /dev/null +++ b/tests/auto/blackbox/testdata/clean/dep.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" + +DLL_EXPORT void f() {} diff --git a/tests/auto/blackbox/testdata/clean/main.cpp b/tests/auto/blackbox/testdata/clean/main.cpp new file mode 100644 index 00000000..e14f806b --- /dev/null +++ b/tests/auto/blackbox/testdata/clean/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { } diff --git a/tests/auto/blackbox/testdata/cli/HelloWorld.cs b/tests/auto/blackbox/testdata/cli/HelloWorld.cs new file mode 100755 index 00000000..1b794c18 --- /dev/null +++ b/tests/auto/blackbox/testdata/cli/HelloWorld.cs @@ -0,0 +1,12 @@ +using Fake; + +public class HelloWorld +{ + public static void Main() + { + System.Console.WriteLine("Hello world!"); + System.Console.WriteLine(Mod.Cool()); + System.Console.WriteLine(Libby.Cool()); + System.Console.WriteLine(Libby2.Cool()); + } +} diff --git a/tests/auto/blackbox/testdata/cli/Libby.cs b/tests/auto/blackbox/testdata/cli/Libby.cs new file mode 100755 index 00000000..5ca27efd --- /dev/null +++ b/tests/auto/blackbox/testdata/cli/Libby.cs @@ -0,0 +1,10 @@ +namespace Fake +{ + public class Libby + { + public static int Cool() + { + return 45; + } + } +} diff --git a/tests/auto/blackbox/testdata/cli/Libby2.cs b/tests/auto/blackbox/testdata/cli/Libby2.cs new file mode 100755 index 00000000..cfaf9907 --- /dev/null +++ b/tests/auto/blackbox/testdata/cli/Libby2.cs @@ -0,0 +1,10 @@ +namespace Fake +{ + public class Libby2 + { + public static int Cool() + { + return 22; + } + } +} diff --git a/tests/auto/blackbox/testdata/cli/Module.cs b/tests/auto/blackbox/testdata/cli/Module.cs new file mode 100644 index 00000000..dc08846d --- /dev/null +++ b/tests/auto/blackbox/testdata/cli/Module.cs @@ -0,0 +1,10 @@ +namespace Fake +{ + public class Mod + { + public static int Cool() + { + return 420; + } + } +} diff --git a/tests/auto/blackbox/testdata/cli/Module.vb b/tests/auto/blackbox/testdata/cli/Module.vb new file mode 100755 index 00000000..19763627 --- /dev/null +++ b/tests/auto/blackbox/testdata/cli/Module.vb @@ -0,0 +1,7 @@ +Namespace Fake + Public Class [Mod] + Public Shared Function Cool() As Integer + Return 420 + End Function + End Class +End Namespace diff --git a/tests/auto/blackbox/testdata/cli/dotnettest.qbs b/tests/auto/blackbox/testdata/cli/dotnettest.qbs new file mode 100644 index 00000000..9a10b806 --- /dev/null +++ b/tests/auto/blackbox/testdata/cli/dotnettest.qbs @@ -0,0 +1,49 @@ +Project { + Application { + Depends { name: "cli" } + Depends { name: "HelloWorldModule"; condition: !qbs.toolchain.contains("mono") } + Depends { name: "NetLib" } + + type: "application" + name: "Hello" + files: ["HelloWorld.cs"] + + Group { + fileTagsFilter: product.type + qbs.install: true + } + } + + // Mono's VB compiler doesn't support modules yet, and if we try with C#, it crashes anyways + NetModule { + condition: !qbs.toolchain.contains("mono") + Depends { name: "cli" } + + name: "HelloWorldModule" + + files: ["Module.vb"] + + Group { + fileTagsFilter: product.type + qbs.install: true + } + } + + DynamicLibrary { + Depends { name: "cli" } + + name: "NetLib" + files: ["Libby.cs", "Libby2.cs"] + + // fill-in for missing NetModule + Group { + condition: qbs.toolchain.contains("mono") + files: ["Module.cs"] + } + + Group { + fileTagsFilter: product.type + qbs.install: true + } + } +} diff --git a/tests/auto/blackbox/testdata/cli/fshello.fs b/tests/auto/blackbox/testdata/cli/fshello.fs new file mode 100644 index 00000000..0df5ca57 --- /dev/null +++ b/tests/auto/blackbox/testdata/cli/fshello.fs @@ -0,0 +1 @@ +printfn "Hello World from F#!" diff --git a/tests/auto/blackbox/testdata/cli/fshello.qbs b/tests/auto/blackbox/testdata/cli/fshello.qbs new file mode 100644 index 00000000..0d503d90 --- /dev/null +++ b/tests/auto/blackbox/testdata/cli/fshello.qbs @@ -0,0 +1,6 @@ +Application { + Depends { name: "cli" } + type: "application" + name: "fshello" + files: "fshello.fs" +} diff --git a/tests/auto/blackbox/testdata/combined-sources/combinable.cpp b/tests/auto/blackbox/testdata/combined-sources/combinable.cpp new file mode 100644 index 00000000..dd0dcc7e --- /dev/null +++ b/tests/auto/blackbox/testdata/combined-sources/combinable.cpp @@ -0,0 +1 @@ +static int i = 1; diff --git a/tests/auto/blackbox/testdata/combined-sources/combined-sources.qbs b/tests/auto/blackbox/testdata/combined-sources/combined-sources.qbs new file mode 100644 index 00000000..277fc7c3 --- /dev/null +++ b/tests/auto/blackbox/testdata/combined-sources/combined-sources.qbs @@ -0,0 +1,11 @@ +CppApplication { + name: "theapp" + files: [ + "combinable.cpp", + "main.cpp", + ] + Group { + files: ["uncombinable.cpp"] + fileTags: ["cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/combined-sources/main.cpp b/tests/auto/blackbox/testdata/combined-sources/main.cpp new file mode 100644 index 00000000..8b8d58de --- /dev/null +++ b/tests/auto/blackbox/testdata/combined-sources/main.cpp @@ -0,0 +1 @@ +int main() { } diff --git a/tests/auto/blackbox/testdata/combined-sources/uncombinable.cpp b/tests/auto/blackbox/testdata/combined-sources/uncombinable.cpp new file mode 100644 index 00000000..1e2d8867 --- /dev/null +++ b/tests/auto/blackbox/testdata/combined-sources/uncombinable.cpp @@ -0,0 +1 @@ +static int i = 2; diff --git a/tests/auto/blackbox/testdata/command-file/command-file.qbs b/tests/auto/blackbox/testdata/command-file/command-file.qbs new file mode 100644 index 00000000..8e25221c --- /dev/null +++ b/tests/auto/blackbox/testdata/command-file/command-file.qbs @@ -0,0 +1,19 @@ +Project { + StaticLibrary { + name: "theLib" + destinationDirectory: project.buildDirectory + Depends { name: "cpp" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + files: ["lib.cpp"] + } + CppApplication { + name: "theApp" + cpp.libraryPaths: project.buildDirectory + files: ["main.cpp"] + cpp.staticLibraries: ['@' + sourceDirectory + '/' + + (qbs.toolchain.contains("msvc") ? "list.msvc" : "list.gcc")] + } +} diff --git a/tests/auto/blackbox/testdata/command-file/lib.cpp b/tests/auto/blackbox/testdata/command-file/lib.cpp new file mode 100644 index 00000000..8101b05d --- /dev/null +++ b/tests/auto/blackbox/testdata/command-file/lib.cpp @@ -0,0 +1 @@ +void f() { } diff --git a/tests/auto/blackbox/testdata/command-file/list.gcc b/tests/auto/blackbox/testdata/command-file/list.gcc new file mode 100644 index 00000000..2bb6dae6 --- /dev/null +++ b/tests/auto/blackbox/testdata/command-file/list.gcc @@ -0,0 +1 @@ +-ltheLib diff --git a/tests/auto/blackbox/testdata/command-file/list.msvc b/tests/auto/blackbox/testdata/command-file/list.msvc new file mode 100644 index 00000000..6f7a50ca --- /dev/null +++ b/tests/auto/blackbox/testdata/command-file/list.msvc @@ -0,0 +1 @@ +theLib.lib diff --git a/tests/auto/blackbox/testdata/command-file/main.cpp b/tests/auto/blackbox/testdata/command-file/main.cpp new file mode 100644 index 00000000..6a0bac9f --- /dev/null +++ b/tests/auto/blackbox/testdata/command-file/main.cpp @@ -0,0 +1,6 @@ +void f(); + +int main() +{ + f(); +} diff --git a/tests/auto/blackbox/testdata/compilerDefinesByLanguage/CppDefinesApp.qbs b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/CppDefinesApp.qbs new file mode 100644 index 00000000..b205ef2a --- /dev/null +++ b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/CppDefinesApp.qbs @@ -0,0 +1,22 @@ +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 00000000..905869df --- /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 00000000..001a8774 --- /dev/null +++ b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/compilerDefinesByLanguage.qbs @@ -0,0 +1,169 @@ +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 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.cpp b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.cpp new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.m b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.m new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.mm b/tests/auto/blackbox/testdata/compilerDefinesByLanguage/test.mm new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/conanfile-probe/testapp/conanfile-probe-project.qbs b/tests/auto/blackbox/testdata/conanfile-probe/testapp/conanfile-probe-project.qbs new file mode 100644 index 00000000..c1cb1ddd --- /dev/null +++ b/tests/auto/blackbox/testdata/conanfile-probe/testapp/conanfile-probe-project.qbs @@ -0,0 +1,22 @@ +import qbs.Probes +import qbs.TextFile + +Project { + + Probes.ConanfileProbe { + id: conan + conanfilePath: path + "/conanfile.py" + options: ({opt: "True"}) + settings: ({os: "AIX"}) + } + + property var check: { + var tf = new TextFile(buildDirectory + "/results.json", TextFile.WriteOnly); + var o = { + json: conan.json.deps_env_info["ENV_VAR"], + dependencies: conan.dependencies["testlib"].libs, + generatedFilesPath: conan.generatedFilesPath + }; + tf.write(JSON.stringify(o)); + } +} diff --git a/tests/auto/blackbox/testdata/conanfile-probe/testapp/conanfile.py b/tests/auto/blackbox/testdata/conanfile-probe/testapp/conanfile.py new file mode 100644 index 00000000..630cf028 --- /dev/null +++ b/tests/auto/blackbox/testdata/conanfile-probe/testapp/conanfile.py @@ -0,0 +1,25 @@ +from conans import ConanFile + +class TestApp(ConanFile): + name = "testapp" + description = "Our project package, to be inspected by the Qbs ConanfileProbe" + license = "none" + version = "6.6.6" + + settings = "os" + options = {"opt": [True, False]} + default_options = {"opt": False} + + requires = "testlib/1.2.3@qbs/testing" + + def configure(self): + self.options["testlib"].opt = self.options.opt + + def source(self): + pass + + def build(self): + pass + + def package(self): + pass diff --git a/tests/auto/blackbox/testdata/conanfile-probe/testlib/conanfile.py b/tests/auto/blackbox/testdata/conanfile-probe/testlib/conanfile.py new file mode 100644 index 00000000..983c2259 --- /dev/null +++ b/tests/auto/blackbox/testdata/conanfile-probe/testlib/conanfile.py @@ -0,0 +1,25 @@ +from conans import ConanFile + +class Testlib(ConanFile): + name = "testlib" + description = "Represents an arbitrary package, for instance on bintray" + license = "none" + version = "1.2.3" + + settings = "os" + options = {"opt": [True, False]} + default_options = {"opt": False} + + def source(self): + pass + + def build(self): + pass + + def package(self): + pass + + def package_info(self): + self.cpp_info.libs = ["testlib1","testlib2"] + self.env_info.ENV_VAR = "TESTLIB_ENV_VAL" + self.user_info.user_var = "testlib_user_val" diff --git a/tests/auto/blackbox/testdata/concurrent-executor/concurrent-executor.qbs b/tests/auto/blackbox/testdata/concurrent-executor/concurrent-executor.qbs new file mode 100644 index 00000000..802aa145 --- /dev/null +++ b/tests/auto/blackbox/testdata/concurrent-executor/concurrent-executor.qbs @@ -0,0 +1,67 @@ +import qbs.File +import qbs.TextFile +import "util.js" as Utils + +Product { + type: ["final1", "final2"] + Group { + files: ["dummy1.input"] + fileTags: ["input1"] + } + Group { + files: ["dummy2.input"] + fileTags: ["input2"] + } + Rule { + inputs: ["input1"] + Artifact { + filePath: project.buildDirectory + "/dummy1.final" + fileTags: ["final1"] + } + prepare: { + var cmds = []; + for (var i = 0; i < 10; ++i) { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.createFile = i == 9; + cmd.sourceCode = function() { + if (createFile) { + console.info("Creating file"); + var file = new TextFile(output.filePath, TextFile.WriteOnly); + file.close(); + } + }; + cmds.push(cmd); + } + return cmds; + } + } + Rule { + inputs: ["input2"] + Artifact { + filePath: "dummy.intermediate" + fileTags: ["intermediate"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { }; + return [cmd]; + } + } + Rule { + inputs: ["intermediate"] + outputFileTags: "final2" + prepare: { + do + Utils.sleep(6000); + while (!File.exists(project.buildDirectory + "/dummy1.final")); + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { }; + return [cmd]; + } + } +} + + diff --git a/tests/auto/blackbox/testdata/concurrent-executor/dummy1.input b/tests/auto/blackbox/testdata/concurrent-executor/dummy1.input new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/concurrent-executor/dummy2.input b/tests/auto/blackbox/testdata/concurrent-executor/dummy2.input new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/concurrent-executor/util.js b/tests/auto/blackbox/testdata/concurrent-executor/util.js new file mode 100644 index 00000000..a37a8cbb --- /dev/null +++ b/tests/auto/blackbox/testdata/concurrent-executor/util.js @@ -0,0 +1,8 @@ +function sleep(timeInMs) +{ + var referenceTime = new Date(); + var time = null; + do { + time = new Date(); + } while (time - referenceTime < timeInMs); +} diff --git a/tests/auto/blackbox/testdata/conditional-export/conditional-export.qbs b/tests/auto/blackbox/testdata/conditional-export/conditional-export.qbs new file mode 100644 index 00000000..0f2fb2f4 --- /dev/null +++ b/tests/auto/blackbox/testdata/conditional-export/conditional-export.qbs @@ -0,0 +1,16 @@ +Project { + property bool enableExport: false + Product { + name: "dep" + Export { + condition: project.enableExport + Depends { name: "cpp" } + cpp.defines: ["THE_DEFINE"] + } + } + CppApplication { + name: "theProduct" + Depends { name: "dep" } + files: "main.cpp" + } +} diff --git a/tests/auto/blackbox/testdata/conditional-export/main.cpp b/tests/auto/blackbox/testdata/conditional-export/main.cpp new file mode 100644 index 00000000..1235aa19 --- /dev/null +++ b/tests/auto/blackbox/testdata/conditional-export/main.cpp @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef THE_DEFINE +#error "missing define" +#endif + +int main() { } diff --git a/tests/auto/blackbox/testdata/conditional-filetagger/conditional-filetagger.qbs b/tests/auto/blackbox/testdata/conditional-filetagger/conditional-filetagger.qbs new file mode 100644 index 00000000..5f0c93e4 --- /dev/null +++ b/tests/auto/blackbox/testdata/conditional-filetagger/conditional-filetagger.qbs @@ -0,0 +1,10 @@ +CppApplication { + name: "theApp" + property bool enableTagger + files: ["main.custom"]; + FileTagger { + condition: enableTagger + patterns: ["*.custom"] + fileTags: ["cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/conditional-filetagger/main.custom b/tests/auto/blackbox/testdata/conditional-filetagger/main.custom new file mode 100644 index 00000000..8b8d58de --- /dev/null +++ b/tests/auto/blackbox/testdata/conditional-filetagger/main.custom @@ -0,0 +1 @@ +int main() { } diff --git a/tests/auto/blackbox/testdata/configure/configure.qbs b/tests/auto/blackbox/testdata/configure/configure.qbs new file mode 100644 index 00000000..62dfa4ce --- /dev/null +++ b/tests/auto/blackbox/testdata/configure/configure.qbs @@ -0,0 +1,20 @@ +import qbs.FileInfo + +Project { + property bool enabled: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + property string name: 'configure' + qbsSearchPaths: '.' + Product { + type: 'application' + consoleApplication: true + name: project.name + files: 'main.cpp' + Depends { name: 'cpp' } + Depends { name: 'definition' } + } +} diff --git a/tests/auto/blackbox/testdata/configure/main.cpp b/tests/auto/blackbox/testdata/configure/main.cpp new file mode 100644 index 00000000..c7213c76 --- /dev/null +++ b/tests/auto/blackbox/testdata/configure/main.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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 + +int main() +{ + printf("%s..\n", TEXT); + return 0; +} + diff --git a/tests/auto/blackbox/testdata/configure/modules/definition/module.qbs b/tests/auto/blackbox/testdata/configure/modules/definition/module.qbs new file mode 100644 index 00000000..1d647faa --- /dev/null +++ b/tests/auto/blackbox/testdata/configure/modules/definition/module.qbs @@ -0,0 +1,34 @@ +import qbs.Process + +Module { + name: 'definition' + Depends { name: 'cpp' } + Probe { + id: node + property stringList targetOS: qbs.targetOS + property stringList windowsShellPath: qbs.windowsShellPath + property string result + configure: { + var cmd; + var args; + var p = path; + if (targetOS.contains("windows")) { + cmd = windowsShellPath; + args = ["/c", "date", "/t"]; + } else { + cmd = 'date'; + args = []; + } + var p = new Process(); + if (0 === p.exec(cmd, args)) { + found = true; + result = p.readLine(); + } else { + found = false; + result = undefined; + } + p.close(); + } + } + cpp.defines: node.found ? 'TEXT="Configured at ' + node.result + '"' : undefined +} diff --git a/tests/auto/blackbox/testdata/conflicting-artifacts/conflicting-artifacts.qbs b/tests/auto/blackbox/testdata/conflicting-artifacts/conflicting-artifacts.qbs new file mode 100644 index 00000000..ade073e1 --- /dev/null +++ b/tests/auto/blackbox/testdata/conflicting-artifacts/conflicting-artifacts.qbs @@ -0,0 +1,14 @@ +Project { + CppApplication { + name: "a" + targetName: "theName" + destinationDirectory: project.buildDirectory + files: ["main.cpp"] + } + CppApplication { + name: "b" + targetName: "theName" + destinationDirectory: project.buildDirectory + files: ["main.cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/conflicting-artifacts/main.cpp b/tests/auto/blackbox/testdata/conflicting-artifacts/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/blackbox/testdata/conflicting-artifacts/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} 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 00000000..0bfdaceb --- /dev/null +++ b/tests/auto/blackbox/testdata/cpu-features/cpu-features.qbs @@ -0,0 +1,14 @@ +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 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/cpu-features/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/cxx-language-version/cxx-language-version.qbs b/tests/auto/blackbox/testdata/cxx-language-version/cxx-language-version.qbs new file mode 100644 index 00000000..322ded85 --- /dev/null +++ b/tests/auto/blackbox/testdata/cxx-language-version/cxx-language-version.qbs @@ -0,0 +1,35 @@ +CppApplication { + name: "app" + + files: ["main.cpp"] + + Probe { + id: compilerProbe + property stringList toolchain: qbs.toolchain + property string compilerVersion: cpp.compilerVersion + + configure: { + var isNewerMsvc; + var isEvenNewerMsvc; + var isOlderMsvc; + var isGcc; + if (toolchain.contains("clang-cl")) { + isEvenNewerMsvc = true; + isNewerMsvc = true; + } else if (toolchain.contains("msvc")) { + if (compilerVersion >= "19.12.25831") + isEvenNewerMsvc = true; + if (compilerVersion >= "18.00.30723") + isNewerMsvc = true; + else + isOlderMsvc = true; + } else { + isGcc = true; + } + console.info("is newer MSVC: " + isNewerMsvc); + console.info("is even newer MSVC: " + isEvenNewerMsvc); + console.info("is older MSVC: " + isOlderMsvc); + console.info("is GCC: " + isGcc); + } + } +} diff --git a/tests/auto/blackbox/testdata/cxx-language-version/main.cpp b/tests/auto/blackbox/testdata/cxx-language-version/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/cxx-language-version/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/dependenciesProperty/dependenciesProperty.qbs b/tests/auto/blackbox/testdata/dependenciesProperty/dependenciesProperty.qbs new file mode 100644 index 00000000..735bfffb --- /dev/null +++ b/tests/auto/blackbox/testdata/dependenciesProperty/dependenciesProperty.qbs @@ -0,0 +1,42 @@ +import qbs.TextFile +import qbs.FileInfo + +Project { + Product { name: "newDependency" } + Product { + type: "deps" + name: "product1" + Depends { name: "product2" } + // Depends { name: 'product2' } + // Depends { name: 'newDependency' } + Rule { + multiplex: true + inputsFromDependencies: "application" + Artifact { + fileTags: ["deps"] + filePath: product.name + '.deps' + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = 'generate ' + FileInfo.fileName(output.filePath); + cmd.highlight = 'codegen'; + cmd.sourceCode = function() { + file = new TextFile(output.filePath, TextFile.WriteOnly); + file.truncate(); + file.write(JSON.stringify(product.dependencies, undefined, 2)); + file.close(); + } + return cmd; + } + } + } + Product { + type: "application" + consoleApplication: true + name: "product2" + property string narf: "zort" + Depends { name: "cpp" } + cpp.defines: ["SMURF"] + files: ["product2.cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/dependenciesProperty/product2.cpp b/tests/auto/blackbox/testdata/dependenciesProperty/product2.cpp new file mode 100644 index 00000000..210c8274 --- /dev/null +++ b/tests/auto/blackbox/testdata/dependenciesProperty/product2.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/dependency-scanning-loop/dependency-scanning-loop.qbs b/tests/auto/blackbox/testdata/dependency-scanning-loop/dependency-scanning-loop.qbs new file mode 100644 index 00000000..ac8e7258 --- /dev/null +++ b/tests/auto/blackbox/testdata/dependency-scanning-loop/dependency-scanning-loop.qbs @@ -0,0 +1,34 @@ +import qbs.FileInfo +import qbs.TextFile + +CppApplication { + name: "app" + cpp.includePaths: buildDirectory + Group { + files: "main.cpp" + fileTags: ["cpp", "custom.in"] + } + Rule { + inputs: "custom.in" + Artifact { + filePath: FileInfo.completeBaseName(input.filePath) + ".h" + fileTags: "hpp" + } + Artifact { + filePath: "custom.txt" + fileTags: "whatever" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + outputs.hpp[0].fileName; + cmd.sourceCode = function() { + var f = new TextFile(outputs.hpp[0].filePath, TextFile.WriteOnly); + f.writeLine("int main() {}"); + f.close(); + f = new TextFile(outputs.whatever[0].filePath, TextFile.WriteOnly); + f.close(); + } + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/dependency-scanning-loop/main.cpp b/tests/auto/blackbox/testdata/dependency-scanning-loop/main.cpp new file mode 100644 index 00000000..5e8dda41 --- /dev/null +++ b/tests/auto/blackbox/testdata/dependency-scanning-loop/main.cpp @@ -0,0 +1 @@ +#include diff --git a/tests/auto/blackbox/testdata/deprecated-property/deprecated-property.qbs b/tests/auto/blackbox/testdata/deprecated-property/deprecated-property.qbs new file mode 100644 index 00000000..a8efb97b --- /dev/null +++ b/tests/auto/blackbox/testdata/deprecated-property/deprecated-property.qbs @@ -0,0 +1,8 @@ +import qbs + +Product { + Depends { name: "themodule" } + themodule.newProp: true + themodule.oldProp: false + themodule.veryOldProp: false +} diff --git a/tests/auto/blackbox/testdata/deprecated-property/modules/themodule/m.qbs b/tests/auto/blackbox/testdata/deprecated-property/modules/themodule/m.qbs new file mode 100644 index 00000000..106ed413 --- /dev/null +++ b/tests/auto/blackbox/testdata/deprecated-property/modules/themodule/m.qbs @@ -0,0 +1,27 @@ +import qbs + +Module { + property bool newProp + property bool oldProp + property bool forgottenProp + + PropertyOptions { + name: "newProp" + description: "Use this, it's good!" + } + PropertyOptions { + name: "oldProp" + description: "Use newProp instead." + removalVersion: "99.9" + } + PropertyOptions { + name: "veryOldProp" + description: "Use newProp instead." + removalVersion: "1.3" + } + PropertyOptions { + name: "forgottenProp" + description: "Use newProp instead." + removalVersion: "1.8" + } +} diff --git a/tests/auto/blackbox/testdata/disappeared-profile/disappeared-profile.qbs b/tests/auto/blackbox/testdata/disappeared-profile/disappeared-profile.qbs new file mode 100644 index 00000000..f1460aea --- /dev/null +++ b/tests/auto/blackbox/testdata/disappeared-profile/disappeared-profile.qbs @@ -0,0 +1,12 @@ +Product { + type: ["out1", "out2"] + Depends { name: "m" } + Group { + files: ["in1.txt"] + fileTags: ["in1"] + } + Group { + files: ["in2.txt"] + fileTags: ["in2"] + } +} diff --git a/tests/auto/blackbox/testdata/disappeared-profile/in1.txt b/tests/auto/blackbox/testdata/disappeared-profile/in1.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/disappeared-profile/in2.txt b/tests/auto/blackbox/testdata/disappeared-profile/in2.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/disappeared-profile/modules-dir/modules/m/m.qbs b/tests/auto/blackbox/testdata/disappeared-profile/modules-dir/modules/m/m.qbs new file mode 100644 index 00000000..3e1747ad --- /dev/null +++ b/tests/auto/blackbox/testdata/disappeared-profile/modules-dir/modules/m/m.qbs @@ -0,0 +1,32 @@ +Module { + property string p1 + property string p2 + + Rule { + inputs: ["in1"] + Artifact { + filePath: "dummy1.txt" + fileTags: ["out1"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating " + output.fileName + " with " + product.m.p1; + cmd.sourceCode = function() {}; + return [cmd]; + } + } + + Rule { + inputs: ["in2"] + Artifact { + filePath: "dummy2.txt" + fileTags: ["out2"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating " + output.fileName + " with " + product.m.p2; + cmd.sourceCode = function() {}; + return [cmd]; + } + } +} 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 00000000..fdd3aa1f --- /dev/null +++ b/tests/auto/blackbox/testdata/discard-unused-data/discard-unused-data.qbs @@ -0,0 +1,27 @@ +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 + outputFileTags: "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 00000000..e3aab266 --- /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/distribution-include-paths/distribution-include-paths.qbs b/tests/auto/blackbox/testdata/distribution-include-paths/distribution-include-paths.qbs new file mode 100644 index 00000000..3e6fb9b0 --- /dev/null +++ b/tests/auto/blackbox/testdata/distribution-include-paths/distribution-include-paths.qbs @@ -0,0 +1,4 @@ +CppApplication { + files: ["main.cpp"] + cpp.distributionIncludePaths: ["subdir"] +} diff --git a/tests/auto/blackbox/testdata/distribution-include-paths/main.cpp b/tests/auto/blackbox/testdata/distribution-include-paths/main.cpp new file mode 100644 index 00000000..e449173d --- /dev/null +++ b/tests/auto/blackbox/testdata/distribution-include-paths/main.cpp @@ -0,0 +1,8 @@ +#include +#include + +int main() +{ + printStuff(); + return 0; +} diff --git a/tests/auto/blackbox/testdata/distribution-include-paths/subdir/gagagugu.h b/tests/auto/blackbox/testdata/distribution-include-paths/subdir/gagagugu.h new file mode 100644 index 00000000..b951d885 --- /dev/null +++ b/tests/auto/blackbox/testdata/distribution-include-paths/subdir/gagagugu.h @@ -0,0 +1,4 @@ +void printStuff() +{ + puts("alalalalonglonglilonglonglong"); +} diff --git a/tests/auto/blackbox/testdata/driver-linker-flags/driver-linker-flags.qbs b/tests/auto/blackbox/testdata/driver-linker-flags/driver-linker-flags.qbs new file mode 100644 index 00000000..5de0fe05 --- /dev/null +++ b/tests/auto/blackbox/testdata/driver-linker-flags/driver-linker-flags.qbs @@ -0,0 +1,17 @@ +CppApplication { + files: "main.cpp" + + Properties { + condition: toolchainProbe.found + cpp.driverLinkerFlags: ["-nostartfiles"] + } + + Probe { + id: toolchainProbe + condition: qbs.toolchain.contains("gcc") + configure: { + console.info("toolchain is GCC-like"); + found = true; + } + } +} diff --git a/tests/auto/blackbox/testdata/driver-linker-flags/main.cpp b/tests/auto/blackbox/testdata/driver-linker-flags/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/driver-linker-flags/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/dynamic-library-in-module/Dll.qbs b/tests/auto/blackbox/testdata/dynamic-library-in-module/Dll.qbs new file mode 100644 index 00000000..1acf606d --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-library-in-module/Dll.qbs @@ -0,0 +1,15 @@ +DynamicLibrary { + Depends { name: "cpp" } + Depends { name: "bundle"; condition: qbs.targetOS.contains("darwin") } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + cpp.minimumMacosVersion: "10.7" // For -rpath + } + + install: true + installImportLib: true + qbs.installPrefix: "" + installDir: "" + importLibInstallDir: "" +} diff --git a/tests/auto/blackbox/testdata/dynamic-library-in-module/lib1.cpp b/tests/auto/blackbox/testdata/dynamic-library-in-module/lib1.cpp new file mode 100644 index 00000000..ccf9e47a --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-library-in-module/lib1.cpp @@ -0,0 +1,7 @@ +#include "../dllexport.h" +#include + +DLL_EXPORT void theLibFunc() +{ + std::cout << "Hello from thelib!" << std::endl; +} diff --git a/tests/auto/blackbox/testdata/dynamic-library-in-module/lib2.cpp b/tests/auto/blackbox/testdata/dynamic-library-in-module/lib2.cpp new file mode 100644 index 00000000..f3f0711f --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-library-in-module/lib2.cpp @@ -0,0 +1,7 @@ +#include "../dllexport.h" +#include + +DLL_EXPORT void theOtherLibFunc() +{ + std::cout << "Hello from theotherlib!" << std::endl; +} diff --git a/tests/auto/blackbox/testdata/dynamic-library-in-module/lib3.cpp b/tests/auto/blackbox/testdata/dynamic-library-in-module/lib3.cpp new file mode 100644 index 00000000..d2beebbb --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-library-in-module/lib3.cpp @@ -0,0 +1,7 @@ +#include "../dllexport.h" +#include + +DLL_EXPORT void theThirdLibFunc() +{ + std::cout << "Hello from thethirdlib!" << std::endl; +} diff --git a/tests/auto/blackbox/testdata/dynamic-library-in-module/lib4.cpp b/tests/auto/blackbox/testdata/dynamic-library-in-module/lib4.cpp new file mode 100644 index 00000000..9e43fe56 --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-library-in-module/lib4.cpp @@ -0,0 +1,8 @@ +#include "../dllexport.h" + +DLL_IMPORT void theLibFunc(); + +DLL_EXPORT void theFourthLibFunc() +{ + theLibFunc(); +} diff --git a/tests/auto/blackbox/testdata/dynamic-library-in-module/lib5.cpp b/tests/auto/blackbox/testdata/dynamic-library-in-module/lib5.cpp new file mode 100644 index 00000000..49887a95 --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-library-in-module/lib5.cpp @@ -0,0 +1 @@ +void staticLibFunc() { } diff --git a/tests/auto/blackbox/testdata/dynamic-library-in-module/main.cpp b/tests/auto/blackbox/testdata/dynamic-library-in-module/main.cpp new file mode 100644 index 00000000..75d272c6 --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-library-in-module/main.cpp @@ -0,0 +1,13 @@ +#include "../dllexport.h" + +DLL_IMPORT void theOtherLibFunc(); +DLL_IMPORT void theFourthLibFunc(); + +void theThirdLibFunc() { } + +int main() +{ + theOtherLibFunc(); + theThirdLibFunc(); + theFourthLibFunc(); +} diff --git a/tests/auto/blackbox/testdata/dynamic-library-in-module/modules/thelib/broken.cpp b/tests/auto/blackbox/testdata/dynamic-library-in-module/modules/thelib/broken.cpp new file mode 100644 index 00000000..c91736ab --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-library-in-module/modules/thelib/broken.cpp @@ -0,0 +1 @@ +syntax error diff --git a/tests/auto/blackbox/testdata/dynamic-library-in-module/modules/thelib/thelib.qbs b/tests/auto/blackbox/testdata/dynamic-library-in-module/modules/thelib/thelib.qbs new file mode 100644 index 00000000..30f87337 --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-library-in-module/modules/thelib/thelib.qbs @@ -0,0 +1,27 @@ +import qbs.FileInfo + +Module { + Depends { name: "cpp" } + property string baseDir: FileInfo.cleanPath(FileInfo.joinPaths(path, "..", "..")) + cpp.rpaths: [product.thelib.baseDir] + Group { + name: "thelib dll" + files: FileInfo.joinPaths(product.thelib.baseDir, + cpp.dynamicLibraryPrefix + "thelib" + cpp.dynamicLibrarySuffix) + fileTags: ["dynamiclibrary"] + filesAreTargets: true + } + Group { + name: "thelib dll import" + condition: qbs.targetOS.contains("windows") + files: FileInfo.joinPaths(product.thelib.baseDir, "thelib.lib") + fileTags: ["dynamiclibrary_import"] + filesAreTargets: true + } + Group { + name: "to be ignored" + filesAreTargets: true + files: "broken.cpp" + fileTags: ["cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/dynamic-library-in-module/modules/theotherlib/theotherlib.qbs b/tests/auto/blackbox/testdata/dynamic-library-in-module/modules/theotherlib/theotherlib.qbs new file mode 100644 index 00000000..1eb1e01a --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-library-in-module/modules/theotherlib/theotherlib.qbs @@ -0,0 +1,21 @@ +import qbs.FileInfo + +Module { + Depends { name: "cpp" } + property string baseDir: FileInfo.cleanPath(FileInfo.joinPaths(path, "..", "..")) + cpp.rpaths: [product.theotherlib.baseDir] + Group { + name: "theotherlib dll" + files: FileInfo.joinPaths(product.theotherlib.baseDir, cpp.dynamicLibraryPrefix + + "theotherlib" + cpp.dynamicLibrarySuffix) + fileTags: ["dynamiclibrary"] + filesAreTargets: true + } + Group { + name: "theotherlib dll import" + condition: qbs.targetOS.contains("windows") + files: FileInfo.joinPaths(product.theotherlib.baseDir, "theotherlib.lib") + fileTags: ["dynamiclibrary_import"] + filesAreTargets: true + } +} diff --git a/tests/auto/blackbox/testdata/dynamic-library-in-module/modules/thethirdlib/thethirdlib.qbs b/tests/auto/blackbox/testdata/dynamic-library-in-module/modules/thethirdlib/thethirdlib.qbs new file mode 100644 index 00000000..b4592f51 --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-library-in-module/modules/thethirdlib/thethirdlib.qbs @@ -0,0 +1,21 @@ +import qbs.FileInfo + +Module { + Depends { name: "cpp" } + property string baseDir: FileInfo.cleanPath(FileInfo.joinPaths(path, "..", "..")) + Group { + name: "thethirdlib dll" + condition: false + files: FileInfo.joinPaths(product.theotherlib.baseDir, cpp.dynamicLibraryPrefix + + "thethirdlib" + cpp.dynamicLibrarySuffix) + fileTags: ["dynamiclibrary"] + filesAreTargets: true + } + Group { + name: "thethirdlib dll import" + condition: false + files: FileInfo.joinPaths(product.thethirdlib.baseDir, "thethirdlib.lib") + fileTags: ["dynamiclibrary_import"] + filesAreTargets: true + } +} diff --git a/tests/auto/blackbox/testdata/dynamic-library-in-module/theapp.qbs b/tests/auto/blackbox/testdata/dynamic-library-in-module/theapp.qbs new file mode 100644 index 00000000..26b3f2ca --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-library-in-module/theapp.qbs @@ -0,0 +1,37 @@ +Project { + CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: "theapp" + cpp.minimumMacosVersion: "10.7" // For -rpath + Depends { name: "theotherlib" } + Depends { name: "thethirdlib" } + Depends { name: "thefourthlib" } + Depends { name: "staticlib" } + files: "main.cpp" + Group { + fileTagsFilter: "dynamiclibrary" + qbs.install: true + qbs.installSourceBase: buildDirectory + } + } + Dll { + name: "thefourthlib" + Depends { name: "thelib" } + files: "lib4.cpp" + Export { + Depends { name: "cpp" } + cpp.rpaths: [qbs.installRoot] + } + } + StaticLibrary { + name: "staticlib" + Depends { name: "cpp" } + Depends { name: "theotherlib" } + files: "lib5.cpp" + } +} diff --git a/tests/auto/blackbox/testdata/dynamic-library-in-module/thelibs.qbs b/tests/auto/blackbox/testdata/dynamic-library-in-module/thelibs.qbs new file mode 100644 index 00000000..b06a3f17 --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-library-in-module/thelibs.qbs @@ -0,0 +1,14 @@ +Project { + Dll { + name: "thelib" + files: "lib1.cpp" + } + Dll { + name: "theotherlib" + files: "lib2.cpp" + } + Dll { + name: "thethirdlib" + files: "lib3.cpp" + } +} diff --git a/tests/auto/blackbox/testdata/dynamic-project/dynamic-project.qbs b/tests/auto/blackbox/testdata/dynamic-project/dynamic-project.qbs new file mode 100644 index 00000000..166648c8 --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-project/dynamic-project.qbs @@ -0,0 +1,42 @@ +import qbs.File +import qbs.FileInfo +import qbs.TextFile + +Project +{ + Probe + { + id: projectBuilder + property stringList refs: [] + property string sourceDir: sourceDirectory + + configure: + { + var tempDir = FileInfo.joinPaths(sourceDir, "temp"); + File.makePath(tempDir); + var srcDir = FileInfo.joinPaths(sourceDir, "src"); + var projectDirs = File.directoryEntries(srcDir, File.Dirs | File.NoDotAndDotDot); + var list = []; + for (it in projectDirs) { + var name = projectDirs[it]; + var productSrcDir = FileInfo.joinPaths(srcDir, name); + var productFilePath = FileInfo.joinPaths(tempDir, name + ".qbs"); + var file = new TextFile(productFilePath, TextFile.WriteOnly); + try { + file.writeLine("import qbs"); + file.writeLine("CppApplication"); + file.writeLine("{"); + file.writeLine("\tfiles: [ \"" + productSrcDir + "/*.cpp\" ]"); + file.writeLine("}"); + } finally { + file.close(); + } + list.push(productFilePath); + } + found = true; + refs = list; + } + } + + references: projectBuilder.refs +} diff --git a/tests/auto/blackbox/testdata/dynamic-project/src/app/main.cpp b/tests/auto/blackbox/testdata/dynamic-project/src/app/main.cpp new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-project/src/app/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/dynamic-project/src/app2/main.cpp b/tests/auto/blackbox/testdata/dynamic-project/src/app2/main.cpp new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamic-project/src/app2/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/dynamicMultiplexRule/dynamicMultiplexRule.qbs b/tests/auto/blackbox/testdata/dynamicMultiplexRule/dynamicMultiplexRule.qbs new file mode 100644 index 00000000..b8adcaf4 --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamicMultiplexRule/dynamicMultiplexRule.qbs @@ -0,0 +1,32 @@ +import qbs.TextFile + +Project { + Product { + type: ["stuff"] + Group { + files: ["one.txt", "two.txt", "three.txt"] + fileTags: ["text"] + } + Rule { + multiplex: true + inputs: "text" + outputFileTags: ["stuff"] + outputArtifacts: { + return [{ + filePath: "stuff-from-" + inputs.text.length + "-inputs", + fileTags: ["stuff"] + }]; + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.write("narf!"); + f.close(); + } + return cmd; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/dynamicMultiplexRule/one.txt b/tests/auto/blackbox/testdata/dynamicMultiplexRule/one.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/dynamicMultiplexRule/three.txt b/tests/auto/blackbox/testdata/dynamicMultiplexRule/three.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/dynamicMultiplexRule/two.txt b/tests/auto/blackbox/testdata/dynamicMultiplexRule/two.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/dynamicRuleOutputs/after/numbers.l b/tests/auto/blackbox/testdata/dynamicRuleOutputs/after/numbers.l new file mode 100644 index 00000000..d0cb4877 --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamicRuleOutputs/after/numbers.l @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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: +** +** "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 and its Subsidiary(-ies) 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." +** +****************************************************************************/ + +/* scanner for integer and float numbers */ + +%option noyywrap + +%{ +#ifndef CRUCIAL_DEFINE +# error CRUCIAL_DEFINE is missing! +#endif + +/* need this for the call to atof() below */ +#include +%} + +DIGIT [0-9] + +%% + +{DIGIT}+ { + printf("integer: %s (%d)\n", yytext, atoi(yytext)); + } + +{DIGIT}+"."{DIGIT}* { + printf("float: %s (%g)\n", yytext, atof(yytext)); + } + +"{"[\^{}}\n]*"}" /* eat up one-line comments */ + +[ \t\n]+ /* eat up whitespace */ + +. printf("Unexpected character: %s\n", yytext); + +%% + +int main(int argc, char **argv) +{ + if (argc > 1) + yyin = fopen(argv[1], "r"); + else + yyin = stdin; + + yylex(); + return 0; +} + diff --git a/tests/auto/blackbox/testdata/dynamicRuleOutputs/before/flexoptionsreader.js b/tests/auto/blackbox/testdata/dynamicRuleOutputs/before/flexoptionsreader.js new file mode 100644 index 00000000..bd596fbc --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamicRuleOutputs/before/flexoptionsreader.js @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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: +** +** "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 and its Subsidiary(-ies) 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." +** +****************************************************************************/ + +// needs import qbs.TextFile + +function readFlexOptions(filePath) +{ + function splitOptions(str) + { + var options = []; + var opt = ""; + var inquote = false; + for (var i = 0; i < str.length; ++i) { + if (str[i] === '"') { + opt += '"'; + inquote = !inquote; + } else if (str[i] === ' ' && !inquote) { + options.push(opt); + opt = ""; + } else { + opt += str[i]; + } + } + if (opt.length) + options.push(opt); + return options; + } + + function unquote(str) + { + var l = str.length; + if (l > 2 && str[0] === '"' && str[l - 1] === '"') + return str.substr(1, l - 2); + return str; + } + + function parseOptionLine(result, str) + { + var options = splitOptions(str); + var re = /^(outfile|header-file)=(.*)$/; + var reres; + for (var k in options) { + re.lastIndex = 0; + reres = re.exec(options[k]); + if (reres === null) + continue; + result[reres[1]] = unquote(reres[2]); + } + } + + var tf = new TextFile(input.filePath); + var line; + var optrex = /^%option\s+(.*$)/; + var res; + var options = {}; + while (!tf.atEof()) { + line = tf.readLine(); + if (line === "%%") + break; + optrex.lastIndex = 0; + res = optrex.exec(line); + if (res === null) + continue; + parseOptionLine(options, res[1]); + } + tf.close(); + return options; +} + diff --git a/tests/auto/blackbox/testdata/dynamicRuleOutputs/before/genlexer.qbs b/tests/auto/blackbox/testdata/dynamicRuleOutputs/before/genlexer.qbs new file mode 100644 index 00000000..00c93a2a --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamicRuleOutputs/before/genlexer.qbs @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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: +** +** "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 and its Subsidiary(-ies) 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." +** +****************************************************************************/ + +import qbs.FileInfo +import qbs.Probes +import qbs.TextFile +import "flexoptionsreader.js" as FlexOptionsReader + +Project { + Product { + name: "genlexer" + type: "application" + consoleApplication: true + Depends { name: "cpp" } + Group { + files: ["numbers.l"] + fileTags: ["flex"] + } + Probes.PathProbe { + id: flexProbe + names: ["flex"] + platformSearchPaths: ["/usr/local/bin", "/usr/bin", "/bin"] + } + property bool isFlexAvailable: flexProbe.found + Rule { + inputs: ["flex"] + outputFileTags: ["c", "hpp"] + outputArtifacts: { + var options = FlexOptionsReader.readFlexOptions(input.filePath); + var sourceFileName = options["outfile"] || "lex.yy.c"; + var headerFileName = options["header-file"]; + var result = [{ + filePath: "GeneratedFiles/" + sourceFileName, + fileTags: ["c"], + cpp: { + defines: ["CRUCIAL_DEFINE"] + } + }]; + if (headerFileName) { + result.push({ + filePath: "GeneratedFiles/" + headerFileName, + fileTags: ["hpp"] + }); + } + return result; + } + prepare: { + var cmd; + if (product.isFlexAvailable) { + // flex is available. Let's call it. + cmd = new Command("flex", [input.filePath]); + cmd.workingDirectory = product.buildDirectory + "/GeneratedFiles"; + } else { + // No flex available here, generate some C source and header. + cmd = new JavaScriptCommand(); + cmd.sourceFileName = outputs["c"][0].filePath; + cmd.headerFileName = outputs["hpp"] ? outputs["hpp"][0].filePath : ""; + cmd.sourceCode = function() { + var fsrc = new TextFile(sourceFileName, TextFile.WriteOnly); + if (headerFileName) { + fsrc.write("#include \"" + FileInfo.fileName(headerFileName) + + "\"\n\n"); + var fhdr = new TextFile(headerFileName, TextFile.WriteOnly); + fhdr.write("// a rather empty header file\n"); + fhdr.close(); + } + fsrc.write("\n#ifndef CRUCIAL_DEFINE\n"); + fsrc.write("# error CRUCIAL_DEFINE is missing!\n"); + fsrc.write("#endif\n\n"); + fsrc.write("int main() { return 0; }\n"); + fsrc.close(); + }; + } + cmd.description = "flexing " + FileInfo.fileName(input.filePath); + return cmd; + } + } + } +} + diff --git a/tests/auto/blackbox/testdata/dynamicRuleOutputs/before/numbers.l b/tests/auto/blackbox/testdata/dynamicRuleOutputs/before/numbers.l new file mode 100644 index 00000000..26419b6c --- /dev/null +++ b/tests/auto/blackbox/testdata/dynamicRuleOutputs/before/numbers.l @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://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: +** +** "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 and its Subsidiary(-ies) 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." +** +****************************************************************************/ + +/* scanner for integer and float numbers */ + +%option noyywrap +%option outfile="numberscanner.c" header-file="numberscanner.h" + +%{ +#ifndef CRUCIAL_DEFINE +# error CRUCIAL_DEFINE is missing! +#endif + +/* need this for the call to atof() below */ +#include +%} + +DIGIT [0-9] + +%% + +{DIGIT}+ { + printf("integer: %s (%d)\n", yytext, atoi(yytext)); + } + +{DIGIT}+"."{DIGIT}* { + printf("float: %s (%g)\n", yytext, atof(yytext)); + } + +"{"[\^{}}\n]*"}" /* eat up one-line comments */ + +[ \t\n]+ /* eat up whitespace */ + +. printf("Unexpected character: %s\n", yytext); + +%% + +int main(int argc, char **argv) +{ + if (argc > 1) + yyin = fopen(argv[1], "r"); + else + yyin = stdin; + + yylex(); + return 0; +} + diff --git a/tests/auto/blackbox/testdata/empty-profile/empty-profile.qbs b/tests/auto/blackbox/testdata/empty-profile/empty-profile.qbs new file mode 100644 index 00000000..da753631 --- /dev/null +++ b/tests/auto/blackbox/testdata/empty-profile/empty-profile.qbs @@ -0,0 +1,3 @@ +CppApplication { + files: ["main.cpp"] +} diff --git a/tests/auto/blackbox/testdata/empty-profile/main.cpp b/tests/auto/blackbox/testdata/empty-profile/main.cpp new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/blackbox/testdata/empty-profile/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/enableExceptions/empty.m b/tests/auto/blackbox/testdata/enableExceptions/empty.m new file mode 100644 index 00000000..d3714dc0 --- /dev/null +++ b/tests/auto/blackbox/testdata/enableExceptions/empty.m @@ -0,0 +1 @@ +int main2() { return 0; } diff --git a/tests/auto/blackbox/testdata/enableExceptions/empty.mm b/tests/auto/blackbox/testdata/enableExceptions/empty.mm new file mode 100644 index 00000000..fe94a49a --- /dev/null +++ b/tests/auto/blackbox/testdata/enableExceptions/empty.mm @@ -0,0 +1 @@ +int main3() { return 0; } diff --git a/tests/auto/blackbox/testdata/enableExceptions/emptymain.cpp b/tests/auto/blackbox/testdata/enableExceptions/emptymain.cpp new file mode 100644 index 00000000..210c8274 --- /dev/null +++ b/tests/auto/blackbox/testdata/enableExceptions/emptymain.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/enableExceptions/exceptions-objc.qbs b/tests/auto/blackbox/testdata/enableExceptions/exceptions-objc.qbs new file mode 100644 index 00000000..eef00da7 --- /dev/null +++ b/tests/auto/blackbox/testdata/enableExceptions/exceptions-objc.qbs @@ -0,0 +1,4 @@ +CppApplication { + files: ["main.m"] + cpp.frameworks: ["Foundation"] +} diff --git a/tests/auto/blackbox/testdata/enableExceptions/exceptions-objcpp-cpp.qbs b/tests/auto/blackbox/testdata/enableExceptions/exceptions-objcpp-cpp.qbs new file mode 100644 index 00000000..7f703b9a --- /dev/null +++ b/tests/auto/blackbox/testdata/enableExceptions/exceptions-objcpp-cpp.qbs @@ -0,0 +1,6 @@ +CppApplication { + Group { + files: ["main.cpp"] + fileTags: ["objcpp"] + } +} diff --git a/tests/auto/blackbox/testdata/enableExceptions/exceptions-objcpp.qbs b/tests/auto/blackbox/testdata/enableExceptions/exceptions-objcpp.qbs new file mode 100644 index 00000000..8946f9a3 --- /dev/null +++ b/tests/auto/blackbox/testdata/enableExceptions/exceptions-objcpp.qbs @@ -0,0 +1,7 @@ +CppApplication { + Group { + files: ["main.m"] + fileTags: ["objcpp"] + } + cpp.frameworks: ["Foundation"] +} diff --git a/tests/auto/blackbox/testdata/enableExceptions/exceptions.qbs b/tests/auto/blackbox/testdata/enableExceptions/exceptions.qbs new file mode 100644 index 00000000..9a75043a --- /dev/null +++ b/tests/auto/blackbox/testdata/enableExceptions/exceptions.qbs @@ -0,0 +1,5 @@ +CppApplication { + files: ["main.cpp"] + cpp.treatWarningsAsErrors: true + cpp.defines: qbs.toolchain.contains("msvc") && !cpp.enableExceptions ? ["FORCE_FAIL_VS"] : [] +} diff --git a/tests/auto/blackbox/testdata/enableExceptions/main.cpp b/tests/auto/blackbox/testdata/enableExceptions/main.cpp new file mode 100644 index 00000000..95a75710 --- /dev/null +++ b/tests/auto/blackbox/testdata/enableExceptions/main.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** 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 + +#if defined(__GNUC__) && !(defined(__cpp_exceptions) || defined(__EXCEPTIONS)) +#error Exceptions are disabled! +#endif + +int main() { +#ifdef FORCE_FAIL_VS +#error "Microsoft Visual C++ cannot disable exceptions at compile-time" +#endif + throw std::runtime_error("failed"); +} diff --git a/tests/auto/blackbox/testdata/enableExceptions/main.m b/tests/auto/blackbox/testdata/enableExceptions/main.m new file mode 100644 index 00000000..86b45fc3 --- /dev/null +++ b/tests/auto/blackbox/testdata/enableExceptions/main.m @@ -0,0 +1,5 @@ +#import + +int main() { + @throw [NSError new]; +} diff --git a/tests/auto/blackbox/testdata/enableExceptions/none.qbs b/tests/auto/blackbox/testdata/enableExceptions/none.qbs new file mode 100644 index 00000000..8fb05247 --- /dev/null +++ b/tests/auto/blackbox/testdata/enableExceptions/none.qbs @@ -0,0 +1,8 @@ +CppApplication { + files: ["emptymain.cpp"] + + Group { + condition: qbs.targetOS.contains("darwin") + files: ["empty.m", "empty.mm"] + } +} diff --git a/tests/auto/blackbox/testdata/enableRtti/main.cpp b/tests/auto/blackbox/testdata/enableRtti/main.cpp new file mode 100644 index 00000000..4011cf82 --- /dev/null +++ b/tests/auto/blackbox/testdata/enableRtti/main.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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 + +#if defined(__GNUC__) && !(defined(__cpp_rtti) || defined(__GXX_RTTI)) +#error RTTI is disabled! +#endif + +#if defined(_MSC_VER) && !defined (_CPPRTTI) +#error RTTI is disabled! +#endif + +class I { +public: + virtual ~I() { } + virtual void x() { } +}; + +class A : public I { + void x() override { } +}; + +class B : public I { + void x() override { } +}; + +int main() { + const auto a = new A(); + B *b = dynamic_cast(a); + (void)b; + delete a; + return 0; +} diff --git a/tests/auto/blackbox/testdata/enableRtti/rtti.qbs b/tests/auto/blackbox/testdata/enableRtti/rtti.qbs new file mode 100644 index 00000000..85bfa9c1 --- /dev/null +++ b/tests/auto/blackbox/testdata/enableRtti/rtti.qbs @@ -0,0 +1,11 @@ +Project { + property bool treatAsObjcpp: false + CppApplication { + cpp.cxxLanguageVersion: "c++11" + cpp.treatWarningsAsErrors: true + Group { + files: ["main.cpp"] + fileTags: [project.treatAsObjcpp ? "objcpp" : "cpp"] + } + } +} diff --git a/tests/auto/blackbox/testdata/env-merging/env-merging.qbs b/tests/auto/blackbox/testdata/env-merging/env-merging.qbs new file mode 100644 index 00000000..b927003e --- /dev/null +++ b/tests/auto/blackbox/testdata/env-merging/env-merging.qbs @@ -0,0 +1,29 @@ +Project { + CppApplication { + name: "tool" + files: "main.c" + } + + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: "p" + type: "custom" + Depends { name: "tool" } + Rule { + inputsFromDependencies: "application" + outputFileTags: "custom" + prepare: { + var cmd = new Command(input.filePath, []); + cmd.description = "running tool"; + cmd.environment = ["PATH=/opt/tool/bin"]; + return cmd; + } + } + } +} + diff --git a/tests/auto/blackbox/testdata/env-merging/main.c b/tests/auto/blackbox/testdata/env-merging/main.c new file mode 100644 index 00000000..d931c292 --- /dev/null +++ b/tests/auto/blackbox/testdata/env-merging/main.c @@ -0,0 +1,8 @@ +#include +#include + +int main() +{ + printf("PATH=%s", getenv("PATH")); + return 0; +} diff --git a/tests/auto/blackbox/testdata/env-normalization/env-normalization.qbs b/tests/auto/blackbox/testdata/env-normalization/env-normalization.qbs new file mode 100644 index 00000000..3b242aeb --- /dev/null +++ b/tests/auto/blackbox/testdata/env-normalization/env-normalization.qbs @@ -0,0 +1,11 @@ +import qbs.Environment + +Product { + Probe { + id: dummy + property var env: Environment.currentEnv() + configure: { + console.info(JSON.stringify(env)); + } + } +} diff --git a/tests/auto/blackbox/testdata/erroneous/nonexistentWorkingDir/nonexistentWorkingDir.qbs b/tests/auto/blackbox/testdata/erroneous/nonexistentWorkingDir/nonexistentWorkingDir.qbs new file mode 100644 index 00000000..6a763e1a --- /dev/null +++ b/tests/auto/blackbox/testdata/erroneous/nonexistentWorkingDir/nonexistentWorkingDir.qbs @@ -0,0 +1,18 @@ +Application { + name: "kaputt" + type: ["nutritious"] + Rule { + multiplex: true + Artifact { + filePath: "Stulle" + fileTags: ["nutritious"] + } + prepare: { + var cmd = new Command("ls"); + cmd.workingDirectory = "/does/not/exist"; + cmd.silent = true; + return [cmd]; + } + } +} + diff --git a/tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-filePath/main.cpp b/tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-filePath/main.cpp new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-filePath/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-filePath/outputArtifacts-missing-filePath.qbs b/tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-filePath/outputArtifacts-missing-filePath.qbs new file mode 100644 index 00000000..047ddb86 --- /dev/null +++ b/tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-filePath/outputArtifacts-missing-filePath.qbs @@ -0,0 +1,16 @@ +CppApplication { + type: base.concat("txt") + files : ["main.cpp"] + Rule { + inputs: ["application"] + outputArtifacts: [{ + fileTags: ["txt"] + }] + outputFileTags: ["txt"] + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-fileTags/main.cpp b/tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-fileTags/main.cpp new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-fileTags/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-fileTags/outputArtifacts-missing-fileTags.qbs b/tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-fileTags/outputArtifacts-missing-fileTags.qbs new file mode 100644 index 00000000..8b7b9482 --- /dev/null +++ b/tests/auto/blackbox/testdata/erroneous/outputArtifacts-missing-fileTags/outputArtifacts-missing-fileTags.qbs @@ -0,0 +1,16 @@ +CppApplication { + type: base.concat("txt") + files : ["main.cpp"] + Rule { + inputs: ["application"] + outputArtifacts: [{ + filePath: input.completeBaseName + ".txt" + }] + outputFileTags: ["txt"] + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/erroneous/tag-mismatch/tag-mismatch.qbs b/tests/auto/blackbox/testdata/erroneous/tag-mismatch/tag-mismatch.qbs new file mode 100644 index 00000000..51ea28e9 --- /dev/null +++ b/tests/auto/blackbox/testdata/erroneous/tag-mismatch/tag-mismatch.qbs @@ -0,0 +1,35 @@ +Product { + name: "p" + type: "p_type" + Rule { + multiplex: true + outputFileTags: ["x"] + outputArtifacts: [{filePath: "dummy1", fileTags: ["x","y","z"]}] + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { }; + return cmd; + } + } + Rule { + inputs: ["y"] + Artifact { filePath: "dummy2"; fileTags: "p_type" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { }; + return cmd; + } + } + Rule { + inputs: ["x"] + Artifact { filePath: "dummy3"; fileTags: "p_type" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/erroneous/texttemplate-unknown-placeholder/boom.txt.in b/tests/auto/blackbox/testdata/erroneous/texttemplate-unknown-placeholder/boom.txt.in new file mode 100644 index 00000000..359e856e --- /dev/null +++ b/tests/auto/blackbox/testdata/erroneous/texttemplate-unknown-placeholder/boom.txt.in @@ -0,0 +1 @@ +Boom! shake-shake-shake the ${what}! diff --git a/tests/auto/blackbox/testdata/erroneous/texttemplate-unknown-placeholder/texttemplate-unknown-placeholder.qbs b/tests/auto/blackbox/testdata/erroneous/texttemplate-unknown-placeholder/texttemplate-unknown-placeholder.qbs new file mode 100644 index 00000000..01963292 --- /dev/null +++ b/tests/auto/blackbox/testdata/erroneous/texttemplate-unknown-placeholder/texttemplate-unknown-placeholder.qbs @@ -0,0 +1,6 @@ +Product { + type: ["text"] + Depends { name: "texttemplate" } + texttemplate.dict: ({ wat: "room" }) // typo in key name + files: [ "boom.txt.in" ] +} diff --git a/tests/auto/blackbox/testdata/error-info/error-info.qbs b/tests/auto/blackbox/testdata/error-info/error-info.qbs new file mode 100644 index 00000000..b8f42b4b --- /dev/null +++ b/tests/auto/blackbox/testdata/error-info/error-info.qbs @@ -0,0 +1,71 @@ +import "helper.js" as Helper + +Project { + property bool fail1: false + property bool fail2: false + property bool fail3: false + property bool fail4: false + property bool fail5: false + property bool fail6: false + property bool fail7: false + + Product { + name: "myproduct" + type: ["foo", "bar"] + + Rule { + multiplex: true + + Artifact { + fileTags: ["foo"] + filePath: { + var path = "foo"; + if (project.fail1) + throw "fail1"; + return path; + } + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.sourceCode = function () { + + }; + cmd.silent = true; + if (project.fail2) + generate.an.error; + if (project.fail6) + Helper.doSomethingEvil(); + return cmd; + } + } + + Rule { + multiplex: true + + outputFileTags: ["bar"] + outputArtifacts: { + var list = []; + list.push({ fileTags: ["bar"], filePath: "bar" }); + if (project.fail3) + throw "fail3"; + if (project.fail5) + Helper.doSomethingBad(); + return list; + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.fail7 = project.fail7; + cmd.sourceCode = function () { + if (fail7) + will.fail; + }; + cmd.silent = true; + if (project.fail4) + generate.an.error; + return cmd; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/error-info/helper.js b/tests/auto/blackbox/testdata/error-info/helper.js new file mode 100644 index 00000000..18323df4 --- /dev/null +++ b/tests/auto/blackbox/testdata/error-info/helper.js @@ -0,0 +1,9 @@ +var x; + +function doSomethingBad() { + nothinghere.works; +} + +function doSomethingEvil() { + throw "OUCH!"; +} diff --git a/tests/auto/blackbox/testdata/escaped-linker-flags/escaped-linker-flags.qbs b/tests/auto/blackbox/testdata/escaped-linker-flags/escaped-linker-flags.qbs new file mode 100644 index 00000000..f48bf8d1 --- /dev/null +++ b/tests/auto/blackbox/testdata/escaped-linker-flags/escaped-linker-flags.qbs @@ -0,0 +1,13 @@ +CppApplication { + name: "app" + property bool escapeLinkerFlags + Properties { + condition: escapeLinkerFlags + cpp.linkerFlags: ["-Wl,-s"] + } + Properties { + condition: !escapeLinkerFlags + cpp.linkerFlags: ["-s"] + } + files: ["main.cpp"] +} diff --git a/tests/auto/blackbox/testdata/escaped-linker-flags/main.cpp b/tests/auto/blackbox/testdata/escaped-linker-flags/main.cpp new file mode 100644 index 00000000..8b8d58de --- /dev/null +++ b/tests/auto/blackbox/testdata/escaped-linker-flags/main.cpp @@ -0,0 +1 @@ +int main() { } diff --git a/tests/auto/blackbox/testdata/explicitly-depends-on/explicitly-depends-on.qbs b/tests/auto/blackbox/testdata/explicitly-depends-on/explicitly-depends-on.qbs new file mode 100644 index 00000000..4f190c23 --- /dev/null +++ b/tests/auto/blackbox/testdata/explicitly-depends-on/explicitly-depends-on.qbs @@ -0,0 +1,123 @@ +import qbs.File +import qbs.TextFile + +Project { + // Cases: + // step1 + in-product final -> step2 -> step3 -> final => rule cycle + // step1 + dependency final -> step2 -> step3 -> final => ok + // step1 + module filesAreTargets final -> step2 -> step3 -> final => ok + + name: "proj1" + property bool useModule: false + + Product { + name: "prod1" + type: "final" + property bool useExplicitlyDependsOn: false + property bool useExplicitlyDependsOnFromDependencies: false + + Depends { + condition: !project.useModule + name: "prod2" + } + Depends { + condition: project.useModule + name: "module1" + } + + Group { + files: ["step1.txt"] + fileTags: ["step1"] + } + + Rule { + inputs: ["step3"] + outputFileTags: ["final"] + Artifact { + filePath: "final.txt" + fileTags: ["final"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "step3 -> final"; + cmd.sourceCode = function() { + File.copy(input.filePath, output.filePath); + }; + return cmd; + } + } + + Rule { + inputs: ["step2"] + outputFileTags: ["step3"] + Artifact { + filePath: "step3.txt" + fileTags: ["step3"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "step2 -> step3"; + cmd.sourceCode = function() { + File.copy(input.filePath, output.filePath); + }; + return cmd; + } + } + + Rule { + inputs: ["step1"] + outputFileTags: ["step2"] + Artifact { + filePath: "step2.txt" + fileTags: ["step2"] + } + + Properties { + condition: useExplicitlyDependsOn + explicitlyDependsOn: ["final"] + } + + Properties { + condition: useExplicitlyDependsOnFromDependencies + explicitlyDependsOnFromDependencies: ["final"] + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "step1 -> step2"; + cmd.sourceCode = function() { + console.info("Using explicitlyDependsOnArtifact: " + + explicitlyDependsOn["final"][0].fileName) + File.copy(input.filePath, output.filePath); + }; + return cmd; + } + } + } + + Product { + name: "prod2" + type: "final" + condition: !project.useModule + + Rule { + multiplex: true + requiresInputs: false + outputFileTags: ["final"] + Artifact { + filePath: "product-fish.txt" + fileTags: ["final"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating 'product-fish.txt' tagged with 'final'"; + cmd.sourceCode = function() { + var file = new TextFile(output.filePath, TextFile.ReadWrite); + file.write("Lots of fish."); + file.close() + }; + return cmd; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/explicitly-depends-on/modules/module1/module-fish.txt b/tests/auto/blackbox/testdata/explicitly-depends-on/modules/module1/module-fish.txt new file mode 100644 index 00000000..10b4b7cb --- /dev/null +++ b/tests/auto/blackbox/testdata/explicitly-depends-on/modules/module1/module-fish.txt @@ -0,0 +1 @@ +Module fish diff --git a/tests/auto/blackbox/testdata/explicitly-depends-on/modules/module1/module1.qbs b/tests/auto/blackbox/testdata/explicitly-depends-on/modules/module1/module1.qbs new file mode 100644 index 00000000..b710c4e2 --- /dev/null +++ b/tests/auto/blackbox/testdata/explicitly-depends-on/modules/module1/module1.qbs @@ -0,0 +1,7 @@ +Module { + Group { + filesAreTargets: true + fileTags: ["final"] + files: ["module-fish.txt"] + } +} diff --git a/tests/auto/blackbox/testdata/explicitly-depends-on/step1.txt b/tests/auto/blackbox/testdata/explicitly-depends-on/step1.txt new file mode 100644 index 00000000..45b983be --- /dev/null +++ b/tests/auto/blackbox/testdata/explicitly-depends-on/step1.txt @@ -0,0 +1 @@ +hi diff --git a/tests/auto/blackbox/testdata/export-rule/blubber.cpp b/tests/auto/blackbox/testdata/export-rule/blubber.cpp new file mode 100644 index 00000000..e7beea33 --- /dev/null +++ b/tests/auto/blackbox/testdata/export-rule/blubber.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void justSomeFunction() {} diff --git a/tests/auto/blackbox/testdata/export-rule/export-rule.qbs b/tests/auto/blackbox/testdata/export-rule/export-rule.qbs new file mode 100644 index 00000000..29899e72 --- /dev/null +++ b/tests/auto/blackbox/testdata/export-rule/export-rule.qbs @@ -0,0 +1,39 @@ +import qbs.File + +Project { + Application { + name: "MyApp" + files: ["myapp.blubb"] + Depends { name: "blubber" } + } + StaticLibrary { + name: "blubber" + files: ["blubber.cpp"] + Depends { name: "cpp" } + Export { + Depends { name: "cpp" } + property bool enableTagger + property string description: "Creating C++ source file."; + FileTagger { + condition: enableTagger + patterns: ["*.blubb"] + fileTags: ["blubb"] + } + Rule { + inputs: ["blubb"] + Artifact { + filePath: input.completeBaseName + ".cpp" + fileTags: ["cpp"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = product.blubber.description; + cmd.sourceCode = function() { + File.copy(input.filePath, output.filePath); + } + return [cmd]; + } + } + } + } +} diff --git a/tests/auto/blackbox/testdata/export-rule/myapp.blubb b/tests/auto/blackbox/testdata/export-rule/myapp.blubb new file mode 100644 index 00000000..ad87b5e8 --- /dev/null +++ b/tests/auto/blackbox/testdata/export-rule/myapp.blubb @@ -0,0 +1,8 @@ +extern void justSomeFunction(); + +int main() +{ + justSomeFunction(); + return 0; +} + diff --git a/tests/auto/blackbox/testdata/export-to-outside-searchpath/export-to-outside-searchpath.qbs b/tests/auto/blackbox/testdata/export-to-outside-searchpath/export-to-outside-searchpath.qbs new file mode 100644 index 00000000..42e17486 --- /dev/null +++ b/tests/auto/blackbox/testdata/export-to-outside-searchpath/export-to-outside-searchpath.qbs @@ -0,0 +1,19 @@ +Project { + Project { + qbsSearchPaths: ["qbs-resources"] + Product { + name: "otherProduct" + Depends { name: "dep" } + } + Product { + name: "dep" + Export { Depends { name: "aModule" } } + } + } + Project { + Product { + name: "theProduct" + Depends { name: "dep" } + } + } +} diff --git a/tests/auto/blackbox/testdata/export-to-outside-searchpath/qbs-resources/modules/aModule/aModule.qbs b/tests/auto/blackbox/testdata/export-to-outside-searchpath/qbs-resources/modules/aModule/aModule.qbs new file mode 100644 index 00000000..84957060 --- /dev/null +++ b/tests/auto/blackbox/testdata/export-to-outside-searchpath/qbs-resources/modules/aModule/aModule.qbs @@ -0,0 +1,2 @@ +Module { +} diff --git a/tests/auto/blackbox/testdata/exported-dependency-in-disabled-product/exported-dependency-in-disabled-product.qbs b/tests/auto/blackbox/testdata/exported-dependency-in-disabled-product/exported-dependency-in-disabled-product.qbs new file mode 100644 index 00000000..ac6022b9 --- /dev/null +++ b/tests/auto/blackbox/testdata/exported-dependency-in-disabled-product/exported-dependency-in-disabled-product.qbs @@ -0,0 +1,17 @@ +Project { + Application { + name: "app" + Depends { name: "dep"; required: false } + files: "main.cpp" + } + Product { + name: "dep" + condition: eval(conditionString) + property string conditionString + Depends { name: "nosuchmodule"; required: false } + Depends { name: "broken"; required: false } + Export { + Depends { name: "cpp" } + } + } +} diff --git a/tests/auto/blackbox/testdata/exported-dependency-in-disabled-product/main.cpp b/tests/auto/blackbox/testdata/exported-dependency-in-disabled-product/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/exported-dependency-in-disabled-product/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/exported-dependency-in-disabled-product/modules/broken/broken.qbs b/tests/auto/blackbox/testdata/exported-dependency-in-disabled-product/modules/broken/broken.qbs new file mode 100644 index 00000000..328e5f55 --- /dev/null +++ b/tests/auto/blackbox/testdata/exported-dependency-in-disabled-product/modules/broken/broken.qbs @@ -0,0 +1,3 @@ +Module { + validate: { throw "broken!"; } +} diff --git a/tests/auto/blackbox/testdata/exported-property-in-disabled-product/exported-property-in-disabled-product.qbs b/tests/auto/blackbox/testdata/exported-property-in-disabled-product/exported-property-in-disabled-product.qbs new file mode 100644 index 00000000..ff8beb34 --- /dev/null +++ b/tests/auto/blackbox/testdata/exported-property-in-disabled-product/exported-property-in-disabled-product.qbs @@ -0,0 +1,18 @@ +Project { + CppApplication { + name: "app" + Depends { name: "dep"; required: false } + files: "main.cpp" + } + Product { + name: "dep" + condition: eval(conditionString) + property string conditionString + Depends { name: "nosuchmodule"; required: false } + Depends { name: "broken"; required: false } + Export { + Depends { name: "cpp" } + cpp.dynamicLibraries: ["nosuchlib"] + } + } +} diff --git a/tests/auto/blackbox/testdata/exported-property-in-disabled-product/main.cpp b/tests/auto/blackbox/testdata/exported-property-in-disabled-product/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/exported-property-in-disabled-product/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/exported-property-in-disabled-product/modules/broken/broken.qbs b/tests/auto/blackbox/testdata/exported-property-in-disabled-product/modules/broken/broken.qbs new file mode 100644 index 00000000..328e5f55 --- /dev/null +++ b/tests/auto/blackbox/testdata/exported-property-in-disabled-product/modules/broken/broken.qbs @@ -0,0 +1,3 @@ +Module { + validate: { throw "broken!"; } +} diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib.pc b/tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib.pc new file mode 100644 index 00000000..6a83a6b3 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib.pc @@ -0,0 +1,10 @@ +prefix=/opt/the firstlib + +Name: TheFirstLib +Description: TheFirstLib +Version: 1.0 +URL: http://www.example.com/thefirstlib +Cflags: -DTheFirstLib -I"${prefix}/include" -pthread -DHAVE_INDUSTRIAL_STRENGTH_HAIR_DRYER -I/otherdir/include1 -I/otherdir/include2 +Libs: -L"${prefix}/lib" -lTheFirstLib -pthread +Requires: Qt5Core +Requires.private: SomeHelper diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib_windows.pc b/tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib_windows.pc new file mode 100644 index 00000000..3c730a0a --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/TheFirstLib_windows.pc @@ -0,0 +1,10 @@ +prefix=/opt/the firstlib + +Name: TheFirstLib +Description: TheFirstLib +Version: 1.0 +URL: http://www.example.com/thefirstlib +Cflags: -DTheFirstLib -I"${prefix}/include" -DHAVE_INDUSTRIAL_STRENGTH_HAIR_DRYER -I/otherdir/include1 -I/otherdir/include2 +Libs: -L"${prefix}/lib" -lTheFirstLib +Requires: Qt5Core +Requires.private: SomeHelper diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/TheSecondLib.pc b/tests/auto/blackbox/testdata/exports-pkgconfig/TheSecondLib.pc new file mode 100644 index 00000000..6c54b451 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/TheSecondLib.pc @@ -0,0 +1,9 @@ +config1=a b +config2=c + +Name: TheSecondLib +Description: The second lib +Version: 2.0 +Cflags: -I/opt/thesecondlib/include +Libs: -L/opt/thesecondlib/lib -lTheSecondLib +Requires: TheFirstLib diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/boringstaticlib.cpp b/tests/auto/blackbox/testdata/exports-pkgconfig/boringstaticlib.cpp new file mode 100644 index 00000000..559a0ecf --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/boringstaticlib.cpp @@ -0,0 +1 @@ +int calculateLuckyNumber() { return 12 * 13; } diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/exports-pkgconfig.qbs b/tests/auto/blackbox/testdata/exports-pkgconfig/exports-pkgconfig.qbs new file mode 100644 index 00000000..96d1cabb --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/exports-pkgconfig.qbs @@ -0,0 +1,128 @@ +import qbs.FileInfo + +Project { + Product { + name: "dummy" + Export { Depends { name: "TheFirstLib" } } + } + + Product { + name: "SomeHelper" + Depends { name: "Exporter.pkgconfig" } + Exporter.pkgconfig.versionEntry: "1.0" + } + StaticLibrary { + Depends { name: "cpp" } + name: "BoringStaticLib" + files: ["boringstaticlib.cpp"] + Export { + Depends { name: "cpp" } + cpp.defines: ["HAVE_INDUSTRIAL_STRENGTH_HAIR_DRYER"] + } + } + DynamicLibrary { + name: "TheFirstLib" + version: "1.0" + + Depends { name: "SomeHelper" } + Depends { name: "Exporter.pkgconfig" } + Exporter.pkgconfig.excludedDependencies: ["Qt.core", "helper3"] + Exporter.pkgconfig.requiresEntry: "Qt5Core" + Exporter.pkgconfig.urlEntry: "http://www.example.com/thefirstlib" + + Depends { name: "cpp" } + cpp.defines: ["FIRSTLIB"] + + qbs.installPrefix: "/opt/the firstlib" + + Export { + prefixMapping: [{prefix: "/somedir", replacement: "/otherdir"}] + Depends { name: "BoringStaticLib" } + Depends { name: "cpp" } + Depends { name: "Qt.core"; required: false } + Depends { name: "helper1" } + Depends { name: "helper3" } + property bool someCondition: qbs.hostOS.contains("windows") // hostOS for easier testing + property bool someOtherCondition: someCondition + Properties { + condition: !someOtherCondition + cpp.driverFlags: ["-pthread"] + } + cpp.defines: product.name + cpp.includePaths: [FileInfo.joinPaths(product.qbs.installPrefix, "include")] + Qt.core.mocName: "muck" + } + + Group { + fileTagsFilter: ["dynamiclibrary", "dynamiclibrary_import"] + qbs.install: true + qbs.installDir: "lib" + } + + Group { + name: "api_headers" + files: ["firstlib.h"] + qbs.install: true + qbs.installDir: "include" + } + + files: ["firstlib.cpp"] + } + DynamicLibrary { + name: "TheSecondLib" + version: "2.0" + + Depends { name: "Exporter.pkgconfig" } + Exporter.pkgconfig.descriptionEntry: "The second lib" + Exporter.pkgconfig.transformFunction: (function(product, moduleName, propertyName, value) { + if (moduleName === "cpp" && propertyName === "includePaths") + return value.filter(function(p) { return p !== product.sourceDirectory; }); + return value; + }) + Exporter.pkgconfig.customVariables: ({config1: "a b", config2: "c"}) + + Depends { name: "cpp" } + cpp.defines: ["SECONDLIB"] + + qbs.installPrefix: "" + + Depends { name: "TheFirstLib" } + + Export { + Depends { name: "TheFirstLib" } + Depends { name: "dummy" } + Depends { name: "cpp" } + cpp.includePaths: [ + "/opt/thesecondlib/include", + product.sourceDirectory, + importingProduct.buildDirectory + ] + property string hurz: importingProduct.name + cpp.defines: hurz.toUpperCase() + + Rule { + property int n: 5 + Artifact { + filePath: "dummy" + fileTags: ["d1", "d2"] + cpp.warningsAreErrors: true + } + } + } + + Group { + fileTagsFilter: ["dynamiclibrary", "dynamiclibrary_import"] + qbs.install: true + qbs.installDir: "/opt/thesecondlib/lib" + } + + Group { + name: "api_headers" + files: ["secondlib.h"] + qbs.install: true + qbs.installDir: "/opt/thesecondlib/include" + } + + files: ["secondlib.cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.cpp b/tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.cpp new file mode 100644 index 00000000..ab48afc9 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.cpp @@ -0,0 +1,3 @@ +#include "firstlib.h" + +void firstLib() { } diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.h b/tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.h new file mode 100644 index 00000000..1ccfb086 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/firstlib.h @@ -0,0 +1,9 @@ +#include "../dllexport.h" + +#ifdef FIRSTLIB +# define FIRSTLIB_EXPORT DLL_EXPORT +#else +# define FIRSTLIB_EXPORT DLL_IMPORT +#endif + +FIRSTLIB_EXPORT void firstLib(); diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper1/helper1.qbs b/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper1/helper1.qbs new file mode 100644 index 00000000..c898e91d --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper1/helper1.qbs @@ -0,0 +1,5 @@ +Module { + Depends { name: "cpp" } + Depends { name: "helper2" } + cpp.includePaths: "/somedir/include1" +} diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper2/helper2.qbs b/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper2/helper2.qbs new file mode 100644 index 00000000..2dbdfdb0 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper2/helper2.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "cpp" } + cpp.includePaths: "/somedir/include2" +} diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper3/helper3.qbs b/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper3/helper3.qbs new file mode 100644 index 00000000..57e65f2f --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/modules/helper3/helper3.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "cpp" } + cpp.includePaths: "/somedir/include3" +} diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.cpp b/tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.cpp new file mode 100644 index 00000000..e1782f57 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.cpp @@ -0,0 +1,7 @@ +#include "secondlib.h" + +#ifndef HAVE_INDUSTRIAL_STRENGTH_HAIR_DRYER +# error I CANT LIVE WITHOUT IT! +#endif + +void secondLib() { } diff --git a/tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.h b/tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.h new file mode 100644 index 00000000..78b52f7f --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-pkgconfig/secondlib.h @@ -0,0 +1,9 @@ +#include "../dllexport.h" + +#ifdef SECONDLIB +# define SECONDLIB_EXPORT DLL_EXPORT +#else +# define SECONDLIB_EXPORT DLL_IMPORT +#endif + +SECONDLIB_EXPORT void secondLib(); diff --git a/tests/auto/blackbox/testdata/exports-qbs/consumer.cpp b/tests/auto/blackbox/testdata/exports-qbs/consumer.cpp new file mode 100644 index 00000000..25338b61 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-qbs/consumer.cpp @@ -0,0 +1,6 @@ +void helper(); + +int main() +{ + helper(); +} diff --git a/tests/auto/blackbox/testdata/exports-qbs/consumer.qbs b/tests/auto/blackbox/testdata/exports-qbs/consumer.qbs new file mode 100644 index 00000000..79ac50e0 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-qbs/consumer.qbs @@ -0,0 +1,27 @@ +CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: "consumer" + qbsSearchPaths: "default/install-root/usr/qbs" + property string outTag: "cpp" + Depends { name: "MyLib" } + Depends { name: "MyTool" } + files: ["consumer.cpp"] + cpp.defines: { + var defs = []; + if (MyLib.config.feature_x) + defs.push("FEATURE_X"); + if (MyLib.config.feature_y) + defs.push("FEATURE_Y"); + return defs; + } + + Group { + files: ["helper.cpp.in"] + fileTags: ["cpp.in"] + } +} diff --git a/tests/auto/blackbox/testdata/exports-qbs/exports-qbs-products.qbs b/tests/auto/blackbox/testdata/exports-qbs/exports-qbs-products.qbs new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/exports-qbs/exports-qbs.qbs b/tests/auto/blackbox/testdata/exports-qbs/exports-qbs.qbs new file mode 100644 index 00000000..27e417ef --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-qbs/exports-qbs.qbs @@ -0,0 +1,16 @@ +import qbs.FileInfo + +Project { + property string installPrefix: "/usr" + qbsSearchPaths: sourceDirectory + Product { + name: "local" + Export { + property bool dummy + Depends { name: "cpp" } + cpp.includePaths: ["/somelocaldir/include"] + } + } + + references: ["consumer.qbs", "lib.qbs", "tool.qbs"] +} diff --git a/tests/auto/blackbox/testdata/exports-qbs/helper.cpp.in b/tests/auto/blackbox/testdata/exports-qbs/helper.cpp.in new file mode 100644 index 00000000..46e3e156 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-qbs/helper.cpp.in @@ -0,0 +1,14 @@ +#include + +#ifdef FEATURE_X +#error "feature x must not be present!" +#endif + +#ifndef FEATURE_Y +#error "feature y is required!" +#endif + +void helper() +{ + MyLib::f(); +} diff --git a/tests/auto/blackbox/testdata/exports-qbs/helper.js b/tests/auto/blackbox/testdata/exports-qbs/helper.js new file mode 100644 index 00000000..17c4e91c --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-qbs/helper.js @@ -0,0 +1 @@ +function toolInputs() { return ["cpp.in"]; } diff --git a/tests/auto/blackbox/testdata/exports-qbs/imports/Helper2/helper2.js b/tests/auto/blackbox/testdata/exports-qbs/imports/Helper2/helper2.js new file mode 100644 index 00000000..616615b1 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-qbs/imports/Helper2/helper2.js @@ -0,0 +1 @@ +var suffix = ".tool"; diff --git a/tests/auto/blackbox/testdata/exports-qbs/lib.qbs b/tests/auto/blackbox/testdata/exports-qbs/lib.qbs new file mode 100644 index 00000000..53a472db --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-qbs/lib.qbs @@ -0,0 +1,59 @@ +import qbs.FileInfo + +DynamicLibrary { + name: "MyLib" + multiplexByQbsProperties: ["buildVariants"] + aggregate: false + qbs.buildVariants: ["debug", "release"] + qbs.installPrefix: project.installPrefix + Depends { name: "cpp" } + Depends { name: "Exporter.qbs" } + Exporter.qbs.fileName: name + "_" + qbs.buildVariant + ".qbs" + Exporter.qbs.excludedDependencies: ["local"] + Exporter.qbs.additionalContent: " condition: qbs.buildVariant === '" + + qbs.buildVariant + "'" + property string headersInstallDir: "include" + cpp.defines: ["MYLIB_BUILD"] + cpp.variantSuffix: qbs.buildVariant === "debug" ? "d" : "" + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + files: ["mylib.cpp"] + property var config: ({feature_x: false, feature_y: true}) + Group { + name: "API headers" + files: ["mylib.h"] + qbs.install: true + qbs.installDir: headersInstallDir + } + install: true + installImportLib: true + installDir: "lib" + Group { + fileTagsFilter: ["Exporter.qbs.module"] + qbs.install: true + qbs.installDir: "qbs/modules/MyLib" + } + + Export { + Depends { name: "cpp" } + property string includeDir: product.sourceDirectory + property var config: product.config + Properties { + condition: true + cpp.includePaths: [includeDir] + cpp.dynamicLibraries: [] + } + cpp.dynamicLibraries: ["nosuchlib"] + Depends { name: "local" } + local.dummy: true + Properties { + condition: true + prefixMapping: [{ + prefix: includeDir, + replacement: FileInfo.joinPaths(qbs.installPrefix, product.headersInstallDir) + }] + } + } +} diff --git a/tests/auto/blackbox/testdata/exports-qbs/mylib.cpp b/tests/auto/blackbox/testdata/exports-qbs/mylib.cpp new file mode 100644 index 00000000..f3dc6a43 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-qbs/mylib.cpp @@ -0,0 +1,5 @@ +#include "mylib.h" + +namespace MyLib { +void f() {} +} diff --git a/tests/auto/blackbox/testdata/exports-qbs/mylib.h b/tests/auto/blackbox/testdata/exports-qbs/mylib.h new file mode 100644 index 00000000..9f5f8269 --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-qbs/mylib.h @@ -0,0 +1,17 @@ +#if defined(_WIN32) || defined(WIN32) +# define DLL_EXPORT __declspec(dllexport) +# define DLL_IMPORT __declspec(dllimport) +#else +# define DLL_EXPORT __attribute__((visibility("default"))) +# define DLL_IMPORT __attribute__((visibility("default"))) +# endif + +#ifdef MYLIB_BUILD +#define MYLIB_EXPORT DLL_EXPORT +#else +#define MYLIB_EXPORT DLL_IMPORT +#endif + +namespace MyLib { +MYLIB_EXPORT void f(); +} diff --git a/tests/auto/blackbox/testdata/exports-qbs/tool.cpp b/tests/auto/blackbox/testdata/exports-qbs/tool.cpp new file mode 100644 index 00000000..4657033f --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-qbs/tool.cpp @@ -0,0 +1,18 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + if (argc != 3) + return EXIT_FAILURE; + std::ifstream in(argv[1]); + if (!in) + return EXIT_FAILURE; + std::ofstream out(argv[2]); + if (!out) + return EXIT_FAILURE; + char ch; + while (in.get(ch)) + out.put(ch); + return in.eof() && out ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/tests/auto/blackbox/testdata/exports-qbs/tool.qbs b/tests/auto/blackbox/testdata/exports-qbs/tool.qbs new file mode 100644 index 00000000..655597cf --- /dev/null +++ b/tests/auto/blackbox/testdata/exports-qbs/tool.qbs @@ -0,0 +1,57 @@ +import qbs.FileInfo + +import "helper.js" as Helper +import Helper2 + +CppApplication { + name: "MyTool" + consoleApplication: true + property stringList toolTags: ["MyTool.tool"] + Depends { name: "Exporter.qbs" } + Exporter.qbs.artifactTypes: ["installable", "blubb"] + files: ["tool.cpp"] + install: true + qbs.installPrefix: project.installPrefix + Group { + files: ["helper.js"] + qbs.install: true + qbs.installDir: "qbs/modules/MyTool" + } + Group { + files: ["imports/Helper2/helper2.js"] + qbs.install: true + qbs.installDir: "qbs/imports/Helper2" + } + + Group { + fileTagsFilter: ["application"] + fileTags: toolTags + } + Group { + fileTagsFilter: ["Exporter.qbs.module"] + qbs.installDir: "qbs/modules/MyTool" + } + + Export { + property stringList toolTags: product.toolTags + property stringList outTags: [importingProduct.outTag] + property var helper2Obj: Helper2 + Rule { + inputs: Helper.toolInputs() + explicitlyDependsOnFromDependencies: toolTags + + outputFileTags: parent.outTags + outputArtifacts: [{ + filePath: FileInfo.completeBaseName(input.fileName) + + product.MyTool.helper2Obj.suffix, + fileTags: product.MyTool.outTags + }] + prepare: { + var cmd = new Command(explicitlyDependsOn["MyTool.tool"][0].filePath, + [input.filePath, output.filePath]); + cmd.description = input.fileName + " -> " + output.fileName; + return [cmd]; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/external-libs/external-libs.qbs b/tests/auto/blackbox/testdata/external-libs/external-libs.qbs new file mode 100644 index 00000000..619f4086 --- /dev/null +++ b/tests/auto/blackbox/testdata/external-libs/external-libs.qbs @@ -0,0 +1,33 @@ +import qbs.TextFile + +Project { + property string libDir: sourceDirectory + "/libs" + StaticLibrary { + name: "lib1" + destinationDirectory: project.libDir + Depends { name: "cpp" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + files: ["lib1.cpp"] + } + StaticLibrary { + name: "lib2" + destinationDirectory: project.libDir + Depends { name: "cpp" } + Depends { name: "lib1" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + files: ["lib2.cpp"] + } + CppApplication { + Depends { name: "lib1"; cpp.link: false } + Depends { name: "lib2"; cpp.link: false } + files: ["main.cpp"] + cpp.libraryPaths: [project.libDir] + cpp.staticLibraries: ["lib1", "lib2", "lib1"] + } +} diff --git a/tests/auto/blackbox/testdata/external-libs/lib1.cpp b/tests/auto/blackbox/testdata/external-libs/lib1.cpp new file mode 100644 index 00000000..bb69fa42 --- /dev/null +++ b/tests/auto/blackbox/testdata/external-libs/lib1.cpp @@ -0,0 +1 @@ +void func_lib1() { } diff --git a/tests/auto/blackbox/testdata/external-libs/lib2.cpp b/tests/auto/blackbox/testdata/external-libs/lib2.cpp new file mode 100644 index 00000000..e669a7f6 --- /dev/null +++ b/tests/auto/blackbox/testdata/external-libs/lib2.cpp @@ -0,0 +1,3 @@ +void func_lib1(); + +void func_lib2() { func_lib1(); } diff --git a/tests/auto/blackbox/testdata/external-libs/main.cpp b/tests/auto/blackbox/testdata/external-libs/main.cpp new file mode 100644 index 00000000..9a6eab23 --- /dev/null +++ b/tests/auto/blackbox/testdata/external-libs/main.cpp @@ -0,0 +1,6 @@ +void func_lib2(); + +int main() +{ + func_lib2(); +} diff --git a/tests/auto/blackbox/testdata/fallback-module-provider/fallback-module-provider.qbs b/tests/auto/blackbox/testdata/fallback-module-provider/fallback-module-provider.qbs new file mode 100644 index 00000000..a798e15b --- /dev/null +++ b/tests/auto/blackbox/testdata/fallback-module-provider/fallback-module-provider.qbs @@ -0,0 +1,8 @@ +CppApplication { + name: "p" + property bool fallbacksEnabled + Depends { name: "pkgconfig"; required: false } + Depends { name: "qbsmetatestmodule"; required: false; enableFallback: fallbacksEnabled } + property bool dummy: { console.info("pkg-config present: " + pkgconfig.present); } + files: "main.cpp" +} diff --git a/tests/auto/blackbox/testdata/fallback-module-provider/libdir/qbsmetatestmodule.pc b/tests/auto/blackbox/testdata/fallback-module-provider/libdir/qbsmetatestmodule.pc new file mode 100644 index 00000000..ae4daba8 --- /dev/null +++ b/tests/auto/blackbox/testdata/fallback-module-provider/libdir/qbsmetatestmodule.pc @@ -0,0 +1,5 @@ +Name: qbsmetatestmodule +Description: just a test +Version: 0.0.1 + +Cflags: -DTHE_MAGIC_DEFINE diff --git a/tests/auto/blackbox/testdata/fallback-module-provider/main.cpp b/tests/auto/blackbox/testdata/fallback-module-provider/main.cpp new file mode 100644 index 00000000..442b755b --- /dev/null +++ b/tests/auto/blackbox/testdata/fallback-module-provider/main.cpp @@ -0,0 +1,5 @@ +#ifndef THE_MAGIC_DEFINE +#error "missing the magic define" +#endif + +int main() {} diff --git a/tests/auto/blackbox/testdata/fileDependencies/awesomelib/awesome.h b/tests/auto/blackbox/testdata/fileDependencies/awesomelib/awesome.h new file mode 100644 index 00000000..4633f974 --- /dev/null +++ b/tests/auto/blackbox/testdata/fileDependencies/awesomelib/awesome.h @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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 +#include "magnificent.h" + +void doAwesomeStuff() +{ + printf(magnificentMessage); +} + diff --git a/tests/auto/blackbox/testdata/fileDependencies/awesomelib/magnificent.h b/tests/auto/blackbox/testdata/fileDependencies/awesomelib/magnificent.h new file mode 100644 index 00000000..3b1f4692 --- /dev/null +++ b/tests/auto/blackbox/testdata/fileDependencies/awesomelib/magnificent.h @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef MAGNIFICENT_H +#define MAGNIFICENT_H + +const char magnificentMessage[] = "Just. Wow."; + +#endif // MAGNIFICENT_H diff --git a/tests/auto/blackbox/testdata/fileDependencies/fileDependencies.qbs b/tests/auto/blackbox/testdata/fileDependencies/fileDependencies.qbs new file mode 100644 index 00000000..49f41380 --- /dev/null +++ b/tests/auto/blackbox/testdata/fileDependencies/fileDependencies.qbs @@ -0,0 +1,15 @@ +Project { + Product { + type: "application" + consoleApplication: true + name: "myapp" + Depends { name: "cpp" } + cpp.includePaths: ["awesomelib"] + files: ["src/narf.h", "src/narf.cpp", "src/zort.cpp"] + // Group { + // fileTagsFilter: product.type + // qbs.install: true + // } + } +} + diff --git a/tests/auto/blackbox/testdata/fileDependencies/src/narf.cpp b/tests/auto/blackbox/testdata/fileDependencies/src/narf.cpp new file mode 100644 index 00000000..54f559ca --- /dev/null +++ b/tests/auto/blackbox/testdata/fileDependencies/src/narf.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 + +void doStuff() +{ + doAwesomeStuff(); +} + diff --git a/tests/auto/blackbox/testdata/fileDependencies/src/narf.h b/tests/auto/blackbox/testdata/fileDependencies/src/narf.h new file mode 100644 index 00000000..d247cfd1 --- /dev/null +++ b/tests/auto/blackbox/testdata/fileDependencies/src/narf.h @@ -0,0 +1,30 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void doStuff(); + diff --git a/tests/auto/blackbox/testdata/fileDependencies/src/zort.cpp b/tests/auto/blackbox/testdata/fileDependencies/src/zort.cpp new file mode 100644 index 00000000..1f9bde90 --- /dev/null +++ b/tests/auto/blackbox/testdata/fileDependencies/src/zort.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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 "narf.h" + +int main() +{ + doStuff(); + return 0; +} + diff --git a/tests/auto/blackbox/testdata/filetagsfilter-merging/MyApplication.qbs b/tests/auto/blackbox/testdata/filetagsfilter-merging/MyApplication.qbs new file mode 100644 index 00000000..890fc51c --- /dev/null +++ b/tests/auto/blackbox/testdata/filetagsfilter-merging/MyApplication.qbs @@ -0,0 +1,9 @@ +CppApplication { + consoleApplication: true + Group { + fileTagsFilter: "application" + qbs.install:true + qbs.installPrefix: product.name + qbs.installDir: "wrong" + } +} diff --git a/tests/auto/blackbox/testdata/filetagsfilter-merging/filetagsfilter-merging.qbs b/tests/auto/blackbox/testdata/filetagsfilter-merging/filetagsfilter-merging.qbs new file mode 100644 index 00000000..eee20782 --- /dev/null +++ b/tests/auto/blackbox/testdata/filetagsfilter-merging/filetagsfilter-merging.qbs @@ -0,0 +1,28 @@ +import qbs.TextFile + +MyApplication { + name: "myapp" + type: base.concat("extra-output") + files: "main.cpp" + Group { + fileTagsFilter: "application" + qbs.installDir: "binDir" + fileTags: "extra-input" + } + Rule { + inputs: "extra-input" + Artifact { + filePath: input.baseName + ".txt" + fileTags: "extra-output" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + } + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/filetagsfilter-merging/main.cpp b/tests/auto/blackbox/testdata/filetagsfilter-merging/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/filetagsfilter-merging/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/find/find-cli.qbs b/tests/auto/blackbox/testdata/find/find-cli.qbs new file mode 100644 index 00000000..20d94803 --- /dev/null +++ b/tests/auto/blackbox/testdata/find/find-cli.qbs @@ -0,0 +1,33 @@ +import qbs.TextFile + +Product { + Depends { name: "cli"; required: false } + type: ["json"] + Rule { + multiplex: true + Artifact { + filePath: ["cli.json"] + fileTags: ["json"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = output.filePath; + cmd.sourceCode = function() { + var tools = {}; + if (product.moduleProperty("cli", "present")) + tools["toolchainInstallPath"] = product.moduleProperty("cli", + "toolchainInstallPath"); + + var tf; + try { + tf = new TextFile(output.filePath, TextFile.WriteOnly); + tf.writeLine(JSON.stringify(tools, undefined, 4)); + } finally { + if (tf) + tf.close(); + } + }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/freedesktop/freedesktop.qbs b/tests/auto/blackbox/testdata/freedesktop/freedesktop.qbs new file mode 100644 index 00000000..60c3d304 --- /dev/null +++ b/tests/auto/blackbox/testdata/freedesktop/freedesktop.qbs @@ -0,0 +1,25 @@ +import qbs 1.0 + +Project { + CppApplication { + name: "main" + install: true + files: [ + "main.cpp", + "myapp.desktop", + "myapp.appdata.xml", + ] + + Depends { name: "freedesktop" } + + freedesktop.name: "My App" + freedesktop.desktopKeys: ({ + 'Icon': "myapp.png" + }) + + Group { + files: "myapp.png" + fileTags: "freedesktop.appIcon" + } + } +} diff --git a/tests/auto/blackbox/testdata/freedesktop/main.cpp b/tests/auto/blackbox/testdata/freedesktop/main.cpp new file mode 100644 index 00000000..905869df --- /dev/null +++ b/tests/auto/blackbox/testdata/freedesktop/main.cpp @@ -0,0 +1,4 @@ +int main() +{ + return 0; +} diff --git a/tests/auto/blackbox/testdata/freedesktop/myapp.appdata.xml b/tests/auto/blackbox/testdata/freedesktop/myapp.appdata.xml new file mode 100644 index 00000000..3cf0a564 --- /dev/null +++ b/tests/auto/blackbox/testdata/freedesktop/myapp.appdata.xml @@ -0,0 +1,15 @@ + + + myapp.desktop + CC0 + MyApp + The coolest app ever + + +

This is a cool application.

+
+ + https://software.house/myapp + GPL-2.0+ + Coding Wizard +
diff --git a/tests/auto/blackbox/testdata/freedesktop/myapp.desktop b/tests/auto/blackbox/testdata/freedesktop/myapp.desktop new file mode 100644 index 00000000..dac3014c --- /dev/null +++ b/tests/auto/blackbox/testdata/freedesktop/myapp.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +GenericName=Image Editor +Comment=Create images and edit photographs +Icon=overridden.png diff --git a/tests/auto/blackbox/testdata/freedesktop/myapp.png b/tests/auto/blackbox/testdata/freedesktop/myapp.png new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/generate-linker-map-file/generate-linker-map-file.qbs b/tests/auto/blackbox/testdata/generate-linker-map-file/generate-linker-map-file.qbs new file mode 100644 index 00000000..8c971a74 --- /dev/null +++ b/tests/auto/blackbox/testdata/generate-linker-map-file/generate-linker-map-file.qbs @@ -0,0 +1,28 @@ +Project { + CppApplication { + name: "app-map" + files: ["main.cpp"] + // lld-link has different flag for map files, test it by switching to "lld" linkerVariant + Properties { condition: qbs.toolchain.contains("clang-cl"); cpp.linkerVariant: "lld" } + cpp.generateLinkerMapFile: true + } + CppApplication { + name: "app-nomap" + files: ["main.cpp"] + Properties { condition: qbs.toolchain.contains("clang-cl"); cpp.linkerVariant: "lld" } + cpp.generateLinkerMapFile: false + } + CppApplication { + name: "app-nomap-default" + files: ["main.cpp"] + } + + Probe { + id: toolchainProbe + property bool isUsed: qbs.toolchain.contains("msvc") + || qbs.toolchain.contains("gcc") + configure: { + console.info("use test: " + isUsed); + } + } +} diff --git a/tests/auto/blackbox/testdata/generate-linker-map-file/main.cpp b/tests/auto/blackbox/testdata/generate-linker-map-file/main.cpp new file mode 100644 index 00000000..e4e2a167 --- /dev/null +++ b/tests/auto/blackbox/testdata/generate-linker-map-file/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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$ +** +****************************************************************************/ + +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/generated-artifact-as-input-to-dynamic-rule/input.txt b/tests/auto/blackbox/testdata/generated-artifact-as-input-to-dynamic-rule/input.txt new file mode 100644 index 00000000..37081c64 --- /dev/null +++ b/tests/auto/blackbox/testdata/generated-artifact-as-input-to-dynamic-rule/input.txt @@ -0,0 +1 @@ +old.txt diff --git a/tests/auto/blackbox/testdata/generated-artifact-as-input-to-dynamic-rule/p.qbs b/tests/auto/blackbox/testdata/generated-artifact-as-input-to-dynamic-rule/p.qbs new file mode 100644 index 00000000..7ed5f5d9 --- /dev/null +++ b/tests/auto/blackbox/testdata/generated-artifact-as-input-to-dynamic-rule/p.qbs @@ -0,0 +1,54 @@ +import qbs.File +import qbs.TextFile + +Product { + type: ["mytype.final"] + + Group { + files: ["input.txt"] + fileTags: ["mytype.in"] + } + + Rule { + inputs: ["mytype.in"] + Artifact { + filePath: "output.txt" + fileTags: ["mytype.out"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); }; + return [cmd]; + } + } + + Rule { + inputs: ["mytype.out"] + outputFileTags: ["mytype.final", "dummy"] + outputArtifacts: { + var file; + var inFile = new TextFile(input.filePath, TextFile.ReadOnly); + try { + file = inFile.readLine(); + if (!file) + throw "no file name found"; + } finally { + inFile.close(); + } + return [ + { filePath: file, fileTags: ["mytype.final"] }, + { filePath: "dummy", fileTags: ["dummy"], alwaysUpdated: false } + ]; + } + prepare: { + var cmd = new JavaScriptCommand(); + var output = outputs["mytype.final"][0]; + cmd.description = "generating " + output.fileName; + cmd.outputFilePath = output.filePath; + cmd.sourceCode = function() { File.copy(input.filePath, outputFilePath); }; + return [cmd]; + } + } +} + diff --git a/tests/auto/blackbox/testdata/generator/generator.qbs b/tests/auto/blackbox/testdata/generator/generator.qbs new file mode 100644 index 00000000..d0857beb --- /dev/null +++ b/tests/auto/blackbox/testdata/generator/generator.qbs @@ -0,0 +1,35 @@ +import qbs.TextFile + +CppApplication { + name: "app" + files: "main.cpp" + Group { + files: "input.txt" + fileTags: "generator.in" + } + Rule { + inputs: ["generator.in"] + Artifact { filePath: "file1.cpp"; fileTags: ["cpp", "file1"]; alwaysUpdated: false } + Artifact { filePath: "file2.cpp"; fileTags: ["cpp", "file2"]; alwaysUpdated: false } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "potentially generating files"; + cmd.sourceCode = function() { + var f = new TextFile(input.filePath, TextFile.ReadOnly); + var content = f.readAll(); + f.close(); + if (content.contains("file1")) { + f = new TextFile(outputs.file1[0].filePath, TextFile.WriteOnly); + f.writeLine("void f1() {}"); + f.close(); + } + if (content.contains("file2")) { + f = new TextFile(outputs.file2[0].filePath, TextFile.WriteOnly); + f.writeLine("void f2() {}"); + f.close(); + } + }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/generator/input.both.txt b/tests/auto/blackbox/testdata/generator/input.both.txt new file mode 100644 index 00000000..137da54e --- /dev/null +++ b/tests/auto/blackbox/testdata/generator/input.both.txt @@ -0,0 +1,2 @@ +file1.cpp +file2.cpp diff --git a/tests/auto/blackbox/testdata/generator/input.file1.txt b/tests/auto/blackbox/testdata/generator/input.file1.txt new file mode 100644 index 00000000..d7152327 --- /dev/null +++ b/tests/auto/blackbox/testdata/generator/input.file1.txt @@ -0,0 +1 @@ +file1.cpp diff --git a/tests/auto/blackbox/testdata/generator/input.file2.txt b/tests/auto/blackbox/testdata/generator/input.file2.txt new file mode 100644 index 00000000..a292ada3 --- /dev/null +++ b/tests/auto/blackbox/testdata/generator/input.file2.txt @@ -0,0 +1 @@ +file2.cpp diff --git a/tests/auto/blackbox/testdata/generator/input.none.txt b/tests/auto/blackbox/testdata/generator/input.none.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/generator/main.cpp b/tests/auto/blackbox/testdata/generator/main.cpp new file mode 100644 index 00000000..13772e01 --- /dev/null +++ b/tests/auto/blackbox/testdata/generator/main.cpp @@ -0,0 +1,8 @@ +void f1(); +void f2(); + +int main() +{ + f1(); + f2(); +} diff --git a/tests/auto/blackbox/testdata/group-condition-change/group-condition-change.qbs b/tests/auto/blackbox/testdata/group-condition-change/group-condition-change.qbs new file mode 100644 index 00000000..ae181137 --- /dev/null +++ b/tests/auto/blackbox/testdata/group-condition-change/group-condition-change.qbs @@ -0,0 +1,25 @@ +Project { + property bool kaputt: true + Product { + type: ["kaputt"] + Group { + name: "kaputt" + condition: project.kaputt + files: "input_kaputt.txt" + fileTags: "input.kaputt" + } + Rule { + inputs: "input.kaputt" + Artifact { + filePath: "output.kaputt" + fileTags: "kaputt" + } + prepare: { + var cmd = new Command("jibbetnich", [output.filePath]); + cmd.silent = true; + return cmd; + } + } + } +} + diff --git a/tests/auto/blackbox/testdata/group-condition-change/input_kaputt.txt b/tests/auto/blackbox/testdata/group-condition-change/input_kaputt.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/groups-in-modules/groups-in-modules.qbs b/tests/auto/blackbox/testdata/groups-in-modules/groups-in-modules.qbs new file mode 100644 index 00000000..0508872d --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/groups-in-modules.qbs @@ -0,0 +1,28 @@ +Project { + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + Depends { name: "dep" } + Depends { name: "helper" } + Depends { + name: "helper3" + required: false + } + type: ["diamond"] + + files: [ + "rock.coal" + ] + } + + Product { + name: "dep" + Export { + Depends { name: "helper4" } + } + } +} diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper/chunk.coal b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper/chunk.coal new file mode 100644 index 00000000..9bed41ac --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper/chunk.coal @@ -0,0 +1 @@ +chunk diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper/diamondc.c b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper/diamondc.c new file mode 100644 index 00000000..448a9404 --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper/diamondc.c @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** 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 + +int main(int argc, char *argv[]) { + if (argc < 3) { + fprintf(stderr, "usage: diamondc input.coal output.diamond"); + return 1; + } + + FILE *in = fopen(argv[1], "r"); + if (in) { + fclose(in); + } else { + return 1; + } + + FILE *out = fopen(argv[2], "w"); + if (out) { + fprintf(out, "diamond\n"); + fclose(out); + } else { + return 1; + } + + return 0; +} diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper/helper.qbs b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper/helper.qbs new file mode 100644 index 00000000..cbd30a35 --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper/helper.qbs @@ -0,0 +1,46 @@ +import qbs.FileInfo + +Module { + Depends { name: "cpp" } + Depends { name: "helper2" } + + additionalProductTypes: ["diamond"] + + Group { + name: "Helper Sources" + files: ["diamondc.c"] + } + + Group { + name: "Additional Chunk" + prefix: path + "/" + files: ["chunk.coal"] + } + + Group { + name: "some other file from helper" + prefix: project.sourceDirectory + '/' + files: ["someotherfile2.txt"] + } + + FileTagger { + patterns: ["*.coal"] + fileTags: ["coal"] + } + + Rule { + inputs: ["coal"] + explicitlyDependsOn: ["application"] + + Artifact { + filePath: FileInfo.joinPaths(product.destinationDirectory, input.baseName + ".diamond") + fileTags: ["diamond"] + } + + prepare: { + var cmd = new Command(FileInfo.joinPaths(product.buildDirectory, product.targetName), [input.filePath, output.filePath]); + cmd.description = "compile " + input.fileName + " => " + output.fileName; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper2/helper2.c b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper2/helper2.c new file mode 100644 index 00000000..36f0b85f --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper2/helper2.c @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void helper2() {} diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper2/helper2.qbs b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper2/helper2.qbs new file mode 100644 index 00000000..18d11c8f --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper2/helper2.qbs @@ -0,0 +1,14 @@ +Module { + Depends { name: "cpp" } + + Group { + name: "Helper2 Sources" + files: ["helper2.c"] + } + + Group { + name: "some other file from helper2" + prefix: product.sourceDirectory + '/' + files: ["someotherfile.txt"] + } +} diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper3/helper3.c b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper3/helper3.c new file mode 100644 index 00000000..475e788e --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper3/helper3.c @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void helper3() {} diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper3/helper3.qbs b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper3/helper3.qbs new file mode 100644 index 00000000..de3e6171 --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper3/helper3.qbs @@ -0,0 +1,10 @@ +Module { + Depends { name: "cpp" } + + Group { + name: "Helper3 Sources" + files: ["helper3.c"] + } + + validate: { throw "Nope."; } +} diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper4/helper4.c b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper4/helper4.c new file mode 100644 index 00000000..9605e6cb --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper4/helper4.c @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void helper4() {} diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper4/helper4.qbs b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper4/helper4.qbs new file mode 100644 index 00000000..18dce4dc --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper4/helper4.qbs @@ -0,0 +1,9 @@ +Module { + Depends { name: "cpp" } + Depends { name: "helper5" } + + Group { + name: "Helper4 Sources" + files: ["helper4.c"] + } +} diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper5/helper5.c b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper5/helper5.c new file mode 100644 index 00000000..064e0f9a --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper5/helper5.c @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void helper5() { +#ifndef COMPILE_FIX + nothatcantwork +#endif +} diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper5/helper5.qbs b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper5/helper5.qbs new file mode 100644 index 00000000..58c8144d --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper5/helper5.qbs @@ -0,0 +1,13 @@ +Module { + Depends { name: "cpp" } + Depends { + name: "helper6" + required: false + } + + Group { + name: "Helper5 Sources" + files: ["helper5.c"] + cpp.defines: ["COMPILE_FIX"] + } +} diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper6/helper6.c b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper6/helper6.c new file mode 100644 index 00000000..da3ac96d --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper6/helper6.c @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void helper6() {} diff --git a/tests/auto/blackbox/testdata/groups-in-modules/modules/helper6/helper6.qbs b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper6/helper6.qbs new file mode 100644 index 00000000..8f73567e --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/modules/helper6/helper6.qbs @@ -0,0 +1,10 @@ +Module { + Depends { name: "cpp" } + + Group { + name: "Helper6 Sources" + files: ["helper6.c"] + } + + validate: { throw "Go away."; } +} diff --git a/tests/auto/blackbox/testdata/groups-in-modules/rock.coal b/tests/auto/blackbox/testdata/groups-in-modules/rock.coal new file mode 100644 index 00000000..d816c96f --- /dev/null +++ b/tests/auto/blackbox/testdata/groups-in-modules/rock.coal @@ -0,0 +1 @@ +coal diff --git a/tests/auto/blackbox/testdata/groups-in-modules/someotherfile.txt b/tests/auto/blackbox/testdata/groups-in-modules/someotherfile.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/groups-in-modules/someotherfile2.txt b/tests/auto/blackbox/testdata/groups-in-modules/someotherfile2.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/grpc/grpc.cpp b/tests/auto/blackbox/testdata/grpc/grpc.cpp new file mode 100644 index 00000000..5f85c5b4 --- /dev/null +++ b/tests/auto/blackbox/testdata/grpc/grpc.cpp @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://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. +** +****************************************************************************/ + +#include + +class ServiceImpl final : public Qbs::Grpc::Service +{ + grpc::Status doWork( + grpc::ServerContext* context, + const Qbs::Request* request, + Qbs::Response* reply) override + { + (void)context; + reply->set_name(request->name()); + return grpc::Status::OK; + } +}; + +int main(int, char**) +{ + return 0; +} diff --git a/tests/auto/blackbox/testdata/grpc/grpc.proto b/tests/auto/blackbox/testdata/grpc/grpc.proto new file mode 100644 index 00000000..631006ba --- /dev/null +++ b/tests/auto/blackbox/testdata/grpc/grpc.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package Qbs; + +message Request { + string name = 1; +} + +message Response { + string name = 1; +} + +service Grpc { + rpc doWork(Request) returns (Response) {} +} diff --git a/tests/auto/blackbox/testdata/grpc/grpc_cpp.qbs b/tests/auto/blackbox/testdata/grpc/grpc_cpp.qbs new file mode 100644 index 00000000..353a40f8 --- /dev/null +++ b/tests/auto/blackbox/testdata/grpc/grpc_cpp.qbs @@ -0,0 +1,32 @@ +import qbs + +CppApplication { + name: "grpc_cpp" + consoleApplication: true + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result && hasDependencies; + } + + Depends { name: "cpp" } + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.8" + cpp.warningLevel: "none" + + Depends { name: "protobuf.cpp"; required: false } + protobuf.cpp.useGrpc: true + + property bool hasDependencies: { + console.info("has grpc: " + protobuf.cpp.present); + return protobuf.cpp.present; + } + + files: "grpc.cpp" + + Group { + files: "grpc.proto" + fileTags: "protobuf.grpc" + } +} diff --git a/tests/auto/blackbox/testdata/host-os-properties/host-os-properties.qbs b/tests/auto/blackbox/testdata/host-os-properties/host-os-properties.qbs new file mode 100644 index 00000000..e1e72276 --- /dev/null +++ b/tests/auto/blackbox/testdata/host-os-properties/host-os-properties.qbs @@ -0,0 +1,14 @@ +CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + consoleApplication: true + cpp.defines: [ + 'HOST_ARCHITECTURE="' + qbs.hostArchitecture + '"', + 'HOST_PLATFORM="' + qbs.hostPlatform + '"' + ] + files: "main.cpp" +} diff --git a/tests/auto/blackbox/testdata/host-os-properties/main.cpp b/tests/auto/blackbox/testdata/host-os-properties/main.cpp new file mode 100644 index 00000000..b0c239e2 --- /dev/null +++ b/tests/auto/blackbox/testdata/host-os-properties/main.cpp @@ -0,0 +1,7 @@ +#include + +int main() { + printf("HOST_ARCHITECTURE = %s\n", HOST_ARCHITECTURE); + printf("HOST_PLATFORM = %s\n", HOST_PLATFORM); + return 0; +} diff --git a/tests/auto/blackbox/testdata/ico/dmg.iconset/icon_128x128.png b/tests/auto/blackbox/testdata/ico/dmg.iconset/icon_128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..6c556a8356d0a818c45a42ca845502a8b2e460a7 GIT binary patch literal 5256 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 M?&_&ms9Hb&A3*vBzW@LL literal 0 HcmV?d00001 diff --git a/tests/auto/blackbox/testdata/ico/dmg.iconset/icon_16x16.png b/tests/auto/blackbox/testdata/ico/dmg.iconset/icon_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..2adcdb5f49180b2c150612ec2849e152da141eb5 GIT binary patch literal 487 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 I&?fSK0D}4Yvj6}9 literal 0 HcmV?d00001 diff --git a/tests/auto/blackbox/testdata/ico/dmg.iconset/icon_32x32.png b/tests/auto/blackbox/testdata/ico/dmg.iconset/icon_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..2a7030fd70568c2b3eadd224afb22db8df22ee72 GIT binary patch literal 1374 zcmb7EdsNbA7{=AI7`S2NEp4UQbQM~OI=9(WG7ZIsm%MZ_ZRW96WNVqO)5Vo1E4MCc zrri)VRLpGKnWmtUfa9hJ2r8f;h=2l;nV_&=$NjO>A3JC7Ip6y}-}9d5J>U7B?|j$x zVs=BpZeS1y1dWbDVS&2--CDT}$S6WF4Fvk&&%H4TyMXQgiXhTIlEAtMK)95bCIk{u z|1qkptPJQvp-?;?4`2)i)6mdBB9ZLv?O7~VettgC?e6Y|Kp;g$MN4z~`}+|H1b_j_ z5)&5}S65fJgx|{q0)fF`Bqk>Qn{R1pxpnInzyP4Wz8(gH0l2ZTv8JZR)6)~c_wU~a z&Va+=z~Fax6&xI#k&%&=m6el|bKt-MKR-W!d@sV-*htR4C~oq3$u-%W+-9>)&TH=~ z9-a^vELs;eW*t%8j91l5-i+QDE9)`*Wwluk3r?c@(q#6pYuZ7X0a~JFPbAb2{Z$dezavy!%{&baLiM^TS)T-Xz-iy*3M$1gaIQ|!Th2pKUP+HPw;`bWaozp#$?4<0T z)3rVIBC$v+m5RmUJBkN-i($Ytm_2xgXB0}6GDcr7oyAZn6h@;_B9Umd+5}!EXNH$O z_#>K`cD1{tn?W5J8JV1%)ai73y`InK4-E}v2yqM6g+ui3yJ%f9nM|cpsnu%0xbgAv zeFIr&W-5hB866#!%jHU?a$;hF&1Q>4A|8*I*L6uC5R8qD0j>#!!r|dzDwR4oIM~tA z!R2xX1_s*O+nG!z@HRLcPHSr`jYgx>>1}OoWHOmTp}Z`8MFxz?`T>iH2iYxK{^1Aq zpSY}b_i%%+0++k4sPcWV9kQk}0J?JHM~EFC2W{W!Q-1i2Nakg|vcb5sVXK2Rd0ijU z@zhXA=(kDxQnO^%vzvVUB6XB6CdXs8j7Uq7|t1LMd;{^-!bb|Yw9S4Ez z^wFrW1Q%C9S1cNS3Pd;RPg-p@4vTYw=yPIzuXAB@;RfOJqIge_a5aDJQ?%cDO&>8{ z|M*xr0_IU&cm47jI1IfUeBEVvNrkrl#mfPfd4I>H&`+)Q_y||)-TYO_xfl<3exTzj zPep0kn~)0y_~73!Zr(H&U`uy2bY6)LU{mt!lCz!9lsXXEVM*!z+hT9;a7?NkjS^*c zt#19M^`=*Yd&UzbV;&n57v@gjFZ+YBCUx7E%14V1Rn^t&xh>WB8Nbf4&#{$m6{EYo z4I)_M+ao_|kP|t`+=JdN>%Mea;o<5$TbNnN&eP?Zs&Tdc`)hu~-DZhW3Wi;nO~2y8 z&HfHODx;lp`YZoLX472xtP`UBBF|eX^g8E;e9H38HQl_fT40rGkQdU#BS_U9I$6&R z-5q~AXirTf99axz((cvT2Z&9<_aKUNA4pL#$B8X>R$OvDBCGa?1YX`Mn=3#Ebr7WR z+)VPukd0rzrZn9=8A9DkY`dGBjX=yjtc|F^Jr8}7pAdDvV>l-xIz_YR=%l)ssUVS8 zWAg&YM<>>5eVwrKdsW|&^i@eK2m&$DG97!41a*oMmp#5JXhs=k62rVsPr+Y>X)vD& wjK@)N3b?;}i`cZyBjDLf^m*=4H{S}`eCQSP!Jku`-~9s7yD+Gx@bt@n103|2K>z>% literal 0 HcmV?d00001 diff --git a/tests/auto/blackbox/testdata/ico/dmg.iconset/icon_512x512.png b/tests/auto/blackbox/testdata/ico/dmg.iconset/icon_512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..069b5c050245260272c21e9839b43593124370e5 GIT binary patch literal 26984 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 diff --git a/tests/auto/blackbox/testdata/imports-conflict/imports-conflict.qbs b/tests/auto/blackbox/testdata/imports-conflict/imports-conflict.qbs new file mode 100644 index 00000000..4966b2f0 --- /dev/null +++ b/tests/auto/blackbox/testdata/imports-conflict/imports-conflict.qbs @@ -0,0 +1,9 @@ +Project { + Product { + name: "Utils" + } + Product { + Depends { name: "Utils" } + Depends { name: "themodule" } + } +} diff --git a/tests/auto/blackbox/testdata/imports-conflict/modules/themodule/m.qbs b/tests/auto/blackbox/testdata/imports-conflict/modules/themodule/m.qbs new file mode 100644 index 00000000..48145d38 --- /dev/null +++ b/tests/auto/blackbox/testdata/imports-conflict/modules/themodule/m.qbs @@ -0,0 +1,5 @@ +import "utils.js" as Utils + +Module { + validate: { Utils.helper(); } +} diff --git a/tests/auto/blackbox/testdata/imports-conflict/modules/themodule/utils.js b/tests/auto/blackbox/testdata/imports-conflict/modules/themodule/utils.js new file mode 100644 index 00000000..ad54d4d0 --- /dev/null +++ b/tests/auto/blackbox/testdata/imports-conflict/modules/themodule/utils.js @@ -0,0 +1 @@ +function helper() { console.info("helper"); } diff --git a/tests/auto/blackbox/testdata/includeLookup/includeLookup.qbs b/tests/auto/blackbox/testdata/includeLookup/includeLookup.qbs new file mode 100644 index 00000000..5b645fad --- /dev/null +++ b/tests/auto/blackbox/testdata/includeLookup/includeLookup.qbs @@ -0,0 +1,20 @@ +import qbs.FileInfo + +Project { + property string name: 'includeLookup' + qbsSearchPaths: '.' + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + type: 'application' + consoleApplication: true + name: project.name + files: 'main.cpp' + Depends { name: 'cpp' } + Depends { name: 'definition' } + } +} diff --git a/tests/auto/blackbox/testdata/includeLookup/main.cpp b/tests/auto/blackbox/testdata/includeLookup/main.cpp new file mode 100644 index 00000000..c7213c76 --- /dev/null +++ b/tests/auto/blackbox/testdata/includeLookup/main.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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 + +int main() +{ + printf("%s..\n", TEXT); + return 0; +} + 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 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/includeLookup/modules/definition/module.qbs b/tests/auto/blackbox/testdata/includeLookup/modules/definition/module.qbs new file mode 100644 index 00000000..8762a780 --- /dev/null +++ b/tests/auto/blackbox/testdata/includeLookup/modules/definition/module.qbs @@ -0,0 +1,13 @@ +import qbs.Probes + +Module { + name: 'definition' + Depends { name: 'cpp' } + property string modulePath: path + Probes.IncludeProbe { + id: includeNode + names: "fakeopenssl/sha.h" + platformSearchPaths: [modulePath] + } + cpp.defines: includeNode.found ? 'TEXT="' + includeNode.path + '"' : undefined +} diff --git a/tests/auto/blackbox/testdata/innosetup/inc/qbsinc.iss b/tests/auto/blackbox/testdata/innosetup/inc/qbsinc.iss new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/innosetup/innosetup.qbs b/tests/auto/blackbox/testdata/innosetup/innosetup.qbs new file mode 100644 index 00000000..c9f6a22e --- /dev/null +++ b/tests/auto/blackbox/testdata/innosetup/innosetup.qbs @@ -0,0 +1,22 @@ +import qbs.FileInfo + +Project { + InnoSetup { + name: "QbsSetup" + targetName: "qbs.setup.test" + version: "1.5" + files: [ + "test.iss" + ] + innosetup.verboseOutput: true + innosetup.includePaths: ["inc"] + innosetup.defines: ["MyProgram=" + name, "MyProgramVersion=" + version] + innosetup.compilerFlags: ["/V9"] + qbs.targetPlatform: "windows" + } + InnoSetup { + name: "Example1" + files: [FileInfo.joinPaths(innosetup.toolchainInstallPath, "Examples", name + ".iss")] + qbs.targetPlatform: "windows" + } +} diff --git a/tests/auto/blackbox/testdata/innosetup/test.iss b/tests/auto/blackbox/testdata/innosetup/test.iss new file mode 100644 index 00000000..f9f9195a --- /dev/null +++ b/tests/auto/blackbox/testdata/innosetup/test.iss @@ -0,0 +1,6 @@ +#include "qbsinc.iss" + +[Setup] +AppName={#MyProgram} +AppVersion={#MyProgramVersion} +DefaultDirName={pf}\{#MyProgram} diff --git a/tests/auto/blackbox/testdata/innosetupDependencies/innosetupDependencies.qbs b/tests/auto/blackbox/testdata/innosetupDependencies/innosetupDependencies.qbs new file mode 100644 index 00000000..db65e127 --- /dev/null +++ b/tests/auto/blackbox/testdata/innosetupDependencies/innosetupDependencies.qbs @@ -0,0 +1,72 @@ +import qbs.TextFile + +Project { + InnoSetup { + Depends { name: "app" } + Depends { name: "lib" } + name: "QbsSetup" + targetName: "qbs.setup.test" + version: "1.5" + files: [ + "test.iss" + ] + innosetup.verboseOutput: true + innosetup.defines: [ + "MyProgram=" + name, + "MyProgramVersion=" + version, + "buildDirectory=" + project.buildDirectory + ] + innosetup.compilerFlags: ["/V9"] + destinationDirectory: project.buildDirectory + } + Application { + Depends { name: "cpp" } + name: "app" + files: ["main.c"] + Group { + fileTagsFilter: product.type + qbs.install: true + } + destinationDirectory: project.buildDirectory + } + DynamicLibrary { + Depends { name: "cpp" } + name: "lib" + files: ["main.c"] + Group { + fileTagsFilter: product.type + qbs.install: true + } + Rule { + // This rule tries to provoke the installer into building too early (and the test + // verifies that it does not) by causing the build of the installables to take + // a lot longer. + multiplex: true + outputFileTags: ["c"] + outputArtifacts: { + var artifacts = []; + for (var i = 0; i < 96; ++i) + artifacts.push({ filePath: "c" + i + ".c", fileTags: ["c"] }); + return artifacts; + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + for (var i = 0; i < outputs["c"].length; ++i) { + var tf; + try { + tf = new TextFile(outputs["c"][i].filePath, TextFile.WriteOnly); + tf.writeLine("int main" + i + "() { return 0; }"); + } finally { + if (tf) + tf.close(); + } + } + }; + return [cmd]; + } + } + destinationDirectory: project.buildDirectory + } +} diff --git a/tests/auto/blackbox/testdata/innosetupDependencies/main.c b/tests/auto/blackbox/testdata/innosetupDependencies/main.c new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/blackbox/testdata/innosetupDependencies/main.c @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/innosetupDependencies/test.iss b/tests/auto/blackbox/testdata/innosetupDependencies/test.iss new file mode 100644 index 00000000..430f9941 --- /dev/null +++ b/tests/auto/blackbox/testdata/innosetupDependencies/test.iss @@ -0,0 +1,8 @@ +[Setup] +AppName={#MyProgram} +AppVersion={#MyProgramVersion} +DefaultDirName={pf}\{#MyProgram} + +[Files] +Source: "{#buildDirectory}\app.exe"; DestDir: "{app}" +Source: "{#buildDirectory}\lib.dll"; DestDir: "{app}" diff --git a/tests/auto/blackbox/testdata/input-tags-change-tracking/input-tags-change-tracking.qbs b/tests/auto/blackbox/testdata/input-tags-change-tracking/input-tags-change-tracking.qbs new file mode 100644 index 00000000..ef2c5c55 --- /dev/null +++ b/tests/auto/blackbox/testdata/input-tags-change-tracking/input-tags-change-tracking.qbs @@ -0,0 +1,63 @@ +import qbs.TextFile + +Product { + name: "p" + type: "p_tag" + property string generateInput + Group { + condition: generateInput == "no" + files: "input.txt" + fileTags: ["txt", "empty"] + } + Rule { + condition: generateInput == "static" + multiplex: true + Artifact { filePath: "input.txt"; fileTags: ["txt", "empty"] } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { + var out = new TextFile(output.filePath, TextFile.WriteOnly); + out.close(); + }; + return cmd; + } + } + Rule { + condition: generateInput == "dynamic" + multiplex: true + outputFileTags: ["txt", "empty"] + outputArtifacts: [{filePath: "input.txt", fileTags: ["txt", "empty"]}] + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { + var out = new TextFile(output.filePath, TextFile.WriteOnly); + out.close(); + }; + return cmd; + } + } + + Rule { + inputs: "txt" + outputFileTags: "p_tag" + outputArtifacts: { + if (input.fileTags.contains("empty")) + return []; + return [{ + filePath: input.fileTags.contains("y") ? "y.out" : "x.out", + fileTags: "p_tag" + }] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { + var out = new TextFile(output.filePath, TextFile.WriteOnly); + out.close(); + }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/input-tags-change-tracking/input.txt b/tests/auto/blackbox/testdata/input-tags-change-tracking/input.txt new file mode 100644 index 00000000..c6cac692 --- /dev/null +++ b/tests/auto/blackbox/testdata/input-tags-change-tracking/input.txt @@ -0,0 +1 @@ +empty diff --git a/tests/auto/blackbox/testdata/inputs-from-dependencies/file1.txt b/tests/auto/blackbox/testdata/inputs-from-dependencies/file1.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/inputs-from-dependencies/file2.txt b/tests/auto/blackbox/testdata/inputs-from-dependencies/file2.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/inputs-from-dependencies/file3.txt b/tests/auto/blackbox/testdata/inputs-from-dependencies/file3.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/inputs-from-dependencies/file4.txt b/tests/auto/blackbox/testdata/inputs-from-dependencies/file4.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/inputs-from-dependencies/inputs-from-dependencies.qbs b/tests/auto/blackbox/testdata/inputs-from-dependencies/inputs-from-dependencies.qbs new file mode 100644 index 00000000..919060c7 --- /dev/null +++ b/tests/auto/blackbox/testdata/inputs-from-dependencies/inputs-from-dependencies.qbs @@ -0,0 +1,49 @@ +Project { + Product { + name: "TextFileContainer1" + type: "txt_container" + Group { + files: ["file1.txt", "file2.txt"] + fileTags: "txt" + } + } + Product { + name: "TextFileContainer2" + Group { + files: ["file3.txt"] + fileTags: "txt" + } + } + Product { + name: "TextFileContainer3" + Group { + files: ["file4.txt"] + fileTags: "txt" + } + } + + Product { + name: "TextFileGatherer" + type: "printed_txt" + Depends { name: "TextFileContainer1" } + Depends { name: "TextFileContainer2" } + Rule { + inputsFromDependencies: "txt" + multiplex: true + Artifact { + filePath: "blubb" + fileTags: "printed_txt" + alwaysUpdated: false + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Gathering text files"; + cmd.sourceCode = function() { + for (i in inputs.txt) + console.info(inputs.txt[i].filePath); + }; + return cmd; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/install-duplicates-no-error/file1.txt b/tests/auto/blackbox/testdata/install-duplicates-no-error/file1.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/install-duplicates-no-error/file2.txt b/tests/auto/blackbox/testdata/install-duplicates-no-error/file2.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/install-duplicates-no-error/file3.txt b/tests/auto/blackbox/testdata/install-duplicates-no-error/file3.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/install-duplicates-no-error/install-duplicates-no-error.qbs b/tests/auto/blackbox/testdata/install-duplicates-no-error/install-duplicates-no-error.qbs new file mode 100644 index 00000000..a3282f94 --- /dev/null +++ b/tests/auto/blackbox/testdata/install-duplicates-no-error/install-duplicates-no-error.qbs @@ -0,0 +1,35 @@ +Project { + Product { + name: "p1" + Group { + files: ["file1.txt"] + qbs.install: true + } + } + Product { + name: "p2" + Group { + files: ["file1.txt"] + qbs.install: true + } + } + Product { + name: "p3" + Group { + files: ["file2.txt"] + qbs.install: true + } + multiplexByQbsProperties: ["buildVariants"] + qbs.buildVariants: ["debug", "release"] + } + Product { + name: "p4" + Group { + files: ["file2.txt"] + qbs.install: true + } + aggregate: true + multiplexByQbsProperties: ["buildVariants"] + qbs.buildVariants: ["debug", "release"] + } +} diff --git a/tests/auto/blackbox/testdata/install-duplicates/dir1/file1.txt b/tests/auto/blackbox/testdata/install-duplicates/dir1/file1.txt new file mode 100644 index 00000000..77621b33 --- /dev/null +++ b/tests/auto/blackbox/testdata/install-duplicates/dir1/file1.txt @@ -0,0 +1 @@ +dir1/file1 diff --git a/tests/auto/blackbox/testdata/install-duplicates/dir1/file2.txt b/tests/auto/blackbox/testdata/install-duplicates/dir1/file2.txt new file mode 100644 index 00000000..e66d24b3 --- /dev/null +++ b/tests/auto/blackbox/testdata/install-duplicates/dir1/file2.txt @@ -0,0 +1 @@ +dir1/file2 diff --git a/tests/auto/blackbox/testdata/install-duplicates/dir2/file1.txt b/tests/auto/blackbox/testdata/install-duplicates/dir2/file1.txt new file mode 100644 index 00000000..2600abca --- /dev/null +++ b/tests/auto/blackbox/testdata/install-duplicates/dir2/file1.txt @@ -0,0 +1 @@ +dir2/file1 diff --git a/tests/auto/blackbox/testdata/install-duplicates/dir2/file2.txt b/tests/auto/blackbox/testdata/install-duplicates/dir2/file2.txt new file mode 100644 index 00000000..1347b5fb --- /dev/null +++ b/tests/auto/blackbox/testdata/install-duplicates/dir2/file2.txt @@ -0,0 +1 @@ +dir2/file2 diff --git a/tests/auto/blackbox/testdata/install-duplicates/dir2/file3.txt b/tests/auto/blackbox/testdata/install-duplicates/dir2/file3.txt new file mode 100644 index 00000000..fe4ab6e3 --- /dev/null +++ b/tests/auto/blackbox/testdata/install-duplicates/dir2/file3.txt @@ -0,0 +1 @@ +dir2/file3 diff --git a/tests/auto/blackbox/testdata/install-duplicates/install-duplicates.qbs b/tests/auto/blackbox/testdata/install-duplicates/install-duplicates.qbs new file mode 100644 index 00000000..5edc83d8 --- /dev/null +++ b/tests/auto/blackbox/testdata/install-duplicates/install-duplicates.qbs @@ -0,0 +1,8 @@ +Product { + Group { + files: ["*.txt"] + prefix: "**/" + qbs.install: true + qbs.installDir: "files" + } +} diff --git a/tests/auto/blackbox/testdata/install-locations/install-locations.qbs b/tests/auto/blackbox/testdata/install-locations/install-locations.qbs new file mode 100644 index 00000000..994b4b14 --- /dev/null +++ b/tests/auto/blackbox/testdata/install-locations/install-locations.qbs @@ -0,0 +1,44 @@ +Project { + property bool dummy: { + if (qbs.targetOS.contains("windows")) { + console.info("is windows"); + } else if (qbs.targetOS.contains("darwin")) { + console.info("is darwin"); + if (qbs.targetOS.contains("macos")) + console.info("is mac"); + } else { + console.info("is unix"); + } + + if (qbs.toolchain.contains("mingw")) + console.info("is mingw"); + } + CppApplication { + name: "theapp" + install: true + installDebugInformation: true + files: "main.cpp" + cpp.separateDebugInformation: true + Group { + fileTagsFilter: "application" + fileTags: "some-tag" + } + } + DynamicLibrary { + name: "thelib" + install: true + installImportLib: true + installDebugInformation: true + Depends { name: "cpp" } + cpp.separateDebugInformation: true + files: "thelib.cpp" + } + LoadableModule { + name: "theplugin" + install: true + installDebugInformation: true + Depends { name: "cpp" } + cpp.separateDebugInformation: true + files: "theplugin.cpp" + } +} diff --git a/tests/auto/blackbox/testdata/install-locations/main.cpp b/tests/auto/blackbox/testdata/install-locations/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/install-locations/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/install-locations/thelib.cpp b/tests/auto/blackbox/testdata/install-locations/thelib.cpp new file mode 100644 index 00000000..d3877db2 --- /dev/null +++ b/tests/auto/blackbox/testdata/install-locations/thelib.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT void libFunc() {} diff --git a/tests/auto/blackbox/testdata/install-locations/theplugin.cpp b/tests/auto/blackbox/testdata/install-locations/theplugin.cpp new file mode 100644 index 00000000..ac1ede09 --- /dev/null +++ b/tests/auto/blackbox/testdata/install-locations/theplugin.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT void pluginFunc() {} diff --git a/tests/auto/blackbox/testdata/install-root-from-project-file/file.txt b/tests/auto/blackbox/testdata/install-root-from-project-file/file.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/install-root-from-project-file/install-root-from-project-file.qbs b/tests/auto/blackbox/testdata/install-root-from-project-file/install-root-from-project-file.qbs new file mode 100644 index 00000000..80a63c95 --- /dev/null +++ b/tests/auto/blackbox/testdata/install-root-from-project-file/install-root-from-project-file.qbs @@ -0,0 +1,11 @@ +Product { + name: "p" + property string installRoot + qbs.installRoot: installRoot + Group { + qbs.install: true + qbs.installPrefix: "/install-prefix" + qbs.installDir: "/install-dir" + files: ["file.txt"] + } +} diff --git a/tests/auto/blackbox/testdata/install-tree/data/foo.txt b/tests/auto/blackbox/testdata/install-tree/data/foo.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/install-tree/data/subdir1/bar.txt b/tests/auto/blackbox/testdata/install-tree/data/subdir1/bar.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/install-tree/data/subdir2/baz.txt b/tests/auto/blackbox/testdata/install-tree/data/subdir2/baz.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/install-tree/install-tree.qbs b/tests/auto/blackbox/testdata/install-tree/install-tree.qbs new file mode 100644 index 00000000..dde39d23 --- /dev/null +++ b/tests/auto/blackbox/testdata/install-tree/install-tree.qbs @@ -0,0 +1,10 @@ +CppApplication { + files: ["main.cpp"] + qbs.installPrefix: "" + Group { + files: ["data/**/*.txt"] + qbs.install: true + qbs.installDir: "content" + qbs.installSourceBase: "data" + } +} diff --git a/tests/auto/blackbox/testdata/install-tree/main.cpp b/tests/auto/blackbox/testdata/install-tree/main.cpp new file mode 100644 index 00000000..210c8274 --- /dev/null +++ b/tests/auto/blackbox/testdata/install-tree/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/installable-as-auxiliary-input/installable-as-auxiliary-input.qbs b/tests/auto/blackbox/testdata/installable-as-auxiliary-input/installable-as-auxiliary-input.qbs new file mode 100644 index 00000000..1fa8f5e4 --- /dev/null +++ b/tests/auto/blackbox/testdata/installable-as-auxiliary-input/installable-as-auxiliary-input.qbs @@ -0,0 +1,84 @@ +import qbs.File +import qbs.FileInfo +import qbs.TextFile + +Project { + name: "p" + CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: "app" + Depends { name: "installed-header" } + Rule { + multiplex: true + auxiliaryInputs: ["installable"] + Artifact { filePath: "main.cpp"; fileTags: "cpp" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.writeLine("#include "); + f.writeLine("int main() {"); + f.writeLine(" f();"); + f.writeLine("}"); + f.close(); + }; + return cmd; + } + } + } + + Product { + name: "installed-header" + type: ["header"] + + property string installDir: "include" + + qbs.installPrefix: "" + Group { + fileTagsFilter: "header" + qbs.install: true + qbs.installDir: installDir + } + + Export { + Depends { name: "cpp" } + cpp.includePaths: FileInfo.joinPaths(qbs.installRoot, product.installDir); + } + + Rule { + multiplex: true + Artifact { filePath: "theheader.h.in"; fileTags: "header.in" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating " + output.fileName; + cmd.sourceCode = function() { + for (var i = 0; i < 1000; ++i) { // Artificial delay. + var file = new TextFile(output.filePath, TextFile.WriteOnly); + file.writeLine("#include "); + file.writeLine("inline void f() {"); + file.writeLine(' std::cout << "f-impl" << std::endl;'); + file.writeLine("}"); + file.close(); + } + }; + return [cmd]; + } + } + Rule { + inputs: "header.in" + Artifact { filePath: "theheader.h"; fileTags: "header" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating " + output.fileName; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); }; + return [cmd]; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/installable/installable.qbs b/tests/auto/blackbox/testdata/installable/installable.qbs new file mode 100644 index 00000000..56feb6ec --- /dev/null +++ b/tests/auto/blackbox/testdata/installable/installable.qbs @@ -0,0 +1,41 @@ +import qbs.TextFile + +Project { + CppApplication { + type: ["application"] + name: "app" + consoleApplication: true + Group { + files: ["main.cpp"] + qbs.install: true + } + + install: true + installDir: "" + } + + Product { + name: "install-list" + type: ["install-list"] + Depends { name: "app" } + Rule { + multiplex: true + inputsFromDependencies: ["installable"] + Artifact { + filePath: "installed-files.txt" + fileTags: product.type + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating " + output.fileName; + cmd.sourceCode = function() { + var file = new TextFile(output.filePath, TextFile.WriteOnly); + for (var i = 0; i < inputs.installable.length; ++i) + file.writeLine(inputs.installable[i].filePath); + file.close(); + }; + return [cmd]; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/installable/main.cpp b/tests/auto/blackbox/testdata/installable/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/blackbox/testdata/installable/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/blackbox/testdata/installed-source-files/installed-source-files.qbs b/tests/auto/blackbox/testdata/installed-source-files/installed-source-files.qbs new file mode 100644 index 00000000..7b4d3009 --- /dev/null +++ b/tests/auto/blackbox/testdata/installed-source-files/installed-source-files.qbs @@ -0,0 +1,19 @@ +CppApplication { + consoleApplication: true + files: ["main.cpp"] + qbs.installPrefix: "" + Group { + fileTagsFilter: ["cpp"] + qbs.install: true + } + Group { // this overwrites the properties of the group below + fileTagsFilter: ["text"] + qbs.install: true + } + Group { + files: ["readme.txt"] + fileTags: ["text"] + qbs.install: false + } +} + diff --git a/tests/auto/blackbox/testdata/installed-source-files/main.cpp b/tests/auto/blackbox/testdata/installed-source-files/main.cpp new file mode 100644 index 00000000..210c8274 --- /dev/null +++ b/tests/auto/blackbox/testdata/installed-source-files/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/installed-source-files/readme.txt b/tests/auto/blackbox/testdata/installed-source-files/readme.txt new file mode 100644 index 00000000..854a4b65 --- /dev/null +++ b/tests/auto/blackbox/testdata/installed-source-files/readme.txt @@ -0,0 +1 @@ +important information diff --git a/tests/auto/blackbox/testdata/installed-transformer-output/qbs668.qbs b/tests/auto/blackbox/testdata/installed-transformer-output/qbs668.qbs new file mode 100644 index 00000000..aa40b769 --- /dev/null +++ b/tests/auto/blackbox/testdata/installed-transformer-output/qbs668.qbs @@ -0,0 +1,31 @@ +import qbs.TextFile + +Product { + name: "install-test" + type: ["text"] + qbs.installPrefix: "" + Group { + qbs.install: true + qbs.installDir: "textfiles" + fileTagsFilter: "text" + } + + Rule { + multiplex: true + Artifact { + filePath: "HelloWorld.txt" + fileTags: ["text"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating file:'" + output.fileName + "'"; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + var file = new TextFile(output.filePath, TextFile.WriteOnly); + file.writeLine("Hello World!") + file.close(); + } + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/installed_artifact/installed_artifact.qbs b/tests/auto/blackbox/testdata/installed_artifact/installed_artifact.qbs new file mode 100644 index 00000000..f7ae7f5e --- /dev/null +++ b/tests/auto/blackbox/testdata/installed_artifact/installed_artifact.qbs @@ -0,0 +1,17 @@ +Application { + name: "installedApp" + type: "application" + consoleApplication: true + Depends { name: "cpp" } + Group { + files: "main.cpp" + qbs.install: true + qbs.installDir: "src" + } + qbs.installPrefix: "/usr" + Group { + fileTagsFilter: "application" + qbs.install: true + qbs.installDir: "bin" + } +} diff --git a/tests/auto/blackbox/testdata/installed_artifact/main.cpp b/tests/auto/blackbox/testdata/installed_artifact/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/blackbox/testdata/installed_artifact/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/blackbox/testdata/installpackage/installpackage.qbs b/tests/auto/blackbox/testdata/installpackage/installpackage.qbs new file mode 100644 index 00000000..a0649a57 --- /dev/null +++ b/tests/auto/blackbox/testdata/installpackage/installpackage.qbs @@ -0,0 +1,49 @@ +Project { + CppApplication { + name: "public_tool" + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + Depends { name: "mylib" } + files: ["main.cpp"] + Group { + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: "bin" + } + } + CppApplication { + name: "internal_tool" + Depends { name: "mylib" } + files: ["main.cpp"] + } + DynamicLibrary { + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + Depends { name: "cpp" } + name: "mylib" + files: ["lib.cpp"] + Group { + name: "public headers" + files: ["lib.h"] + qbs.install: true + qbs.installDir: "include" + } + Group { + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: "lib" + } + } + + InstallPackage { + archiver.type: "tar" + builtByDefault: true + name: "tar-package" + Depends { name: "public_tool" } + Depends { name: "mylib" } + } +} diff --git a/tests/auto/blackbox/testdata/installpackage/lib.cpp b/tests/auto/blackbox/testdata/installpackage/lib.cpp new file mode 100644 index 00000000..1a34ab3a --- /dev/null +++ b/tests/auto/blackbox/testdata/installpackage/lib.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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 "lib.h" + +void f() {} diff --git a/tests/auto/blackbox/testdata/installpackage/lib.h b/tests/auto/blackbox/testdata/installpackage/lib.h new file mode 100644 index 00000000..9be25e20 --- /dev/null +++ b/tests/auto/blackbox/testdata/installpackage/lib.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" + +#ifdef MYLIB +# define MYLIB_EXPORT DLL_EXPORT +#else +# define MYLIB_EXPORT DLL_IMPORT +#endif + +MYLIB_EXPORT void f(); diff --git a/tests/auto/blackbox/testdata/installpackage/main.cpp b/tests/auto/blackbox/testdata/installpackage/main.cpp new file mode 100644 index 00000000..145b3ed6 --- /dev/null +++ b/tests/auto/blackbox/testdata/installpackage/main.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 "lib.h" + +int main() +{ + f(); +} diff --git a/tests/auto/blackbox/testdata/invalid-command-property/input.txt b/tests/auto/blackbox/testdata/invalid-command-property/input.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/invalid-command-property/invalid-command-property.qbs b/tests/auto/blackbox/testdata/invalid-command-property/invalid-command-property.qbs new file mode 100644 index 00000000..b08fcd4a --- /dev/null +++ b/tests/auto/blackbox/testdata/invalid-command-property/invalid-command-property.qbs @@ -0,0 +1,32 @@ +import qbs.TextFile + +Product { + name: "p" + property string errorType + type: ["output"] + Group { + files: ["input.txt"] + fileTags: ["input"] + } + Rule { + inputs: ["input"] + Artifact { + filePath: "dummy" + fileTags: ["output"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating output"; + if (product.errorType === "qobject") + cmd.dummy = new TextFile(input.filePath, TextFile.ReadOnly); + else if (product.errorType === "input") + cmd.dummy = input; + else if (product.errorType === "artifact") + cmd.dummy = product.artifacts.qbs[0]; + else + throw "invalid error type " + product.errorType; + cmd.sourceCode = function() { } + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/invalid-extension-instantiation/invalid-extension-instantiation.qbs b/tests/auto/blackbox/testdata/invalid-extension-instantiation/invalid-extension-instantiation.qbs new file mode 100644 index 00000000..d5e4dd5c --- /dev/null +++ b/tests/auto/blackbox/testdata/invalid-extension-instantiation/invalid-extension-instantiation.qbs @@ -0,0 +1,26 @@ +import qbs.Environment +import qbs.File +import qbs.FileInfo +import qbs.Utilities + +Product { + name: "theProduct" + type: ["mytype"] + property string extension + + Rule { + multiplex: true + Artifact { + filePath: "dummy" + fileTags: product.type + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + var f = eval("new " + product.extension); + }; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/invalid-install-dir/invalid-install-dir.qbs b/tests/auto/blackbox/testdata/invalid-install-dir/invalid-install-dir.qbs new file mode 100644 index 00000000..57505712 --- /dev/null +++ b/tests/auto/blackbox/testdata/invalid-install-dir/invalid-install-dir.qbs @@ -0,0 +1,7 @@ +CppApplication { + consoleApplication: true + files: ["main.cpp"] + qbs.installPrefix: "" + install: true + installDir: "../whatever" +} diff --git a/tests/auto/blackbox/testdata/invalid-install-dir/main.cpp b/tests/auto/blackbox/testdata/invalid-install-dir/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/invalid-install-dir/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/invalid-library-names/invalid-library-names.qbs b/tests/auto/blackbox/testdata/invalid-library-names/invalid-library-names.qbs new file mode 100644 index 00000000..659b3996 --- /dev/null +++ b/tests/auto/blackbox/testdata/invalid-library-names/invalid-library-names.qbs @@ -0,0 +1,10 @@ +Project { + minimumQbsVersion: "1.6" + property var values: [null, undefined, 5, [], ""] + property int valueIndex + CppApplication { + cpp.dynamicLibraries: [project.values[project.valueIndex]] + cpp.staticLibraries: [project.values[project.valueIndex]] + files: ["main.cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/invalid-library-names/main.cpp b/tests/auto/blackbox/testdata/invalid-library-names/main.cpp new file mode 100644 index 00000000..e01d8252 --- /dev/null +++ b/tests/auto/blackbox/testdata/invalid-library-names/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/blackbox/testdata/jsextensions-binaryfile/binaryfile.qbs b/tests/auto/blackbox/testdata/jsextensions-binaryfile/binaryfile.qbs new file mode 100644 index 00000000..1e742674 --- /dev/null +++ b/tests/auto/blackbox/testdata/jsextensions-binaryfile/binaryfile.qbs @@ -0,0 +1,44 @@ +import qbs.BinaryFile + +Product { + type: ["dummy"] + Rule { + multiplex: true + outputFileTags: "dummy" + prepare: { + var commands = []; + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + var source = new BinaryFile("source.dat", BinaryFile.WriteOnly); + source.write([ 0x01, 0x02 ]); // First data. + // Do not close the file to test the auto close functionality. + }; + commands.push(cmd); + cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + source = new BinaryFile("source.dat", BinaryFile.ReadWrite); + source.seek(source.size()); + source.write([ 0x03, 0x04 ]); // Second data. + source.close(); + source = new BinaryFile("source.dat", BinaryFile.ReadWrite); + var destination = new BinaryFile("destination.dat", BinaryFile.WriteOnly); + destination.write(source.atEof() ? [ 0xFF ] : [ 0x00 ]); + while (true) { + var data = source.read(1); + if (!data || data.length == 0) + break; + destination.write(data); + } + source.resize(0); // truncate + destination.write([ 0x05, 0x06 ]); // Third data. + destination.write(source.atEof() ? [ 0xFF ] : [ 0x00 ]); + source.close(); + destination.close(); + }; + commands.push(cmd); + return commands; + } + } +} diff --git a/tests/auto/blackbox/testdata/jsextensions-file/file.qbs b/tests/auto/blackbox/testdata/jsextensions-file/file.qbs new file mode 100644 index 00000000..6adf714e --- /dev/null +++ b/tests/auto/blackbox/testdata/jsextensions-file/file.qbs @@ -0,0 +1,52 @@ +import qbs.File +import qbs.FileInfo +import qbs.TextFile + +Product { + type: ["dummy"] + Rule { + multiplex: true + outputFileTags: "dummy" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + var origPath = FileInfo.joinPaths(product.sourceDirectory, "original.txt"); + var copyPath = FileInfo.joinPaths(product.sourceDirectory, "copy.txt"); + console.info("copy path: "+copyPath); + var original = new TextFile(origPath, TextFile.WriteOnly); + original.close(); + File.copy(origPath, copyPath); + var origTimestamp = File.lastModified(origPath); + var copyTimestamp = File.lastModified(copyPath); + if (origTimestamp > copyTimestamp) + throw new Error("Older file has newer timestamp."); + File.remove(origPath); + var copy = new TextFile(copyPath, TextFile.WriteOnly); + copy.writeLine(File.exists(origPath)); + copy.writeLine(File.exists(copyPath)); + copy.close(); + var zePath = FileInfo.joinPaths(product.sourceDirectory, "zePath"); + if (File.exists(zePath)) + throw new Error(zePath + " already exists."); + var created = File.makePath(zePath); + if (!created || !File.exists(zePath)) + throw new Error("zePath was not created."); + var entries = File.directoryEntries(product.sourceDirectory, File.AllEntries | File.NoDotAndDotDot); + if (entries.length < 3 || !entries.contains("file.qbs")) + throw new Error("Directory did not contain file.qbs"); + entries = File.directoryEntries(product.sourceDirectory, File.Dirs | File.NoDotAndDotDot); + if (entries.length < 1 || !entries.contains("zePath")) + throw new Error("Directory did not contain only zePath"); + var moveSource = FileInfo.joinPaths(product.sourceDirectory, "tomove.txt"); + var moveTarget = FileInfo.joinPaths(product.sourceDirectory, "moved.txt"); + File.move(moveSource, moveTarget); + if (File.exists(moveSource)) + throw new Error("Moved file still exists under old name"); + if (!File.exists(moveTarget)) + throw new Error("Moved file does not exist under new name"); + }; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/jsextensions-fileinfo/fileinfo.qbs b/tests/auto/blackbox/testdata/jsextensions-fileinfo/fileinfo.qbs new file mode 100644 index 00000000..d63ba296 --- /dev/null +++ b/tests/auto/blackbox/testdata/jsextensions-fileinfo/fileinfo.qbs @@ -0,0 +1,47 @@ +import qbs.FileInfo +import qbs.TextFile + +Product { + type: ["dummy"] + property string messyPath: path + "/../" + FileInfo.fileName(path) + Rule { + multiplex: true + outputFileTags: "dummy" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + var output = new TextFile(FileInfo.joinPaths(product.sourceDirectory, "output.txt"), + TextFile.WriteOnly); + output.writeLine(FileInfo.baseName("/tmp/blubb.tar.gz")); + output.writeLine(FileInfo.canonicalPath(product.messyPath)); + 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")); + output.writeLine(FileInfo.fromWindowsSeparators("c:\\tmp\\blubb.tar.gz")); + output.writeLine(FileInfo.isAbsolutePath("/tmp/blubb.tar.gz")); + output.writeLine(FileInfo.isAbsolutePath("c:\\tmp\\blubb.tar.gz")); + output.writeLine(FileInfo.isAbsolutePath("c:\\tmp\\blubb.tar.gz", ["unix"])); + output.writeLine(FileInfo.isAbsolutePath("c:\\tmp\\blubb.tar.gz", ["windows"])); + output.writeLine(FileInfo.isAbsolutePath("blubb.tar.gz")); + output.writeLine(FileInfo.isAbsolutePath("../blubb.tar.gz")); + output.writeLine(FileInfo.joinPaths("/", "tmp", "blubb.tar.gz")); + output.writeLine(FileInfo.joinPaths("//", "/tmp/", "/blubb.tar.gz")); + output.writeLine(FileInfo.path("/tmp/blubb.tar.gz")); + output.writeLine(FileInfo.path("/tmp/")); + output.writeLine(FileInfo.path("/")); + output.writeLine(FileInfo.path("d:/")); + output.writeLine(FileInfo.path("d:/", ["unix"])); + output.writeLine(FileInfo.path("d:/", ["windows"])); + output.writeLine(FileInfo.relativePath("/tmp", "/tmp/blubb.tar.gz")); + output.writeLine(FileInfo.relativePath("/", "/tmp/blubb.tar.gz")); + output.writeLine(FileInfo.relativePath("/tmp", "/blubb.tar.gz")); + output.writeLine(FileInfo.toWindowsSeparators("/tmp/blubb.tar.gz")); + output.writeLine(FileInfo.toWindowsSeparators("c:\\tmp\\blubb.tar.gz")); + output.close(); + }; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/jsextensions-process/main.cpp b/tests/auto/blackbox/testdata/jsextensions-process/main.cpp new file mode 100644 index 00000000..df769de8 --- /dev/null +++ b/tests/auto/blackbox/testdata/jsextensions-process/main.cpp @@ -0,0 +1,20 @@ +#include +#include +#include + +int main(int argc, char *argv[]) +{ + if (argc != 2 || strcmp(argv[1], "help") != 0) { + fprintf(stderr, "First argument to this program must be 'help'.\n"); + return 1; + } + + const char *env = std::getenv("SOME_ENV"); + if (!env || strcmp(env, "why, hello!") != 0) { + fprintf(stderr, "The SOME_ENV environment variable must be 'why, hello!'.\n"); + return 1; + } + + printf("qbs jsextensions-process test\n"); + return 0; +} diff --git a/tests/auto/blackbox/testdata/jsextensions-process/process.qbs b/tests/auto/blackbox/testdata/jsextensions-process/process.qbs new file mode 100644 index 00000000..b634a805 --- /dev/null +++ b/tests/auto/blackbox/testdata/jsextensions-process/process.qbs @@ -0,0 +1,96 @@ +import qbs.Environment +import qbs.FileInfo +import qbs.Process +import qbs.TextFile + +Project { + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + Depends { name: "cpp" } + type: ["dummy"] + name: "dummy" + files: ["main.cpp"] + Rule { + multiplex: true + inputs: ["application"] + outputFileTags: "dummy" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + var exeFilePath = FileInfo.joinPaths(product.buildDirectory, + product.cpp.executablePrefix + + "dummy" + + product.cpp.executableSuffix); + + // Synchronous run, successful. + var process = new Process(); + var pathVal = "why, hello!"; + process.setEnv("SOME_ENV", pathVal); + process.exec(exeFilePath, ["help"], true); + var output = new TextFile("output.txt", TextFile.WriteOnly); + output.writeLine(process.exitCode()); + output.writeLine(process.readLine()); + process.close(); + + // Asynchronous run, successful. + process = new Process(); + process.setEnv("SOME_ENV", pathVal); + output.writeLine(process.start(exeFilePath, ["help"])); + output.writeLine(process.waitForFinished()); + output.writeLine(process.exitCode()); + output.writeLine(process.readLine()); + process.close(); + + // Asynchronous run, unsuccessful. + process = new Process(); + output.writeLine(process.start("blubb", [])); + process.close(); + + // closeWriteChannel test + process = new Process(); + if (product.qbs.hostOS.contains("windows")) + process.start(product.qbs.windowsShellPath, + ["/C", product.qbs.windowsSystemRoot + "\\system32\\sort.exe"]); + else + process.start("cat", []); + process.writeLine("should be"); + process.closeWriteChannel(); + process.writeLine("should not be"); + if (!process.waitForFinished()) + process.kill(); + output.write(process.readStdOut()); + process.close(); + + // readLine and atEnd + var testReadlineFile = new TextFile("123.txt", TextFile.WriteOnly); + testReadlineFile.writeLine("1"); + testReadlineFile.writeLine("2"); + testReadlineFile.writeLine("3"); + testReadlineFile.close(); + + process = new Process(); + if (product.qbs.hostOS.contains("windows")) + process.exec(product.qbs.windowsShellPath, + ["/C", "type", "123.txt"], + true); + else + process.exec("cat", ["123.txt"], true); + + while(!process.atEnd()) + output.write(process.readLine()); + + // TODO: Test all the other Process methods as well. + + output.close(); + }; + return [cmd]; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/jsextensions-propertylist/propertylist.qbs b/tests/auto/blackbox/testdata/jsextensions-propertylist/propertylist.qbs new file mode 100644 index 00000000..aa135154 --- /dev/null +++ b/tests/auto/blackbox/testdata/jsextensions-propertylist/propertylist.qbs @@ -0,0 +1,129 @@ +import qbs.Process +import qbs.PropertyList +import qbs.TextFile + +Product { + type: ["Pineapple Steve"] + property bool dummy: { + var plistobj = new PropertyList(); + if (!plistobj.isEmpty()) { + throw "newly created PropertyList was not empty!"; + } + + if (plistobj.format() !== undefined) { + throw "newly created PropertyList did not have an undefined format"; + } + + plistobj.clear(); + + if (!plistobj.isEmpty() || plistobj.format() !== undefined) { + throw "clear() somehow had an effect on an empty PropertyList"; + } + + plistobj.readFromString('{"key":["value"]}'); + if (plistobj.isEmpty() || plistobj.format() !== "json") { + throw "readFromString did not set format to JSON or object thinks it is empty"; + } + + plistobj.clear(); + + if (!plistobj.isEmpty() || plistobj.format() !== undefined) { + throw "clear() had no effect on a non-empty PropertyList"; + } + + var obj = { + "Array": ["ListItem1", "ListItem2", "ListItem3"], + "Integer": 1, + "Boolean": true, + "String": "otherString" + }; + + var infoplist = new TextFile("test.xml", TextFile.WriteOnly); + infoplist.write(JSON.stringify(obj)); + infoplist.close(); + + var process = new Process(); + process.exec("plutil", ["-convert", "xml1", "test.xml"]); + process.close(); + + var xmlfile = new TextFile("test.xml", TextFile.ReadOnly); + var propertyList = new PropertyList(); + propertyList.readFromString(xmlfile.readAll()); + xmlfile.close(); + + var jsontextfile = new TextFile("test.json", TextFile.WriteOnly); + jsontextfile.write(propertyList.toJSON()); + jsontextfile.close(); + + propertyList.writeToFile("test2.json", "json-compact"); + propertyList.writeToFile("test3.json", "json-pretty"); + + process = new Process(); + process.exec("plutil", ["-convert", "json", "test.xml"]); + process.close(); + + propertyList = new PropertyList(); + propertyList.readFromFile("test.xml"); + if (propertyList.format() !== "json") { // yes, JSON -- ignore the file extension + throw "expected property list format json but got " + propertyList.format(); + } + + if (propertyList.isEmpty()) { + throw "PropertyList was 'empty' after being loaded with data"; + } + + var opensteptextfile = new TextFile("test.openstep.plist", TextFile.WriteOnly); + opensteptextfile.write('{ rootObject = ( "val1", "val3", "val5", /* comment */ "val7", "val9", ); }'); + opensteptextfile.close(); + + propertyList = new PropertyList(); + propertyList.readFromFile("test.openstep.plist"); + if (propertyList.format() !== "openstep") { + throw "expected property list format openstep but got " + propertyList.format(); + } + + var jsonObj = JSON.parse(propertyList.toJSON()); + if (jsonObj["rootObject"].length != 5) { + throw "going from OpenStep to a JSON string to a JSON object somehow broke"; + } + + propertyList.clear(); + propertyList.readFromString('foobarz'); + jsonObj = JSON.parse(propertyList.toJSON()); + if (jsonObj["foo"] !== "barz") { + throw "the XML plist did not get parsed properly"; + } + + propertyList.writeToFile("woof.xml", "xml1"); + propertyList.readFromFile("woof.xml"); + if (propertyList.format() !== "xml1") { + throw "round trip writing and reading XML failed"; + } + + propertyList.writeToFile("woof.plist", "binary1"); + propertyList.readFromFile("woof.plist"); + if (propertyList.format() !== "binary1") { + throw "round trip writing and reading binary failed"; + } + + if (jsonObj["foo"] !== "barz") { + throw "the binary plist did not get parsed properly"; + } + + if (propertyList.toString("json") !== propertyList.toString("json-compact") || + propertyList.toJSON() !== propertyList.toJSON("compact")) { + throw "json and json-compact formats were not equivalent"; + } + + if (propertyList.toString("json") === propertyList.toString("json-pretty") || + propertyList.toJSON() === propertyList.toJSON("pretty")) { + throw "json and json-pretty formats were not different"; + } + + if (propertyList.toString("xml1") !== propertyList.toXMLString()) { + throw 'toString("xml1") and toXMLString() were not equivalent'; + } + + return true; + } +} diff --git a/tests/auto/blackbox/testdata/jsextensions-temporarydir/jsextensions-temporarydir.qbs b/tests/auto/blackbox/testdata/jsextensions-temporarydir/jsextensions-temporarydir.qbs new file mode 100644 index 00000000..c540e919 --- /dev/null +++ b/tests/auto/blackbox/testdata/jsextensions-temporarydir/jsextensions-temporarydir.qbs @@ -0,0 +1,27 @@ +import qbs.File +import qbs.TemporaryDir + +Product { + targetName: { + var dir; + var dirPath; + try { + dir = new TemporaryDir(); + dirPath = dir.path(); + if (!dirPath) + throw "path is empty"; + + if (!dir.isValid()) + throw "dir is not valid"; + + if (!File.exists(dirPath)) + throw "dir does not exist"; + } finally { + if (!dir.remove()) + throw "could not remove"; + } + + if (File.exists(dirPath)) + throw "dir was not removed"; + } +} diff --git a/tests/auto/blackbox/testdata/jsextensions-textfile/textfile.qbs b/tests/auto/blackbox/testdata/jsextensions-textfile/textfile.qbs new file mode 100644 index 00000000..62b54b13 --- /dev/null +++ b/tests/auto/blackbox/testdata/jsextensions-textfile/textfile.qbs @@ -0,0 +1,43 @@ +import qbs.TextFile + +Product { + type: ["dummy"] + Rule { + multiplex: true + outputFileTags: "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.\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); + file2.writeLine(file1.atEof()); + while (true) { + var line = file1.readLine(); + if (!line || line.length == 0) + break; + file2.writeLine(line); + } + file1.truncate(); + file2.writeLine(file1.filePath()); + file2.writeLine(file1.atEof()); + file1.close(); + file2.close(); + }; + commands.push(cmd); + return commands; + } + } +} diff --git a/tests/auto/blackbox/testdata/last-module-candidate-broken/last-module-candidate-broken.qbs b/tests/auto/blackbox/testdata/last-module-candidate-broken/last-module-candidate-broken.qbs new file mode 100644 index 00000000..db7dc226 --- /dev/null +++ b/tests/auto/blackbox/testdata/last-module-candidate-broken/last-module-candidate-broken.qbs @@ -0,0 +1,5 @@ +CppApplication { + qbsSearchPaths: "qbs" + Depends { name: "Foo" } + files: "main.cpp" +} diff --git a/tests/auto/blackbox/testdata/last-module-candidate-broken/main.cpp b/tests/auto/blackbox/testdata/last-module-candidate-broken/main.cpp new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/blackbox/testdata/last-module-candidate-broken/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/last-module-candidate-broken/qbs/modules/Foo/Foo1.qbs b/tests/auto/blackbox/testdata/last-module-candidate-broken/qbs/modules/Foo/Foo1.qbs new file mode 100644 index 00000000..ba08b862 --- /dev/null +++ b/tests/auto/blackbox/testdata/last-module-candidate-broken/qbs/modules/Foo/Foo1.qbs @@ -0,0 +1,3 @@ +Module { + condition: false +} diff --git a/tests/auto/blackbox/testdata/last-module-candidate-broken/qbs/modules/Foo/Foo2.qbs b/tests/auto/blackbox/testdata/last-module-candidate-broken/qbs/modules/Foo/Foo2.qbs new file mode 100644 index 00000000..0bc383b8 --- /dev/null +++ b/tests/auto/blackbox/testdata/last-module-candidate-broken/qbs/modules/Foo/Foo2.qbs @@ -0,0 +1,2 @@ +Group { +} diff --git a/tests/auto/blackbox/testdata/ld/coreutils.cpp b/tests/auto/blackbox/testdata/ld/coreutils.cpp new file mode 100644 index 00000000..29acfc48 --- /dev/null +++ b/tests/auto/blackbox/testdata/ld/coreutils.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 "coreutils.h" + +int foo() +{ + return 42; +} diff --git a/tests/auto/blackbox/testdata/ld/coreutils.h b/tests/auto/blackbox/testdata/ld/coreutils.h new file mode 100644 index 00000000..5ed9ffbe --- /dev/null +++ b/tests/auto/blackbox/testdata/ld/coreutils.h @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" + +DLL_EXPORT int foo(); diff --git a/tests/auto/blackbox/testdata/ld/ld.qbs b/tests/auto/blackbox/testdata/ld/ld.qbs new file mode 100644 index 00000000..25fede1b --- /dev/null +++ b/tests/auto/blackbox/testdata/ld/ld.qbs @@ -0,0 +1,23 @@ +Project { + Library { + Depends { name: "cpp" } + name: "coreutils" + targetName: "qbs can handle any file paths, even the crazy ones! ;)" + files: ["coreutils.cpp", "coreutils.h"] + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + cpp.sonamePrefix: "@rpath" + } + + Group { + fileTagsFilter: product.type + qbs.install: true + } + } + + CppApplication { + Depends { name: "coreutils" } + files: ["main.cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/ld/main.cpp b/tests/auto/blackbox/testdata/ld/main.cpp new file mode 100644 index 00000000..fd6b72f3 --- /dev/null +++ b/tests/auto/blackbox/testdata/ld/main.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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 "coreutils.h" +#include + +int main(int argc, char *argv[]) +{ + printf("%d\n", foo()); + return 0; +} diff --git a/tests/auto/blackbox/testdata/lexyacc/lex_outfile/lex_outfile.qbs b/tests/auto/blackbox/testdata/lexyacc/lex_outfile/lex_outfile.qbs new file mode 100644 index 00000000..61f76f4b --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/lex_outfile/lex_outfile.qbs @@ -0,0 +1,16 @@ +CppApplication { + qbsSearchPaths: ".." + Depends { name: "bisonhelper" } + Depends { name: "lex_yacc" } + lex_yacc.outputTag: "cpp" + lex_yacc.yaccFlags: ["-l"] + cpp.includePaths: [".", ".."] + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.7" + consoleApplication: true + files: [ + "lexer.l", + "parser.y", + "types.h", + ] +} diff --git a/tests/auto/blackbox/testdata/lexyacc/lex_outfile/lexer.l b/tests/auto/blackbox/testdata/lexyacc/lex_outfile/lexer.l new file mode 100644 index 00000000..07115405 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/lex_outfile/lexer.l @@ -0,0 +1,21 @@ +%option outfile="quark.cpp" +%{ +#include +void yyerror(const char *e) { std::cerr << e; } +extern "C" int yywrap() { return 1; } +extern YYSTYPE yylval; +%} + +ID [a-z]+ +AND "&&" +OR "||" +NOT "!" + +%% +[[:space:]]+ +{ID} yylval.s = yytext; return 1; +{AND} return 2; +{OR} return 3; +{NOT} return 4; + +%% diff --git a/tests/auto/blackbox/testdata/lexyacc/lex_outfile/parser.y b/tests/auto/blackbox/testdata/lexyacc/lex_outfile/parser.y new file mode 100644 index 00000000..e19c9a90 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/lex_outfile/parser.y @@ -0,0 +1,30 @@ +%{ +#include +%} + +%type expr +%left OR +%left AND +%nonassoc NOT +%token ID + +%% + +start: expr { root = $1; } +expr: expr AND expr { auto t = std::make_shared(); t->val = "AND"; t->children = { $1, $3 }; $$ = t; } + | expr OR expr { auto t = std::make_shared(); t->val = "OR"; t->children = { $1, $3 }; $$ = t; } + | NOT expr { auto t = std::make_shared(); t->val = "NOT"; t->children = { $2 }; $$ = t; } + | ID { auto t = std::make_shared(); t->val = $1; $$ = t; } + +%% + +TreePtr root; + +int main() +{ + yyparse(); + if (!root) + return 1; + root->print(); + std::cout << std::endl; +} diff --git a/tests/auto/blackbox/testdata/lexyacc/lex_outfile/types.h b/tests/auto/blackbox/testdata/lexyacc/lex_outfile/types.h new file mode 100644 index 00000000..12cafe89 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/lex_outfile/types.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include + +struct Tree; +using TreePtr = std::shared_ptr; +struct Tree { + std::string val; + std::vector children; + void print() const { + std::cout << val << ' '; + for (const TreePtr &t : children) + t->print(); + } +}; +struct YaccType { TreePtr t; std::string s; }; +#define YYSTYPE YaccType +extern TreePtr root; + +int yylex(); +void yyerror(const char *); diff --git a/tests/auto/blackbox/testdata/lexyacc/lex_prefix/lex_prefix.qbs b/tests/auto/blackbox/testdata/lexyacc/lex_prefix/lex_prefix.qbs new file mode 100644 index 00000000..61f76f4b --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/lex_prefix/lex_prefix.qbs @@ -0,0 +1,16 @@ +CppApplication { + qbsSearchPaths: ".." + Depends { name: "bisonhelper" } + Depends { name: "lex_yacc" } + lex_yacc.outputTag: "cpp" + lex_yacc.yaccFlags: ["-l"] + cpp.includePaths: [".", ".."] + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.7" + consoleApplication: true + files: [ + "lexer.l", + "parser.y", + "types.h", + ] +} diff --git a/tests/auto/blackbox/testdata/lexyacc/lex_prefix/lexer.l b/tests/auto/blackbox/testdata/lexyacc/lex_prefix/lexer.l new file mode 100644 index 00000000..cbbc79fb --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/lex_prefix/lexer.l @@ -0,0 +1,21 @@ +%option prefix="bla" +%{ +#include +void blaerror(const char *e) { std::cerr << e; } +extern "C" int blawrap() { return 1; } +extern BLASTYPE blalval; +%} + +ID [a-z]+ +AND "&&" +OR "||" +NOT "!" + +%% +[[:space:]]+ +{ID} blalval.s = blatext; return 1; +{AND} return 2; +{OR} return 3; +{NOT} return 4; + +%% diff --git a/tests/auto/blackbox/testdata/lexyacc/lex_prefix/parser.y b/tests/auto/blackbox/testdata/lexyacc/lex_prefix/parser.y new file mode 100644 index 00000000..17ec6700 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/lex_prefix/parser.y @@ -0,0 +1,31 @@ +%{ +#include +%} + +%define api.prefix {bla} +%type expr +%left OR +%left AND +%nonassoc NOT +%token ID + +%% + +start: expr { root = $1; } +expr: expr AND expr { auto t = std::make_shared(); t->val = "AND"; t->children = { $1, $3 }; $$ = t; } + | expr OR expr { auto t = std::make_shared(); t->val = "OR"; t->children = { $1, $3 }; $$ = t; } + | NOT expr { auto t = std::make_shared(); t->val = "NOT"; t->children = { $2 }; $$ = t; } + | ID { auto t = std::make_shared(); t->val = $1; $$ = t; } + +%% + +TreePtr root; + +int main() +{ + blaparse(); + if (!root) + return 1; + root->print(); + std::cout << std::endl; +} diff --git a/tests/auto/blackbox/testdata/lexyacc/lex_prefix/types.h b/tests/auto/blackbox/testdata/lexyacc/lex_prefix/types.h new file mode 100644 index 00000000..e0682141 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/lex_prefix/types.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include + +struct Tree; +using TreePtr = std::shared_ptr; +struct Tree { + std::string val; + std::vector children; + void print() const { + std::cout << val << ' '; + for (const TreePtr &t : children) + t->print(); + } +}; +struct YaccType { TreePtr t; std::string s; }; +#define BLASTYPE YaccType +extern TreePtr root; + +int blalex(); +void blaerror(const char *); diff --git a/tests/auto/blackbox/testdata/lexyacc/modules/bisonhelper/bisonhelper.qbs b/tests/auto/blackbox/testdata/lexyacc/modules/bisonhelper/bisonhelper.qbs new file mode 100644 index 00000000..449b130e --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/modules/bisonhelper/bisonhelper.qbs @@ -0,0 +1,19 @@ +import qbs +import qbs.Process + +Module { + Depends { name: "lex_yacc" } + Probe { + id: bisonProbe + property string yaccBinary: lex_yacc.yaccBinary + configure: { + var p = Process(); + found = p.exec(yaccBinary, ["-V"]) == 0 && p.readStdOut().contains("bison"); + p.close(); + } + } + Properties { + condition: bisonProbe.found + lex_yacc.yaccFlags: "-y" + } +} diff --git a/tests/auto/blackbox/testdata/lexyacc/one-grammar/lexer.l b/tests/auto/blackbox/testdata/lexyacc/one-grammar/lexer.l new file mode 100644 index 00000000..4ba41110 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/one-grammar/lexer.l @@ -0,0 +1,21 @@ +%{ +#include +#include +void yyerror(const char *e) { std::cerr << e; } +extern "C" int yywrap() { return 1; } +extern YYSTYPE yylval; +%} + +ID [a-z]+ +AND "&&" +OR "||" +NOT "!" + +%% +[[:space:]]+ +{ID} yylval.s = yytext; return ID; +{AND} return AND; +{OR} return OR; +{NOT} return NOT; + +%% diff --git a/tests/auto/blackbox/testdata/lexyacc/one-grammar/one-grammar.qbs b/tests/auto/blackbox/testdata/lexyacc/one-grammar/one-grammar.qbs new file mode 100644 index 00000000..ec4ede64 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/one-grammar/one-grammar.qbs @@ -0,0 +1,39 @@ +CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + qbsSearchPaths: ".." + Depends { name: "bisonhelper" } + Depends { name: "lex_yacc" } + lex_yacc.outputTag: "cpp" + lex_yacc.yaccFlags: ["-l"] + cpp.includePaths: [".", ".."] + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.7" + consoleApplication: true + Probe { + id: pathCheck + property string theDir: { + if (qbs.targetOS.contains("windows")) { + if (qbs.toolchain.contains("mingw")) + return cpp.toolchainInstallPath; + if (qbs.toolchain.contains("clang") && qbs.sysroot) + return qbs.sysroot + "/bin"; + } + } + configure: { + if (theDir) + console.info("add to PATH: " + theDir); + found = true; + } + } + + files: [ + "lexer.l", + "parser.y", + "types.h", + ] +} diff --git a/tests/auto/blackbox/testdata/lexyacc/one-grammar/parser.y b/tests/auto/blackbox/testdata/lexyacc/one-grammar/parser.y new file mode 100644 index 00000000..e19c9a90 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/one-grammar/parser.y @@ -0,0 +1,30 @@ +%{ +#include +%} + +%type expr +%left OR +%left AND +%nonassoc NOT +%token ID + +%% + +start: expr { root = $1; } +expr: expr AND expr { auto t = std::make_shared(); t->val = "AND"; t->children = { $1, $3 }; $$ = t; } + | expr OR expr { auto t = std::make_shared(); t->val = "OR"; t->children = { $1, $3 }; $$ = t; } + | NOT expr { auto t = std::make_shared(); t->val = "NOT"; t->children = { $2 }; $$ = t; } + | ID { auto t = std::make_shared(); t->val = $1; $$ = t; } + +%% + +TreePtr root; + +int main() +{ + yyparse(); + if (!root) + return 1; + root->print(); + std::cout << std::endl; +} diff --git a/tests/auto/blackbox/testdata/lexyacc/one-grammar/types.h b/tests/auto/blackbox/testdata/lexyacc/one-grammar/types.h new file mode 100644 index 00000000..12cafe89 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/one-grammar/types.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include + +struct Tree; +using TreePtr = std::shared_ptr; +struct Tree { + std::string val; + std::vector children; + void print() const { + std::cout << val << ' '; + for (const TreePtr &t : children) + t->print(); + } +}; +struct YaccType { TreePtr t; std::string s; }; +#define YYSTYPE YaccType +extern TreePtr root; + +int yylex(); +void yyerror(const char *); diff --git a/tests/auto/blackbox/testdata/lexyacc/two-grammars/g1.l b/tests/auto/blackbox/testdata/lexyacc/two-grammars/g1.l new file mode 100644 index 00000000..369f1c91 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/two-grammars/g1.l @@ -0,0 +1,15 @@ +%option noyywrap + +%{ +#include +#ifdef _MSC_BUILD +#pragma message("whatever") +#else +#pragma whatever +#endif +void g1error(const char *e) { } +%} + +%% + +%% diff --git a/tests/auto/blackbox/testdata/lexyacc/two-grammars/g1.y b/tests/auto/blackbox/testdata/lexyacc/two-grammars/g1.y new file mode 100644 index 00000000..402bf351 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/two-grammars/g1.y @@ -0,0 +1,7 @@ +%{ +int g1lex(); +%} + +%% + +expr: '1' diff --git a/tests/auto/blackbox/testdata/lexyacc/two-grammars/g2.l b/tests/auto/blackbox/testdata/lexyacc/two-grammars/g2.l new file mode 100644 index 00000000..918e60ad --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/two-grammars/g2.l @@ -0,0 +1,10 @@ +%option noyywrap + +%{ +#include +void g2error(const char *e) { } +%} + +%% + +%% diff --git a/tests/auto/blackbox/testdata/lexyacc/two-grammars/g2.y b/tests/auto/blackbox/testdata/lexyacc/two-grammars/g2.y new file mode 100644 index 00000000..402bf351 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/two-grammars/g2.y @@ -0,0 +1,7 @@ +%{ +int g1lex(); +%} + +%% + +expr: '1' diff --git a/tests/auto/blackbox/testdata/lexyacc/two-grammars/main.c b/tests/auto/blackbox/testdata/lexyacc/two-grammars/main.c new file mode 100644 index 00000000..0889bb4f --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/two-grammars/main.c @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int g1parse(); +int g2parse(); + +int main() +{ + g1parse(); + g2parse(); + return 0; +} diff --git a/tests/auto/blackbox/testdata/lexyacc/two-grammars/two-grammars.qbs b/tests/auto/blackbox/testdata/lexyacc/two-grammars/two-grammars.qbs new file mode 100644 index 00000000..7b0c1e51 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/two-grammars/two-grammars.qbs @@ -0,0 +1,12 @@ +CppApplication { + Depends { name: "lex_yacc" } + consoleApplication: true + cpp.includePaths: ".." + files: [ + "g1.l", + "g1.y", + "g2.l", + "g2.y", + "main.c", + ] +} diff --git a/tests/auto/blackbox/testdata/lexyacc/unistd.h b/tests/auto/blackbox/testdata/lexyacc/unistd.h new file mode 100644 index 00000000..d9abd4d2 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/unistd.h @@ -0,0 +1,11 @@ +#ifndef MY_UNISTD_H +#define MY_UNISTD_H + +#ifdef _MSC_BUILD +#include +#define isatty _isatty +#else +#include_next +#endif + +#endif diff --git a/tests/auto/blackbox/testdata/lexyacc/yacc_output/lexer.l b/tests/auto/blackbox/testdata/lexyacc/yacc_output/lexer.l new file mode 100644 index 00000000..600ff283 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/yacc_output/lexer.l @@ -0,0 +1,21 @@ +%{ +#include +#include +void yyerror(const char *e) { std::cerr << e; } +extern "C" int yywrap() { return 1; } +extern YYSTYPE yylval; +%} + +ID [a-z]+ +AND "&&" +OR "||" +NOT "!" + +%% +[[:space:]]+ +{ID} yylval.s = yytext; return ID; +{AND} return AND; +{OR} return OR; +{NOT} return NOT; + +%% diff --git a/tests/auto/blackbox/testdata/lexyacc/yacc_output/parser.y b/tests/auto/blackbox/testdata/lexyacc/yacc_output/parser.y new file mode 100644 index 00000000..111e20bc --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/yacc_output/parser.y @@ -0,0 +1,31 @@ +%{ +#include +%} + +%output "parser.cxx" +%type expr +%left OR +%left AND +%nonassoc NOT +%token ID + +%% + +start: expr { root = $1; } +expr: expr AND expr { auto t = std::make_shared(); t->val = "AND"; t->children = { $1, $3 }; $$ = t; } + | expr OR expr { auto t = std::make_shared(); t->val = "OR"; t->children = { $1, $3 }; $$ = t; } + | NOT expr { auto t = std::make_shared(); t->val = "NOT"; t->children = { $2 }; $$ = t; } + | ID { auto t = std::make_shared(); t->val = $1; $$ = t; } + +%% + +TreePtr root; + +int main() +{ + yyparse(); + if (!root) + return 1; + root->print(); + std::cout << std::endl; +} diff --git a/tests/auto/blackbox/testdata/lexyacc/yacc_output/types.h b/tests/auto/blackbox/testdata/lexyacc/yacc_output/types.h new file mode 100644 index 00000000..12cafe89 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/yacc_output/types.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include + +struct Tree; +using TreePtr = std::shared_ptr; +struct Tree { + std::string val; + std::vector children; + void print() const { + std::cout << val << ' '; + for (const TreePtr &t : children) + t->print(); + } +}; +struct YaccType { TreePtr t; std::string s; }; +#define YYSTYPE YaccType +extern TreePtr root; + +int yylex(); +void yyerror(const char *); diff --git a/tests/auto/blackbox/testdata/lexyacc/yacc_output/yacc_output.qbs b/tests/auto/blackbox/testdata/lexyacc/yacc_output/yacc_output.qbs new file mode 100644 index 00000000..7e89b317 --- /dev/null +++ b/tests/auto/blackbox/testdata/lexyacc/yacc_output/yacc_output.qbs @@ -0,0 +1,14 @@ +CppApplication { + Depends { name: "lex_yacc" } + lex_yacc.outputTag: "cpp" + lex_yacc.yaccFlags: ["-l"] + cpp.includePaths: [".", ".."] + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.7" + consoleApplication: true + files: [ + "lexer.l", + "parser.y", + "types.h", + ] +} diff --git a/tests/auto/blackbox/testdata/linker-library-duplicates/lib1.cpp b/tests/auto/blackbox/testdata/linker-library-duplicates/lib1.cpp new file mode 100644 index 00000000..f2db0a75 --- /dev/null +++ b/tests/auto/blackbox/testdata/linker-library-duplicates/lib1.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT void lib1Func() { } diff --git a/tests/auto/blackbox/testdata/linker-library-duplicates/lib2.cpp b/tests/auto/blackbox/testdata/linker-library-duplicates/lib2.cpp new file mode 100644 index 00000000..4042b2d3 --- /dev/null +++ b/tests/auto/blackbox/testdata/linker-library-duplicates/lib2.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT void lib2Func() { } diff --git a/tests/auto/blackbox/testdata/linker-library-duplicates/lib3.cpp b/tests/auto/blackbox/testdata/linker-library-duplicates/lib3.cpp new file mode 100644 index 00000000..0f0183f6 --- /dev/null +++ b/tests/auto/blackbox/testdata/linker-library-duplicates/lib3.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT void lib3Func() { } diff --git a/tests/auto/blackbox/testdata/linker-library-duplicates/main.cpp b/tests/auto/blackbox/testdata/linker-library-duplicates/main.cpp new file mode 100644 index 00000000..ab698e90 --- /dev/null +++ b/tests/auto/blackbox/testdata/linker-library-duplicates/main.cpp @@ -0,0 +1,13 @@ +#include "../dllexport.h" + +DLL_IMPORT void lib1Func(); +DLL_IMPORT void lib2Func(); +DLL_IMPORT void lib3Func(); + +int main() +{ + lib1Func(); + lib2Func(); + lib3Func(); + return 0; +} diff --git a/tests/auto/blackbox/testdata/linker-library-duplicates/setup-run-environment.qbs b/tests/auto/blackbox/testdata/linker-library-duplicates/setup-run-environment.qbs new file mode 100644 index 00000000..9723fd3f --- /dev/null +++ b/tests/auto/blackbox/testdata/linker-library-duplicates/setup-run-environment.qbs @@ -0,0 +1,48 @@ +import qbs 1.0 + +Project { + DynamicLibrary { + id: idLib1 + name: "lib1" + Depends { name: "cpp" } + files: ["lib1.cpp"] + Depends { name: "bundle" } + bundle.isBundle: false + } + + DynamicLibrary { + id: idLib2 + name: "lib2" + Depends { name: "cpp" } + files: ["lib2.cpp"] + Depends { name: "bundle" } + bundle.isBundle: false + } + + DynamicLibrary { + id: idLib3 + name: "lib3" + Depends { name: "cpp" } + files: ["lib3.cpp"] + Depends { name: "bundle" } + bundle.isBundle: false + } + + CppApplication { + name: "main" + files: "main.cpp" + + Depends { name: "lib1"; cpp.link: false } + Depends { name: "lib2"; cpp.link: false } + Depends { name: "lib3"; cpp.link: false } + + cpp.libraryPaths: [ + idLib1.buildDirectory, + idLib2.buildDirectory, + idLib3.buildDirectory + ] + cpp.dynamicLibraries: [ + "lib1", "lib2", "lib1", "lib3", "lib2", "lib1" + ] + } +} diff --git a/tests/auto/blackbox/testdata/linker-module-definition/linker-module-definition.qbs b/tests/auto/blackbox/testdata/linker-module-definition/linker-module-definition.qbs new file mode 100644 index 00000000..519b6e06 --- /dev/null +++ b/tests/auto/blackbox/testdata/linker-module-definition/linker-module-definition.qbs @@ -0,0 +1,20 @@ +import qbs + +Project { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + DynamicLibrary { + name: "testlib" + Depends { name: "cpp"} + files: ["testlib.cpp", "testlib.def"] + } + CppApplication { + name: "testapp" + Depends { name: "testlib"} + files: ["testapp.cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/linker-module-definition/testapp.cpp b/tests/auto/blackbox/testdata/linker-module-definition/testapp.cpp new file mode 100644 index 00000000..7cb5ee90 --- /dev/null +++ b/tests/auto/blackbox/testdata/linker-module-definition/testapp.cpp @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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. +** +****************************************************************************/ + +extern void foo(); +extern void bar(); + +int main() +{ + foo(); + bar(); + return 0; +} diff --git a/tests/auto/blackbox/testdata/linker-module-definition/testlib.cpp b/tests/auto/blackbox/testdata/linker-module-definition/testlib.cpp new file mode 100644 index 00000000..d1cfc253 --- /dev/null +++ b/tests/auto/blackbox/testdata/linker-module-definition/testlib.cpp @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** 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. +** +****************************************************************************/ + +#include + +void foo() +{ + printf("foo\n"); +} + +void bar() +{ + printf("bar\n"); +} diff --git a/tests/auto/blackbox/testdata/linker-module-definition/testlib.def b/tests/auto/blackbox/testdata/linker-module-definition/testlib.def new file mode 100644 index 00000000..36967ddd --- /dev/null +++ b/tests/auto/blackbox/testdata/linker-module-definition/testlib.def @@ -0,0 +1,3 @@ +EXPORTS +foo +bar diff --git a/tests/auto/blackbox/testdata/linker-variant/linker-variant.qbs b/tests/auto/blackbox/testdata/linker-variant/linker-variant.qbs new file mode 100644 index 00000000..57bd4ccb --- /dev/null +++ b/tests/auto/blackbox/testdata/linker-variant/linker-variant.qbs @@ -0,0 +1,20 @@ +CppApplication { + name: "p" + property string linkerVariant + Probe { + id: gccProbe + property bool isGcc: qbs.toolchain.contains("gcc") + configure: { + console.info("is GCC: " + isGcc); + if (isGcc) + found = true; + } + } + + Properties { + condition: gccProbe.found + cpp.linkerVariant: linkerVariant + } + + files: "main.cpp" +} diff --git a/tests/auto/blackbox/testdata/linker-variant/main.cpp b/tests/auto/blackbox/testdata/linker-variant/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/linker-variant/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/linkerMode/linkerMode.qbs b/tests/auto/blackbox/testdata/linkerMode/linkerMode.qbs new file mode 100644 index 00000000..1be50c0a --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerMode/linkerMode.qbs @@ -0,0 +1,80 @@ +Project { + CppApplication { + consoleApplication: true + name: "LinkedProduct-Assembly" + files: ["main.s"] + + cpp.linkerPath: cpp.compilerPathByLanguage["c"] + + Group { + fileTagsFilter: product.type + qbs.install: true + } + } + + CppApplication { + consoleApplication: true + name: "LinkedProduct-C" + files: ["main.c"] + + Group { + fileTagsFilter: product.type + qbs.install: true + } + } + + CppApplication { + condition: qbs.targetOS.contains("darwin") + + consoleApplication: true + name: "LinkedProduct-Objective-C" + files: ["main.m"] + + cpp.dynamicLibraries: ["ObjC"] + + Group { + fileTagsFilter: product.type + qbs.install: true + } + } + + CppApplication { + consoleApplication: true + name: "LinkedProduct-C++" + files: ["main.cpp"] + + Group { + fileTagsFilter: product.type + qbs.install: true + } + } + + CppApplication { + condition: qbs.targetOS.contains("darwin") + + consoleApplication: true + name: "LinkedProduct-Objective-C++" + files: ["main.mm"] + + cpp.dynamicLibraries: ["ObjC"] + + Group { + fileTagsFilter: product.type + qbs.install: true + } + } + + CppApplication { + Depends { name: "LinkedProduct-C++StaticLibrary" } + + name: "LinkedProduct-BlankApp" + files: ["staticmain.c"] + } + + StaticLibrary { + Depends { name: "cpp" } + + name: "LinkedProduct-C++StaticLibrary" + files: ["staticlib.cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/linkerMode/main.c b/tests/auto/blackbox/testdata/linkerMode/main.c new file mode 100644 index 00000000..4ef6de1d --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerMode/main.c @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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 + +int main() +{ + void *memory = malloc(8); + free(memory); + return 0; +} diff --git a/tests/auto/blackbox/testdata/linkerMode/main.cpp b/tests/auto/blackbox/testdata/linkerMode/main.cpp new file mode 100644 index 00000000..11062f6a --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerMode/main.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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 + +int main() +{ + std::string s = "Hello World"; + (void)s; + return 0; +} diff --git a/tests/auto/blackbox/testdata/linkerMode/main.m b/tests/auto/blackbox/testdata/linkerMode/main.m new file mode 100644 index 00000000..8db28e5b --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerMode/main.m @@ -0,0 +1,7 @@ +#include + +int main() +{ + sel_registerName("qbs"); + return 0; +} diff --git a/tests/auto/blackbox/testdata/linkerMode/main.mm b/tests/auto/blackbox/testdata/linkerMode/main.mm new file mode 100644 index 00000000..65ef0409 --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerMode/main.mm @@ -0,0 +1,10 @@ +#include +#include + +int main() +{ + std::string s = "Hello World"; + (void)s; + sel_registerName("qbs"); + return 0; +} diff --git a/tests/auto/blackbox/testdata/linkerMode/main.s b/tests/auto/blackbox/testdata/linkerMode/main.s new file mode 100644 index 00000000..ef88f8c5 --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerMode/main.s @@ -0,0 +1,5 @@ +.globl _main +.globl main + +_main: +main: diff --git a/tests/auto/blackbox/testdata/linkerMode/staticlib.cpp b/tests/auto/blackbox/testdata/linkerMode/staticlib.cpp new file mode 100644 index 00000000..ca791682 --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerMode/staticlib.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 + +extern "C" int cpp(); + +int cpp() +{ + std::cout << "Hello world" << std::endl; + return 0; +} diff --git a/tests/auto/blackbox/testdata/linkerMode/staticmain.c b/tests/auto/blackbox/testdata/linkerMode/staticmain.c new file mode 100644 index 00000000..bd41eec0 --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerMode/staticmain.c @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +extern int cpp(); + +int main() +{ + return cpp(); +} diff --git a/tests/auto/blackbox/testdata/linkerscripts/linkerscript1 b/tests/auto/blackbox/testdata/linkerscripts/linkerscript1 new file mode 100644 index 00000000..d0f2e9cf --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerscripts/linkerscript1 @@ -0,0 +1 @@ +TEST_SYMBOL1 = 0; diff --git a/tests/auto/blackbox/testdata/linkerscripts/linkerscript2 b/tests/auto/blackbox/testdata/linkerscripts/linkerscript2 new file mode 100644 index 00000000..62832ba0 --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerscripts/linkerscript2 @@ -0,0 +1 @@ +TEST_SYMBOL2 = 1; diff --git a/tests/auto/blackbox/testdata/linkerscripts/linkerscript_recursive b/tests/auto/blackbox/testdata/linkerscripts/linkerscript_recursive new file mode 100644 index 00000000..11bc411a --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerscripts/linkerscript_recursive @@ -0,0 +1 @@ +TEST_SYMBOL_FROM_RECURSIVE = 1; diff --git a/tests/auto/blackbox/testdata/linkerscripts/linkerscript_to_include b/tests/auto/blackbox/testdata/linkerscripts/linkerscript_to_include new file mode 100644 index 00000000..68c67eb6 --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerscripts/linkerscript_to_include @@ -0,0 +1,2 @@ +TEST_SYMBOL_FROM_INCLUDE = 1; +INCLUDE linkerscript_recursive diff --git a/tests/auto/blackbox/testdata/linkerscripts/linkerscripts.qbs b/tests/auto/blackbox/testdata/linkerscripts/linkerscripts.qbs new file mode 100644 index 00000000..0b4de0ab --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerscripts/linkerscripts.qbs @@ -0,0 +1,61 @@ +import qbs.TextFile + +DynamicLibrary { + type: base.concat("custom") + Depends { name: "cpp" } + files: ["testlib.c"] + Group { + name: "linker scripts" + files: [ + "linkerscript1", + "linkerscript2", + ] + fileTags: ["linkerscript"] + } + + cpp.libraryPaths: [ + product.sourceDirectory, // location of linkerscripts that are included + ] + + Rule { + multiplex: true + outputFileTags: "custom" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + console.warn("---" + product.cpp.nmPath + "---"); + } + return [cmd]; + } + } + + Rule { + multiplex: true + requiresInputs: false + Artifact { + filePath: product.buildDirectory + "/linkerscript_with_includes" + fileTags: ["linkerscript"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.sourcePath = product.sourceDirectory; + cmd.buildPath = product.buildDirectory; + cmd.sourceCode = function() { + var file = new TextFile(buildPath + "/linkerscript_with_includes", + TextFile.WriteOnly); + file.write("SEARCH_DIR(\"" + sourcePath + "/scripts\")\n" + + "INCLUDE linkerscript_to_include\n" + + "INCLUDE linkerscript_in_directory\n"); + file.close(); + } + cmd.highlight = "codegen"; + cmd.description = "generating linkerscript with SEARCH_DIR and INCLUDE"; + return [cmd]; + } + } + + qbs.installPrefix: "" + install: true + installDir: "" +} diff --git a/tests/auto/blackbox/testdata/linkerscripts/scripts/linkerscript_in_directory b/tests/auto/blackbox/testdata/linkerscripts/scripts/linkerscript_in_directory new file mode 100644 index 00000000..09815629 --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerscripts/scripts/linkerscript_in_directory @@ -0,0 +1 @@ +TEST_SYMBOL_FROM_DIRECTORY = 1; diff --git a/tests/auto/blackbox/testdata/linkerscripts/testlib.c b/tests/auto/blackbox/testdata/linkerscripts/testlib.c new file mode 100644 index 00000000..cd4792b5 --- /dev/null +++ b/tests/auto/blackbox/testdata/linkerscripts/testlib.c @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void dummy() {} 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 00000000..5431b937 --- /dev/null +++ b/tests/auto/blackbox/testdata/list-products/list-products.qbs @@ -0,0 +1,14 @@ +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/auto/blackbox/testdata/list-properties-with-outer/dummy.txt b/tests/auto/blackbox/testdata/list-properties-with-outer/dummy.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/list-properties-with-outer/list-properties-with-outer.qbs b/tests/auto/blackbox/testdata/list-properties-with-outer/list-properties-with-outer.qbs new file mode 100644 index 00000000..ba157a7a --- /dev/null +++ b/tests/auto/blackbox/testdata/list-properties-with-outer/list-properties-with-outer.qbs @@ -0,0 +1,10 @@ +Product { + type: ["outtype"] + Depends { name: "higher" } + lower.listProp: ["product"] + Group { + files: ["dummy.txt"] + fileTags: ["intype"] + lower.listProp: outer.concat(["group"]) + } +} diff --git a/tests/auto/blackbox/testdata/list-properties-with-outer/modules/higher/higher.qbs b/tests/auto/blackbox/testdata/list-properties-with-outer/modules/higher/higher.qbs new file mode 100644 index 00000000..ddb7b6f4 --- /dev/null +++ b/tests/auto/blackbox/testdata/list-properties-with-outer/modules/higher/higher.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "lower" } + lower.listProp: ["higher"] +} diff --git a/tests/auto/blackbox/testdata/list-properties-with-outer/modules/lower/lower.qbs b/tests/auto/blackbox/testdata/list-properties-with-outer/modules/lower/lower.qbs new file mode 100644 index 00000000..63c97aa7 --- /dev/null +++ b/tests/auto/blackbox/testdata/list-properties-with-outer/modules/lower/lower.qbs @@ -0,0 +1,16 @@ +Module { + property stringList listProp + + Rule { + inputs: ["intype"] + outputFileTags: "outtype" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + console.info("listProp: " + JSON.stringify(input.lower.listProp)); + } + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/list-property-order/dummy.txt b/tests/auto/blackbox/testdata/list-property-order/dummy.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/list-property-order/modules/higher1/higher1.qbs b/tests/auto/blackbox/testdata/list-property-order/modules/higher1/higher1.qbs new file mode 100644 index 00000000..67c7c790 --- /dev/null +++ b/tests/auto/blackbox/testdata/list-property-order/modules/higher1/higher1.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "lower" } + lower.listProp: ["higher1"] +} diff --git a/tests/auto/blackbox/testdata/list-property-order/modules/higher2/higher2.qbs b/tests/auto/blackbox/testdata/list-property-order/modules/higher2/higher2.qbs new file mode 100644 index 00000000..3ca22f51 --- /dev/null +++ b/tests/auto/blackbox/testdata/list-property-order/modules/higher2/higher2.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "lower" } + lower.listProp: ["higher2"] +} diff --git a/tests/auto/blackbox/testdata/list-property-order/modules/higher3/higher3.qbs b/tests/auto/blackbox/testdata/list-property-order/modules/higher3/higher3.qbs new file mode 100644 index 00000000..f534cf8b --- /dev/null +++ b/tests/auto/blackbox/testdata/list-property-order/modules/higher3/higher3.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "lower" } + lower.listProp: ["higher3"] +} diff --git a/tests/auto/blackbox/testdata/list-property-order/modules/lower/lower.qbs b/tests/auto/blackbox/testdata/list-property-order/modules/lower/lower.qbs new file mode 100644 index 00000000..c47a40ae --- /dev/null +++ b/tests/auto/blackbox/testdata/list-property-order/modules/lower/lower.qbs @@ -0,0 +1,19 @@ +Module { + property stringList listProp: [ "lower" ] + + Rule { + inputs: ["intype"] + Artifact { + filePath: "dummy.out" + fileTags: "outtype" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.sourceCode = function() { + console.warn("listProp = " + JSON.stringify(product.lower.listProp)); + }; + cmd.silent = true; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/list-property-order/product.qbs b/tests/auto/blackbox/testdata/list-property-order/product.qbs new file mode 100644 index 00000000..bec12221 --- /dev/null +++ b/tests/auto/blackbox/testdata/list-property-order/product.qbs @@ -0,0 +1,12 @@ +Product { + type: "outtype" + name: "toplevel" + Depends { name: "higher1" } + Depends { name: "higher2" } + Depends { name: "higher3" } + lower.listProp: ["product"] + Group { + files: ["dummy.txt"] + fileTags: ["intype"] + } +} diff --git a/tests/auto/blackbox/testdata/loadablemodule/exported.cpp b/tests/auto/blackbox/testdata/loadablemodule/exported.cpp new file mode 100644 index 00000000..919bd350 --- /dev/null +++ b/tests/auto/blackbox/testdata/loadablemodule/exported.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 "exported.h" + +extern "C" { + int foo() { + return 42; + } +} diff --git a/tests/auto/blackbox/testdata/loadablemodule/exported.h b/tests/auto/blackbox/testdata/loadablemodule/exported.h new file mode 100644 index 00000000..6a3f3c57 --- /dev/null +++ b/tests/auto/blackbox/testdata/loadablemodule/exported.h @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" + +extern "C" { + DLL_EXPORT int foo(); +} diff --git a/tests/auto/blackbox/testdata/loadablemodule/loadablemodule.qbs b/tests/auto/blackbox/testdata/loadablemodule/loadablemodule.qbs new file mode 100644 index 00000000..5749480c --- /dev/null +++ b/tests/auto/blackbox/testdata/loadablemodule/loadablemodule.qbs @@ -0,0 +1,43 @@ +Project { + LoadableModule { + Depends { name: "cpp" } + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + name: "CoolPlugIn" + files: ["exported.cpp", "exported.h"] + + Group { + fileTagsFilter: product.type + qbs.install: true + } + } + + CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + Depends { name: "cpp" } + Depends { name: "CoolPlugIn"; cpp.link: false } + consoleApplication: true + name: "CoolApp" + files: ["main.cpp"] + + cpp.cxxLanguageVersion: "c++11" + cpp.dynamicLibraries: [qbs.targetOS.contains("windows") ? "kernel32" : "dl"] + + Properties { + condition: qbs.targetOS.contains("unix") && !qbs.targetOS.contains("darwin") + cpp.rpaths: [cpp.rpathOrigin] + } + + Group { + fileTagsFilter: product.type + qbs.install: true + } + } +} diff --git a/tests/auto/blackbox/testdata/loadablemodule/main.cpp b/tests/auto/blackbox/testdata/loadablemodule/main.cpp new file mode 100644 index 00000000..44248044 --- /dev/null +++ b/tests/auto/blackbox/testdata/loadablemodule/main.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 Jake Petroules. +** Contact: https://www.qt.io/licensing/ +** +** This file is part 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 + +#ifdef _WIN32 +#include +#define PREFIX "" +#define SUFFIX ".dll" +#define dlopen(path, mode) LoadLibraryA(path) +#define dlsym(handle, symbol) GetProcAddress(handle, symbol) +#define dlclose(handle) FreeLibrary(handle) +#elif defined(__APPLE__) +#define PREFIX "" +#define SUFFIX ".bundle" +#else +#define PREFIX "lib" +#define SUFFIX ".so" +#endif + +#ifndef _WIN32 +#include +#endif + +int main() { + auto lib = dlopen(PREFIX "CoolPlugIn" SUFFIX, RTLD_LAZY); + if (lib) { + auto fptr = dlsym(lib, "foo"); + if (fptr) + std::cout << "foo = " << ((int (*)(void))fptr)() << std::endl; + else + std::cout << "function foo not found in CoolPlugIn" << std::endl; + dlclose(lib); + return fptr ? 0 : 1; + } else { + std::cout << "CoolPlugIn not loaded" << std::endl; + } + return 1; +} diff --git a/tests/auto/blackbox/testdata/localDeployment/localDeployment.qbs b/tests/auto/blackbox/testdata/localDeployment/localDeployment.qbs new file mode 100644 index 00000000..85664268 --- /dev/null +++ b/tests/auto/blackbox/testdata/localDeployment/localDeployment.qbs @@ -0,0 +1,30 @@ +Project { + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + type: ["application"] + consoleApplication: true + name: "HelloWorld" + destinationDirectory: "bin" + + Depends { name: "cpp" } + cpp.cxxLanguageVersion: "c++11" + + Group { + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: "bin" + } + + Group { + qbs.install: true + qbs.installDir: "share" + files: ['main.cpp'] + } + } +} + diff --git a/tests/auto/blackbox/testdata/localDeployment/main.cpp b/tests/auto/blackbox/testdata/localDeployment/main.cpp new file mode 100644 index 00000000..c991b6d2 --- /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 +#include +#include + +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(in)), + std::istreambuf_iterator()); + std::cout << str << std::endl; + return 0; +} diff --git a/tests/auto/blackbox/testdata/makefile-generator/app.qbs b/tests/auto/blackbox/testdata/makefile-generator/app.qbs new file mode 100644 index 00000000..2f53c480 --- /dev/null +++ b/tests/auto/blackbox/testdata/makefile-generator/app.qbs @@ -0,0 +1,22 @@ +CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: "the app" + consoleApplication: true + + cpp.cxxLanguageVersion: "c++11" + cpp.separateDebugInformation: false + Properties { + condition: qbs.targetOS.contains("macos") + bundle.embedInfoPlist: false + cpp.minimumMacosVersion: "10.7" + } + + files: "main.cpp" + qbs.installPrefix: "/usr/local" + install: true +} diff --git a/tests/auto/blackbox/testdata/makefile-generator/main.cpp b/tests/auto/blackbox/testdata/makefile-generator/main.cpp new file mode 100644 index 00000000..7f95e64b --- /dev/null +++ b/tests/auto/blackbox/testdata/makefile-generator/main.cpp @@ -0,0 +1,6 @@ +#include + +int main() +{ + std::cout << "Hello, World!" << std::endl; +} diff --git a/tests/auto/blackbox/testdata/maximum-c-language-version/main.c b/tests/auto/blackbox/testdata/maximum-c-language-version/main.c new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/blackbox/testdata/maximum-c-language-version/main.c @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/maximum-c-language-version/maximum-c-language-version.qbs b/tests/auto/blackbox/testdata/maximum-c-language-version/maximum-c-language-version.qbs new file mode 100644 index 00000000..320494d0 --- /dev/null +++ b/tests/auto/blackbox/testdata/maximum-c-language-version/maximum-c-language-version.qbs @@ -0,0 +1,20 @@ +CppApplication { + name: "app" + property bool enableNewestModule: true + + Probe { + id: osProbe + property stringList toolchain: qbs.toolchain + configure: { + if (toolchain.contains("msvc")) + console.info("is msvc"); + found = true; + } + } + + Depends { name: "oldmodule" } + Depends { name: "newermodule" } + Depends { name: "newestmodule"; condition: enableNewestModule } + + files: "main.c" +} diff --git a/tests/auto/blackbox/testdata/maximum-c-language-version/modules/newermodule/newermodule.qbs b/tests/auto/blackbox/testdata/maximum-c-language-version/modules/newermodule/newermodule.qbs new file mode 100644 index 00000000..d5b015e4 --- /dev/null +++ b/tests/auto/blackbox/testdata/maximum-c-language-version/modules/newermodule/newermodule.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "cpp" } + cpp.cLanguageVersion: "c99" +} diff --git a/tests/auto/blackbox/testdata/maximum-c-language-version/modules/newestmodule/newestmodule.qbs b/tests/auto/blackbox/testdata/maximum-c-language-version/modules/newestmodule/newestmodule.qbs new file mode 100644 index 00000000..5437957f --- /dev/null +++ b/tests/auto/blackbox/testdata/maximum-c-language-version/modules/newestmodule/newestmodule.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "cpp" } + cpp.cLanguageVersion: "c11" +} diff --git a/tests/auto/blackbox/testdata/maximum-c-language-version/modules/oldmodule/oldmodule.qbs b/tests/auto/blackbox/testdata/maximum-c-language-version/modules/oldmodule/oldmodule.qbs new file mode 100644 index 00000000..10e53009 --- /dev/null +++ b/tests/auto/blackbox/testdata/maximum-c-language-version/modules/oldmodule/oldmodule.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "cpp" } + cpp.cLanguageVersion: "c90" +} diff --git a/tests/auto/blackbox/testdata/maximum-cxx-language-version/main.cpp b/tests/auto/blackbox/testdata/maximum-cxx-language-version/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/maximum-cxx-language-version/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/maximum-cxx-language-version/maximum-cxx-language-version.qbs b/tests/auto/blackbox/testdata/maximum-cxx-language-version/maximum-cxx-language-version.qbs new file mode 100644 index 00000000..71933cf6 --- /dev/null +++ b/tests/auto/blackbox/testdata/maximum-cxx-language-version/maximum-cxx-language-version.qbs @@ -0,0 +1,10 @@ +CppApplication { + name: "app" + property bool enableNewestModule: true + + Depends { name: "oldmodule" } + Depends { name: "newermodule" } + Depends { name: "newestmodule"; condition: enableNewestModule } + + files: "main.cpp" +} diff --git a/tests/auto/blackbox/testdata/maximum-cxx-language-version/modules/newermodule/newermodule.qbs b/tests/auto/blackbox/testdata/maximum-cxx-language-version/modules/newermodule/newermodule.qbs new file mode 100644 index 00000000..88b1da21 --- /dev/null +++ b/tests/auto/blackbox/testdata/maximum-cxx-language-version/modules/newermodule/newermodule.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "cpp" } + cpp.cxxLanguageVersion: "c++14" +} diff --git a/tests/auto/blackbox/testdata/maximum-cxx-language-version/modules/newestmodule/newestmodule.qbs b/tests/auto/blackbox/testdata/maximum-cxx-language-version/modules/newestmodule/newestmodule.qbs new file mode 100644 index 00000000..f99932b1 --- /dev/null +++ b/tests/auto/blackbox/testdata/maximum-cxx-language-version/modules/newestmodule/newestmodule.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "cpp" } + cpp.cxxLanguageVersion: "c++17" +} diff --git a/tests/auto/blackbox/testdata/maximum-cxx-language-version/modules/oldmodule/oldmodule.qbs b/tests/auto/blackbox/testdata/maximum-cxx-language-version/modules/oldmodule/oldmodule.qbs new file mode 100644 index 00000000..73d3bf16 --- /dev/null +++ b/tests/auto/blackbox/testdata/maximum-cxx-language-version/modules/oldmodule/oldmodule.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "cpp" } + cpp.cxxLanguageVersion: "c++11" +} diff --git a/tests/auto/blackbox/testdata/minimumSystemVersion/fakewindows.qbs b/tests/auto/blackbox/testdata/minimumSystemVersion/fakewindows.qbs new file mode 100644 index 00000000..1a56e0b7 --- /dev/null +++ b/tests/auto/blackbox/testdata/minimumSystemVersion/fakewindows.qbs @@ -0,0 +1,15 @@ +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 00000000..8ae787b9 --- /dev/null +++ b/tests/auto/blackbox/testdata/minimumSystemVersion/macappstore.qbs @@ -0,0 +1,15 @@ +// 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: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result && qbs.targetOS.contains("macos"); + } + files: ["main.mm"] + consoleApplication: true + cpp.frameworks: "Foundation" + cpp.minimumMacosVersion: "10.7.1" +} diff --git a/tests/auto/blackbox/testdata/minimumSystemVersion/main.cpp b/tests/auto/blackbox/testdata/minimumSystemVersion/main.cpp new file mode 100644 index 00000000..47c9ed00 --- /dev/null +++ b/tests/auto/blackbox/testdata/minimumSystemVersion/main.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include +#include +#include +#endif + +int main(int argc, char *argv[]) +{ + if (argc != 1) + return 1; + +#ifdef _WIN32 +#if defined(__clang__) + std::cout << "Unsupported compiler" << std::endl; + return 0; +#endif +#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(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 00000000..23d31c85 --- /dev/null +++ b/tests/auto/blackbox/testdata/minimumSystemVersion/main.mm @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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 +#import + +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; + continue; + } + +#ifdef __clang__ +#if __clang_major__ >= 10 && __clang_minor__ >= 0 + if ([line rangeOfString:@"LC_BUILD_VERSION"].location != NSNotFound) + print = true; + + if (print && [line rangeOfString:@"minos"].location != NSNotFound) { + std::cout << [[line stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] UTF8String] << std::endl; + print = false; + } +#endif +#endif // __clang__ + } + +} diff --git a/tests/auto/blackbox/testdata/minimumSystemVersion/specific.qbs b/tests/auto/blackbox/testdata/minimumSystemVersion/specific.qbs new file mode 100644 index 00000000..8099d79b --- /dev/null +++ b/tests/auto/blackbox/testdata/minimumSystemVersion/specific.qbs @@ -0,0 +1,30 @@ +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: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result && 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.7" + } +} 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 00000000..c0b70a0b --- /dev/null +++ b/tests/auto/blackbox/testdata/minimumSystemVersion/unspecified-forced.qbs @@ -0,0 +1,28 @@ +import qbs.Utilities + +// no minimum versions are specified, and explicitly set to undefined in +// case the profile has set it +CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + 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 00000000..a27ffcd2 --- /dev/null +++ b/tests/auto/blackbox/testdata/minimumSystemVersion/unspecified.qbs @@ -0,0 +1,23 @@ +import qbs.Utilities + +// no minimum versions are specified so the profile defaults will be used +CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + 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/missing-dependency/main.cpp b/tests/auto/blackbox/testdata/missing-dependency/main.cpp new file mode 100644 index 00000000..7751df91 --- /dev/null +++ b/tests/auto/blackbox/testdata/missing-dependency/main.cpp @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** 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 + +int main() +{ +} diff --git a/tests/auto/blackbox/testdata/missing-dependency/missing-dependency.qbs b/tests/auto/blackbox/testdata/missing-dependency/missing-dependency.qbs new file mode 100644 index 00000000..6bb8b96f --- /dev/null +++ b/tests/auto/blackbox/testdata/missing-dependency/missing-dependency.qbs @@ -0,0 +1,32 @@ +import qbs.TextFile + +Project { + Product { + name: "theDep" + type: ["genheader"] + + Rule { + multiplex: true + Artifact { + filePath: project.buildDirectory + "/theHeader.h" + fileTags: product.type + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + } + return [cmd]; + } + } + } + CppApplication { + name: "theApp" + cpp.includePaths: [project.buildDirectory] + files: ["main.cpp"] + } +} + + diff --git a/tests/auto/blackbox/testdata/missing-override-prefix/missing-override-prefix.qbs b/tests/auto/blackbox/testdata/missing-override-prefix/missing-override-prefix.qbs new file mode 100644 index 00000000..86718b57 --- /dev/null +++ b/tests/auto/blackbox/testdata/missing-override-prefix/missing-override-prefix.qbs @@ -0,0 +1 @@ +Product { } diff --git a/tests/auto/blackbox/testdata/missing-project-file/ambiguous-dir/p1.qbs b/tests/auto/blackbox/testdata/missing-project-file/ambiguous-dir/p1.qbs new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/missing-project-file/ambiguous-dir/p2.qbs b/tests/auto/blackbox/testdata/missing-project-file/ambiguous-dir/p2.qbs new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/missing-project-file/ambiguous-dir/p3.qbs b/tests/auto/blackbox/testdata/missing-project-file/ambiguous-dir/p3.qbs new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/missing-project-file/empty-dir/irrelevant.txt b/tests/auto/blackbox/testdata/missing-project-file/empty-dir/irrelevant.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/missing-project-file/project-dir/file.cpp b/tests/auto/blackbox/testdata/missing-project-file/project-dir/file.cpp new file mode 100644 index 00000000..8101b05d --- /dev/null +++ b/tests/auto/blackbox/testdata/missing-project-file/project-dir/file.cpp @@ -0,0 +1 @@ +void f() { } diff --git a/tests/auto/blackbox/testdata/missing-project-file/project-dir/main.cpp b/tests/auto/blackbox/testdata/missing-project-file/project-dir/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/missing-project-file/project-dir/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/missing-project-file/project-dir/missing-project-file.qbs b/tests/auto/blackbox/testdata/missing-project-file/project-dir/missing-project-file.qbs new file mode 100644 index 00000000..421bad1f --- /dev/null +++ b/tests/auto/blackbox/testdata/missing-project-file/project-dir/missing-project-file.qbs @@ -0,0 +1,6 @@ +CppApplication { + files: [ + "file.cpp", + "main.cpp" + ] +} diff --git a/tests/auto/blackbox/testdata/module-conditions/module-conditions.qbs b/tests/auto/blackbox/testdata/module-conditions/module-conditions.qbs new file mode 100644 index 00000000..dc376820 --- /dev/null +++ b/tests/auto/blackbox/testdata/module-conditions/module-conditions.qbs @@ -0,0 +1,21 @@ +import qbs + +Project { + Product { + name: "p1" + qbs.architecture: "a" + Depends { name: "m" } + } + Product { + name: "p2" + qbs.architecture: "b" + Depends { name: "m" } + } + Product { + name: "p3" + multiplexByQbsProperties: "architectures" + aggregate: false + qbs.architectures: ["b", "c", "d"] + Depends { name: "m" } + } +} diff --git a/tests/auto/blackbox/testdata/module-conditions/modules/m/m1.qbs b/tests/auto/blackbox/testdata/module-conditions/modules/m/m1.qbs new file mode 100644 index 00000000..884350c3 --- /dev/null +++ b/tests/auto/blackbox/testdata/module-conditions/modules/m/m1.qbs @@ -0,0 +1,6 @@ +Module { + condition: qbs.architecture === "a" + validate: { + console.info("loaded m1"); + } +} diff --git a/tests/auto/blackbox/testdata/module-conditions/modules/m/m2.qbs b/tests/auto/blackbox/testdata/module-conditions/modules/m/m2.qbs new file mode 100644 index 00000000..bcec6f42 --- /dev/null +++ b/tests/auto/blackbox/testdata/module-conditions/modules/m/m2.qbs @@ -0,0 +1,6 @@ +Module { + condition: qbs.architecture === "b" + validate: { + console.info("loaded m2"); + } +} diff --git a/tests/auto/blackbox/testdata/module-conditions/modules/m/m3.qbs b/tests/auto/blackbox/testdata/module-conditions/modules/m/m3.qbs new file mode 100644 index 00000000..5453c617 --- /dev/null +++ b/tests/auto/blackbox/testdata/module-conditions/modules/m/m3.qbs @@ -0,0 +1,6 @@ +Module { + condition: qbs.architecture === "c" + validate: { + console.info("loaded m3"); + } +} diff --git a/tests/auto/blackbox/testdata/module-conditions/modules/m/m4.qbs b/tests/auto/blackbox/testdata/module-conditions/modules/m/m4.qbs new file mode 100644 index 00000000..a4cb0350 --- /dev/null +++ b/tests/auto/blackbox/testdata/module-conditions/modules/m/m4.qbs @@ -0,0 +1,6 @@ +Module { + condition: qbs.architecture === "d" + validate: { + console.info("loaded m4"); + } +} diff --git a/tests/auto/blackbox/testdata/module-providers/main.cpp b/tests/auto/blackbox/testdata/module-providers/main.cpp new file mode 100644 index 00000000..85a4f551 --- /dev/null +++ b/tests/auto/blackbox/testdata/module-providers/main.cpp @@ -0,0 +1,8 @@ +#include + +int main() +{ + std::cout << "The letters are " << LETTER1 << " and " << LETTER2 << std::endl; + std::cout << "The MY_DEFINE is " << MY_DEFINE << std::endl; + return 0; +} diff --git a/tests/auto/blackbox/testdata/module-providers/module-providers.qbs b/tests/auto/blackbox/testdata/module-providers/module-providers.qbs new file mode 100644 index 00000000..0322cc0e --- /dev/null +++ b/tests/auto/blackbox/testdata/module-providers/module-providers.qbs @@ -0,0 +1,32 @@ +Project { + property bool enabled: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + readonly property string beginning: "beginning" + CppApplication { + name: "app1" + Depends { name: "mygenerator.module1" } + Depends { name: "mygenerator.module2" } + Depends { name: "othergenerator" } + moduleProviders.mygenerator.chooseLettersFrom: project.beginning + moduleProviders.othergenerator.someDefines: name + files: "main.cpp" + } + CppApplication { + readonly property string end: "end" + name: "app2" + Depends { name: "mygenerator.module1" } + Depends { name: "mygenerator.module2" } + Depends { name: "othergenerator" } + Profile { + name: "myProfile" + moduleProviders.mygenerator.chooseLettersFrom: product.end + moduleProviders.othergenerator.someDefines: "app2" + } + qbs.profile: "myProfile" + files: "main.cpp" + } +} diff --git a/tests/auto/blackbox/testdata/module-providers/module-providers/mygenerator/provider.qbs b/tests/auto/blackbox/testdata/module-providers/module-providers/mygenerator/provider.qbs new file mode 100644 index 00000000..dae02c03 --- /dev/null +++ b/tests/auto/blackbox/testdata/module-providers/module-providers/mygenerator/provider.qbs @@ -0,0 +1,31 @@ +import qbs.File; +import qbs.FileInfo; +import qbs.TextFile; + +ModuleProvider { + property string chooseLettersFrom + relativeSearchPaths: { + console.info("Running setup script for " + name); + var startAtBeginning = chooseLettersFrom === "beginning"; + var moduleBaseDir = FileInfo.joinPaths(outputBaseDir, "modules", "mygenerator"); + var module1Dir = FileInfo.joinPaths(moduleBaseDir, "module1"); + File.makePath(module1Dir); + var module1 = new TextFile(FileInfo.joinPaths(module1Dir, "module1.qbs"), TextFile.WriteOnly); + module1.writeLine("Module {"); + module1.writeLine(" Depends { name: 'cpp' }"); + module1.writeLine(" cpp.defines: 'LETTER1=" + (startAtBeginning ? "\\\'A\\\'" : "\\\'Z\\\'") + + "'"); + module1.writeLine("}"); + module1.close(); + var module2Dir = FileInfo.joinPaths(moduleBaseDir, "module2"); + File.makePath(module2Dir); + var module2 = new TextFile(FileInfo.joinPaths(module2Dir, "module2.qbs"), TextFile.WriteOnly); + module2.writeLine("Module {"); + module2.writeLine(" Depends { name: 'cpp' }"); + module2.writeLine(" cpp.defines: 'LETTER2=" + (startAtBeginning ? "\\\'B\\\'" : "\\\'Y\\\'") + + "'"); + module2.writeLine("}"); + module2.close(); + return ""; + } +} diff --git a/tests/auto/blackbox/testdata/module-providers/module-providers/othergenerator/provider.qbs b/tests/auto/blackbox/testdata/module-providers/module-providers/othergenerator/provider.qbs new file mode 100644 index 00000000..66557037 --- /dev/null +++ b/tests/auto/blackbox/testdata/module-providers/module-providers/othergenerator/provider.qbs @@ -0,0 +1,19 @@ +import qbs.File; +import qbs.FileInfo; +import qbs.TextFile; + +ModuleProvider { + property string someDefines + relativeSearchPaths: { + console.info("Running setup script for " + name); + var moduleDir = FileInfo.joinPaths(outputBaseDir, "modules", "othergenerator"); + File.makePath(moduleDir); + var module = new TextFile(FileInfo.joinPaths(moduleDir, "module.qbs"), TextFile.WriteOnly); + module.writeLine("Module {"); + module.writeLine(" Depends { name: 'cpp' }"); + module.writeLine(" cpp.defines: 'MY_DEFINE=\"" + someDefines + "\"'"); + module.writeLine("}"); + module.close(); + return ""; + } +} diff --git a/tests/auto/blackbox/testdata/moved-file-dependency/main.cpp b/tests/auto/blackbox/testdata/moved-file-dependency/main.cpp new file mode 100644 index 00000000..3e89e9f2 --- /dev/null +++ b/tests/auto/blackbox/testdata/moved-file-dependency/main.cpp @@ -0,0 +1,3 @@ +#include + +int main() {} diff --git a/tests/auto/blackbox/testdata/moved-file-dependency/moved-file-dependency.qbs b/tests/auto/blackbox/testdata/moved-file-dependency/moved-file-dependency.qbs new file mode 100644 index 00000000..79719868 --- /dev/null +++ b/tests/auto/blackbox/testdata/moved-file-dependency/moved-file-dependency.qbs @@ -0,0 +1,4 @@ +CppApplication { + cpp.includePaths: ["subdir1", "subdir2"] + files: ["main.cpp"] +} diff --git a/tests/auto/blackbox/testdata/moved-file-dependency/subdir1/theheader.h b/tests/auto/blackbox/testdata/moved-file-dependency/subdir1/theheader.h new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/multiple-changes/dummy.txt b/tests/auto/blackbox/testdata/multiple-changes/dummy.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/multiple-changes/multiple-changes.qbs b/tests/auto/blackbox/testdata/multiple-changes/multiple-changes.qbs new file mode 100644 index 00000000..51e6b1a5 --- /dev/null +++ b/tests/auto/blackbox/testdata/multiple-changes/multiple-changes.qbs @@ -0,0 +1,31 @@ +Project { + property bool prop: false + Product { + name: "test" + type: ["out-type"] + Group { + name: "Rule input" + files: ["dummy.txt"] + fileTags: ["in-type"] + } + Group { + name: "irrelevant" + files: ["*.blubb"] + } + Rule { + inputs: ["in-type"] + Artifact { + filePath: "dummy.out" + fileTags: product.type + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + console.info("prop: " + project.prop); + } + return [cmd]; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/multiple-configurations/file.cpp b/tests/auto/blackbox/testdata/multiple-configurations/file.cpp new file mode 100644 index 00000000..44ebc2ee --- /dev/null +++ b/tests/auto/blackbox/testdata/multiple-configurations/file.cpp @@ -0,0 +1,6 @@ +#include + +void f() +{ + l(); +} diff --git a/tests/auto/blackbox/testdata/multiple-configurations/file.h b/tests/auto/blackbox/testdata/multiple-configurations/file.h new file mode 100644 index 00000000..789447c0 --- /dev/null +++ b/tests/auto/blackbox/testdata/multiple-configurations/file.h @@ -0,0 +1 @@ +void f(); diff --git a/tests/auto/blackbox/testdata/multiple-configurations/lib.cpp b/tests/auto/blackbox/testdata/multiple-configurations/lib.cpp new file mode 100644 index 00000000..9a614565 --- /dev/null +++ b/tests/auto/blackbox/testdata/multiple-configurations/lib.cpp @@ -0,0 +1 @@ +void l() {} diff --git a/tests/auto/blackbox/testdata/multiple-configurations/lib.h b/tests/auto/blackbox/testdata/multiple-configurations/lib.h new file mode 100644 index 00000000..f8be99ce --- /dev/null +++ b/tests/auto/blackbox/testdata/multiple-configurations/lib.h @@ -0,0 +1 @@ +void l(); diff --git a/tests/auto/blackbox/testdata/multiple-configurations/main.cpp b/tests/auto/blackbox/testdata/multiple-configurations/main.cpp new file mode 100644 index 00000000..4fa5b1f9 --- /dev/null +++ b/tests/auto/blackbox/testdata/multiple-configurations/main.cpp @@ -0,0 +1,6 @@ +#include + +int main() +{ + f(); +} diff --git a/tests/auto/blackbox/testdata/multiple-configurations/multiple-configurations.qbs b/tests/auto/blackbox/testdata/multiple-configurations/multiple-configurations.qbs new file mode 100644 index 00000000..f6fd16ba --- /dev/null +++ b/tests/auto/blackbox/testdata/multiple-configurations/multiple-configurations.qbs @@ -0,0 +1,13 @@ +Project { + StaticLibrary { + name: "lib" + Depends { name: "cpp" } + files: ["lib.cpp", "lib.h"] + } + CppApplication { + name: "app" + Depends { name: "lib" } + cpp.includePaths: project.sourceDirectory + files: ["file.cpp", "file.h", "main.cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/multiplexed-tool/multiplexed-tool.qbs b/tests/auto/blackbox/testdata/multiplexed-tool/multiplexed-tool.qbs new file mode 100644 index 00000000..3994bd95 --- /dev/null +++ b/tests/auto/blackbox/testdata/multiplexed-tool/multiplexed-tool.qbs @@ -0,0 +1,56 @@ +Project { + CppApplication { + name: "tool" + consoleApplication: true + Profile { + name: "debugProfile" + qbs.buildVariant: "debug" + } + Profile { + name: "releaseProfile" + qbs.buildVariant: "release" + } + multiplexByQbsProperties: "profiles" + qbs.profiles: ["debugProfile", "releaseProfile"] + files: "tool.cpp" + Properties { + condition: qbs.buildVariant === "debug" + cpp.defines: "WRONG_VARIANT" + } + Export { + Rule { + multiplex: true + inputsFromDependencies: "application" + Artifact { + filePath: "tool.out" + fileTags: "tool.output" + } + prepare: { + var cmd = new Command(input.filePath, []); + cmd.description = "creating " + output.fileName; + return cmd; + } + } + } + } + Product { + name: "p" + type: "tool.output" + multiplexByQbsProperties: "buildVariants" + qbs.buildVariants: ["debug", "release"] + Depends { name: "tool"; profiles: "releaseProfile" } + } + Product { + name: "p2" + type: "tool.output" + multiplexByQbsProperties: "buildVariants" + qbs.buildVariants: ["debug", "release"] + Depends { name: "helper" } + } + Product { + name: "helper" + Export { + Depends { name: "tool"; profiles: "releaseProfile" } + } + } +} diff --git a/tests/auto/blackbox/testdata/multiplexed-tool/tool.cpp b/tests/auto/blackbox/testdata/multiplexed-tool/tool.cpp new file mode 100644 index 00000000..ac2e22ed --- /dev/null +++ b/tests/auto/blackbox/testdata/multiplexed-tool/tool.cpp @@ -0,0 +1,8 @@ +#include + +int main() +{ +#ifdef WRONG_VARIANT + return EXIT_FAILURE; +#endif +} diff --git a/tests/auto/blackbox/testdata/nested-groups/file3.cpp b/tests/auto/blackbox/testdata/nested-groups/file3.cpp new file mode 100644 index 00000000..7c3a4b92 --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-groups/file3.cpp @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef REQUIRED_FOR_FILE3 +#error "missing define" +#endif + +void file3() {} diff --git a/tests/auto/blackbox/testdata/nested-groups/file3.h b/tests/auto/blackbox/testdata/nested-groups/file3.h new file mode 100644 index 00000000..22fe31c1 --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-groups/file3.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void file3(); diff --git a/tests/auto/blackbox/testdata/nested-groups/main.cpp b/tests/auto/blackbox/testdata/nested-groups/main.cpp new file mode 100644 index 00000000..c9c73b9e --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-groups/main.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** 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 "file1.h" +#include "file2.h" +#include "file3.h" +#include "other.h" + +int main() +{ + file1(); + file2(); + file3(); + other(); +} diff --git a/tests/auto/blackbox/testdata/nested-groups/modules/themodule/themodule.qbs b/tests/auto/blackbox/testdata/nested-groups/modules/themodule/themodule.qbs new file mode 100644 index 00000000..73426ddf --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-groups/modules/themodule/themodule.qbs @@ -0,0 +1,9 @@ +Module { + Group { + cpp.defines: ["REQUIRED_FOR_FILE3"] + Group { + prefix: product.sourceDirectory + '/' + files: ["file3.cpp", "file3.h"] + } + } +} diff --git a/tests/auto/blackbox/testdata/nested-groups/nested-groups.qbs b/tests/auto/blackbox/testdata/nested-groups/nested-groups.qbs new file mode 100644 index 00000000..88f5ae30 --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-groups/nested-groups.qbs @@ -0,0 +1,40 @@ +CppApplication { + consoleApplication: true + Depends { name: "themodule" } + cpp.includePaths: ["subdir"] + files: ["main.cpp"] + Group { + prefix: "subdir/" + cpp.defines: ["REQUIRED_FOR_FILE1", "BREAKS_FILE2"] + + fileTags: ["cpp"] + + // This group has no files, and that's okay. + + Group { + files: ["other.cpp", "other.h"] + Group { + cpp.defines: outer.concat(["ALSO_REQUIRED_FOR_FILE1"]) + files: ["file1.cpp", "file1.h"] + } + Group { + cpp.defines: ["REQUIRED_FOR_FILE2"] + files: ["file2.cpp", "file2.h"] + } + Group { + name: "disabled" + condition: false + Group { + name: "indirectly disabled" + condition: true + files: ["main2.cpp"] + } + } + Group { + name: "no tags" + fileTags: [] + files: ["main3.cpp"] + } + } + } +} diff --git a/tests/auto/blackbox/testdata/nested-groups/subdir/file1.cpp b/tests/auto/blackbox/testdata/nested-groups/subdir/file1.cpp new file mode 100644 index 00000000..95255e4e --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-groups/subdir/file1.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef REQUIRED_FOR_FILE1 +#error "missing define" +#endif + +#ifndef ALSO_REQUIRED_FOR_FILE1 +#error "missing define" +#endif + +void file1() {} diff --git a/tests/auto/blackbox/testdata/nested-groups/subdir/file1.h b/tests/auto/blackbox/testdata/nested-groups/subdir/file1.h new file mode 100644 index 00000000..d52b9f05 --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-groups/subdir/file1.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void file1(); diff --git a/tests/auto/blackbox/testdata/nested-groups/subdir/file2.cpp b/tests/auto/blackbox/testdata/nested-groups/subdir/file2.cpp new file mode 100644 index 00000000..87333eab --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-groups/subdir/file2.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifdef BREAKS_FILE2 +#error "unexpected define" +#endif + +#ifndef REQUIRED_FOR_FILE2 +#error "missing define" +#endif + +void file2() {} diff --git a/tests/auto/blackbox/testdata/nested-groups/subdir/file2.h b/tests/auto/blackbox/testdata/nested-groups/subdir/file2.h new file mode 100644 index 00000000..97e76d0e --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-groups/subdir/file2.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void file2(); diff --git a/tests/auto/blackbox/testdata/nested-groups/subdir/main2.cpp b/tests/auto/blackbox/testdata/nested-groups/subdir/main2.cpp new file mode 100644 index 00000000..e14f806b --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-groups/subdir/main2.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { } diff --git a/tests/auto/blackbox/testdata/nested-groups/subdir/main3.cpp b/tests/auto/blackbox/testdata/nested-groups/subdir/main3.cpp new file mode 100644 index 00000000..e14f806b --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-groups/subdir/main3.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { } diff --git a/tests/auto/blackbox/testdata/nested-groups/subdir/other.cpp b/tests/auto/blackbox/testdata/nested-groups/subdir/other.cpp new file mode 100644 index 00000000..c18f141e --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-groups/subdir/other.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef REQUIRED_FOR_FILE1 +#error "missing define" +#endif + +#ifndef BREAKS_FILE2 +#error "missing define" +#endif + +void other() { } diff --git a/tests/auto/blackbox/testdata/nested-groups/subdir/other.h b/tests/auto/blackbox/testdata/nested-groups/subdir/other.h new file mode 100644 index 00000000..4c04b3ed --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-groups/subdir/other.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void other(); diff --git a/tests/auto/blackbox/testdata/nested-properties/dummy.txt b/tests/auto/blackbox/testdata/nested-properties/dummy.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/nested-properties/modules/higherlevel/higher-level.qbs b/tests/auto/blackbox/testdata/nested-properties/modules/higherlevel/higher-level.qbs new file mode 100644 index 00000000..071737c3 --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-properties/modules/higherlevel/higher-level.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "lowerlevel" } + lowerlevel.propDependency: "value in higherlevel" +} diff --git a/tests/auto/blackbox/testdata/nested-properties/modules/lowerlevel/lower-level.qbs b/tests/auto/blackbox/testdata/nested-properties/modules/lowerlevel/lower-level.qbs new file mode 100644 index 00000000..f8b6a7dc --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-properties/modules/lowerlevel/lower-level.qbs @@ -0,0 +1,17 @@ +Module { + property string propDependency: "value in lowerlevel module" + property string prop: propDependency + property string someOtherProp + + Rule { + inputs: ["dummy-input"] + outputFileTags: "mytype" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.sourceCode = function() { }; + var prop = product.lowerlevel.prop; + cmd.description = "lowerlevel.prop is '" + prop + "'."; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/nested-properties/product.qbs b/tests/auto/blackbox/testdata/nested-properties/product.qbs new file mode 100644 index 00000000..e38813b7 --- /dev/null +++ b/tests/auto/blackbox/testdata/nested-properties/product.qbs @@ -0,0 +1,20 @@ +Project { + Product { + name: "dep" + Export { + Depends { name: "lowerlevel" } + lowerlevel.someOtherProp: "blubb" + } + } + Product { + type: "mytype" + name: "toplevel" + Depends { name: "higherlevel" } + Depends { name: "lowerlevel" } + Depends { name: "dep" } + Group { + files: ["dummy.txt"] + fileTags: ["dummy-input"] + } + } +} diff --git a/tests/auto/blackbox/testdata/new-output-artifact/input.txt b/tests/auto/blackbox/testdata/new-output-artifact/input.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/new-output-artifact/new-output-artifact.qbs b/tests/auto/blackbox/testdata/new-output-artifact/new-output-artifact.qbs new file mode 100644 index 00000000..b297a3f6 --- /dev/null +++ b/tests/auto/blackbox/testdata/new-output-artifact/new-output-artifact.qbs @@ -0,0 +1,37 @@ +import qbs.TextFile + +Product { + name: "theProduct" + type: ["output"] + property int artifactCount: 99 + Group { + files: ["input.txt"] + fileTags: ["input"] + } + qbs.installPrefix: "" + Group { + fileTagsFilter: product.type + qbs.install: true + } + Rule { + inputs: ["input"] + outputFileTags: ["output"] + outputArtifacts: { + var list = []; + for (var i = 0; i < product.artifactCount; ++i) + list.push({ filePath: "output_" + i + ".out", fileTags: ["output"]}); + return list; + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + for (var i = 0; i < outputs["output"].length; ++i) { + var f = new TextFile(outputs["output"][i].filePath, TextFile.WriteOnly); + f.close(); + } + } + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/no-exported-symbols/lib.cpp b/tests/auto/blackbox/testdata/no-exported-symbols/lib.cpp new file mode 100644 index 00000000..20cfe21d --- /dev/null +++ b/tests/auto/blackbox/testdata/no-exported-symbols/lib.cpp @@ -0,0 +1 @@ +static void someFunc() {} diff --git a/tests/auto/blackbox/testdata/no-exported-symbols/lib.h b/tests/auto/blackbox/testdata/no-exported-symbols/lib.h new file mode 100644 index 00000000..48fa2de9 --- /dev/null +++ b/tests/auto/blackbox/testdata/no-exported-symbols/lib.h @@ -0,0 +1,6 @@ +#ifndef TEST_LIB +#define TEST_LIB + +inline int success() { return 0; } + +#endif diff --git a/tests/auto/blackbox/testdata/no-exported-symbols/main.cpp b/tests/auto/blackbox/testdata/no-exported-symbols/main.cpp new file mode 100644 index 00000000..a76122e1 --- /dev/null +++ b/tests/auto/blackbox/testdata/no-exported-symbols/main.cpp @@ -0,0 +1,6 @@ +#include + +int main() +{ + return success(); +} diff --git a/tests/auto/blackbox/testdata/no-exported-symbols/no-exported-symbols.qbs b/tests/auto/blackbox/testdata/no-exported-symbols/no-exported-symbols.qbs new file mode 100644 index 00000000..346a94e2 --- /dev/null +++ b/tests/auto/blackbox/testdata/no-exported-symbols/no-exported-symbols.qbs @@ -0,0 +1,28 @@ +Project { + DynamicLibrary { + name: "the_lib" + Depends { name: "cpp" } + files: ["lib.cpp", "lib.h"] + Export { + Depends { name: "cpp" } + cpp.includePaths: path + } + + Probe { + id: toolchainProbe + property stringList toolchain: qbs.toolchain + configure: { + if (toolchain.contains("msvc") && !toolchain.contains("clang-cl")) + console.info("compiler is MSVC") + else + console.info("compiler is not MSVC") + } + } + } + CppApplication { + name: "the_app" + property bool link + Depends { name: "the_lib"; cpp.link: product.link } + files: "main.cpp" + } +} 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 00000000..6fd7dd07 --- /dev/null +++ b/tests/auto/blackbox/testdata/no-profile/no-profile.qbs @@ -0,0 +1,3 @@ +Product { + property bool dummy: { console.info("profile: " + project.profile); } +} diff --git a/tests/auto/blackbox/testdata/no-such-profile/no-such-profile.qbs b/tests/auto/blackbox/testdata/no-such-profile/no-such-profile.qbs new file mode 100644 index 00000000..8e64ba47 --- /dev/null +++ b/tests/auto/blackbox/testdata/no-such-profile/no-such-profile.qbs @@ -0,0 +1,6 @@ +import qbs + +Product { + name: "theProduct" + property int p +} diff --git a/tests/auto/blackbox/testdata/nodejs/hello.js b/tests/auto/blackbox/testdata/nodejs/hello.js new file mode 100644 index 00000000..43f1e2ff --- /dev/null +++ b/tests/auto/blackbox/testdata/nodejs/hello.js @@ -0,0 +1,3 @@ +if (console) { + console.log("hello world"); +} diff --git a/tests/auto/blackbox/testdata/nodejs/hello.qbs b/tests/auto/blackbox/testdata/nodejs/hello.qbs new file mode 100644 index 00000000..d7c54592 --- /dev/null +++ b/tests/auto/blackbox/testdata/nodejs/hello.qbs @@ -0,0 +1,10 @@ +NodeJSApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + nodejs.applicationFile: "hello.js" + name: "hello" +} diff --git a/tests/auto/blackbox/testdata/non-broken-files-in-broken-product/broken.cpp b/tests/auto/blackbox/testdata/non-broken-files-in-broken-product/broken.cpp new file mode 100644 index 00000000..57f89ed1 --- /dev/null +++ b/tests/auto/blackbox/testdata/non-broken-files-in-broken-product/broken.cpp @@ -0,0 +1 @@ +broken diff --git a/tests/auto/blackbox/testdata/non-broken-files-in-broken-product/fine.cpp b/tests/auto/blackbox/testdata/non-broken-files-in-broken-product/fine.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/blackbox/testdata/non-broken-files-in-broken-product/fine.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/blackbox/testdata/non-broken-files-in-broken-product/non-broken-files-in-broken-product.qbs b/tests/auto/blackbox/testdata/non-broken-files-in-broken-product/non-broken-files-in-broken-product.qbs new file mode 100644 index 00000000..abd41fb6 --- /dev/null +++ b/tests/auto/blackbox/testdata/non-broken-files-in-broken-product/non-broken-files-in-broken-product.qbs @@ -0,0 +1,4 @@ +CppApplication { + consoleApplication: true + files: ["fine.cpp", "broken.cpp"] +} diff --git a/tests/auto/blackbox/testdata/non-default-product/main.cpp b/tests/auto/blackbox/testdata/non-default-product/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/blackbox/testdata/non-default-product/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/blackbox/testdata/non-default-product/non-default-product.qbs b/tests/auto/blackbox/testdata/non-default-product/non-default-product.qbs new file mode 100644 index 00000000..6e006e83 --- /dev/null +++ b/tests/auto/blackbox/testdata/non-default-product/non-default-product.qbs @@ -0,0 +1,15 @@ +Project { + CppApplication { + name: "default app" + consoleApplication: true + files: "main.cpp" + } + + CppApplication { + name: "non-default app" + Depends { name: "default app" } + consoleApplication: true + builtByDefault: false + files: "main.cpp" + } +} diff --git a/tests/auto/blackbox/testdata/not-always-updated/not-always-updated.qbs b/tests/auto/blackbox/testdata/not-always-updated/not-always-updated.qbs new file mode 100644 index 00000000..8d6c1f5d --- /dev/null +++ b/tests/auto/blackbox/testdata/not-always-updated/not-always-updated.qbs @@ -0,0 +1,31 @@ +import qbs.TextFile + +Product { + type: "p" + Rule { + multiplex: true + Artifact { filePath: "dummy.txt"; fileTags: "t1"; alwaysUpdated: false } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating dummy"; + cmd.sourceCode = function() {}; + return cmd; + } + } + Rule { + inputs: "t1" + Artifact { filePath: "o.txt"; fileTags: "p" } + prepare: { + var cmd = new JavaScriptCommand; + cmd.description = "creating final"; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + }; + return cmd; + } + } +} + + + diff --git a/tests/auto/blackbox/testdata/nsis/hello.bat b/tests/auto/blackbox/testdata/nsis/hello.bat new file mode 100644 index 00000000..3af583cd --- /dev/null +++ b/tests/auto/blackbox/testdata/nsis/hello.bat @@ -0,0 +1 @@ +echo Hello world! diff --git a/tests/auto/blackbox/testdata/nsis/hello.nsi b/tests/auto/blackbox/testdata/nsis/hello.nsi new file mode 100644 index 00000000..70b73056 --- /dev/null +++ b/tests/auto/blackbox/testdata/nsis/hello.nsi @@ -0,0 +1,19 @@ +SetCompressor zlib + +!ifdef Win64 + Name "Qbs Hello - ${qbs.architecture} (64-bit)" +!else + Name "Qbs Hello - ${qbs.architecture} (32-bit)" +!endif + +OutFile "you-should-not-see-a-file-with-this-name.exe" +InstallDir "$DESKTOP\Qbs Hello" +RequestExecutionLevel user + +Page directory +Page instfiles + +Section "" + SetOutPath "$INSTDIR" + File "${batchFile}" +SectionEnd diff --git a/tests/auto/blackbox/testdata/nsis/hello.qbs b/tests/auto/blackbox/testdata/nsis/hello.qbs new file mode 100644 index 00000000..a161a699 --- /dev/null +++ b/tests/auto/blackbox/testdata/nsis/hello.qbs @@ -0,0 +1,8 @@ +NSISSetup { + condition: qbs.targetOS.contains("windows") + name: "Qbs Hello" + targetName: "qbs-hello-" + qbs.architecture + files: ["hello.nsi", "hello.bat"] + nsis.defines: ["batchFile=hello.bat"] + nsis.compressor: "lzma-solid" +} diff --git a/tests/auto/blackbox/testdata/nsisDependencies/hello.nsi b/tests/auto/blackbox/testdata/nsisDependencies/hello.nsi new file mode 100644 index 00000000..4079d40d --- /dev/null +++ b/tests/auto/blackbox/testdata/nsisDependencies/hello.nsi @@ -0,0 +1,8 @@ +Page directory +Page instfiles + +Section "" + SetOutPath "$INSTDIR" + File "${buildDirectory}\app.exe" + File "${buildDirectory}\lib.dll" +SectionEnd diff --git a/tests/auto/blackbox/testdata/nsisDependencies/main.c b/tests/auto/blackbox/testdata/nsisDependencies/main.c new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/blackbox/testdata/nsisDependencies/main.c @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/nsisDependencies/nsisDependencies.qbs b/tests/auto/blackbox/testdata/nsisDependencies/nsisDependencies.qbs new file mode 100644 index 00000000..d8185aab --- /dev/null +++ b/tests/auto/blackbox/testdata/nsisDependencies/nsisDependencies.qbs @@ -0,0 +1,67 @@ +import qbs.FileInfo +import qbs.TextFile + +Project { + condition: qbs.targetOS.contains("windows") + + NSISSetup { + Depends { name: "app" } + Depends { name: "lib" } + name: "inst" + files: ["hello.nsi"] + nsis.defines: ["buildDirectory=" + FileInfo.toWindowsSeparators(project.buildDirectory)] + destinationDirectory: project.buildDirectory + } + + Application { + Depends { name: "cpp" } + name: "app" + files: ["main.c"] + Group { + fileTagsFilter: product.type + qbs.install: true + } + destinationDirectory: project.buildDirectory + } + + DynamicLibrary { + Depends { name: "cpp" } + name: "lib" + files: ["main.c"] + Group { + fileTagsFilter: product.type + qbs.install: true + } + Rule { + // This rule tries to provoke the installer into building too early (and the test + // verifies that it does not) by causing the build of the installables to take + // a lot longer. + multiplex: true + outputFileTags: ["c"] + outputArtifacts: { + var artifacts = []; + for (var i = 0; i < 96; ++i) + artifacts.push({ filePath: "c" + i + ".c", fileTags: ["c"] }); + return artifacts; + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + for (var i = 0; i < outputs["c"].length; ++i) { + var tf; + try { + tf = new TextFile(outputs["c"][i].filePath, TextFile.WriteOnly); + tf.writeLine("int main" + i + "() { return 0; }"); + } finally { + if (tf) + tf.close(); + } + } + }; + return [cmd]; + } + } + destinationDirectory: project.buildDirectory + } +} diff --git a/tests/auto/blackbox/testdata/out-of-date-marking/main.c b/tests/auto/blackbox/testdata/out-of-date-marking/main.c new file mode 100644 index 00000000..7b4a384a --- /dev/null +++ b/tests/auto/blackbox/testdata/out-of-date-marking/main.c @@ -0,0 +1,3 @@ +#include + +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/out-of-date-marking/out-of-date-marking.qbs b/tests/auto/blackbox/testdata/out-of-date-marking/out-of-date-marking.qbs new file mode 100644 index 00000000..d5224657 --- /dev/null +++ b/tests/auto/blackbox/testdata/out-of-date-marking/out-of-date-marking.qbs @@ -0,0 +1,21 @@ +import qbs.TextFile + +CppApplication { + name: "app" + files: "main.c" + cpp.includePaths: buildDirectory + Rule { + multiplex: true + alwaysRun: true + Artifact { filePath: "myheader.h"; fileTags: "hpp" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/output-artifact-auto-tagging/broken.cpp.in b/tests/auto/blackbox/testdata/output-artifact-auto-tagging/broken.cpp.in new file mode 100644 index 00000000..10bec280 --- /dev/null +++ b/tests/auto/blackbox/testdata/output-artifact-auto-tagging/broken.cpp.in @@ -0,0 +1 @@ +void f() { blubb(); } diff --git a/tests/auto/blackbox/testdata/output-artifact-auto-tagging/main.cpp.in b/tests/auto/blackbox/testdata/output-artifact-auto-tagging/main.cpp.in new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/output-artifact-auto-tagging/main.cpp.in @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/output-artifact-auto-tagging/output-artifact-auto-tagging.qbs b/tests/auto/blackbox/testdata/output-artifact-auto-tagging/output-artifact-auto-tagging.qbs new file mode 100644 index 00000000..7ec8fd91 --- /dev/null +++ b/tests/auto/blackbox/testdata/output-artifact-auto-tagging/output-artifact-auto-tagging.qbs @@ -0,0 +1,30 @@ +import qbs.File + +CppApplication { + consoleApplication: true + Group { + files: ["broken.cpp.in", "main.cpp.in"] + fileTags: ["cpp.in"] + } + Rule { + multiplex: true + inputs: ["cpp.in"] + outputFileTags: ["cpp"] + outputArtifacts: [{ filePath: "main.cpp" }, { filePath: "broken.nomatch" }] + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating main.cpp"; + cmd.sourceCode = function() { + File.copy(product.sourceDirectory + "/main.cpp.in", + product.buildDirectory + "/main.cpp"); + File.copy(product.sourceDirectory + "/broken.cpp.in", + product.buildDirectory + "/broken.nomatch"); + }; + return [cmd]; + } + } + FileTagger { + patterns: ["*.nomatch"] + fileTags: ["utter nonsense"] + } +} diff --git a/tests/auto/blackbox/testdata/output-redirection/input.bin b/tests/auto/blackbox/testdata/output-redirection/input.bin new file mode 100644 index 0000000000000000000000000000000000000000..2b231c7a8a8ca93b46caa52cb72fab5c336c8e95 GIT binary patch literal 1024 zcmV+b1poW$(YSWXw}2T^h?XaSF17=AYzxT>K7Etqi-P-NyDh~i;vDl&Pjuj!I%Oi` znrvvqm&%Ep8&4{z;HP9Xw>)LamR0|Svm$)hlt-?51ymajJWQW!ul&DdbGvV*f~pks z?8FoCTd0**rzS1crb#s=Eh9g&hHqUB4eU3Z@Br)!dsi-!%mJ}ks>MT;<7mr%AQ2Fb z`oT#FiqM|QK+yA#msS$@RFQLeYCh4NX=ujD4w46pdyE#0D&`N(juFN6;7^=`qQ6>{ z0@JSev48*AAUkjVSf{u3Rcxn;1z3jJcGJ=D(t)OFs#BQunz%?SEX1OO4r($$)?@>5 z9x6H-XQiiirq8fCI|t*RTj-4gOa0E3$s1DQ?p6^pc%I0LsH?8C5j2tAdw2xLyGcjB zln6w=er9uzREIWg#bNB1ov4?948M=Ll7rX#W9`K9dFy@MK#00IYyj;r(QDD)FUE|c zZf>pET}^o7ZB33A9|2@Iwdww5eUa+c91}}Q65s^uH>lf0_oHVH|Kx+8$0ttm%AiR7 z?(NTgz8QP!yV1(CPlm^Q`q#z7#rAAN%b`)<;Te(-!fs~nW`P@B`3;b76A59k=J%`A z{JuuneRD1H{^WbdB+IpW;4*`Ibe65>y~1r&H>X#V1&f$NDaQ?n(F=4f!Ij^Lj$r5O z7DlYE+F6XkU?Rt1J7eY1-qlBoMf#MlkpN-q{9b+ha%)`h%>!i?Zd=S9q{?#^w3Eh; z*+?yVRJ_5mDQ`5FrfjZUuy5H_z*j;HbQ@22H&XGWQhUu@T2YGkCD&@f-Ul1RtNg3< zllo(x3gw=?Y}PAw4ZgMWcwRkFLl=SUAO$R?=f-_{jp(#_Xm2$g|sdK&xbEpEQfyU0u6>p(7HSr$XU(BSm8>!L5%qGcr&r+fZmWNzbu)qF&OT}@3WaU za)orTK)_(frsR*sgud{oCwQjUZiRzME{jMtCj@sdhk--6kB@1=>s%V$ u90`vtw6ZRo69(1`Kp*Y_Xx9?c6hGOUlPOw*;wwdl&46=5GHNiCE;uaf{R*W3 literal 0 HcmV?d00001 diff --git a/tests/auto/blackbox/testdata/output-redirection/input.txt b/tests/auto/blackbox/testdata/output-redirection/input.txt new file mode 100644 index 00000000..0ba7b6bf --- /dev/null +++ b/tests/auto/blackbox/testdata/output-redirection/input.txt @@ -0,0 +1 @@ +this is a plain text file diff --git a/tests/auto/blackbox/testdata/output-redirection/output-redirection.qbs b/tests/auto/blackbox/testdata/output-redirection/output-redirection.qbs new file mode 100644 index 00000000..3ee44343 --- /dev/null +++ b/tests/auto/blackbox/testdata/output-redirection/output-redirection.qbs @@ -0,0 +1,38 @@ +import qbs.FileInfo + +Product { + name: "the-product" + type: "output" + Group { + files: "input.bin" + fileTags: "binary" + } + Group { + files: "input.txt" + fileTags: "text" + } + + Rule { + inputs: ["text", "binary"] + Artifact { + filePath: "output." + FileInfo.completeSuffix(input.filePath) + fileTags: "output" + } + prepare: { + var binary; + var prefixArgs; + if (product.qbs.hostOS.contains("windows")) { + binary = product.qbs.windowsShellPath; + prefixArgs = ["/c", "type"]; + } else { + binary = "cat"; + prefixArgs = []; + } + var inputPath = FileInfo.toNativeSeparators(input.filePath); + var cmd = new Command(binary, prefixArgs.concat([inputPath, inputPath])); + cmd.stdoutFilePath = output.filePath; + cmd.highlight = "filegen"; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/output-redirection/output.bin b/tests/auto/blackbox/testdata/output-redirection/output.bin new file mode 100644 index 0000000000000000000000000000000000000000..efb4ec03b8e7ecf34fa4c6861bc326939847ffee GIT binary patch literal 2048 zcmeIxi8>nw003YrW{yze(`vHSbecI5rmkXSiD{ z@ASafKP*Fb(P+X_p=p5nN)ZmrD&VCRgV9>mOI7{`o#p|&eCN}oR`vU{xz-J_l1^ZG z^IuCt8;|+|@I4U>O0BX;$O3)vtj=?(x(a*6KpASlu2{AtM__grHm{6AF3eSEa z-t1n>M&_UmT3(49jOrWsoveGPTwf zMrn%dy(M%pwY<+G_uOQ|r{)Kzvub7$CvH`pd(OD3G)cGQx#&s$_R9oDaP95KAB(zd zv~^hJiSi-(h-rU`Kfcv6y%h9!RSaXi$<$10rPTA?LkYX%vAkihT)tG!o%xlG4{ljFngNHWpRI#_Ww=-$ypoI5Rjd%(h@*mT>Y=renpV{ z=_<993;k_}_r&ISYjd2;R3&Zn@OiE1WbXaL8TJtSBMv!I=CJriHCtgYJa8*80gO2S zK8gTI`!=ob*G(NUZ_bhNu$_}t68HO&_ShwTDv6L^|F(B9%*nXQHAgbzF;bHYOy$cG zU;}xJsWhLr0+pLj8(+I;4*IBbea-yV_zSPBGj1H_HfD+W3XZ(VM^T|(+oKWzD&d|l zzNzHlRoZemv^g7CtkXXOEt(Mq1q@t+S5w5CbHD2^as)7%Xzci*(vdXk<>8RNuQ3xc zupk9y)g9G|at{4UWY$W0anst-KxX?c(fg-c$P2&oA?DCAvqDR%u%^DC9~A7)A!Bw9 z*v{8g$t*a9#s^<`S?eBkwI*n@kC?)yJnX2y5>=M{U`!yI)h$v3(10yuVk|Zh5Ah#W z#>1jpsTzQ0ixp2yXh%4McR=793PF*D_EMIB|41WdGWpG#9ajTQ*P7fXscwL%@ zNYi0$E%0K{SJSeH%Uj~u8Kp^O%eg;uG(D)x+BcF% + +inline void printGreeting() +{ + std::cout << "Tach." << std::endl; +} diff --git a/tests/auto/blackbox/testdata/pch-change-tracking/header2.cpp b/tests/auto/blackbox/testdata/pch-change-tracking/header2.cpp new file mode 100644 index 00000000..17213a4a --- /dev/null +++ b/tests/auto/blackbox/testdata/pch-change-tracking/header2.cpp @@ -0,0 +1,8 @@ +#include "header2.h" +// header1 is forced-included via pch. + +void printPersonalGreeting() +{ + printGreeting(); + std::cout << "Was geht, Rumpelstilzchen?" << std::endl; +} diff --git a/tests/auto/blackbox/testdata/pch-change-tracking/header2.h b/tests/auto/blackbox/testdata/pch-change-tracking/header2.h new file mode 100644 index 00000000..e0d28dcb --- /dev/null +++ b/tests/auto/blackbox/testdata/pch-change-tracking/header2.h @@ -0,0 +1,30 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#pragma once +void printPersonalGreeting(); diff --git a/tests/auto/blackbox/testdata/pch-change-tracking/main.cpp b/tests/auto/blackbox/testdata/pch-change-tracking/main.cpp new file mode 100644 index 00000000..b3badcc5 --- /dev/null +++ b/tests/auto/blackbox/testdata/pch-change-tracking/main.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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 "header2.h" +#include "pch.h" + +int main() +{ + printGreeting(); + printPersonalGreeting(); +} diff --git a/tests/auto/blackbox/testdata/pch-change-tracking/pch-change-tracking.qbs b/tests/auto/blackbox/testdata/pch-change-tracking/pch-change-tracking.qbs new file mode 100644 index 00000000..3da526fd --- /dev/null +++ b/tests/auto/blackbox/testdata/pch-change-tracking/pch-change-tracking.qbs @@ -0,0 +1,12 @@ +CppApplication { + files: [ + "header1.h", + "header2.cpp", + "header2.h", + "main.cpp", + ] + Group { + files: ["pch.h"] + fileTags: ["cpp_pch_src"] + } +} diff --git a/tests/auto/blackbox/testdata/pch-change-tracking/pch.h b/tests/auto/blackbox/testdata/pch-change-tracking/pch.h new file mode 100644 index 00000000..bdceaa8a --- /dev/null +++ b/tests/auto/blackbox/testdata/pch-change-tracking/pch.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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 "header1.h" diff --git a/tests/auto/blackbox/testdata/per-group-define-in-export-item/main.cpp b/tests/auto/blackbox/testdata/per-group-define-in-export-item/main.cpp new file mode 100644 index 00000000..f9cd52e8 --- /dev/null +++ b/tests/auto/blackbox/testdata/per-group-define-in-export-item/main.cpp @@ -0,0 +1,3 @@ +#ifdef MAIN +int main() {} +#endif diff --git a/tests/auto/blackbox/testdata/per-group-define-in-export-item/per-group-define-in-export-item.qbs b/tests/auto/blackbox/testdata/per-group-define-in-export-item/per-group-define-in-export-item.qbs new file mode 100644 index 00000000..a81b8f6c --- /dev/null +++ b/tests/auto/blackbox/testdata/per-group-define-in-export-item/per-group-define-in-export-item.qbs @@ -0,0 +1,16 @@ +Project { + Product { + name: "dep" + Export { + Depends { name: "cpp" } + Group { + cpp.defines: ["MAIN"] + files: ["main.cpp"] + } + } + } + Application { + name: "app" + Depends { name: "dep" } + } +} diff --git a/tests/auto/blackbox/testdata/pkg-config-probe-sysroot/modules/themodule/themodule.qbs b/tests/auto/blackbox/testdata/pkg-config-probe-sysroot/modules/themodule/themodule.qbs new file mode 100644 index 00000000..991876f6 --- /dev/null +++ b/tests/auto/blackbox/testdata/pkg-config-probe-sysroot/modules/themodule/themodule.qbs @@ -0,0 +1,23 @@ +import qbs.Probes + +Module { + Probes.PkgConfigProbe { + id: theProbe + name: "dummy" + } + + property stringList libs: theProbe.libs + + Rule { + multiplex: true + outputFileTags: "theType" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + console.info(product.name + " libs: " + JSON.stringify(product.themodule.libs)); + } + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/pkg-config-probe-sysroot/pkg-config.qbs b/tests/auto/blackbox/testdata/pkg-config-probe-sysroot/pkg-config.qbs new file mode 100644 index 00000000..11498ebd --- /dev/null +++ b/tests/auto/blackbox/testdata/pkg-config-probe-sysroot/pkg-config.qbs @@ -0,0 +1,27 @@ +Project { + property string packageBaseName + + Product { + name: "theProduct1" + type: ["theType"] + + Depends { name: "themodule" } + qbs.sysroot: path + "/sysroot1" + } + + Product { + name: "theProduct2" + type: ["theType"] + + Depends { name: "themodule" } + qbs.sysroot: path + "/sysroot2" + } + + Product { + name: "theProduct3" + type: ["theType"] + + Depends { name: "themodule" } + qbs.sysroot: path + "/sysroot1" + } +} diff --git a/tests/auto/blackbox/testdata/pkg-config-probe-sysroot/sysroot1/usr/share/pkgconfig/dummy.pc b/tests/auto/blackbox/testdata/pkg-config-probe-sysroot/sysroot1/usr/share/pkgconfig/dummy.pc new file mode 100644 index 00000000..9e89e44b --- /dev/null +++ b/tests/auto/blackbox/testdata/pkg-config-probe-sysroot/sysroot1/usr/share/pkgconfig/dummy.pc @@ -0,0 +1,7 @@ +Name: dummy1 +Description: dummy1 package +Version: 0.0.1 + +Requires: +Libs: -L/usr/dummy -ldummy1 +Cflags: diff --git a/tests/auto/blackbox/testdata/pkg-config-probe-sysroot/sysroot2/usr/share/pkgconfig/dummy.pc b/tests/auto/blackbox/testdata/pkg-config-probe-sysroot/sysroot2/usr/share/pkgconfig/dummy.pc new file mode 100644 index 00000000..9e89e44b --- /dev/null +++ b/tests/auto/blackbox/testdata/pkg-config-probe-sysroot/sysroot2/usr/share/pkgconfig/dummy.pc @@ -0,0 +1,7 @@ +Name: dummy1 +Description: dummy1 package +Version: 0.0.1 + +Requires: +Libs: -L/usr/dummy -ldummy1 +Cflags: diff --git a/tests/auto/blackbox/testdata/pkg-config-probe/dummy1/dummy1.pc b/tests/auto/blackbox/testdata/pkg-config-probe/dummy1/dummy1.pc new file mode 100644 index 00000000..94263ec0 --- /dev/null +++ b/tests/auto/blackbox/testdata/pkg-config-probe/dummy1/dummy1.pc @@ -0,0 +1,7 @@ +Name: dummy1 +Description: dummy1 package +Version: 0.0.1 + +Requires: +Libs: -Ldummydir1 -ldummy1 +Cflags: diff --git a/tests/auto/blackbox/testdata/pkg-config-probe/dummy2/dummy2.pc b/tests/auto/blackbox/testdata/pkg-config-probe/dummy2/dummy2.pc new file mode 100644 index 00000000..c6e5631d --- /dev/null +++ b/tests/auto/blackbox/testdata/pkg-config-probe/dummy2/dummy2.pc @@ -0,0 +1,7 @@ +Name: dummy2 +Description: dummy2 package +Version: 0.0.2 + +Requires: +Libs: -Ldummydir2 -ldummy2 +Cflags: diff --git a/tests/auto/blackbox/testdata/pkg-config-probe/modules/themodule/themodule.qbs b/tests/auto/blackbox/testdata/pkg-config-probe/modules/themodule/themodule.qbs new file mode 100644 index 00000000..d5488da1 --- /dev/null +++ b/tests/auto/blackbox/testdata/pkg-config-probe/modules/themodule/themodule.qbs @@ -0,0 +1,34 @@ +import qbs.Probes + +Module { + property string packageName + property string libDir + + Probes.PkgConfigProbe { + id: theProbe + name: packageName + libDirs: [libDir] + } + + property bool probeSuccess: theProbe.found + property stringList libs: theProbe.libs + property stringList cFlags: theProbe.cflags + property string packageVersion: theProbe.modversion + + Rule { + multiplex: true + outputFileTags: "theType" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + console.info(product.name + " found: " + product.themodule.probeSuccess); + console.info(product.name + " libs: " + JSON.stringify(product.themodule.libs)); + console.info(product.name + " cflags: " + JSON.stringify(product.themodule.cFlags)); + console.info(product.name + " version: " + product.themodule.packageVersion); + } + return [cmd]; + } + } + +} diff --git a/tests/auto/blackbox/testdata/pkg-config-probe/pkg-config.qbs b/tests/auto/blackbox/testdata/pkg-config-probe/pkg-config.qbs new file mode 100644 index 00000000..d4dc3b92 --- /dev/null +++ b/tests/auto/blackbox/testdata/pkg-config-probe/pkg-config.qbs @@ -0,0 +1,21 @@ +Project { + property string packageBaseName + + Product { + name: "theProduct1" + type: ["theType"] + + Depends { name: "themodule" } + themodule.packageName: project.packageBaseName + "1" + themodule.libDir: path + "/dummy1" + } + + Product { + name: "theProduct2" + type: ["theType"] + + Depends { name: "themodule" } + themodule.packageName: project.packageBaseName + "2" + themodule.libDir: path + "/dummy2" + } +} diff --git a/tests/auto/blackbox/testdata/plugin-dependency/helper1.cpp b/tests/auto/blackbox/testdata/plugin-dependency/helper1.cpp new file mode 100644 index 00000000..72331da8 --- /dev/null +++ b/tests/auto/blackbox/testdata/plugin-dependency/helper1.cpp @@ -0,0 +1,16 @@ +#include + +#if defined(_WIN32) || defined(WIN32) +# define EXPORT __declspec(dllexport) +#else +# define EXPORT +#endif + +#ifndef USING_HELPER2 +#error define USING_HELPER2 missing +#endif + +EXPORT void helper1_hello() +{ + puts("helper1 says hello!"); +} diff --git a/tests/auto/blackbox/testdata/plugin-dependency/helper2.cpp b/tests/auto/blackbox/testdata/plugin-dependency/helper2.cpp new file mode 100644 index 00000000..cdcdfc94 --- /dev/null +++ b/tests/auto/blackbox/testdata/plugin-dependency/helper2.cpp @@ -0,0 +1,7 @@ +#include "../dllexport.h" +#include + +DLL_EXPORT void helper2_hello() +{ + puts("Hello from helper2!"); +} diff --git a/tests/auto/blackbox/testdata/plugin-dependency/main.cpp b/tests/auto/blackbox/testdata/plugin-dependency/main.cpp new file mode 100644 index 00000000..d34c880f --- /dev/null +++ b/tests/auto/blackbox/testdata/plugin-dependency/main.cpp @@ -0,0 +1,13 @@ +#include "../dllexport.h" + +DLL_IMPORT void plugin3_hello(); +DLL_IMPORT void plugin4_hello(); +DLL_IMPORT void helper1_hello(); + +int main() +{ + plugin3_hello(); + plugin4_hello(); + helper1_hello(); + return 0; +} diff --git a/tests/auto/blackbox/testdata/plugin-dependency/plugin-dependency.qbs b/tests/auto/blackbox/testdata/plugin-dependency/plugin-dependency.qbs new file mode 100644 index 00000000..c619b33e --- /dev/null +++ b/tests/auto/blackbox/testdata/plugin-dependency/plugin-dependency.qbs @@ -0,0 +1,73 @@ +Project { + CppApplication { + name: "myapp" + files: ["main.cpp"] + Depends { + name: "plugin1" // not to be linked + cpp.link: qbs.hostOS === undefined + } + Depends { name: "plugin2" } // not to be linked + Depends { + name: "plugin3" // supposed to be linked + //property bool theCondition: true + cpp.link: /*theCondition && */product.name === "myapp" // TODO: Make this work + } + Depends { name: "plugin4" } // supposed to be linked + Depends { name: "helper1" } // supposed to be linked + } + DynamicLibrary { + name: "plugin1" + files: ["plugin1.cpp"] + Depends { name: "cpp" } + } + DynamicLibrary { + name: "plugin2" + files: ["plugin2.cpp"] + Depends { name: "cpp" } + Export { + Parameters { + cpp.link: false // marker 1 + } + } + } + DynamicLibrary { + name: "plugin3" + files: ["plugin3.cpp"] + Depends { name: "cpp" } + Export { + Parameters { + cpp.link: false + } + } + } + DynamicLibrary { + name: "plugin4" + files: ["plugin4.cpp"] + Depends { name: "cpp" } + Export { + Parameters { + // property bool theCondition: true + cpp.link: true // theCondition TODO: Make this work + } + } + } + DynamicLibrary { + name: "helper1" + files: ["helper1.cpp"] + Depends { name: "cpp" } + Depends { name: "helper2"; cpp.link: false /* marker 2 */ } + Export { + Depends { name: "cpp" } + Depends { name: "helper2"; cpp.link: false } + } + } + DynamicLibrary { + name: "helper2" + files: ["helper2.cpp"] + Depends { name: "cpp" } + Export { + Depends { name: "cpp" } + cpp.defines: ["USING_HELPER2"] + } + } +} diff --git a/tests/auto/blackbox/testdata/plugin-dependency/plugin1.cpp b/tests/auto/blackbox/testdata/plugin-dependency/plugin1.cpp new file mode 100644 index 00000000..2535bd85 --- /dev/null +++ b/tests/auto/blackbox/testdata/plugin-dependency/plugin1.cpp @@ -0,0 +1,7 @@ +#include "../dllexport.h" +#include + +DLL_EXPORT void plugin1_hello() +{ + puts("plugin1 says hello!"); +} diff --git a/tests/auto/blackbox/testdata/plugin-dependency/plugin2.cpp b/tests/auto/blackbox/testdata/plugin-dependency/plugin2.cpp new file mode 100644 index 00000000..fb2030d6 --- /dev/null +++ b/tests/auto/blackbox/testdata/plugin-dependency/plugin2.cpp @@ -0,0 +1,7 @@ +#include "../dllexport.h" +#include + +DLL_EXPORT void plugin2_hello() +{ + puts("plugin2 says hello!"); +} diff --git a/tests/auto/blackbox/testdata/plugin-dependency/plugin3.cpp b/tests/auto/blackbox/testdata/plugin-dependency/plugin3.cpp new file mode 100644 index 00000000..8a9f5ee7 --- /dev/null +++ b/tests/auto/blackbox/testdata/plugin-dependency/plugin3.cpp @@ -0,0 +1,7 @@ +#include "../dllexport.h" +#include + +DLL_EXPORT void plugin3_hello() +{ + puts("plugin3 says hello!"); +} diff --git a/tests/auto/blackbox/testdata/plugin-dependency/plugin4.cpp b/tests/auto/blackbox/testdata/plugin-dependency/plugin4.cpp new file mode 100644 index 00000000..4663247f --- /dev/null +++ b/tests/auto/blackbox/testdata/plugin-dependency/plugin4.cpp @@ -0,0 +1,7 @@ +#include "../dllexport.h" +#include + +DLL_EXPORT void plugin4_hello() +{ + puts("plugin4 says hello!"); +} diff --git a/tests/auto/blackbox/testdata/precompiled-and-prefix-headers/main.cpp b/tests/auto/blackbox/testdata/precompiled-and-prefix-headers/main.cpp new file mode 100644 index 00000000..98e7b406 --- /dev/null +++ b/tests/auto/blackbox/testdata/precompiled-and-prefix-headers/main.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() +{ +} diff --git a/tests/auto/blackbox/testdata/precompiled-and-prefix-headers/pch.h b/tests/auto/blackbox/testdata/precompiled-and-prefix-headers/pch.h new file mode 100644 index 00000000..f19f4b59 --- /dev/null +++ b/tests/auto/blackbox/testdata/precompiled-and-prefix-headers/pch.h @@ -0,0 +1,28 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + diff --git a/tests/auto/blackbox/testdata/precompiled-and-prefix-headers/precompiled-and-prefix-headers.qbs b/tests/auto/blackbox/testdata/precompiled-and-prefix-headers/precompiled-and-prefix-headers.qbs new file mode 100644 index 00000000..bbd524e2 --- /dev/null +++ b/tests/auto/blackbox/testdata/precompiled-and-prefix-headers/precompiled-and-prefix-headers.qbs @@ -0,0 +1,11 @@ +CppApplication { + name: "MyApp" + consoleApplication: true + cpp.includePaths: [product.buildDirectory] + cpp.prefixHeaders: [ "prefix.h" ] + Group { + files: ["pch.h"] + fileTags: ["cpp_pch_src"] + } + files: ["main.cpp"] +} diff --git a/tests/auto/blackbox/testdata/precompiled-and-prefix-headers/prefix.h b/tests/auto/blackbox/testdata/precompiled-and-prefix-headers/prefix.h new file mode 100644 index 00000000..f19f4b59 --- /dev/null +++ b/tests/auto/blackbox/testdata/precompiled-and-prefix-headers/prefix.h @@ -0,0 +1,28 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + diff --git a/tests/auto/blackbox/testdata/precompiled-headers-and-redefine/file.cpp b/tests/auto/blackbox/testdata/precompiled-headers-and-redefine/file.cpp new file mode 100644 index 00000000..1d9f38c5 --- /dev/null +++ b/tests/auto/blackbox/testdata/precompiled-headers-and-redefine/file.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/tests/auto/blackbox/testdata/precompiled-headers-and-redefine/main.cpp b/tests/auto/blackbox/testdata/precompiled-headers-and-redefine/main.cpp new file mode 100644 index 00000000..e20edd49 --- /dev/null +++ b/tests/auto/blackbox/testdata/precompiled-headers-and-redefine/main.cpp @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** 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 "pch.h" + +int main() +{ +} diff --git a/tests/auto/blackbox/testdata/precompiled-headers-and-redefine/pch.h b/tests/auto/blackbox/testdata/precompiled-headers-and-redefine/pch.h new file mode 100644 index 00000000..59d72018 --- /dev/null +++ b/tests/auto/blackbox/testdata/precompiled-headers-and-redefine/pch.h @@ -0,0 +1,30 @@ +/**************************************************************************** +** +** 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 +#include diff --git a/tests/auto/blackbox/testdata/precompiled-headers-and-redefine/precompiled-headers-and-redefine.qbs b/tests/auto/blackbox/testdata/precompiled-headers-and-redefine/precompiled-headers-and-redefine.qbs new file mode 100644 index 00000000..55b53a7a --- /dev/null +++ b/tests/auto/blackbox/testdata/precompiled-headers-and-redefine/precompiled-headers-and-redefine.qbs @@ -0,0 +1,16 @@ +CppApplication { + name: "MyApp" + consoleApplication: true + Group { + files: ["pch.h"] + fileTags: ["cpp_pch_src"] + } + Group { + files: ["file.cpp"] + cpp.defines: ["MYDEF=1"] + cpp.cxxFlags: base.concat(qbs.toolchain.contains("clang-cl") ? ["-Wno-clang-cl-pch"] : []) + } + cpp.treatWarningsAsErrors: true + + files: ["main.cpp"] +} diff --git a/tests/auto/blackbox/testdata/prevent-floating-point-values/prevent-floating-point-values.qbs b/tests/auto/blackbox/testdata/prevent-floating-point-values/prevent-floating-point-values.qbs new file mode 100644 index 00000000..bd1d3162 --- /dev/null +++ b/tests/auto/blackbox/testdata/prevent-floating-point-values/prevent-floating-point-values.qbs @@ -0,0 +1,4 @@ +Product { + name: "p" + property bool dummy: { console.info("version: " + version); } +} diff --git a/tests/auto/blackbox/testdata/probe-change-tracking/probe-change-tracking.qbs b/tests/auto/blackbox/testdata/probe-change-tracking/probe-change-tracking.qbs new file mode 100644 index 00000000..6007a33a --- /dev/null +++ b/tests/auto/blackbox/testdata/probe-change-tracking/probe-change-tracking.qbs @@ -0,0 +1,41 @@ +Project { + Probe { + id: tlpProbe + property int confValue + configure: { + console.info("running tlpProbe"); + confValue = 5; + } + } + property int tlpCount: tlpProbe.confValue + + Project { + Probe { + id: subProbe + property int confValue + configure: { + console.info("running subProbe"); + confValue = 7; + } + } + property int subCount: subProbe.confValue + + Product { + name: "theProduct" + property bool runProbe + property int v1: project.tlpCount + property int v2: project.subCount + + Probe { + id: productProbe + condition: product.runProbe + property int v1: product.v1 + property int v2: product.v2 + configure: { + console.info("running productProbe: " + (v1 + v2)); + } + } + } + } +} + diff --git a/tests/auto/blackbox/testdata/probe-in-exported-module/dependee.qbs b/tests/auto/blackbox/testdata/probe-in-exported-module/dependee.qbs new file mode 100644 index 00000000..fe81cf55 --- /dev/null +++ b/tests/auto/blackbox/testdata/probe-in-exported-module/dependee.qbs @@ -0,0 +1,14 @@ +Product { + name: "dependee" + Depends { name: "myothermodule" } + Depends { name: "dependency" } + type: ["out", "dep-out"] + Group { + files: "test.in" + fileTags: ["dep-in"] + } + Group { + files: "test2.in" + fileTags: ["in"] + } +} diff --git a/tests/auto/blackbox/testdata/probe-in-exported-module/dependency.qbs b/tests/auto/blackbox/testdata/probe-in-exported-module/dependency.qbs new file mode 100644 index 00000000..eea5180e --- /dev/null +++ b/tests/auto/blackbox/testdata/probe-in-exported-module/dependency.qbs @@ -0,0 +1,6 @@ +Product { + name: "dependency" + Export { + Depends { name: "mymodule" } + } +} diff --git a/tests/auto/blackbox/testdata/probe-in-exported-module/modules/depmodule/depmodule.qbs b/tests/auto/blackbox/testdata/probe-in-exported-module/modules/depmodule/depmodule.qbs new file mode 100644 index 00000000..41c6cfe5 --- /dev/null +++ b/tests/auto/blackbox/testdata/probe-in-exported-module/modules/depmodule/depmodule.qbs @@ -0,0 +1,18 @@ +Module { + property string prop + property stringList listProp: [] + + Rule { + inputs: ["dep-in"] + outputFileTags: "dep-out" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating dep-out artifact"; + cmd.sourceCode = function() { + console.info("prop: " + product.depmodule.prop); + console.info("listProp: " + product.depmodule.listProp); + }; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/probe-in-exported-module/modules/mymodule/mymodule.qbs b/tests/auto/blackbox/testdata/probe-in-exported-module/modules/mymodule/mymodule.qbs new file mode 100644 index 00000000..89d544f7 --- /dev/null +++ b/tests/auto/blackbox/testdata/probe-in-exported-module/modules/mymodule/mymodule.qbs @@ -0,0 +1,23 @@ +Module { + Depends { name: "depmodule" } + Probe { + id: theProbe + configure: { found = true; } + } + property bool found: theProbe.found + depmodule.prop: found ? "yes" : "no" + depmodule.listProp: theProbe.found ? ["my"] : [] + + Rule { + inputs: ["in"] + outputFileTags: "out" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating out artifact"; + cmd.sourceCode = function() { + console.info("found: " + product.mymodule.found); + }; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/probe-in-exported-module/modules/myothermodule/myothermodule.qbs b/tests/auto/blackbox/testdata/probe-in-exported-module/modules/myothermodule/myothermodule.qbs new file mode 100644 index 00000000..4ea5dd07 --- /dev/null +++ b/tests/auto/blackbox/testdata/probe-in-exported-module/modules/myothermodule/myothermodule.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "depmodule" } + depmodule.listProp: ["myother"] +} diff --git a/tests/auto/blackbox/testdata/probe-in-exported-module/probe-in-exported-module.qbs b/tests/auto/blackbox/testdata/probe-in-exported-module/probe-in-exported-module.qbs new file mode 100644 index 00000000..39809019 --- /dev/null +++ b/tests/auto/blackbox/testdata/probe-in-exported-module/probe-in-exported-module.qbs @@ -0,0 +1,3 @@ +Project { + references: [ "dependee.qbs", "dependency.qbs" ] +} diff --git a/tests/auto/blackbox/testdata/probe-in-exported-module/test.in b/tests/auto/blackbox/testdata/probe-in-exported-module/test.in new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/probe-in-exported-module/test2.in b/tests/auto/blackbox/testdata/probe-in-exported-module/test2.in new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/probeProperties/bin/tool b/tests/auto/blackbox/testdata/probeProperties/bin/tool new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/probeProperties/main.c b/tests/auto/blackbox/testdata/probeProperties/main.c new file mode 100644 index 00000000..210c8274 --- /dev/null +++ b/tests/auto/blackbox/testdata/probeProperties/main.c @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/probeProperties/probeProperties.qbs b/tests/auto/blackbox/testdata/probeProperties/probeProperties.qbs new file mode 100644 index 00000000..ce89d11f --- /dev/null +++ b/tests/auto/blackbox/testdata/probeProperties/probeProperties.qbs @@ -0,0 +1,40 @@ +import qbs.Probes + +Project { + + CppApplication { + Probes.PathProbe { + id: probe1 + names: ["bin/tool"] + platformSearchPaths: [product.sourceDirectory] + } + + Probes.PathProbe { + id: probe2 + names: ["tool"] + platformSearchPaths: [product.sourceDirectory + "/bin"] + } + + targetName: { + console.info("probe1.fileName=" + probe1.fileName); + console.info("probe1.path=" + probe1.path); + console.info("probe1.filePath=" + probe1.filePath); + + console.info("probe2.fileName=" + probe2.fileName); + console.info("probe2.path=" + probe2.path); + console.info("probe2.filePath=" + probe2.filePath); + + console.info("probe3.fileName=" + probe3.fileName); + console.info("probe3.path=" + probe3.path); + console.info("probe3.filePath=" + probe3.filePath); + return name; + } + } + + Probes.PathProbe { + id: probe3 + names: ["tool"] + platformSearchPaths: [project.sourceDirectory + "/bin"] + } + +} diff --git a/tests/auto/blackbox/testdata/probes-and-array-properties/modules/mymodule/mymodule.qbs b/tests/auto/blackbox/testdata/probes-and-array-properties/modules/mymodule/mymodule.qbs new file mode 100644 index 00000000..e5c368a8 --- /dev/null +++ b/tests/auto/blackbox/testdata/probes-and-array-properties/modules/mymodule/mymodule.qbs @@ -0,0 +1,26 @@ +Module { + Probe { + id: propProbe + property stringList prop: [] + configure: { + prop = []; + prop.push("probe"); + found = true; + } + } + + property stringList prop: propProbe.found ? propProbe.prop : ["other"] + + Rule { + multiplex: true + outputFileTags: "the-output" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating dummy"; + cmd.sourceCode = function() { + console.info("prop: " + JSON.stringify(product.mymodule.prop)); + } + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/probes-and-array-properties/probes-and-array-properties.qbs b/tests/auto/blackbox/testdata/probes-and-array-properties/probes-and-array-properties.qbs new file mode 100644 index 00000000..70ca8e1d --- /dev/null +++ b/tests/auto/blackbox/testdata/probes-and-array-properties/probes-and-array-properties.qbs @@ -0,0 +1,6 @@ +Product { + name: "theProduct" + type: ["the-output"] + Depends { name: "mymodule" } +// mymodule.prop: ["product"] +} diff --git a/tests/auto/blackbox/testdata/probes-and-shadow-products/probes-and-shadow-products.qbs b/tests/auto/blackbox/testdata/probes-and-shadow-products/probes-and-shadow-products.qbs new file mode 100644 index 00000000..19fc67f7 --- /dev/null +++ b/tests/auto/blackbox/testdata/probes-and-shadow-products/probes-and-shadow-products.qbs @@ -0,0 +1,11 @@ +Product { + name: "p" + multiplexByQbsProperties: "buildVariants" + qbs.buildVariants: ["debug", "release"] + Export { + Probe { + id: dummy + configure: { found = true; } + } + } +} diff --git a/tests/auto/blackbox/testdata/probes-in-nested-modules/modules/inner/inner.qbs b/tests/auto/blackbox/testdata/probes-in-nested-modules/modules/inner/inner.qbs new file mode 100644 index 00000000..5a254b49 --- /dev/null +++ b/tests/auto/blackbox/testdata/probes-in-nested-modules/modules/inner/inner.qbs @@ -0,0 +1,19 @@ +import qbs.Probes + +Module { + property bool alt: false + + Probe { + id: foo + property string baz + property bool useAlt: alt + property string named: product.name + configure: { + console.info("running probe " + named); + baz = useAlt ? "hahaha" : "hello"; + found = true; + } + } + + property string something: foo.baz +} diff --git a/tests/auto/blackbox/testdata/probes-in-nested-modules/modules/outer/outer.qbs b/tests/auto/blackbox/testdata/probes-in-nested-modules/modules/outer/outer.qbs new file mode 100644 index 00000000..4472d573 --- /dev/null +++ b/tests/auto/blackbox/testdata/probes-in-nested-modules/modules/outer/outer.qbs @@ -0,0 +1,17 @@ +Module { + Depends { name: "inner" } + + Probe { + id: foo2 + property string barz + property string named: product.name + configure: { + console.info("running second probe " + named); + barz = "goodbye"; + found = true; + } + } + + property string something: inner.something + property string somethingElse: foo2.barz +} diff --git a/tests/auto/blackbox/testdata/probes-in-nested-modules/probes-in-nested-modules.qbs b/tests/auto/blackbox/testdata/probes-in-nested-modules/probes-in-nested-modules.qbs new file mode 100644 index 00000000..877300c1 --- /dev/null +++ b/tests/auto/blackbox/testdata/probes-in-nested-modules/probes-in-nested-modules.qbs @@ -0,0 +1,36 @@ +Project { + Product { + name: "a" + Depends { name: "outer" } + inner.alt: true + property bool dummy: { + console.info("product " + name + ", inner.something = " + inner.something); + console.info("product " + name + ", outer.something = " + outer.something); + console.info("product " + name + ", outer.somethingElse = " + outer.somethingElse); + return true; + } + type: ["foo"] + } + + Product { + name: "b" + Depends { name: "inner" } + inner.alt: true + property bool dummy: { + console.info("product " + name + ", inner.something = " + inner.something); + return true; + } + type: ["foo"] + } + + Product { + name: "c" + Depends { name: "inner" } + inner.alt: false + property bool dummy: { + console.info("product " + name + ", inner.something = " + inner.something); + return true; + } + type: ["foo"] + } +} diff --git a/tests/auto/blackbox/testdata/product-dependencies-by-type/main.cpp b/tests/auto/blackbox/testdata/product-dependencies-by-type/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/blackbox/testdata/product-dependencies-by-type/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/blackbox/testdata/product-dependencies-by-type/modules/myconfig/myconfig.qbs b/tests/auto/blackbox/testdata/product-dependencies-by-type/modules/myconfig/myconfig.qbs new file mode 100644 index 00000000..5bc1fd61 --- /dev/null +++ b/tests/auto/blackbox/testdata/product-dependencies-by-type/modules/myconfig/myconfig.qbs @@ -0,0 +1,3 @@ +Module { + property bool typeDecider: true +} diff --git a/tests/auto/blackbox/testdata/product-dependencies-by-type/product-dependencies-by-type.qbs b/tests/auto/blackbox/testdata/product-dependencies-by-type/product-dependencies-by-type.qbs new file mode 100644 index 00000000..8fa761a2 --- /dev/null +++ b/tests/auto/blackbox/testdata/product-dependencies-by-type/product-dependencies-by-type.qbs @@ -0,0 +1,139 @@ +import qbs.TextFile + +Project { + CppApplication { + consoleApplication: true + name: "no-match" + files: "main.cpp" + } + + Project { + CppApplication { + consoleApplication: true + name: "app1" + files: "main.cpp" + } + CppApplication { + consoleApplication: true + name: "app2" + files: "main.cpp" + } + CppApplication { + consoleApplication: true + name: "app3" + files: "main.cpp" + } + Product { + type: myconfig.typeDecider ? ["application"] : [] + Depends { name: "cpp" } + Depends { name: "myconfig" } + consoleApplication: true + name: "app4" + files: "main.cpp" + } + Product { + name: "other-product" + type: "other" + Rule { + multiplex: true + Artifact { + filePath: "output.txt" + fileTags: "other" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.filePath; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + } + return cmd; + } + } + Export { + Depends { productTypes: "other2" } + } + } + Product { + name: "yet-another-product" + type: "other2" + Rule { + multiplex: true + Artifact { + filePath: "output.txt" + fileTags: "other2" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.filePath; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + } + return cmd; + } + } + } + Product { + name: "helper" + Export { + Depends { productTypes: "other" } + } + } + + CppApplication { + condition: false + consoleApplication: true + name: "disabled-app" + files: "main.cpp" + } + + DynamicLibrary { + Depends { name: "cpp" } + name: "lib-product" + files: "main.cpp" + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } + + CppApplication { + type: base.concat(["app-list"]) + consoleApplication: true + name: "app list" + Depends { + productTypes: ["application"] + limitToSubProject: true + } + Depends { name: "helper" } + files: ["main.cpp"] + + Rule { + multiplex: true + inputsFromDependencies: ["application", "other", "other2"] + Artifact { + filePath: "app-list.txt" + fileTags: "app-list" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Collecting apps"; + cmd.sourceCode = function() { + var file = new TextFile(output.filePath, TextFile.WriteOnly); + for (var i = 0; i < inputs["application"].length; ++i) + file.writeLine(inputs["application"][i].filePath); + for (i = 0; i < inputs["other"].length; ++i) + file.writeLine(inputs["other"][i].filePath); + for (i = 0; i < inputs["other2"].length; ++i) + file.writeLine(inputs["other2"][i].filePath); + file.close(); + }; + return cmd; + } + } + } + Product { + name: "dummy" + Depends { productTypes: [] } + } + } +} diff --git a/tests/auto/blackbox/testdata/product-in-exported-module/modules/m/m.qbs b/tests/auto/blackbox/testdata/product-in-exported-module/modules/m/m.qbs new file mode 100644 index 00000000..0e79d0ab --- /dev/null +++ b/tests/auto/blackbox/testdata/product-in-exported-module/modules/m/m.qbs @@ -0,0 +1,3 @@ +Module { + Depends { name: "dummy"; condition: { console.info("product: " + product.name); return false; } } +} diff --git a/tests/auto/blackbox/testdata/product-in-exported-module/product-in-exported-module.qbs b/tests/auto/blackbox/testdata/product-in-exported-module/product-in-exported-module.qbs new file mode 100644 index 00000000..f978aa77 --- /dev/null +++ b/tests/auto/blackbox/testdata/product-in-exported-module/product-in-exported-module.qbs @@ -0,0 +1,10 @@ +Project { + Product { + name: "p" + Depends { name: "dep" } + } + Product { + name: "dep" + Export { Depends { name: "m" } } + } +} diff --git a/tests/auto/blackbox/testdata/productproperties/app.qbs b/tests/auto/blackbox/testdata/productproperties/app.qbs new file mode 100644 index 00000000..b867aec3 --- /dev/null +++ b/tests/auto/blackbox/testdata/productproperties/app.qbs @@ -0,0 +1,10 @@ +Product { + consoleApplication: true + type: "application" + name: "blubb_user" + + files: "main.cpp" + + Depends { name: "blubb_header" } + Depends { name: "cpp" } +} diff --git a/tests/auto/blackbox/testdata/productproperties/blubb_header.h.in b/tests/auto/blackbox/testdata/productproperties/blubb_header.h.in new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/productproperties/header.qbs b/tests/auto/blackbox/testdata/productproperties/header.qbs new file mode 100644 index 00000000..42f9c88d --- /dev/null +++ b/tests/auto/blackbox/testdata/productproperties/header.qbs @@ -0,0 +1,34 @@ +import qbs.TextFile + +Product { + name: "blubb_header" + type: "hpp" + files: "blubb_header.h.in" + property string blubbProp: project.blubbProp + + Rule { + multiplex: true + Artifact { + filePath: "blubb_header.h" + fileTags: "hpp" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating blubb_header.h"; + cmd.highlight = "codegen"; + cmd.blubbProp = product.blubbProp; + cmd.sourceCode = function() { + file = new TextFile(output.filePath, TextFile.WriteOnly); + file.truncate(); + file.write("#define BLUBB_PROP " + blubbProp); + file.close(); + } + return cmd; + } + } + + Export { + Depends { name: "cpp" } + cpp.includePaths: product.buildDirectory + } +} diff --git a/tests/auto/blackbox/testdata/productproperties/main.cpp b/tests/auto/blackbox/testdata/productproperties/main.cpp new file mode 100644 index 00000000..cb991ec9 --- /dev/null +++ b/tests/auto/blackbox/testdata/productproperties/main.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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 "blubb_header.h" + +int main() +{ +#if BLUBB_PROP != 5 + blubb(); +#endif +} diff --git a/tests/auto/blackbox/testdata/productproperties/productproperties.qbs b/tests/auto/blackbox/testdata/productproperties/productproperties.qbs new file mode 100644 index 00000000..79fdc34a --- /dev/null +++ b/tests/auto/blackbox/testdata/productproperties/productproperties.qbs @@ -0,0 +1,4 @@ +Project { + property string blubbProp: "5" + references: ["header.qbs", "app.qbs"] +} diff --git a/tests/auto/blackbox/testdata/project_filepath_check/main.cpp b/tests/auto/blackbox/testdata/project_filepath_check/main.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/blackbox/testdata/project_filepath_check/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/blackbox/testdata/project_filepath_check/main2.cpp b/tests/auto/blackbox/testdata/project_filepath_check/main2.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/project_filepath_check/main2.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/project_filepath_check/project1.qbs b/tests/auto/blackbox/testdata/project_filepath_check/project1.qbs new file mode 100644 index 00000000..253992dd --- /dev/null +++ b/tests/auto/blackbox/testdata/project_filepath_check/project1.qbs @@ -0,0 +1,3 @@ +CppApplication { + files: "main.cpp" +} diff --git a/tests/auto/blackbox/testdata/project_filepath_check/project2.qbs b/tests/auto/blackbox/testdata/project_filepath_check/project2.qbs new file mode 100644 index 00000000..8bae2dd6 --- /dev/null +++ b/tests/auto/blackbox/testdata/project_filepath_check/project2.qbs @@ -0,0 +1,3 @@ +CppApplication { + files: "main2.cpp" +} diff --git a/tests/auto/blackbox/testdata/proper quoting/main.cpp b/tests/auto/blackbox/testdata/proper quoting/main.cpp new file mode 100644 index 00000000..22cafeaa --- /dev/null +++ b/tests/auto/blackbox/testdata/proper quoting/main.cpp @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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 + +int bla(); + +int main() +{ + printf(DEFINE"\n"); + printf(DEFINEWITHSPACE"\n"); + printf(DEFINEWITHTAB"\n"); + printf(DEFINEWITHBACKSLASH"\n"); + + return bla(); +} diff --git a/tests/auto/blackbox/testdata/proper quoting/my static lib helper.cpp b/tests/auto/blackbox/testdata/proper quoting/my static lib helper.cpp new file mode 100644 index 00000000..96ccb1b4 --- /dev/null +++ b/tests/auto/blackbox/testdata/proper quoting/my static lib helper.cpp @@ -0,0 +1,33 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int helper_function() +{ + return 156; +} + diff --git a/tests/auto/blackbox/testdata/proper quoting/my static lib.cpp b/tests/auto/blackbox/testdata/proper quoting/my static lib.cpp new file mode 100644 index 00000000..e7490e80 --- /dev/null +++ b/tests/auto/blackbox/testdata/proper quoting/my static lib.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 +#include + +int bla() +{ + int n = getSomeNumber(); + printf("Hello World! The magic number is %d.", n); + return n; +} diff --git a/tests/auto/blackbox/testdata/proper quoting/proper quoting.qbs b/tests/auto/blackbox/testdata/proper quoting/proper quoting.qbs new file mode 100644 index 00000000..cf1fabf2 --- /dev/null +++ b/tests/auto/blackbox/testdata/proper quoting/proper quoting.qbs @@ -0,0 +1,46 @@ +import qbs 1.0 + +Project { + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + type: "application" + consoleApplication: true + name: "Hello World" + files : [ "main.cpp" ] + Depends { name: "cpp" } + Depends { name: "my static lib" } + cpp.defines: [ + 'DEFINE="whitespaceless"', + 'DEFINEWITHSPACE="contains space"', + 'DEFINEWITHTAB="contains\ttab"', + 'DEFINEWITHBACKSLASH="backslash\\\\"', + ] + } + + Product { + type: "staticlibrary" + name : "my static lib" + files : [ "my static lib.cpp" ] + Depends { name: "cpp" } + Depends { name: "helper lib" } + } + + Product { + type: "staticlibrary" + name : "helper lib" + files : [ + "some helper/some helper.h", + "some helper/some helper.cpp" + ] + Depends { name: "cpp" } + Export { + Depends { name: "cpp" } + cpp.includePaths: [product.sourceDirectory + '/some helper'] + } + } +} diff --git a/tests/auto/blackbox/testdata/proper quoting/some helper/some helper.cpp b/tests/auto/blackbox/testdata/proper quoting/some helper/some helper.cpp new file mode 100644 index 00000000..981a07e1 --- /dev/null +++ b/tests/auto/blackbox/testdata/proper quoting/some helper/some helper.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 "some helper.h" + +int getSomeNumber() +{ + return 156; +} + diff --git a/tests/auto/blackbox/testdata/proper quoting/some helper/some helper.h b/tests/auto/blackbox/testdata/proper quoting/some helper/some helper.h new file mode 100644 index 00000000..e0c0b2fa --- /dev/null +++ b/tests/auto/blackbox/testdata/proper quoting/some helper/some helper.h @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef HELPER_H +#define HELPER_H + +extern int getSomeNumber(); + +#endif + diff --git a/tests/auto/blackbox/testdata/properties-in-export-items/main1.cpp b/tests/auto/blackbox/testdata/properties-in-export-items/main1.cpp new file mode 100644 index 00000000..c1c3786a --- /dev/null +++ b/tests/auto/blackbox/testdata/properties-in-export-items/main1.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifdef P1 +int main() { } +#endif diff --git a/tests/auto/blackbox/testdata/properties-in-export-items/main2.cpp b/tests/auto/blackbox/testdata/properties-in-export-items/main2.cpp new file mode 100644 index 00000000..e5a0cd4d --- /dev/null +++ b/tests/auto/blackbox/testdata/properties-in-export-items/main2.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifdef P2 +int main() { } +#endif diff --git a/tests/auto/blackbox/testdata/properties-in-export-items/properties-in-export-items.qbs b/tests/auto/blackbox/testdata/properties-in-export-items/properties-in-export-items.qbs new file mode 100644 index 00000000..a13578f9 --- /dev/null +++ b/tests/auto/blackbox/testdata/properties-in-export-items/properties-in-export-items.qbs @@ -0,0 +1,31 @@ +Project { + minimumQbsVersion: "1.6" + + Product { + name: "dep" + Export { + property string theDefine: "" + Depends { name: "cpp" } + cpp.defines: [theDefine] + } + } + + Application { + name: "p1" + consoleApplication: true + Depends { name: "dep" } + dep.theDefine: "P1" + cpp.minimumMacosVersion: "10.9" + files: ["main1.cpp"] + } + Application { + name: "p2" + consoleApplication: true + Depends { name: "dep" } + cpp.minimumMacosVersion: "10.9" + Group { + dep.theDefine: "P2" + files: ["main2.cpp"] + } + } +} diff --git a/tests/auto/blackbox/testdata/property-assignment-in-failed-module/main.cpp b/tests/auto/blackbox/testdata/property-assignment-in-failed-module/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/property-assignment-in-failed-module/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/property-assignment-in-failed-module/modules/m/m.qbs b/tests/auto/blackbox/testdata/property-assignment-in-failed-module/modules/m/m.qbs new file mode 100644 index 00000000..8170c199 --- /dev/null +++ b/tests/auto/blackbox/testdata/property-assignment-in-failed-module/modules/m/m.qbs @@ -0,0 +1,9 @@ +Module { + property bool doFail + Depends { name: "cpp" } + cpp.dynamicLibraries: ["nosuchlib"] + validate: { + if (doFail) + throw "Failure!"; + } +} diff --git a/tests/auto/blackbox/testdata/property-assignment-in-failed-module/property-assignment-in-failed-module.qbs b/tests/auto/blackbox/testdata/property-assignment-in-failed-module/property-assignment-in-failed-module.qbs new file mode 100644 index 00000000..baf315bf --- /dev/null +++ b/tests/auto/blackbox/testdata/property-assignment-in-failed-module/property-assignment-in-failed-module.qbs @@ -0,0 +1,5 @@ +CppApplication { + name: "app" + Depends { name: "m"; required: false } + files: "main.cpp" +} diff --git a/tests/auto/blackbox/testdata/property-assignment-on-non-present-module/property-assignment-on-non-present-module.qbs b/tests/auto/blackbox/testdata/property-assignment-on-non-present-module/property-assignment-on-non-present-module.qbs new file mode 100644 index 00000000..a01d6c56 --- /dev/null +++ b/tests/auto/blackbox/testdata/property-assignment-on-non-present-module/property-assignment-on-non-present-module.qbs @@ -0,0 +1,4 @@ +Product { + Depends { name: "nein"; required: false } + nein.doch: "ohhh!" +} diff --git a/tests/auto/blackbox/testdata/property-evaluation-context/modules/base/base.qbs b/tests/auto/blackbox/testdata/property-evaluation-context/modules/base/base.qbs new file mode 100644 index 00000000..a9753875 --- /dev/null +++ b/tests/auto/blackbox/testdata/property-evaluation-context/modules/base/base.qbs @@ -0,0 +1,4 @@ +Module { + property string productInBase: product.name + property string productInTop: "" +} diff --git a/tests/auto/blackbox/testdata/property-evaluation-context/modules/top/top.qbs b/tests/auto/blackbox/testdata/property-evaluation-context/modules/top/top.qbs new file mode 100644 index 00000000..fa073ff7 --- /dev/null +++ b/tests/auto/blackbox/testdata/property-evaluation-context/modules/top/top.qbs @@ -0,0 +1,6 @@ +Module { + Depends { name: "base" } + base.productInTop: product.name + property string productInTop: product.name + property string productInExport: "" +} diff --git a/tests/auto/blackbox/testdata/property-evaluation-context/property-evaluation-context.qbs b/tests/auto/blackbox/testdata/property-evaluation-context/property-evaluation-context.qbs new file mode 100644 index 00000000..28216c15 --- /dev/null +++ b/tests/auto/blackbox/testdata/property-evaluation-context/property-evaluation-context.qbs @@ -0,0 +1,34 @@ +Project { + qbsSearchPaths: [ path ] + Product { + name: "mylib" + Export { + Depends { name: "top" } + top.productInExport: product.name + } + } + + Product { + type: "rule-output" + name: "myapp" + Depends { name: "mylib" } + + Rule { + alwaysRun: true + multiplex: true + requiresInputs: false + outputFileTags: "rule-output" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + console.info("base.productInBase evaluated in: " + product.base.productInBase); + console.info("base.productInTop evaluated in: " + product.base.productInTop); + console.info("top.productInExport evaluated in: " + product.top.productInExport); + console.info("top.productInTop evaluated in: " + product.top.productInTop); + } + return [cmd]; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/property-precedence/dep.qbs b/tests/auto/blackbox/testdata/property-precedence/dep.qbs new file mode 100644 index 00000000..9f9668ce --- /dev/null +++ b/tests/auto/blackbox/testdata/property-precedence/dep.qbs @@ -0,0 +1,9 @@ +Product { + name: "dep" + Export { + Depends { name: "leaf" } + Depends { name: "nonleaf" } + // leaf.scalarProp: "export" + // leaf.listProp: ["export"] + } +} diff --git a/tests/auto/blackbox/testdata/property-precedence/dummy.txt b/tests/auto/blackbox/testdata/property-precedence/dummy.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/property-precedence/modules/leaf/leaf.qbs b/tests/auto/blackbox/testdata/property-precedence/modules/leaf/leaf.qbs new file mode 100644 index 00000000..1bfb1a1b --- /dev/null +++ b/tests/auto/blackbox/testdata/property-precedence/modules/leaf/leaf.qbs @@ -0,0 +1,18 @@ +Module { + property string scalarProp: "leaf" + property stringList listProp: ["leaf"] + + Rule { + inputs: ["rule-input"] + outputFileTags: "rule-output" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + console.info("scalar prop: " + product.leaf.scalarProp); + console.info("list prop: " + JSON.stringify(product.leaf.listProp)); + } + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/property-precedence/modules/nonleaf/nonleaf.qbs b/tests/auto/blackbox/testdata/property-precedence/modules/nonleaf/nonleaf.qbs new file mode 100644 index 00000000..a829da09 --- /dev/null +++ b/tests/auto/blackbox/testdata/property-precedence/modules/nonleaf/nonleaf.qbs @@ -0,0 +1,6 @@ +Module { + Depends { name: "leaf" } + + // leaf.scalarProp: "nonleaf" + // leaf.listProp: ["nonleaf"] +} diff --git a/tests/auto/blackbox/testdata/property-precedence/property-precedence.qbs b/tests/auto/blackbox/testdata/property-precedence/property-precedence.qbs new file mode 100644 index 00000000..1691376e --- /dev/null +++ b/tests/auto/blackbox/testdata/property-precedence/property-precedence.qbs @@ -0,0 +1,16 @@ +Project { + references: ["dep.qbs"] + Product { + name: "toplevel" + type: ["rule-output"] + Depends { name: "leaf" } + Depends { name: "nonleaf" } + Depends { name: "dep" } + Group { + files: ["dummy.txt"] + fileTags: ["rule-input"] + } + // leaf.scalarProp: "product" + // leaf.listProp: ["product"] + } +} diff --git a/tests/auto/blackbox/testdata/propertyChanges/lib.cpp b/tests/auto/blackbox/testdata/propertyChanges/lib.cpp new file mode 100644 index 00000000..5997c193 --- /dev/null +++ b/tests/auto/blackbox/testdata/propertyChanges/lib.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" + +DLL_EXPORT void f() {} diff --git a/tests/auto/blackbox/testdata/propertyChanges/modules/TestModule/module.qbs b/tests/auto/blackbox/testdata/propertyChanges/modules/TestModule/module.qbs new file mode 100644 index 00000000..b1e4a1fd --- /dev/null +++ b/tests/auto/blackbox/testdata/propertyChanges/modules/TestModule/module.qbs @@ -0,0 +1,33 @@ +import qbs.File + +Module { + FileTagger { + patterns: ["*.in"] + fileTags: "test-input" + } + + property string testProperty + + Rule { + inputs: ['test-input'] + Artifact { + fileTags: "test-output" + filePath: input.fileName + ".out" + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.highlight = "codegen"; + cmd.description = "Making output from input"; + cmd.sourceCode = function() { + // console.info('Change in source code'); + console.info(input.TestModule.testProperty); + File.copy(input.filePath, output.filePath); + } + var dummyCmd = new JavaScriptCommand(); + dummyCmd.silent = true; + dummyCmd.sourceCode = function() {}; + return [cmd, dummyCmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/propertyChanges/propertyChanges.qbs b/tests/auto/blackbox/testdata/propertyChanges/propertyChanges.qbs new file mode 100644 index 00000000..f13b1986 --- /dev/null +++ b/tests/auto/blackbox/testdata/propertyChanges/propertyChanges.qbs @@ -0,0 +1,93 @@ +import qbs.Environment +import qbs.File +import qbs.TextFile + +Project { + property var projectDefines: ["blubb2"] + property string fileContentSuffix: "suffix 1" + property string testProperty: "default value" + CppApplication { + name: qbs.enableDebugCode ? "product 1.debug" : "product 1.release" + cpp.defines: ["blubb1"] + files: "source1.cpp" + } + CppApplication { + Depends { name: 'library' } + name: "product 2" + cpp.defines: project.projectDefines + files: "source2.cpp" + } + CppApplication { + name: "product 3" + cpp.defines: Environment.getEnv("QBS_BLACKBOX_DEFINE") + files: "source3.cpp" + } + DynamicLibrary { + Depends { name: "cpp" } + name: "library" + files: "lib.cpp" + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + } + + Product { + name: "generated text file" + type: ["my_output"] + property string fileContentPrefix: "prefix 1" + + Rule { + multiplex: true + Artifact { filePath: "nothing"; fileTags: ["my_output"] } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { console.info(product.fileContentPrefix); } + return cmd; + } + } + + Rule { + multiplex: true + Artifact { filePath: "generated.txt"; fileTags: ["my_output"] } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.filePath; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + file = new TextFile(output.filePath, TextFile.WriteOnly); + file.truncate(); + file.write(product.fileContentPrefix + "contents 1" + + project.fileContentSuffix); + file.close(); + } + return cmd; + } + } + } + + Product { + Depends { name: "ruletest" } + type: ["test-output2"] + Rule { + inputsFromDependencies: ['test-output'] + Artifact { + fileTags: "test-output2" + filePath: input.fileName + ".out2" + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.highlight = "codegen"; + cmd.description = "Making output from other output"; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); } + return cmd; + } + } + } + + references: "ruletest.qbs" + + qbsSearchPaths: "." +} diff --git a/tests/auto/blackbox/testdata/propertyChanges/ruletest.qbs b/tests/auto/blackbox/testdata/propertyChanges/ruletest.qbs new file mode 100644 index 00000000..1d8ea961 --- /dev/null +++ b/tests/auto/blackbox/testdata/propertyChanges/ruletest.qbs @@ -0,0 +1,9 @@ +Product { + name: "ruletest" + type: "test-output" + Depends { name: "TestModule" } + Group { + files: "test.in" + TestModule.testProperty: project.testProperty + } +} diff --git a/tests/auto/blackbox/testdata/propertyChanges/source1.cpp b/tests/auto/blackbox/testdata/propertyChanges/source1.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/blackbox/testdata/propertyChanges/source1.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/blackbox/testdata/propertyChanges/source2.cpp b/tests/auto/blackbox/testdata/propertyChanges/source2.cpp new file mode 100644 index 00000000..13c5f83a --- /dev/null +++ b/tests/auto/blackbox/testdata/propertyChanges/source2.cpp @@ -0,0 +1,30 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} + diff --git a/tests/auto/blackbox/testdata/propertyChanges/source3.cpp b/tests/auto/blackbox/testdata/propertyChanges/source3.cpp new file mode 100644 index 00000000..13c5f83a --- /dev/null +++ b/tests/auto/blackbox/testdata/propertyChanges/source3.cpp @@ -0,0 +1,30 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} + diff --git a/tests/auto/blackbox/testdata/propertyChanges/test.in b/tests/auto/blackbox/testdata/propertyChanges/test.in new file mode 100644 index 00000000..8633abf1 --- /dev/null +++ b/tests/auto/blackbox/testdata/propertyChanges/test.in @@ -0,0 +1 @@ +blubb diff --git a/tests/auto/blackbox/testdata/protobuf/addressbook.proto b/tests/auto/blackbox/testdata/protobuf/addressbook.proto new file mode 100644 index 00000000..b4b33b4c --- /dev/null +++ b/tests/auto/blackbox/testdata/protobuf/addressbook.proto @@ -0,0 +1,51 @@ +// See README.txt for information and build instructions. +// +// Note: START and END tags are used in comments to define sections used in +// tutorials. They are not part of the syntax for Protocol Buffers. +// +// To get an in-depth walkthrough of this file and the related examples, see: +// https://developers.google.com/protocol-buffers/docs/tutorials + +// [START declaration] +syntax = "proto3"; +package tutorial; + +import "google/protobuf/timestamp.proto"; +// [END declaration] + +// [START java_declaration] +option java_package = "com.example.tutorial"; +option java_outer_classname = "AddressBookProtos"; +// [END java_declaration] + +// [START csharp_declaration] +option csharp_namespace = "Google.Protobuf.Examples.AddressBook"; +// [END csharp_declaration] + +// [START messages] +message Person { + string name = 1; + int32 id = 2; // Unique ID number for this person. + string email = 3; + + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + message PhoneNumber { + string number = 1; + PhoneType type = 2; + } + + repeated PhoneNumber phones = 4; + + google.protobuf.Timestamp last_updated = 5; +} + +// Our address book file is just one of these. +message AddressBook { + repeated Person people = 1; +} +// [END messages] diff --git a/tests/auto/blackbox/testdata/protobuf/addressbook_cpp.qbs b/tests/auto/blackbox/testdata/protobuf/addressbook_cpp.qbs new file mode 100644 index 00000000..5e6ffc50 --- /dev/null +++ b/tests/auto/blackbox/testdata/protobuf/addressbook_cpp.qbs @@ -0,0 +1,27 @@ +import qbs + +CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result && hasProtobuf; + } + name: "addressbook_cpp" + consoleApplication: true + + Depends { name: "cpp" } + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.8" + + Depends { name: "protobuf.cpp"; required: false } + property bool hasProtobuf: { + console.info("has protobuf: " + protobuf.cpp.present); + return protobuf.cpp.present; + } + + files: [ + "addressbook.proto", + "main.cpp", + ] +} diff --git a/tests/auto/blackbox/testdata/protobuf/addressbook_objc.qbs b/tests/auto/blackbox/testdata/protobuf/addressbook_objc.qbs new file mode 100644 index 00000000..54433ea6 --- /dev/null +++ b/tests/auto/blackbox/testdata/protobuf/addressbook_objc.qbs @@ -0,0 +1,24 @@ +import qbs + +CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result && hasProtobuf; + } + name: "addressbook_objc" + consoleApplication: true + + Depends { name: "cpp" } + Depends { name: "protobuf.objc"; required: false } + property bool hasProtobuf: { + console.info("has protobuf: " + protobuf.objc.present); + return protobuf.objc.present; + } + + files: [ + "addressbook.proto", + "main.m", + ] +} diff --git a/tests/auto/blackbox/testdata/protobuf/import-main.cpp b/tests/auto/blackbox/testdata/protobuf/import-main.cpp new file mode 100644 index 00000000..65048dd8 --- /dev/null +++ b/tests/auto/blackbox/testdata/protobuf/import-main.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://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. +** +****************************************************************************/ + +#include + +int main() +{ + MyMessage message; + message.set_msg("msg"); + return 0; +} diff --git a/tests/auto/blackbox/testdata/protobuf/import.proto b/tests/auto/blackbox/testdata/protobuf/import.proto new file mode 100644 index 00000000..d11e83b7 --- /dev/null +++ b/tests/auto/blackbox/testdata/protobuf/import.proto @@ -0,0 +1,6 @@ +syntax = "proto2"; +import "subdir/myenum.proto"; + +message MyMessage { + required string msg = 1; +} diff --git a/tests/auto/blackbox/testdata/protobuf/import.qbs b/tests/auto/blackbox/testdata/protobuf/import.qbs new file mode 100644 index 00000000..ef4e80c1 --- /dev/null +++ b/tests/auto/blackbox/testdata/protobuf/import.qbs @@ -0,0 +1,29 @@ +import qbs + +CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result && hasProtobuf; + } + name: "app" + consoleApplication: true + + protobuf.cpp.importPaths: [sourceDirectory] + + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.8" + + Depends { name: "protobuf.cpp"; required: false } + property bool hasProtobuf: { + console.info("has protobuf: " + protobuf.cpp.present); + return protobuf.cpp.present; + } + + files: [ + "import.proto", + "import-main.cpp", + "subdir/myenum.proto", + ] +} diff --git a/tests/auto/blackbox/testdata/protobuf/main.cpp b/tests/auto/blackbox/testdata/protobuf/main.cpp new file mode 100644 index 00000000..d4b1c431 --- /dev/null +++ b/tests/auto/blackbox/testdata/protobuf/main.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://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. +** +****************************************************************************/ + +#include +#include + +#include "addressbook.pb.h" + +using google::protobuf::util::TimeUtil; + +int main(int /*argc*/, char* /*argv*/[]) { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + tutorial::AddressBook addressBook; + + auto person = addressBook.add_people(); + person->set_name("name"); + person->set_id(1); + person->set_email("email"); + + auto phone_number = person->add_phones(); + phone_number->set_number("number"); + phone_number->set_type(tutorial::Person::MOBILE); + + *person->mutable_last_updated() = TimeUtil::SecondsToTimestamp(time(nullptr)); + + google::protobuf::ShutdownProtobufLibrary(); + + return 0; +} + diff --git a/tests/auto/blackbox/testdata/protobuf/main.m b/tests/auto/blackbox/testdata/protobuf/main.m new file mode 100644 index 00000000..414d1aa2 --- /dev/null +++ b/tests/auto/blackbox/testdata/protobuf/main.m @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://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 "Addressbook.pbobjc.h" + +int main(int argc, char* argv[]) +{ + AddressBook *addressBook = [[AddressBook alloc] init]; + + Person *person = [[Person alloc] init]; + person.name = @"name"; + person.id_p = 1; + person.email = @"email"; + + Person_PhoneNumber *number = [[Person_PhoneNumber alloc] init]; + number.number = @"number"; + number.type = Person_PhoneType_Mobile; + + [addressBook.peopleArray addObject:person]; + [addressBook release]; + + return 0; +} diff --git a/tests/auto/blackbox/testdata/protobuf/needs-import-dir-main.cpp b/tests/auto/blackbox/testdata/protobuf/needs-import-dir-main.cpp new file mode 100644 index 00000000..5d62a0d0 --- /dev/null +++ b/tests/auto/blackbox/testdata/protobuf/needs-import-dir-main.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://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. +** +****************************************************************************/ + +#include + +int main() +{ + MyMessage message; + message.set_msg("msg"); + return 0; +} diff --git a/tests/auto/blackbox/testdata/protobuf/needs-import-dir.proto b/tests/auto/blackbox/testdata/protobuf/needs-import-dir.proto new file mode 100644 index 00000000..0d0cb657 --- /dev/null +++ b/tests/auto/blackbox/testdata/protobuf/needs-import-dir.proto @@ -0,0 +1,6 @@ +syntax = "proto2"; +import "myenum.proto"; + +message MyMessage { + required string msg = 1; +} diff --git a/tests/auto/blackbox/testdata/protobuf/needs-import-dir.qbs b/tests/auto/blackbox/testdata/protobuf/needs-import-dir.qbs new file mode 100644 index 00000000..493632a0 --- /dev/null +++ b/tests/auto/blackbox/testdata/protobuf/needs-import-dir.qbs @@ -0,0 +1,30 @@ +import qbs + +CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result && hasProtobuf; + } + name: "app" + consoleApplication: true + + property path theImportDir + protobuf.cpp.importPaths: (theImportDir ? [theImportDir] : []).concat([sourceDirectory]) + + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.8" + + Depends { name: "protobuf.cpp"; required: false } + property bool hasProtobuf: { + console.info("has protobuf: " + protobuf.cpp.present); + return protobuf.cpp.present; + } + + files: [ + "needs-import-dir.proto", + "needs-import-dir-main.cpp", + "subdir/myenum.proto", + ] +} diff --git a/tests/auto/blackbox/testdata/protobuf/subdir/myenum.proto b/tests/auto/blackbox/testdata/protobuf/subdir/myenum.proto new file mode 100644 index 00000000..0b82869f --- /dev/null +++ b/tests/auto/blackbox/testdata/protobuf/subdir/myenum.proto @@ -0,0 +1,6 @@ +syntax = "proto2"; + +enum MyEnum { + VAL1 = 0; + VAL2 = 1; +} diff --git a/tests/auto/blackbox/testdata/pseudo-multiplexing/pseudo-multiplexing.qbs b/tests/auto/blackbox/testdata/pseudo-multiplexing/pseudo-multiplexing.qbs new file mode 100644 index 00000000..88c565ba --- /dev/null +++ b/tests/auto/blackbox/testdata/pseudo-multiplexing/pseudo-multiplexing.qbs @@ -0,0 +1,12 @@ +Project { + Product { + name: "a" + multiplexByQbsProperties: ["architectures"] + Depends { name: "cpp" } + } + Product { + name: "b" + multiplexByQbsProperties: [] + Depends { name: "cpp" } + } +} diff --git a/tests/auto/blackbox/testdata/qbs-session/file1.cpp b/tests/auto/blackbox/testdata/qbs-session/file1.cpp new file mode 100644 index 00000000..1d6ea3b7 --- /dev/null +++ b/tests/auto/blackbox/testdata/qbs-session/file1.cpp @@ -0,0 +1 @@ +void f1() {} diff --git a/tests/auto/blackbox/testdata/qbs-session/file2.cpp b/tests/auto/blackbox/testdata/qbs-session/file2.cpp new file mode 100644 index 00000000..8ccc02b4 --- /dev/null +++ b/tests/auto/blackbox/testdata/qbs-session/file2.cpp @@ -0,0 +1 @@ +void f2() {} diff --git a/tests/auto/blackbox/testdata/qbs-session/lib.cpp b/tests/auto/blackbox/testdata/qbs-session/lib.cpp new file mode 100644 index 00000000..8101b05d --- /dev/null +++ b/tests/auto/blackbox/testdata/qbs-session/lib.cpp @@ -0,0 +1 @@ +void f() { } diff --git a/tests/auto/blackbox/testdata/qbs-session/lib.h b/tests/auto/blackbox/testdata/qbs-session/lib.h new file mode 100644 index 00000000..789447c0 --- /dev/null +++ b/tests/auto/blackbox/testdata/qbs-session/lib.h @@ -0,0 +1 @@ +void f(); diff --git a/tests/auto/blackbox/testdata/qbs-session/main.cpp b/tests/auto/blackbox/testdata/qbs-session/main.cpp new file mode 100644 index 00000000..654a5d65 --- /dev/null +++ b/tests/auto/blackbox/testdata/qbs-session/main.cpp @@ -0,0 +1,4 @@ +int main() +{ + int i; // Should trigger a warning and thus a process-exited message. +} diff --git a/tests/auto/blackbox/testdata/qbs-session/modules/mymodule/mymodule.qbs b/tests/auto/blackbox/testdata/qbs-session/modules/mymodule/mymodule.qbs new file mode 100644 index 00000000..ecf12b5a --- /dev/null +++ b/tests/auto/blackbox/testdata/qbs-session/modules/mymodule/mymodule.qbs @@ -0,0 +1,5 @@ +import qbs.Environment + +Module { + setupRunEnvironment: { Environment.putEnv("MY_MODULE", 1); } +} diff --git a/tests/auto/blackbox/testdata/qbs-session/qbs-session.qbs b/tests/auto/blackbox/testdata/qbs-session/qbs-session.qbs new file mode 100644 index 00000000..8496fb38 --- /dev/null +++ b/tests/auto/blackbox/testdata/qbs-session/qbs-session.qbs @@ -0,0 +1,25 @@ +Project { + StaticLibrary { + name: "theLib" + Depends { name: "cpp" } + cpp.cxxLanguageVersion: "c++11" + Group { + name: "sources" + files: "lib.cpp" + } + Group { + name: "headers" + files: "lib.h" + } + } + CppApplication { + name: "theApp" + consoleApplication: true + Depends { name: "mymodule" } + cpp.cxxLanguageVersion: "c++14" + cpp.warningLevel: "all" + files: "main.cpp" + install: true + } +} + diff --git a/tests/auto/blackbox/testdata/qbsVersion/qbs-version.qbs b/tests/auto/blackbox/testdata/qbsVersion/qbs-version.qbs new file mode 100644 index 00000000..ced0f3a3 --- /dev/null +++ b/tests/auto/blackbox/testdata/qbsVersion/qbs-version.qbs @@ -0,0 +1,20 @@ +Project { + property string qbsVersion + property int qbsVersionMajor + property int qbsVersionMinor + property int qbsVersionPatch + + Product { + property bool dummy: { + if (qbsVersion !== qbs.version || + qbsVersionMajor !== qbs.versionMajor || + qbsVersionMinor !== qbs.versionMinor || + qbsVersionPatch !== qbs.versionPatch) + throw("expected " + + [qbsVersion, qbsVersionMajor, qbsVersionMinor, qbsVersionPatch].join(", ") + + ", got " + + [qbs.version, qbs.versionMajor, qbs.versionMinor, qbs.versionPatch].join(", ")); + return false; + } + } +} diff --git a/tests/auto/blackbox/testdata/rad-after-incomplete-build/dummy.txt b/tests/auto/blackbox/testdata/rad-after-incomplete-build/dummy.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/rad-after-incomplete-build/project_with_rule.qbs b/tests/auto/blackbox/testdata/rad-after-incomplete-build/project_with_rule.qbs new file mode 100644 index 00000000..ca71ab93 --- /dev/null +++ b/tests/auto/blackbox/testdata/rad-after-incomplete-build/project_with_rule.qbs @@ -0,0 +1,25 @@ +import qbs.TextFile + +Product { + type: "custom" + Group { + files: "dummy.txt" + fileTags: "input" + } + Rule { + inputs: "input" + Artifact { + fileTags: "custom" + filePath: "oldfile" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating file"; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + } + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/recursive_renaming/dir/subdir/blubb.txt b/tests/auto/blackbox/testdata/recursive_renaming/dir/subdir/blubb.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/recursive_renaming/dir/wasser.txt b/tests/auto/blackbox/testdata/recursive_renaming/dir/wasser.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/recursive_renaming/recursive_renaming.qbs b/tests/auto/blackbox/testdata/recursive_renaming/recursive_renaming.qbs new file mode 100644 index 00000000..b49e9c53 --- /dev/null +++ b/tests/auto/blackbox/testdata/recursive_renaming/recursive_renaming.qbs @@ -0,0 +1,8 @@ +Product { + qbs.installPrefix: "" + Group { + qbs.install: true + qbs.installSourceBase: "." + files: ["dir/**"] + } +} diff --git a/tests/auto/blackbox/testdata/recursive_wildcards/dir/file1.txt b/tests/auto/blackbox/testdata/recursive_wildcards/dir/file1.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/recursive_wildcards/dir/subdir/file2.txt b/tests/auto/blackbox/testdata/recursive_wildcards/dir/subdir/file2.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/recursive_wildcards/recursive_wildcards.qbs b/tests/auto/blackbox/testdata/recursive_wildcards/recursive_wildcards.qbs new file mode 100644 index 00000000..4e9da01d --- /dev/null +++ b/tests/auto/blackbox/testdata/recursive_wildcards/recursive_wildcards.qbs @@ -0,0 +1,43 @@ +import qbs.TextFile + +Product { + type: ["txt.out"] + qbs.installPrefix: "" + Group { + files: "dir/**" + qbs.install: true + qbs.installDir: "dir" + } + FileTagger { + patterns: ["*.txt"] + fileTags: ["txt.in"] + } + Rule { + multiplex: true + requiresInputs: false + explicitlyDependsOn: ["txt.in"] + Artifact { + filePath: "output.txt" + fileTags: product.type + qbs.install: true + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating " + output.fileName; + cmd.sourceCode = function() { + var inputList = explicitlyDependsOn["txt.in"]; + var fileNameList = []; + for (var i = 0; i < inputList.length; ++i) + fileNameList.push(inputList[i].fileName); + fileNameList.sort(); + var f = new TextFile(output.filePath, TextFile.WriteOnly); + try { + f.write(fileNameList.join('')); + } finally { + f.close(); + } + }; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/referenceErrorInExport/main.c b/tests/auto/blackbox/testdata/referenceErrorInExport/main.c new file mode 100644 index 00000000..210c8274 --- /dev/null +++ b/tests/auto/blackbox/testdata/referenceErrorInExport/main.c @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/referenceErrorInExport/referenceErrorInExport.qbs b/tests/auto/blackbox/testdata/referenceErrorInExport/referenceErrorInExport.qbs new file mode 100644 index 00000000..d64244f0 --- /dev/null +++ b/tests/auto/blackbox/testdata/referenceErrorInExport/referenceErrorInExport.qbs @@ -0,0 +1,18 @@ +Project { + CppApplication { + Depends { name: "other" } + files: ["main.c"] + cpp.includePaths: ["."] + } + + DynamicLibrary { + name: "other" + files: ["main.c"] + + property stringList includePaths: [] + Export { + Depends { name: "cpp" } + cpp.includePaths: includePaths + } + } +} diff --git a/tests/auto/blackbox/testdata/remove-duplicate-libs/MyStaticLib.qbs b/tests/auto/blackbox/testdata/remove-duplicate-libs/MyStaticLib.qbs new file mode 100644 index 00000000..bd79759e --- /dev/null +++ b/tests/auto/blackbox/testdata/remove-duplicate-libs/MyStaticLib.qbs @@ -0,0 +1,10 @@ +StaticLibrary { + Properties { + condition: isForDarwin + bundle.isBundle: false + } + + Depends { name: "cpp" } + files: name + ".c" + destinationDirectory: project.libDir +} diff --git a/tests/auto/blackbox/testdata/remove-duplicate-libs/main.c b/tests/auto/blackbox/testdata/remove-duplicate-libs/main.c new file mode 100644 index 00000000..5b23b6f5 --- /dev/null +++ b/tests/auto/blackbox/testdata/remove-duplicate-libs/main.c @@ -0,0 +1,9 @@ +void requestor1(); +void requestor2(); + +int main(void) +{ + requestor1(); + requestor2(); + return 0; +} diff --git a/tests/auto/blackbox/testdata/remove-duplicate-libs/provider.c b/tests/auto/blackbox/testdata/remove-duplicate-libs/provider.c new file mode 100644 index 00000000..25a85319 --- /dev/null +++ b/tests/auto/blackbox/testdata/remove-duplicate-libs/provider.c @@ -0,0 +1 @@ +void provider1() {} diff --git a/tests/auto/blackbox/testdata/remove-duplicate-libs/provider2.c b/tests/auto/blackbox/testdata/remove-duplicate-libs/provider2.c new file mode 100644 index 00000000..d7cdce83 --- /dev/null +++ b/tests/auto/blackbox/testdata/remove-duplicate-libs/provider2.c @@ -0,0 +1 @@ +void provider2() {} diff --git a/tests/auto/blackbox/testdata/remove-duplicate-libs/remove-duplicate-libs.qbs b/tests/auto/blackbox/testdata/remove-duplicate-libs/remove-duplicate-libs.qbs new file mode 100644 index 00000000..4ffb8d0e --- /dev/null +++ b/tests/auto/blackbox/testdata/remove-duplicate-libs/remove-duplicate-libs.qbs @@ -0,0 +1,26 @@ +import "MyStaticLib.qbs" as MyStaticLib + +Project { + property bool removeDuplicates + property string libDir: buildDirectory + "/lib" + property bool dummy: { + console.info("is bfd linker: " + + (qbs.toolchain.contains("gcc") && !qbs.hostOS.contains("macos"))) + } + + qbsSearchPaths: "." + MyStaticLib { name: "requestor1" } + MyStaticLib { name: "requestor2" } + MyStaticLib { name: "provider"; Group { files: "provider2.c" } } + + CppApplication { + consoleApplication: true + Depends { name: "requestor1"; cpp.link: false } + Depends { name: "requestor2"; cpp.link: false } + Depends { name: "provider"; cpp.link: false } + cpp.libraryPaths: project.libDir + cpp.removeDuplicateLibraries: project.removeDuplicates + cpp.staticLibraries: ["requestor1", "requestor2", "provider", "requestor2"] + files: "main.c" + } +} diff --git a/tests/auto/blackbox/testdata/remove-duplicate-libs/requestor1.c b/tests/auto/blackbox/testdata/remove-duplicate-libs/requestor1.c new file mode 100644 index 00000000..6d6b1a2d --- /dev/null +++ b/tests/auto/blackbox/testdata/remove-duplicate-libs/requestor1.c @@ -0,0 +1,3 @@ +void provider1(); + +void requestor1() { provider1(); } diff --git a/tests/auto/blackbox/testdata/remove-duplicate-libs/requestor2.c b/tests/auto/blackbox/testdata/remove-duplicate-libs/requestor2.c new file mode 100644 index 00000000..fa50d1d6 --- /dev/null +++ b/tests/auto/blackbox/testdata/remove-duplicate-libs/requestor2.c @@ -0,0 +1,3 @@ +void provider2(); + +void requestor2() { provider2(); } diff --git a/tests/auto/blackbox/testdata/renameDependency/after/lib2.cpp b/tests/auto/blackbox/testdata/renameDependency/after/lib2.cpp new file mode 100644 index 00000000..7ecd6a10 --- /dev/null +++ b/tests/auto/blackbox/testdata/renameDependency/after/lib2.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 "lib2.h" +#include + +void print_two_numbers(int a, int b, int c) +{ + std::cout << "a=" << a << ", b=" << b << ", c=" << c << std::endl; +} diff --git a/tests/auto/blackbox/testdata/renameDependency/after/lib2.h b/tests/auto/blackbox/testdata/renameDependency/after/lib2.h new file mode 100644 index 00000000..fdd6bb87 --- /dev/null +++ b/tests/auto/blackbox/testdata/renameDependency/after/lib2.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void print_two_numbers(int a, int b, int c); diff --git a/tests/auto/blackbox/testdata/renameDependency/before/lib.cpp b/tests/auto/blackbox/testdata/renameDependency/before/lib.cpp new file mode 100644 index 00000000..6958518a --- /dev/null +++ b/tests/auto/blackbox/testdata/renameDependency/before/lib.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 "lib.h" +#include + +void print_two_numbers(int a, int b/*, int c*/) +{ + std::cout << "a=" << a << ", b=" << b /*<< ", c=" << c */ << std::endl; +} diff --git a/tests/auto/blackbox/testdata/renameDependency/before/lib.h b/tests/auto/blackbox/testdata/renameDependency/before/lib.h new file mode 100644 index 00000000..17a0b170 --- /dev/null +++ b/tests/auto/blackbox/testdata/renameDependency/before/lib.h @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void print_two_numbers(int a, int b/*, int c*/); diff --git a/tests/auto/blackbox/testdata/renameDependency/before/main.cpp b/tests/auto/blackbox/testdata/renameDependency/before/main.cpp new file mode 100644 index 00000000..7f1bbe16 --- /dev/null +++ b/tests/auto/blackbox/testdata/renameDependency/before/main.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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 +#include "lib.h" + +int main() +{ + print_two_numbers(2, 3); + return 0; +} diff --git a/tests/auto/blackbox/testdata/renameDependency/before/renameDependency.qbs b/tests/auto/blackbox/testdata/renameDependency/before/renameDependency.qbs new file mode 100644 index 00000000..d42a760d --- /dev/null +++ b/tests/auto/blackbox/testdata/renameDependency/before/renameDependency.qbs @@ -0,0 +1,3 @@ +CppApplication { + files: ["*.cpp", "*.h"] +} diff --git a/tests/auto/blackbox/testdata/reproducible-build/file1.cpp b/tests/auto/blackbox/testdata/reproducible-build/file1.cpp new file mode 100644 index 00000000..ce54c007 --- /dev/null +++ b/tests/auto/blackbox/testdata/reproducible-build/file1.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +static void f() { } + +void f1() { f(); } diff --git a/tests/auto/blackbox/testdata/reproducible-build/file2.cpp b/tests/auto/blackbox/testdata/reproducible-build/file2.cpp new file mode 100644 index 00000000..8c095814 --- /dev/null +++ b/tests/auto/blackbox/testdata/reproducible-build/file2.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +static void f() { } + +void f2() { f(); } diff --git a/tests/auto/blackbox/testdata/reproducible-build/main.cpp b/tests/auto/blackbox/testdata/reproducible-build/main.cpp new file mode 100644 index 00000000..6390016a --- /dev/null +++ b/tests/auto/blackbox/testdata/reproducible-build/main.cpp @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void f1(); +void f2(); + +int main() +{ + f1(); + f2(); +} diff --git a/tests/auto/blackbox/testdata/reproducible-build/reproducible-build.qbs b/tests/auto/blackbox/testdata/reproducible-build/reproducible-build.qbs new file mode 100644 index 00000000..f7ed8e61 --- /dev/null +++ b/tests/auto/blackbox/testdata/reproducible-build/reproducible-build.qbs @@ -0,0 +1,5 @@ +CppApplication { + name: "the product" + files: ["file1.cpp", "file2.cpp", "main.cpp"] + cpp.cxxFlags: ["-flto"] +} diff --git a/tests/auto/blackbox/testdata/require-deprecated/blubb.js b/tests/auto/blackbox/testdata/require-deprecated/blubb.js new file mode 100644 index 00000000..9acc1396 --- /dev/null +++ b/tests/auto/blackbox/testdata/require-deprecated/blubb.js @@ -0,0 +1,13 @@ +var TextFile = loadExtension("qbs.TextFile") +var zort = loadFile("zort.js") + +function createCommands(filePaths) { + var cmd = new JavaScriptCommand(); + cmd.description = "Write an empty file"; + cmd.filePath = filePaths[0]; + cmd.sourceCode = function() { + var f = new TextFile(filePath, TextFile.WriteOnly); + f.close(); + } + return [cmd, zort.createCommand(filePaths)]; +} diff --git a/tests/auto/blackbox/testdata/require-deprecated/require.qbs b/tests/auto/blackbox/testdata/require-deprecated/require.qbs new file mode 100644 index 00000000..87d8b054 --- /dev/null +++ b/tests/auto/blackbox/testdata/require-deprecated/require.qbs @@ -0,0 +1,21 @@ +import 'blubb.js' as blubb + +Product { + type: ["text"] + Rule { + multiplex: true + Artifact { + fileTags: ["text"] + filePath: "one.txt" + } + Artifact { + fileTags: ["text"] + filePath: "two.txt" + } + prepare: { + var filePaths = outputs.text.map(function (artifact) {return artifact.filePath; }); + return blubb.createCommands(filePaths); + } + } +} + diff --git a/tests/auto/blackbox/testdata/require-deprecated/zort.js b/tests/auto/blackbox/testdata/require-deprecated/zort.js new file mode 100644 index 00000000..0dcffb76 --- /dev/null +++ b/tests/auto/blackbox/testdata/require-deprecated/zort.js @@ -0,0 +1,11 @@ +var File = loadExtension("qbs.File") + +function createCommand(filePaths) { + var cmd = new JavaScriptCommand(); + cmd.description = "Create another empty file"; + cmd.filePaths = filePaths; + cmd.sourceCode = function() { + File.copy(filePaths[0], filePaths[1]); + }; + return cmd; +} diff --git a/tests/auto/blackbox/testdata/require/blubb.js b/tests/auto/blackbox/testdata/require/blubb.js new file mode 100644 index 00000000..8b9811ca --- /dev/null +++ b/tests/auto/blackbox/testdata/require/blubb.js @@ -0,0 +1,13 @@ +var TextFile = require("qbs.TextFile") +var zort = require("./zort.js") + +function createCommands(filePaths) { + var cmd = new JavaScriptCommand(); + cmd.description = "Write an empty file"; + cmd.filePath = filePaths[0]; + cmd.sourceCode = function() { + var f = new TextFile(filePath, TextFile.WriteOnly); + f.close(); + } + return [cmd, zort.createCommand(filePaths)]; +} diff --git a/tests/auto/blackbox/testdata/require/require.qbs b/tests/auto/blackbox/testdata/require/require.qbs new file mode 100644 index 00000000..87d8b054 --- /dev/null +++ b/tests/auto/blackbox/testdata/require/require.qbs @@ -0,0 +1,21 @@ +import 'blubb.js' as blubb + +Product { + type: ["text"] + Rule { + multiplex: true + Artifact { + fileTags: ["text"] + filePath: "one.txt" + } + Artifact { + fileTags: ["text"] + filePath: "two.txt" + } + prepare: { + var filePaths = outputs.text.map(function (artifact) {return artifact.filePath; }); + return blubb.createCommands(filePaths); + } + } +} + diff --git a/tests/auto/blackbox/testdata/require/zort.js b/tests/auto/blackbox/testdata/require/zort.js new file mode 100644 index 00000000..5dbcd970 --- /dev/null +++ b/tests/auto/blackbox/testdata/require/zort.js @@ -0,0 +1,11 @@ +var File = require("qbs.File") + +function createCommand(filePaths) { + var cmd = new JavaScriptCommand(); + cmd.description = "Create another empty file"; + cmd.filePaths = filePaths; + cmd.sourceCode = function() { + File.copy(filePaths[0], filePaths[1]); + }; + return cmd; +} diff --git a/tests/auto/blackbox/testdata/rescue-transformer-data/main.cpp b/tests/auto/blackbox/testdata/rescue-transformer-data/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/rescue-transformer-data/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/rescue-transformer-data/modules/m/m.qbs b/tests/auto/blackbox/testdata/rescue-transformer-data/modules/m/m.qbs new file mode 100644 index 00000000..640d2c3b --- /dev/null +++ b/tests/auto/blackbox/testdata/rescue-transformer-data/modules/m/m.qbs @@ -0,0 +1,19 @@ +Module { + property bool p + + Rule { + multiplex: true + Artifact { + filePath: "dummy" + fileTags: ["out"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating dummy"; + cmd.sourceCode = function() { + console.info("m.p: " + product.m.p); + }; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/rescue-transformer-data/transformer-data-rescue.qbs b/tests/auto/blackbox/testdata/rescue-transformer-data/transformer-data-rescue.qbs new file mode 100644 index 00000000..4d266bbd --- /dev/null +++ b/tests/auto/blackbox/testdata/rescue-transformer-data/transformer-data-rescue.qbs @@ -0,0 +1,5 @@ +CppApplication { + type: base.concat(["out"]) + Depends { name: "m" } + files: ["main.cpp"] +} diff --git a/tests/auto/blackbox/testdata/response-files/cat-response-file.cpp b/tests/auto/blackbox/testdata/response-files/cat-response-file.cpp new file mode 100644 index 00000000..c9839681 --- /dev/null +++ b/tests/auto/blackbox/testdata/response-files/cat-response-file.cpp @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include + +using namespace std; + +int main(int argc, char *argv[]) +{ + if (argc < 3) { + cerr << "cat-response-file: not enough arguments: " << argc - 1 << endl; + return 1; + } + if (strlen(argv[2]) < 2) { + cerr << "cat-response-file: second argument is too short: " << argv[2] << endl; + return 2; + } + if (argv[2][0] != '@') { + cerr << "cat-response-file: second argument does not start with @: " << argv[2] << endl; + return 3; + } + ifstream inf(argv[2] + 1); + if (!inf.is_open()) { + cerr << "cat-response-file: cannot open input file " << argv[2] + 1 << endl; + return 4; + } + ofstream ouf(argv[1]); + if (!ouf.is_open()) { + cerr << "cat-response-file: cannot open output file " << argv[1] << endl; + return 5; + } + string line; + while (getline(inf, line)) + ouf << line << endl; + inf.close(); + ouf.close(); + return 0; +} + diff --git a/tests/auto/blackbox/testdata/response-files/response-files.qbs b/tests/auto/blackbox/testdata/response-files/response-files.qbs new file mode 100644 index 00000000..168cdf66 --- /dev/null +++ b/tests/auto/blackbox/testdata/response-files/response-files.qbs @@ -0,0 +1,82 @@ +import qbs.FileInfo +import qbs.TextFile + +Project { + CppApplication { + name: "cat-response-file" + files: ["cat-response-file.cpp"] + cpp.enableExceptions: true + } + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: "response-file-text" + type: ["text"] + Depends { name: "cpp" } + Depends { name: "cat-response-file" } + qbs.installPrefix: "" + Group { + fileTagsFilter: ["text"] + qbs.install: true + } + Rule { + inputsFromDependencies: ["application"] + Artifact { + filePath: "response-file-content.txt" + fileTags: ["text"] + } + prepare: { + var filePath = inputs["application"][0].filePath; + var args = [output.filePath, "foo", "with space", "bar"]; + var cmd = new Command(filePath, args); + cmd.responseFileThreshold = 1; + cmd.responseFileArgumentIndex = 1; + cmd.responseFileUsagePrefix = '@'; + cmd.silent = true; + return cmd; + } + } + } + Product { + name: "lotsofobjects" + type: ["dynamiclibrary"] + // clang-cl does not use response file internally, thus linker complains that command is + // too long. This can be worked around by calling the linker directly + cpp.linkerMode: qbs.toolchain.contains("clang-cl") ? "manual" : original + Depends { name: "cpp" } + Rule { + multiplex: true + outputFileTags: ["cpp"] + outputArtifacts: { + var artifacts = []; + for (var i = 0; i < 1000; ++i) + artifacts.push({filePath: "source-" + i + ".cpp", fileTags: ["cpp"]}); + return artifacts; + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + outputs["cpp"].length + " dummy source files"; + cmd.outputFilePaths = outputs["cpp"].map(function (a) { + return a.filePath; + }); + cmd.sourceCode = function () { + var index = 0; + outputFilePaths.map(function (fp) { + var tf = new TextFile(fp, TextFile.WriteOnly); + try { + tf.writeLine("extern int foo" + index + "() { return 0; }"); + ++index; + } finally { + tf.close(); + } + }); + }; + return [cmd]; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/retagged-output-artifact/retagged-output-artifact.qbs b/tests/auto/blackbox/testdata/retagged-output-artifact/retagged-output-artifact.qbs new file mode 100644 index 00000000..8af84bcd --- /dev/null +++ b/tests/auto/blackbox/testdata/retagged-output-artifact/retagged-output-artifact.qbs @@ -0,0 +1,42 @@ +import qbs.File +import qbs.TextFile + +Product { + name: "p" + type: "p_type" + property bool useTag1 + Rule { + multiplex: true + outputFileTags: ["tag1", "tag2"] + outputArtifacts: [{filePath: "a1.txt", fileTags: product.useTag1 ? "tag1" : "tag2"}] + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.filePath; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + }; + return cmd; + } + } + Rule { + inputs: "tag1" + Artifact { filePath: "a2.txt"; fileTags: "p_type" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.filePath; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); }; + return cmd; + } + } + Rule { + inputs: "tag2" + Artifact { filePath: "a3.txt"; fileTags: "p_type" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.filePath; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/rule-connection-with-excluded-inputs/rule-connection-with-excluded-inputs.qbs b/tests/auto/blackbox/testdata/rule-connection-with-excluded-inputs/rule-connection-with-excluded-inputs.qbs new file mode 100644 index 00000000..9d6482f9 --- /dev/null +++ b/tests/auto/blackbox/testdata/rule-connection-with-excluded-inputs/rule-connection-with-excluded-inputs.qbs @@ -0,0 +1,40 @@ +Product { + name: "p" + type: "p_type" + Rule { + multiplex: true + Artifact { filePath: "x.txt"; fileTags: "x" } + Artifact { filePath: "y.txt"; fileTags: "y" } + Artifact { filePath: "p.txt"; fileTags: "p_type" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() {}; + return cmd; + } + } + Rule { + multiplex: true + Artifact { filePath: "x2.txt"; fileTags: "x" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() {}; + return cmd; + } + } + Rule { + multiplex: true + inputs: "x" + excludedInputs: "y" + Artifact { filePath: "dummy"; fileTags: "p_type" } + prepare: { + console.info("inputs.x: " + (inputs.x ? inputs.x.length : 0)); + console.info("inputs.y: " + (inputs.y ? inputs.y.length : 0)); + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() {}; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/rule-with-no-inputs/rule-with-no-inputs.qbs b/tests/auto/blackbox/testdata/rule-with-no-inputs/rule-with-no-inputs.qbs new file mode 100644 index 00000000..9c0f0121 --- /dev/null +++ b/tests/auto/blackbox/testdata/rule-with-no-inputs/rule-with-no-inputs.qbs @@ -0,0 +1,27 @@ +import qbs.TextFile + +Product { + name: "theProduct" + type: ["output"] + version: "1" + property bool dummy: false + Rule { + inputs: [] + multiplex: true + Artifact { + filePath: "output.out" + fileTags: ["output"] + } + prepare: { + console.info("running the rule: " + product.dummy); + var cmd = new JavaScriptCommand(); + cmd.description = "creating output"; + cmd.sourceCode = function() { + console.info(product.version); + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + } + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/rule-with-non-required-inputs/a.inp b/tests/auto/blackbox/testdata/rule-with-non-required-inputs/a.inp new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/rule-with-non-required-inputs/b.inp b/tests/auto/blackbox/testdata/rule-with-non-required-inputs/b.inp new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/rule-with-non-required-inputs/c.inp b/tests/auto/blackbox/testdata/rule-with-non-required-inputs/c.inp new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/rule-with-non-required-inputs/rule-with-non-required-inputs.qbs b/tests/auto/blackbox/testdata/rule-with-non-required-inputs/rule-with-non-required-inputs.qbs new file mode 100644 index 00000000..1bd9beeb --- /dev/null +++ b/tests/auto/blackbox/testdata/rule-with-non-required-inputs/rule-with-non-required-inputs.qbs @@ -0,0 +1,41 @@ +import qbs.TextFile + +Product { + name: "p" + type: ["p.out"] + + property bool enableTagger + + FileTagger { + condition: enableTagger + patterns: ["*.inp"] + fileTags: ["p.in"] + } + + Rule { + multiplex: true + requiresInputs: false + inputs: ["p.in"] + Artifact { + filePath: "output.txt" + fileTags: ["p.out"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Generating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.write('('); + var inputsList = inputs["p.in"]; + if (inputsList) { + for (var i = 0; i < inputsList.length; ++i) + f.write(inputsList[i].fileName + ','); + } + f.write(')'); + }; + return [cmd]; + } + } + + files: ["a.inp", "b.inp", "c.inp"] +} diff --git a/tests/auto/blackbox/testdata/ruleConditions/foo.narf b/tests/auto/blackbox/testdata/ruleConditions/foo.narf new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/ruleConditions/main.cpp b/tests/auto/blackbox/testdata/ruleConditions/main.cpp new file mode 100644 index 00000000..210c8274 --- /dev/null +++ b/tests/auto/blackbox/testdata/ruleConditions/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/ruleConditions/modules/narfzort/narfzort.qbs b/tests/auto/blackbox/testdata/ruleConditions/modules/narfzort/narfzort.qbs new file mode 100644 index 00000000..0044537c --- /dev/null +++ b/tests/auto/blackbox/testdata/ruleConditions/modules/narfzort/narfzort.qbs @@ -0,0 +1,29 @@ +import qbs.FileInfo +import qbs.TextFile + +Module { + property bool buildZort: true + FileTagger { + patterns: "*.narf" + fileTags: ["narf"] + } + Rule { + condition: product.narfzort.buildZort + inputs: ["narf"] + outputFileTags: ["zort"] + outputArtifacts: [{ + filePath: product.name + "." + input.fileName + ".zort", + fileTags: ["zort"] + }] + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + FileInfo.fileName(output.filePath); + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.write("NARF! ZORT!"); + f.close(); + } + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/ruleConditions/ruleConditions.qbs b/tests/auto/blackbox/testdata/ruleConditions/ruleConditions.qbs new file mode 100644 index 00000000..a8f7ffad --- /dev/null +++ b/tests/auto/blackbox/testdata/ruleConditions/ruleConditions.qbs @@ -0,0 +1,11 @@ +import "templates/zorduct.qbs" as Zorduct + +Project { + Zorduct { + narfzort.buildZort: false + name: "unzorted" + } + Zorduct { + name: "zorted" + } +} diff --git a/tests/auto/blackbox/testdata/ruleConditions/templates/zorduct.qbs b/tests/auto/blackbox/testdata/ruleConditions/templates/zorduct.qbs new file mode 100644 index 00000000..cddad8c3 --- /dev/null +++ b/tests/auto/blackbox/testdata/ruleConditions/templates/zorduct.qbs @@ -0,0 +1,10 @@ +Product { + type: ["application", "zort"] + consoleApplication: true + Depends { name: "cpp" } + Depends { name: "narfzort" } + files: [ + "main.cpp", + "foo.narf" + ] +} diff --git a/tests/auto/blackbox/testdata/ruleCycle/happy.grass b/tests/auto/blackbox/testdata/ruleCycle/happy.grass new file mode 100644 index 00000000..8cf7f02e --- /dev/null +++ b/tests/auto/blackbox/testdata/ruleCycle/happy.grass @@ -0,0 +1 @@ +happy! happy! joy! joy! diff --git a/tests/auto/blackbox/testdata/ruleCycle/ruleCycle.qbs b/tests/auto/blackbox/testdata/ruleCycle/ruleCycle.qbs new file mode 100644 index 00000000..1c5d61d9 --- /dev/null +++ b/tests/auto/blackbox/testdata/ruleCycle/ruleCycle.qbs @@ -0,0 +1,45 @@ +Project { + Product { + name: "the cycle of life" + type: "cow" + Group { + files: ["happy.grass"] + fileTags: ["grass"] + } + Rule { + inputs: ["grass"] + outputFileTags: ["cow"] + outputArtifacts: [{ + filePath: input.completeBaseName + ".cow", + fileTags: ["cow"] + }] + prepare: { console.info("The cow feeds on grass."); } + } + Rule { + inputs: ["cow"] + Artifact { + filePath: input.completeBaseName + ".cow_pat" + fileTags: ["cow_pat"] + } + prepare: { console.info("The cow pat falls out of the cow."); } + } + Rule { + inputs: ["cow_pat"] + Artifact { + filePath: input.completeBaseName + ".fertilizer" + fileTags: ["fertilizer"] + } + prepare: { console.info("The cow pat is used as fertilizer."); } + } + Rule { + inputs: ["fertilizer"] + outputFileTags: ["grass"] + outputArtifacts: [{ + filePath: input.completeBaseName + ".grass", + fileTags: ["grass"] + }] + prepare: { console.info("The fertilizer lets the grass grow."); } + } + } +} + diff --git a/tests/auto/blackbox/testdata/sanitizer/sanitizer.cpp b/tests/auto/blackbox/testdata/sanitizer/sanitizer.cpp new file mode 100644 index 00000000..4a7c3ee3 --- /dev/null +++ b/tests/auto/blackbox/testdata/sanitizer/sanitizer.cpp @@ -0,0 +1,4 @@ +int main(int argc, char *argv[]) +{ + return 0; +} diff --git a/tests/auto/blackbox/testdata/sanitizer/sanitizer.qbs b/tests/auto/blackbox/testdata/sanitizer/sanitizer.qbs new file mode 100644 index 00000000..e5db199d --- /dev/null +++ b/tests/auto/blackbox/testdata/sanitizer/sanitizer.qbs @@ -0,0 +1,34 @@ +CppApplication { + property string sanitizer + + property bool supportsSanitizer: { + if (qbs.toolchain.contains("clang-cl")) + // only these are supported + return sanitizer === "address" || sanitizer === "undefined"; + if (!qbs.toolchain.contains("gcc")) + return false; + if (qbs.toolchain.contains("mingw")) + return false; + if (qbs.targetOS.contains("ios")) { + // thread sanitizer is not supported + return sanitizer !== "thread"; + } + return true; + } + + condition: { + if (!sanitizer) + return true; + if (!supportsSanitizer) + console.info("Compiler does not support sanitizer"); + return supportsSanitizer; + } + qbs.buildVariant: "release" + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.8" + consoleApplication: true + cpp.runtimeLibrary: "static" + cpp.driverFlags: sanitizer ? ["-fsanitize=" + sanitizer] : [] + cpp.debugInformation: true + files: "sanitizer.cpp" +} diff --git a/tests/auto/blackbox/testdata/scan-result-in-non-dependency/app/app.h b/tests/auto/blackbox/testdata/scan-result-in-non-dependency/app/app.h new file mode 100644 index 00000000..a82b12fb --- /dev/null +++ b/tests/auto/blackbox/testdata/scan-result-in-non-dependency/app/app.h @@ -0,0 +1 @@ +#include "lib.h" diff --git a/tests/auto/blackbox/testdata/scan-result-in-non-dependency/app/app.qbs b/tests/auto/blackbox/testdata/scan-result-in-non-dependency/app/app.qbs new file mode 100644 index 00000000..e931b853 --- /dev/null +++ b/tests/auto/blackbox/testdata/scan-result-in-non-dependency/app/app.qbs @@ -0,0 +1,4 @@ +CppApplication { + cpp.includePaths: project.sourceDirectory + "/lib" + files: "main.cpp" +} diff --git a/tests/auto/blackbox/testdata/scan-result-in-non-dependency/app/main.cpp b/tests/auto/blackbox/testdata/scan-result-in-non-dependency/app/main.cpp new file mode 100644 index 00000000..2e7bedac --- /dev/null +++ b/tests/auto/blackbox/testdata/scan-result-in-non-dependency/app/main.cpp @@ -0,0 +1,3 @@ +#include "app.h" + +int main() { } diff --git a/tests/auto/blackbox/testdata/scan-result-in-non-dependency/lib/lib.h b/tests/auto/blackbox/testdata/scan-result-in-non-dependency/lib/lib.h new file mode 100644 index 00000000..af6f627b --- /dev/null +++ b/tests/auto/blackbox/testdata/scan-result-in-non-dependency/lib/lib.h @@ -0,0 +1,3 @@ +#pragma once + +void lib1_foo(); \ No newline at end of file diff --git a/tests/auto/blackbox/testdata/scan-result-in-non-dependency/other/other.qbs b/tests/auto/blackbox/testdata/scan-result-in-non-dependency/other/other.qbs new file mode 100644 index 00000000..29682da1 --- /dev/null +++ b/tests/auto/blackbox/testdata/scan-result-in-non-dependency/other/other.qbs @@ -0,0 +1,24 @@ +import qbs.TextFile + +Product { + type: "testproduct" + files: "../lib/lib.h" + + Rule { + multiplex: true + Artifact { + fileTags: ["testproduct"] + filePath: "fubar" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating text file"; + cmd.sourceCode = function() { + var tf = new TextFile(output.filePath, TextFile.WriteOnly); + tf.writeLine("blubb"); + tf.close(); + } + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/scan-result-in-non-dependency/p.qbs b/tests/auto/blackbox/testdata/scan-result-in-non-dependency/p.qbs new file mode 100644 index 00000000..bcbd5ebc --- /dev/null +++ b/tests/auto/blackbox/testdata/scan-result-in-non-dependency/p.qbs @@ -0,0 +1,6 @@ +Project { + references: [ + "app/app.qbs", + "other/other.qbs", + ] +} diff --git a/tests/auto/blackbox/testdata/scan-result-in-other-product/app/app.h b/tests/auto/blackbox/testdata/scan-result-in-other-product/app/app.h new file mode 100644 index 00000000..a82b12fb --- /dev/null +++ b/tests/auto/blackbox/testdata/scan-result-in-other-product/app/app.h @@ -0,0 +1 @@ +#include "lib.h" diff --git a/tests/auto/blackbox/testdata/scan-result-in-other-product/app/app.qbs b/tests/auto/blackbox/testdata/scan-result-in-other-product/app/app.qbs new file mode 100644 index 00000000..984e9aca --- /dev/null +++ b/tests/auto/blackbox/testdata/scan-result-in-other-product/app/app.qbs @@ -0,0 +1,4 @@ +CppApplication { + Depends { name: "lib" } + files: "main.cpp" +} diff --git a/tests/auto/blackbox/testdata/scan-result-in-other-product/app/main.cpp b/tests/auto/blackbox/testdata/scan-result-in-other-product/app/main.cpp new file mode 100644 index 00000000..2e7bedac --- /dev/null +++ b/tests/auto/blackbox/testdata/scan-result-in-other-product/app/main.cpp @@ -0,0 +1,3 @@ +#include "app.h" + +int main() { } diff --git a/tests/auto/blackbox/testdata/scan-result-in-other-product/lib/lib.h b/tests/auto/blackbox/testdata/scan-result-in-other-product/lib/lib.h new file mode 100644 index 00000000..af6f627b --- /dev/null +++ b/tests/auto/blackbox/testdata/scan-result-in-other-product/lib/lib.h @@ -0,0 +1,3 @@ +#pragma once + +void lib1_foo(); \ No newline at end of file diff --git a/tests/auto/blackbox/testdata/scan-result-in-other-product/lib/lib.qbs b/tests/auto/blackbox/testdata/scan-result-in-other-product/lib/lib.qbs new file mode 100644 index 00000000..fe291671 --- /dev/null +++ b/tests/auto/blackbox/testdata/scan-result-in-other-product/lib/lib.qbs @@ -0,0 +1,7 @@ +Product { + files: "lib.h" + Export { + Depends { name: "cpp" } + cpp.includePaths: product.sourceDirectory + } +} diff --git a/tests/auto/blackbox/testdata/scan-result-in-other-product/other/other.qbs b/tests/auto/blackbox/testdata/scan-result-in-other-product/other/other.qbs new file mode 100644 index 00000000..29682da1 --- /dev/null +++ b/tests/auto/blackbox/testdata/scan-result-in-other-product/other/other.qbs @@ -0,0 +1,24 @@ +import qbs.TextFile + +Product { + type: "testproduct" + files: "../lib/lib.h" + + Rule { + multiplex: true + Artifact { + fileTags: ["testproduct"] + filePath: "fubar" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating text file"; + cmd.sourceCode = function() { + var tf = new TextFile(output.filePath, TextFile.WriteOnly); + tf.writeLine("blubb"); + tf.close(); + } + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/scan-result-in-other-product/p.qbs b/tests/auto/blackbox/testdata/scan-result-in-other-product/p.qbs new file mode 100644 index 00000000..fedf8498 --- /dev/null +++ b/tests/auto/blackbox/testdata/scan-result-in-other-product/p.qbs @@ -0,0 +1,7 @@ +Project { + references: [ + "app/app.qbs", + "lib/lib.qbs", + "other/other.qbs", + ] +} diff --git a/tests/auto/blackbox/testdata/scanner-item/modules/m/m.qbs b/tests/auto/blackbox/testdata/scanner-item/modules/m/m.qbs new file mode 100644 index 00000000..9af4cc53 --- /dev/null +++ b/tests/auto/blackbox/testdata/scanner-item/modules/m/m.qbs @@ -0,0 +1,9 @@ +import qbs.FileInfo + +Module { + Scanner { + inputs: "i" + searchPaths: [FileInfo.path(input.filePath)] + scan: ["file.inc"] + } +} diff --git a/tests/auto/blackbox/testdata/scanner-item/scanner-item.qbs b/tests/auto/blackbox/testdata/scanner-item/scanner-item.qbs new file mode 100644 index 00000000..f389651d --- /dev/null +++ b/tests/auto/blackbox/testdata/scanner-item/scanner-item.qbs @@ -0,0 +1,24 @@ +import qbs.File +import qbs.FileInfo + +Product { + type: "t" + Depends { name: "m" } + Group { + files: ["subdir1/file1.in", "subdir2/file2.in"] + fileTags: "i" + } + Rule { + inputs: "i" + Artifact { + filePath: FileInfo.baseName(input.fileName) + ".out" + fileTags: "t" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "handling " + input.fileName; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/scanner-item/subdir1/file.inc b/tests/auto/blackbox/testdata/scanner-item/subdir1/file.inc new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/scanner-item/subdir1/file1.in b/tests/auto/blackbox/testdata/scanner-item/subdir1/file1.in new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/scanner-item/subdir2/file.inc b/tests/auto/blackbox/testdata/scanner-item/subdir2/file.inc new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/scanner-item/subdir2/file2.in b/tests/auto/blackbox/testdata/scanner-item/subdir2/file2.in new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/separate-debug-info/foo.cpp b/tests/auto/blackbox/testdata/separate-debug-info/foo.cpp new file mode 100644 index 00000000..7e734cdb --- /dev/null +++ b/tests/auto/blackbox/testdata/separate-debug-info/foo.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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 "../dllexport.h" + +DLL_EXPORT int getAnswer() { return 42; } diff --git a/tests/auto/blackbox/testdata/separate-debug-info/main.cpp b/tests/auto/blackbox/testdata/separate-debug-info/main.cpp new file mode 100644 index 00000000..210c8274 --- /dev/null +++ b/tests/auto/blackbox/testdata/separate-debug-info/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/separate-debug-info/separate-debug-info.qbs b/tests/auto/blackbox/testdata/separate-debug-info/separate-debug-info.qbs new file mode 100644 index 00000000..48e70f26 --- /dev/null +++ b/tests/auto/blackbox/testdata/separate-debug-info/separate-debug-info.qbs @@ -0,0 +1,156 @@ +Project { + CppApplication { + name: "app1" + type: ["application"] + files: ["main.cpp"] + cpp.separateDebugInformation: true + + Probe { + id: osProbe + property stringList targetOS: qbs.targetOS + configure: { + console.info("is windows: " + (targetOS.contains("windows") ? "yes" : "no")); + console.info("is macos: " + (targetOS.contains("macos") ? "yes" : "no")); + console.info("is darwin: " + (targetOS.contains("darwin") ? "yes" : "no")); + } + } + } + DynamicLibrary { + Depends { name: "cpp" } + name: "foo1" + type: ["dynamiclibrary"] + files: ["foo.cpp"] + cpp.separateDebugInformation: true + } + LoadableModule { + Depends { name: "cpp" } + name: "bar1" + files: ["foo.cpp"] + cpp.separateDebugInformation: true + } + + CppApplication { + name: "app2" + type: ["application"] + files: ["main.cpp"] + cpp.separateDebugInformation: false + } + DynamicLibrary { + Depends { name: "cpp" } + name: "foo2" + type: ["dynamiclibrary"] + files: ["foo.cpp"] + cpp.separateDebugInformation: false + } + LoadableModule { + Depends { name: "cpp" } + name: "bar2" + files: ["foo.cpp"] + cpp.separateDebugInformation: false + } + + CppApplication { + name: "app3" + type: ["application"] + files: ["main.cpp"] + cpp.separateDebugInformation: true + Properties { + condition: qbs.targetOS.contains("darwin") + cpp.dsymutilFlags: ["--flat"] + } + } + DynamicLibrary { + Depends { name: "cpp" } + name: "foo3" + type: ["dynamiclibrary"] + files: ["foo.cpp"] + cpp.separateDebugInformation: true + Properties { + condition: qbs.targetOS.contains("darwin") + cpp.dsymutilFlags: ["--flat"] + } + } + LoadableModule { + Depends { name: "cpp" } + name: "bar3" + files: ["foo.cpp"] + cpp.separateDebugInformation: true + Properties { + condition: qbs.targetOS.contains("darwin") + cpp.dsymutilFlags: ["--flat"] + } + } + + CppApplication { + name: "app4" + type: ["application"] + files: ["main.cpp"] + consoleApplication: true + cpp.separateDebugInformation: true + } + DynamicLibrary { + Depends { name: "cpp" } + name: "foo4" + type: ["dynamiclibrary"] + files: ["foo.cpp"] + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + cpp.separateDebugInformation: true + } + LoadableModule { + Depends { name: "cpp" } + name: "bar4" + files: ["foo.cpp"] + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + cpp.separateDebugInformation: true + } + + CppApplication { + name: "app5" + type: ["application"] + files: ["main.cpp"] + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + cpp.separateDebugInformation: true + Properties { + condition: qbs.targetOS.contains("darwin") + cpp.dsymutilFlags: ["--flat"] + } + } + DynamicLibrary { + Depends { name: "cpp" } + name: "foo5" + type: ["dynamiclibrary"] + files: ["foo.cpp"] + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + cpp.separateDebugInformation: true + Properties { + condition: qbs.targetOS.contains("darwin") + cpp.dsymutilFlags: ["--flat"] + } + } + LoadableModule { + Depends { name: "cpp" } + name: "bar5" + files: ["foo.cpp"] + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + cpp.separateDebugInformation: true + Properties { + condition: qbs.targetOS.contains("darwin") + cpp.dsymutilFlags: ["--flat"] + } + } +} diff --git a/tests/auto/blackbox/testdata/setup-build-environment/modules/buildenv/buildenv.qbs b/tests/auto/blackbox/testdata/setup-build-environment/modules/buildenv/buildenv.qbs new file mode 100644 index 00000000..9213c646 --- /dev/null +++ b/tests/auto/blackbox/testdata/setup-build-environment/modules/buildenv/buildenv.qbs @@ -0,0 +1,8 @@ +import qbs.Environment + +Module { + property string varPrefix: "BUILD_ENV_" + setupBuildEnvironment: { + Environment.putEnv(product.buildenv.varPrefix + product.name.toUpperCase(), "1"); + } +} diff --git a/tests/auto/blackbox/testdata/setup-build-environment/modules/m/m.qbs b/tests/auto/blackbox/testdata/setup-build-environment/modules/m/m.qbs new file mode 100644 index 00000000..74e718c5 --- /dev/null +++ b/tests/auto/blackbox/testdata/setup-build-environment/modules/m/m.qbs @@ -0,0 +1,25 @@ +import qbs.Environment +import qbs.TextFile + +Module { + additionalProductTypes: ["m.target"] + Rule { + multiplex: true + Artifact { + filePath: "m.output" + fileTags: "m.target" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "creating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + var env = Environment.getEnv("BUILD_ENV_" + product.name.toUpperCase()); + if (env) + f.writeLine(env); + f.close(); + }; + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/setup-build-environment/setup-build-environment.qbs b/tests/auto/blackbox/testdata/setup-build-environment/setup-build-environment.qbs new file mode 100644 index 00000000..c5a7e87e --- /dev/null +++ b/tests/auto/blackbox/testdata/setup-build-environment/setup-build-environment.qbs @@ -0,0 +1,11 @@ +Project { + Product { + name: "first_product" + Depends { name: "buildenv" } + Depends { name: "m" } + } + Product { + name: "second_product" + Depends { name: "m" } + } +} diff --git a/tests/auto/blackbox/testdata/setup-run-environment/lib1.cpp b/tests/auto/blackbox/testdata/setup-run-environment/lib1.cpp new file mode 100644 index 00000000..f2db0a75 --- /dev/null +++ b/tests/auto/blackbox/testdata/setup-run-environment/lib1.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT void lib1Func() { } diff --git a/tests/auto/blackbox/testdata/setup-run-environment/lib2.cpp b/tests/auto/blackbox/testdata/setup-run-environment/lib2.cpp new file mode 100644 index 00000000..936020e0 --- /dev/null +++ b/tests/auto/blackbox/testdata/setup-run-environment/lib2.cpp @@ -0,0 +1,7 @@ +#include "../dllexport.h" +DLL_IMPORT void lib5Func(); + +DLL_EXPORT void lib2Func() +{ + lib5Func(); +} diff --git a/tests/auto/blackbox/testdata/setup-run-environment/lib3.cpp b/tests/auto/blackbox/testdata/setup-run-environment/lib3.cpp new file mode 100644 index 00000000..0f0183f6 --- /dev/null +++ b/tests/auto/blackbox/testdata/setup-run-environment/lib3.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT void lib3Func() { } diff --git a/tests/auto/blackbox/testdata/setup-run-environment/lib4.cpp b/tests/auto/blackbox/testdata/setup-run-environment/lib4.cpp new file mode 100644 index 00000000..aa9cc508 --- /dev/null +++ b/tests/auto/blackbox/testdata/setup-run-environment/lib4.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT void lib4Func() { } diff --git a/tests/auto/blackbox/testdata/setup-run-environment/lib5.cpp b/tests/auto/blackbox/testdata/setup-run-environment/lib5.cpp new file mode 100644 index 00000000..3cf8bd04 --- /dev/null +++ b/tests/auto/blackbox/testdata/setup-run-environment/lib5.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT void lib5Func() { } diff --git a/tests/auto/blackbox/testdata/setup-run-environment/main.cpp b/tests/auto/blackbox/testdata/setup-run-environment/main.cpp new file mode 100644 index 00000000..caa1d9ef --- /dev/null +++ b/tests/auto/blackbox/testdata/setup-run-environment/main.cpp @@ -0,0 +1,15 @@ +#include "../dllexport.h" + +DLL_IMPORT void lib1Func(); +DLL_IMPORT void lib2Func(); +DLL_IMPORT void lib3Func(); +DLL_IMPORT void lib4Func(); + +int main() +{ + lib1Func(); + lib2Func(); + lib3Func(); + lib4Func(); + return 0; +} diff --git a/tests/auto/blackbox/testdata/setup-run-environment/setup-run-environment.qbs b/tests/auto/blackbox/testdata/setup-run-environment/setup-run-environment.qbs new file mode 100644 index 00000000..5c5100b2 --- /dev/null +++ b/tests/auto/blackbox/testdata/setup-run-environment/setup-run-environment.qbs @@ -0,0 +1,125 @@ +import qbs.FileInfo + +Project { + DynamicLibrary { // Product dependency, installed + name: "lib1" + Depends { name: "cpp" } + + files: ["lib1.cpp"] + + install: !qbs.targetOS.contains("darwin") + installImportLib: true + installDir: "lib1" + importLibInstallDir: installDir + + Group { + condition: qbs.targetOS.contains("darwin") + fileTagsFilter: ["bundle.content"] + qbs.install: true + qbs.installSourceBase: destinationDirectory + } + } + + DynamicLibrary { // Product dependency, non-installed + name: "lib2" + Depends { name: "cpp" } + Depends { name: "lib5" } + + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + + files: ["lib2.cpp"] + } + + DynamicLibrary { // Non-dependency, referred to by full path + name: "lib3" + Depends { name: "cpp" } + + files: ["lib3.cpp"] + + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + + install: true + installImportLib: true + installDir: "lib3" + importLibInstallDir: installDir + } + + DynamicLibrary { // Non-dependency, referred to by name + name: "lib4" + Depends { name: "cpp" } + + files: ["lib4.cpp"] + + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + + // Testing shows that clang (8.0) does not find dynamic libraries via + // the -L and -l mechanism unless the name is "lib.a". + Properties { + condition: qbs.hostOS.contains("windows") && qbs.toolchain.contains("clang") + cpp.dynamicLibraryPrefix: "lib" + cpp.dynamicLibraryImportSuffix: ".a" + } + cpp.dynamicLibraryPrefix: original + cpp.dynamicLibraryImportSuffix: original + + install: true + installImportLib: true + installDir: "lib4" + importLibInstallDir: installDir + } + + DynamicLibrary { // Recursive product dependency + name: "lib5" + Depends { name: "cpp" } + + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + + files: ["lib5.cpp"] + } + + CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: "app" + consoleApplication: true + files: "main.cpp" + + Depends { name: "lib1" } + Depends { name: "lib2" } + Depends { name: "lib3"; cpp.link: false } + Depends { name: "lib4"; cpp.link: false } + + property string fullInstallPrefix: FileInfo.joinPaths(qbs.installRoot, qbs.installPrefix) + property string lib3FilePath: FileInfo.joinPaths(fullInstallPrefix, "lib3", + cpp.dynamicLibraryPrefix + "lib3" + (qbs.targetOS.contains("windows") + ? cpp.dynamicLibraryImportSuffix + : cpp.dynamicLibrarySuffix)) + cpp.dynamicLibraries: [lib3FilePath, "lib4"] + cpp.libraryPaths: FileInfo.joinPaths(fullInstallPrefix, "lib4") + } + + Probe { + id: osPrinter + property bool isWindows: qbs.targetOS.contains("windows") + configure: { + console.info("is windows"); + found = true; + } + } +} diff --git a/tests/auto/blackbox/testdata/smart-relinking/lib.cpp b/tests/auto/blackbox/testdata/smart-relinking/lib.cpp new file mode 100644 index 00000000..d8aa5270 --- /dev/null +++ b/tests/auto/blackbox/testdata/smart-relinking/lib.cpp @@ -0,0 +1,24 @@ +#include + +__attribute__ ((visibility ("default"))) void publicFunc() +{ +#ifdef PRINTF + std::printf("Tach\n"); +#endif +} + +__attribute__ ((visibility ("hidden"))) void privateFunc() +{ +} + +#ifdef PRIV2 +__attribute__ ((visibility ("hidden"))) void privateFunc2() +{ +} +#endif + +#ifdef PUB2 +__attribute__ ((visibility ("default"))) void publicFunc2() +{ +} +#endif diff --git a/tests/auto/blackbox/testdata/smart-relinking/main.cpp b/tests/auto/blackbox/testdata/smart-relinking/main.cpp new file mode 100644 index 00000000..280938c8 --- /dev/null +++ b/tests/auto/blackbox/testdata/smart-relinking/main.cpp @@ -0,0 +1,6 @@ +void publicFunc(); + +int main() +{ + publicFunc(); +} diff --git a/tests/auto/blackbox/testdata/smart-relinking/smart-relinking.qbs b/tests/auto/blackbox/testdata/smart-relinking/smart-relinking.qbs new file mode 100644 index 00000000..aac0692a --- /dev/null +++ b/tests/auto/blackbox/testdata/smart-relinking/smart-relinking.qbs @@ -0,0 +1,36 @@ +Project { + minimumQbsVersion: "1.6" + Probe { + id: tcProbe + property stringList toolchain: qbs.toolchain + property stringList targetOS: qbs.targetOS + configure: { + found = toolchain.contains("gcc") && targetOS.contains("unix"); + if (!found) + console.info("project disabled"); + } + } + + DynamicLibrary { + condition: tcProbe.found + name: "lib" + property stringList defines: [] + cpp.defines: defines + Depends { name: "cpp" } + files: ["lib.cpp"] + } + CppApplication { + condition: tcProbe.found + name:"app" + Depends { name: "lib" } + Depends { name: "staticlib" } + files: ["main.cpp"] + } + StaticLibrary { + condition: tcProbe.found + name: "staticlib" + Depends { name: "lib" } + Depends { name: "cpp" } + files: "staticlib.cpp" + } +} diff --git a/tests/auto/blackbox/testdata/smart-relinking/staticlib.cpp b/tests/auto/blackbox/testdata/smart-relinking/staticlib.cpp new file mode 100644 index 00000000..24fd8c6e --- /dev/null +++ b/tests/auto/blackbox/testdata/smart-relinking/staticlib.cpp @@ -0,0 +1 @@ +static void myFunc() {} diff --git a/tests/auto/blackbox/testdata/source-artifact-changes/modules/module_with_files/main.cpp b/tests/auto/blackbox/testdata/source-artifact-changes/modules/module_with_files/main.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/tests/auto/blackbox/testdata/source-artifact-changes/modules/module_with_files/main.cpp @@ -0,0 +1 @@ +int main() {} diff --git a/tests/auto/blackbox/testdata/source-artifact-changes/modules/module_with_files/module_with_files.qbs b/tests/auto/blackbox/testdata/source-artifact-changes/modules/module_with_files/module_with_files.qbs new file mode 100644 index 00000000..514248d2 --- /dev/null +++ b/tests/auto/blackbox/testdata/source-artifact-changes/modules/module_with_files/module_with_files.qbs @@ -0,0 +1,15 @@ +Module { + property stringList fileTags + property bool overrideTags + property bool filesAreTargets + + Depends { name: "cpp" } + + Group { + prefix: path + '/' + files: "main.cpp" + fileTags: product.module_with_files.fileTags + overrideTags: product.module_with_files.overrideTags + filesAreTargets: product.module_with_files.filesAreTargets + } +} diff --git a/tests/auto/blackbox/testdata/source-artifact-changes/source-artifact-changes.qbs b/tests/auto/blackbox/testdata/source-artifact-changes/source-artifact-changes.qbs new file mode 100644 index 00000000..de56376d --- /dev/null +++ b/tests/auto/blackbox/testdata/source-artifact-changes/source-artifact-changes.qbs @@ -0,0 +1,56 @@ +CppApplication { + name: "app" + type: base.concat("dummy") + consoleApplication: true + + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.embedInfoPlist: false + } + + Probe { + id: toolchainProbe + property stringList toolchain: qbs.toolchain + configure: { + console.info("is gcc: " + toolchain.contains("gcc")); + found = true; + } + } + + Rule { + multiplex: true + inputs: "cpp" + Artifact { + filePath: "dummy" + fileTags: "dummy" + cpp.cxxLanguageVersion: "hoppla" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + if (output.cpp.cxxLanguageVersion !== "hoppla") + throw "This cannot be!"; + }; + return cmd; + } + } + + Rule { + multiplex: true + inputs: "cpp" + requiresInputs: false + Artifact { filePath: "dummy2"; fileTags: "dummy" } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + console.info("cpp artifacts: " + + (product.artifacts.cpp ? product.artifacts.cpp.length : 0)) + }; + return cmd; + } + } + + Depends { name: "module_with_files" } +} diff --git a/tests/auto/blackbox/testdata/source-artifact-in-inputs-from-dependencies/header.h b/tests/auto/blackbox/testdata/source-artifact-in-inputs-from-dependencies/header.h new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/source-artifact-in-inputs-from-dependencies/source-artifact-in-inputs-from-dependencies.qbs b/tests/auto/blackbox/testdata/source-artifact-in-inputs-from-dependencies/source-artifact-in-inputs-from-dependencies.qbs new file mode 100644 index 00000000..3486667f --- /dev/null +++ b/tests/auto/blackbox/testdata/source-artifact-in-inputs-from-dependencies/source-artifact-in-inputs-from-dependencies.qbs @@ -0,0 +1,58 @@ +import qbs.FileInfo +import qbs.TextFile + +Project { + Product { + name: "dep1" + Group { + files: ["header.h"] + qbs.install: true + qbs.installDir: "include1" + } + } + + Product { + name: "dep2" + Group { + files: ["header.h"] + qbs.install: true + qbs.installDir: "include2" + } + } + + Product { + name: "p" + type: ["custom"] + Depends { name: "dep1" } + Depends { name: "dep2" } + + Rule { + multiplex: true + inputsFromDependencies: ["installable"] + Artifact { + filePath: "output.txt" + fileTags: ["custom"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + var tf; + try { + tf = new TextFile(output.filePath, TextFile.WriteOnly); + var artifactList = inputs["installable"]; + for (var i = 0; i < (artifactList ? artifactList.length : 0); ++i) { + var artifact = artifactList[i]; + tf.writeLine(FileInfo.joinPaths(artifact.qbs.installDir, + artifact.fileName)); + } + } finally { + if (tf) + tf.close(); + } + } + return [cmd]; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/soversion/lib.cpp b/tests/auto/blackbox/testdata/soversion/lib.cpp new file mode 100644 index 00000000..8101b05d --- /dev/null +++ b/tests/auto/blackbox/testdata/soversion/lib.cpp @@ -0,0 +1 @@ +void f() { } diff --git a/tests/auto/blackbox/testdata/soversion/soversion.qbs b/tests/auto/blackbox/testdata/soversion/soversion.qbs new file mode 100644 index 00000000..6ce2144c --- /dev/null +++ b/tests/auto/blackbox/testdata/soversion/soversion.qbs @@ -0,0 +1,7 @@ +DynamicLibrary { + name: "mylib" + property bool useVersion + version: useVersion ? "1.2.3" : undefined + Depends { name: "cpp" } + files: ["lib.cpp"] +} diff --git a/tests/auto/blackbox/testdata/static-lib-without-sources/lib.cpp b/tests/auto/blackbox/testdata/static-lib-without-sources/lib.cpp new file mode 100644 index 00000000..8101b05d --- /dev/null +++ b/tests/auto/blackbox/testdata/static-lib-without-sources/lib.cpp @@ -0,0 +1 @@ +void f() { } diff --git a/tests/auto/blackbox/testdata/static-lib-without-sources/static-lib-without-sources.qbs b/tests/auto/blackbox/testdata/static-lib-without-sources/static-lib-without-sources.qbs new file mode 100644 index 00000000..32a58c94 --- /dev/null +++ b/tests/auto/blackbox/testdata/static-lib-without-sources/static-lib-without-sources.qbs @@ -0,0 +1,14 @@ +Project { +StaticLibrary { + name: "a" + Depends { name: "cpp" } + files: ["lib.cpp"] + } + +Product { + type: qbs.targetOS.contains("darwin") ? undefined : ["staticlibrary"] + name: "b" + Depends { name: "cpp" } + Depends { name: "a" } +} +} diff --git a/tests/auto/blackbox/testdata/subprofile-change-tracking/main1.cpp b/tests/auto/blackbox/testdata/subprofile-change-tracking/main1.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/blackbox/testdata/subprofile-change-tracking/main1.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/blackbox/testdata/subprofile-change-tracking/main2.cpp b/tests/auto/blackbox/testdata/subprofile-change-tracking/main2.cpp new file mode 100644 index 00000000..612a484a --- /dev/null +++ b/tests/auto/blackbox/testdata/subprofile-change-tracking/main2.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() {} diff --git a/tests/auto/blackbox/testdata/subprofile-change-tracking/subprofile-change-tracking.qbs b/tests/auto/blackbox/testdata/subprofile-change-tracking/subprofile-change-tracking.qbs new file mode 100644 index 00000000..8abd1286 --- /dev/null +++ b/tests/auto/blackbox/testdata/subprofile-change-tracking/subprofile-change-tracking.qbs @@ -0,0 +1,8 @@ +Project { + CppApplication { name: "app1"; files: ["main1.cpp"] } + CppApplication { + name: "app2" + qbs.profiles: ["qbs-autotests-subprofile"] + files: ["main2.cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/successive-changes/input.in b/tests/auto/blackbox/testdata/successive-changes/input.in new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/successive-changes/successive-changes.qbs b/tests/auto/blackbox/testdata/successive-changes/successive-changes.qbs new file mode 100644 index 00000000..77df81e3 --- /dev/null +++ b/tests/auto/blackbox/testdata/successive-changes/successive-changes.qbs @@ -0,0 +1,29 @@ +import qbs.TextFile + +Project { + property string version: "1" + Product { + name: "theProduct" + type: ["output"] + Group { + files: ["input.in"] + fileTags: ["input"] + } + Rule { + inputs: ["input"] + Artifact { + filePath: "output.out" + fileTags: ["output"] + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating output"; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.write(project.version); + } + return [cmd]; + } + } + } +} diff --git a/tests/auto/blackbox/testdata/suspicious-calls/copy-command.qbs b/tests/auto/blackbox/testdata/suspicious-calls/copy-command.qbs new file mode 100644 index 00000000..3d43ef1f --- /dev/null +++ b/tests/auto/blackbox/testdata/suspicious-calls/copy-command.qbs @@ -0,0 +1,19 @@ +import qbs.File + +Product { + type: ["out"] + Group { + files: ["test.txt"] + fileTags: ["in"] + } + Rule { + inputs: ["in"] + outputFileTags: "out" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { File.copy(input.filePath, output.filePath); }; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/suspicious-calls/copy-eval.qbs b/tests/auto/blackbox/testdata/suspicious-calls/copy-eval.qbs new file mode 100644 index 00000000..b074c00c --- /dev/null +++ b/tests/auto/blackbox/testdata/suspicious-calls/copy-eval.qbs @@ -0,0 +1,9 @@ +import qbs.File + +Product { + name: { + File.copy(sourceDirectory + "/copy-eval.qbs", + sourceDirectory + "/copy-eval2.qbs"); + return "blubb" + } +} diff --git a/tests/auto/blackbox/testdata/suspicious-calls/copy-prepare.qbs b/tests/auto/blackbox/testdata/suspicious-calls/copy-prepare.qbs new file mode 100644 index 00000000..0408c9d5 --- /dev/null +++ b/tests/auto/blackbox/testdata/suspicious-calls/copy-prepare.qbs @@ -0,0 +1,20 @@ +import qbs.File + +Product { + type: ["out"] + Group { + files: ["test.txt"] + fileTags: ["in"] + } + Rule { + inputs: ["in"] + outputFileTags: "out" + prepare: { + File.copy(input.filePath, output.filePath); + var cmd = new JavaScriptCommand(); + cmd.description = "no-op"; + cmd.sourceCode = function() { }; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/suspicious-calls/copy-probe.qbs b/tests/auto/blackbox/testdata/suspicious-calls/copy-probe.qbs new file mode 100644 index 00000000..f47b204d --- /dev/null +++ b/tests/auto/blackbox/testdata/suspicious-calls/copy-probe.qbs @@ -0,0 +1,13 @@ +import qbs.File + +Product { + Probe { + id: theProbe + property string baseDir: project.sourceDirectory + + configure: { + File.copy(baseDir + "/copy-probe.qbs", + baseDir + "/copy-probe2.qbs"); + } + } +} diff --git a/tests/auto/blackbox/testdata/suspicious-calls/direntries-command.qbs b/tests/auto/blackbox/testdata/suspicious-calls/direntries-command.qbs new file mode 100644 index 00000000..c20d6f2e --- /dev/null +++ b/tests/auto/blackbox/testdata/suspicious-calls/direntries-command.qbs @@ -0,0 +1,21 @@ +import qbs.File + +Product { + type: ["out"] + Group { + files: ["test.txt"] + fileTags: ["in"] + } + Rule { + inputs: ["in"] + outputFileTags: "out" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + var dummy = File.directoryEntries(product.sourceDirectory, File.Files); + }; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/suspicious-calls/direntries-eval.qbs b/tests/auto/blackbox/testdata/suspicious-calls/direntries-eval.qbs new file mode 100644 index 00000000..2a759d93 --- /dev/null +++ b/tests/auto/blackbox/testdata/suspicious-calls/direntries-eval.qbs @@ -0,0 +1,5 @@ +import qbs.File + +Product { + name: File.directoryEntries(sourceDirectory, File.Files)[0] +} diff --git a/tests/auto/blackbox/testdata/suspicious-calls/direntries-prepare.qbs b/tests/auto/blackbox/testdata/suspicious-calls/direntries-prepare.qbs new file mode 100644 index 00000000..7b39a896 --- /dev/null +++ b/tests/auto/blackbox/testdata/suspicious-calls/direntries-prepare.qbs @@ -0,0 +1,20 @@ +import qbs.File + +Product { + type: ["out"] + Group { + files: ["test.txt"] + fileTags: ["in"] + } + Rule { + inputs: ["in"] + outputFileTags: "out" + prepare: { + var dummy = File.directoryEntries(product.sourceDirectory, File.Files); + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { }; + return [cmd]; + } + } +} diff --git a/tests/auto/blackbox/testdata/suspicious-calls/direntries-probe.qbs b/tests/auto/blackbox/testdata/suspicious-calls/direntries-probe.qbs new file mode 100644 index 00000000..fff82df8 --- /dev/null +++ b/tests/auto/blackbox/testdata/suspicious-calls/direntries-probe.qbs @@ -0,0 +1,14 @@ +import qbs.File + +Product { + Probe { + id: theProbe + property string baseDir: project.sourceDirectory + property stringList subDirs + + configure: { + subDirs = File.directoryEntries(baseDir, File.AllDirs); + found = true; + } + } +} diff --git a/tests/auto/blackbox/testdata/suspicious-calls/test.txt b/tests/auto/blackbox/testdata/suspicious-calls/test.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/symbolLinkMode/indirect.cpp b/tests/auto/blackbox/testdata/symbolLinkMode/indirect.cpp new file mode 100644 index 00000000..023acf40 --- /dev/null +++ b/tests/auto/blackbox/testdata/symbolLinkMode/indirect.cpp @@ -0,0 +1,3 @@ +void indirect() +{ +} diff --git a/tests/auto/blackbox/testdata/symbolLinkMode/lib.cpp b/tests/auto/blackbox/testdata/symbolLinkMode/lib.cpp new file mode 100644 index 00000000..8d96f509 --- /dev/null +++ b/tests/auto/blackbox/testdata/symbolLinkMode/lib.cpp @@ -0,0 +1,11 @@ +int somefunction() +{ + return 42; +} + +#include + +static const auto func = []() { + printf("Lib was loaded!\n"); + return 0; +}(); diff --git a/tests/auto/blackbox/testdata/symbolLinkMode/main.cpp b/tests/auto/blackbox/testdata/symbolLinkMode/main.cpp new file mode 100644 index 00000000..80149163 --- /dev/null +++ b/tests/auto/blackbox/testdata/symbolLinkMode/main.cpp @@ -0,0 +1,17 @@ +extern WEAK_IMPORT int somefunction(); +extern void indirect(); + +#include + +int main() +{ + printf("meow\n"); + if (&somefunction != nullptr) + printf("somefunction existed and it returned %d\n", somefunction()); + else + printf("somefunction did not exist\n"); +#if SHOULD_INSTALL_LIB + indirect(); +#endif + return 0; +} diff --git a/tests/auto/blackbox/testdata/symbolLinkMode/symbolLinkMode.qbs b/tests/auto/blackbox/testdata/symbolLinkMode/symbolLinkMode.qbs new file mode 100644 index 00000000..63789a18 --- /dev/null +++ b/tests/auto/blackbox/testdata/symbolLinkMode/symbolLinkMode.qbs @@ -0,0 +1,114 @@ +import qbs.FileInfo + +Project { + property bool shouldInstallLibrary: true + property bool lazy: false + + Application { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + Depends { name: "cpp" } + Depends { + name: "functions"; + cpp.symbolLinkMode: product.symbolLinkMode + cpp.link: !(product.qbs.targetOS.contains("linux") && product.symbolLinkMode === "weak") + } + + property string symbolLinkMode: project.lazy ? "lazy" : "weak" + + name: "driver" + files: ["main.cpp"] + consoleApplication: true + property string installLib: "SHOULD_INSTALL_LIB=" + project.shouldInstallLibrary + cpp.defines: { + if (symbolLinkMode === "weak") { + return qbs.targetOS.contains("darwin") + ? ["WEAK_IMPORT=__attribute__((weak_import))", installLib] + : ["WEAK_IMPORT=__attribute__((weak))", installLib]; + } + return ["WEAK_IMPORT=", installLib]; + } + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.7" + cpp.rpaths: [cpp.rpathOrigin + "/../lib"] + + Group { + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: "bin" + } + } + + DynamicLibrary { + Depends { name: "cpp" } + Depends { name: "indirect"; cpp.symbolLinkMode: "reexport" } + + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + name: "functions" + files: ["lib.cpp"] + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.7" + cpp.rpaths: [cpp.rpathOrigin] + + Properties { + condition: qbs.targetOS.contains("darwin") + cpp.sonamePrefix: "@rpath" + } + + Group { + condition: project.shouldInstallLibrary + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: "lib" + } + + Export { + // let the autotest pass on Linux where reexport is not supported + Depends { name: "indirect"; condition: !qbs.targetOS.contains("darwin") } + + // on Linux, there is no LC_WEAK_LOAD_DYLIB equivalent (the library is simply omitted + // from the list of load commands entirely), so use LD_PRELOAD to emulate + qbs.commonRunEnvironment: { + var env = original || {}; + if (project.shouldInstallLibrary) { + env["LD_PRELOAD"] = FileInfo.joinPaths(qbs.installRoot, + "lib", "libfunctions.so"); + } + return env; + } + } + } + + DynamicLibrary { + Depends { name: "cpp" } + + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.isBundle: false + } + name: "indirect" + files: ["indirect.cpp"] + cpp.cxxLanguageVersion: "c++11" + cpp.minimumMacosVersion: "10.7" + + Properties { + condition: qbs.targetOS.contains("darwin") + // reexport is incompatible with rpath, + // "ERROR: ld: file not found: @rpath/libindirect.dylib for architecture x86_64" + cpp.sonamePrefix: qbs.installRoot + "/lib" + } + + Group { + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: "lib" + } + } +} diff --git a/tests/auto/blackbox/testdata/symlink-removal/symlink-removal.qbs b/tests/auto/blackbox/testdata/symlink-removal/symlink-removal.qbs new file mode 100644 index 00000000..4aa36142 --- /dev/null +++ b/tests/auto/blackbox/testdata/symlink-removal/symlink-removal.qbs @@ -0,0 +1,17 @@ +import qbs.File + +Product { + type: "removal" + Rule { + multiplex: true + outputFileTags: "removal" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + File.remove(product.sourceDirectory + "/dir1"); + }; + return [cmd]; + } + } +} 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 00000000..e449173d --- /dev/null +++ b/tests/auto/blackbox/testdata/system-include-paths/main.cpp @@ -0,0 +1,8 @@ +#include +#include + +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 00000000..b951d885 --- /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 00000000..9c644fc2 --- /dev/null +++ b/tests/auto/blackbox/testdata/system-include-paths/system-include-paths.qbs @@ -0,0 +1,4 @@ +CppApplication { + files: ["main.cpp"] + cpp.systemIncludePaths: ["subdir"] +} diff --git a/tests/auto/blackbox/testdata/system-run-paths/lib.cpp b/tests/auto/blackbox/testdata/system-run-paths/lib.cpp new file mode 100644 index 00000000..bd3fa911 --- /dev/null +++ b/tests/auto/blackbox/testdata/system-run-paths/lib.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void func() { } diff --git a/tests/auto/blackbox/testdata/system-run-paths/main.cpp b/tests/auto/blackbox/testdata/system-run-paths/main.cpp new file mode 100644 index 00000000..183c82f6 --- /dev/null +++ b/tests/auto/blackbox/testdata/system-run-paths/main.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void func(); + +int main() +{ + func(); +} diff --git a/tests/auto/blackbox/testdata/system-run-paths/system-run-paths.qbs b/tests/auto/blackbox/testdata/system-run-paths/system-run-paths.qbs new file mode 100644 index 00000000..6e213717 --- /dev/null +++ b/tests/auto/blackbox/testdata/system-run-paths/system-run-paths.qbs @@ -0,0 +1,23 @@ +Project { + property bool setRunPaths + Product { + name: "theLib" + type: ["dynamiclibrary"] + Depends { name: "cpp" } + qbs.installPrefix: "" + Group { + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: "lib" + } + files: ["lib.cpp"] + } + + CppApplication { + name: "app" + Depends { name: "theLib" } + files: ["main.cpp"] + cpp.rpaths: qbs.installRoot + "/lib" + cpp.systemRunPaths: project.setRunPaths ? [qbs.installRoot + "/lib"] : [] + } +} diff --git a/tests/auto/blackbox/testdata/texttemplate/cdefgabc.txt.in b/tests/auto/blackbox/testdata/texttemplate/cdefgabc.txt.in new file mode 100644 index 00000000..9e3a753b --- /dev/null +++ b/tests/auto/blackbox/testdata/texttemplate/cdefgabc.txt.in @@ -0,0 +1 @@ +${c} ${d} ${e} ${f} ${g} ${a} ${b} ${c} diff --git a/tests/auto/blackbox/testdata/texttemplate/expected/lalala.txt b/tests/auto/blackbox/testdata/texttemplate/expected/lalala.txt new file mode 100644 index 00000000..c4743471 --- /dev/null +++ b/tests/auto/blackbox/testdata/texttemplate/expected/lalala.txt @@ -0,0 +1 @@ +do re mi fa so la ti do diff --git a/tests/auto/blackbox/testdata/texttemplate/expected/output.txt b/tests/auto/blackbox/testdata/texttemplate/expected/output.txt new file mode 100644 index 00000000..5c4b1a82 --- /dev/null +++ b/tests/auto/blackbox/testdata/texttemplate/expected/output.txt @@ -0,0 +1,12 @@ +foo bar baz +fu bar baz +foo BAR baz +foo bar buzz +fu BAR baz +fu bar buzz +fu BAR buzz +fooBARbaz +foo\BARbaz +foo\\BARbaz +foo\\\BARbaz +foo${bar}baz diff --git a/tests/auto/blackbox/testdata/texttemplate/output.txt.in b/tests/auto/blackbox/testdata/texttemplate/output.txt.in new file mode 100644 index 00000000..f5f645b7 --- /dev/null +++ b/tests/auto/blackbox/testdata/texttemplate/output.txt.in @@ -0,0 +1,12 @@ +foo bar baz +${foo} bar baz +foo ${bar} baz +foo bar ${baz} +${foo} ${bar} baz +${foo} bar ${baz} +${foo} ${bar} ${baz} +foo${bar}baz +foo\${bar}baz +foo\\${bar}baz +foo\\\${bar}baz +foo${$}{bar}baz diff --git a/tests/auto/blackbox/testdata/texttemplate/texttemplatetest.qbs b/tests/auto/blackbox/testdata/texttemplate/texttemplatetest.qbs new file mode 100644 index 00000000..6abda64f --- /dev/null +++ b/tests/auto/blackbox/testdata/texttemplate/texttemplatetest.qbs @@ -0,0 +1,24 @@ +Product { + name: "one" + type: ["text"] + files: ["output.txt.in"] + Depends { name: "texttemplate" } + texttemplate.dict: ({ + foo: "fu", + bar: "BAR", + baz: "buzz", + }) + Group { + files: ["cdefgabc.txt.in"] + texttemplate.outputFileName: "lalala.txt" + texttemplate.dict: ({ + c: "do", + d: "re", + e: "mi", + f: "fa", + g: "so", + a: "la", + b: "ti", + }) + } +} diff --git a/tests/auto/blackbox/testdata/toplevel-searchpath/qbs-resources/imports/MyProduct.qbs b/tests/auto/blackbox/testdata/toplevel-searchpath/qbs-resources/imports/MyProduct.qbs new file mode 100644 index 00000000..86718b57 --- /dev/null +++ b/tests/auto/blackbox/testdata/toplevel-searchpath/qbs-resources/imports/MyProduct.qbs @@ -0,0 +1 @@ +Product { } diff --git a/tests/auto/blackbox/testdata/toplevel-searchpath/toplevel-searchpath.qbs b/tests/auto/blackbox/testdata/toplevel-searchpath/toplevel-searchpath.qbs new file mode 100644 index 00000000..8ca6cfca --- /dev/null +++ b/tests/auto/blackbox/testdata/toplevel-searchpath/toplevel-searchpath.qbs @@ -0,0 +1 @@ +MyProduct { } diff --git a/tests/auto/blackbox/testdata/trackAddFile/after/main.cpp b/tests/auto/blackbox/testdata/trackAddFile/after/main.cpp new file mode 100644 index 00000000..0e474b22 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackAddFile/after/main.cpp @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** 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 "narf.h" +#include "zort.h" +#include + +int main(int argc, char **argv) +{ + printf("Hello World!\n"); + Narf narf; + narf.shout(); + Zort zort; + zort.shout(); + return 0; +} + diff --git a/tests/auto/blackbox/testdata/trackAddFile/after/trackAddFile.qbs b/tests/auto/blackbox/testdata/trackAddFile/after/trackAddFile.qbs new file mode 100644 index 00000000..113f4431 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackAddFile/after/trackAddFile.qbs @@ -0,0 +1,20 @@ +Project { + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: 'someapp' + type: 'application' + consoleApplication: true + Depends { name: 'cpp' } + files: [ + "main.cpp", + "narf.h", "narf.cpp", + "zort.h", "zort.cpp" + ] + } +} + diff --git a/tests/auto/blackbox/testdata/trackAddFile/after/zort.cpp b/tests/auto/blackbox/testdata/trackAddFile/after/zort.cpp new file mode 100644 index 00000000..1915ea86 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackAddFile/after/zort.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 "zort.h" +#include + +void Zort::shout() +{ + printf("ZORT!\n"); +} + diff --git a/tests/auto/blackbox/testdata/trackAddFile/after/zort.h b/tests/auto/blackbox/testdata/trackAddFile/after/zort.h new file mode 100644 index 00000000..9f7acbf2 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackAddFile/after/zort.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef ZORT_H +#define ZORT_H + +class Zort +{ +public: + void shout(); +}; + +#endif + diff --git a/tests/auto/blackbox/testdata/trackAddFile/before/main.cpp b/tests/auto/blackbox/testdata/trackAddFile/before/main.cpp new file mode 100644 index 00000000..f989e1d9 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackAddFile/before/main.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** 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 "narf.h" +#include + +int main(int argc, char **argv) +{ + printf("Hello World!\n"); + Narf narf; + narf.shout(); + return 0; +} + diff --git a/tests/auto/blackbox/testdata/trackAddFile/before/narf.cpp b/tests/auto/blackbox/testdata/trackAddFile/before/narf.cpp new file mode 100644 index 00000000..280d5f3b --- /dev/null +++ b/tests/auto/blackbox/testdata/trackAddFile/before/narf.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 "narf.h" +#include + +void Narf::shout() +{ + printf("NARF!\n"); +} + diff --git a/tests/auto/blackbox/testdata/trackAddFile/before/narf.h b/tests/auto/blackbox/testdata/trackAddFile/before/narf.h new file mode 100644 index 00000000..bd50f6e3 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackAddFile/before/narf.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef NARF_H +#define NARF_H + +class Narf +{ +public: + void shout(); +}; + +#endif + diff --git a/tests/auto/blackbox/testdata/trackAddFile/before/trackAddFile.qbs b/tests/auto/blackbox/testdata/trackAddFile/before/trackAddFile.qbs new file mode 100644 index 00000000..74efdf8f --- /dev/null +++ b/tests/auto/blackbox/testdata/trackAddFile/before/trackAddFile.qbs @@ -0,0 +1,16 @@ +Project { + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: 'someapp' + type: 'application' + consoleApplication: true + Depends { name: 'cpp' } + files: [ "main.cpp", "narf.h", "narf.cpp" ] + } +} + diff --git a/tests/auto/blackbox/testdata/trackExternalProductChanges/environmentChange.cpp b/tests/auto/blackbox/testdata/trackExternalProductChanges/environmentChange.cpp new file mode 100644 index 00000000..9ea74035 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackExternalProductChanges/environmentChange.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void environmentChange() { } diff --git a/tests/auto/blackbox/testdata/trackExternalProductChanges/fileList.js b/tests/auto/blackbox/testdata/trackExternalProductChanges/fileList.js new file mode 100644 index 00000000..707de033 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackExternalProductChanges/fileList.js @@ -0,0 +1,6 @@ +var File = require("qbs.File"); + +function fileList() { return []; } + +function filesFromFs(path) { return File.exists(path + "/fileExists.cpp") ? ["fileExists.cpp"] : []; } + diff --git a/tests/auto/blackbox/testdata/trackExternalProductChanges/hidden/hiddenheaderqbs.h b/tests/auto/blackbox/testdata/trackExternalProductChanges/hidden/hiddenheaderqbs.h new file mode 100644 index 00000000..50841a22 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackExternalProductChanges/hidden/hiddenheaderqbs.h @@ -0,0 +1,27 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ diff --git a/tests/auto/blackbox/testdata/trackExternalProductChanges/including.cpp b/tests/auto/blackbox/testdata/trackExternalProductChanges/including.cpp new file mode 100644 index 00000000..7feac10d --- /dev/null +++ b/tests/auto/blackbox/testdata/trackExternalProductChanges/including.cpp @@ -0,0 +1,31 @@ +/**************************************************************************** +** +** 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 + +void f() {} diff --git a/tests/auto/blackbox/testdata/trackExternalProductChanges/jsFileChange.cpp b/tests/auto/blackbox/testdata/trackExternalProductChanges/jsFileChange.cpp new file mode 100644 index 00000000..93fbe3ac --- /dev/null +++ b/tests/auto/blackbox/testdata/trackExternalProductChanges/jsFileChange.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void jsFileChange() { } diff --git a/tests/auto/blackbox/testdata/trackExternalProductChanges/main.cpp b/tests/auto/blackbox/testdata/trackExternalProductChanges/main.cpp new file mode 100644 index 00000000..e14f806b --- /dev/null +++ b/tests/auto/blackbox/testdata/trackExternalProductChanges/main.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { } diff --git a/tests/auto/blackbox/testdata/trackExternalProductChanges/trackExternalProductChanges.qbs b/tests/auto/blackbox/testdata/trackExternalProductChanges/trackExternalProductChanges.qbs new file mode 100644 index 00000000..534f49ff --- /dev/null +++ b/tests/auto/blackbox/testdata/trackExternalProductChanges/trackExternalProductChanges.qbs @@ -0,0 +1,14 @@ +import qbs.Environment +import "fileList.js" as FileList + +CppApplication { + property stringList filesFromEnv: Environment.getEnv("QBS_TEST_PULL_IN_FILE_VIA_ENV") + ? ["environmentChange.cpp"] : [] + files: ["main.cpp"].concat(FileList.fileList()).concat(filesFromEnv).concat(FileList.filesFromFs(path)) + + Group { + condition: Environment.getEnv("INCLUDE_PATH_TEST") + name: "file that needs help from the environment to find a header" + files: "including.cpp" + } +} diff --git a/tests/auto/blackbox/testdata/trackFileTags/after/main.cpp b/tests/auto/blackbox/testdata/trackFileTags/after/main.cpp new file mode 100644 index 00000000..1d2c8ebb --- /dev/null +++ b/tests/auto/blackbox/testdata/trackFileTags/after/main.cpp @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 + +int foo(); + +int main(int argc, char **argv) +{ + printf("there's %d foo here\n", foo()); + return 0; +} + diff --git a/tests/auto/blackbox/testdata/trackFileTags/after/trackFileTags.qbs b/tests/auto/blackbox/testdata/trackFileTags/after/trackFileTags.qbs new file mode 100644 index 00000000..1d589978 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackFileTags/after/trackFileTags.qbs @@ -0,0 +1,58 @@ +import qbs.TextFile + +Project { + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: 'someapp' + type: 'application' + consoleApplication: true + Depends { name: 'cpp' } + Group { + files: [ "main.cpp" ] + fileTags: [ "foosource", "cpp" ] + } + } + + Rule { + inputs: ["foosource"] + Artifact { + filePath: input.baseName + ".foo" + fileTags: ["foo"] + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.sourceCode = "var file = new TextFile(output.filePath, TextFile.WriteOnly);"; + cmd.sourceCode += "file.truncate();" + cmd.sourceCode += "file.write(\"There's nothing to see here!\");" + cmd.sourceCode += "file.close();" + cmd.description = "generating something"; + return cmd; + } + } + + Rule { + inputs: ["foo"] + Artifact { + filePath: input.baseName + "_foo.cpp" + fileTags: ["cpp"] + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.sourceCode = "var file = new TextFile(output.filePath, TextFile.WriteOnly);"; + cmd.sourceCode += "file.truncate();"; + cmd.sourceCode += "file.write(\"// There's nothing to see here!\\n\");"; + cmd.sourceCode += "file.write(\"int foo() { return 15; }\\n\");"; + cmd.sourceCode += "file.close();"; + cmd.description = "generating something"; + return cmd; + } + } +} + diff --git a/tests/auto/blackbox/testdata/trackFileTags/before/main.cpp b/tests/auto/blackbox/testdata/trackFileTags/before/main.cpp new file mode 100644 index 00000000..3016f8bb --- /dev/null +++ b/tests/auto/blackbox/testdata/trackFileTags/before/main.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** 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 + +int main(int argc, char **argv) +{ + printf("there's no foo here\n"); + return 0; +} + diff --git a/tests/auto/blackbox/testdata/trackFileTags/before/trackFileTags.qbs b/tests/auto/blackbox/testdata/trackFileTags/before/trackFileTags.qbs new file mode 100644 index 00000000..4c9e77dd --- /dev/null +++ b/tests/auto/blackbox/testdata/trackFileTags/before/trackFileTags.qbs @@ -0,0 +1,58 @@ +import qbs.TextFile + +Project { + Product { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + name: 'someapp' + type: 'application' + consoleApplication: true + Depends { name: 'cpp' } + Group { + files: [ "main.cpp" ] + fileTags: [ "cpp" ] + } + } + + Rule { + inputs: ["foosource"] + Artifact { + filePath: input.baseName + ".foo" + fileTags: ["foo"] + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.sourceCode = "var file = new TextFile(output.filePath, TextFile.WriteOnly);"; + cmd.sourceCode += "file.truncate();" + cmd.sourceCode += "file.write(\"There's nothing to see here!\");" + cmd.sourceCode += "file.close();" + cmd.description = "generating something"; + return cmd; + } + } + + Rule { + inputs: ["foo"] + Artifact { + filePath: input.baseName + "_foo.cpp" + fileTags: ["cpp"] + } + + prepare: { + var cmd = new JavaScriptCommand(); + cmd.sourceCode = "var file = new TextFile(output.filePath, TextFile.WriteOnly);"; + cmd.sourceCode += "file.truncate();"; + cmd.sourceCode += "file.write(\"// There's nothing to see here!\\n\");"; + cmd.sourceCode += "file.write(\"int foo() { return 15; }\\n\");"; + cmd.sourceCode += "file.close();"; + cmd.description = "generating something"; + return cmd; + } + } +} + diff --git a/tests/auto/blackbox/testdata/trackProducts/after/product3.qbs b/tests/auto/blackbox/testdata/trackProducts/after/product3.qbs new file mode 100644 index 00000000..bd9cdc00 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackProducts/after/product3.qbs @@ -0,0 +1,6 @@ +Product { + Depends { name: "cpp" } + type: "application" + consoleApplication: true + files: ["zoo.cpp"] +} diff --git a/tests/auto/blackbox/testdata/trackProducts/after/trackProducts.qbs b/tests/auto/blackbox/testdata/trackProducts/after/trackProducts.qbs new file mode 100644 index 00000000..2935ed1d --- /dev/null +++ b/tests/auto/blackbox/testdata/trackProducts/after/trackProducts.qbs @@ -0,0 +1,5 @@ +Project +{ + name: "trackProducts" + references: ["product1.qbs", "product2.qbs", "product3.qbs"] +} diff --git a/tests/auto/blackbox/testdata/trackProducts/after/zoo.cpp b/tests/auto/blackbox/testdata/trackProducts/after/zoo.cpp new file mode 100644 index 00000000..b65fb3f9 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackProducts/after/zoo.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 + +int main() +{ + printf("zoo\n"); +} diff --git a/tests/auto/blackbox/testdata/trackProducts/before/bar.cpp b/tests/auto/blackbox/testdata/trackProducts/before/bar.cpp new file mode 100644 index 00000000..7880c866 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackProducts/before/bar.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 + +int main() +{ + printf("bar\n"); +} diff --git a/tests/auto/blackbox/testdata/trackProducts/before/foo.cpp b/tests/auto/blackbox/testdata/trackProducts/before/foo.cpp new file mode 100644 index 00000000..865fb129 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackProducts/before/foo.cpp @@ -0,0 +1,34 @@ +/**************************************************************************** +** +** 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 + +int main() +{ + printf("foo\n"); +} diff --git a/tests/auto/blackbox/testdata/trackProducts/before/product1.qbs b/tests/auto/blackbox/testdata/trackProducts/before/product1.qbs new file mode 100644 index 00000000..48176c80 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackProducts/before/product1.qbs @@ -0,0 +1,6 @@ +Product { + Depends { name: "cpp" } + type: "application" + consoleApplication: true + files: ["foo.cpp"] +} diff --git a/tests/auto/blackbox/testdata/trackProducts/before/product2.qbs b/tests/auto/blackbox/testdata/trackProducts/before/product2.qbs new file mode 100644 index 00000000..be250486 --- /dev/null +++ b/tests/auto/blackbox/testdata/trackProducts/before/product2.qbs @@ -0,0 +1,6 @@ +Product { + Depends { name: "cpp" } + type: "application" + consoleApplication: true + files: ["bar.cpp"] +} diff --git a/tests/auto/blackbox/testdata/trackProducts/before/trackProducts.qbs b/tests/auto/blackbox/testdata/trackProducts/before/trackProducts.qbs new file mode 100644 index 00000000..8b5edb6b --- /dev/null +++ b/tests/auto/blackbox/testdata/trackProducts/before/trackProducts.qbs @@ -0,0 +1,5 @@ +Project +{ + name: "trackProducts" + references: ["product1.qbs", "product2.qbs"] +} diff --git a/tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/a/a.qbs b/tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/a/a.qbs new file mode 100644 index 00000000..fd52488f --- /dev/null +++ b/tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/a/a.qbs @@ -0,0 +1,5 @@ +Module { + validate: { + throw "Module cannot be loaded"; + } +} diff --git a/tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/b/b.qbs b/tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/b/b.qbs new file mode 100644 index 00000000..605a2aae --- /dev/null +++ b/tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/b/b.qbs @@ -0,0 +1,3 @@ +Module { + Depends { name: "a" } +} diff --git a/tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/c/c.qbs b/tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/c/c.qbs new file mode 100644 index 00000000..ac7dbbec --- /dev/null +++ b/tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/c/c.qbs @@ -0,0 +1,3 @@ +Module { + Depends { name: "a"; required: false } +} diff --git a/tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/d/d.qbs b/tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/d/d.qbs new file mode 100644 index 00000000..0bdd8c3b --- /dev/null +++ b/tests/auto/blackbox/testdata/transitive-invalid-dependencies/modules/d/d.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "b"; } + Depends { name: "c"; } +} diff --git a/tests/auto/blackbox/testdata/transitive-invalid-dependencies/transitive-invalid-dependencies.qbs b/tests/auto/blackbox/testdata/transitive-invalid-dependencies/transitive-invalid-dependencies.qbs new file mode 100644 index 00000000..209b1e47 --- /dev/null +++ b/tests/auto/blackbox/testdata/transitive-invalid-dependencies/transitive-invalid-dependencies.qbs @@ -0,0 +1,11 @@ +Product { + property bool modulePresent: { + console.info("b.present = " + b.present); + console.info("c.present = " + c.present); + console.info("d.present = " + d.present); + } + + Depends { name: "b"; required: false } + Depends { name: "c"; required: false } + Depends { name: "d"; required: false } +} diff --git a/tests/auto/blackbox/testdata/transitive-optional-dependencies/modules/a/a.qbs b/tests/auto/blackbox/testdata/transitive-optional-dependencies/modules/a/a.qbs new file mode 100644 index 00000000..ffe7ef70 --- /dev/null +++ b/tests/auto/blackbox/testdata/transitive-optional-dependencies/modules/a/a.qbs @@ -0,0 +1,3 @@ +Module { + Depends { name: "b"; required: false } +} diff --git a/tests/auto/blackbox/testdata/transitive-optional-dependencies/modules/b/b.qbs b/tests/auto/blackbox/testdata/transitive-optional-dependencies/modules/b/b.qbs new file mode 100644 index 00000000..ba08b862 --- /dev/null +++ b/tests/auto/blackbox/testdata/transitive-optional-dependencies/modules/b/b.qbs @@ -0,0 +1,3 @@ +Module { + condition: false +} diff --git a/tests/auto/blackbox/testdata/transitive-optional-dependencies/transitive-optional-dependencies.qbs b/tests/auto/blackbox/testdata/transitive-optional-dependencies/transitive-optional-dependencies.qbs new file mode 100644 index 00000000..6171fa94 --- /dev/null +++ b/tests/auto/blackbox/testdata/transitive-optional-dependencies/transitive-optional-dependencies.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "a" } +} diff --git a/tests/auto/blackbox/testdata/typescript/animals.ts b/tests/auto/blackbox/testdata/typescript/animals.ts new file mode 100644 index 00000000..a33ae5c1 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/animals.ts @@ -0,0 +1,21 @@ +export interface Mammal { + speak(): string; +} + +export class Cat implements Mammal { + public speak() { + return "Meow"; // a cat says meow + } +} + +export class Dog implements Mammal { + public speak() { + return "Woof"; // a dog says woof + } +} + +export class Human implements Mammal { + public speak() { + return "Hello"; + } +} diff --git a/tests/auto/blackbox/testdata/typescript/extra.js b/tests/auto/blackbox/testdata/typescript/extra.js new file mode 100644 index 00000000..5500e468 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/extra.js @@ -0,0 +1,3 @@ +if (console) { + console.log("This doesn't do anything useful!"); +} diff --git a/tests/auto/blackbox/testdata/typescript/foo.ts b/tests/auto/blackbox/testdata/typescript/foo.ts new file mode 100644 index 00000000..3554d317 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/foo.ts @@ -0,0 +1,5 @@ +export class Greeter { + public getGreeting(): string { + return "guten Tag!"; + } +} diff --git a/tests/auto/blackbox/testdata/typescript/foo2.ts b/tests/auto/blackbox/testdata/typescript/foo2.ts new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/foo2.ts @@ -0,0 +1 @@ + diff --git a/tests/auto/blackbox/testdata/typescript/hello.ts b/tests/auto/blackbox/testdata/typescript/hello.ts new file mode 100644 index 00000000..940a3ff0 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/hello.ts @@ -0,0 +1 @@ +console.log("Hello world!"); diff --git a/tests/auto/blackbox/testdata/typescript/main.ts b/tests/auto/blackbox/testdata/typescript/main.ts new file mode 100644 index 00000000..e2f7ed32 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/main.ts @@ -0,0 +1,22 @@ +import Animals = require("./animals"); +import Foo = require("./foo"); +import Extra = require("./woosh/extra"); + +function main() { + var mammals: Animals.Mammal[] = []; + mammals.push(new Animals.Human()); + mammals.push(new Animals.Dog()); + mammals.push(new Animals.Cat()); + + // Make everyone speak + for (var i = 0; i < mammals.length; ++i) { + console.log(mammals[i].speak()); + } + + (new Extra.Boom()); + + var greeting: string = (new Foo.Greeter()).getGreeting(); + console.log(greeting); +} + +main(); diff --git a/tests/auto/blackbox/testdata/typescript/typescript.qbs b/tests/auto/blackbox/testdata/typescript/typescript.qbs new file mode 100644 index 00000000..28c62eb4 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/typescript.qbs @@ -0,0 +1,54 @@ +Project { + NodeJSApplication { + Depends { name: "typescript" } + Depends { name: "lib" } + + typescript.warningLevel: "pedantic" + typescript.generateDeclarations: true + typescript.moduleLoader: "commonjs" + nodejs.applicationFile: "main.ts" + + name: "animals" + + files: [ + "animals.ts", + "extra.js", + "woosh/extra.ts" + ] + } + + Product { + Depends { name: "typescript" } + + typescript.generateDeclarations: true + typescript.moduleLoader: "commonjs" + + name: "lib" + + files: [ + "foo.ts" + ] + } + + Product { + Depends { name: "typescript" } + + typescript.generateDeclarations: true + + name: "lib2" + + files: [ + "foo2.ts" + ] + } + + NodeJSApplication { + Depends { name: "typescript" } + Depends { name: "lib2" } + + typescript.singleFile: true + nodejs.applicationFile: "hello.ts" + + name: "single" + } +} diff --git a/tests/auto/blackbox/testdata/typescript/woosh/extra.ts b/tests/auto/blackbox/testdata/typescript/woosh/extra.ts new file mode 100644 index 00000000..7dbef6a7 --- /dev/null +++ b/tests/auto/blackbox/testdata/typescript/woosh/extra.ts @@ -0,0 +1,2 @@ +export class Boom { +} diff --git a/tests/auto/blackbox/testdata/undefined-target-platform/undefined-target-platform.qbs b/tests/auto/blackbox/testdata/undefined-target-platform/undefined-target-platform.qbs new file mode 100644 index 00000000..2b3724c2 --- /dev/null +++ b/tests/auto/blackbox/testdata/undefined-target-platform/undefined-target-platform.qbs @@ -0,0 +1,13 @@ +import qbs.File +import qbs.FileInfo + +Product { + name: "undefined-target-platform" + qbs.targetPlatform: undefined + + readonly property bool _validate: { + if (Array.isArray(qbs.targetOS) && qbs.targetOS.length === 0) + return true; + throw "Invalid qbs.targetOS value: " + qbs.targetOS; + } +} diff --git a/tests/auto/blackbox/testdata/usings-as-sole-inputs-non-multiplexed/custom1.in b/tests/auto/blackbox/testdata/usings-as-sole-inputs-non-multiplexed/custom1.in new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/usings-as-sole-inputs-non-multiplexed/custom2.in b/tests/auto/blackbox/testdata/usings-as-sole-inputs-non-multiplexed/custom2.in new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/usings-as-sole-inputs-non-multiplexed/usings-as-sole-inputs-non-multiplexed.qbs b/tests/auto/blackbox/testdata/usings-as-sole-inputs-non-multiplexed/usings-as-sole-inputs-non-multiplexed.qbs new file mode 100644 index 00000000..cd2d2389 --- /dev/null +++ b/tests/auto/blackbox/testdata/usings-as-sole-inputs-non-multiplexed/usings-as-sole-inputs-non-multiplexed.qbs @@ -0,0 +1,61 @@ +import qbs.FileInfo +import qbs.TextFile + +Project { + Product { + name: "p1" + type: "custom" + Group { + files: "custom1.in" + fileTags: "custom.in" + } + } + Product { + name: "p2" + type: "custom" + Group { + files: "custom2.in" + fileTags: "custom.in" + } + } + + Rule { + inputs: "custom.in" + Artifact { + filePath: FileInfo.baseName(input.filePath) + ".out" + fileTags: "custom" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + } + return cmd; + } + } + + Product { + name: "p3" + type: "custom-plus" + Depends { name: "p1" } + Depends { name: "p2" } + Rule { + inputsFromDependencies: "custom" + Artifact { + filePath: FileInfo.fileName(input.filePath) + ".plus" + fileTags: "custom-plus" + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "generating " + output.fileName; + cmd.sourceCode = function() { + var f = new TextFile(output.filePath, TextFile.WriteOnly); + f.close(); + } + return cmd; + } + } + } +} 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 00000000..56757a70 --- /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 00000000..b6e025e4 --- /dev/null +++ b/tests/auto/blackbox/testdata/variant-suffix/variant-suffix.qbs @@ -0,0 +1,38 @@ +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" + + qbs.installPrefix: "" + install: true + + 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 00000000..bc653e86 --- /dev/null +++ b/tests/auto/blackbox/testdata/vcs/main.cpp @@ -0,0 +1,7 @@ +#include +#include + +int main() +{ + std::cout << "__" << 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 00000000..dabe889b --- /dev/null +++ b/tests/auto/blackbox/testdata/vcs/vcstest.qbs @@ -0,0 +1,11 @@ +CppApplication { + condition: { + var result = qbs.targetPlatform === qbs.hostPlatform; + if (!result) + console.info("targetPlatform differs from hostPlatform"); + return result; + } + Depends { name: "vcs" } + vcs.headerFileName: "my-repo-state.h" + files: ["main.cpp"] +} diff --git a/tests/auto/blackbox/testdata/versioncheck/modules/higher/higher.qbs b/tests/auto/blackbox/testdata/versioncheck/modules/higher/higher.qbs new file mode 100644 index 00000000..c5ab20f5 --- /dev/null +++ b/tests/auto/blackbox/testdata/versioncheck/modules/higher/higher.qbs @@ -0,0 +1,8 @@ +Module { + Depends { + name: "lower" + required: false + versionAtLeast: "1.0" + versionBelow: "10.0" + } +} diff --git a/tests/auto/blackbox/testdata/versioncheck/modules/lower/lower.qbs b/tests/auto/blackbox/testdata/versioncheck/modules/lower/lower.qbs new file mode 100644 index 00000000..cd8c6471 --- /dev/null +++ b/tests/auto/blackbox/testdata/versioncheck/modules/lower/lower.qbs @@ -0,0 +1 @@ +Module { } diff --git a/tests/auto/blackbox/testdata/versioncheck/versioncheck.qbs b/tests/auto/blackbox/testdata/versioncheck/versioncheck.qbs new file mode 100644 index 00000000..63bef1d7 --- /dev/null +++ b/tests/auto/blackbox/testdata/versioncheck/versioncheck.qbs @@ -0,0 +1,10 @@ +Product { + property string requestedMinVersion + property string requestedMaxVersion + Depends { name: "higher" } + Depends { + name: "lower" + versionAtLeast: requestedMinVersion + versionBelow: requestedMaxVersion + } +} diff --git a/tests/auto/blackbox/testdata/versionscript/testlib.c b/tests/auto/blackbox/testdata/versionscript/testlib.c new file mode 100644 index 00000000..87c59b27 --- /dev/null +++ b/tests/auto/blackbox/testdata/versionscript/testlib.c @@ -0,0 +1,30 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +void dummyLocal() {} +void dummyGlobal() {} diff --git a/tests/auto/blackbox/testdata/versionscript/versionscript b/tests/auto/blackbox/testdata/versionscript/versionscript new file mode 100644 index 00000000..ac9c05b4 --- /dev/null +++ b/tests/auto/blackbox/testdata/versionscript/versionscript @@ -0,0 +1 @@ +{ global: dummyGlobal; local: dummyLocal; }; diff --git a/tests/auto/blackbox/testdata/versionscript/versionscript.qbs b/tests/auto/blackbox/testdata/versionscript/versionscript.qbs new file mode 100644 index 00000000..cc5c7b1c --- /dev/null +++ b/tests/auto/blackbox/testdata/versionscript/versionscript.qbs @@ -0,0 +1,27 @@ +DynamicLibrary { + type: base.concat("custom") + Depends { name: "cpp" } + files: ["testlib.c"] + Group { + name: "version script" + files: ["versionscript"] + fileTags: ["versionscript"] + } + + Rule { + multiplex: true + outputFileTags: "custom" + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + console.warn("---" + product.cpp.nmPath + "---"); + } + return [cmd]; + } + } + + qbs.installPrefix: "" + install: true + installDir: "" +} diff --git a/tests/auto/blackbox/testdata/whole-archive/dynamiclib.cpp b/tests/auto/blackbox/testdata/whole-archive/dynamiclib.cpp new file mode 100644 index 00000000..f828b539 --- /dev/null +++ b/tests/auto/blackbox/testdata/whole-archive/dynamiclib.cpp @@ -0,0 +1,5 @@ +#include "../dllexport.h" + +void usedFunc(); + +DLL_EXPORT void f() { usedFunc(); } diff --git a/tests/auto/blackbox/testdata/whole-archive/main1.cpp b/tests/auto/blackbox/testdata/whole-archive/main1.cpp new file mode 100644 index 00000000..f909ac71 --- /dev/null +++ b/tests/auto/blackbox/testdata/whole-archive/main1.cpp @@ -0,0 +1,8 @@ +#include "../dllexport.h" + +DLL_IMPORT void unusedFunc1(); + +int main() +{ + unusedFunc1(); +} diff --git a/tests/auto/blackbox/testdata/whole-archive/main2.cpp b/tests/auto/blackbox/testdata/whole-archive/main2.cpp new file mode 100644 index 00000000..cc20b2fa --- /dev/null +++ b/tests/auto/blackbox/testdata/whole-archive/main2.cpp @@ -0,0 +1,8 @@ +#include "../dllexport.h" + +DLL_IMPORT void unusedFunc2(); + +int main() +{ + unusedFunc2(); +} diff --git a/tests/auto/blackbox/testdata/whole-archive/main3.cpp b/tests/auto/blackbox/testdata/whole-archive/main3.cpp new file mode 100644 index 00000000..d47f728a --- /dev/null +++ b/tests/auto/blackbox/testdata/whole-archive/main3.cpp @@ -0,0 +1,7 @@ +#include "../dllexport.h" + +DLL_IMPORT void unusedFunc3(); +int main() +{ + unusedFunc3(); +} diff --git a/tests/auto/blackbox/testdata/whole-archive/main4.cpp b/tests/auto/blackbox/testdata/whole-archive/main4.cpp new file mode 100644 index 00000000..769026bc --- /dev/null +++ b/tests/auto/blackbox/testdata/whole-archive/main4.cpp @@ -0,0 +1,8 @@ +#include "../dllexport.h" + +DLL_IMPORT void unusedFunc4(); + +int main() +{ + unusedFunc4(); +} diff --git a/tests/auto/blackbox/testdata/whole-archive/unused1.cpp b/tests/auto/blackbox/testdata/whole-archive/unused1.cpp new file mode 100644 index 00000000..179de1b2 --- /dev/null +++ b/tests/auto/blackbox/testdata/whole-archive/unused1.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT void unusedFunc1() { } diff --git a/tests/auto/blackbox/testdata/whole-archive/unused2.cpp b/tests/auto/blackbox/testdata/whole-archive/unused2.cpp new file mode 100644 index 00000000..28350f70 --- /dev/null +++ b/tests/auto/blackbox/testdata/whole-archive/unused2.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT void unusedFunc2() { } diff --git a/tests/auto/blackbox/testdata/whole-archive/unused3.cpp b/tests/auto/blackbox/testdata/whole-archive/unused3.cpp new file mode 100644 index 00000000..ab5fb74e --- /dev/null +++ b/tests/auto/blackbox/testdata/whole-archive/unused3.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT void unusedFunc3() { } diff --git a/tests/auto/blackbox/testdata/whole-archive/unused4.cpp b/tests/auto/blackbox/testdata/whole-archive/unused4.cpp new file mode 100644 index 00000000..95975345 --- /dev/null +++ b/tests/auto/blackbox/testdata/whole-archive/unused4.cpp @@ -0,0 +1,3 @@ +#include "../dllexport.h" + +DLL_EXPORT void unusedFunc4() { } diff --git a/tests/auto/blackbox/testdata/whole-archive/used.cpp b/tests/auto/blackbox/testdata/whole-archive/used.cpp new file mode 100644 index 00000000..fe7afff2 --- /dev/null +++ b/tests/auto/blackbox/testdata/whole-archive/used.cpp @@ -0,0 +1 @@ +void usedFunc() { } diff --git a/tests/auto/blackbox/testdata/whole-archive/whole-archive.qbs b/tests/auto/blackbox/testdata/whole-archive/whole-archive.qbs new file mode 100644 index 00000000..f3bcff2c --- /dev/null +++ b/tests/auto/blackbox/testdata/whole-archive/whole-archive.qbs @@ -0,0 +1,71 @@ +import qbs.Utilities + +Project { + StaticLibrary { + name: "staticlib 1" + Depends { name: "cpp" } + files: ["unused1.cpp", "used.cpp"] + } + StaticLibrary { + name: "staticlib2" + Depends { name: "cpp" } + files: ["unused2.cpp"] + } + StaticLibrary { + name: "staticlib3" + Depends { name: "cpp" } + files: ["unused3.cpp"] + } + StaticLibrary { + name: "staticlib4" + Depends { name: "cpp" } + files: ["unused4.cpp"] + } + + DynamicLibrary { + name: "dynamiclib" + property string linkWholeArchive + Depends { name: "cpp" } + Probe { + id: dummy + property stringList toolchain: qbs.toolchain + property string compilerVersion: cpp.compilerVersion + property string dummy: product.linkWholeArchive // To force probe re-execution + configure: { + if (!toolchain.contains("msvc") + || Utilities.versionCompare(compilerVersion, "19.0.24215.1") >= 0) { + console.info("can link whole archives"); + } else { + console.info("cannot link whole archives"); + } + } + } + Depends { name: "staticlib 1"; cpp.linkWholeArchive: product.linkWholeArchive } + Depends { name: "staticlib2"; cpp.linkWholeArchive: product.linkWholeArchive } + Depends { name: "staticlib3" } + Depends { name: "staticlib4"; cpp.linkWholeArchive: product.linkWholeArchive } + files: ["dynamiclib.cpp"] + } + + CppApplication { + name: "app1" + Depends { name: "dynamiclib" } + files: ["main1.cpp"] + } + + CppApplication { + name: "app2" + Depends { name: "dynamiclib" } + files: ["main2.cpp"] + } + CppApplication { + name: "app3" + Depends { name: "dynamiclib" } + files: ["main3.cpp"] + } + CppApplication { + name: "app4" + Depends { name: "dynamiclib" } + files: ["main4.cpp"] + } +} diff --git a/tests/auto/blackbox/testdata/wildcard_renaming/pioniere.txt b/tests/auto/blackbox/testdata/wildcard_renaming/pioniere.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/wildcard_renaming/wildcard_renaming.qbs b/tests/auto/blackbox/testdata/wildcard_renaming/wildcard_renaming.qbs new file mode 100644 index 00000000..12416188 --- /dev/null +++ b/tests/auto/blackbox/testdata/wildcard_renaming/wildcard_renaming.qbs @@ -0,0 +1,7 @@ +Product { + qbs.installPrefix: "" + Group { + qbs.install: true + files: "*" + } +} diff --git a/tests/auto/blackbox/testdata/wildcards-and-rules/input1.inp b/tests/auto/blackbox/testdata/wildcards-and-rules/input1.inp new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/blackbox/testdata/wildcards-and-rules/wildcards-and-rules.qbs b/tests/auto/blackbox/testdata/wildcards-and-rules/wildcards-and-rules.qbs new file mode 100644 index 00000000..f6662529 --- /dev/null +++ b/tests/auto/blackbox/testdata/wildcards-and-rules/wildcards-and-rules.qbs @@ -0,0 +1,36 @@ +import qbs.TextFile + +Product { + name: "wildcards-and-rules" + type: "mytype" + files: ["*.inp", "*.dep"] + FileTagger { + patterns: "*.inp" + fileTags: ["inp"] + } + FileTagger { + patterns: "*.dep" + fileTags: ["dep"] + } + Rule { + multiplex: true + inputs: ["inp"] + explicitlyDependsOn: ["dep"] + Artifact { + filePath: "test.mytype" + fileTags: product.type + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.description = "Creating output artifact"; + cmd.highlight = "codegen"; + cmd.sourceCode = function() { + var file = new TextFile(output.filePath, TextFile.WriteOnly); + for (var i = 0; i < inputs.inp.length; ++i) + file.writeLine(inputs.inp[i].fileName); + file.close(); + } + return cmd; + } + } +} diff --git a/tests/auto/blackbox/testdata/wix/ExampleScript.bat b/tests/auto/blackbox/testdata/wix/ExampleScript.bat new file mode 100644 index 00000000..3af583cd --- /dev/null +++ b/tests/auto/blackbox/testdata/wix/ExampleScript.bat @@ -0,0 +1 @@ +echo Hello world! diff --git a/tests/auto/blackbox/testdata/wix/QbsBootstrapper.wxs b/tests/auto/blackbox/testdata/wix/QbsBootstrapper.wxs new file mode 100644 index 00000000..272f6af5 --- /dev/null +++ b/tests/auto/blackbox/testdata/wix/QbsBootstrapper.wxs @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tests/auto/blackbox/testdata/wix/QbsSetup.wxs b/tests/auto/blackbox/testdata/wix/QbsSetup.wxs new file mode 100644 index 00000000..8f97ff66 --- /dev/null +++ b/tests/auto/blackbox/testdata/wix/QbsSetup.wxs @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/auto/blackbox/testdata/wix/Qt.wxs b/tests/auto/blackbox/testdata/wix/Qt.wxs new file mode 100644 index 00000000..fbd992c4 --- /dev/null +++ b/tests/auto/blackbox/testdata/wix/Qt.wxs @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/auto/blackbox/testdata/wix/WiXInstallers.qbs b/tests/auto/blackbox/testdata/wix/WiXInstallers.qbs new file mode 100644 index 00000000..07f61ba2 --- /dev/null +++ b/tests/auto/blackbox/testdata/wix/WiXInstallers.qbs @@ -0,0 +1,38 @@ +import qbs.FileInfo + +Project { + WindowsInstallerPackage { + name: "QbsSetup" + targetName: "qbs" + files: ["QbsSetup.wxs", "ExampleScript.bat"] + wix.defines: ["scriptName=ExampleScript.bat"] + wix.extensions: ["WixBalExtension", "WixUIExtension"] + qbs.targetPlatform: "windows" + + Export { + Depends { name: "wix" } + wix.defines: base.concat(["msiName=" + + FileInfo.joinPaths(product.buildDirectory, + product.targetName + wix.windowsInstallerSuffix)]) + } + } + + WindowsSetupPackage { + Depends { name: "QbsSetup" } + condition: qbs.hostOS.contains("windows") // currently does not work in Wine with WiX 3.9 + name: "QbsBootstrapper" + targetName: "qbs-setup-" + qbs.architecture + files: ["QbsBootstrapper.wxs"] + qbs.architecture: original || "x86" + qbs.targetPlatform: "windows" + } + + WindowsInstallerPackage { + name: "RegressionBuster9000" + files: ["QbsSetup.wxs", "Qt.wxs", "de.wxl"] + wix.defines: ["scriptName=ExampleScript.bat"] + wix.cultures: [] + qbs.architecture: original || "x86" + qbs.targetPlatform: "windows" + } +} diff --git a/tests/auto/blackbox/testdata/wix/de.wxl b/tests/auto/blackbox/testdata/wix/de.wxl new file mode 100644 index 00000000..75394cfd --- /dev/null +++ b/tests/auto/blackbox/testdata/wix/de.wxl @@ -0,0 +1 @@ + diff --git a/tests/auto/blackbox/testdata/wixDependencies/QbsSetup.wxs b/tests/auto/blackbox/testdata/wixDependencies/QbsSetup.wxs new file mode 100644 index 00000000..ec839a26 --- /dev/null +++ b/tests/auto/blackbox/testdata/wixDependencies/QbsSetup.wxs @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/auto/blackbox/testdata/wixDependencies/main.c b/tests/auto/blackbox/testdata/wixDependencies/main.c new file mode 100644 index 00000000..76e81970 --- /dev/null +++ b/tests/auto/blackbox/testdata/wixDependencies/main.c @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata/wixDependencies/wixDependencies.qbs b/tests/auto/blackbox/testdata/wixDependencies/wixDependencies.qbs new file mode 100644 index 00000000..d42a1805 --- /dev/null +++ b/tests/auto/blackbox/testdata/wixDependencies/wixDependencies.qbs @@ -0,0 +1,67 @@ +import qbs.TextFile + +Project { + WindowsInstallerPackage { + Depends { name: "app" } + Depends { name: "lib" } + name: "QbsSetup" + targetName: "qbs" + files: ["QbsSetup.wxs"] + wix.extensions: ["WixBalExtension", "WixUIExtension"] + destinationDirectory: project.buildDirectory + } + + Application { + Depends { name: "cpp" } + name: "app" + files: ["main.c"] + Group { + fileTagsFilter: product.type + qbs.install: true + } + destinationDirectory: project.buildDirectory + } + + DynamicLibrary { + Depends { name: "cpp" } + name: "lib" + files: ["main.c"] + Group { + fileTagsFilter: product.type + qbs.install: true + } + Rule { + // This rule tries to provoke the installer into building too early (and the test + // verifies that it does not) by causing the build of the installables to take + // a lot longer. + multiplex: true + outputFileTags: ["c"] + outputArtifacts: { + var artifacts = []; + for (var i = 0; i < 96; ++i) + artifacts.push({ filePath: "c" + i + ".c", fileTags: ["c"] }); + return artifacts; + } + prepare: { + var cmd = new JavaScriptCommand(); + cmd.silent = true; + cmd.sourceCode = function() { + for (var j = 0; j < 1000; ++j) { // Artificial delay. + for (var i = 0; i < outputs["c"].length; ++i) { + var tf; + try { + tf = new TextFile(outputs["c"][i].filePath, TextFile.WriteOnly); + tf.writeLine("int main" + i + "() { return 0; }"); + } finally { + if (tf) + tf.close(); + } + } + } + }; + return [cmd]; + } + } + destinationDirectory: project.buildDirectory + } +} diff --git a/tests/auto/blackbox/tst_blackbox.cpp b/tests/auto/blackbox/tst_blackbox.cpp new file mode 100644 index 00000000..ba07b632 --- /dev/null +++ b/tests/auto/blackbox/tst_blackbox.cpp @@ -0,0 +1,8516 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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_blackbox.h" + +#include "../shared.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define WAIT_FOR_NEW_TIMESTAMP() waitForNewTimestamp(testDataDir) + +using qbs::Internal::HostOsInfo; +using qbs::Profile; + +class MacosTarHealer { +public: + MacosTarHealer() { + if (HostOsInfo::hostOs() == HostOsInfo::HostOsMacos) { + // work around absurd tar behavior on macOS + qputenv("COPY_EXTENDED_ATTRIBUTES_DISABLE", "true"); + qputenv("COPYFILE_DISABLE", "true"); + } + } + + ~MacosTarHealer() { + if (HostOsInfo::hostOs() == HostOsInfo::HostOsMacos) { + qunsetenv("COPY_EXTENDED_ATTRIBUTES_DISABLE"); + qunsetenv("COPYFILE_DISABLE"); + } + } +}; + +QMap TestBlackbox::findCli(int *status) +{ + QTemporaryDir temp; + QDir::setCurrent(testDataDir + "/find"); + QbsRunParameters params = QStringList() << "-f" << "find-cli.qbs"; + params.buildDirectory = temp.path(); + const int res = runQbs(params); + if (status) + *status = res; + QFile file(temp.path() + "/" + relativeProductBuildDir("find-cli") + "/cli.json"); + if (!file.open(QIODevice::ReadOnly)) + return {}; + const auto tools = QJsonDocument::fromJson(file.readAll()).toVariant().toMap(); + return {{"path", QDir::fromNativeSeparators(tools["path"].toString())}}; +} + +QMap TestBlackbox::findNodejs(int *status) +{ + QTemporaryDir temp; + QDir::setCurrent(testDataDir + "/find"); + QbsRunParameters params = QStringList() << "-f" << "find-nodejs.qbs"; + params.buildDirectory = temp.path(); + const int res = runQbs(params); + if (status) + *status = res; + QFile file(temp.path() + "/" + relativeProductBuildDir("find-nodejs") + "/nodejs.json"); + if (!file.open(QIODevice::ReadOnly)) + return {}; + const auto tools = QJsonDocument::fromJson(file.readAll()).toVariant().toMap(); + return {{"node", QDir::fromNativeSeparators(tools["node"].toString())}}; +} + +QMap TestBlackbox::findTypeScript(int *status) +{ + QTemporaryDir temp; + QDir::setCurrent(testDataDir + "/find"); + QbsRunParameters params = QStringList() << "-f" << "find-typescript.qbs"; + params.buildDirectory = temp.path(); + const int res = runQbs(params); + if (status) + *status = res; + QFile file(temp.path() + "/" + relativeProductBuildDir("find-typescript") + "/typescript.json"); + if (!file.open(QIODevice::ReadOnly)) + return {}; + const auto tools = QJsonDocument::fromJson(file.readAll()).toVariant().toMap(); + return {{"tsc", QDir::fromNativeSeparators(tools["tsc"].toString())}}; +} + +QString TestBlackbox::findArchiver(const QString &fileName, int *status) +{ + if (fileName == "jar") + return findJdkTools(status).value(fileName); + + QString binary = findExecutable(QStringList(fileName)); + if (binary.isEmpty()) { + const SettingsPtr s = settings(); + Profile p(profileName(), s.get()); + binary = findExecutable(p.value("archiver.command").toStringList()); + } + return binary; +} + +bool TestBlackbox::lexYaccExist() +{ + return !findExecutable(QStringList("lex")).isEmpty() + && !findExecutable(QStringList("yacc")).isEmpty(); +} + +qbs::Version TestBlackbox::bisonVersion() +{ + const auto yaccBinary = findExecutable(QStringList("yacc")); + QProcess process; + process.start(yaccBinary, QStringList() << "--version"); + if (!process.waitForStarted()) + return qbs::Version(); + if (!process.waitForFinished()) + return qbs::Version(); + const auto processStdOut = process.readAllStandardOutput(); + if (processStdOut.isEmpty()) + return qbs::Version(); + const auto line = processStdOut.split('\n')[0]; + const auto words = line.split(' '); + if (words.empty()) + return qbs::Version(); + return qbs::Version::fromString(words.last()); +} + +void TestBlackbox::sevenZip() +{ + QDir::setCurrent(testDataDir + "/archiver"); + QString binary = findArchiver("7z"); + if (binary.isEmpty()) + QSKIP("7zip not found"); + QCOMPARE(runQbs(QbsRunParameters(QStringList() << "modules.archiver.type:7zip")), 0); + const QString outputFile = relativeProductBuildDir("archivable") + "/archivable.7z"; + QVERIFY2(regularFileExists(outputFile), qPrintable(outputFile)); + QProcess listContents; + listContents.start(binary, QStringList() << "l" << outputFile); + QVERIFY2(listContents.waitForStarted(), qPrintable(listContents.errorString())); + QVERIFY2(listContents.waitForFinished(), qPrintable(listContents.errorString())); + QVERIFY2(listContents.exitCode() == 0, listContents.readAllStandardError().constData()); + const QByteArray output = listContents.readAllStandardOutput(); + QVERIFY2(output.contains("2 files"), output.constData()); + QVERIFY2(output.contains("test.txt"), output.constData()); + QVERIFY2(output.contains("archivable.qbs"), output.constData()); +} + +void TestBlackbox::sourceArtifactInInputsFromDependencies() +{ + QDir::setCurrent(testDataDir + "/source-artifact-in-inputs-from-dependencies"); + QCOMPARE(runQbs(), 0); + QFile outFile(relativeProductBuildDir("p") + "/output.txt"); + QVERIFY2(outFile.exists(), qPrintable(outFile.fileName())); + QVERIFY2(outFile.open(QIODevice::ReadOnly), qPrintable(outFile.errorString())); + const QByteArrayList output = outFile.readAll().trimmed().split('\n'); + QCOMPARE(output.size(), 2); + bool header1Found = false; + bool header2Found = false; + for (const QByteArray &line : output) { + const QByteArray &path = line.trimmed(); + if (path == "include1/header.h") + header1Found = true; + else if (path == "include2/header.h") + header2Found = true; + } + QVERIFY(header1Found); + QVERIFY(header2Found); +} + +void TestBlackbox::staticLibWithoutSources() +{ + QDir::setCurrent(testDataDir + "/static-lib-without-sources"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::suspiciousCalls() +{ + const QString projectDir = testDataDir + "/suspicious-calls"; + QDir::setCurrent(projectDir); + rmDirR(relativeBuildDir()); + QFETCH(QString, projectFile); + QbsRunParameters params(QStringList() << "-f" << projectFile); + QCOMPARE(runQbs(params), 0); + QFETCH(QByteArray, expectedWarning); + QVERIFY2(m_qbsStderr.contains(expectedWarning), m_qbsStderr.constData()); +} + +void TestBlackbox::suspiciousCalls_data() +{ + QTest::addColumn("projectFile"); + QTest::addColumn("expectedWarning"); + QTest::newRow("File.copy() in Probe") << "copy-probe.qbs" << QByteArray(); + QTest::newRow("File.copy() during evaluation") << "copy-eval.qbs" << QByteArray("File.copy()"); + QTest::newRow("File.copy() in prepare script") + << "copy-prepare.qbs" << QByteArray("File.copy()"); + QTest::newRow("File.copy() in command") << "copy-command.qbs" << QByteArray(); + QTest::newRow("File.directoryEntries() in Probe") << "direntries-probe.qbs" << QByteArray(); + QTest::newRow("File.directoryEntries() during evaluation") + << "direntries-eval.qbs" << QByteArray("File.directoryEntries()"); + QTest::newRow("File.directoryEntries() in prepare script") + << "direntries-prepare.qbs" << QByteArray(); + 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::distributionIncludePaths() +{ + const QString projectDir = testDataDir + "/distribution-include-paths"; + QDir::setCurrent(projectDir); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::tar() +{ + if (HostOsInfo::hostOs() == HostOsInfo::HostOsWindows) + QSKIP("Beware of the msys tar"); + MacosTarHealer tarHealer; + QDir::setCurrent(testDataDir + "/archiver"); + rmDirR(relativeBuildDir()); + QString binary = findArchiver("tar"); + if (binary.isEmpty()) + QSKIP("tar not found"); + QCOMPARE(runQbs(QbsRunParameters(QStringList() << "modules.archiver.type:tar")), 0); + const QString outputFile = relativeProductBuildDir("archivable") + "/archivable.tar.gz"; + QVERIFY2(regularFileExists(outputFile), qPrintable(outputFile)); + QProcess listContents; + listContents.start(binary, QStringList() << "tf" << outputFile); + QVERIFY2(listContents.waitForStarted(), qPrintable(listContents.errorString())); + QVERIFY2(listContents.waitForFinished(), qPrintable(listContents.errorString())); + QVERIFY2(listContents.exitCode() == 0, listContents.readAllStandardError().constData()); + QFile listFile("list.txt"); + QVERIFY2(listFile.open(QIODevice::ReadOnly), qPrintable(listFile.errorString())); + QCOMPARE(listContents.readAllStandardOutput(), listFile.readAll()); +} + +void TestBlackbox::textTemplate() +{ + QVERIFY(QDir::setCurrent(testDataDir + "/texttemplate")); + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(), 0); + QString outputFilePath = relativeProductBuildDir("one") + "/output.txt"; + QString expectedOutputFilePath = QFINDTESTDATA("expected/output.txt"); + TEXT_FILE_COMPARE(outputFilePath, expectedOutputFilePath); + outputFilePath = relativeProductBuildDir("one") + "/lalala.txt"; + expectedOutputFilePath = QFINDTESTDATA("expected/lalala.txt"); + TEXT_FILE_COMPARE(outputFilePath, expectedOutputFilePath); +} + +static QStringList sortedFileList(const QByteArray &ba) +{ + auto list = QString::fromUtf8(ba).split(QRegExp("[\r\n]"), QBS_SKIP_EMPTY_PARTS); + std::sort(list.begin(), list.end()); + return list; +} + +void TestBlackbox::zip() +{ + QFETCH(QString, binaryName); + int status = 0; + const QString binary = findArchiver(binaryName, &status); + QCOMPARE(status, 0); + if (binary.isEmpty()) + QSKIP("zip tool not found"); + + QDir::setCurrent(testDataDir + "/archiver"); + rmDirR(relativeBuildDir()); + QbsRunParameters params(QStringList() + << "modules.archiver.type:zip" << "modules.archiver.command:" + binary); + QCOMPARE(runQbs(params), 0); + const QString outputFile = relativeProductBuildDir("archivable") + "/archivable.zip"; + QVERIFY2(regularFileExists(outputFile), qPrintable(outputFile)); + QProcess listContents; + if (binaryName == "zip") { + // zipinfo is part of Info-Zip + listContents.start("zipinfo", QStringList() << "-1" << outputFile); + } else { + listContents.start(binary, QStringList() << "tf" << outputFile); + } + QVERIFY2(listContents.waitForStarted(), qPrintable(listContents.errorString())); + QVERIFY2(listContents.waitForFinished(), qPrintable(listContents.errorString())); + QVERIFY2(listContents.exitCode() == 0, listContents.readAllStandardError().constData()); + QFile listFile("list.txt"); + QVERIFY2(listFile.open(QIODevice::ReadOnly), qPrintable(listFile.errorString())); + QCOMPARE(sortedFileList(listContents.readAllStandardOutput()), + sortedFileList(listFile.readAll())); + + // Make sure the module is still loaded when the java/jar fallback is not available + params.command = "resolve"; + params.arguments << "modules.java.jdkPath:/blubb"; + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::zip_data() +{ + QTest::addColumn("binaryName"); + QTest::newRow("zip") << "zip"; + QTest::newRow("jar") << "jar"; +} + +void TestBlackbox::zipInvalid() +{ + QDir::setCurrent(testDataDir + "/archiver"); + rmDirR(relativeBuildDir()); + QbsRunParameters params(QStringList() << "modules.archiver.type:zip" + << "modules.archiver.command:/bin/something"); + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("unrecognized archive tool: 'something'"), m_qbsStderr.constData()); +} + +TestBlackbox::TestBlackbox() : TestBlackboxBase (SRCDIR "/testdata", "blackbox") +{ +} + +void TestBlackbox::addFileTagToGeneratedArtifact() +{ + QDir::setCurrent(testDataDir + "/add-filetag-to-generated-artifact"); + QCOMPARE(runQbs(QStringList("project.enableTagging:true")), 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)); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("project.enableTagging:false"))), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(!regularFileExists(compressedAppFilePath)); +} + +void TestBlackbox::alwaysRun() +{ + QFETCH(QString, projectFile); + + QDir::setCurrent(testDataDir + "/always-run"); + rmDirR(relativeBuildDir()); + QbsRunParameters params("build", QStringList() << "-f" << projectFile); + if (projectFile.contains("transformer")) { + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("removed"), m_qbsStderr.constData()); + return; + } + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("yo")); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("yo")); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "alwaysRun: false", "alwaysRun: true"); + + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("yo")); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("yo")); +} + +void TestBlackbox::alwaysRun_data() +{ + QTest::addColumn("projectFile"); + QTest::newRow("Transformer") << "transformer.qbs"; + QTest::newRow("Rule") << "rule.qbs"; +} + +void TestBlackbox::artifactsMapChangeTracking() +{ + QDir::setCurrent(testDataDir + "/artifacts-map-change-tracking"); + QCOMPARE(runQbs(QStringList{"-p", "TheApp"}), 0); + QVERIFY2(m_qbsStdout.contains("running rule for test.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("creating test.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("linking"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QStringList{"-p", "meta,p"}), 0); + QVERIFY2(m_qbsStdout.contains("printing artifacts"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("test.txt"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("main.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("test.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("TheBinary"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("dummy1"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("dummy2"), m_qbsStdout.constData()); + + // Change name of target binary. Command must be re-run, because the file name of an + // artifact changed. + WAIT_FOR_NEW_TIMESTAMP(); + const QString projectFile("artifacts-map-change-tracking.qbs"); + REPLACE_IN_FILE(projectFile, "TheBinary", "TheNewBinary"); + QCOMPARE(runQbs(QStringList{"-p", "TheApp"}), 0); + + QVERIFY2(!m_qbsStdout.contains("running rule for test.cpp"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("creating test.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("linking"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QStringList{"-p", "meta,p"}), 0); + QVERIFY2(m_qbsStdout.contains("printing artifacts"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("test.txt"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("main.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("test.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("TheNewBinary"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("TheBinary"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("dummy1"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("dummy2"), m_qbsStdout.constData()); + + // Add file tag to generated artifact. Command must be re-run, because it enumerates the keys + // of the artifacts map. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "fileTags: 'cpp'", "fileTags: ['cpp', 'blubb']"); + QCOMPARE(runQbs(QStringList{"-p", "TheApp"}), 0); + QVERIFY2(m_qbsStdout.contains("running rule for test.cpp"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("creating test.cpp"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("linking"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QStringList{"-p", "meta,p"}), 0); + QVERIFY2(m_qbsStdout.contains("printing artifacts"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("test.txt"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("main.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("test.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("TheNewBinary"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("dummy1"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("dummy2"), m_qbsStdout.constData()); + + // Add redundant file tag to generated artifact. Command must not be re-run, because + // the artifacts map has not changed. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "fileTags: ['cpp', 'blubb']", + "fileTags: ['cpp', 'blubb', 'blubb']"); + QCOMPARE(runQbs(QStringList{"-p", "TheApp"}), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running rule for test.cpp"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("creating test.cpp"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("linking"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QStringList{"-p", "meta,p"}), 0); + QVERIFY2(!m_qbsStdout.contains("printing artifacts"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("dummy1"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("dummy2"), m_qbsStdout.constData()); + + // Rebuild the app. Command must not be re-run, because the artifacts map has not changed. + WAIT_FOR_NEW_TIMESTAMP(); + touch("main.cpp"); + QCOMPARE(runQbs(QStringList{"-p", "TheApp"}), 0); + QVERIFY2(!m_qbsStdout.contains("running rule for test.cpp"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("creating test.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("linking"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QStringList{"-p", "meta,p"}), 0); + QVERIFY2(!m_qbsStdout.contains("printing artifacts"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("dummy1"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("dummy2"), m_qbsStdout.constData()); + + // Add source file to app. Command must be re-run, because the artifacts map has changed. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "/* 'test.txt' */", "'test.txt'"); + QCOMPARE(runQbs(QStringList{"-p", "TheApp"}), 0); + QEXPECT_FAIL("", "change tracking could become even more fine-grained", Continue); + QVERIFY2(!m_qbsStdout.contains("running rule for test.cpp"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("creating test.cpp"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("linking"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QStringList{"-p", "meta,p"}), 0); + QVERIFY2(m_qbsStdout.contains("printing artifacts"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("test.txt"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("main.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("test.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("TheNewBinary"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("dummy1"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("dummy2"), m_qbsStdout.constData()); +} + +void TestBlackbox::artifactsMapInvalidation() +{ + const QString projectDir = testDataDir + "/artifacts-map-invalidation"; + QDir::setCurrent(projectDir); + QCOMPARE(runQbs(), 0); + TEXT_FILE_COMPARE(relativeProductBuildDir("p") + "/myfile.out", "file.in"); +} + +void TestBlackbox::artifactsMapRaceCondition() +{ + QDir::setCurrent(testDataDir + "/artifacts-map-race-condition"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::artifactScanning() +{ + const QString projectDir = testDataDir + "/artifact-scanning"; + QDir::setCurrent(projectDir); + 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); + + 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); + + 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); + + 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); + + 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); + + 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); + + 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); + + 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); + + WAIT_FOR_NEW_TIMESTAMP(); + touch("p1.cpp"); + params.command = "resolve"; + params.arguments << "modules.cpp.treatSystemHeadersAsDependencies:true"; + 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); +} + +void TestBlackbox::buildDirectories() +{ + const QString projectDir + = QDir::cleanPath(testDataDir + QLatin1String("/build-directories")); + const QString projectBuildDir = projectDir + '/' + relativeBuildDir(); + QDir::setCurrent(projectDir); + QCOMPARE(runQbs(), 0); + const QStringList outputLines + = QString::fromLocal8Bit(m_qbsStdout.trimmed()).split('\n', QBS_SKIP_EMPTY_PARTS); + QVERIFY2(outputLines.contains(projectDir + '/' + relativeProductBuildDir("p1")), + m_qbsStdout.constData()); + QVERIFY2(outputLines.contains(projectDir + '/' + relativeProductBuildDir("p2")), + m_qbsStdout.constData()); + QVERIFY2(outputLines.contains(projectBuildDir), m_qbsStdout.constData()); + QVERIFY2(outputLines.contains(projectDir), m_qbsStdout.constData()); +} + +void TestBlackbox::buildEnvChange() +{ + QDir::setCurrent(testDataDir + "/buildenv-change"); + QbsRunParameters params; + params.expectFailure = true; + params.arguments << "-k"; + QVERIFY(runQbs(params) != 0); + const bool isMsvc = m_qbsStdout.contains("msvc"); + QVERIFY2(m_qbsStdout.contains("compiling file.c"), m_qbsStdout.constData()); + QString includePath = QDir::currentPath() + "/subdir"; + params.environment.insert("CPLUS_INCLUDE_PATH", includePath); + params.environment.insert("CL", "/I" + includePath); + QVERIFY(runQbs(params) != 0); + params.command = "resolve"; + params.expectFailure = false; + params.arguments.clear(); + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QCOMPARE(m_qbsStdout.contains("compiling file.c"), isMsvc); + includePath = QDir::currentPath() + "/subdir2"; + params.environment.insert("CPLUS_INCLUDE_PATH", includePath); + params.environment.insert("CL", "/I" + includePath); + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QCOMPARE(m_qbsStdout.contains("compiling file.c"), isMsvc); + params.environment = QProcessEnvironment::systemEnvironment(); + QCOMPARE(runQbs(params), 0); + params.command = "build"; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); +} + +void TestBlackbox::buildGraphVersions() +{ + QDir::setCurrent(testDataDir + "/build-graph-versions"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QFile bgFile(relativeBuildGraphFilePath()); + QVERIFY2(bgFile.open(QIODevice::ReadWrite), qPrintable(bgFile.errorString())); + bgFile.write("blubb"); + bgFile.close(); + + // The first attempt at simple rebuilding as well as subsequent ones must fail. + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("Cannot use stored build graph"), m_qbsStderr.constData()); + QVERIFY2(m_qbsStderr.contains("Use the 'resolve' command"), m_qbsStderr.constData()); + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("Cannot use stored build graph"), m_qbsStderr.constData()); + QVERIFY2(m_qbsStderr.contains("Use the 'resolve' command"), m_qbsStderr.constData()); + + // On re-resolving, the error turns into a warning and a new build graph is created. + QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); + QVERIFY2(m_qbsStderr.contains("Cannot use stored build graph"), m_qbsStderr.constData()); + QVERIFY2(!m_qbsStderr.contains("Use the 'resolve' command"), m_qbsStderr.constData()); + + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStderr.contains("Cannot use stored build graph"), m_qbsStderr.constData()); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); +} + +void TestBlackbox::buildVariantDefaults_data() +{ + QTest::addColumn("buildVariant"); + QTest::newRow("default") << QString(); + QTest::newRow("debug") << QStringLiteral("debug"); + QTest::newRow("release") << QStringLiteral("release"); + QTest::newRow("profiling") << QStringLiteral("profiling"); +} + +void TestBlackbox::buildVariantDefaults() +{ + QFETCH(QString, buildVariant); + QDir::setCurrent(testDataDir + "/build-variant-defaults"); + QbsRunParameters params{QStringLiteral("resolve")}; + if (!buildVariant.isEmpty()) + params.arguments << ("modules.qbs.buildVariant:" + buildVariant); + QCOMPARE(runQbs(params), 0); +} + +void TestBlackbox::capnproto() +{ + QFETCH(QString, projectFile); + QDir::setCurrent(testDataDir + "/capnproto"); + rmDirR(relativeBuildDir()); + + QbsRunParameters params{QStringLiteral("resolve"), {QStringLiteral("-f"), projectFile}}; + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + if (m_qbsStdout.contains("capnproto is not present")) + QSKIP("capnproto is not present"); + + params.command.clear(); + QCOMPARE(runQbs(params), 0); +} + +void TestBlackbox::capnproto_data() +{ + QTest::addColumn("projectFile"); + + QTest::newRow("cpp") << QStringLiteral("capnproto_cpp.qbs"); + QTest::newRow("greeter cpp (grpc)") << QStringLiteral("greeter_cpp.qbs"); + QTest::newRow("relative import") << QStringLiteral("capnproto_relative_import.qbs"); + QTest::newRow("absolute import") << QStringLiteral("capnproto_absolute_import.qbs"); +} + +void TestBlackbox::changedFiles_data() +{ + QTest::addColumn("useChangedFilesForInitialBuild"); + QTest::newRow("initial build with changed files") << true; + QTest::newRow("initial build without changed files") << false; +} + +void TestBlackbox::changedFiles() +{ + QFETCH(bool, useChangedFilesForInitialBuild); + + QDir::setCurrent(testDataDir + "/changed-files"); + rmDirR(relativeBuildDir()); + const QString changedFile = QDir::cleanPath(QDir::currentPath() + "/file1.cpp"); + QbsRunParameters params1; + if (useChangedFilesForInitialBuild) + params1 = QbsRunParameters(QStringList("--changed-files") << changedFile); + + // Initial run: Build all files, even though only one of them was marked as changed + // (if --changed-files was used). + QCOMPARE(runQbs(params1), 0); + QCOMPARE(m_qbsStdout.count("compiling"), 3); + QCOMPARE(m_qbsStdout.count("creating"), 3); + + WAIT_FOR_NEW_TIMESTAMP(); + touch(QDir::currentPath() + "/main.cpp"); + + // Now only the file marked as changed must be compiled, even though it hasn't really + // changed and another one has. + QbsRunParameters params2(QStringList("--changed-files") << changedFile); + QCOMPARE(runQbs(params2), 0); + QCOMPARE(m_qbsStdout.count("compiling"), 1); + QCOMPARE(m_qbsStdout.count("creating"), 1); + QVERIFY2(m_qbsStdout.contains("file1.cpp"), m_qbsStdout.constData()); +} + +void TestBlackbox::changedInputsFromDependencies() +{ + QDir::setCurrent(testDataDir + "/changed-inputs-from-dependencies"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("final prepare script"), m_qbsStdout.constData()); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("final prepare script"), m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + touch("input.txt"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("final prepare script"), m_qbsStdout.constData()); +} + +void TestBlackbox::changedRuleInputs() +{ + QDir::setCurrent(testDataDir + "/changed-rule-inputs"); + + // Initial build. + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("generating p1-dummy"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generating p2-dummy"), m_qbsStdout.constData()); + + // Re-build: p1 is always regenerated, and p2 has a dependency on it. + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("generating p1-dummy"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generating p2-dummy"), m_qbsStdout.constData()); + + // Remove the dependency. p2 gets re-generated one last time, because its set of + // inputs changed. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("changed-rule-inputs.qbs", "inputsFromDependencies: \"p1\"", + "inputsFromDependencies: \"p3\""); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("generating p1-dummy"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generating p2-dummy"), m_qbsStdout.constData()); + + // Now the artifacts are no longer connected, and p2 must not get rebuilt anymore. + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("generating p1-dummy"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("generating p2-dummy"), m_qbsStdout.constData()); +} + +void TestBlackbox::changeInDisabledProduct() +{ + QDir::setCurrent(testDataDir + "/change-in-disabled-product"); + QCOMPARE(runQbs(), 0); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("change-in-disabled-product.qbs", "// 'test2.txt'", "'test2.txt'"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::changeInImportedFile() +{ + QDir::setCurrent(testDataDir + "/change-in-imported-file"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("old output"), m_qbsStdout.constData()); + + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("prepare.js", "old output", "new output"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("new output"), m_qbsStdout.constData()); + + WAIT_FOR_NEW_TIMESTAMP(); + touch("prepare.js"); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("output"), m_qbsStdout.constData()); +} + +void TestBlackbox::changeTrackingAndMultiplexing() +{ + QDir::setCurrent(testDataDir + "/change-tracking-and-multiplexing"); + QCOMPARE(runQbs(QStringList("modules.cpp.staticLibraryPrefix:prefix1")), 0); + QCOMPARE(m_qbsStdout.count("compiling lib.cpp"), 2); + QCOMPARE(m_qbsStdout.count("creating prefix1l"), 2); + QCOMPARE(runQbs(QbsRunParameters("resolve", + QStringList("modules.cpp.staticLibraryPrefix:prefix2"))), 0); + QCOMPARE(runQbs(), 0); + QCOMPARE(m_qbsStdout.count("compiling lib.cpp"), 0); + QCOMPARE(m_qbsStdout.count("creating prefix2l"), 2); +} + +static QJsonObject findByName(const QJsonArray &objects, const QString &name) +{ + for (const QJsonValue &v : objects) { + if (!v.isObject()) + continue; + QJsonObject obj = v.toObject(); + const QString objName = obj.value(QStringLiteral("name")).toString(); + if (objName == name) + return obj; + } + return {}; +} + +static void readDepsOutput(const QString &depsFilePath, QJsonDocument &jsonDocument) +{ + jsonDocument = QJsonDocument(); + QFile depsFile(depsFilePath); + QVERIFY2(depsFile.open(QFile::ReadOnly), qPrintable(depsFile.errorString())); + QJsonParseError jsonerror; + jsonDocument = QJsonDocument::fromJson(depsFile.readAll(), &jsonerror); + if (jsonerror.error != QJsonParseError::NoError) { + qDebug() << jsonerror.errorString(); + QFAIL("JSON parsing failed."); + } +} + +void TestBlackbox::dependenciesProperty() +{ + QDir::setCurrent(testDataDir + QLatin1String("/dependenciesProperty")); + QCOMPARE(runQbs(), 0); + const QString depsFile(relativeProductBuildDir("product1") + "/product1.deps"); + QJsonDocument jsondoc; + readDepsOutput(depsFile, jsondoc); + QVERIFY(jsondoc.isArray()); + QJsonArray dependencies = jsondoc.array(); + QCOMPARE(dependencies.size(), 2); + QJsonObject product2 = findByName(dependencies, QStringLiteral("product2")); + QJsonArray product2_type = product2.value(QStringLiteral("type")).toArray(); + QCOMPARE(product2_type.size(), 1); + QCOMPARE(product2_type.first().toString(), QLatin1String("application")); + QCOMPARE(product2.value(QLatin1String("narf")).toString(), QLatin1String("zort")); + QJsonArray product2_cppArtifacts + = product2.value("artifacts").toObject().value("cpp").toArray(); + QCOMPARE(product2_cppArtifacts.size(), 1); + QJsonArray product2_deps = product2.value(QStringLiteral("dependencies")).toArray(); + QVERIFY(!product2_deps.empty()); + QJsonObject product2_qbs = findByName(product2_deps, QStringLiteral("qbs")); + QVERIFY(!product2_qbs.empty()); + QJsonObject product2_cpp = findByName(product2_deps, QStringLiteral("cpp")); + QJsonArray product2_cpp_defines = product2_cpp.value(QLatin1String("defines")).toArray(); + QCOMPARE(product2_cpp_defines.size(), 1); + QCOMPARE(product2_cpp_defines.first().toString(), QLatin1String("SMURF")); + QJsonArray cpp_dependencies = product2_cpp.value("dependencies").toArray(); + QVERIFY(!cpp_dependencies.isEmpty()); + int qbsCount = 0; + for (const auto &dep : cpp_dependencies) { + if (dep.toObject().value("name").toString() == "qbs") + ++qbsCount; + } + QCOMPARE(qbsCount, 1); + + // Add new dependency, check that command is re-run. + const QString projectFile("dependenciesProperty.qbs"); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "// Depends { name: 'newDependency' }", + "Depends { name: 'newDependency' }"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("generate product1.deps"), m_qbsStdout.constData()); + readDepsOutput(depsFile, jsondoc); + dependencies = jsondoc.array(); + QCOMPARE(dependencies.size(), 3); + + // Add new Depends item that does not actually introduce a new dependency, check + // that command is not re-run. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "// Depends { name: 'product2' }", "Depends { name: 'product2' }"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("generate product1.deps"), m_qbsStdout.constData()); + readDepsOutput(depsFile, jsondoc); + dependencies = jsondoc.array(); + QCOMPARE(dependencies.size(), 3); + + // Change property of dependency, check that command is re-run. + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList{"products.product2.narf:zortofsky"})), + 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("compiling product2.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generate product1.deps"), m_qbsStdout.constData()); + readDepsOutput(depsFile, jsondoc); + dependencies = jsondoc.array(); + QCOMPARE(dependencies.size(), 3); + product2 = findByName(dependencies, QStringLiteral("product2")); + QCOMPARE(product2.value(QLatin1String("narf")).toString(), QLatin1String("zortofsky")); + + // Change module property of dependency, check that command is re-run. + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList{"products.product2.narf:zortofsky", + "products.product2.cpp.defines:DIGEDAG"})), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling product2.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generate product1.deps"), m_qbsStdout.constData()); + readDepsOutput(depsFile, jsondoc); + dependencies = jsondoc.array(); + QCOMPARE(dependencies.size(), 3); + product2 = findByName(dependencies, QStringLiteral("product2")); + product2_deps = product2.value(QStringLiteral("dependencies")).toArray(); + product2_cpp = findByName(product2_deps, QStringLiteral("cpp")); + product2_cpp_defines = product2_cpp.value(QStringLiteral("defines")).toArray(); + QCOMPARE(product2_cpp_defines.size(), 1); + QCOMPARE(product2_cpp_defines.first().toString(), QLatin1String("DIGEDAG")); +} + +void TestBlackbox::dependencyScanningLoop() +{ + QDir::setCurrent(testDataDir + "/dependency-scanning-loop"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); +} + +void TestBlackbox::deprecatedProperty() +{ + QDir::setCurrent(testDataDir + "/deprecated-property"); + 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()); + 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()); +} + +void TestBlackbox::disappearedProfile() +{ + QDir::setCurrent(testDataDir + "/disappeared-profile"); + QbsRunParameters resolveParams; + + // First, we need to fail, because we don't tell qbs where the module is. + resolveParams.expectFailure = true; + QVERIFY(runQbs(resolveParams) != 0); + + // Now we set up a profile with all the necessary information, and qbs succeeds. + qbs::Settings settings(QDir::currentPath() + "/settings-dir"); + qbs::Profile profile("p", &settings); + profile.setValue("m.p1", "p1 from profile"); + profile.setValue("m.p2", "p2 from profile"); + profile.setValue("preferences.qbsSearchPaths", + QStringList({QDir::currentPath() + "/modules-dir"})); + settings.sync(); + resolveParams.command = "resolve"; + resolveParams.expectFailure = false; + resolveParams.settingsDir = settings.baseDirectory(); + resolveParams.profile = profile.name(); + QCOMPARE(runQbs(resolveParams), 0); + + // Now we change a property in the profile, but because we don't use the "resolve" command, + // the old profile contents stored in the build graph are used. + profile.setValue("m.p2", "p2 new from profile"); + settings.sync(); + QbsRunParameters buildParams; + buildParams.profile.clear(); + QCOMPARE(runQbs(buildParams), 0); + QVERIFY2(m_qbsStdout.contains("Creating dummy1.txt with p1 from profile"), + m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("Creating dummy2.txt with p2 from profile"), + m_qbsStdout.constData()); + + // Now we do use the "resolve" command, so the new property value is taken into account. + QCOMPARE(runQbs(resolveParams), 0); + QCOMPARE(runQbs(buildParams), 0); + QVERIFY2(!m_qbsStdout.contains("Creating dummy1.txt"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("Creating dummy2.txt with p2 new from profile"), + m_qbsStdout.constData()); + + // Now we change the profile again without a "resolve" command. However, this time we + // force re-resolving indirectly by changing a project file. The updated property value + // must still not be taken into account. + profile.setValue("m.p1", "p1 new from profile"); + settings.sync(); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("modules-dir/modules/m/m.qbs", "property string p1", + "property string p1: 'p1 from module'"); + QCOMPARE(runQbs(buildParams), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("Creating dummy1.txt"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("Creating dummy2.txt"), m_qbsStdout.constData()); + + // Now we run the "resolve" command without giving the necessary settings path to find + // the profile. + resolveParams.expectFailure = true; + resolveParams.settingsDir.clear(); + resolveParams.profile.clear(); + QVERIFY(runQbs(resolveParams) != 0); + 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("discardString"); + QTest::addColumn("symbolPresent"); + + QTest::newRow("discard") << QString("true") << false; + QTest::newRow("don't discard") << QString("false") << true; + QTest::newRow("default") << QString() << true; +} + +void TestBlackbox::driverLinkerFlags() +{ + QDir::setCurrent(testDataDir + QLatin1String("/driver-linker-flags")); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0); + if (!m_qbsStdout.contains("toolchain is GCC-like")) + QSKIP("Test applies on GCC-like toolchains only"); + QFETCH(QString, linkerMode); + QFETCH(bool, expectDriverOption); + const QString linkerModeArg = "modules.cpp.linkerMode:" + linkerMode; + QCOMPARE(runQbs(QStringList({"-n", "--command-echo-mode", "command-line", linkerModeArg})), 0); + const QByteArray driverArg = "-nostartfiles"; + const QByteArrayList output = m_qbsStdout.split('\n'); + QByteArray compileLine; + QByteArray linkLine; + for (const QByteArray &line : output) { + if (line.contains(" -c ")) + compileLine = line; + else if (line.contains("main.cpp.o")) + linkLine = line; + } + QVERIFY(!compileLine.isEmpty()); + QVERIFY(!linkLine.isEmpty()); + QVERIFY2(!compileLine.contains(driverArg), compileLine.constData()); + QVERIFY2(linkLine.contains(driverArg) == expectDriverOption, linkLine.constData()); +} + +void TestBlackbox::driverLinkerFlags_data() +{ + QTest::addColumn("linkerMode"); + QTest::addColumn("expectDriverOption"); + + QTest::newRow("link using compiler driver") << "automatic" << true; + QTest::newRow("link using linker") << "manual" << false; +} + +void TestBlackbox::dynamicLibraryInModule() +{ + QDir::setCurrent(testDataDir + "/dynamic-library-in-module"); + const QString installRootSpec = QString("qbs.installRoot:") + QDir::currentPath(); + QbsRunParameters libParams(QStringList({"-f", "thelibs.qbs", installRootSpec})); + libParams.buildDirectory = "libbuild"; + QCOMPARE(runQbs(libParams), 0); + QbsRunParameters appParams("build", QStringList({"-f", "theapp.qbs", installRootSpec})); + appParams.buildDirectory = "appbuild"; + QCOMPARE(runQbs(appParams), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + appParams.command = "run"; + QCOMPARE(runQbs(appParams), 0); + QVERIFY2(m_qbsStdout.contains("Hello from thelib"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("Hello from theotherlib"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("thirdlib"), m_qbsStdout.constData()); + QVERIFY(!QFileInfo::exists(appParams.buildDirectory + '/' + + qbs::InstallOptions::defaultInstallRoot())); +} + +void TestBlackbox::symlinkRemoval() +{ + if (HostOsInfo::isWindowsHost()) + QSKIP("No symlink support on Windows."); + QDir::setCurrent(testDataDir + "/symlink-removal"); + QVERIFY(QDir::current().mkdir("dir1")); + QVERIFY(QDir::current().mkdir("dir2")); + QVERIFY(QFile::link("dir2", "dir1/broken-link")); + QVERIFY(QFile::link(QFileInfo("dir2").absoluteFilePath(), "dir1/valid-link-to-dir")); + QVERIFY(QFile::link(QFileInfo("symlink-removal.qbs").absoluteFilePath(), + "dir1/valid-link-to-file")); + QCOMPARE(runQbs(), 0); + QVERIFY(!QFile::exists("dir1")); + QVERIFY(QFile::exists("dir2")); + QVERIFY(QFile::exists("symlink-removal.qbs")); +} + +void TestBlackbox::usingsAsSoleInputsNonMultiplexed() +{ + QDir::setCurrent(testDataDir + QLatin1String("/usings-as-sole-inputs-non-multiplexed")); + QCOMPARE(runQbs(), 0); + const QString p3BuildDir = relativeProductBuildDir("p3"); + QVERIFY(regularFileExists(p3BuildDir + "/custom1.out.plus")); + 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("multiplex"); + QTest::addColumn("expectFailure"); + QTest::addColumn("variantSuffix"); + QTest::addColumn("buildVariant"); + QTest::addColumn("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)); + + const auto getRepoStateFromApp = [this] { + const int startIndex = m_qbsStdout.indexOf("__"); + if (startIndex == -1) + return QByteArray(); + const int endIndex = m_qbsStdout.indexOf("__", startIndex + 2); + if (endIndex == -1) + return QByteArray(); + return m_qbsStdout.mid(startIndex + 2, endIndex - startIndex - 2); + }; + + QCOMPARE(runQbs({"resolve", {"-f", repoDir.path()}}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + // Run without git metadata. + QbsRunParameters params("run", QStringList{"-f", repoDir.path()}); + params.workingDir = repoDir.path() + "/.."; + params.buildDirectory = repoDir.path(); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("generating my-repo-state.h"), m_qbsStderr.constData()); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData()); + QByteArray newRepoState = getRepoStateFromApp(); + QCOMPARE(newRepoState, QByteArray("none")); + QByteArray oldRepoState = newRepoState; + + // Initial commit + git.start(gitFilePath, QStringList({"add", "main.cpp"})); + QVERIFY(waitForProcessSuccess(git)); + git.start(gitFilePath, QStringList({"commit", "-m", "initial commit"})); + QVERIFY(waitForProcessSuccess(git)); + + // Run with git metadata. + WAIT_FOR_NEW_TIMESTAMP(); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("generating my-repo-state.h"), m_qbsStderr.constData()); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData()); + newRepoState = getRepoStateFromApp(); + QVERIFY(oldRepoState != newRepoState); + oldRepoState = newRepoState; + + // Run with no changes. + QCOMPARE(runQbs(params), 0); + QVERIFY2(!m_qbsStdout.contains("generating my-repo-state.h"), m_qbsStderr.constData()); + QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData()); + newRepoState = getRepoStateFromApp(); + QCOMPARE(oldRepoState, newRepoState); + + // Run with changed source file. + WAIT_FOR_NEW_TIMESTAMP(); + touch("main.cpp"); + QCOMPARE(runQbs(params), 0); + QVERIFY2(!m_qbsStdout.contains("generating my-repo-state.h"), m_qbsStderr.constData()); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData()); + newRepoState = getRepoStateFromApp(); + QCOMPARE(oldRepoState, newRepoState); + + // 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(params), 0); + QVERIFY2(m_qbsStdout.contains("generating my-repo-state.h"), m_qbsStderr.constData()); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData()); + newRepoState = getRepoStateFromApp(); + QVERIFY(oldRepoState != newRepoState); +} + +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_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + if (m_qbsStderr.contains("svn too old")) + QSKIP("svn too old"); + QCOMPARE(retval, 0); + QVERIFY2(m_qbsStdout.contains("__1__"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QbsRunParameters("run")), 0); + QVERIFY2(!m_qbsStdout.contains("generating my-repo-state.h"), m_qbsStderr.constData()); + QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData()); + QVERIFY2(m_qbsStdout.contains("__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 my-repo-state.h"), m_qbsStderr.constData()); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStderr.constData()); + QVERIFY2(m_qbsStdout.contains("__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("__2__"), m_qbsStdout.constData()); +} + +void TestBlackbox::versionCheck() +{ + QDir::setCurrent(testDataDir + "/versioncheck"); + QFETCH(QString, requestedMinVersion); + QFETCH(QString, requestedMaxVersion); + QFETCH(QString, actualVersion); + QFETCH(QString, errorMessage); + QbsRunParameters params; + params.expectFailure = !errorMessage.isEmpty(); + params.arguments << "-n" + << ("products.versioncheck.requestedMinVersion:'" + requestedMinVersion + "'") + << ("products.versioncheck.requestedMaxVersion:'" + requestedMaxVersion + "'") + << ("modules.lower.version:'" + actualVersion + "'"); + QCOMPARE(runQbs(params) == 0, errorMessage.isEmpty()); + if (params.expectFailure) + QVERIFY2(QString(m_qbsStderr).contains(errorMessage), m_qbsStderr.constData()); +} + +void TestBlackbox::versionCheck_data() +{ + QTest::addColumn("requestedMinVersion"); + QTest::addColumn("requestedMaxVersion"); + QTest::addColumn("actualVersion"); + QTest::addColumn("errorMessage"); + + QTest::newRow("ok1") << "1.0" << "1.1" << "1.0" << QString(); + QTest::newRow("ok2") << "1.0" << "2.0.1" << "2.0" << QString(); + QTest::newRow("ok3") << "1.0" << "2.5" << "1.5" << QString(); + QTest::newRow("ok3") << "1.0" << "2.0" << "1.99" << QString(); + QTest::newRow("bad1") << "2.0" << "2.1" << "1.5" << "needs to be at least"; + QTest::newRow("bad2") << "2.0" << "3.0" << "1.5" << "needs to be at least"; + QTest::newRow("bad3") << "2.0" << "3.0" << "3.5" << "needs to be lower than"; + QTest::newRow("bad4") << "2.0" << "3.0" << "3.0" << "needs to be lower than"; + + // "bad" because the "higer" module has stronger requirements. + QTest::newRow("bad5") << "0.1" << "0.9" << "0.5" << "Impossible version constraint"; +} + +void TestBlackbox::versionScript() +{ + const SettingsPtr s = settings(); + Profile buildProfile(profileName(), s.get()); + QStringList toolchain = profileToolchain(buildProfile); + if (!toolchain.contains("gcc") || targetOs() != HostOsInfo::HostOsLinux) + QSKIP("version script test only applies to Linux"); + QDir::setCurrent(testDataDir + "/versionscript"); + QCOMPARE(runQbs(QbsRunParameters(QStringList("-q") + << ("qbs.installRoot:" + QDir::currentPath()))), 0); + const QString output = QString::fromLocal8Bit(m_qbsStderr); + 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() + "/libversionscript.so")); + QVERIFY(nm.waitForStarted()); + QVERIFY(nm.waitForFinished()); + const QByteArray allSymbols = nm.readAllStandardOutput(); + QCOMPARE(nm.exitCode(), 0); + QVERIFY2(allSymbols.contains("dummyLocal"), allSymbols.constData()); + QVERIFY2(allSymbols.contains("dummyGlobal"), allSymbols.constData()); + nm.start(nmPath, QStringList() << "-g" << QDir::currentPath() + "/libversionscript.so"); + QVERIFY(nm.waitForStarted()); + QVERIFY(nm.waitForFinished()); + const QByteArray globalSymbols = nm.readAllStandardOutput(); + QCOMPARE(nm.exitCode(), 0); + QVERIFY2(!globalSymbols.contains("dummyLocal"), allSymbols.constData()); + QVERIFY2(globalSymbols.contains("dummyGlobal"), allSymbols.constData()); +} + +void TestBlackbox::wholeArchive() +{ + QDir::setCurrent(testDataDir + "/whole-archive"); + QFETCH(QString, wholeArchiveString); + QFETCH(bool, ruleInvalidationExpected); + QFETCH(bool, dllLinkingExpected); + const QbsRunParameters resolveParams("resolve", + QStringList("products.dynamiclib.linkWholeArchive:" + wholeArchiveString)); + QCOMPARE(runQbs(QbsRunParameters(resolveParams)), 0); + const bool linkerSupportsWholeArchive = m_qbsStdout.contains("can link whole archives"); + const bool linkerDoesNotSupportWholeArchive + = m_qbsStdout.contains("cannot link whole archives"); + QVERIFY(linkerSupportsWholeArchive != linkerDoesNotSupportWholeArchive); + + QCOMPARE(runQbs(QbsRunParameters(QStringList({ "-vvp", "dynamiclib" }))), 0); + const bool wholeArchive = !wholeArchiveString.isEmpty(); + const bool outdatedVisualStudio = wholeArchive && linkerDoesNotSupportWholeArchive; + const QByteArray invalidationOutput + = "Value for property 'staticlib 1:cpp.linkWholeArchive' has changed."; + if (!outdatedVisualStudio) + QCOMPARE(m_qbsStderr.contains(invalidationOutput), ruleInvalidationExpected); + QCOMPARE(m_qbsStdout.contains("linking"), dllLinkingExpected && !outdatedVisualStudio); + QbsRunParameters buildParams(QStringList("-p")); + buildParams.expectFailure = !wholeArchive || outdatedVisualStudio; + buildParams.arguments << "app1"; + QCOMPARE(runQbs(QbsRunParameters(buildParams)) == 0, wholeArchive && !outdatedVisualStudio); + buildParams.arguments.last() = "app2"; + QCOMPARE(runQbs(QbsRunParameters(buildParams)) == 0, wholeArchive && !outdatedVisualStudio); + buildParams.arguments.last() = "app4"; + QCOMPARE(runQbs(QbsRunParameters(buildParams)) == 0, wholeArchive && !outdatedVisualStudio); + buildParams.arguments.last() = "app3"; + buildParams.expectFailure = true; + QVERIFY(runQbs(QbsRunParameters(buildParams)) != 0); +} + +void TestBlackbox::wholeArchive_data() +{ + QTest::addColumn("wholeArchiveString"); + QTest::addColumn("ruleInvalidationExpected"); + QTest::addColumn("dllLinkingExpected"); + QTest::newRow("link normally") << QString() << false << true; + QTest::newRow("link whole archive") << "true" << true << true; + QTest::newRow("link whole archive again") << "notfalse" << false << false; +} + +static bool symlinkExists(const QString &linkFilePath) +{ + return QFileInfo(linkFilePath).isSymLink(); +} + +void TestBlackbox::clean() +{ + const QString appObjectFilePath = relativeProductBuildDir("app") + '/' + inputDirHash(".") + + objectFileName("/main.cpp", profileName()); + const QString appExeFilePath = relativeExecutableFilePath("app"); + const QString depObjectFilePath = relativeProductBuildDir("dep") + '/' + inputDirHash(".") + + objectFileName("/dep.cpp", profileName()); + const QString depLibBase = relativeProductBuildDir("dep") + + '/' + QBS_HOST_DYNAMICLIB_PREFIX + "dep"; + QString depLibFilePath; + QStringList symlinks; + if (qbs::Internal::HostOsInfo::isMacosHost()) { + depLibFilePath = depLibBase + ".1.1.0" + QBS_HOST_DYNAMICLIB_SUFFIX; + symlinks << depLibBase + ".1.1" + QBS_HOST_DYNAMICLIB_SUFFIX + << depLibBase + ".1" + QBS_HOST_DYNAMICLIB_SUFFIX + << depLibBase + QBS_HOST_DYNAMICLIB_SUFFIX; + } else if (qbs::Internal::HostOsInfo::isAnyUnixHost()) { + depLibFilePath = depLibBase + QBS_HOST_DYNAMICLIB_SUFFIX + ".1.1.0"; + symlinks << depLibBase + QBS_HOST_DYNAMICLIB_SUFFIX + ".1.1" + << depLibBase + QBS_HOST_DYNAMICLIB_SUFFIX + ".1" + << depLibBase + QBS_HOST_DYNAMICLIB_SUFFIX; + } else { + depLibFilePath = depLibBase + QBS_HOST_DYNAMICLIB_SUFFIX; + } + + 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)); + QVERIFY(regularFileExists(appExeFilePath)); + QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("clean"))), 0); + QVERIFY(!QFile(appObjectFilePath).exists()); + QVERIFY(!QFile(appExeFilePath).exists()); + QVERIFY(!QFile(depObjectFilePath).exists()); + QVERIFY(!QFile(depLibFilePath).exists()); + for (const QString &symLink : qAsConst(symlinks)) + QVERIFY2(!symlinkExists(symLink), qPrintable(symLink)); + + // Remove all, with a forced re-resolve in between. + // This checks that rescuable artifacts are also removed. + QCOMPARE(runQbs(QbsRunParameters("resolve", + QStringList() << "modules.cpp.optimization:none")), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); + QCOMPARE(runQbs(QbsRunParameters("resolve", + QStringList() << "modules.cpp.optimization:fast")), 0); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); + QCOMPARE(runQbs(QbsRunParameters("clean")), 0); + QVERIFY(!QFile(appObjectFilePath).exists()); + QVERIFY(!QFile(appExeFilePath).exists()); + QVERIFY(!QFile(depObjectFilePath).exists()); + QVERIFY(!QFile(depLibFilePath).exists()); + for (const QString &symLink : qAsConst(symlinks)) + QVERIFY2(!symlinkExists(symLink), qPrintable(symLink)); + + // Dry run. + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); + QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("clean"), QStringList("-n"))), 0); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); + QVERIFY(regularFileExists(depObjectFilePath)); + QVERIFY(regularFileExists(depLibFilePath)); + for (const QString &symLink : qAsConst(symlinks)) + QVERIFY2(symlinkExists(symLink), qPrintable(symLink)); + + // Product-wise, dependency only. + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); + QVERIFY(regularFileExists(depObjectFilePath)); + QVERIFY(regularFileExists(depLibFilePath)); + QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("clean"), QStringList("-p") << "dep")), 0); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); + QVERIFY(!QFile(depObjectFilePath).exists()); + QVERIFY(!QFile(depLibFilePath).exists()); + for (const QString &symLink : qAsConst(symlinks)) + QVERIFY2(!symlinkExists(symLink), qPrintable(symLink)); + + // Product-wise, dependent product only. + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(appObjectFilePath)); + QVERIFY(regularFileExists(appExeFilePath)); + QVERIFY(regularFileExists(depObjectFilePath)); + QVERIFY(regularFileExists(depLibFilePath)); + QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("clean"), QStringList("-p") << "app")), 0); + QVERIFY(!QFile(appObjectFilePath).exists()); + QVERIFY(!QFile(appExeFilePath).exists()); + QVERIFY(regularFileExists(depObjectFilePath)); + QVERIFY(regularFileExists(depLibFilePath)); + for (const QString &symLink : qAsConst(symlinks)) + QVERIFY2(symlinkExists(symLink), qPrintable(symLink)); +} + +void TestBlackbox::concurrentExecutor() +{ + QDir::setCurrent(testDataDir + "/concurrent-executor"); + QCOMPARE(runQbs(QStringList() << "-j" << "2"), 0); + QVERIFY2(!m_qbsStderr.contains("ASSERT"), m_qbsStderr.constData()); +} + +void TestBlackbox::conditionalExport() +{ + QDir::setCurrent(testDataDir + "/conditional-export"); + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("missing define"), m_qbsStderr.constData()); + + params.expectFailure = false; + params.arguments << "project.enableExport:true"; + params.command = "resolve"; + QCOMPARE(runQbs(params), 0); + params.command = "build"; + QCOMPARE(runQbs(params), 0); +} + +void TestBlackbox::conditionalFileTagger() +{ + QDir::setCurrent(testDataDir + "/conditional-filetagger"); + QbsRunParameters params(QStringList("products.theApp.enableTagger:false")); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compiling")); + params.arguments = QStringList("products.theApp.enableTagger:true"); + params.command = "resolve"; + QCOMPARE(runQbs(params), 0); + params.command = "build"; + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("compiling")); +} + +void TestBlackbox::configure() +{ + QDir::setCurrent(testDataDir + "/configure"); + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + 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"); + QbsRunParameters params(QStringList() << "-n"); + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("Conflicting artifacts"), m_qbsStderr.constData()); +} + +void TestBlackbox::cxxLanguageVersion() +{ + QDir::setCurrent(testDataDir + "/cxx-language-version"); + rmDirR(relativeBuildDir()); + QFETCH(QString, version); + QFETCH(QVariantMap, requiredFlags); + QFETCH(QVariantMap, forbiddenFlags); + QbsRunParameters resolveParams; + resolveParams.command = "resolve"; + resolveParams.arguments << "--force-probe-execution"; + resolveParams.arguments << "modules.cpp.useLanguageVersionFallback:true"; + if (!version.isEmpty()) + resolveParams.arguments << ("modules.cpp.cxxLanguageVersion:" + version); + QCOMPARE(runQbs(resolveParams), 0); + QString mapKey; + if (version == "c++17" && m_qbsStdout.contains("is even newer MSVC: true")) + mapKey = "msvc-brandnew"; + else if (m_qbsStdout.contains("is newer MSVC: true")) + mapKey = "msvc-new"; + else if (m_qbsStdout.contains("is older MSVC: true")) + mapKey = "msvc_old"; + else if (m_qbsStdout.contains("is GCC: true")) + mapKey = "gcc"; + QVERIFY2(!mapKey.isEmpty(), m_qbsStdout.constData()); + QbsRunParameters buildParams; + buildParams.expectFailure = mapKey == "gcc" && (version == "c++17" || version == "c++21"); + buildParams.arguments = QStringList({"--command-echo-mode", "command-line"}); + const int retVal = runQbs(buildParams); + if (!buildParams.expectFailure) + QCOMPARE(retVal, 0); + const QString requiredFlag = requiredFlags.value(mapKey).toString(); + if (!requiredFlag.isEmpty()) + QVERIFY2(m_qbsStdout.contains(requiredFlag.toLocal8Bit()), m_qbsStdout.constData()); + const QString forbiddenFlag = forbiddenFlags.value(mapKey).toString(); + if (!forbiddenFlag.isEmpty()) + QVERIFY2(!m_qbsStdout.contains(forbiddenFlag.toLocal8Bit()), m_qbsStdout.constData()); +} + +void TestBlackbox::cxxLanguageVersion_data() +{ + QTest::addColumn("version"); + QTest::addColumn("requiredFlags"); + QTest::addColumn("forbiddenFlags"); + + QTest::newRow("C++98") + << QString("c++98") + << QVariantMap({std::make_pair(QString("gcc"), QString("-std=c++98"))}) + << QVariantMap({std::make_pair(QString("msvc-old"), QString("/std:")), + std::make_pair(QString("msvc-new"), QString("/std:"))}); + QTest::newRow("C++11") + << QString("c++11") + << QVariantMap({std::make_pair(QString("gcc"), QString("-std=c++0x"))}) + << QVariantMap({std::make_pair(QString("msvc-old"), QString("/std:")), + std::make_pair(QString("msvc-new"), QString("/std:"))}); + QTest::newRow("C++14") + << QString("c++14") + << QVariantMap({std::make_pair(QString("gcc"), QString("-std=c++1y")), + std::make_pair(QString("msvc-new"), QString("/std:c++14")) + }) + << QVariantMap({std::make_pair(QString("msvc-old"), QString("/std:"))}); + QTest::newRow("C++17") + << QString("c++17") + << QVariantMap({std::make_pair(QString("gcc"), QString("-std=c++1z")), + std::make_pair(QString("msvc-new"), QString("/std:c++latest")), + std::make_pair(QString("msvc-brandnew"), QString("/std:c++17")) + }) + << QVariantMap({std::make_pair(QString("msvc-old"), QString("/std:"))}); + QTest::newRow("C++21") + << QString("c++21") + << QVariantMap({std::make_pair(QString("gcc"), QString("-std=c++21")), + std::make_pair(QString("msvc-new"), QString("/std:c++latest")) + }) + << QVariantMap({std::make_pair(QString("msvc-old"), QString("/std:"))}); + QTest::newRow("default") + << QString() + << QVariantMap() + << QVariantMap({std::make_pair(QString("gcc"), QString("-std=")), + std::make_pair(QString("msvc-old"), QString("/std:")), + std::make_pair(QString("msvc-new"), QString("/std:"))}); +} + +void TestBlackbox::conanfileProbe() +{ + QString executable = findExecutable({"conan"}); + if (executable.isEmpty()) + QSKIP("conan is not installed or not available in PATH."); + + // We first build a dummy package testlib and use that as dependency + // in the testapp package. + QDir::setCurrent(testDataDir + "/conanfile-probe/testlib"); + QStringList arguments { "create", "-o", "opt=True", "-s", "os=AIX", ".", + "testlib/1.2.3@qbs/testing" }; + QProcess conan; + conan.start(executable, arguments); + QVERIFY(waitForProcessSuccess(conan)); + + QDir::setCurrent(testDataDir + "/conanfile-probe/testapp"); + QCOMPARE(runQbs(QbsRunParameters("resolve", {"--force-probe-execution"})), 0); + + QFile file(relativeBuildDir() + "/results.json"); + QVERIFY(file.open(QIODevice::ReadOnly)); + QVariantMap actualResults = QJsonDocument::fromJson(file.readAll()).toVariant().toMap(); + const auto generatedFilesPath = actualResults.take("generatedFilesPath").toString(); + // We want to make sure that generatedFilesPath is under the project directory, + // but we don't care about the actual name. + QVERIFY(directoryExists(relativeBuildDir() + "/genconan/" + + QFileInfo(generatedFilesPath).baseName())); + + const QVariantMap expectedResults = { + { "json", "TESTLIB_ENV_VAL" }, + { "dependencies", QVariantList{"testlib1", "testlib2"} }, + }; + QCOMPARE(actualResults, expectedResults); +} + +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"); + if (QFile::exists("work")) + rmDirR("work"); + QDir().mkdir("work"); + ccp("before", "work"); + QDir::setCurrent(testDataDir + "/renameDependency/work"); + QCOMPARE(runQbs(), 0); + + WAIT_FOR_NEW_TIMESTAMP(); + QFile::remove("lib.h"); + QFile::remove("lib.cpp"); + ccp("../after", "."); + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY(m_qbsStdout.contains("compiling main.cpp")); +} + +void TestBlackbox::separateDebugInfo() +{ + QDir::setCurrent(testDataDir + "/separate-debug-info"); + QCOMPARE(runQbs(QbsRunParameters(QStringList("qbs.debugInformation:true"))), 0); + const bool isWindows = m_qbsStdout.contains("is windows: yes"); + const bool isNotWindows = m_qbsStdout.contains("is windows: no"); + QVERIFY(isWindows != isNotWindows); + const bool isMacos = m_qbsStdout.contains("is macos: yes"); + const bool isNotMacos = m_qbsStdout.contains("is macos: no"); + QVERIFY(isMacos != isNotMacos); + const bool isDarwin = m_qbsStdout.contains("is darwin: yes"); + const bool isNotDarwin = m_qbsStdout.contains("is darwin: no"); + QVERIFY(isDarwin != isNotDarwin); + + const SettingsPtr s = settings(); + Profile buildProfile(profileName(), s.get()); + QStringList toolchain = profileToolchain(buildProfile); + if (isDarwin) { + QVERIFY(directoryExists(relativeProductBuildDir("app1") + "/app1.app.dSYM")); + QVERIFY(regularFileExists(relativeProductBuildDir("app1") + + "/app1.app.dSYM/Contents/Info.plist")); + QVERIFY(regularFileExists(relativeProductBuildDir("app1") + + "/app1.app.dSYM/Contents/Resources/DWARF/app1")); + QCOMPARE(QDir(relativeProductBuildDir("app1") + + "/app1.app.dSYM/Contents/Resources/DWARF") + .entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1); + QVERIFY(!QFile::exists(relativeProductBuildDir("app2") + "/app2.app.dSYM")); + QVERIFY(!QFile::exists(relativeProductBuildDir("app3") + "/app3.app.dSYM")); + if (isMacos) { + QVERIFY(regularFileExists(relativeProductBuildDir("app3") + + "/app3.app/Contents/MacOS/app3.dwarf")); + } else { + QVERIFY(regularFileExists(relativeProductBuildDir("app3") + + "/app3.app/app3.dwarf")); + } + QVERIFY(directoryExists(relativeProductBuildDir("app4") + "/app4.dSYM")); + QVERIFY(regularFileExists(relativeProductBuildDir("app4") + + "/app4.dSYM/Contents/Info.plist")); + QVERIFY(regularFileExists(relativeProductBuildDir("app4") + + "/app4.dSYM/Contents/Resources/DWARF/app4")); + QCOMPARE(QDir(relativeProductBuildDir("app4") + + "/app4.dSYM/Contents/Resources/DWARF") + .entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1); + QVERIFY(regularFileExists(relativeProductBuildDir("app5") + "/app5.dwarf")); + QVERIFY(directoryExists(relativeProductBuildDir("foo1") + "/foo1.framework.dSYM")); + QVERIFY(regularFileExists(relativeProductBuildDir("foo1") + + "/foo1.framework.dSYM/Contents/Info.plist")); + QVERIFY(regularFileExists(relativeProductBuildDir("foo1") + + "/foo1.framework.dSYM/Contents/Resources/DWARF/foo1")); + QCOMPARE(QDir(relativeProductBuildDir("foo1") + + "/foo1.framework.dSYM/Contents/Resources/DWARF") + .entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1); + QVERIFY(!QFile::exists(relativeProductBuildDir("foo2") + "/foo2.framework.dSYM")); + QVERIFY(!QFile::exists(relativeProductBuildDir("foo3") + "/foo3.framework.dSYM")); + if (isMacos) { + QVERIFY(regularFileExists(relativeProductBuildDir("foo3") + + "/foo3.framework/Versions/A/foo3.dwarf")); + } else { + QVERIFY(regularFileExists(relativeProductBuildDir("foo3") + + "/foo3.framework/foo3.dwarf")); + } + QVERIFY(directoryExists(relativeProductBuildDir("foo4") + "/libfoo4.dylib.dSYM")); + QVERIFY(regularFileExists(relativeProductBuildDir("foo4") + + "/libfoo4.dylib.dSYM/Contents/Info.plist")); + QVERIFY(regularFileExists(relativeProductBuildDir("foo4") + + "/libfoo4.dylib.dSYM/Contents/Resources/DWARF/libfoo4.dylib")); + QCOMPARE(QDir(relativeProductBuildDir("foo4") + + "/libfoo4.dylib.dSYM/Contents/Resources/DWARF") + .entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1); + QVERIFY(regularFileExists(relativeProductBuildDir("foo5") + "/libfoo5.dylib.dwarf")); + QVERIFY(directoryExists(relativeProductBuildDir("bar1") + "/bar1.bundle.dSYM")); + QVERIFY(regularFileExists(relativeProductBuildDir("bar1") + + "/bar1.bundle.dSYM/Contents/Info.plist")); + QVERIFY(regularFileExists(relativeProductBuildDir("bar1") + + "/bar1.bundle.dSYM/Contents/Resources/DWARF/bar1")); + QCOMPARE(QDir(relativeProductBuildDir("bar1") + + "/bar1.bundle.dSYM/Contents/Resources/DWARF") + .entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1); + QVERIFY(!QFile::exists(relativeProductBuildDir("bar2") + "/bar2.bundle.dSYM")); + QVERIFY(!QFile::exists(relativeProductBuildDir("bar3") + "/bar3.bundle.dSYM")); + if (isMacos) { + QVERIFY(regularFileExists(relativeProductBuildDir("bar3") + + "/bar3.bundle/Contents/MacOS/bar3.dwarf")); + } else { + QVERIFY(regularFileExists(relativeProductBuildDir("bar3") + + "/bar3.bundle/bar3.dwarf")); + } + QVERIFY(directoryExists(relativeProductBuildDir("bar4") + "/bar4.bundle.dSYM")); + QVERIFY(regularFileExists(relativeProductBuildDir("bar4") + + "/bar4.bundle.dSYM/Contents/Info.plist")); + QVERIFY(regularFileExists(relativeProductBuildDir("bar4") + + "/bar4.bundle.dSYM/Contents/Resources/DWARF/bar4.bundle")); + QCOMPARE(QDir(relativeProductBuildDir("bar4") + + "/bar4.bundle.dSYM/Contents/Resources/DWARF") + .entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries).size(), 1); + QVERIFY(regularFileExists(relativeProductBuildDir("bar5") + "/bar5.bundle.dwarf")); + } else if (toolchain.contains("gcc")) { + const QString exeSuffix = isWindows ? ".exe" : ""; + const QString dllPrefix = isWindows ? "" : "lib"; + const QString dllSuffix = isWindows ? ".dll" : ".so"; + QVERIFY(QFile::exists(relativeProductBuildDir("app1") + "/app1" + exeSuffix + ".debug")); + QVERIFY(!QFile::exists(relativeProductBuildDir("app2") + "/app2" + exeSuffix + ".debug")); + QVERIFY(QFile::exists(relativeProductBuildDir("foo1") + + '/' + dllPrefix + "foo1" + dllSuffix + ".debug")); + QVERIFY(!QFile::exists(relativeProductBuildDir("foo2") + + '/' + "foo2" + dllSuffix + ".debug")); + QVERIFY(QFile::exists(relativeProductBuildDir("bar1") + + '/' + dllPrefix + "bar1" + dllSuffix + ".debug")); + QVERIFY(!QFile::exists(relativeProductBuildDir("bar2") + + '/' + dllPrefix + "bar2" + dllSuffix + ".debug")); + } else if (toolchain.contains("msvc")) { + QVERIFY(QFile::exists(relativeProductBuildDir("app1") + "/app1.pdb")); + QVERIFY(QFile::exists(relativeProductBuildDir("foo1") + "/foo1.pdb")); + QVERIFY(QFile::exists(relativeProductBuildDir("bar1") + "/bar1.pdb")); + // MSVC's linker even creates a pdb file if /Z7 is passed to the compiler. + } else { + QSKIP("Unsupported toolchain. Skipping."); + } +} + +void TestBlackbox::trackAddFile() +{ + QList output; + QDir::setCurrent(testDataDir + "/trackAddFile"); + if (QFile::exists("work")) + rmDirR("work"); + QDir().mkdir("work"); + ccp("before", "work"); + QDir::setCurrent(testDataDir + "/trackAddFile/work"); + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + const QbsRunParameters runParams("run", QStringList{"-qp", "someapp"}); + QCOMPARE(runQbs(runParams), 0); + + output = m_qbsStdout.split('\n'); + QCOMPARE(output.takeFirst().trimmed().constData(), "Hello World!"); + QCOMPARE(output.takeFirst().trimmed().constData(), "NARF!"); + QString unchangedObjectFile = relativeBuildDir() + + objectFileName("/someapp/narf.cpp", profileName()); + QDateTime unchangedObjectFileTime1 = QFileInfo(unchangedObjectFile).lastModified(); + + WAIT_FOR_NEW_TIMESTAMP(); + ccp("../after", "."); + touch("trackAddFile.qbs"); + touch("main.cpp"); + QCOMPARE(runQbs(runParams), 0); + + output = m_qbsStdout.split('\n'); + QCOMPARE(output.takeFirst().trimmed().constData(), "Hello World!"); + QCOMPARE(output.takeFirst().trimmed().constData(), "NARF!"); + QCOMPARE(output.takeFirst().trimmed().constData(), "ZORT!"); + + // the object file of the untouched source should not have changed + QDateTime unchangedObjectFileTime2 = QFileInfo(unchangedObjectFile).lastModified(); + QCOMPARE(unchangedObjectFileTime1, unchangedObjectFileTime2); +} + +void TestBlackbox::trackExternalProductChanges() +{ + QDir::setCurrent(testDataDir + "/trackExternalProductChanges"); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("compiling main.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling environmentChange.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp")); + + QbsRunParameters params; + params.environment.insert("QBS_TEST_PULL_IN_FILE_VIA_ENV", "1"); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compiling main.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling environmentChange.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp")); + params.command = "resolve"; + QCOMPARE(runQbs(params), 0); + params.command = "build"; + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compiling main.cpp")); + QVERIFY(m_qbsStdout.contains("compiling environmentChange.cpp")); + QVERIFY2(!m_qbsStdout.contains("compiling jsFileChange.cpp"), m_qbsStdout.constData()); + QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp")); + + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("compiling main.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling environmentChange.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp")); + + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("fileList.js", "return []", "return ['jsFileChange.cpp']"); + QCOMPARE(runQbs(), 0); + QVERIFY(!m_qbsStdout.contains("compiling main.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling environmentChange.cpp")); + QVERIFY(m_qbsStdout.contains("compiling jsFileChange.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp")); + + rmDirR(relativeBuildDir()); + REPLACE_IN_FILE("fileList.js", "['jsFileChange.cpp']", "[]"); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("compiling main.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling environmentChange.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling fileExists.cpp")); + + QFile cppFile("fileExists.cpp"); + QVERIFY(cppFile.open(QIODevice::WriteOnly)); + cppFile.write("void fileExists() { }\n"); + cppFile.close(); + QCOMPARE(runQbs(), 0); + QVERIFY(!m_qbsStdout.contains("compiling main.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling environmentChange.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling jsFileChange.cpp")); + QVERIFY(m_qbsStdout.contains("compiling fileExists.cpp")); + + rmDirR(relativeBuildDir()); + const SettingsPtr s = settings(); + const Profile profile(profileName(), s.get()); + const QStringList toolchainTypes = profileToolchain(profile); + if (!toolchainTypes.contains("gcc")) + QSKIP("Need GCC-like compiler to run this test"); + params.environment = QProcessEnvironment::systemEnvironment(); + params.environment.insert("INCLUDE_PATH_TEST", "1"); + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("hiddenheaderqbs.h"), m_qbsStderr.constData()); + params.command = "resolve"; + params.environment.insert("CPLUS_INCLUDE_PATH", + QDir::toNativeSeparators(QDir::currentPath() + "/hidden")); + params.expectFailure = false; + QCOMPARE(runQbs(params), 0); + params.command = "build"; + QCOMPARE(runQbs(params), 0); +} + +void TestBlackbox::trackGroupConditionChange() +{ + QbsRunParameters params; + params.expectFailure = true; + QDir::setCurrent(testDataDir + "/group-condition-change"); + QVERIFY(runQbs(params) != 0); + QVERIFY(m_qbsStderr.contains("jibbetnich")); + + params.command = "resolve"; + params.arguments = QStringList("project.kaputt:false"); + params.expectFailure = false; + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::trackRemoveFile() +{ + QList output; + QDir::setCurrent(testDataDir + "/trackAddFile"); + if (QFile::exists("work")) + rmDirR("work"); + QDir().mkdir("work"); + ccp("before", "work"); + ccp("after", "work"); + QDir::setCurrent(testDataDir + "/trackAddFile/work"); + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + const QbsRunParameters runParams("run", QStringList{"-qp", "someapp"}); + QCOMPARE(runQbs(runParams), 0); + output = m_qbsStdout.split('\n'); + QCOMPARE(output.takeFirst().trimmed().constData(), "Hello World!"); + QCOMPARE(output.takeFirst().trimmed().constData(), "NARF!"); + QCOMPARE(output.takeFirst().trimmed().constData(), "ZORT!"); + QString unchangedObjectFile = relativeBuildDir() + + objectFileName("/someapp/narf.cpp", profileName()); + QDateTime unchangedObjectFileTime1 = QFileInfo(unchangedObjectFile).lastModified(); + + WAIT_FOR_NEW_TIMESTAMP(); + QFile::remove("trackAddFile.qbs"); + QFile::remove("main.cpp"); + QFile::copy("../before/trackAddFile.qbs", "trackAddFile.qbs"); + QFile::copy("../before/main.cpp", "main.cpp"); + QVERIFY(QFile::remove("zort.h")); + QVERIFY(QFile::remove("zort.cpp")); + QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("resolve"))), 0); + + touch("main.cpp"); + touch("trackAddFile.qbs"); + QCOMPARE(runQbs(runParams), 0); + output = m_qbsStdout.split('\n'); + QCOMPARE(output.takeFirst().trimmed().constData(), "Hello World!"); + QCOMPARE(output.takeFirst().trimmed().constData(), "NARF!"); + + // the object file of the untouched source should not have changed + QDateTime unchangedObjectFileTime2 = QFileInfo(unchangedObjectFile).lastModified(); + QCOMPARE(unchangedObjectFileTime1, unchangedObjectFileTime2); + + // the object file for the removed cpp file should have vanished too + QVERIFY(!regularFileExists(relativeBuildDir() + + objectFileName("/someapp/zort.cpp", profileName()))); +} + +void TestBlackbox::trackAddFileTag() +{ + QList output; + QDir::setCurrent(testDataDir + "/trackFileTags"); + if (QFile::exists("work")) + rmDirR("work"); + QDir().mkdir("work"); + ccp("before", "work"); + QDir::setCurrent(testDataDir + "/trackFileTags/work"); + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + const QbsRunParameters runParams("run", QStringList{"-qp", "someapp"}); + QCOMPARE(runQbs(runParams), 0); + output = m_qbsStdout.split('\n'); + QCOMPARE(output.takeFirst().trimmed().constData(), "there's no foo here"); + + WAIT_FOR_NEW_TIMESTAMP(); + ccp("../after", "."); + touch("main.cpp"); + touch("trackFileTags.qbs"); + waitForFileUnlock(); + QCOMPARE(runQbs(runParams), 0); + output = m_qbsStdout.split('\n'); + QCOMPARE(output.takeFirst().trimmed().constData(), "there's 15 foo here"); +} + +void TestBlackbox::trackRemoveFileTag() +{ + QList output; + QDir::setCurrent(testDataDir + "/trackFileTags"); + if (QFile::exists("work")) + rmDirR("work"); + QDir().mkdir("work"); + ccp("after", "work"); + QDir::setCurrent(testDataDir + "/trackFileTags/work"); + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + const QbsRunParameters runParams("run", QStringList{"-qp", "someapp"}); + QCOMPARE(runQbs(runParams), 0); + + // check if the artifacts are here that will become stale in the 2nd step + QVERIFY(regularFileExists(relativeProductBuildDir("someapp") + '/' + inputDirHash(".") + + objectFileName("/main_foo.cpp", profileName()))); + QVERIFY(regularFileExists(relativeProductBuildDir("someapp") + "/main_foo.cpp")); + QVERIFY(regularFileExists(relativeProductBuildDir("someapp") + "/main.foo")); + output = m_qbsStdout.split('\n'); + QCOMPARE(output.takeFirst().trimmed().constData(), "there's 15 foo here"); + + WAIT_FOR_NEW_TIMESTAMP(); + ccp("../before", "."); + touch("main.cpp"); + touch("trackFileTags.qbs"); + QCOMPARE(runQbs(runParams), 0); + output = m_qbsStdout.split('\n'); + QCOMPARE(output.takeFirst().trimmed().constData(), "there's no foo here"); + + // check if stale artifacts have been removed + QCOMPARE(regularFileExists(relativeProductBuildDir("someapp") + '/' + inputDirHash(".") + + objectFileName("/main_foo.cpp", profileName())), false); + QCOMPARE(regularFileExists(relativeProductBuildDir("someapp") + "/main_foo.cpp"), false); + QCOMPARE(regularFileExists(relativeProductBuildDir("someapp") + "/main.foo"), false); +} + +void TestBlackbox::trackAddProduct() +{ + QDir::setCurrent(testDataDir + "/trackProducts"); + if (QFile::exists("work")) + rmDirR("work"); + QDir().mkdir("work"); + ccp("before", "work"); + QDir::setCurrent(testDataDir + "/trackProducts/work"); + QbsRunParameters params(QStringList() << "-f" << "trackProducts.qbs"); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("compiling foo.cpp")); + QVERIFY(m_qbsStdout.contains("compiling bar.cpp")); + QVERIFY(m_qbsStdout.contains("linking product1")); + QVERIFY(m_qbsStdout.contains("linking product2")); + + WAIT_FOR_NEW_TIMESTAMP(); + ccp("../after", "."); + touch("trackProducts.qbs"); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("compiling zoo.cpp")); + QVERIFY(m_qbsStdout.contains("linking product3")); + QVERIFY(!m_qbsStdout.contains("compiling foo.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling bar.cpp")); + QVERIFY(!m_qbsStdout.contains("linking product1")); + QVERIFY(!m_qbsStdout.contains("linking product2")); +} + +void TestBlackbox::trackRemoveProduct() +{ + QDir::setCurrent(testDataDir + "/trackProducts"); + if (QFile::exists("work")) + rmDirR("work"); + QDir().mkdir("work"); + ccp("before", "work"); + ccp("after", "work"); + QDir::setCurrent(testDataDir + "/trackProducts/work"); + QbsRunParameters params(QStringList() << "-f" << "trackProducts.qbs"); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("compiling foo.cpp")); + QVERIFY(m_qbsStdout.contains("compiling bar.cpp")); + QVERIFY(m_qbsStdout.contains("compiling zoo.cpp")); + QVERIFY(m_qbsStdout.contains("linking product1")); + QVERIFY(m_qbsStdout.contains("linking product2")); + QVERIFY(m_qbsStdout.contains("linking product3")); + + WAIT_FOR_NEW_TIMESTAMP(); + QFile::remove("zoo.cpp"); + QFile::remove("product3.qbs"); + copyFileAndUpdateTimestamp("../before/trackProducts.qbs", "trackProducts.qbs"); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compiling foo.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling bar.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling zoo.cpp")); + QVERIFY(!m_qbsStdout.contains("linking product1")); + QVERIFY(!m_qbsStdout.contains("linking product2")); + QVERIFY(!m_qbsStdout.contains("linking product3")); +} + +void TestBlackbox::wildcardRenaming() +{ + QDir::setCurrent(testDataDir + "/wildcard_renaming"); + QCOMPARE(runQbs(QbsRunParameters("install")), 0); + QVERIFY(QFileInfo(defaultInstallRoot + "/pioniere.txt").exists()); + WAIT_FOR_NEW_TIMESTAMP(); + QFile::rename(QDir::currentPath() + "/pioniere.txt", QDir::currentPath() + "/fdj.txt"); + QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("install"), + QStringList("--clean-install-root"))), 0); + QVERIFY(!QFileInfo(defaultInstallRoot + "/pioniere.txt").exists()); + QVERIFY(QFileInfo(defaultInstallRoot + "/fdj.txt").exists()); +} + +void TestBlackbox::recursiveRenaming() +{ + QDir::setCurrent(testDataDir + "/recursive_renaming"); + QCOMPARE(runQbs(QbsRunParameters("install")), 0); + QVERIFY(QFileInfo(defaultInstallRoot + "/dir/wasser.txt").exists()); + QVERIFY(QFileInfo(defaultInstallRoot + "/dir/subdir/blubb.txt").exists()); + WAIT_FOR_NEW_TIMESTAMP(); + QVERIFY(QFile::rename(QDir::currentPath() + "/dir/wasser.txt", QDir::currentPath() + "/dir/wein.txt")); + QCOMPARE(runQbs(QbsRunParameters(QStringLiteral("install"), + QStringList("--clean-install-root"))), 0); + QVERIFY(!QFileInfo(defaultInstallRoot + "/dir/wasser.txt").exists()); + QVERIFY(QFileInfo(defaultInstallRoot + "/dir/wein.txt").exists()); + QVERIFY(QFileInfo(defaultInstallRoot + "/dir/subdir/blubb.txt").exists()); +} + +void TestBlackbox::recursiveWildcards() +{ + QDir::setCurrent(testDataDir + "/recursive_wildcards"); + QCOMPARE(runQbs(QbsRunParameters("install")), 0); + QVERIFY(QFileInfo(defaultInstallRoot + "/dir/file1.txt").exists()); + QVERIFY(QFileInfo(defaultInstallRoot + "/dir/file2.txt").exists()); + QFile outputFile(defaultInstallRoot + "/output.txt"); + QVERIFY2(outputFile.open(QIODevice::ReadOnly), qPrintable(outputFile.errorString())); + QCOMPARE(outputFile.readAll(), QByteArray("file1.txtfile2.txt")); + outputFile.close(); + WAIT_FOR_NEW_TIMESTAMP(); + QFile newFile("dir/subdir/file3.txt"); + QVERIFY2(newFile.open(QIODevice::WriteOnly), qPrintable(newFile.errorString())); + newFile.close(); + QCOMPARE(runQbs(QbsRunParameters("install")), 0); + QVERIFY(QFileInfo(defaultInstallRoot + "/dir/file3.txt").exists()); + QVERIFY2(outputFile.open(QIODevice::ReadOnly), qPrintable(outputFile.errorString())); + QCOMPARE(outputFile.readAll(), QByteArray("file1.txtfile2.txtfile3.txt")); + outputFile.close(); + WAIT_FOR_NEW_TIMESTAMP(); + QVERIFY2(newFile.remove(), qPrintable(newFile.errorString())); + QVERIFY2(!newFile.exists(), qPrintable(newFile.fileName())); + QCOMPARE(runQbs(QbsRunParameters("install", QStringList{ "--clean-install-root"})), 0); + QVERIFY(QFileInfo(defaultInstallRoot + "/dir/file1.txt").exists()); + QVERIFY(QFileInfo(defaultInstallRoot + "/dir/file2.txt").exists()); + QVERIFY(!QFileInfo(defaultInstallRoot + "/dir/file3.txt").exists()); + QVERIFY2(outputFile.open(QIODevice::ReadOnly), qPrintable(outputFile.errorString())); + QCOMPARE(outputFile.readAll(), QByteArray("file1.txtfile2.txt")); +} + +void TestBlackbox::referenceErrorInExport() +{ + QDir::setCurrent(testDataDir + "/referenceErrorInExport"); + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY(m_qbsStderr.contains( + "referenceErrorInExport.qbs:15:12 ReferenceError: Can't find variable: includePaths")); +} + +void TestBlackbox::removeDuplicateLibraries_data() +{ + QTest::addColumn("removeDuplicates"); + QTest::newRow("remove duplicates") << true; + QTest::newRow("don't remove duplicates") << false; +} + +void TestBlackbox::removeDuplicateLibraries() +{ + QDir::setCurrent(testDataDir + "/remove-duplicate-libs"); + QFETCH(bool, removeDuplicates); + const QbsRunParameters resolveParams("resolve", {"-f", "remove-duplicate-libs.qbs", + "project.removeDuplicates:" + QString(removeDuplicates? "true" : "false")}); + QCOMPARE(runQbs(resolveParams), 0); + const bool isBfd = m_qbsStdout.contains("is bfd linker: true"); + const bool isNotBfd = m_qbsStdout.contains("is bfd linker: false"); + QVERIFY2(isBfd != isNotBfd, m_qbsStdout.constData()); + QbsRunParameters buildParams("build"); + buildParams.expectFailure = removeDuplicates && isBfd; + QCOMPARE(runQbs(buildParams) == 0, !buildParams.expectFailure); +} + +void TestBlackbox::reproducibleBuild() +{ + const SettingsPtr s = settings(); + const Profile profile(profileName(), s.get()); + const QStringList toolchains = profileToolchain(profile); + if (!toolchains.contains("gcc")) + QSKIP("reproducible builds only supported for gcc"); + if (toolchains.contains("clang")) + QSKIP("reproducible builds are not supported for clang"); + + QFETCH(bool, reproducible); + + QDir::setCurrent(testDataDir + "/reproducible-build"); + QbsRunParameters params; + params.arguments << QString("modules.cpp.enableReproducibleBuilds:") + + (reproducible ? "true" : "false"); + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(params), 0); + QFile object(relativeProductBuildDir("the product") + '/' + inputDirHash(".") + '/' + + objectFileName("file1.cpp", profileName())); + QVERIFY2(object.open(QIODevice::ReadOnly), qPrintable(object.fileName())); + const QByteArray oldContents = object.readAll(); + object.close(); + QCOMPARE(runQbs(QbsRunParameters("clean")), 0); + QVERIFY(!object.exists()); + QCOMPARE(runQbs(params), 0); + if (reproducible) { + QVERIFY(object.open(QIODevice::ReadOnly)); + const QByteArray newContents = object.readAll(); + QVERIFY(oldContents == newContents); + object.close(); + } + QCOMPARE(runQbs(QbsRunParameters("clean")), 0); +} + +void TestBlackbox::reproducibleBuild_data() +{ + QTest::addColumn("reproducible"); + QTest::newRow("non-reproducible build") << false; + QTest::newRow("reproducible build") << true; +} + +void TestBlackbox::responseFiles() +{ + QDir::setCurrent(testDataDir + "/response-files"); + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QbsRunParameters params; + params.command = "install"; + params.arguments << "--install-root" << "installed"; + QCOMPARE(runQbs(params), 0); + QFile file("installed/response-file-content.txt"); + QVERIFY(file.open(QIODevice::ReadOnly)); + const QList expected = QList() + << "foo" << qbs::Internal::shellQuote(QStringLiteral("with space")).toUtf8() + << "bar" << ""; + QList lines = file.readAll().split('\n'); + for (auto &line : lines) + line = line.trimmed(); + QCOMPARE(lines, expected); +} + +void TestBlackbox::retaggedOutputArtifact() +{ + QDir::setCurrent(testDataDir + "/retagged-output-artifact"); + QbsRunParameters resolveParams("resolve"); + resolveParams.arguments = QStringList("products.p.useTag1:true"); + QCOMPARE(runQbs(resolveParams), 0); + QCOMPARE(runQbs(), 0); + const QString a2 = relativeProductBuildDir("p") + "/a2.txt"; + const QString a3 = relativeProductBuildDir("p") + "/a3.txt"; + QVERIFY2(QFile::exists(a2), qPrintable(a2)); + QVERIFY2(!QFile::exists(a3), qPrintable(a3)); + resolveParams.arguments = QStringList("products.p.useTag1:false"); + QCOMPARE(runQbs(resolveParams), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(!QFile::exists(a2), qPrintable(a2)); + QVERIFY2(QFile::exists(a3), qPrintable(a3)); + resolveParams.arguments = QStringList("products.p.useTag1:true"); + QCOMPARE(runQbs(resolveParams), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(QFile::exists(a2), qPrintable(a2)); + QVERIFY2(!QFile::exists(a3), qPrintable(a3)); +} + +void TestBlackbox::ruleConditions() +{ + QDir::setCurrent(testDataDir + "/ruleConditions"); + QCOMPARE(runQbs(), 0); + QVERIFY(QFileInfo(relativeExecutableFilePath("zorted")).exists()); + QVERIFY(QFileInfo(relativeExecutableFilePath("unzorted")).exists()); + QVERIFY(QFileInfo(relativeProductBuildDir("zorted") + "/zorted.foo.narf.zort").exists()); + QVERIFY(!QFileInfo(relativeProductBuildDir("unzorted") + "/unzorted.foo.narf.zort").exists()); +} + +void TestBlackbox::ruleConnectionWithExcludedInputs() +{ + QDir::setCurrent(testDataDir + "/rule-connection-with-excluded-inputs"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("inputs.x: 2") && m_qbsStdout.contains("inputs.y: 0"), + m_qbsStdout.constData()); +} + +void TestBlackbox::ruleCycle() +{ + QDir::setCurrent(testDataDir + "/ruleCycle"); + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY(m_qbsStderr.contains("Cycle detected in rule dependencies")); +} + +void TestBlackbox::ruleWithNoInputs() +{ + QDir::setCurrent(testDataDir + "/rule-with-no-inputs"); + QVERIFY2(runQbs() == 0, m_qbsStderr.constData()); + QVERIFY2(m_qbsStdout.contains("running the rule"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("creating output"), m_qbsStdout.constData()); + QVERIFY2(runQbs() == 0, m_qbsStderr.constData()); + QVERIFY2(!m_qbsStdout.contains("running the rule"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("creating output"), m_qbsStdout.constData()); + QbsRunParameters params("resolve", QStringList() << "products.theProduct.version:1"); + QVERIFY2(runQbs(params) == 0, m_qbsStderr.constData()); + params.command = "build"; + QVERIFY2(runQbs(params) == 0, m_qbsStderr.constData()); + QVERIFY2(!m_qbsStdout.contains("running the rule"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("creating output"), m_qbsStdout.constData()); + params.command = "resolve"; + params.arguments = QStringList() << "products.theProduct.version:2"; + QVERIFY2(runQbs(params) == 0, m_qbsStderr.constData()); + params.command = "build"; + QVERIFY2(runQbs(params) == 0, m_qbsStderr.constData()); + QVERIFY2(!m_qbsStdout.contains("running the rule"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("creating output"), m_qbsStdout.constData()); + params.command = "resolve"; + params.arguments = QStringList() << "products.theProduct.version:2" + << "products.theProduct.dummy:true"; + QVERIFY2(runQbs(params) == 0, m_qbsStderr.constData()); + params.command = "build"; + QVERIFY2(runQbs(params) == 0, m_qbsStderr.constData()); + QVERIFY2(m_qbsStdout.contains("running the rule"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("creating output"), m_qbsStdout.constData()); +} + +void TestBlackbox::ruleWithNonRequiredInputs() +{ + QDir::setCurrent(testDataDir + "/rule-with-non-required-inputs"); + QbsRunParameters params("build", {"products.p.enableTagger:false"}); + QCOMPARE(runQbs(params), 0); + QFile outFile(relativeProductBuildDir("p") + "/output.txt"); + QVERIFY2(outFile.open(QIODevice::ReadOnly), qPrintable(outFile.errorString())); + QByteArray output = outFile.readAll(); + QCOMPARE(output, QByteArray("()")); + outFile.close(); + params.command = "resolve"; + params.arguments = QStringList({"products.p.enableTagger:true"}); + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(outFile.open(QIODevice::ReadOnly), qPrintable(outFile.errorString())); + output = outFile.readAll(); + QCOMPARE(output, QByteArray("(a.inp,b.inp,c.inp,)")); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("Generating"), m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + touch("a.inp"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Generating"), m_qbsStdout.constData()); +} + +void TestBlackbox::sanitizer_data() +{ + QTest::addColumn("sanitizer"); + QTest::newRow("none") << QString(); + QTest::newRow("address") << QStringLiteral("address"); + QTest::newRow("undefined") << QStringLiteral("undefined"); + QTest::newRow("thread") << QStringLiteral("thread"); +} + +void TestBlackbox::sanitizer() +{ + QFETCH(QString, sanitizer); + QDir::setCurrent(testDataDir + "/sanitizer"); + rmDirR(relativeBuildDir()); + QbsRunParameters params("build", {"--command-echo-mode", "command-line"}); + if (!sanitizer.isEmpty()) { + params.arguments.append( + {QStringLiteral("products.sanitizer.sanitizer:\"") + sanitizer + "\""}); + } + QCOMPARE(runQbs(params), 0); + if (m_qbsStdout.contains(QByteArrayLiteral("Compiler does not support sanitizer"))) + QSKIP("Compiler does not support the specified sanitizer"); + if (!sanitizer.isEmpty()) { + QVERIFY2(m_qbsStdout.contains(QByteArrayLiteral("-fsanitize=") + sanitizer.toLatin1()), + qPrintable(m_qbsStdout)); + } else { + QVERIFY2(!m_qbsStdout.contains(QByteArrayLiteral("-fsanitize=")), qPrintable(m_qbsStdout)); + } +} + +void TestBlackbox::scannerItem() +{ + QDir::setCurrent(testDataDir + "/scanner-item"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("handling file1.in"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("handling file2.in"), m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + touch("subdir1/file.inc"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("handling file1.in"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("handling file2.in"), m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + touch("subdir2/file.inc"); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("handling file1.in"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("handling file2.in"), m_qbsStdout.constData()); +} + +void TestBlackbox::scanResultInOtherProduct() +{ + QDir::setCurrent(testDataDir + "/scan-result-in-other-product"); + QCOMPARE(runQbs(QStringList("-vv")), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generating text file"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStderr.contains("The file dependency might get lost during change tracking"), + m_qbsStderr.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("other/other.qbs", "blubb", "blubb2"); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generating text file"), m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + touch("lib/lib.h"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("generating text file"), m_qbsStdout.constData()); +} + +void TestBlackbox::scanResultInNonDependency() +{ + QDir::setCurrent(testDataDir + "/scan-result-in-non-dependency"); + QCOMPARE(runQbs(QStringList("-vv")), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generating text file"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStderr.contains("The file dependency might get lost during change tracking"), + m_qbsStderr.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("other/other.qbs", "blubb", "blubb2"); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generating text file"), m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + touch("lib/lib.h"); + QCOMPARE(runQbs(), 0); + QEXPECT_FAIL("", "QBS-1532", Continue); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("generating text file"), m_qbsStdout.constData()); +} + +void TestBlackbox::setupBuildEnvironment() +{ + QDir::setCurrent(testDataDir + "/setup-build-environment"); + QCOMPARE(runQbs(), 0); + QFile f(relativeProductBuildDir("first_product") + QLatin1String("/m.output")); + QVERIFY2(f.open(QIODevice::ReadOnly), qPrintable(f.errorString())); + QCOMPARE(f.readAll().trimmed(), QByteArray("1")); + f.close(); + f.setFileName(relativeProductBuildDir("second_product") + QLatin1String("/m.output")); + QVERIFY2(f.open(QIODevice::ReadOnly), qPrintable(f.errorString())); + QCOMPARE(f.readAll().trimmed(), QByteArray()); +} + +void TestBlackbox::setupRunEnvironment() +{ + QDir::setCurrent(testDataDir + "/setup-run-environment"); + QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QbsRunParameters failParams("run", QStringList({"--setup-run-env-config", + "ignore-lib-dependencies"})); + failParams.expectFailure = true; + failParams.expectCrash = m_qbsStdout.contains("is windows"); + QVERIFY(runQbs(QbsRunParameters(failParams)) != 0); + QVERIFY2(failParams.expectCrash || m_qbsStderr.contains("lib"), m_qbsStderr.constData()); + QCOMPARE(runQbs(QbsRunParameters("run")), 0); + QbsRunParameters dryRunParams("run", QStringList("--dry-run")); + dryRunParams.buildDirectory = "dryrun"; + QCOMPARE(runQbs(dryRunParams), 0); + const QString appFilePath = QDir::currentPath() + "/dryrun/" + + relativeExecutableFilePath("app"); + QVERIFY2(m_qbsStdout.contains("Would start target") + && m_qbsStdout.contains(QDir::toNativeSeparators(appFilePath).toLocal8Bit()), + m_qbsStdout.constData()); +} + +void TestBlackbox::smartRelinking() +{ + QDir::setCurrent(testDataDir + "/smart-relinking"); + rmDirR(relativeBuildDir()); + QFETCH(bool, strictChecking); + QbsRunParameters params(QStringList() << (QString("modules.cpp.exportedSymbolsCheckMode:%1") + .arg(strictChecking ? "strict" : "ignore-undefined"))); + QCOMPARE(runQbs(params), 0); + if (m_qbsStdout.contains("project disabled")) + QSKIP("Test does not apply on this target"); + QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("linking app"), m_qbsStdout.constData()); + + // Irrelevant change. + WAIT_FOR_NEW_TIMESTAMP(); + touch("lib.cpp"); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("linking app"), m_qbsStdout.constData()); + + // Add new private symbol. + WAIT_FOR_NEW_TIMESTAMP(); + params.command = "resolve"; + params.arguments << "products.lib.defines:PRIV2"; + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("linking app"), m_qbsStdout.constData()); + + // Remove private symbol. + WAIT_FOR_NEW_TIMESTAMP(); + params.arguments.removeLast(); + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("linking app"), m_qbsStdout.constData()); + + // Add new public symbol. + WAIT_FOR_NEW_TIMESTAMP(); + params.arguments << "products.lib.defines:PUB2"; + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("linking app"), m_qbsStdout.constData()); + + // Remove public symbol. + WAIT_FOR_NEW_TIMESTAMP(); + params.arguments.removeLast(); + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("linking app"), m_qbsStdout.constData()); + + // Add new undefined symbol. + WAIT_FOR_NEW_TIMESTAMP(); + params.arguments << "products.lib.defines:PRINTF"; + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData()); + QVERIFY2(strictChecking == m_qbsStdout.contains("linking app"), m_qbsStdout.constData()); + + // Remove undefined symbol. + WAIT_FOR_NEW_TIMESTAMP(); + params.arguments.removeLast(); + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("linking lib"), m_qbsStdout.constData()); + QVERIFY2(strictChecking == m_qbsStdout.contains("linking app"), m_qbsStdout.constData()); +} + +void TestBlackbox::smartRelinking_data() +{ + QTest::addColumn("strictChecking"); + QTest::newRow("strict checking") << true; + QTest::newRow("ignore undefined") << false; +} + + +static QString soName(const QString &readElfPath, const QString &libFilePath) +{ + QProcess readElf; + auto env = QProcessEnvironment::systemEnvironment(); + env.insert(QStringLiteral("LC_ALL"), QStringLiteral("C")); // force readelf to use US encoding + readElf.setProcessEnvironment(env); + readElf.start(readElfPath, QStringList() << "-a" << libFilePath); + if (!readElf.waitForStarted() || !readElf.waitForFinished() || readElf.exitCode() != 0) { + qDebug() << readElf.errorString() << readElf.readAllStandardError(); + return {}; + } + const QByteArray output = readElf.readAllStandardOutput(); + const QByteArray magicString = "Library soname: ["; + const int magicStringIndex = output.indexOf(magicString); + if (magicStringIndex == -1) + return {}; + const int endIndex = output.indexOf(']', magicStringIndex); + if (endIndex == -1) + return {}; + const int nameIndex = magicStringIndex + magicString.size(); + const QByteArray theName = output.mid(nameIndex, endIndex - nameIndex); + return QString::fromLatin1(theName); +} + +void TestBlackbox::soVersion() +{ + const QString readElfPath = findExecutable(QStringList("readelf")); + if (readElfPath.isEmpty() || readElfPath.endsWith("exe")) + QSKIP("soversion test not applicable on this system"); + QDir::setCurrent(testDataDir + "/soversion"); + + QFETCH(QString, soVersion); + QFETCH(bool, useVersion); + QFETCH(QString, expectedSoName); + + QbsRunParameters params; + params.arguments << ("products.mylib.useVersion:" + QString((useVersion ? "true" : "false"))); + if (!soVersion.isNull()) + params.arguments << ("modules.cpp.soVersion:" + soVersion); + const QString libFilePath = relativeProductBuildDir("mylib") + "/libmylib.so" + + (useVersion ? ".1.2.3" : QString()); + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(regularFileExists(libFilePath), qPrintable(libFilePath)); + QCOMPARE(soName(readElfPath, libFilePath), expectedSoName); +} + +void TestBlackbox::soVersion_data() +{ + QTest::addColumn("soVersion"); + QTest::addColumn("useVersion"); + QTest::addColumn("expectedSoName"); + + QTest::newRow("default") << QString() << true << QString("libmylib.so.1"); + QTest::newRow("explicit soVersion") << QString("1.2") << true << QString("libmylib.so.1.2"); + QTest::newRow("empty soVersion") << QString("") << true << QString("libmylib.so.1.2.3"); + QTest::newRow("no version, explicit soVersion") << QString("5") << false + << QString("libmylib.so.5"); + QTest::newRow("no version, default soVersion") << QString() << false << QString("libmylib.so"); + QTest::newRow("no version, empty soVersion") << QString("") << false << QString("libmylib.so"); +} + +void TestBlackbox::sourceArtifactChanges() +{ + QDir::setCurrent(testDataDir + "/source-artifact-changes"); + bool useCustomFileTags = false; + bool overrideFileTags = true; + bool filesAreTargets = false; + bool useCxx11 = false; + const QString appFilePath = QDir::currentPath() + '/' + relativeExecutableFilePath("app"); + static const auto b2s = [](bool b) { return QString(b ? "true" : "false"); }; + const auto resolveParams = [&useCustomFileTags, &overrideFileTags, &filesAreTargets, &useCxx11] { + return QbsRunParameters("resolve", QStringList{ + "modules.module_with_files.overrideTags:" + b2s(overrideFileTags), + "modules.module_with_files.filesAreTargets:" + b2s(filesAreTargets), + "modules.module_with_files.fileTags:" + QString(useCustomFileTags ? "custom" : "cpp"), + "modules.cpp.cxxLanguageVersion:" + QString(useCxx11 ? "c++11" : "c++98") + }); + }; +#define VERIFY_COMPILATION(expected) \ + do { \ + QVERIFY2(m_qbsStdout.contains("compiling main.cpp") == expected, m_qbsStdout.constData()); \ + QVERIFY2(QFile::exists(appFilePath) == expected, qPrintable(appFilePath)); \ + if (expected) \ + QVERIFY2(m_qbsStdout.contains("cpp artifacts: 1"), m_qbsStdout.constData()); \ + else \ + QVERIFY2(m_qbsStdout.contains("cpp artifacts: 0"), m_qbsStdout.constData()); \ + } while (false) + + // Initial build. + QCOMPARE(runQbs(resolveParams()), 0); + QVERIFY2(m_qbsStdout.contains("is gcc: "), m_qbsStdout.constData()); + const bool isGcc = m_qbsStdout.contains("is gcc: true"); + QCOMPARE(runQbs(), 0); + VERIFY_COMPILATION(true); + + // Overwrite the file tags. Now the source file is no longer tagged "cpp" and nothing + // should get built. + WAIT_FOR_NEW_TIMESTAMP(); + touch("modules/module_with_files/main.cpp"); + useCustomFileTags = true; + QCOMPARE(runQbs(resolveParams()), 0); + QCOMPARE(runQbs(), 0); + VERIFY_COMPILATION(false); + + // Now the custom file tag exists in addition to "cpp", and the app should get built again. + overrideFileTags = false; + QCOMPARE(runQbs(resolveParams()), 0); + QCOMPARE(runQbs(), 0); + VERIFY_COMPILATION(true); + + // Mark the cpp file as a module target. Now it will no longer be considered an input + // by the compiler rule, and nothing should get built. + WAIT_FOR_NEW_TIMESTAMP(); + touch("modules/module_with_files/main.cpp"); + filesAreTargets = true; + QCOMPARE(runQbs(resolveParams()), 0); + QCOMPARE(runQbs(), 0); + VERIFY_COMPILATION(false); + + // Now just revert the last change. + filesAreTargets = false; + QCOMPARE(runQbs(resolveParams()), 0); + QCOMPARE(runQbs(), 0); + VERIFY_COMPILATION(true); + + // Change a relevant cpp property. A rebuild is expected. + useCxx11 = true; + QCOMPARE(runQbs(resolveParams()), 0); + QCOMPARE(runQbs(QStringList({"--command-echo-mode", "command-line"})), 0); + if (isGcc) { + QVERIFY2(m_qbsStdout.contains("-std=c++11") || m_qbsStdout.contains("-std=c++0x"), + m_qbsStdout.constData()); + } + +#undef VERIFY_COMPILATION +} + +void TestBlackbox::overrideProjectProperties() +{ + QDir::setCurrent(testDataDir + "/overrideProjectProperties"); + QCOMPARE(runQbs(QbsRunParameters(QStringList() + << QStringLiteral("-f") + << QStringLiteral("overrideProjectProperties.qbs") + << QStringLiteral("project.nameSuffix:ForYou") + << QStringLiteral("project.someBool:false") + << QStringLiteral("project.someInt:156") + << QStringLiteral("project.someStringList:one") + << QStringLiteral("products.MyAppForYou.mainFile:main.cpp"))), + 0); + QVERIFY(regularFileExists(relativeExecutableFilePath("MyAppForYou"))); + QVERIFY(QFile::remove(relativeBuildGraphFilePath())); + QbsRunParameters params; + params.arguments << QStringLiteral("-f") << QStringLiteral("project_using_helper_lib.qbs"); + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + + rmDirR(relativeBuildDir()); + params.arguments = QStringList() << QStringLiteral("-f") + << QStringLiteral("project_using_helper_lib.qbs") + << QStringLiteral("project.linkSuccessfully:true"); + params.expectFailure = false; + QCOMPARE(runQbs(params), 0); +} + +void TestBlackbox::pathProbe_data() +{ + QTest::addColumn("projectFile"); + QTest::addColumn("successExpected"); + QTest::newRow("non-existent") << QString("non-existent.qbs") << false; + QTest::newRow("non-existent-selector.qbs") << QString("non-existent-selector.qbs") << false; + QTest::newRow("single-file") << QString("single-file.qbs") << true; + QTest::newRow("single-file-selector") << QString("single-file-selector.qbs") << true; + QTest::newRow("single-file-selector-array") << QString("single-file-selector-array.qbs") << true; + QTest::newRow("single-file-mult-variants") << QString("single-file-mult-variants.qbs") << true; + QTest::newRow("mult-files") << QString("mult-files.qbs") << true; + QTest::newRow("mult-files-mult-variants") << QString("mult-files-mult-variants.qbs") << true; + QTest::newRow("single-file-suffixes") << QString("single-file-suffixes.qbs") << true; + QTest::newRow("mult-files-suffixes") << QString("mult-files-suffixes.qbs") << true; + QTest::newRow("mult-files-common-suffixes") << QString("mult-files-common-suffixes.qbs") << true; + QTest::newRow("mult-files-mult-suffixes") << QString("mult-files-mult-suffixes.qbs") << true; + QTest::newRow("name-filter") << QString("name-filter.qbs") << true; + QTest::newRow("candidate-filter") << QString("candidate-filter.qbs") << true; + QTest::newRow("environment-paths") << QString("environment-paths.qbs") << true; +} + +void TestBlackbox::pathProbe() +{ + QDir::setCurrent(testDataDir + "/path-probe"); + QFETCH(QString, projectFile); + QFETCH(bool, successExpected); + rmDirR(relativeBuildDir()); + + QbsRunParameters buildParams("build", QStringList{"-f", projectFile}); + buildParams.expectFailure = !successExpected; + buildParams.environment.insert("SEARCH_PATH", "usr/bin"); + QCOMPARE(runQbs(buildParams) == 0, successExpected); + if (!successExpected) + QVERIFY2(m_qbsStderr.contains("Probe failed to find files"), m_qbsStderr); +} + +void TestBlackbox::pchChangeTracking() +{ + QDir::setCurrent(testDataDir + "/pch-change-tracking"); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("precompiling pch.h (cpp)")); + WAIT_FOR_NEW_TIMESTAMP(); + touch("header1.h"); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("precompiling pch.h (cpp)")); + QVERIFY(m_qbsStdout.contains("compiling header2.cpp")); + QVERIFY(m_qbsStdout.contains("compiling main.cpp")); + WAIT_FOR_NEW_TIMESTAMP(); + touch("header2.h"); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("precompiling pch.h (cpp)"), m_qbsStdout.constData()); +} + +void TestBlackbox::perGroupDefineInExportItem() +{ + QDir::setCurrent(testDataDir + "/per-group-define-in-export-item"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::pkgConfigProbe() +{ + const QString exe = findExecutable(QStringList() << "pkg-config"); + if (exe.isEmpty()) + QSKIP("This test requires the pkg-config tool"); + + QDir::setCurrent(testDataDir + "/pkg-config-probe"); + + QFETCH(QString, packageBaseName); + QFETCH(QStringList, found); + QFETCH(QStringList, libs); + QFETCH(QStringList, cflags); + QFETCH(QStringList, version); + + rmDirR(relativeBuildDir()); + QbsRunParameters params(QStringList() << ("project.packageBaseName:" + packageBaseName)); + QCOMPARE(runQbs(params), 0); + const QString stdOut = m_qbsStdout; + QVERIFY2(stdOut.contains("theProduct1 found: " + found.at(0)), m_qbsStdout.constData()); + QVERIFY2(stdOut.contains("theProduct2 found: " + found.at(1)), m_qbsStdout.constData()); + QVERIFY2(stdOut.contains("theProduct1 libs: " + libs.at(0)), m_qbsStdout.constData()); + QVERIFY2(stdOut.contains("theProduct2 libs: " + libs.at(1)), m_qbsStdout.constData()); + QVERIFY2(stdOut.contains("theProduct1 cflags: " + cflags.at(0)), m_qbsStdout.constData()); + QVERIFY2(stdOut.contains("theProduct2 cflags: " + cflags.at(1)), m_qbsStdout.constData()); + QVERIFY2(stdOut.contains("theProduct1 version: " + version.at(0)), m_qbsStdout.constData()); + QVERIFY2(stdOut.contains("theProduct2 version: " + version.at(1)), m_qbsStdout.constData()); +} + +void TestBlackbox::pkgConfigProbe_data() +{ + QTest::addColumn("packageBaseName"); + QTest::addColumn("found"); + QTest::addColumn("libs"); + QTest::addColumn("cflags"); + QTest::addColumn("version"); + + QTest::newRow("existing package") + << "dummy" << (QStringList() << "true" << "true") + << (QStringList() << "[\"-Ldummydir1\",\"-ldummy1\"]" + << "[\"-Ldummydir2\",\"-ldummy2\"]") + << (QStringList() << "[]" << "[]") << (QStringList() << "0.0.1" << "0.0.2"); + + // Note: The array values should be "undefined", but we lose that information when + // converting to QVariants in the ProjectResolver. + QTest::newRow("non-existing package") + << "blubb" << (QStringList() << "false" << "false") << (QStringList() << "[]" << "[]") + << (QStringList() << "[]" << "[]") << (QStringList() << "undefined" << "undefined"); +} + +void TestBlackbox::pkgConfigProbeSysroot() +{ + const QString exe = findExecutable(QStringList() << "pkg-config"); + if (exe.isEmpty()) + QSKIP("This test requires the pkg-config tool"); + + QDir::setCurrent(testDataDir + "/pkg-config-probe-sysroot"); + QCOMPARE(runQbs(QStringList("-v")), 0); + QCOMPARE(m_qbsStderr.count("PkgConfigProbe: found packages"), 2); + const QString outputTemplate = "theProduct%1 libs: [\"-L%2/usr/dummy\",\"-ldummy1\"]"; + QVERIFY2(m_qbsStdout.contains(outputTemplate + .arg("1", QDir::currentPath() + "/sysroot1").toLocal8Bit()), + m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains(outputTemplate + .arg("2", QDir::currentPath() + "/sysroot2").toLocal8Bit()), + m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains(outputTemplate + .arg("3", QDir::currentPath() + "/sysroot1").toLocal8Bit()), + m_qbsStdout.constData()); +} + +void TestBlackbox::pluginDependency() +{ + QDir::setCurrent(testDataDir + "/plugin-dependency"); + + // Build the plugins and the helper2 lib. + QCOMPARE(runQbs(QStringList{"--products", "plugin1,plugin2,plugin3,plugin4,helper2"}), 0); + QVERIFY(m_qbsStdout.contains("plugin1")); + QVERIFY(m_qbsStdout.contains("plugin2")); + QVERIFY(m_qbsStdout.contains("plugin3")); + QVERIFY(m_qbsStdout.contains("plugin4")); + QVERIFY(m_qbsStdout.contains("helper2")); + QVERIFY(!m_qbsStderr.contains("SOFT ASSERT")); + + // Build the app. Plugins 1 and 2 must not be linked. Plugin 3 must be linked. + QCOMPARE(runQbs(QStringList{"--command-echo-mode", "command-line"}), 0); + QByteArray output = m_qbsStdout + '\n' + m_qbsStderr; + QVERIFY(!output.contains("plugin1")); + QVERIFY(!output.contains("plugin2")); + QVERIFY(!output.contains("helper2")); + + // Test change tracking for parameter in Parameters item. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("plugin-dependency.qbs", "false // marker 1", "true"); + QCOMPARE(runQbs(QStringList{"-p", "plugin2"}), 0); + QVERIFY2(!m_qbsStdout.contains("linking"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QStringList{"--command-echo-mode", "command-line"}), 0); + output = m_qbsStdout + '\n' + m_qbsStderr; + QVERIFY2(!output.contains("plugin1"), output.constData()); + QVERIFY2(!output.contains("helper2"), output.constData()); + QVERIFY2(output.contains("plugin2"), output.constData()); + + // Test change tracking for parameter in Depends item. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("plugin-dependency.qbs", "false /* marker 2 */", "true"); + QCOMPARE(runQbs(QStringList{"-p", "helper1", "--command-echo-mode", "command-line"}), 0); + output = m_qbsStdout + '\n' + m_qbsStderr; + QVERIFY2(output.contains("helper2"), output.constData()); + + // Check that the build dependency still works. + QCOMPARE(runQbs(QStringLiteral("clean")), 0); + QCOMPARE(runQbs(QStringList{"--products", "myapp", "--command-echo-mode", "command-line"}), 0); + QVERIFY(m_qbsStdout.contains("plugin1")); + QVERIFY(m_qbsStdout.contains("plugin2")); + QVERIFY(m_qbsStdout.contains("plugin3")); + QVERIFY(m_qbsStdout.contains("plugin4")); +} + +void TestBlackbox::precompiledAndPrefixHeaders() +{ + QDir::setCurrent(testDataDir + "/precompiled-and-prefix-headers"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::precompiledHeaderAndRedefine() +{ + QDir::setCurrent(testDataDir + "/precompiled-headers-and-redefine"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::preventFloatingPointValues() +{ + QDir::setCurrent(testDataDir + "/prevent-floating-point-values"); + QCOMPARE(runQbs(QStringList("products.p.version:1.50")), 0); + QVERIFY2(m_qbsStdout.contains("version: 1.50"), m_qbsStdout.constData()); +} + +void TestBlackbox::probeChangeTracking() +{ + QDir::setCurrent(testDataDir + "/probe-change-tracking"); + + // Product probe disabled, other probes enabled. + QbsRunParameters params; + params.command = "resolve"; + params.arguments = QStringList("products.theProduct.runProbe:false"); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("running tlpProbe")); + QVERIFY(m_qbsStdout.contains("running subProbe")); + QVERIFY(!m_qbsStdout.contains("running productProbe")); + + // Product probe newly enabled. + params.arguments = QStringList("products.theProduct.runProbe:true"); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("running tlpProbe")); + QVERIFY(!m_qbsStdout.contains("running subProbe")); + QVERIFY(m_qbsStdout.contains("running productProbe: 12")); + + // Re-resolving with unchanged probe. + WAIT_FOR_NEW_TIMESTAMP(); + touch("probe-change-tracking.qbs"); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("Resolving")); + QVERIFY(!m_qbsStdout.contains("running tlpProbe")); + QVERIFY(!m_qbsStdout.contains("running subProbe")); + QVERIFY(!m_qbsStdout.contains("running productProbe")); + + // Re-resolving with changed configure scripts. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("probe-change-tracking.qbs", "console.info", " console.info"); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("Resolving")); + QVERIFY(m_qbsStdout.contains("running tlpProbe")); + QVERIFY(m_qbsStdout.contains("running subProbe")); + QVERIFY(m_qbsStdout.contains("running productProbe: 12")); + + // Re-resolving with added property. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("probe-change-tracking.qbs", "condition: product.runProbe", + "condition: product.runProbe\nproperty string something: 'x'"); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("Resolving")); + QVERIFY(!m_qbsStdout.contains("running tlpProbe")); + QVERIFY(!m_qbsStdout.contains("running subProbe")); + QVERIFY(m_qbsStdout.contains("running productProbe: 12")); + + // Re-resolving with changed property. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("probe-change-tracking.qbs", "'x'", "'y'"); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("Resolving")); + QVERIFY(!m_qbsStdout.contains("running tlpProbe")); + QVERIFY(!m_qbsStdout.contains("running subProbe")); + QVERIFY(m_qbsStdout.contains("running productProbe: 12")); + + // Re-resolving with removed property. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("probe-change-tracking.qbs", "property string something: 'y'", ""); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("Resolving")); + QVERIFY(!m_qbsStdout.contains("running tlpProbe")); + QVERIFY(!m_qbsStdout.contains("running subProbe")); + QVERIFY(m_qbsStdout.contains("running productProbe: 12")); + + // Re-resolving with unchanged probe again. + WAIT_FOR_NEW_TIMESTAMP(); + touch("probe-change-tracking.qbs"); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("Resolving")); + QVERIFY(!m_qbsStdout.contains("running tlpProbe")); + QVERIFY(!m_qbsStdout.contains("running subProbe")); + QVERIFY(!m_qbsStdout.contains("running productProbe")); + + // Enforcing re-running via command-line option. + params.arguments.prepend("--force-probe-execution"); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("Resolving")); + QVERIFY(m_qbsStdout.contains("running tlpProbe")); + QVERIFY(m_qbsStdout.contains("running subProbe")); + QVERIFY(m_qbsStdout.contains("running productProbe: 12")); +} + +void TestBlackbox::probeProperties() +{ + QDir::setCurrent(testDataDir + "/probeProperties"); + const QByteArray dir = QDir::cleanPath(testDataDir).toLocal8Bit() + "/probeProperties"; + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("probe1.fileName=bin/tool"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("probe1.path=" + dir), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("probe1.filePath=" + dir + "/bin/tool"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("probe2.fileName=tool"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("probe2.path=" + dir + "/bin"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("probe2.filePath=" + dir + "/bin/tool"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("probe3.fileName=tool"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("probe3.path=" + dir + "/bin"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("probe3.filePath=" + dir + "/bin/tool"), m_qbsStdout.constData()); +} + +void TestBlackbox::probesAndShadowProducts() +{ + QDir::setCurrent(testDataDir + "/probes-and-shadow-products"); + QCOMPARE(runQbs(QStringList("--log-time")), 0); + QVERIFY2(m_qbsStdout.contains("2 probes encountered, 1 configure scripts executed"), + m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + touch("probes-and-shadow-products.qbs"); + QCOMPARE(runQbs(QStringList("--log-time")), 0); + QVERIFY2(m_qbsStdout.contains("2 probes encountered, 0 configure scripts executed"), + m_qbsStdout.constData()); +} + +void TestBlackbox::probeInExportedModule() +{ + QDir::setCurrent(testDataDir + "/probe-in-exported-module"); + QCOMPARE(runQbs(QbsRunParameters(QStringList() << QStringLiteral("-f") + << QStringLiteral("probe-in-exported-module.qbs"))), 0); + QVERIFY2(m_qbsStdout.contains("found: true"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("prop: yes"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("listProp: myother,my"), m_qbsStdout.constData()); +} + +void TestBlackbox::probesAndArrayProperties() +{ + QDir::setCurrent(testDataDir + "/probes-and-array-properties"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("prop: [\"probe\"]"), m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("probes-and-array-properties.qbs", "//", ""); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("prop: [\"product\",\"probe\"]"), m_qbsStdout.constData()); +} + +void TestBlackbox::productProperties() +{ + QDir::setCurrent(testDataDir + "/productproperties"); + QCOMPARE(runQbs(QbsRunParameters(QStringList() << QStringLiteral("-f") + << QStringLiteral("productproperties.qbs"))), 0); + QVERIFY(regularFileExists(relativeExecutableFilePath("blubb_user"))); +} + +void TestBlackbox::propertyAssignmentOnNonPresentModule() +{ + QDir::setCurrent(testDataDir + "/property-assignment-on-non-present-module"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); +} + +void TestBlackbox::propertyAssignmentInFailedModule() +{ + QDir::setCurrent(testDataDir + "/property-assignment-in-failed-module"); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("modules.m.doFail:false"))), 0); + QbsRunParameters failParams; + failParams.expectFailure = true; + QVERIFY(runQbs(failParams) != 0); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("modules.m.doFail:true"))), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QEXPECT_FAIL(0, "circular dependency between module merging and validation", Continue); + QCOMPARE(runQbs(failParams), 0); +} + +void TestBlackbox::propertyChanges() +{ + QDir::setCurrent(testDataDir + "/propertyChanges"); + const QString projectFile("propertyChanges.qbs"); + QbsRunParameters params(QStringList({"-f", "propertyChanges.qbs", "qbs.enableDebugCode:true"})); + + // Initial build. + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("compiling source1.cpp")); + QVERIFY(m_qbsStdout.contains("compiling source2.cpp")); + QVERIFY(m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(m_qbsStdout.contains("compiling lib.cpp")); + QVERIFY(m_qbsStdout.contains("linking product 1.debug")); + QVERIFY(m_qbsStdout.contains("generated.txt")); + QVERIFY(m_qbsStdout.contains("Making output from input")); + QVERIFY(m_qbsStdout.contains("default value")); + QVERIFY(m_qbsStdout.contains("Making output from other output")); + QFile generatedFile(relativeProductBuildDir("generated text file") + "/generated.txt"); + QVERIFY(generatedFile.open(QIODevice::ReadOnly)); + QCOMPARE(generatedFile.readAll(), QByteArray("prefix 1contents 1suffix 1")); + generatedFile.close(); + + // Incremental build with no changes. + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compiling source1.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source2.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling lib.cpp.cpp")); + QVERIFY(!m_qbsStdout.contains("linking")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + QVERIFY(!m_qbsStdout.contains("Making output from input")); + QVERIFY(!m_qbsStdout.contains("Making output from other output")); + + // Incremental build with no changes, but updated project file timestamp. + WAIT_FOR_NEW_TIMESTAMP(); + touch(projectFile); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compiling source1.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source2.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling lib.cpp")); + QVERIFY(!m_qbsStdout.contains("linking")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + QVERIFY(!m_qbsStdout.contains("Making output from input")); + QVERIFY(!m_qbsStdout.contains("Making output from other output")); + + // Incremental build, input property changed for first product + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "blubb1", "blubb01"); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("compiling source1.cpp")); + QVERIFY(m_qbsStdout.contains("linking product 1.debug")); + QVERIFY(!m_qbsStdout.contains("linking product 2")); + QVERIFY(!m_qbsStdout.contains("linking product 3")); + QVERIFY(!m_qbsStdout.contains("linking library")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + QVERIFY(!m_qbsStdout.contains("Making output from input")); + QVERIFY(!m_qbsStdout.contains("Making output from other output")); + + // Incremental build, input property changed via project for second product. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "blubb2", "blubb02"); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("linking product 1")); + QVERIFY(m_qbsStdout.contains("compiling source2.cpp")); + QVERIFY(!m_qbsStdout.contains("linking product 3")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + QVERIFY(!m_qbsStdout.contains("Making output from input")); + QVERIFY(!m_qbsStdout.contains("Making output from other output")); + + // Incremental build, input property changed via command line for second product. + params.command = "resolve"; + params.arguments << "project.projectDefines:blubb002"; + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(!m_qbsStdout.contains("linking product 1")); + QVERIFY(m_qbsStdout.contains("compiling source2.cpp")); + QVERIFY(!m_qbsStdout.contains("linking product 3")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + params.arguments.removeLast(); + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(!m_qbsStdout.contains("linking product 1")); + QVERIFY(m_qbsStdout.contains("compiling source2.cpp")); + QVERIFY(!m_qbsStdout.contains("linking product 3")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + QVERIFY(!m_qbsStdout.contains("Making output from input")); + QVERIFY(!m_qbsStdout.contains("Making output from other output")); + + // Incremental build, input property changed via environment for third product. + params.environment.insert("QBS_BLACKBOX_DEFINE", "newvalue"); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("linking product 1")); + QVERIFY(!m_qbsStdout.contains("linking product 2")); + QVERIFY(!m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + params.environment.remove("QBS_BLACKBOX_DEFINE"); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("linking product 1")); + QVERIFY(!m_qbsStdout.contains("linking product 2")); + QVERIFY(!m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + QVERIFY(!m_qbsStdout.contains("Making output from input")); + QVERIFY(!m_qbsStdout.contains("Making output from other output")); + params.environment.insert("QBS_BLACKBOX_DEFINE", "newvalue"); + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(!m_qbsStdout.contains("linking product 1")); + QVERIFY(!m_qbsStdout.contains("linking product 2")); + QVERIFY(m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + params.environment.remove("QBS_BLACKBOX_DEFINE"); + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(!m_qbsStdout.contains("linking product 1")); + QVERIFY(!m_qbsStdout.contains("linking product 2")); + QVERIFY(m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + QVERIFY(!m_qbsStdout.contains("Making output from input")); + QVERIFY(!m_qbsStdout.contains("Making output from other output")); + + // Incremental build, module property changed via command line. + params.arguments << "qbs.enableDebugCode:false"; + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("compiling source1.cpp")); + QVERIFY(m_qbsStdout.contains("linking product 1.release")); + QVERIFY(m_qbsStdout.contains("compiling source2.cpp")); + QVERIFY(m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(m_qbsStdout.contains("compiling lib.cpp")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + params.arguments.removeLast(); + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("compiling source1.cpp")); + QVERIFY(m_qbsStdout.contains("linking product 1.debug")); + QVERIFY(m_qbsStdout.contains("compiling source2.cpp")); + QVERIFY(m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + QVERIFY(!m_qbsStdout.contains("Making output from input")); + QVERIFY(!m_qbsStdout.contains("Making output from other output")); + + // Incremental build, non-essential dependency removed. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "Depends { name: 'library' }", "// Depends { name: 'library' }"); + params.command = "build"; + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("linking product 1")); + QVERIFY(m_qbsStdout.contains("linking product 2")); + QVERIFY(!m_qbsStdout.contains("linking product 3")); + QVERIFY(!m_qbsStdout.contains("linking library")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + QVERIFY(!m_qbsStdout.contains("Making output from input")); + QVERIFY(!m_qbsStdout.contains("Making output from other output")); + + // Incremental build, prepare script of a transformer changed. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "contents 1", "contents 2"); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compiling source1.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source2.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling lib.cpp")); + QVERIFY(m_qbsStdout.contains("generated.txt")); + QVERIFY(!m_qbsStdout.contains("Making output from input")); + QVERIFY(!m_qbsStdout.contains("Making output from other output")); + QVERIFY(generatedFile.open(QIODevice::ReadOnly)); + QCOMPARE(generatedFile.readAll(), QByteArray("prefix 1contents 2suffix 1")); + generatedFile.close(); + + // Incremental build, product property used in JavaScript command changed. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "prefix 1", "prefix 2"); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compiling source1.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source2.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling lib.cpp")); + QVERIFY(m_qbsStdout.contains("generated.txt")); + QVERIFY(!m_qbsStdout.contains("Making output from input")); + QVERIFY(!m_qbsStdout.contains("Making output from other output")); + QVERIFY(generatedFile.open(QIODevice::ReadOnly)); + QCOMPARE(generatedFile.readAll(), QByteArray("prefix 2contents 2suffix 1")); + generatedFile.close(); + + // Incremental build, project property used in JavaScript command changed. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "suffix 1", "suffix 2"); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compiling source1.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source2.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling lib.cpp")); + QVERIFY(m_qbsStdout.contains("generated.txt")); + QVERIFY(!m_qbsStdout.contains("Making output from input")); + QVERIFY(!m_qbsStdout.contains("Making output from other output")); + QVERIFY(generatedFile.open(QIODevice::ReadOnly)); + QCOMPARE(generatedFile.readAll(), QByteArray("prefix 2contents 2suffix 2")); + generatedFile.close(); + + // Incremental build, module property used in JavaScript command changed. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "default value", "new value"); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compiling source1.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source2.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling lib.cpp")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + QVERIFY(m_qbsStdout.contains("Making output from input")); + QVERIFY(m_qbsStdout.contains("Making output from other output")); + QVERIFY(m_qbsStdout.contains("new value")); + + // Incremental build, prepare script of a rule in a module changed. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("modules/TestModule/module.qbs", "// console.info('Change in source code')", + "console.info('Change in source code')"); + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compiling source1.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source2.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling source3.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling lib.cpp")); + QVERIFY(!m_qbsStdout.contains("generated.txt")); + QVERIFY(m_qbsStdout.contains("Making output from input")); + QVERIFY(m_qbsStdout.contains("Making output from other output")); +} + +void TestBlackbox::propertyEvaluationContext() +{ + const QString testDir = testDataDir + "/property-evaluation-context"; + QDir::setCurrent(testDir); + QCOMPARE(runQbs(), 0); + QCOMPARE(m_qbsStdout.count("base.productInBase evaluated in: myapp"), 1); + QCOMPARE(m_qbsStdout.count("base.productInTop evaluated in: myapp"), 1); + QCOMPARE(m_qbsStdout.count("top.productInExport evaluated in: mylib"), 1); + QCOMPARE(m_qbsStdout.count("top.productInTop evaluated in: myapp"), 1); +} + +void TestBlackbox::qtBug51237() +{ + const QString profileName = "profile-qtBug51237"; + const QString propertyName = "mymodule.theProperty"; + { + const SettingsPtr s = settings(); + Profile profile(profileName, s.get()); + profile.setValue(propertyName, QStringList()); + } + QDir::setCurrent(testDataDir + "/QTBUG-51237"); + QbsRunParameters params; + params.profile = profileName; + QCOMPARE(runQbs(params), 0); +} + +void TestBlackbox::dynamicMultiplexRule() +{ + const QString testDir = testDataDir + "/dynamicMultiplexRule"; + QDir::setCurrent(testDir); + QCOMPARE(runQbs(), 0); + const QString outputFilePath = relativeProductBuildDir("dynamicMultiplexRule") + "/stuff-from-3-inputs"; + QVERIFY(regularFileExists(outputFilePath)); + WAIT_FOR_NEW_TIMESTAMP(); + touch("two.txt"); + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(outputFilePath)); +} + +void TestBlackbox::dynamicProject() +{ + const QString testDir = testDataDir + "/dynamic-project"; + QDir::setCurrent(testDir); + QCOMPARE(runQbs(), 0); + QCOMPARE(m_qbsStdout.count("compiling main.cpp"), 2); +} + +void TestBlackbox::dynamicRuleOutputs() +{ + const QString testDir = testDataDir + "/dynamicRuleOutputs"; + QDir::setCurrent(testDir); + if (QFile::exists("work")) + rmDirR("work"); + QDir().mkdir("work"); + ccp("before", "work"); + QDir::setCurrent(testDir + "/work"); + QCOMPARE(runQbs(), 0); + + const QString appFile = relativeExecutableFilePath("genlexer"); + const QString headerFile1 = relativeProductBuildDir("genlexer") + "/GeneratedFiles/numberscanner.h"; + const QString sourceFile1 = relativeProductBuildDir("genlexer") + "/GeneratedFiles/numberscanner.c"; + const QString sourceFile2 = relativeProductBuildDir("genlexer") + "/GeneratedFiles/lex.yy.c"; + + // Check build #1: source and header file name are specified in numbers.l + QVERIFY(regularFileExists(appFile)); + QVERIFY(regularFileExists(headerFile1)); + QVERIFY(regularFileExists(sourceFile1)); + QVERIFY(!QFile::exists(sourceFile2)); + + QDateTime appFileTimeStamp1 = QFileInfo(appFile).lastModified(); + WAIT_FOR_NEW_TIMESTAMP(); + copyFileAndUpdateTimestamp("../after/numbers.l", "numbers.l"); + QCOMPARE(runQbs(), 0); + + // Check build #2: no file names are specified in numbers.l + // flex will default to lex.yy.c without header file. + QDateTime appFileTimeStamp2 = QFileInfo(appFile).lastModified(); + QVERIFY(appFileTimeStamp1 < appFileTimeStamp2); + QVERIFY(!QFile::exists(headerFile1)); + QVERIFY(!QFile::exists(sourceFile1)); + QVERIFY(regularFileExists(sourceFile2)); + + WAIT_FOR_NEW_TIMESTAMP(); + copyFileAndUpdateTimestamp("../before/numbers.l", "numbers.l"); + QCOMPARE(runQbs(), 0); + + // Check build #3: source and header file name are specified in numbers.l + QDateTime appFileTimeStamp3 = QFileInfo(appFile).lastModified(); + QVERIFY(appFileTimeStamp2 < appFileTimeStamp3); + QVERIFY(regularFileExists(appFile)); + QVERIFY(regularFileExists(headerFile1)); + QVERIFY(regularFileExists(sourceFile1)); + QVERIFY(!QFile::exists(sourceFile2)); +} + +void TestBlackbox::emptyProfile() +{ + QDir::setCurrent(testDataDir + "/empty-profile"); + + const SettingsPtr s = settings(); + const Profile buildProfile(profileName(), s.get()); + bool isMsvc = false; + auto toolchainType = buildProfile.value(QStringLiteral("qbs.toolchainType")).toString(); + QbsRunParameters params; + params.profile = "none"; + + if (toolchainType.isEmpty()) { + const auto toolchain = buildProfile.value(QStringLiteral("qbs.toolchain")).toStringList(); + if (!toolchain.isEmpty()) + toolchainType = toolchain.first(); + } + if (!toolchainType.isEmpty()) { + params.arguments = QStringList{QStringLiteral("qbs.toolchainType:") + toolchainType}; + isMsvc = toolchainType == "msvc" || toolchainType == "clang-cl"; + } + + if (!isMsvc) { + const auto tcPath = + QDir::toNativeSeparators( + buildProfile.value(QStringLiteral("cpp.toolchainInstallPath")).toString()); + auto paths = params.environment.value(QStringLiteral("PATH")) + .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS); + if (!tcPath.isEmpty() && !paths.contains(tcPath)) { + paths.prepend(tcPath); + params.environment.insert( + QStringLiteral("PATH"), paths.join(HostOsInfo::pathListSeparator())); + } + } + QCOMPARE(runQbs(params), 0); +} + +void TestBlackbox::erroneousFiles_data() +{ + QTest::addColumn("errorMessage"); + QTest::newRow("nonexistentWorkingDir") + << "The working directory '.does.not.exist' for process '.*ls.*' is invalid."; + QTest::newRow("outputArtifacts-missing-filePath") + << "Error in Rule\\.outputArtifacts\\[0\\]\n\r?" + "Property filePath must be a non-empty string\\."; + QTest::newRow("outputArtifacts-missing-fileTags") + << "Error in Rule\\.outputArtifacts\\[0\\]\n\r?" + "Property fileTags for artifact 'outputArtifacts-missing-fileTags\\.txt' " + "must be a non-empty string list\\."; + QTest::newRow("texttemplate-unknown-placeholder") + << "Placeholder 'what' is not defined in textemplate.dict for 'boom.txt.in'"; + QTest::newRow("tag-mismatch") + << "tag-mismatch.qbs:8:18.*Artifact '.*dummy1' has undeclared file tags " + "\\[\"y\",\"z\"\\]."; +} + +void TestBlackbox::erroneousFiles() +{ + QFETCH(QString, errorMessage); + QDir::setCurrent(testDataDir + "/erroneous/" + QTest::currentDataTag()); + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QString err = QString::fromLocal8Bit(m_qbsStderr); + if (!err.contains(QRegExp(errorMessage))) { + qDebug().noquote() << "Output: " << err; + qDebug().noquote() << "Expected: " << errorMessage; + QFAIL("Unexpected error message."); + } +} + +void TestBlackbox::errorInfo() +{ + QDir::setCurrent(testDataDir + "/error-info"); + QCOMPARE(runQbs(), 0); + + QbsRunParameters resolveParams; + QbsRunParameters buildParams; + buildParams.expectFailure = true; + + resolveParams.command = "resolve"; + resolveParams.arguments = QStringList() << "project.fail1:true"; + QCOMPARE(runQbs(resolveParams), 0); + buildParams.arguments = resolveParams.arguments; + QVERIFY(runQbs(buildParams) != 0); + QVERIFY2(m_qbsStderr.contains("error-info.qbs:24"), m_qbsStderr); + + resolveParams.arguments = QStringList() << "project.fail2:true"; + QCOMPARE(runQbs(resolveParams), 0); + buildParams.arguments = resolveParams.arguments; + QVERIFY(runQbs(buildParams) != 0); + QVERIFY2(m_qbsStderr.contains("error-info.qbs:36"), m_qbsStderr); + + resolveParams.arguments = QStringList() << "project.fail3:true"; + QCOMPARE(runQbs(resolveParams), 0); + buildParams.arguments = resolveParams.arguments; + QVERIFY(runQbs(buildParams) != 0); + QVERIFY2(m_qbsStderr.contains("error-info.qbs:51"), m_qbsStderr); + + resolveParams.arguments = QStringList() << "project.fail4:true"; + QCOMPARE(runQbs(resolveParams), 0); + buildParams.arguments = resolveParams.arguments; + QVERIFY(runQbs(buildParams) != 0); + QVERIFY2(m_qbsStderr.contains("error-info.qbs:66"), m_qbsStderr); + + resolveParams.arguments = QStringList() << "project.fail5:true"; + QCOMPARE(runQbs(resolveParams), 0); + buildParams.arguments = resolveParams.arguments; + QVERIFY(runQbs(buildParams) != 0); + QVERIFY2(m_qbsStderr.contains("helper.js:4"), m_qbsStderr); + + resolveParams.arguments = QStringList() << "project.fail6:true"; + QCOMPARE(runQbs(resolveParams), 0); + buildParams.arguments = resolveParams.arguments; + QVERIFY(runQbs(buildParams) != 0); + QVERIFY2(m_qbsStderr.contains("helper.js:8"), m_qbsStderr); + + resolveParams.arguments = QStringList() << "project.fail7:true"; + QCOMPARE(runQbs(resolveParams), 0); + buildParams.arguments = resolveParams.arguments; + QVERIFY(runQbs(buildParams) != 0); + QVERIFY2(m_qbsStderr.contains("JavaScriptCommand.sourceCode"), m_qbsStderr); + QVERIFY2(m_qbsStderr.contains("error-info.qbs:57"), m_qbsStderr); +} + +void TestBlackbox::escapedLinkerFlags() +{ + const SettingsPtr s = settings(); + const Profile buildProfile(profileName(), s.get()); + const QStringList toolchain = profileToolchain(buildProfile); + if (!toolchain.contains("gcc")) + QSKIP("escaped linker flags test only applies with gcc and GNU ld"); + if (targetOs() == HostOsInfo::HostOsMacos) + QSKIP("Does not apply on macOS"); + QDir::setCurrent(testDataDir + "/escaped-linker-flags"); + QbsRunParameters params(QStringList("products.app.escapeLinkerFlags:false")); + QCOMPARE(runQbs(params), 0); + params.command = "resolve"; + params.arguments = QStringList() << "products.app.escapeLinkerFlags:true"; + QCOMPARE(runQbs(params), 0); + params.command = "build"; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("Encountered escaped linker flag"), m_qbsStderr.constData()); +} + +void TestBlackbox::exportedDependencyInDisabledProduct() +{ + QDir::setCurrent(testDataDir + "/exported-dependency-in-disabled-product"); + QFETCH(QString, depCondition); + QFETCH(bool, compileExpected); + rmDirR(relativeBuildDir()); + const QString propertyArg = "products.dep.conditionString:" + depCondition; + QCOMPARE(runQbs(QStringList(propertyArg)), 0); + QCOMPARE(m_qbsStdout.contains("compiling"), compileExpected); +} + +void TestBlackbox::exportedDependencyInDisabledProduct_data() +{ + QTest::addColumn("depCondition"); + QTest::addColumn("compileExpected"); + QTest::newRow("dependency enabled") << "true" << true; + QTest::newRow("dependency directly disabled") << "false" << false; + QTest::newRow("dependency disabled via non-present module") << "nosuchmodule.present" << false; + QTest::newRow("dependency disabled via failed module") << "broken.present" << false; +} + +void TestBlackbox::exportedPropertyInDisabledProduct() +{ + QDir::setCurrent(testDataDir + "/exported-property-in-disabled-product"); + QFETCH(QString, depCondition); + QFETCH(bool, successExpected); + const QString propertyArg = "products.dep.conditionString:" + depCondition; + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList(propertyArg))), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QbsRunParameters buildParams; + buildParams.expectFailure = !successExpected; + QCOMPARE(runQbs(buildParams) == 0, successExpected); +} + +void TestBlackbox::exportedPropertyInDisabledProduct_data() +{ + QTest::addColumn("depCondition"); + QTest::addColumn("successExpected"); + QTest::newRow("dependency enabled") << "true" << false; + QTest::newRow("dependency directly disabled") << "false" << true; + QTest::newRow("dependency disabled via non-present module") << "nosuchmodule.present" << true; + QTest::newRow("dependency disabled via failed module") << "broken.present" << true; +} + +void TestBlackbox::systemRunPaths() +{ + const SettingsPtr s = settings(); + const Profile buildProfile(profileName(), s.get()); + switch (targetOs()) { + case HostOsInfo::HostOsLinux: + case HostOsInfo::HostOsMacos: + case HostOsInfo::HostOsOtherUnix: + break; + default: + QSKIP("only applies on Unix"); + } + + const QString lddFilePath = findExecutable(QStringList() << "ldd"); + if (lddFilePath.isEmpty()) + QSKIP("ldd not found"); + QDir::setCurrent(testDataDir + "/system-run-paths"); + QFETCH(bool, setRunPaths); + rmDirR(relativeBuildDir()); + QbsRunParameters params; + params.arguments << QString("project.setRunPaths:") + (setRunPaths ? "true" : "false"); + QCOMPARE(runQbs(params), 0); + QProcess ldd; + ldd.start(lddFilePath, QStringList() << relativeExecutableFilePath("app")); + QVERIFY2(ldd.waitForStarted(), qPrintable(ldd.errorString())); + QVERIFY2(ldd.waitForFinished(), qPrintable(ldd.errorString())); + QVERIFY2(ldd.exitCode() == 0, ldd.readAllStandardError().constData()); + const QByteArray output = ldd.readAllStandardOutput(); + const QList outputLines = output.split('\n'); + QByteArray libLine; + for (const auto &line : outputLines) { + if (line.contains("theLib")) { + libLine = line; + break; + } + } + QVERIFY2(!libLine.isEmpty(), output.constData()); + QVERIFY2(setRunPaths == libLine.contains("not found"), libLine.constData()); +} + +void TestBlackbox::systemRunPaths_data() +{ + QTest::addColumn("setRunPaths"); + QTest::newRow("do not set system run paths") << false; + QTest::newRow("do set system run paths") << true; +} + +void TestBlackbox::exportRule() +{ + QDir::setCurrent(testDataDir + "/export-rule"); + QbsRunParameters params(QStringList{"modules.blubber.enableTagger:false"}); + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + params.command = "resolve"; + params.arguments = QStringList{"modules.blubber.enableTagger:true"}; + params.expectFailure = false; + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Creating C++ source file"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("compiling myapp.cpp"), m_qbsStdout.constData()); +} + +void TestBlackbox::exportToOutsideSearchPath() +{ + QDir::setCurrent(testDataDir + "/export-to-outside-searchpath"); + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("Dependency 'aModule' not found for product 'theProduct'."), + m_qbsStderr.constData()); +} + +void TestBlackbox::exportsPkgconfig() +{ + QDir::setCurrent(testDataDir + "/exports-pkgconfig"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Creating TheFirstLib.pc"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("Creating TheSecondLib.pc"), m_qbsStdout.constData()); + QFile sourcePcFile(HostOsInfo::isWindowsHost() ? "TheFirstLib_windows.pc" : "TheFirstLib.pc"); + QString generatedPcFilePath = relativeProductBuildDir("TheFirstLib") + "/TheFirstLib.pc"; + QFile generatedPcFile(generatedPcFilePath); + QVERIFY2(sourcePcFile.open(QIODevice::ReadOnly), qPrintable(sourcePcFile.errorString())); + QVERIFY2(generatedPcFile.open(QIODevice::ReadOnly), qPrintable(generatedPcFile.errorString())); + QCOMPARE(generatedPcFile.readAll().replace("\r", ""), sourcePcFile.readAll().replace("\r", "")); + sourcePcFile.close(); + generatedPcFile.close(); + TEXT_FILE_COMPARE(relativeProductBuildDir("TheSecondLib") + "/TheSecondLib.pc", + "TheSecondLib.pc"); + WAIT_FOR_NEW_TIMESTAMP(); + touch("firstlib.cpp"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("linking"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("Creating TheFirstLib.pc"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("Creating TheSecondLib.pc"), m_qbsStdout.constData()); +} + +void TestBlackbox::exportsQbs() +{ + QDir::setCurrent(testDataDir + "/exports-qbs"); + + QCOMPARE(runQbs({"resolve", {"-f", "exports-qbs.qbs"}}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + // First we build exportable products and use them (as products) inside + // the original project. + QCOMPARE(runQbs(QStringList{"-f", "exports-qbs.qbs", "--command-echo-mode", "command-line"}), + 0); + QVERIFY2(m_qbsStdout.contains("somelocaldir"), m_qbsStdout.constData()); + + // Now we build an external product against the modules that were just installed. + // We try debug and release mode; one module exists for each of them. + QbsRunParameters paramsExternalBuild(QStringList{"-f", "consumer.qbs", + "--command-echo-mode", "command-line", + "modules.qbs.buildVariant:debug",}); + paramsExternalBuild.buildDirectory = QDir::currentPath() + "/external-consumer-debug"; + QCOMPARE(runQbs(paramsExternalBuild), 0); + QVERIFY2(!m_qbsStdout.contains("somelocaldir"), m_qbsStdout.constData()); + + paramsExternalBuild.arguments = QStringList{"-f", "consumer.qbs", + "modules.qbs.buildVariant:release"}; + paramsExternalBuild.buildDirectory = QDir::currentPath() + "/external-consumer-release"; + QCOMPARE(runQbs(paramsExternalBuild), 0); + + // Trying to build with an unsupported build variant must fail. + paramsExternalBuild.arguments = QStringList{"-f", "consumer.qbs", + "modules.qbs.buildVariant:unknown"}; + paramsExternalBuild.buildDirectory = QDir::currentPath() + "/external-consumer-profile"; + paramsExternalBuild.expectFailure = true; + QVERIFY(runQbs(paramsExternalBuild) != 0); + QVERIFY2(m_qbsStderr.contains("MyLib could not be loaded"), m_qbsStderr.constData()); + + // Removing the condition from the generated module leaves us with two conflicting + // candidates. + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList{ "-f", "exports-qbs.qbs", + "modules.Exporter.qbs.additionalContent:''"})), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(runQbs(paramsExternalBuild) != 0); + QVERIFY2(m_qbsStderr.contains("There is more than one equally prioritized candidate " + "for module 'MyLib'."), m_qbsStderr.constData()); + + // Change tracking for accesses to product.exports (negative). + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList{"-f", "exports-qbs.qbs"})), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Creating MyTool.qbs"), m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + touch("exports-qbs.qbs"); + QCOMPARE(runQbs(QStringList({"-p", "MyTool"})), 0); + QVERIFY2(!m_qbsStdout.contains("Creating MyTool.qbs"), m_qbsStdout.constData()); + + // Rebuilding the target binary should not cause recreating the module file. + WAIT_FOR_NEW_TIMESTAMP(); + touch("mylib.cpp"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.count("linking") >= 2, m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("Creating MyLib"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("Creating MyTool.qbs"), m_qbsStdout.constData()); + + // Changing a setting that influences the name of a target artifact should cause + // recreating the module file. + const QbsRunParameters resolveParams("resolve", QStringList{"-f", "exports-qbs.qbs", + "modules.cpp.dynamicLibrarySuffix:.blubb"}); + QCOMPARE(runQbs(resolveParams), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.count("linking") >= 2, m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.count("Creating MyLib") == 2, m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("Creating MyTool.qbs"), m_qbsStdout.constData()); + + // Change tracking for accesses to product.exports (positive). + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("tool.qbs", "product.toolTags", "[]"); + QCOMPARE(runQbs(QStringList({"-p", "MyTool"})), 0); + QVERIFY2(m_qbsStdout.contains("Creating MyTool.qbs"), m_qbsStdout.constData()); +} + +void TestBlackbox::externalLibs() +{ + QDir::setCurrent(testDataDir + "/external-libs"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::fileDependencies() +{ + QDir::setCurrent(testDataDir + "/fileDependencies"); + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("compiling narf.cpp")); + QVERIFY(m_qbsStdout.contains("compiling zort.cpp")); + const QString productFileName = relativeExecutableFilePath("myapp"); + QVERIFY2(regularFileExists(productFileName), qPrintable(productFileName)); + + // Incremental build without changes. + QCOMPARE(runQbs(), 0); + QVERIFY(!m_qbsStdout.contains("compiling")); + QVERIFY(!m_qbsStdout.contains("linking")); + + // Incremental build with changed file dependency. + WAIT_FOR_NEW_TIMESTAMP(); + touch("awesomelib/awesome.h"); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("compiling narf.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling zort.cpp")); + + // Incremental build with changed 2nd level file dependency. + WAIT_FOR_NEW_TIMESTAMP(); + touch("awesomelib/magnificent.h"); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("compiling narf.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling zort.cpp")); + + // Change the product in between to force the list of dependencies to get rescued. + REPLACE_IN_FILE("fileDependencies.qbs", "//", ""); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY(!m_qbsStdout.contains("compiling narf.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling zort.cpp")); + WAIT_FOR_NEW_TIMESTAMP(); + touch("awesomelib/magnificent.h"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling narf.cpp [myapp]"), m_qbsStdout.constData()); + QVERIFY(!m_qbsStdout.contains("compiling zort.cpp")); +} + +void TestBlackbox::fileTagsFilterMerging() +{ + QDir::setCurrent(testDataDir + "/filetagsfilter-merging"); + QCOMPARE(runQbs(QStringList{"-f", "filetagsfilter-merging.qbs"}), 0); + const QString installedApp = defaultInstallRoot + "/myapp/binDir/" + + QFileInfo(relativeExecutableFilePath("myapp")).fileName(); + QVERIFY2(QFile::exists(installedApp), qPrintable(installedApp)); + const QString otherOutput = relativeProductBuildDir("myapp") + "/myapp.txt"; + QVERIFY2(QFile::exists(otherOutput), qPrintable(otherOutput)); +} + +void TestBlackbox::freedesktop() +{ + if (!HostOsInfo::isAnyUnixHost()) + QSKIP("only applies on Unix"); + if (HostOsInfo::isMacosHost()) + QSKIP("Does not apply on macOS"); + QDir::setCurrent(testDataDir + "/freedesktop"); + QCOMPARE(runQbs(), 0); + + // Check desktop file + QString desktopFilePath = + defaultInstallRoot + "/usr/local/share/applications/myapp.desktop"; + QVERIFY(QFile::exists(desktopFilePath)); + QFile desktopFile(desktopFilePath); + QVERIFY2(desktopFile.open(QIODevice::ReadOnly), qPrintable(desktopFile.errorString())); + QByteArrayList lines = desktopFile.readAll().split('\n'); + // Automatically filled line: + QVERIFY(lines.contains("Exec=main")); + // Name specified in `freedesktop.name` property + QVERIFY(lines.contains("Name=My App")); + // Overridden line: + QVERIFY(lines.contains("Icon=myapp.png")); + // Untouched line: + QVERIFY(lines.contains("Terminal=false")); + + // Check AppStream file + QVERIFY(QFile::exists(defaultInstallRoot + + "/usr/local/share/metainfo/myapp.appdata.xml")); + + // Check icon file + QVERIFY(QFile::exists(defaultInstallRoot + + "/usr/local/share/icons/hicolor/scalable/apps/myapp.png")); +} + +void TestBlackbox::installedTransformerOutput() +{ + QDir::setCurrent(testDataDir + "/installed-transformer-output"); + QCOMPARE(runQbs(), 0); + const QString installedFilePath = defaultInstallRoot + "/textfiles/HelloWorld.txt"; + QVERIFY2(QFile::exists(installedFilePath), qPrintable(installedFilePath)); +} + +void TestBlackbox::installLocations_data() +{ + QTest::addColumn("binDir"); + QTest::addColumn("dllDir"); + QTest::addColumn("libDir"); + QTest::addColumn("pluginDir"); + QTest::addColumn("dsymDir"); + QTest::newRow("explicit values") + << QString("bindir") + << QString("dlldir") + << QString("libdir") + << QString("pluginDir") + << QString("dsymDir"); + QTest::newRow("default values") + << QString() << QString() << QString() << QString() << QString(); +} + +void TestBlackbox::installLocations() +{ + QDir::setCurrent(testDataDir + "/install-locations"); + QFETCH(QString, binDir); + QFETCH(QString, dllDir); + QFETCH(QString, libDir); + QFETCH(QString, pluginDir); + QFETCH(QString, dsymDir); + QbsRunParameters params("resolve"); + if (!binDir.isEmpty()) + params.arguments.push_back("products.theapp.installDir:" + binDir); + if (!dllDir.isEmpty()) + params.arguments.push_back("products.thelib.installDir:" + dllDir); + if (!libDir.isEmpty()) + params.arguments.push_back("products.thelib.importLibInstallDir:" + libDir); + if (!pluginDir.isEmpty()) + params.arguments.push_back("products.theplugin.installDir:" + pluginDir); + if (!dsymDir.isEmpty()) { + params.arguments.push_back("products.theapp.debugInformationInstallDir:" + dsymDir); + params.arguments.push_back("products.thelib.debugInformationInstallDir:" + dsymDir); + params.arguments.push_back("products.theplugin.debugInformationInstallDir:" + dsymDir); + } + QCOMPARE(runQbs(params), 0); + const bool isWindows = m_qbsStdout.contains("is windows"); + const bool isDarwin = m_qbsStdout.contains("is darwin"); + const bool isMac = m_qbsStdout.contains("is mac"); + const bool isUnix = m_qbsStdout.contains("is unix"); + const bool isMingw = m_qbsStdout.contains("is mingw"); + QVERIFY(isWindows || isDarwin || isUnix); + QCOMPARE(runQbs(QbsRunParameters(QStringList("--clean-install-root"))), 0); + + struct BinaryInfo + { + QString fileName; + QString installDir; + QString subDir; + + QString absolutePath(const QString &prefix) const + { + return QDir::cleanPath(prefix + '/' + installDir + '/' + subDir + '/' + fileName); + } + }; + + const BinaryInfo dll = { + isWindows ? "thelib.dll" : isDarwin ? "thelib" : "libthelib.so", + dllDir.isEmpty() + ? (isDarwin ? "/Library/Frameworks" : (isWindows ? "/bin" : "/lib")) + : dllDir, + isDarwin ? "thelib.framework" : "" + }; + const BinaryInfo dllDsym = { + isWindows + ? (!isMingw ? "thelib.pdb" : "thelib.dll.debug") + : isDarwin ? "thelib.framework.dSYM" : "libthelib.so.debug", + dsymDir.isEmpty() ? dll.installDir : dsymDir, + {} + }; + const BinaryInfo plugin = { + isWindows ? "theplugin.dll" : isDarwin ? "theplugin" : "libtheplugin.so", + pluginDir.isEmpty() ? dll.installDir : pluginDir, + isDarwin ? (isMac ? "theplugin.bundle/Contents/MacOS" : "theplugin.bundle") : "" + }; + const BinaryInfo pluginDsym = { + isWindows + ? (!isMingw ? "theplugin.pdb" : "theplugin.dll.debug") + : isDarwin ? "theplugin.bundle.dSYM" : "libtheplugin.so.debug", + dsymDir.isEmpty() ? plugin.installDir : dsymDir, + {} + }; + const BinaryInfo app = { + isWindows ? "theapp.exe" : "theapp", + binDir.isEmpty() ? (isDarwin ? "/Applications" : "/bin") : binDir, + isDarwin ? (isMac ? "theapp.app/Contents/MacOS" : "theapp.app") : "" + }; + const BinaryInfo appDsym = { + isWindows + ? (!isMingw ? "theapp.pdb" : "theapp.exe.debug") + : isDarwin ? "theapp.app.dSYM" : "theapp.debug", + dsymDir.isEmpty() ? app.installDir : dsymDir, + {} + }; + + const QString installRoot = QDir::currentPath() + "/default/install-root"; + const QString installPrefix = isWindows ? QString() : "/usr/local"; + const QString fullInstallPrefix = installRoot + '/' + installPrefix + '/'; + const QString appFilePath = app.absolutePath(fullInstallPrefix); + QVERIFY2(QFile::exists(appFilePath), qPrintable(appFilePath)); + const QString dllFilePath = dll.absolutePath(fullInstallPrefix); + QVERIFY2(QFile::exists(dllFilePath), qPrintable(dllFilePath)); + if (isWindows) { + const BinaryInfo lib = { + "thelib.lib", + libDir.isEmpty() ? "/lib" : libDir, + "" + }; + const QString libFilePath = lib.absolutePath(fullInstallPrefix); + QVERIFY2(QFile::exists(libFilePath), qPrintable(libFilePath)); + } + const QString pluginFilePath = plugin.absolutePath(fullInstallPrefix); + QVERIFY2(QFile::exists(pluginFilePath), qPrintable(pluginFilePath)); + + const QString appDsymFilePath = appDsym.absolutePath(fullInstallPrefix); + QVERIFY2(QFileInfo(appDsymFilePath).exists(), qPrintable(appDsymFilePath)); + const QString dllDsymFilePath = dllDsym.absolutePath(fullInstallPrefix); + QVERIFY2(QFileInfo(dllDsymFilePath).exists(), qPrintable(dllDsymFilePath)); + const QString pluginDsymFilePath = pluginDsym.absolutePath(fullInstallPrefix); + QVERIFY2(QFile::exists(pluginDsymFilePath), qPrintable(pluginDsymFilePath)); +} + +void TestBlackbox::inputsFromDependencies() +{ + QDir::setCurrent(testDataDir + "/inputs-from-dependencies"); + QCOMPARE(runQbs(), 0); + const QList output = m_qbsStdout.trimmed().split('\n'); + QVERIFY2(output.contains((QDir::currentPath() + "/file1.txt").toUtf8()), + m_qbsStdout.constData()); + QVERIFY2(output.contains((QDir::currentPath() + "/file2.txt").toUtf8()), + m_qbsStdout.constData()); + QVERIFY2(output.contains((QDir::currentPath() + "/file3.txt").toUtf8()), + m_qbsStdout.constData()); + QVERIFY2(!output.contains((QDir::currentPath() + "/file4.txt").toUtf8()), + m_qbsStdout.constData()); +} + +void TestBlackbox::installPackage() +{ + if (HostOsInfo::hostOs() == HostOsInfo::HostOsWindows) + QSKIP("Beware of the msys tar"); + QString binary = findArchiver("tar"); + if (binary.isEmpty()) + QSKIP("tar not found"); + MacosTarHealer tarHealer; + QDir::setCurrent(testDataDir + "/installpackage"); + QCOMPARE(runQbs(), 0); + const QString tarFilePath = relativeProductBuildDir("tar-package") + "/tar-package.tar.gz"; + QVERIFY2(regularFileExists(tarFilePath), qPrintable(tarFilePath)); + QProcess tarList; + tarList.start(binary, QStringList() << "tf" << tarFilePath); + QVERIFY2(tarList.waitForStarted(), qPrintable(tarList.errorString())); + QVERIFY2(tarList.waitForFinished(), qPrintable(tarList.errorString())); + const QList outputLines = tarList.readAllStandardOutput().split('\n'); + QList cleanOutputLines; + for (const QByteArray &line : outputLines) { + const QByteArray trimmedLine = line.trimmed(); + if (!trimmedLine.isEmpty()) + cleanOutputLines.push_back(trimmedLine); + } + QCOMPARE(cleanOutputLines.size(), 3); + for (const QByteArray &line : qAsConst(cleanOutputLines)) { + QVERIFY2(line.contains("public_tool") || line.contains("mylib") || line.contains("lib.h"), + line.constData()); + } +} + +void TestBlackbox::installRootFromProjectFile() +{ + QDir::setCurrent(testDataDir + "/install-root-from-project-file"); + const QString installRoot = QDir::currentPath() + '/' + relativeBuildDir() + + "/my-install-root/"; + QCOMPARE(runQbs(QbsRunParameters(QStringList("products.p.installRoot:" + installRoot))), 0); + const QString installedFile = installRoot + "/install-prefix/install-dir/file.txt"; + QVERIFY2(QFile::exists(installedFile), qPrintable(installedFile)); +} + +void TestBlackbox::installable() +{ + QDir::setCurrent(testDataDir + "/installable"); + QCOMPARE(runQbs(), 0); + QFile installList(relativeProductBuildDir("install-list") + "/installed-files.txt"); + QVERIFY2(installList.open(QIODevice::ReadOnly), qPrintable(installList.errorString())); + QCOMPARE(installList.readAll().count('\n'), 2); +} + +void TestBlackbox::installableAsAuxiliaryInput() +{ + QDir::setCurrent(testDataDir + "/installable-as-auxiliary-input"); + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QCOMPARE(runQbs(QbsRunParameters("run")), 0); + QVERIFY2(m_qbsStdout.contains("f-impl"), m_qbsStdout.constData()); +} + +void TestBlackbox::installTree() +{ + QDir::setCurrent(testDataDir + "/install-tree"); + QbsRunParameters params; + params.command = "install"; + QCOMPARE(runQbs(params), 0); + const QString installRoot = relativeBuildDir() + "/install-root/"; + QVERIFY(QFile::exists(installRoot + "content/foo.txt")); + QVERIFY(QFile::exists(installRoot + "content/subdir1/bar.txt")); + QVERIFY(QFile::exists(installRoot + "content/subdir2/baz.txt")); +} + +void TestBlackbox::invalidCommandProperty_data() +{ + QTest::addColumn("errorType"); + + QTest::newRow("assigning QObject") << QString("qobject"); + QTest::newRow("assigning input artifact") << QString("input"); + QTest::newRow("assigning other artifact") << QString("artifact"); +} + +void TestBlackbox::invalidCommandProperty() +{ + QDir::setCurrent(testDataDir + "/invalid-command-property"); + QFETCH(QString, errorType); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("products.p.errorType:" + errorType))), + 0); + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("unsuitable"), m_qbsStderr.constData()); +} + +void TestBlackbox::invalidLibraryNames() +{ + QDir::setCurrent(testDataDir + "/invalid-library-names"); + QFETCH(QString, index); + QFETCH(bool, success); + QFETCH(QStringList, diagnostics); + QbsRunParameters params(QStringList("project.valueIndex:" + index)); + params.expectFailure = !success; + QCOMPARE(runQbs(params) == 0, success); + for (const QString &diag : qAsConst(diagnostics)) + QVERIFY2(m_qbsStderr.contains(diag.toLocal8Bit()), m_qbsStderr.constData()); +} + +void TestBlackbox::invalidLibraryNames_data() +{ + QTest::addColumn("index"); + QTest::addColumn("success"); + QTest::addColumn("diagnostics"); + + QTest::newRow("null") << "0" << false << QStringList("is null"); + QTest::newRow("undefined") << "1" << false << QStringList("is undefined"); + QTest::newRow("number") << "2" << false << QStringList("does not have string type"); + QTest::newRow("array") << "3" << false << QStringList("does not have string type"); + QTest::newRow("empty string") << "4" << true << (QStringList() + << "WARNING: Removing empty string from value of property " + "'cpp.dynamicLibraries' in product 'invalid-library-names'." + << "WARNING: Removing empty string from value of property " + "'cpp.staticLibraries' in product 'invalid-library-names'."); +} + +void TestBlackbox::invalidExtensionInstantiation() +{ + rmDirR(relativeBuildDir()); + QDir::setCurrent(testDataDir + "/invalid-extension-instantiation"); + QbsRunParameters params; + params.expectFailure = true; + params.arguments << (QString("products.theProduct.extension:") + QTest::currentDataTag()); + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("invalid-extension-instantiation.qbs:17") + && m_qbsStderr.contains('\'' + QByteArray(QTest::currentDataTag()) + + "' cannot be instantiated"), + m_qbsStderr.constData()); +} + +void TestBlackbox::invalidExtensionInstantiation_data() +{ + QTest::addColumn("dummy"); + + QTest::newRow("Environment"); + QTest::newRow("File"); + QTest::newRow("FileInfo"); + QTest::newRow("Utilities"); +} + +void TestBlackbox::invalidInstallDir() +{ + QDir::setCurrent(testDataDir + "/invalid-install-dir"); + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("outside of install root"), m_qbsStderr.constData()); +} + +void TestBlackbox::cli() +{ + int status; + findCli(&status); + QCOMPARE(status, 0); + + const SettingsPtr s = settings(); + Profile p("qbs_autotests-cli", s.get()); + const QStringList toolchain = profileToolchain(p); + if (!p.exists() || !(toolchain.contains("dotnet") || toolchain.contains("mono"))) + QSKIP("No suitable Common Language Infrastructure test profile"); + + QDir::setCurrent(testDataDir + "/cli"); + QbsRunParameters params(QStringList() << "-f" << "dotnettest.qbs"); + params.profile = p.name(); + + status = runQbs(params); + if (p.value("cli.toolchainInstallPath").toString().isEmpty() + && status != 0 && m_qbsStderr.contains("toolchainInstallPath")) + QSKIP("cli.toolchainInstallPath not set and automatic detection failed"); + + QCOMPARE(status, 0); + rmDirR(relativeBuildDir()); + + QbsRunParameters params2(QStringList() << "-f" << "fshello.qbs"); + params2.profile = p.name(); + QCOMPARE(runQbs(params2), 0); + rmDirR(relativeBuildDir()); +} + +void TestBlackbox::combinedSources() +{ + QDir::setCurrent(testDataDir + "/combined-sources"); + QbsRunParameters params(QStringList("modules.cpp.combineCxxSources:false")); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("compiling main.cpp")); + QVERIFY(m_qbsStdout.contains("compiling combinable.cpp")); + QVERIFY(m_qbsStdout.contains("compiling uncombinable.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling amalgamated_theapp.cpp")); + params.arguments = QStringList("modules.cpp.combineCxxSources:true"); + params.command = "resolve"; + QCOMPARE(runQbs(params), 0); + WAIT_FOR_NEW_TIMESTAMP(); + touch("combinable.cpp"); + touch("main.cpp"); + touch("uncombinable.cpp"); + params.command = "build"; + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compiling main.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling combinable.cpp")); + QVERIFY(m_qbsStdout.contains("compiling uncombinable.cpp")); + QVERIFY(m_qbsStdout.contains("compiling amalgamated_theapp.cpp")); +} + +void TestBlackbox::commandFile() +{ + QDir::setCurrent(testDataDir + "/command-file"); + QbsRunParameters params(QStringList() << "-p" << "theLib"); + QCOMPARE(runQbs(params), 0); + params.arguments = QStringList() << "-p" << "theApp"; + 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"); + QFile fileToMove("tomove.txt"); + QVERIFY2(fileToMove.open(QIODevice::WriteOnly), qPrintable(fileToMove.errorString())); + fileToMove.close(); + fileToMove.setPermissions(fileToMove.permissions() & ~(QFile::ReadUser | QFile::ReadOwner + | QFile::ReadGroup | QFile::ReadOther)); + QbsRunParameters params(QStringList() << "-f" << "file.qbs"); + QCOMPARE(runQbs(params), 0); + QVERIFY(!QFileInfo("original.txt").exists()); + QFile copy("copy.txt"); + QVERIFY(copy.exists()); + QVERIFY(copy.open(QIODevice::ReadOnly)); + const QList lines = copy.readAll().trimmed().split('\n'); + QCOMPARE(lines.size(), 2); + QCOMPARE(lines.at(0).trimmed().constData(), "false"); + QCOMPARE(lines.at(1).trimmed().constData(), "true"); +} + +void TestBlackbox::jsExtensionsFileInfo() +{ + QDir::setCurrent(testDataDir + "/jsextensions-fileinfo"); + QbsRunParameters params(QStringList() << "-f" << "fileinfo.qbs"); + QCOMPARE(runQbs(params), 0); + QFile output("output.txt"); + QVERIFY(output.exists()); + QVERIFY(output.open(QIODevice::ReadOnly)); + const QList lines = output.readAll().trimmed().split('\n'); + QCOMPARE(lines.size(), 26); + int i = 0; + QCOMPARE(lines.at(i++).trimmed().constData(), "blubb"); + QCOMPARE(lines.at(i++).trimmed().constData(), qUtf8Printable( + QFileInfo(QDir::currentPath()).canonicalFilePath())); + 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"); + QCOMPARE(lines.at(i++).trimmed().constData(), "c:/tmp/blubb.tar.gz"); + QCOMPARE(lines.at(i++).trimmed().constData(), "true"); + QCOMPARE(lines.at(i++).trimmed().constData(), HostOsInfo::isWindowsHost() ? "true" : "false"); + QCOMPARE(lines.at(i++).trimmed().constData(), "false"); + QCOMPARE(lines.at(i++).trimmed().constData(), "true"); + QCOMPARE(lines.at(i++).trimmed().constData(), "false"); + QCOMPARE(lines.at(i++).trimmed().constData(), "false"); + QCOMPARE(lines.at(i++).trimmed().constData(), "/tmp/blubb.tar.gz"); + QCOMPARE(lines.at(i++).trimmed().constData(), "/tmp/blubb.tar.gz"); + QCOMPARE(lines.at(i++).trimmed().constData(), "/tmp"); + QCOMPARE(lines.at(i++).trimmed().constData(), "/tmp"); + QCOMPARE(lines.at(i++).trimmed().constData(), "/"); + QCOMPARE(lines.at(i++).trimmed().constData(), HostOsInfo::isWindowsHost() ? "d:/" : "d:"); + QCOMPARE(lines.at(i++).trimmed().constData(), "d:"); + QCOMPARE(lines.at(i++).trimmed().constData(), "d:/"); + QCOMPARE(lines.at(i++).trimmed().constData(), "blubb.tar.gz"); + QCOMPARE(lines.at(i++).trimmed().constData(), "tmp/blubb.tar.gz"); + QCOMPARE(lines.at(i++).trimmed().constData(), "../blubb.tar.gz"); + QCOMPARE(lines.at(i++).trimmed().constData(), "\\tmp\\blubb.tar.gz"); + QCOMPARE(lines.at(i++).trimmed().constData(), "c:\\tmp\\blubb.tar.gz"); +} + +void TestBlackbox::jsExtensionsProcess() +{ + QDir::setCurrent(testDataDir + "/jsextensions-process"); + QCOMPARE(runQbs({"resolve", {"-f", "process.qbs"}}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QbsRunParameters params(QStringList() << "-f" << "process.qbs"); + QCOMPARE(runQbs(params), 0); + QFile output("output.txt"); + QVERIFY(output.exists()); + QVERIFY(output.open(QIODevice::ReadOnly)); + const QList lines = output.readAll().trimmed().split('\n'); + QCOMPARE(lines.size(), 9); + QCOMPARE(lines.at(0).trimmed().constData(), "0"); + QVERIFY(lines.at(1).startsWith("qbs ")); + QCOMPARE(lines.at(2).trimmed().constData(), "true"); + QCOMPARE(lines.at(3).trimmed().constData(), "true"); + QCOMPARE(lines.at(4).trimmed().constData(), "0"); + QVERIFY(lines.at(5).startsWith("qbs ")); + QCOMPARE(lines.at(6).trimmed().constData(), "false"); + QCOMPARE(lines.at(7).trimmed().constData(), "should be"); + QCOMPARE(lines.at(8).trimmed().constData(), "123"); +} + +void TestBlackbox::jsExtensionsPropertyList() +{ + if (!HostOsInfo::isMacosHost()) + QSKIP("temporarily only applies on macOS"); + + QDir::setCurrent(testDataDir + "/jsextensions-propertylist"); + QbsRunParameters params(QStringList() << "-nf" << "propertylist.qbs"); + QCOMPARE(runQbs(params), 0); + QFile file1("test.json"); + QVERIFY(file1.exists()); + QVERIFY(file1.open(QIODevice::ReadOnly)); + QFile file2("test.xml"); + QVERIFY(file2.exists()); + QVERIFY(file2.open(QIODevice::ReadOnly)); + QFile file3("test2.json"); + QVERIFY(file3.exists()); + QVERIFY(file3.open(QIODevice::ReadOnly)); + QByteArray file1Contents = file1.readAll(); + QCOMPARE(file3.readAll(), file1Contents); + //QCOMPARE(file1Contents, file2.readAll()); // keys don't have guaranteed order + QJsonParseError err1, err2; + QCOMPARE(QJsonDocument::fromJson(file1Contents, &err1), + QJsonDocument::fromJson(file2.readAll(), &err2)); + QVERIFY(err1.error == QJsonParseError::NoError && err2.error == QJsonParseError::NoError); + QFile file4("test.openstep.plist"); + QVERIFY(file4.exists()); + QFile file5("test3.json"); + QVERIFY(file5.exists()); + QVERIFY(file5.open(QIODevice::ReadOnly)); + QVERIFY(file1Contents != file5.readAll()); +} + +void TestBlackbox::jsExtensionsTemporaryDir() +{ + QDir::setCurrent(testDataDir + "/jsextensions-temporarydir"); + QbsRunParameters params; + QCOMPARE(runQbs(params), 0); +} + +void TestBlackbox::jsExtensionsTextFile() +{ + QDir::setCurrent(testDataDir + "/jsextensions-textfile"); + QbsRunParameters params(QStringList() << "-f" << "textfile.qbs"); + QCOMPARE(runQbs(params), 0); + QFile file1("file1.txt"); + QVERIFY(file1.exists()); + QVERIFY(file1.open(QIODevice::ReadOnly)); + QCOMPARE(file1.size(), qint64(0)); + QFile file2("file2.txt"); + QVERIFY(file2.exists()); + QVERIFY(file2.open(QIODevice::ReadOnly)); + const QList lines = file2.readAll().trimmed().split('\n'); + QCOMPARE(lines.size(), 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(), qPrintable(QDir::currentPath() + "/file1.txt")); + QCOMPARE(lines.at(5).trimmed().constData(), "true"); +} + +void TestBlackbox::jsExtensionsBinaryFile() +{ + QDir::setCurrent(testDataDir + "/jsextensions-binaryfile"); + QbsRunParameters params(QStringList() << "-f" << "binaryfile.qbs"); + QCOMPARE(runQbs(params), 0); + QFile source("source.dat"); + QVERIFY(source.exists()); + QVERIFY(source.open(QIODevice::ReadOnly)); + QCOMPARE(source.size(), qint64(0)); + QFile destination("destination.dat"); + QVERIFY(destination.exists()); + QVERIFY(destination.open(QIODevice::ReadOnly)); + const QByteArray data = destination.readAll(); + QCOMPARE(data.size(), 8); + QCOMPARE(data.at(0), char(0x00)); + QCOMPARE(data.at(1), char(0x01)); + QCOMPARE(data.at(2), char(0x02)); + QCOMPARE(data.at(3), char(0x03)); + QCOMPARE(data.at(4), char(0x04)); + QCOMPARE(data.at(5), char(0x05)); + QCOMPARE(data.at(6), char(0x06)); + QCOMPARE(data.at(7), char(0xFF)); +} + +void TestBlackbox::lastModuleCandidateBroken() +{ + QDir::setCurrent(testDataDir + "/last-module-candidate-broken"); + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("Module Foo could not be loaded"), m_qbsStderr); +} + +void TestBlackbox::ld() +{ + QDir::setCurrent(testDataDir + "/ld"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::symbolLinkMode() +{ + if (!HostOsInfo::isAnyUnixHost()) + QSKIP("only applies on Unix"); + + QDir::setCurrent(testDataDir + "/symbolLinkMode"); + + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QbsRunParameters params; + params.command = "run"; + const QStringList commonArgs{"-p", "driver", "--setup-run-env-config", + "ignore-lib-dependencies", "qbs.installPrefix:''"}; + + rmDirR(relativeBuildDir()); + params.arguments = QStringList() << commonArgs << "project.shouldInstallLibrary:true"; + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("somefunction existed and it returned 42"), + m_qbsStdout.constData()); + + rmDirR(relativeBuildDir()); + params.arguments = QStringList() << commonArgs << "project.shouldInstallLibrary:false"; + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("somefunction did not exist"), m_qbsStdout.constData()); + + rmDirR(relativeBuildDir()); + params.arguments = QStringList() << commonArgs << "project.lazy:false"; + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("Lib was loaded!\nmeow\n"), m_qbsStdout.constData()); + + if (HostOsInfo::isMacosHost()) { + rmDirR(relativeBuildDir()); + params.arguments = QStringList() << commonArgs << "project.lazy:true"; + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("meow\n") && m_qbsStdout.contains("Lib was loaded!\n"), + m_qbsStdout.constData()); + } +} + +void TestBlackbox::linkerMode() +{ + if (!HostOsInfo::isAnyUnixHost()) + QSKIP("only applies on Unix"); + + QDir::setCurrent(testDataDir + "/linkerMode"); + QCOMPARE(runQbs(QbsRunParameters(QStringList("qbs.installPrefix:''"))), 0); + + auto testCondition = [&](const QString &lang, + const std::function &condition) { + if ((lang == "Objective-C" || lang == "Objective-C++") + && HostOsInfo::hostOs() != HostOsInfo::HostOsMacos) + return; + const QString binary = defaultInstallRoot + "/LinkedProduct-" + lang; + QProcess deptool; + if (HostOsInfo::hostOs() == HostOsInfo::HostOsMacos) + deptool.start("otool", QStringList() << "-L" << binary); + else + deptool.start("readelf", QStringList() << "-a" << binary); + QVERIFY(deptool.waitForStarted()); + QVERIFY(deptool.waitForFinished()); + QByteArray deptoolOutput = deptool.readAllStandardOutput(); + if (HostOsInfo::hostOs() != HostOsInfo::HostOsMacos) { + QList lines = deptoolOutput.split('\n'); + int sz = lines.size(); + for (int i = 0; i < sz; ++i) { + if (!lines.at(i).contains("NEEDED")) { + lines.removeAt(i--); + sz--; + } + } + + deptoolOutput = lines.join('\n'); + } + QCOMPARE(deptool.exitCode(), 0); + QVERIFY2(condition(deptoolOutput), deptoolOutput.constData()); + }; + + const QStringList nocpplangs = QStringList() << "Assembly" << "C" << "Objective-C"; + for (const QString &lang : nocpplangs) + testCondition(lang, [](const QByteArray &lddOutput) { return !lddOutput.contains("c++"); }); + + const QStringList cpplangs = QStringList() << "C++" << "Objective-C++"; + for (const QString &lang : cpplangs) + testCondition(lang, [](const QByteArray &lddOutput) { return lddOutput.contains("c++"); }); + + const QStringList objclangs = QStringList() << "Objective-C" << "Objective-C++"; + for (const QString &lang : objclangs) + testCondition(lang, [](const QByteArray &lddOutput) { return lddOutput.contains("objc"); }); +} + +void TestBlackbox::linkerVariant_data() +{ + QTest::addColumn("theType"); + QTest::newRow("default") << QString(); + QTest::newRow("bfd") << QString("bfd"); + QTest::newRow("gold") << QString("gold"); +} + +void TestBlackbox::linkerVariant() +{ + QDir::setCurrent(testDataDir + "/linker-variant"); + QFETCH(QString, theType); + QStringList resolveArgs("--force-probe-execution"); + if (!theType.isEmpty()) + resolveArgs << ("products.p.linkerVariant:" + theType); + QCOMPARE(runQbs(QbsRunParameters("resolve", resolveArgs)), 0); + const bool isGcc = m_qbsStdout.contains("is GCC: true"); + const bool isNotGcc = m_qbsStdout.contains("is GCC: false"); + QVERIFY2(isGcc != isNotGcc, m_qbsStdout.constData()); + QbsRunParameters buildParams("build", QStringList{"--command-echo-mode", "command-line"}); + buildParams.expectFailure = true; + runQbs(buildParams); + if (isGcc && !theType.isEmpty()) + QCOMPARE(m_qbsStdout.count("-fuse-ld=" + theType.toLocal8Bit()), 1); + else + QVERIFY2(!m_qbsStdout.contains("-fuse-ld"), m_qbsStdout.constData()); +} + +void TestBlackbox::lexyacc() +{ + if (!lexYaccExist()) + QSKIP("lex or yacc not present"); + QDir::setCurrent(testDataDir + "/lexyacc/one-grammar"); + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QCOMPARE(runQbs(), 0); + const QString parserBinary = relativeExecutableFilePath("one-grammar"); + QProcess p; + const QByteArray magicString = "add to PATH: "; + const int magicStringIndex = m_qbsStdout.indexOf(magicString); + if (magicStringIndex != -1) { + const int newLineIndex = m_qbsStdout.indexOf('\n', magicStringIndex); + QVERIFY(newLineIndex != -1); + const int dirIndex = magicStringIndex + magicString.length(); + const QString dir = QString::fromLocal8Bit(m_qbsStdout.mid(dirIndex, + newLineIndex - dirIndex)); + QProcessEnvironment env; + env.insert("PATH", dir); + p.setProcessEnvironment(env); + } + p.start(parserBinary, QStringList()); + QVERIFY2(p.waitForStarted(), qPrintable(p.errorString())); + p.write("a && b || c && !d"); + p.closeWriteChannel(); + QVERIFY2(p.waitForFinished(), qPrintable(p.errorString())); + QVERIFY2(p.exitCode() == 0, p.readAllStandardError().constData()); + const QByteArray parserOutput = p.readAllStandardOutput(); + QVERIFY2(parserOutput.contains("OR AND a b AND c NOT d"), parserOutput.constData()); + + QDir::setCurrent(testDataDir + "/lexyacc/two-grammars"); + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + + params.expectFailure = false; + params.command = "resolve"; + params.arguments << (QStringList() << "modules.lex_yacc.uniqueSymbolPrefix:true"); + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStderr.contains("whatever"), m_qbsStderr.constData()); + params.arguments << "modules.lex_yacc.enableCompilerWarnings:true"; + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + const QByteArray outputToCheck = m_qbsStdout + m_qbsStderr; + QVERIFY2(outputToCheck.contains("whatever"), outputToCheck.constData()); +} + +void TestBlackbox::lexyaccOutputs() +{ + if (!lexYaccExist()) + QSKIP("lex or yacc not present"); + + QFETCH(QString, lexOutputFilePath); + QFETCH(QString, yaccOutputFilePath); + QbsRunParameters params; + if (!lexOutputFilePath.isEmpty()) + params.arguments << "modules.lex_yacc.lexOutputFilePath:" + lexOutputFilePath; + if (!yaccOutputFilePath.isEmpty()) + params.arguments << "modules.lex_yacc.yaccOutputFilePath:" + yaccOutputFilePath; + +#define VERIFY_COMPILATION(file) \ + if (!file.isEmpty()) { \ + QByteArray expected = "compiling " + file.toUtf8(); \ + if (!m_qbsStdout.contains(expected)) { \ + qDebug() << "Expected output:" << expected; \ + qDebug() << "Actual output:" << m_qbsStdout; \ + QFAIL("Expected stdout content missing."); \ + } \ + } + + const auto version = bisonVersion(); + if (version >= qbs::Version(2, 6)) { + // prefix only supported starting from bison 2.6 + QVERIFY(QDir::setCurrent(testDataDir + "/lexyacc/lex_prefix")); + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(params), 0); + VERIFY_COMPILATION(yaccOutputFilePath); + } + + QVERIFY(QDir::setCurrent(testDataDir + "/lexyacc/lex_outfile")); + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(params), 0); + VERIFY_COMPILATION(yaccOutputFilePath); + + if (version >= qbs::Version(2, 4)) { + // output syntax was changed in bison 2.4 + QVERIFY(QDir::setCurrent(testDataDir + "/lexyacc/yacc_output")); + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(params), 0); + VERIFY_COMPILATION(lexOutputFilePath); + } + +#undef VERIFY_COMPILATION +} + +void TestBlackbox::lexyaccOutputs_data() +{ + QTest::addColumn("lexOutputFilePath"); + QTest::addColumn("yaccOutputFilePath"); + QTest::newRow("none") << QString() << QString(); + QTest::newRow("lexOutputFilePath") + << QString{"lex_luthor.cpp"} << QString(); + QTest::newRow("yaccOutputFilePath") + << QString() << QString{"shaven_yak.cpp"}; +} + +void TestBlackbox::linkerLibraryDuplicates() +{ + const SettingsPtr s = settings(); + Profile buildProfile(profileName(), s.get()); + QStringList toolchain = profileToolchain(buildProfile); + if (!toolchain.contains("gcc")) + QSKIP("linkerLibraryDuplicates test only applies to GCC toolchain"); + + QDir::setCurrent(testDataDir + "/linker-library-duplicates"); + rmDirR(relativeBuildDir()); + + QFETCH(QString, removeDuplicateLibraries); + QStringList runParams; + if (!removeDuplicateLibraries.isEmpty()) { + runParams.append(removeDuplicateLibraries); + } + + QCOMPARE(runQbs(QbsRunParameters("resolve", runParams)), 0); + QCOMPARE(runQbs(QStringList { "--command-echo-mode", "command-line" }), 0); + const QByteArrayList output = m_qbsStdout.split('\n'); + QByteArray linkLine; + for (const QByteArray &line : output) { + if (line.contains("main.cpp.o")) + linkLine = line; + } + QVERIFY(!linkLine.isEmpty()); + + /* Now check the the libraries appear just once. In order to avoid dealing + * with the different file extensions used in different platforms, we check + * only for the library base name. But we must also take into account that + * the build directories of each library will contain the library base name, + * so we now exclude them. */ + QByteArrayList elementsWithoutPath; + for (const QByteArray &element: linkLine.split(' ')) { + if (element.indexOf('/') < 0) + elementsWithoutPath.append(element); + } + QByteArray pathLessLinkLine = elementsWithoutPath.join(' '); + + typedef QMap ObjectCount; + QFETCH(ObjectCount, expectedObjectCount); + for (auto i = expectedObjectCount.begin(); + i != expectedObjectCount.end(); + i++) { + QCOMPARE(pathLessLinkLine.count(i.key()), i.value()); + } +} + +void TestBlackbox::linkerLibraryDuplicates_data() +{ + typedef QMap ObjectCount; + + QTest::addColumn("removeDuplicateLibraries"); + QTest::addColumn("expectedObjectCount"); + + QTest::newRow("default") << + QString() << + ObjectCount { + { "lib1", 1 }, + { "lib2", 1 }, + { "lib3", 1 }, + }; + + QTest::newRow("enabled") << + "modules.cpp.removeDuplicateLibraries:true" << + ObjectCount { + { "lib1", 1 }, + { "lib2", 1 }, + { "lib3", 1 }, + }; + + QTest::newRow("disabled") << + "modules.cpp.removeDuplicateLibraries:false" << + ObjectCount { + { "lib1", 3 }, + { "lib2", 2 }, + { "lib3", 1 }, + }; +} + +void TestBlackbox::linkerScripts() +{ + const SettingsPtr s = settings(); + Profile buildProfile(profileName(), s.get()); + QStringList toolchain = profileToolchain(buildProfile); + if (!toolchain.contains("gcc") || targetOs() != HostOsInfo::HostOsLinux) + QSKIP("linker script test only applies to Linux "); + + QbsRunParameters runParams(QStringList() +// << "--log-level" << "debug" + << ("qbs.installRoot:" + QDir::currentPath())); + const QString sourceDir = QDir::cleanPath(testDataDir + "/linkerscripts"); + runParams.buildDirectory = sourceDir + "/build"; + runParams.workingDir = sourceDir; + + QCOMPARE(runQbs(runParams), 0); + const QString output = QString::fromLocal8Bit(m_qbsStderr); + 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."); + + const auto verifySymbols = [nmPath](const QByteArrayList& symbols) -> bool { + QProcess nm; + nm.start(nmPath, QStringList(QDir::currentPath() + "/liblinkerscripts.so")); + if (!nm.waitForStarted()) { + qDebug() << "Wait for process started failed."; + return false; + } + if (!nm.waitForFinished()) { + qDebug() << "Wait for process finished failed."; + return false; + } + if (nm.exitCode() != 0) { + qDebug() << "nm returned exit code " << nm.exitCode(); + return false; + } + const QByteArray nmOutput = nm.readAllStandardOutput(); + for (const QByteArray& symbol : symbols) { + if (!nmOutput.contains(symbol)) { + qDebug() << "Expected symbol" << symbol + << "not found in" << nmOutput.constData(); + return false; + } + } + return true; + }; + + QVERIFY(verifySymbols({"TEST_SYMBOL1", + "TEST_SYMBOL2", + "TEST_SYMBOL_FROM_INCLUDE", + "TEST_SYMBOL_FROM_DIRECTORY", + "TEST_SYMBOL_FROM_RECURSIVE"})); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(sourceDir + "/linkerscript_to_include", + "TEST_SYMBOL_FROM_INCLUDE = 1;", + "TEST_SYMBOL_FROM_INCLUDE_MODIFIED = 1;\n"); + QCOMPARE(runQbs(runParams), 0); + QVERIFY2(m_qbsStdout.contains("linking liblinkerscripts.so"), + "No linking after modifying included file"); + QVERIFY(verifySymbols({"TEST_SYMBOL1", + "TEST_SYMBOL2", + "TEST_SYMBOL_FROM_INCLUDE_MODIFIED", + "TEST_SYMBOL_FROM_DIRECTORY", + "TEST_SYMBOL_FROM_RECURSIVE"})); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(sourceDir + "/scripts/linkerscript_in_directory", + "TEST_SYMBOL_FROM_DIRECTORY = 1;\n", + "TEST_SYMBOL_FROM_DIRECTORY_MODIFIED = 1;\n"); + QCOMPARE(runQbs(runParams), 0); + QVERIFY2(m_qbsStdout.contains("linking liblinkerscripts.so"), + "No linking after modifying file in directory"); + QVERIFY(verifySymbols({"TEST_SYMBOL1", + "TEST_SYMBOL2", + "TEST_SYMBOL_FROM_INCLUDE_MODIFIED", + "TEST_SYMBOL_FROM_DIRECTORY_MODIFIED", + "TEST_SYMBOL_FROM_RECURSIVE"})); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(sourceDir + "/linkerscript_recursive", + "TEST_SYMBOL_FROM_RECURSIVE = 1;\n", + "TEST_SYMBOL_FROM_RECURSIVE_MODIFIED = 1;\n"); + QCOMPARE(runQbs(runParams), 0); + QVERIFY2(m_qbsStdout.contains("linking liblinkerscripts.so"), + "No linking after modifying recursive file"); + QVERIFY(verifySymbols({"TEST_SYMBOL1", + "TEST_SYMBOL2", + "TEST_SYMBOL_FROM_INCLUDE_MODIFIED", + "TEST_SYMBOL_FROM_DIRECTORY_MODIFIED", + "TEST_SYMBOL_FROM_RECURSIVE_MODIFIED"})); +} + +void TestBlackbox::linkerModuleDefinition() +{ + QDir::setCurrent(testDataDir + "/linker-module-definition"); + QCOMPARE(runQbs({"build"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QCOMPARE(runQbs({"run"}), 0); + const auto verifyOutput = [this](const QByteArrayList &symbols) { + for (const QByteArray &symbol : symbols) { + if (!m_qbsStdout.contains(symbol)) { + qDebug() << "Expected symbol" << symbol + << "not found in" << m_qbsStdout; + return false; + } + } + return true; + }; + QVERIFY(verifyOutput({"foo", "bar"})); +} + +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"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("listProp: [\"product\",\"higher\",\"group\"]"), + m_qbsStdout.constData()); +} + +void TestBlackbox::listPropertyOrder() +{ + QDir::setCurrent(testDataDir + "/list-property-order"); + const QbsRunParameters params(QStringList() << "-q"); + QCOMPARE(runQbs(params), 0); + const QByteArray firstOutput = m_qbsStderr; + QVERIFY(firstOutput.contains("listProp = [\"product\",\"higher3\",\"higher2\",\"higher1\",\"lower\"]")); + for (int i = 0; i < 25; ++i) { + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(params), 0); + if (m_qbsStderr != firstOutput) + break; + } + QCOMPARE(m_qbsStderr.constData(), firstOutput.constData()); +} + +void TestBlackbox::require() +{ + QDir::setCurrent(testDataDir + "/require"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::requireDeprecated() +{ + QDir::setCurrent(testDataDir + "/require-deprecated"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStderr.contains("loadExtension() function is deprecated"), + m_qbsStderr.constData()); + QVERIFY2(m_qbsStderr.contains("loadFile() function is deprecated"), + m_qbsStderr.constData()); +} + +void TestBlackbox::rescueTransformerData() +{ + QDir::setCurrent(testDataDir + "/rescue-transformer-data"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp") && m_qbsStdout.contains("m.p: undefined"), + m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + touch("main.cpp"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp") && !m_qbsStdout.contains("m.p: "), + m_qbsStdout.constData()); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("modules.m.p:true"))), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("compiling main.cpp") && m_qbsStdout.contains("m.p: true"), + m_qbsStdout.constData()); +} + +void TestBlackbox::multipleChanges() +{ + QDir::setCurrent(testDataDir + "/multiple-changes"); + QCOMPARE(runQbs(), 0); + QFile newFile("test.blubb"); + QVERIFY(newFile.open(QIODevice::WriteOnly)); + newFile.close(); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList() << "project.prop:true")), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("prop: true")); +} + +void TestBlackbox::multipleConfigurations() +{ + QDir::setCurrent(testDataDir + "/multiple-configurations"); + QbsRunParameters params(QStringList{"config:x", "config:y", "config:z"}); + params.profile.clear(); + struct DefaultProfileSwitcher + { + DefaultProfileSwitcher() + { + const SettingsPtr s = settings(); + oldDefaultProfile = s->defaultProfile(); + s->setValue("defaultProfile", profileName()); + s->sync(); + } + ~DefaultProfileSwitcher() + { + const SettingsPtr s = settings(); + s->setValue("defaultProfile", oldDefaultProfile); + s->sync(); + } + QVariant oldDefaultProfile; + }; + DefaultProfileSwitcher dps; + QCOMPARE(runQbs(params), 0); + QCOMPARE(m_qbsStdout.count("compiling lib.cpp"), 3); + QCOMPARE(m_qbsStdout.count("compiling file.cpp"), 3); + QCOMPARE(m_qbsStdout.count("compiling main.cpp"), 3); +} + +void TestBlackbox::multiplexedTool() +{ + QDir::setCurrent(testDataDir + "/multiplexed-tool"); + QCOMPARE(runQbs(), 0); + QCOMPARE(m_qbsStdout.count("creating tool.out"), 4); +} + +void TestBlackbox::nestedGroups() +{ + QDir::setCurrent(testDataDir + "/nested-groups"); + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(relativeExecutableFilePath("nested-groups"))); +} + +void TestBlackbox::nestedProperties() +{ + QDir::setCurrent(testDataDir + "/nested-properties"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("value in higherlevel"), m_qbsStdout.constData()); +} + +void TestBlackbox::newOutputArtifact() +{ + QDir::setCurrent(testDataDir + "/new-output-artifact"); + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(relativeBuildDir() + "/install-root/output_98.out")); + const QString the100thArtifact = relativeBuildDir() + "/install-root/output_99.out"; + QVERIFY(!regularFileExists(the100thArtifact)); + QbsRunParameters params("resolve", QStringList() << "products.theProduct.artifactCount:100"); + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(the100thArtifact)); +} + +void TestBlackbox::noExportedSymbols_data() +{ + QTest::addColumn("link"); + QTest::addColumn("dryRun"); + QTest::newRow("link") << true << false; + QTest::newRow("link (dry run)") << true << true; + QTest::newRow("do not link") << false << false; +} + +void TestBlackbox::noExportedSymbols() +{ + QDir::setCurrent(testDataDir + "/no-exported-symbols"); + QFETCH(bool, link); + QFETCH(bool, dryRun); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList{"--force-probe-execution", + QString("products.the_app.link:") + (link ? "true" : "false")})), 0); + const bool isMsvc = m_qbsStdout.contains("compiler is MSVC"); + const bool isNotMsvc = m_qbsStdout.contains("compiler is not MSVC"); + QVERIFY2(isMsvc || isNotMsvc, m_qbsStdout.constData()); + if (isNotMsvc) + QSKIP("Test applies with MSVC only"); + QbsRunParameters buildParams; + if (dryRun) + buildParams.arguments << "--dry-run"; + buildParams.expectFailure = link && !dryRun; + QCOMPARE(runQbs(buildParams) == 0, !buildParams.expectFailure); + QVERIFY2(m_qbsStderr.contains("This typically happens when a DLL does not export " + "any symbols.") == buildParams.expectFailure, + m_qbsStderr.constData()); +} + +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::noSuchProfile() +{ + QDir::setCurrent(testDataDir + "/no-such-profile"); + QbsRunParameters params(QStringList("products.theProduct.p:1")); + params.profile = "jibbetnich"; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("Profile 'jibbetnich' does not exist"), m_qbsStderr.constData()); +} + +void TestBlackbox::nonBrokenFilesInBrokenProduct() +{ + QDir::setCurrent(testDataDir + "/non-broken-files-in-broken-product"); + QbsRunParameters params(QStringList() << "-k"); + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY(m_qbsStdout.contains("fine.cpp")); + QVERIFY(runQbs(params) != 0); + QVERIFY(!m_qbsStdout.contains("fine.cpp")); // The non-broken file must not be recompiled. +} + +void TestBlackbox::nonDefaultProduct() +{ + QDir::setCurrent(testDataDir + "/non-default-product"); + const QString defaultAppExe = relativeExecutableFilePath("default app"); + const QString nonDefaultAppExe = relativeExecutableFilePath("non-default app"); + + QCOMPARE(runQbs(), 0); + QVERIFY2(QFile::exists(defaultAppExe), qPrintable(defaultAppExe)); + QVERIFY2(!QFile::exists(nonDefaultAppExe), qPrintable(nonDefaultAppExe)); + + QCOMPARE(runQbs(QbsRunParameters(QStringList() << "--all-products")), 0); + QVERIFY2(QFile::exists(nonDefaultAppExe), qPrintable(nonDefaultAppExe)); +} + +void TestBlackbox::notAlwaysUpdated() +{ + QDir::setCurrent(testDataDir + "/not-always-updated"); + QCOMPARE(runQbs(), 0); + QCOMPARE(runQbs(), 0); +} + +static void switchProfileContents(qbs::Profile &p, qbs::Settings *s, bool on) +{ + const QString scalarKey = "leaf.scalarProp"; + const QString listKey = "leaf.listProp"; + if (on) { + p.setValue(scalarKey, "profile"); + p.setValue(listKey, QStringList() << "profile"); + } else { + p.remove(scalarKey); + p.remove(listKey); + } + s->sync(); +} + +static void switchFileContents(QFile &f, bool on) +{ + f.seek(0); + QByteArray contents = f.readAll(); + f.resize(0); + if (on) + contents.replace("// leaf.", "leaf."); + else + contents.replace("leaf.", "// leaf."); + f.write(contents); + f.flush(); +} + +void TestBlackbox::propertyPrecedence() +{ + QDir::setCurrent(testDataDir + "/property-precedence"); + const SettingsPtr s = settings(); + qbs::Internal::TemporaryProfile profile("qbs_autotests_propPrecedence", s.get()); + profile.p.setValue("qbs.architecture", "x86"); // Profiles must not be empty... + s->sync(); + const QStringList args = QStringList() << "-f" << "property-precedence.qbs"; + QbsRunParameters params(args); + params.profile = profile.p.name(); + QbsRunParameters resolveParams = params; + resolveParams.command = "resolve"; + + // Case 1: [cmdline=0,prod=0,export=0,nonleaf=0,profile=0] + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + + QVERIFY2(m_qbsStdout.contains("scalar prop: leaf\n") + && m_qbsStdout.contains("list prop: [\"leaf\"]\n"), + m_qbsStdout.constData()); + params.arguments.clear(); + + // Case 2: [cmdline=0,prod=0,export=0,nonleaf=0,profile=1] + switchProfileContents(profile.p, s.get(), true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: profile\n") + && m_qbsStdout.contains("list prop: [\"profile\"]\n"), + m_qbsStdout.constData()); + + // Case 3: [cmdline=0,prod=0,export=0,nonleaf=1,profile=0] + QFile nonleafFile("modules/nonleaf/nonleaf.qbs"); + QVERIFY2(nonleafFile.open(QIODevice::ReadWrite), qPrintable(nonleafFile.errorString())); + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: nonleaf\n") + && m_qbsStdout.contains("list prop: [\"nonleaf\",\"leaf\"]\n"), + m_qbsStdout.constData()); + + // Case 4: [cmdline=0,prod=0,export=0,nonleaf=1,profile=1] + switchProfileContents(profile.p, s.get(), true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: nonleaf\n") + && m_qbsStdout.contains("list prop: [\"nonleaf\",\"profile\"]\n"), + m_qbsStdout.constData()); + + // Case 5: [cmdline=0,prod=0,export=1,nonleaf=0,profile=0] + QFile depFile("dep.qbs"); + QVERIFY2(depFile.open(QIODevice::ReadWrite), qPrintable(depFile.errorString())); + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, false); + switchFileContents(depFile, true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: export\n") + && m_qbsStdout.contains("list prop: [\"export\",\"leaf\"]\n"), + m_qbsStdout.constData()); + + // Case 6: [cmdline=0,prod=0,export=1,nonleaf=0,profile=1] + switchProfileContents(profile.p, s.get(), true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: export\n") + && m_qbsStdout.contains("list prop: [\"export\",\"profile\"]\n"), + m_qbsStdout.constData()); + + + // Case 7: [cmdline=0,prod=0,export=1,nonleaf=1,profile=0] + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.contains("WARNING: Conflicting scalar values at") + && m_qbsStderr.contains("nonleaf.qbs:4:22") + && m_qbsStderr.contains("dep.qbs:6:26"), + m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: export\n") + && m_qbsStdout.contains("list prop: [\"export\",\"nonleaf\",\"leaf\"]\n"), + m_qbsStdout.constData()); + + // Case 8: [cmdline=0,prod=0,export=1,nonleaf=1,profile=1] + switchProfileContents(profile.p, s.get(), true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.contains("WARNING: Conflicting scalar values at") + && m_qbsStderr.contains("nonleaf.qbs:4:22") + && m_qbsStderr.contains("dep.qbs:6:26"), + m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: export\n") + && m_qbsStdout.contains("list prop: [\"export\",\"nonleaf\",\"profile\"]\n"), + m_qbsStdout.constData()); + + // Case 9: [cmdline=0,prod=1,export=0,nonleaf=0,profile=0] + QFile productFile("property-precedence.qbs"); + QVERIFY2(productFile.open(QIODevice::ReadWrite), qPrintable(productFile.errorString())); + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, false); + switchFileContents(depFile, false); + switchFileContents(productFile, true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: product\n") + && m_qbsStdout.contains("list prop: [\"product\",\"leaf\"]\n"), + m_qbsStdout.constData()); + + // Case 10: [cmdline=0,prod=1,export=0,nonleaf=0,profile=1] + switchProfileContents(profile.p, s.get(), true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: product\n") + && m_qbsStdout.contains("list prop: [\"product\",\"profile\"]\n"), + m_qbsStdout.constData()); + + // Case 11: [cmdline=0,prod=1,export=0,nonleaf=1,profile=0] + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: product\n") + && m_qbsStdout.contains("list prop: [\"product\",\"nonleaf\",\"leaf\"]\n"), + m_qbsStdout.constData()); + + // Case 12: [cmdline=0,prod=1,export=0,nonleaf=1,profile=1] + switchProfileContents(profile.p, s.get(), true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: product\n") + && m_qbsStdout.contains("list prop: [\"product\",\"nonleaf\",\"profile\"]\n"), + m_qbsStdout.constData()); + + // Case 13: [cmdline=0,prod=1,export=1,nonleaf=0,profile=0] + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, false); + switchFileContents(depFile, true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: product\n") + && m_qbsStdout.contains("list prop: [\"product\",\"export\",\"leaf\"]\n"), + m_qbsStdout.constData()); + + // Case 14: [cmdline=0,prod=1,export=1,nonleaf=0,profile=1] + switchProfileContents(profile.p, s.get(), true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: product\n") + && m_qbsStdout.contains("list prop: [\"product\",\"export\",\"profile\"]\n"), + m_qbsStdout.constData()); + + // Case 15: [cmdline=0,prod=1,export=1,nonleaf=1,profile=0] + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: product\n") + && m_qbsStdout.contains("list prop: [\"product\",\"export\",\"nonleaf\",\"leaf\"]\n"), + m_qbsStdout.constData()); + + // Case 16: [cmdline=0,prod=1,export=1,nonleaf=1,profile=1] + switchProfileContents(profile.p, s.get(), true); + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: product\n") + && m_qbsStdout.contains("list prop: [\"product\",\"export\",\"nonleaf\",\"profile\"]\n"), + m_qbsStdout.constData()); + + // Command line properties wipe everything, including lists. + // Case 17: [cmdline=1,prod=0,export=0,nonleaf=0,profile=0] + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, false); + switchFileContents(depFile, false); + switchFileContents(productFile, false); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 18: [cmdline=1,prod=0,export=0,nonleaf=0,profile=1] + switchProfileContents(profile.p, s.get(), true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 19: [cmdline=1,prod=0,export=0,nonleaf=1,profile=0] + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 20: [cmdline=1,prod=0,export=0,nonleaf=1,profile=1] + switchProfileContents(profile.p, s.get(), true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 21: [cmdline=1,prod=0,export=1,nonleaf=0,profile=0] + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, false); + switchFileContents(depFile, true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 22: [cmdline=1,prod=0,export=1,nonleaf=0,profile=1] + switchProfileContents(profile.p, s.get(), true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 23: [cmdline=1,prod=0,export=1,nonleaf=1,profile=0] + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 24: [cmdline=1,prod=0,export=1,nonleaf=1,profile=1] + switchProfileContents(profile.p, s.get(), true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 25: [cmdline=1,prod=1,export=0,nonleaf=0,profile=0] + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, false); + switchFileContents(depFile, false); + switchFileContents(productFile, true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 26: [cmdline=1,prod=1,export=0,nonleaf=0,profile=1] + switchProfileContents(profile.p, s.get(), true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 27: [cmdline=1,prod=1,export=0,nonleaf=1,profile=0] + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 28: [cmdline=1,prod=1,export=0,nonleaf=1,profile=1] + switchProfileContents(profile.p, s.get(), true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 29: [cmdline=1,prod=1,export=1,nonleaf=0,profile=0] + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, false); + switchFileContents(depFile, true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 30: [cmdline=1,prod=1,export=1,nonleaf=0,profile=1] + switchProfileContents(profile.p, s.get(), true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 31: [cmdline=1,prod=1,export=1,nonleaf=1,profile=0] + switchProfileContents(profile.p, s.get(), false); + switchFileContents(nonleafFile, true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); + + // Case 32: [cmdline=1,prod=1,export=1,nonleaf=1,profile=1] + switchProfileContents(profile.p, s.get(), true); + resolveParams.arguments << "modules.leaf.scalarProp:cmdline" << "modules.leaf.listProp:cmdline"; + QCOMPARE(runQbs(resolveParams), 0); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("scalar prop: cmdline\n") + && m_qbsStdout.contains("list prop: [\"cmdline\"]\n"), + m_qbsStdout.constData()); +} + +void TestBlackbox::productDependenciesByType() +{ + QDir::setCurrent(testDataDir + "/product-dependencies-by-type"); + QCOMPARE(runQbs(), 0); + QFile appListFile(relativeProductBuildDir("app list") + "/app-list.txt"); + QVERIFY2(appListFile.open(QIODevice::ReadOnly), qPrintable(appListFile.fileName())); + const QList appList = appListFile.readAll().trimmed().split('\n'); + QCOMPARE(appList.size(), 6); + QStringList apps = QStringList() + << QDir::currentPath() + '/' + relativeExecutableFilePath("app1") + << QDir::currentPath() + '/' + relativeExecutableFilePath("app2") + << QDir::currentPath() + '/' + relativeExecutableFilePath("app3") + << QDir::currentPath() + '/' + relativeExecutableFilePath("app4") + << QDir::currentPath() + '/' + relativeProductBuildDir("other-product") + "/output.txt" + << QDir::currentPath() + '/' + relativeProductBuildDir("yet-another-product") + + "/output.txt"; + for (const QByteArray &line : appList) { + const QString cleanLine = QString::fromLocal8Bit(line.trimmed()); + QVERIFY2(apps.removeOne(cleanLine), qPrintable(cleanLine)); + } + QVERIFY(apps.empty()); +} + +void TestBlackbox::productInExportedModule() +{ + QDir::setCurrent(testDataDir + "/product-in-exported-module"); + QCOMPARE(runQbs(), 0); + QEXPECT_FAIL(nullptr, "QBS-1576", Abort); + QVERIFY2(!m_qbsStdout.contains("product: dep"), m_qbsStdout.constData()); +} + +void TestBlackbox::properQuoting() +{ + QDir::setCurrent(testDataDir + "/proper quoting"); + QCOMPARE(runQbs(), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QbsRunParameters params(QStringLiteral("run"), QStringList() << "-q" << "-p" << "Hello World"); + params.expectFailure = true; // Because the exit code is non-zero. + QCOMPARE(runQbs(params), 156); + const char * const expectedOutput = "whitespaceless\ncontains space\ncontains\ttab\n" + "backslash\\\nHello World! The magic number is 156."; + QCOMPARE(unifiedLineEndings(m_qbsStdout).constData(), expectedOutput); +} + +void TestBlackbox::propertiesInExportItems() +{ + QDir::setCurrent(testDataDir + "/properties-in-export-items"); + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(relativeExecutableFilePath("p1"))); + QVERIFY(regularFileExists(relativeExecutableFilePath("p2"))); + QVERIFY2(m_qbsStderr.isEmpty(), m_qbsStderr.constData()); +} + +void TestBlackbox::protobuf_data() +{ + QTest::addColumn("projectFile"); + QTest::addColumn("properties"); + QTest::addColumn("successExpected"); + QTest::newRow("cpp") << QString("addressbook_cpp.qbs") << QStringList() << true; + QTest::newRow("objc") << QString("addressbook_objc.qbs") << QStringList() << true; + QTest::newRow("import") << QString("import.qbs") << QStringList() << true; + QTest::newRow("missing import dir") << QString("needs-import-dir.qbs") + << QStringList() << false; + QTest::newRow("provided import dir") + << QString("needs-import-dir.qbs") + << QStringList("products.app.theImportDir:subdir") << true; +} + +void TestBlackbox::protobuf() +{ + QDir::setCurrent(testDataDir + "/protobuf"); + QFETCH(QString, projectFile); + QFETCH(QStringList, properties); + QFETCH(bool, successExpected); + rmDirR(relativeBuildDir()); + QbsRunParameters resolveParams("resolve", QStringList{"-f", projectFile} << properties); + QCOMPARE(runQbs(resolveParams), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + const bool withProtobuf = m_qbsStdout.contains("has protobuf: true"); + const bool withoutProtobuf = m_qbsStdout.contains("has protobuf: false"); + QVERIFY2(withProtobuf || withoutProtobuf, m_qbsStdout.constData()); + if (withoutProtobuf) + QSKIP("protobuf module not present"); + QbsRunParameters runParams("run"); + runParams.expectFailure = !successExpected; + QCOMPARE(runQbs(runParams) == 0, successExpected); +} + +void TestBlackbox::pseudoMultiplexing() +{ + // This is "pseudo-multiplexing" on all platforms that initialize qbs.architectures + // to an array with one element. See QBS-1243. + QDir::setCurrent(testDataDir + "/pseudo-multiplexing"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::qbsConfig() +{ + QbsRunParameters params("config"); +#ifdef QBS_ENABLE_UNIT_TESTS + QTemporaryDir tempSystemSettingsDir; + params.environment.insert("QBS_AUTOTEST_SYSTEM_SETTINGS_DIR", tempSystemSettingsDir.path()); + QTemporaryDir tempUserSettingsDir; + QVERIFY(tempSystemSettingsDir.isValid()); + QVERIFY(tempUserSettingsDir.isValid()); + const QStringList settingsDirArgs = QStringList{"--settings-dir", tempUserSettingsDir.path()}; + + // Set values. + params.arguments = settingsDirArgs + QStringList{"--system", "key.subkey.scalar", "s"}; + QCOMPARE(runQbs(params), 0); + params.arguments = settingsDirArgs + QStringList{"--system", "key.subkey.list", "['sl']"}; + QCOMPARE(runQbs(params), 0); + params.arguments = settingsDirArgs + QStringList{"--user", "key.subkey.scalar", "u"}; + QCOMPARE(runQbs(params), 0); + params.arguments = settingsDirArgs + QStringList{"key.subkey.list", "[\"u1\",\"u2\"]"}; + QCOMPARE(runQbs(params), 0); + + // Check outputs. + const auto valueExtractor = [this] { + const QByteArray trimmed = m_qbsStdout.trimmed(); + return trimmed.mid(trimmed.lastIndexOf(':') + 2); + }; + params.arguments = settingsDirArgs + QStringList{"--list", "key.subkey.scalar"}; + QCOMPARE(runQbs(params), 0); + QCOMPARE(valueExtractor(), QByteArray("\"u\"")); + params.arguments = settingsDirArgs + QStringList{"--list", "--user", "key.subkey.scalar"}; + QCOMPARE(runQbs(params), 0); + QCOMPARE(valueExtractor(), QByteArray("\"u\"")); + params.arguments = settingsDirArgs + QStringList{"--list", "--system", "key.subkey.scalar"}; + QCOMPARE(runQbs(params), 0); + QCOMPARE(valueExtractor(), QByteArray("\"s\"")); + params.arguments = settingsDirArgs + QStringList{"--list", "key.subkey.list"}; + QCOMPARE(runQbs(params), 0); + QCOMPARE(valueExtractor(), QByteArray("[\"u1\", \"u2\", \"sl\"]")); + params.arguments = settingsDirArgs + QStringList{"--list", "--user", "key.subkey.list"}; + QCOMPARE(runQbs(params), 0); + QCOMPARE(valueExtractor(), QByteArray("[\"u1\", \"u2\"]")); + params.arguments = settingsDirArgs + QStringList{"--list", "--system", "key.subkey.list"}; + QCOMPARE(runQbs(params), 0); + QCOMPARE(valueExtractor(), QByteArray("[\"sl\"]")); + + // Remove some values and re-check. + params.arguments = settingsDirArgs + QStringList{"--unset", "key.subkey.scalar"}; + QCOMPARE(runQbs(params), 0); + params.arguments = settingsDirArgs + QStringList{"--system", "--unset", "key.subkey.list"}; + QCOMPARE(runQbs(params), 0); + params.arguments = settingsDirArgs + QStringList{"--list", "key.subkey.scalar"}; + QCOMPARE(runQbs(params), 0); + QCOMPARE(valueExtractor(), QByteArray("\"s\"")); + params.arguments = settingsDirArgs + QStringList{"--list", "key.subkey.list"}; + QCOMPARE(runQbs(params), 0); + QCOMPARE(valueExtractor(), QByteArray("[\"u1\", \"u2\"]")); + + // Check preferences.ignoreSystemSearchPaths + params.arguments = settingsDirArgs + + QStringList{"--system", "preferences.qbsSearchPaths", "['/usr/lib/qbs']"}; + QCOMPARE(runQbs(params), 0); + params.arguments = settingsDirArgs + + QStringList{"preferences.qbsSearchPaths", "['/home/user/qbs']"}; + QCOMPARE(runQbs(params), 0); + qbs::Settings settings(tempUserSettingsDir.path(), tempSystemSettingsDir.path()); + const qbs::Preferences prefs(&settings, "SomeProfile"); + QVERIFY2(prefs.searchPaths().contains("/usr/lib/qbs") + && prefs.searchPaths().contains("/home/user/qbs"), + qPrintable(prefs.searchPaths().join(','))); + settings.setValue("profiles.SomeProfile.preferences.ignoreSystemSearchPaths", true); + QVERIFY2(!prefs.searchPaths().contains("/usr/lib/qbs") + && prefs.searchPaths().contains("/home/user/qbs"), + qPrintable(prefs.searchPaths().join(','))); + +#else + qDebug() << "ability to redirect the system settings dir not compiled in, skipping" + "most qbs-config tests"; +#endif // QBS_ENABLE_UNIT_TESTS + + bool canWriteToSystemSettings; + QString testSettingsFilePath; + { + QSettings testSettings( + qbs::Settings::defaultSystemSettingsBaseDir() + "/dummyOrg" + "/dummyApp.conf", + QSettings::IniFormat); + testSettings.setValue("dummyKey", "dummyValue"); + testSettings.sync(); + canWriteToSystemSettings = testSettings.status() == QSettings::NoError; + testSettingsFilePath = testSettings.fileName(); + } + if (canWriteToSystemSettings) + QVERIFY(QFile::remove(testSettingsFilePath)); + + // Check that trying to write to actual system settings causes access failure. + params.expectFailure = !canWriteToSystemSettings; + params.environment.clear(); + params.arguments = QStringList{"--system", "key.subkey.scalar", "s"}; + QCOMPARE(runQbs(params) == 0, canWriteToSystemSettings); + if (!canWriteToSystemSettings) { + QVERIFY2(m_qbsStderr.contains("You do not have permission to write to that location."), + m_qbsStderr.constData()); + } +} + +static QJsonObject getNextSessionPacket(QProcess &session, QByteArray &data) +{ + int totalSize = -1; + QElapsedTimer timer; + timer.start(); + QByteArray msg; + while (totalSize == -1 || msg.size() < totalSize) { + if (data.isEmpty()) + session.waitForReadyRead(1000); + if (timer.elapsed() >= testTimeoutInMsecs()) + return QJsonObject(); + data += session.readAllStandardOutput(); + if (totalSize == -1) { + static const QByteArray magicString = "qbsmsg:"; + const int magicStringOffset = data.indexOf(magicString); + if (magicStringOffset == -1) + continue; + const int sizeOffset = magicStringOffset + magicString.length(); + const int newlineOffset = data.indexOf('\n', sizeOffset); + if (newlineOffset == -1) + continue; + const QByteArray sizeString = data.mid(sizeOffset, newlineOffset - sizeOffset); + bool isNumber; + const int size = sizeString.toInt(&isNumber); + if (!isNumber || size <= 0) + return QJsonObject(); + data = data.mid(newlineOffset + 1); + totalSize = size; + } + const int bytesToTake = std::min(totalSize - msg.size(), data.size()); + msg += data.left(bytesToTake); + data = data.mid(bytesToTake); + } + return QJsonDocument::fromJson(QByteArray::fromBase64(msg)).object(); +} + +void TestBlackbox::qbsSession() +{ + QDir::setCurrent(testDataDir + "/qbs-session"); + QProcess sessionProc; + sessionProc.start(qbsExecutableFilePath, QStringList("session")); + + // Uncomment for debugging. + /* + connect(&sessionProc, &QProcess::readyReadStandardError, [&sessionProc] { + qDebug() << "stderr:" << sessionProc.readAllStandardError(); + }); + */ + + QVERIFY(sessionProc.waitForStarted()); + + const auto sendPacket = [&sessionProc](const QJsonObject &message) { + const QByteArray data = QJsonDocument(message).toJson().toBase64(); + sessionProc.write("qbsmsg:"); + sessionProc.write(QByteArray::number(data.length())); + sessionProc.write("\n"); + sessionProc.write(data); + }; + + static const auto envToJson = [](const QProcessEnvironment &env) { + QJsonObject envObj; + const QStringList keys = env.keys(); + for (const QString &key : keys) + envObj.insert(key, env.value(key)); + return envObj; + }; + + static const auto envFromJson = [](const QJsonValue &v) { + const QJsonObject obj = v.toObject(); + QProcessEnvironment env; + for (auto it = obj.begin(); it != obj.end(); ++it) + env.insert(it.key(), it.value().toString()); + return env; + }; + + QByteArray incomingData; + + // Wait for and verify hello packet. + QJsonObject receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QCOMPARE(receivedMessage.value("type"), "hello"); + QCOMPARE(receivedMessage.value("api-level").toInt(), 2); + QCOMPARE(receivedMessage.value("api-compat-level").toInt(), 2); + + // Resolve & verify structure + QJsonObject resolveMessage; + resolveMessage.insert("type", "resolve-project"); + resolveMessage.insert("top-level-profile", profileName()); + resolveMessage.insert("configuration-name", "my-config"); + resolveMessage.insert("project-file-path", QDir::currentPath() + "/qbs-session.qbs"); + resolveMessage.insert("build-root", QDir::currentPath()); + resolveMessage.insert("settings-directory", settings()->baseDirectory()); + QJsonObject overriddenValues; + overriddenValues.insert("products.theLib.cpp.cxxLanguageVersion", "c++17"); + resolveMessage.insert("overridden-properties", overriddenValues); + resolveMessage.insert("environment", envToJson(QProcessEnvironment::systemEnvironment())); + resolveMessage.insert("data-mode", "only-if-changed"); + resolveMessage.insert("log-time", true); + resolveMessage.insert("module-properties", + QJsonArray::fromStringList({"cpp.cxxLanguageVersion"})); + sendPacket(resolveMessage); + bool receivedLogData = false; + bool receivedStartedSignal = false; + bool receivedProgressData = false; + bool receivedReply = false; + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QVERIFY(!receivedMessage.isEmpty()); + const QString msgType = receivedMessage.value("type").toString(); + if (msgType == "project-resolved") { + receivedReply = true; + const QJsonObject error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + const QJsonObject projectData = receivedMessage.value("project-data").toObject(); + QCOMPARE(projectData.value("name").toString(), "qbs-session"); + const QJsonArray products = projectData.value("products").toArray(); + QCOMPARE(products.size(), 2); + for (const QJsonValue &v : products) { + const QJsonObject product = v.toObject(); + const QString productName = product.value("name").toString(); + QVERIFY(!productName.isEmpty()); + QVERIFY2(product.value("is-enabled").toBool(), qPrintable(productName)); + bool theLib = false; + bool theApp = false; + if (productName == "theLib") + theLib = true; + else if (productName == "theApp") + theApp = true; + QVERIFY2(theLib || theApp, qPrintable(productName)); + const QJsonArray groups = product.value("groups").toArray(); + if (theLib) + QVERIFY(groups.size() >= 3); + else + QVERIFY(!groups.isEmpty()); + for (const QJsonValue &v : groups) { + const QJsonObject group = v.toObject(); + const QJsonArray sourceArtifacts + = group.value("source-artifacts").toArray(); + const auto findArtifact = [&sourceArtifacts](const QString &fileName) { + for (const QJsonValue &v : sourceArtifacts) { + const QJsonObject artifact = v.toObject(); + if (QFileInfo(artifact.value("file-path").toString()).fileName() + == fileName) { + return artifact; + } + } + return QJsonObject(); + }; + const QString groupName = group.value("name").toString(); + const auto getCxxLanguageVersion = [&group, &product] { + QJsonObject moduleProperties = group.value("module-properties").toObject(); + if (moduleProperties.isEmpty()) + moduleProperties = product.value("module-properties").toObject(); + return moduleProperties.toVariantMap().value("cpp.cxxLanguageVersion") + .toStringList(); + }; + if (groupName == "sources") { + const QJsonObject artifact = findArtifact("lib.cpp"); + QVERIFY2(!artifact.isEmpty(), "lib.cpp"); + QCOMPARE(getCxxLanguageVersion(), {"c++17"}); + } else if (groupName == "headers") { + const QJsonObject artifact = findArtifact("lib.h"); + QVERIFY2(!artifact.isEmpty(), "lib.h"); + } else if (groupName == "theApp") { + const QJsonObject artifact = findArtifact("main.cpp"); + QVERIFY2(!artifact.isEmpty(), "main.cpp"); + QCOMPARE(getCxxLanguageVersion(), {"c++14"}); + } + } + } + break; + } else if (msgType == "log-data") { + if (receivedMessage.value("message").toString().contains("activity")) + receivedLogData = true; + } else if (msgType == "task-started") { + receivedStartedSignal = true; + } else if (msgType == "task-progress") { + receivedProgressData = true; + } else if (msgType != "new-max-progress") { + QVERIFY2(false, qPrintable(QString("Unexpected message type '%1'").arg(msgType))); + } + } + QVERIFY(receivedReply); + QVERIFY(receivedLogData); + QVERIFY(receivedStartedSignal); + QVERIFY(receivedProgressData); + + // First build: No install, log time, default command description. + QJsonObject buildRequest; + buildRequest.insert("type", "build-project"); + buildRequest.insert("log-time", true); + buildRequest.insert("install", false); + buildRequest.insert("data-mode", "only-if-changed"); + sendPacket(buildRequest); + receivedReply = false; + receivedLogData = false; + receivedStartedSignal = false; + receivedProgressData = false; + bool receivedCommandDescription = false; + bool receivedProcessResult = false; + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QVERIFY(!receivedMessage.isEmpty()); + const QString msgType = receivedMessage.value("type").toString(); + if (msgType == "project-built") { + receivedReply = true; + const QJsonObject error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + const QJsonObject projectData = receivedMessage.value("project-data").toObject(); + QCOMPARE(projectData.value("name").toString(), "qbs-session"); + } else if (msgType == "log-data") { + if (receivedMessage.value("message").toString().contains("activity")) + receivedLogData = true; + } else if (msgType == "task-started") { + receivedStartedSignal = true; + } else if (msgType == "task-progress") { + receivedProgressData = true; + } else if (msgType == "command-description") { + if (receivedMessage.value("message").toString().contains("compiling main.cpp")) + receivedCommandDescription = true; + } else if (msgType == "process-result") { + QCOMPARE(receivedMessage.value("exit-code").toInt(), 0); + receivedProcessResult = true; + } else if (msgType != "new-max-progress") { + QVERIFY2(false, qPrintable(QString("Unexpected message type '%1'").arg(msgType))); + } + } + QVERIFY(receivedReply); + QVERIFY(receivedLogData); + QVERIFY(receivedStartedSignal); + QVERIFY(receivedProgressData); + QVERIFY(receivedCommandDescription); + QVERIFY(receivedProcessResult); + const QString &exeFilePath = QDir::currentPath() + '/' + + relativeExecutableFilePath("theApp", "my-config"); + QVERIFY2(regularFileExists(exeFilePath), qPrintable(exeFilePath)); + const QString defaultInstallRoot = QDir::currentPath() + '/' + + relativeBuildDir("my-config") + "/install-root"; + QVERIFY2(!directoryExists(defaultInstallRoot), qPrintable(defaultInstallRoot)); + + // Clean. + QJsonObject cleanRequest; + cleanRequest.insert("type", "clean-project"); + cleanRequest.insert("settings-dir", settings()->baseDirectory()); + cleanRequest.insert("log-time", true); + sendPacket(cleanRequest); + receivedReply = false; + receivedLogData = false; + receivedStartedSignal = false; + receivedProgressData = false; + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QVERIFY(!receivedMessage.isEmpty()); + const QString msgType = receivedMessage.value("type").toString(); + if (msgType == "project-cleaned") { + receivedReply = true; + const QJsonObject error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + } else if (msgType == "log-data") { + if (receivedMessage.value("message").toString().contains("activity")) + receivedLogData = true; + } else if (msgType == "task-started") { + receivedStartedSignal = true; + } else if (msgType == "task-progress") { + receivedProgressData = true; + } else if (msgType != "new-max-progress") { + QVERIFY2(false, qPrintable(QString("Unexpected message type '%1'").arg(msgType))); + } + } + QVERIFY(receivedReply); + QVERIFY(receivedLogData); + QVERIFY(receivedStartedSignal); + QVERIFY(receivedProgressData); + QVERIFY2(!regularFileExists(exeFilePath), qPrintable(exeFilePath)); + + // Second build: Do not log the time, show command lines. + buildRequest.insert("log-time", false); + buildRequest.insert("command-echo-mode", "command-line"); + sendPacket(buildRequest); + receivedReply = false; + receivedLogData = false; + receivedStartedSignal = false; + receivedProgressData = false; + receivedCommandDescription = false; + receivedProcessResult = false; + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QVERIFY(!receivedMessage.isEmpty()); + const QString msgType = receivedMessage.value("type").toString(); + if (msgType == "project-built") { + receivedReply = true; + const QJsonObject error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + const QJsonObject projectData = receivedMessage.value("project-data").toObject(); + QVERIFY(projectData.isEmpty()); + } else if (msgType == "log-data") { + if (receivedMessage.value("message").toString().contains("activity")) + receivedLogData = true; + } else if (msgType == "task-started") { + receivedStartedSignal = true; + } else if (msgType == "task-progress") { + receivedProgressData = true; + } else if (msgType == "command-description") { + if (QDir::fromNativeSeparators(receivedMessage.value("message").toString()) + .contains("/main.cpp")) { + receivedCommandDescription = true; + } + } else if (msgType == "process-result") { + QCOMPARE(receivedMessage.value("exit-code").toInt(), 0); + receivedProcessResult = true; + } else if (msgType != "new-max-progress") { + QVERIFY2(false, qPrintable(QString("Unexpected message type '%1'").arg(msgType))); + } + } + QVERIFY(receivedReply); + QVERIFY(!receivedLogData); + QVERIFY(receivedStartedSignal); + QVERIFY(receivedProgressData); + QVERIFY(receivedCommandDescription); + QVERIFY(receivedProcessResult); + QVERIFY2(regularFileExists(exeFilePath), qPrintable(exeFilePath)); + QVERIFY2(!directoryExists(defaultInstallRoot), qPrintable(defaultInstallRoot)); + + // Install. + QJsonObject installRequest; + installRequest.insert("type", "install-project"); + installRequest.insert("log-time", true); + const QString customInstallRoot = QDir::currentPath() + "/my-install-root"; + QVERIFY2(!QFile::exists(customInstallRoot), qPrintable(customInstallRoot)); + installRequest.insert("install-root", customInstallRoot); + sendPacket(installRequest); + receivedReply = false; + receivedLogData = false; + receivedStartedSignal = false; + receivedProgressData = false; + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QVERIFY(!receivedMessage.isEmpty()); + const QString msgType = receivedMessage.value("type").toString(); + if (msgType == "install-done") { + receivedReply = true; + const QJsonObject error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + } else if (msgType == "log-data") { + if (receivedMessage.value("message").toString().contains("activity")) + receivedLogData = true; + } else if (msgType == "task-started") { + receivedStartedSignal = true; + } else if (msgType == "task-progress") { + receivedProgressData = true; + } else if (msgType != "new-max-progress") { + QVERIFY2(false, qPrintable(QString("Unexpected message type '%1'").arg(msgType))); + } + } + QVERIFY(receivedReply); + QVERIFY(receivedLogData); + QVERIFY(receivedStartedSignal); + QVERIFY(receivedProgressData); + QVERIFY2(!directoryExists(defaultInstallRoot), qPrintable(defaultInstallRoot)); + QVERIFY2(directoryExists(customInstallRoot), qPrintable(customInstallRoot)); + + // Retrieve modified environment. + QJsonObject getRunEnvRequest; + getRunEnvRequest.insert("type", "get-run-environment"); + getRunEnvRequest.insert("product", "theApp"); + const QProcessEnvironment inEnv = QProcessEnvironment::systemEnvironment(); + QVERIFY(!inEnv.contains("MY_MODULE")); + getRunEnvRequest.insert("base-environment", envToJson(inEnv)); + sendPacket(getRunEnvRequest); + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QCOMPARE(receivedMessage.value("type").toString(), QString("run-environment")); + QJsonObject error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + const QProcessEnvironment outEnv = envFromJson(receivedMessage.value("full-environment")); + QVERIFY(outEnv.keys().size() > inEnv.keys().size()); + QCOMPARE(outEnv.value("MY_MODULE"), QString("1")); + + // Add two files to library and re-build. + QJsonObject addFilesRequest; + addFilesRequest.insert("type", "add-files"); + addFilesRequest.insert("product", "theLib"); + addFilesRequest.insert("group", "sources"); + addFilesRequest.insert("files", + QJsonArray::fromStringList({QDir::currentPath() + "/file1.cpp", + QDir::currentPath() + "/file2.cpp"})); + sendPacket(addFilesRequest); + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QCOMPARE(receivedMessage.value("type").toString(), QString("files-added")); + error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) { + for (const auto &item: error[QStringLiteral("items")].toArray()) { + const auto description = QStringLiteral("Project file updates are not enabled"); + if (item.toObject()[QStringLiteral("description")].toString().contains(description)) + QSKIP("File updates are disabled"); + } + qDebug() << error; + } + QVERIFY(error.isEmpty()); + + receivedReply = false; + sendPacket(resolveMessage); + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QVERIFY(!receivedMessage.isEmpty()); + const QString msgType = receivedMessage.value("type").toString(); + if (msgType == "project-resolved") { + receivedReply = true; + const QJsonObject error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + const QJsonObject projectData = receivedMessage.value("project-data").toObject(); + QJsonArray products = projectData.value("products").toArray(); + bool file1 = false; + bool file2 = false; + for (const QJsonValue &v : products) { + const QJsonObject product = v.toObject(); + const QString productName = product.value("full-display-name").toString(); + const QJsonArray groups = product.value("groups").toArray(); + for (const QJsonValue &v : groups) { + const QJsonObject group = v.toObject(); + const QString groupName = group.value("name").toString(); + const QJsonArray sourceArtifacts = group.value("source-artifacts").toArray(); + for (const QJsonValue &v : sourceArtifacts) { + const QString filePath = v.toObject().value("file-path").toString(); + if (filePath.endsWith("file1.cpp")) { + QCOMPARE(productName, QString("theLib")); + QCOMPARE(groupName, QString("sources")); + file1 = true; + } else if (filePath.endsWith("file2.cpp")) { + QCOMPARE(productName, QString("theLib")); + QCOMPARE(groupName, QString("sources")); + file2 = true; + } + } + } + } + QVERIFY(file1); + QVERIFY(file2); + } + } + QVERIFY(receivedReply); + + receivedReply = false; + receivedProcessResult = false; + bool compiledFile1 = false; + bool compiledFile2 = false; + bool compiledMain = false; + bool compiledLib = false; + buildRequest.remove("command-echo-mode"); + sendPacket(buildRequest); + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QVERIFY(!receivedMessage.isEmpty()); + const QString msgType = receivedMessage.value("type").toString(); + if (msgType == "project-built") { + receivedReply = true; + const QJsonObject error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + } else if (msgType == "command-description") { + const QString msg = receivedMessage.value("message").toString(); + if (msg.contains("compiling file1.cpp")) + compiledFile1 = true; + else if (msg.contains("compiling file2.cpp")) + compiledFile2 = true; + else if (msg.contains("compiling main.cpp")) + compiledMain = true; + else if (msg.contains("compiling lib.cpp")) + compiledLib = true; + } else if (msgType == "process-result") { + QCOMPARE(receivedMessage.value("exit-code").toInt(), 0); + receivedProcessResult = true; + } + } + QVERIFY(receivedReply); + QVERIFY(!receivedProcessResult); + QVERIFY(compiledFile1); + QVERIFY(compiledFile2); + QVERIFY(!compiledLib); + QVERIFY(!compiledMain); + + // Remove one of the newly added files again and re-build. + WAIT_FOR_NEW_TIMESTAMP(); + touch("file1.cpp"); + touch("file2.cpp"); + touch("main.cpp"); + QJsonObject removeFilesRequest; + removeFilesRequest.insert("type", "remove-files"); + removeFilesRequest.insert("product", "theLib"); + removeFilesRequest.insert("group", "sources"); + removeFilesRequest.insert("files", + QJsonArray::fromStringList({QDir::currentPath() + "/file1.cpp"})); + sendPacket(removeFilesRequest); + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QCOMPARE(receivedMessage.value("type").toString(), QString("files-removed")); + error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + receivedReply = false; + sendPacket(resolveMessage); + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QVERIFY(!receivedMessage.isEmpty()); + const QString msgType = receivedMessage.value("type").toString(); + if (msgType == "project-resolved") { + receivedReply = true; + const QJsonObject error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + const QJsonObject projectData = receivedMessage.value("project-data").toObject(); + QJsonArray products = projectData.value("products").toArray(); + bool file1 = false; + bool file2 = false; + for (const QJsonValue &v : products) { + const QJsonObject product = v.toObject(); + const QString productName = product.value("full-display-name").toString(); + const QJsonArray groups = product.value("groups").toArray(); + for (const QJsonValue &v : groups) { + const QJsonObject group = v.toObject(); + const QString groupName = group.value("name").toString(); + const QJsonArray sourceArtifacts = group.value("source-artifacts").toArray(); + for (const QJsonValue &v : sourceArtifacts) { + const QString filePath = v.toObject().value("file-path").toString(); + if (filePath.endsWith("file1.cpp")) { + file1 = true; + } else if (filePath.endsWith("file2.cpp")) { + QCOMPARE(productName, QString("theLib")); + QCOMPARE(groupName, QString("sources")); + file2 = true; + } + } + } + } + QVERIFY(!file1); + QVERIFY(file2); + } + } + QVERIFY(receivedReply); + + receivedReply = false; + receivedProcessResult = false; + compiledFile1 = false; + compiledFile2 = false; + compiledMain = false; + compiledLib = false; + sendPacket(buildRequest); + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QVERIFY(!receivedMessage.isEmpty()); + const QString msgType = receivedMessage.value("type").toString(); + if (msgType == "project-built") { + receivedReply = true; + const QJsonObject error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + } else if (msgType == "command-description") { + const QString msg = receivedMessage.value("message").toString(); + if (msg.contains("compiling file1.cpp")) + compiledFile1 = true; + else if (msg.contains("compiling file2.cpp")) + compiledFile2 = true; + else if (msg.contains("compiling main.cpp")) + compiledMain = true; + else if (msg.contains("compiling lib.cpp")) + compiledLib = true; + } else if (msgType == "process-result") { + QCOMPARE(receivedMessage.value("exit-code").toInt(), 0); + receivedProcessResult = true; + } + } + QVERIFY(receivedReply); + QVERIFY(receivedProcessResult); + QVERIFY(!compiledFile1); + QVERIFY(compiledFile2); + QVERIFY(!compiledLib); + QVERIFY(compiledMain); + + // Get generated files. + QJsonObject genFilesRequestPerFile; + genFilesRequestPerFile.insert("source-file", QDir::currentPath() + "/main.cpp"); + genFilesRequestPerFile.insert("tags", QJsonArray{QJsonValue("obj")}); + QJsonObject genFilesRequestPerProduct; + genFilesRequestPerProduct.insert("full-display-name", "theApp"); + genFilesRequestPerProduct.insert("requests", QJsonArray({genFilesRequestPerFile})); + QJsonObject genFilesRequest; + genFilesRequest.insert("type", "get-generated-files-for-sources"); + genFilesRequest.insert("products", QJsonArray({genFilesRequestPerProduct})); + sendPacket(genFilesRequest); + receivedReply = false; + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QCOMPARE(receivedMessage.value("type").toString(), QString("generated-files-for-sources")); + const QJsonArray products = receivedMessage.value("products").toArray(); + QCOMPARE(products.size(), 1); + const QJsonArray results = products.first().toObject().value("results").toArray(); + QCOMPARE(results.size(), 1); + const QJsonObject result = results.first().toObject(); + QCOMPARE(result.value("source-file"), QDir::currentPath() + "/main.cpp"); + const QJsonArray generatedFiles = result.value("generated-files").toArray(); + QCOMPARE(generatedFiles.count(), 1); + QCOMPARE(QFileInfo(generatedFiles.first().toString()).fileName(), + objectFileName("main.cpp", profileName())); + receivedReply = true; + } + QVERIFY(receivedReply); + + // Release project. + const QJsonObject releaseRequest{qMakePair(QString("type"), QJsonValue("release-project"))}; + sendPacket(releaseRequest); + receivedReply = false; + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QCOMPARE(receivedMessage.value("type").toString(), QString("project-released")); + const QJsonObject error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + receivedReply = true; + } + QVERIFY(receivedReply); + + // Get build graph info. + QJsonObject loadProjectMessage; + loadProjectMessage.insert("type", "resolve-project"); + loadProjectMessage.insert("configuration-name", "my-config"); + loadProjectMessage.insert("build-root", QDir::currentPath()); + loadProjectMessage.insert("settings-dir", settings()->baseDirectory()); + loadProjectMessage.insert("restore-behavior", "restore-only"); + loadProjectMessage.insert("data-mode", "only-if-changed"); + sendPacket(loadProjectMessage); + receivedReply = false; + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + if (receivedMessage.value("type") != "project-resolved") + continue; + receivedReply = true; + const QJsonObject error = receivedMessage.value("error").toObject(); + if (!error.isEmpty()) + qDebug() << error; + QVERIFY(error.isEmpty()); + const QString bgFilePath = QDir::currentPath() + '/' + + relativeBuildGraphFilePath("my-config"); + const QJsonObject projectData = receivedMessage.value("project-data").toObject(); + QCOMPARE(projectData.value("build-graph-file-path").toString(), bgFilePath); + QCOMPARE(projectData.value("overridden-properties"), overriddenValues); + } + QVERIFY(receivedReply); + + // Send unknown request. + const QJsonObject unknownRequest({qMakePair(QString("type"), QJsonValue("blubb"))}); + sendPacket(unknownRequest); + receivedReply = false; + while (!receivedReply) { + receivedMessage = getNextSessionPacket(sessionProc, incomingData); + QCOMPARE(receivedMessage.value("type").toString(), QString("protocol-error")); + receivedReply = true; + } + QVERIFY(receivedReply); + + QJsonObject quitRequest; + quitRequest.insert("type", "quit"); + sendPacket(quitRequest); + QVERIFY(sessionProc.waitForFinished(3000)); +} + +void TestBlackbox::radAfterIncompleteBuild_data() +{ + QTest::addColumn("projectFileName"); + QTest::newRow("Project with Rule") << "project_with_rule.qbs"; + QTest::newRow("Project with Transformer") << "project_with_transformer.qbs"; +} + +void TestBlackbox::radAfterIncompleteBuild() +{ + QDir::setCurrent(testDataDir + "/rad-after-incomplete-build"); + rmDirR(relativeBuildDir()); + const QString projectFileName = "project_with_rule.qbs"; + + // Step 1: Have a directory where a file used to be. + QbsRunParameters params(QStringList() << "-f" << projectFileName); + QCOMPARE(runQbs(params), 0); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFileName, "oldfile", "oldfile/newfile"); + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFileName, "oldfile/newfile", "newfile"); + params.expectFailure = false; + QCOMPARE(runQbs(params), 0); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFileName, "newfile", "oldfile/newfile"); + QCOMPARE(runQbs(params), 0); + + // Step 2: Have a file where a directory used to be. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFileName, "oldfile/newfile", "oldfile"); + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFileName, "oldfile", "newfile"); + params.expectFailure = false; + QCOMPARE(runQbs(params), 0); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFileName, "newfile", "oldfile"); + QCOMPARE(runQbs(params), 0); +} + +void TestBlackbox::subProfileChangeTracking() +{ + QDir::setCurrent(testDataDir + "/subprofile-change-tracking"); + const SettingsPtr s = settings(); + qbs::Internal::TemporaryProfile subProfile("qbs-autotests-subprofile", s.get()); + subProfile.p.setValue("baseProfile", profileName()); + subProfile.p.setValue("cpp.includePaths", QStringList("/tmp/include1")); + s->sync(); + QCOMPARE(runQbs(), 0); + + subProfile.p.setValue("cpp.includePaths", QStringList("/tmp/include2")); + s->sync(); + QbsRunParameters params; + params.command = "resolve"; + QCOMPARE(runQbs(params), 0); + params.command = "build"; + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("main1.cpp")); + QVERIFY(m_qbsStdout.contains("main2.cpp")); +} + +void TestBlackbox::successiveChanges() +{ + QDir::setCurrent(testDataDir + "/successive-changes"); + QCOMPARE(runQbs(), 0); + + QbsRunParameters params("resolve", QStringList() << "products.theProduct.type:output,blubb"); + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + + params.arguments << "project.version:2"; + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + QFile output(relativeProductBuildDir("theProduct") + "/output.out"); + QVERIFY2(output.open(QIODevice::ReadOnly), qPrintable(output.errorString())); + const QByteArray version = output.readAll(); + QCOMPARE(version.constData(), "2"); +} + +void TestBlackbox::installedApp() +{ + QDir::setCurrent(testDataDir + "/installed_artifact"); + + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(defaultInstallRoot + + HostOsInfo::appendExecutableSuffix(QStringLiteral("/usr/bin/installedApp")))); + + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("qbs.installRoot:" + testDataDir + + "/installed-app"))), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(testDataDir + + HostOsInfo::appendExecutableSuffix("/installed-app/usr/bin/installedApp"))); + + QFile addedFile(defaultInstallRoot + QLatin1String("/blubb.txt")); + QVERIFY(addedFile.open(QIODevice::WriteOnly)); + addedFile.close(); + QVERIFY(addedFile.exists()); + QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); + QCOMPARE(runQbs(QbsRunParameters(QStringList() << "--clean-install-root")), 0); + QVERIFY(regularFileExists(defaultInstallRoot + + HostOsInfo::appendExecutableSuffix(QStringLiteral("/usr/bin/installedApp")))); + QVERIFY(regularFileExists(defaultInstallRoot + QLatin1String("/usr/src/main.cpp"))); + QVERIFY(!addedFile.exists()); + + // Check whether changing install parameters on the product causes re-installation. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("installed_artifact.qbs", "qbs.installPrefix: \"/usr\"", + "qbs.installPrefix: '/usr/local'"); + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(defaultInstallRoot + + HostOsInfo::appendExecutableSuffix(QStringLiteral("/usr/local/bin/installedApp")))); + QVERIFY(regularFileExists(defaultInstallRoot + QLatin1String("/usr/local/src/main.cpp"))); + + // Check whether changing install parameters on the artifact causes re-installation. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("installed_artifact.qbs", "qbs.installDir: \"bin\"", + "qbs.installDir: 'custom'"); + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(defaultInstallRoot + + HostOsInfo::appendExecutableSuffix(QStringLiteral("/usr/local/custom/installedApp")))); + + // Check whether changing install parameters on a source file causes re-installation. + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("installed_artifact.qbs", "qbs.installDir: \"src\"", + "qbs.installDir: 'source'"); + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(defaultInstallRoot + QLatin1String("/usr/local/source/main.cpp"))); + + // Check whether changing install parameters on the command line causes re-installation. + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("qbs.installRoot:" + relativeBuildDir() + + "/blubb"))), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(relativeBuildDir() + "/blubb/usr/local/source/main.cpp")); + + // Check --no-install + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(QbsRunParameters(QStringList() << "--no-install")), 0); + QCOMPARE(QDir(defaultInstallRoot).entryList(QDir::NoDotAndDotDot).size(), 0); + + // Check --no-build (with and without an existing build graph) + QbsRunParameters params("install", QStringList() << "--no-build"); + QCOMPARE(runQbs(params), 0); + rmDirR(relativeBuildDir()); + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("Build graph not found"), m_qbsStderr.constData()); +} + +void TestBlackbox::installDuplicates() +{ + QDir::setCurrent(testDataDir + "/install-duplicates"); + + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY(m_qbsStderr.contains("Cannot install files")); +} + +void TestBlackbox::installDuplicatesNoError() +{ + QDir::setCurrent(testDataDir + "/install-duplicates-no-error"); + + QbsRunParameters params; + QCOMPARE(runQbs(params), 0); +} + +void TestBlackbox::installedSourceFiles() +{ + QDir::setCurrent(testDataDir + "/installed-source-files"); + + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(defaultInstallRoot + QLatin1String("/readme.txt"))); + QVERIFY(regularFileExists(defaultInstallRoot + QLatin1String("/main.cpp"))); +} + +void TestBlackbox::toolLookup() +{ + QbsRunParameters params(QStringLiteral("setup-toolchains"), QStringList("--help")); + params.profile.clear(); + QCOMPARE(runQbs(params), 0); +} + +void TestBlackbox::topLevelSearchPath() +{ + QDir::setCurrent(testDataDir + "/toplevel-searchpath"); + + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("MyProduct"), m_qbsStderr.constData()); + params.arguments << ("project.qbsSearchPaths:" + QDir::currentPath() + "/qbs-resources"); + QCOMPARE(runQbs(params), 0); +} + +void TestBlackbox::checkProjectFilePath() +{ + QDir::setCurrent(testDataDir + "/project_filepath_check"); + QbsRunParameters params(QStringList("-f") << "project1.qbs"); + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("main.cpp"), m_qbsStdout.constData()); + QCOMPARE(runQbs(params), 0); + + params.arguments = QStringList("-f") << "project2.qbs"; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY(m_qbsStderr.contains("project file")); + + params.arguments = QStringList("-f") << "project2.qbs"; + params.command = "resolve"; + params.expectFailure = false; + QCOMPARE(runQbs(params), 0); + params.command = "build"; + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("main2.cpp"), m_qbsStdout.constData()); +} + +void TestBlackbox::checkTimestamps() +{ + QDir::setCurrent(testDataDir + "/check-timestamps"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling file.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QVERIFY(QFile::remove(relativeBuildGraphFilePath())); + WAIT_FOR_NEW_TIMESTAMP(); + touch("file.h"); + QCOMPARE(runQbs(QStringList("--check-timestamps")), 0); + QVERIFY2(m_qbsStdout.contains("compiling file.cpp"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); +} + +void TestBlackbox::chooseModuleInstanceByPriority() +{ + QFETCH(QString, idol); + QFETCH(QStringList, expectedSubStrings); + QFETCH(bool, expectSuccess); + QDir::setCurrent(testDataDir + "/choose-module-instance"); + rmDirR(relativeBuildDir()); + QbsRunParameters params(QStringList("modules.qbs.targetPlatform:" + idol)); + params.expectFailure = !expectSuccess; + if (expectSuccess) { + QCOMPARE(runQbs(params), 0); + } else { + QVERIFY(runQbs(params) != 0); + return; + } + + const QString installRoot = relativeBuildDir() + "/install-root/"; + QVERIFY(QFile::exists(installRoot + "/gerbil.txt")); + QFile file(installRoot + "/gerbil.txt"); + QVERIFY(file.open(QIODevice::ReadOnly)); + const QString content = QString::fromUtf8(file.readAll()); + for (const auto &str : expectedSubStrings) { + if (content.contains(str)) + continue; + qDebug() << "content:" << content; + qDebug() << "substring:" << str; + QFAIL("missing substring"); + } +} + +void TestBlackbox::chooseModuleInstanceByPriority_data() +{ + QTest::addColumn("idol"); + QTest::addColumn("expectedSubStrings"); + QTest::addColumn("expectSuccess"); + QTest::newRow("ringo") + << "Beatles" << QStringList() << false; + QTest::newRow("ritchie1") + << "Deep Purple" << QStringList{"slipped", "litchi", "ritchie"} << true; + QTest::newRow("ritchie2") + << "Rainbow" << QStringList{"slipped", "litchi", "ritchie"} << true; + QTest::newRow("lord") + << "Whitesnake" << QStringList{"chewed", "cord", "lord"} << true; +} + +class TemporaryDefaultProfileRemover +{ +public: + TemporaryDefaultProfileRemover(qbs::Settings *settings) + : m_settings(settings), m_defaultProfile(settings->defaultProfile()) + { + m_settings->remove(QStringLiteral("defaultProfile")); + } + + ~TemporaryDefaultProfileRemover() + { + if (!m_defaultProfile.isEmpty()) + m_settings->setValue(QStringLiteral("defaultProfile"), m_defaultProfile); + } + +private: + qbs::Settings *m_settings; + const QString m_defaultProfile; +}; + +void TestBlackbox::assembly() +{ + QDir::setCurrent(testDataDir + "/assembly"); + QVERIFY(runQbs() == 0); + + const QVariantMap properties = ([&]() { + QFile propertiesFile(relativeProductBuildDir("assembly") + "/properties.json"); + if (propertiesFile.open(QIODevice::ReadOnly)) + return QJsonDocument::fromJson(propertiesFile.readAll()).toVariant().toMap(); + return QVariantMap{}; + })(); + QVERIFY(!properties.empty()); + const auto toolchain = properties.value("qbs.toolchain").toStringList(); + QVERIFY(!toolchain.empty()); + const bool haveGcc = toolchain.contains("gcc"); + const bool haveMSVC = toolchain.contains("msvc"); + + QCOMPARE(m_qbsStdout.contains("assembling testa.s"), haveGcc); + QCOMPARE(m_qbsStdout.contains("compiling testb.S"), haveGcc); + QCOMPARE(m_qbsStdout.contains("compiling testc.sx"), haveGcc); + QCOMPARE(m_qbsStdout.contains("creating libtesta.a"), haveGcc); + QCOMPARE(m_qbsStdout.contains("creating libtestb.a"), haveGcc); + QCOMPARE(m_qbsStdout.contains("creating libtestc.a"), haveGcc); + QCOMPARE(m_qbsStdout.contains("creating testd.lib"), haveMSVC); +} + +void TestBlackbox::autotestWithDependencies() +{ + QDir::setCurrent(testDataDir + "/autotest-with-dependencies"); + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QCOMPARE(runQbs(QStringList({"-p", "autotest-runner"})), 0); + QVERIFY2(m_qbsStdout.contains("i am the test app") + && m_qbsStdout.contains("i am the helper"), m_qbsStdout.constData()); +} + +void TestBlackbox::autotestTimeout() +{ + QFETCH(QStringList, resolveParams); + QFETCH(bool, expectFailure); + QDir::setCurrent(testDataDir + "/autotest-timeout"); + QbsRunParameters resolveParameters("resolve", resolveParams); + QCOMPARE(runQbs(resolveParameters), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QbsRunParameters buildParameters(QStringList({"-p", "autotest-runner"})); + buildParameters.expectFailure = expectFailure; + if (expectFailure) { + QVERIFY(runQbs(buildParameters) != 0); + QVERIFY(m_qbsStderr.contains("cancelled") && m_qbsStderr.contains("timeout")); + } + else + QVERIFY(runQbs(buildParameters) == 0); +} + +void TestBlackbox::autotestTimeout_data() +{ + QTest::addColumn("resolveParams"); + QTest::addColumn("expectFailure"); + QTest::newRow("no timeout") << QStringList() << false; + QTest::newRow("timeout on test") << QStringList({"products.testApp.autotest.timeout:2"}) + << true; + QTest::newRow("timeout on runner") << QStringList({"products.autotest-runner.timeout:2"}) + << true; +} + +void TestBlackbox::autotests_data() +{ + QTest::addColumn("evilPropertySpec"); + QTest::addColumn("expectedErrorMessage"); + QTest::newRow("missing arguments") << QString("products.test1.autotest.arguments:[]") + << QByteArray("This test needs exactly one argument"); + QTest::newRow("missing working dir") << QString("products.test2.autotest.workingDir:''") + << QByteArray("Test resource not found"); + QTest::newRow("missing flaky specifier") + << QString("products.test3.autotest.allowFailure:false") + << QByteArray("I am an awful test"); + QTest::newRow("everything's fine") << QString() << QByteArray(); +} + +void TestBlackbox::autotests() +{ + QDir::setCurrent(testDataDir + "/autotests"); + QFETCH(QString, evilPropertySpec); + QFETCH(QByteArray, expectedErrorMessage); + QbsRunParameters resolveParams("resolve"); + if (!evilPropertySpec.isEmpty()) + resolveParams.arguments << evilPropertySpec; + QCOMPARE(runQbs(resolveParams), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QbsRunParameters testParams(QStringList{"-p", "autotest-runner"}); + if (!evilPropertySpec.isEmpty()) + testParams.expectFailure = true; + QCOMPARE(runQbs(testParams) == 0, !testParams.expectFailure); + if (testParams.expectFailure) { + QVERIFY2(m_qbsStderr.contains(expectedErrorMessage), m_qbsStderr.constData()); + return; + } + QVERIFY2(m_qbsStdout.contains("Running test test1") + && m_qbsStdout.contains("Running test test2") + && m_qbsStdout.contains("Running test test3"), m_qbsStdout.constData()); + QCOMPARE(m_qbsStdout.count("PASS"), 2); + QCOMPARE(m_qbsStderr.count("FAIL"), 1); +} + +void TestBlackbox::auxiliaryInputsFromDependencies() +{ + QDir::setCurrent(testDataDir + "/aux-inputs-from-deps"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generating dummy.out"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("products.dep.sleep:false"))), 0); + WAIT_FOR_NEW_TIMESTAMP(); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("generating dummy.out"), m_qbsStdout.constData()); +} + +void TestBlackbox::explicitlyDependsOn() +{ + QFETCH(QString, useExplicitlyDependsOn); + QFETCH(QString, useExplicitlyDependsOnFromDependencies); + QFETCH(QString, useModule); + QFETCH(bool, expectFailure); + + QDir::setCurrent(testDataDir + "/explicitly-depends-on"); + QbsRunParameters params("", + QStringList("products.prod1.useExplicitlyDependsOn:" + useExplicitlyDependsOn) + << "products.prod1.useExplicitlyDependsOnFromDependencies:" + + useExplicitlyDependsOnFromDependencies + << "projects.proj1.useModule:" + + useModule); + params.expectFailure = expectFailure; + + rmDirR(relativeBuildDir()); + + if (params.expectFailure) { + // Build should fail because a rule cycle is created within the product when + // explicitlyDependsOn is used. + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("Cycle detected in rule dependencies"), + m_qbsStderr.constData()); + } else { + // When explicitlyDependsOnFromDependencies is used, build should succeed due to the + // "final" tag being pulled in from dependencies. + QCOMPARE(runQbs(params), 0); + + if (useModule == QLatin1String("false")) { + QVERIFY2(m_qbsStdout.contains("creating 'product-fish.txt' tagged with 'final'"), + m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("Using explicitlyDependsOnArtifact: product-fish.txt"), + m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("step1 -> step2"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("step2 -> step3"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("step3 -> final"), m_qbsStdout.constData()); + } else { + QVERIFY2(!m_qbsStdout.contains("creating 'product-fish.txt' tagged with 'final'"), + m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("Using explicitlyDependsOnArtifact: module-fish.txt"), + m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("step1 -> step2"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("step2 -> step3"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("step3 -> final"), m_qbsStdout.constData()); + } + } +} + +void TestBlackbox::explicitlyDependsOn_data() +{ + QTest::addColumn("useExplicitlyDependsOn"); + QTest::addColumn("useExplicitlyDependsOnFromDependencies"); + QTest::addColumn("useModule"); + QTest::addColumn("expectFailure"); + + QTest::newRow("useExplicitlyDependsOn -> causes cycle") + << "true" << "false" << "false" << true; + QTest::newRow("explicitlyDependsOnFromDependencies + product") + << "false" << "true" << "false" << false; + QTest::newRow("explicitlyDependsOnFromDependencies + module + filesAreTargets") + << "false" << "true" << "true" << false; +} + +static bool haveMakeNsis() +{ + QStringList regKeys; + regKeys << QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\NSIS") + << QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\NSIS"); + + QStringList paths = QProcessEnvironment::systemEnvironment().value("PATH") + .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS); + + for (const QString &key : qAsConst(regKeys)) { + QSettings settings(key, QSettings::NativeFormat); + QString str = settings.value(QStringLiteral(".")).toString(); + if (!str.isEmpty()) + paths.prepend(str); + } + + bool haveMakeNsis = false; + for (const QString &path : qAsConst(paths)) { + if (regularFileExists(QDir::fromNativeSeparators(path) + + HostOsInfo::appendExecutableSuffix(QStringLiteral("/makensis")))) { + haveMakeNsis = true; + break; + } + } + + return haveMakeNsis; +} + +void TestBlackbox::nsis() +{ + if (!haveMakeNsis()) { + QSKIP("makensis is not installed"); + return; + } + + bool targetIsWindows = targetOs() == HostOsInfo::HostOsWindows; + QDir::setCurrent(testDataDir + "/nsis"); + QVERIFY(runQbs() == 0); + QCOMPARE((bool)m_qbsStdout.contains("compiling hello.nsi"), targetIsWindows); + QCOMPARE((bool)m_qbsStdout.contains("SetCompressor ignored due to previous call with the /FINAL switch"), targetIsWindows); + QVERIFY(!QFile::exists(defaultInstallRoot + "/you-should-not-see-a-file-with-this-name.exe")); +} + +void TestBlackbox::nsisDependencies() +{ + if (!haveMakeNsis()) { + QSKIP("makensis is not installed"); + return; + } + + bool targetIsWindows = targetOs() == HostOsInfo::HostOsWindows; + QDir::setCurrent(testDataDir + "/nsisDependencies"); + QCOMPARE(runQbs(), 0); + QCOMPARE(m_qbsStdout.contains("compiling hello.nsi"), targetIsWindows); +} + +void TestBlackbox::outOfDateMarking() +{ + QDir::setCurrent(testDataDir + "/out-of-date-marking"); + for (int i = 0; i < 25; ++i) { + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("generating myheader.h"), qPrintable(QString::number(i))); + QVERIFY2(m_qbsStdout.contains("compiling main.c"), qPrintable(QString::number(i))); + } +} + +void TestBlackbox::enableExceptions() +{ + QFETCH(QString, file); + QFETCH(bool, enable); + QFETCH(bool, expectSuccess); + + QDir::setCurrent(testDataDir + QStringLiteral("/enableExceptions")); + + QbsRunParameters params; + params.arguments = QStringList() << "-f" << file + << (QStringLiteral("modules.cpp.enableExceptions:") + + (enable ? "true" : "false")); + params.expectFailure = !expectSuccess; + rmDirR(relativeBuildDir()); + if (!params.expectFailure) + QCOMPARE(runQbs(params), 0); + else + QVERIFY(runQbs(params) != 0); +} + +void TestBlackbox::enableExceptions_data() +{ + QTest::addColumn("file"); + QTest::addColumn("enable"); + QTest::addColumn("expectSuccess"); + + QTest::newRow("no exceptions, enabled") << "none.qbs" << true << true; + QTest::newRow("no exceptions, disabled") << "none.qbs" << false << true; + + QTest::newRow("C++ exceptions, enabled") << "exceptions.qbs" << true << true; + QTest::newRow("C++ exceptions, disabled") << "exceptions.qbs" << false << false; + + if (HostOsInfo::isMacosHost()) { + QTest::newRow("Objective-C exceptions, enabled") << "exceptions-objc.qbs" << true << true; + QTest::newRow("Objective-C exceptions in Objective-C++ source, enabled") << "exceptions-objcpp.qbs" << true << true; + QTest::newRow("C++ exceptions in Objective-C++ source, enabled") << "exceptions-objcpp-cpp.qbs" << true << true; + QTest::newRow("Objective-C, disabled") << "exceptions-objc.qbs" << false << false; + QTest::newRow("Objective-C exceptions in Objective-C++ source, disabled") << "exceptions-objcpp.qbs" << false << false; + QTest::newRow("C++ exceptions in Objective-C++ source, disabled") << "exceptions-objcpp-cpp.qbs" << false << false; + } +} + +void TestBlackbox::enableRtti() +{ + QDir::setCurrent(testDataDir + QStringLiteral("/enableRtti")); + + QbsRunParameters params; + + params.arguments = QStringList() << "modules.cpp.enableRtti:true"; + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(params), 0); + + if (HostOsInfo::isMacosHost()) { + params.arguments = QStringList() << "modules.cpp.enableRtti:true" + << "project.treatAsObjcpp:true"; + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(params), 0); + } + + params.expectFailure = true; + + params.arguments = QStringList() << "modules.cpp.enableRtti:false"; + rmDirR(relativeBuildDir()); + QVERIFY(runQbs(params) != 0); + + if (HostOsInfo::isMacosHost()) { + params.arguments = QStringList() << "modules.cpp.enableRtti:false" + << "project.treatAsObjcpp:true"; + rmDirR(relativeBuildDir()); + QVERIFY(runQbs(params) != 0); + } +} + +void TestBlackbox::envMerging() +{ + QDir::setCurrent(testDataDir + "/env-merging"); + QbsRunParameters params("resolve"); + QString pathVal = params.environment.value("PATH"); + pathVal.prepend(HostOsInfo::pathListSeparator()).prepend("/opt/blackbox/bin"); + const QString keyName = HostOsInfo::isWindowsHost() ? "pATh" : "PATH"; + params.environment.insert(keyName, pathVal); + QCOMPARE(runQbs(params), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + params.command = "build"; + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains(QByteArray("PATH=/opt/tool/bin") + + HostOsInfo::pathListSeparator().toLatin1()) + && m_qbsStdout.contains(HostOsInfo::pathListSeparator().toLatin1() + + QByteArray("/opt/blackbox/bin")), + m_qbsStdout.constData()); +} + +void TestBlackbox::envNormalization() +{ + QDir::setCurrent(testDataDir + "/env-normalization"); + QbsRunParameters params; + params.environment.insert("myvar", "x"); + QCOMPARE(runQbs(params), 0); + if (HostOsInfo::isWindowsHost()) + QVERIFY2(m_qbsStdout.contains("\"MYVAR\":\"x\""), m_qbsStdout.constData()); + else + QVERIFY2(m_qbsStdout.contains("\"myvar\":\"x\""), m_qbsStdout.constData()); +} + +void TestBlackbox::generatedArtifactAsInputToDynamicRule() +{ + QDir::setCurrent(testDataDir + "/generated-artifact-as-input-to-dynamic-rule"); + QCOMPARE(runQbs(), 0); + const QString oldFile = relativeProductBuildDir("p") + "/old.txt"; + QVERIFY2(regularFileExists(oldFile), qPrintable(oldFile)); + WAIT_FOR_NEW_TIMESTAMP(); + QFile inputFile("input.txt"); + QVERIFY2(inputFile.open(QIODevice::WriteOnly), qPrintable(inputFile.errorString())); + inputFile.resize(0); + inputFile.write("new.txt"); + inputFile.close(); + QCOMPARE(runQbs(), 0); + QVERIFY2(!regularFileExists(oldFile), qPrintable(oldFile)); + const QString newFile = relativeProductBuildDir("p") + "/new.txt"; + QVERIFY2(regularFileExists(newFile), qPrintable(oldFile)); + QVERIFY2(m_qbsStdout.contains("generating"), m_qbsStdout.constData()); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("generating"), m_qbsStdout.constData()); +} + +void TestBlackbox::generateLinkerMapFile() +{ + QDir::setCurrent(testDataDir + "/generate-linker-map-file"); + QCOMPARE(runQbs(), 0); + const bool isUsed = m_qbsStdout.contains("use test: true"); + const bool isNotUsed = m_qbsStdout.contains("use test: false"); + QVERIFY(isUsed != isNotUsed); + if (isUsed) { + QVERIFY(QFile::exists(relativeProductBuildDir("app-map") + + "/app-map.map")); + QVERIFY(!QFile::exists(relativeProductBuildDir("app-nomap") + + "/app-nomap.map")); + QVERIFY(!QFile::exists(relativeProductBuildDir("app-nomap-default") + + "/app-nomap-default.map")); + } else { + QSKIP("Unsupported toolchain. Skipping."); + } +} + +void TestBlackbox::generator() +{ + QFETCH(QString, inputFile); + QFETCH(QStringList, toBeCompiled); + QDir::setCurrent(testDataDir + "/generator"); + if (!inputFile.isEmpty()) { + WAIT_FOR_NEW_TIMESTAMP(); + QFile input(inputFile); + QFile output("input.txt"); + QVERIFY2(!output.exists() || output.remove(), qPrintable(output.errorString())); + QVERIFY2(input.copy(output.fileName()), qPrintable(input.errorString())); + touch(output.fileName()); + } + QCOMPARE(runQbs(), 0); + QCOMPARE(toBeCompiled.contains("main.cpp"), m_qbsStdout.contains("compiling main.cpp")); + QCOMPARE(toBeCompiled.contains("file1.cpp"), m_qbsStdout.contains("compiling file1.cpp")); + QCOMPARE(toBeCompiled.contains("file2.cpp"), m_qbsStdout.contains("compiling file2.cpp")); +} + +void TestBlackbox::generator_data() +{ + QTest::addColumn("inputFile"); + QTest::addColumn("toBeCompiled"); + QTest::newRow("both") << "input.both.txt" << QStringList{"main.cpp", "file1.cpp", "file2.cpp"}; + QTest::newRow("file1") << "input.file1.txt" << QStringList{"file1.cpp"}; + QTest::newRow("file2") << "input.file2.txt" << QStringList{"file2.cpp"}; + QTest::newRow("none") << "input.none.txt" << QStringList(); + QTest::newRow("both again") << "input.both.txt" << QStringList{"file1.cpp", "file2.cpp"}; + QTest::newRow("no update") << QString() << QStringList(); +} + +static bool haveWiX(const Profile &profile) +{ + if (profile.value("wix.toolchainInstallPath").isValid() && + profile.value("wix.toolchainInstallRoot").isValid()) { + return true; + } + + QStringList regKeys; + regKeys << QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows Installer XML\\") + << QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Installer XML\\"); + + QStringList paths = QProcessEnvironment::systemEnvironment().value("PATH") + .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS); + + for (const QString &key : qAsConst(regKeys)) { + const QStringList versions = QSettings(key, QSettings::NativeFormat).childGroups(); + for (const QString &version : versions) { + QSettings settings(key + version, QSettings::NativeFormat); + QString str = settings.value(QStringLiteral("InstallRoot")).toString(); + if (!str.isEmpty()) + paths.prepend(str); + } + } + + for (const QString &path : qAsConst(paths)) { + if (regularFileExists(QDir::fromNativeSeparators(path) + + HostOsInfo::appendExecutableSuffix(QStringLiteral("/candle"))) && + regularFileExists(QDir::fromNativeSeparators(path) + + HostOsInfo::appendExecutableSuffix(QStringLiteral("/light")))) { + return true; + } + } + + return false; +} + +void TestBlackbox::wix() +{ + const SettingsPtr s = settings(); + Profile profile(profileName(), s.get()); + + if (!haveWiX(profile)) { + QSKIP("WiX is not installed"); + return; + } + + QByteArray arch = profile.value("qbs.architecture").toString().toLatin1(); + if (arch.isEmpty()) + arch = QByteArrayLiteral("x86"); + + QDir::setCurrent(testDataDir + "/wix"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling QbsSetup.wxs"), m_qbsStdout); + QVERIFY2(m_qbsStdout.contains("linking qbs.msi"), m_qbsStdout); + QVERIFY(regularFileExists(relativeProductBuildDir("QbsSetup") + "/qbs.msi")); + + if (HostOsInfo::isWindowsHost()) { + QVERIFY2(m_qbsStdout.contains("compiling QbsBootstrapper.wxs"), m_qbsStdout); + QVERIFY2(m_qbsStdout.contains("linking qbs-setup-" + arch + ".exe"), m_qbsStdout); + QVERIFY(regularFileExists(relativeProductBuildDir("QbsBootstrapper") + + "/qbs-setup-" + arch + ".exe")); + } +} + +void TestBlackbox::wixDependencies() +{ + const SettingsPtr s = settings(); + Profile profile(profileName(), s.get()); + + if (!haveWiX(profile)) { + QSKIP("WiX is not installed"); + return; + } + + QByteArray arch = profile.value("qbs.architecture").toString().toLatin1(); + if (arch.isEmpty()) + arch = QByteArrayLiteral("x86"); + + QDir::setCurrent(testDataDir + "/wixDependencies"); + QbsRunParameters params; + if (!HostOsInfo::isWindowsHost()) + params.arguments << "qbs.targetOS:windows"; + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("compiling QbsSetup.wxs"), m_qbsStdout); + QVERIFY2(m_qbsStdout.contains("linking qbs.msi"), m_qbsStdout); + QVERIFY(regularFileExists(relativeBuildDir() + "/qbs.msi")); +} + +void TestBlackbox::nodejs() +{ + const SettingsPtr s = settings(); + Profile p(profileName(), s.get()); + + int status; + findNodejs(&status); + QCOMPARE(status, 0); + + QDir::setCurrent(testDataDir + QLatin1String("/nodejs")); + + status = runQbs(); + if (p.value("nodejs.toolchainInstallPath").toString().isEmpty() + && status != 0 && m_qbsStderr.contains("toolchainInstallPath")) { + QSKIP("nodejs.toolchainInstallPath not set and automatic detection failed"); + } + + if (p.value("nodejs.packageManagerPrefixPath").toString().isEmpty() + && status != 0 && m_qbsStderr.contains("nodejs.packageManagerPrefixPath")) { + QSKIP("nodejs.packageManagerFilePath not set and automatic detection failed"); + } + + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QCOMPARE(status, 0); + + QbsRunParameters params; + params.command = QLatin1String("run"); + QCOMPARE(runQbs(params), 0); + QVERIFY((bool)m_qbsStdout.contains("hello world")); + QVERIFY(regularFileExists(relativeProductBuildDir("hello") + "/hello.js")); +} + +void TestBlackbox::typescript() +{ + const SettingsPtr s = settings(); + Profile p(profileName(), s.get()); + + int status; + findTypeScript(&status); + QCOMPARE(status, 0); + + QDir::setCurrent(testDataDir + QLatin1String("/typescript")); + + QbsRunParameters params; + params.expectFailure = true; + status = runQbs(params); + if (p.value("typescript.toolchainInstallPath").toString().isEmpty() && status != 0) { + if (m_qbsStderr.contains("Path\" must be specified")) + QSKIP("typescript probe failed"); + if (m_qbsStderr.contains("typescript.toolchainInstallPath")) + QSKIP("typescript.toolchainInstallPath not set and automatic detection failed"); + if (m_qbsStderr.contains("nodejs.interpreterFilePath")) + QSKIP("nodejs.interpreterFilePath not set and automatic detection failed"); + } + + if (status != 0) + qDebug() << m_qbsStderr; + QCOMPARE(status, 0); + + params.expectFailure = false; + params.command = QStringLiteral("run"); + params.arguments = QStringList() << "-p" << "animals"; + QCOMPARE(runQbs(params), 0); + + QVERIFY(regularFileExists(relativeProductBuildDir("animals") + "/animals.js")); + QVERIFY(regularFileExists(relativeProductBuildDir("animals") + "/extra.js")); + QVERIFY(regularFileExists(relativeProductBuildDir("animals") + "/main.js")); +} + +void TestBlackbox::undefinedTargetPlatform() +{ + QDir::setCurrent(testDataDir + "/undefined-target-platform"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::importInPropertiesCondition() +{ + QDir::setCurrent(testDataDir + "/import-in-properties-condition"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::importSearchPath() +{ + QDir::setCurrent(testDataDir + "/import-searchpath"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("compiling somefile.cpp"), m_qbsStdout.constData()); +} + +void TestBlackbox::importingProduct() +{ + QDir::setCurrent(testDataDir + "/importing-product"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::importsConflict() +{ + QDir::setCurrent(testDataDir + "/imports-conflict"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackbox::includeLookup() +{ + QDir::setCurrent(testDataDir + "/includeLookup"); + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + 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()) + return true; + + QStringList regKeys; + regKeys << QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Inno Setup 5_is1") + << QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Inno Setup 5_is1"); + + QStringList paths = QProcessEnvironment::systemEnvironment().value("PATH") + .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS); + + for (const QString &key : qAsConst(regKeys)) { + QSettings settings(key, QSettings::NativeFormat); + QString str = settings.value(QStringLiteral("InstallLocation")).toString(); + if (!str.isEmpty()) + paths.prepend(str); + } + + for (const QString &path : qAsConst(paths)) { + if (regularFileExists(QDir::fromNativeSeparators(path) + + HostOsInfo::appendExecutableSuffix(QStringLiteral("/ISCC")))) + return true; + } + + return false; +} + +void TestBlackbox::innoSetup() +{ + const SettingsPtr s = settings(); + Profile profile(profileName(), s.get()); + + if (!haveInnoSetup(profile)) { + QSKIP("Inno Setup is not installed"); + return; + } + + QDir::setCurrent(testDataDir + "/innosetup"); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("compiling test.iss")); + QVERIFY(m_qbsStdout.contains("compiling Example1.iss")); + QVERIFY(regularFileExists(relativeProductBuildDir("QbsSetup") + "/qbs.setup.test.exe")); + QVERIFY(regularFileExists(relativeProductBuildDir("Example1") + "/Example1.exe")); +} + +void TestBlackbox::innoSetupDependencies() +{ + const SettingsPtr s = settings(); + Profile profile(profileName(), s.get()); + + if (!haveInnoSetup(profile)) { + QSKIP("Inno Setup is not installed"); + return; + } + + QDir::setCurrent(testDataDir + "/innosetupDependencies"); + QbsRunParameters params; + if (!HostOsInfo::isWindowsHost()) + params.arguments << "qbs.targetOS:windows"; + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("compiling test.iss")); + QVERIFY(regularFileExists(relativeBuildDir() + "/qbs.setup.test.exe")); +} + +void TestBlackbox::inputTagsChangeTracking_data() +{ + QTest::addColumn("generateInput"); + QTest::newRow("source artifact") << QString("no"); + QTest::newRow("generated artifact (static)") << QString("static"); + QTest::newRow("generated artifact (dynamic)") << QString("dynamic"); +} + +void TestBlackbox::inputTagsChangeTracking() +{ + QDir::setCurrent(testDataDir + "/input-tags-change-tracking"); + const QString xOut = QDir::currentPath() + '/' + relativeProductBuildDir("p") + "/x.out"; + const QString yOut = QDir::currentPath() + '/' + relativeProductBuildDir("p") + "/y.out"; + QFETCH(QString, generateInput); + const QbsRunParameters resolveParams("resolve", + QStringList("products.p.generateInput:" + generateInput)); + QCOMPARE(runQbs(resolveParams), 0); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("generating input.txt") == (generateInput == "static")); + QVERIFY2(!QFile::exists(xOut), qPrintable(xOut)); + QVERIFY2(!QFile::exists(yOut), qPrintable(yOut)); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("input-tags-change-tracking.qbs", "Tags: [\"txt\", \"empty\"]", + "Tags: \"txt\""); + QCOMPARE(runQbs(), 0); + QVERIFY2(QFile::exists(xOut), qPrintable(xOut)); + QVERIFY2(!QFile::exists(yOut), qPrintable(yOut)); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("input-tags-change-tracking.qbs", "Tags: \"txt\"", + "Tags: [\"txt\", \"y\"]"); + QCOMPARE(runQbs(), 0); + QVERIFY2(!QFile::exists(xOut), qPrintable(xOut)); + QVERIFY2(QFile::exists(yOut), qPrintable(yOut)); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("input-tags-change-tracking.qbs", "Tags: [\"txt\", \"y\"]", + "Tags: [\"txt\", \"empty\"]"); + QCOMPARE(runQbs(), 0); + QVERIFY2(!QFile::exists(xOut), qPrintable(xOut)); + QVERIFY2(!QFile::exists(yOut), qPrintable(yOut)); +} + +void TestBlackbox::outputArtifactAutoTagging() +{ + QDir::setCurrent(testDataDir + QLatin1String("/output-artifact-auto-tagging")); + + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(relativeExecutableFilePath("output-artifact-auto-tagging"))); +} + +void TestBlackbox::outputRedirection() +{ + QDir::setCurrent(testDataDir + "/output-redirection"); + QCOMPARE(runQbs(), 0); + TEXT_FILE_COMPARE("output.txt", relativeProductBuildDir("the-product") + "/output.txt"); + TEXT_FILE_COMPARE("output.bin", relativeProductBuildDir("the-product") + "/output.bin"); +} + +void TestBlackbox::wildCardsAndRules() +{ + QDir::setCurrent(testDataDir + "/wildcards-and-rules"); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("Creating output artifact")); + QFile output(relativeProductBuildDir("wildcards-and-rules") + "/test.mytype"); + QVERIFY2(output.open(QIODevice::ReadOnly), qPrintable(output.errorString())); + QCOMPARE(output.readAll().count('\n'), 1); + output.close(); + + // Add input. + WAIT_FOR_NEW_TIMESTAMP(); + touch("input2.inp"); + QbsRunParameters params; + params.expectFailure = true; + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("Creating output artifact")); + QVERIFY2(output.open(QIODevice::ReadOnly), qPrintable(output.errorString())); + QCOMPARE(output.readAll().count('\n'), 2); + output.close(); + + // Add "explicitlyDependsOn". + WAIT_FOR_NEW_TIMESTAMP(); + touch("dep.dep"); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("Creating output artifact")); + + // Add nothing. + QCOMPARE(runQbs(), 0); + QVERIFY(!m_qbsStdout.contains("Creating output artifact")); +} + +void TestBlackbox::loadableModule() +{ + QDir::setCurrent(testDataDir + QLatin1String("/loadablemodule")); + + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QbsRunParameters params; + params.command = "run"; + QCOMPARE(runQbs(params), 0); + 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', ""); + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + + QbsRunParameters params; + params.command = "run"; + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains(content), m_qbsStdout.constData()); +} + +void TestBlackbox::makefileGenerator() +{ + QDir::setCurrent(testDataDir + "/makefile-generator"); + const QbsRunParameters params("generate", QStringList{"-g", "makefile"}); + QCOMPARE(runQbs(params), 0); + if (HostOsInfo::isWindowsHost()) + return; + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QProcess make; + make.setWorkingDirectory(QDir::currentPath() + '/' + relativeBuildDir()); + const QString customInstallRoot = QDir::currentPath() + "/my-install-root"; + make.start("make", QStringList{"INSTALL_ROOT=" + customInstallRoot, "install"}); + QVERIFY(waitForProcessSuccess(make)); + QVERIFY(QFile::exists(relativeExecutableFilePath("the app"))); + QVERIFY(!QFile::exists(relativeBuildGraphFilePath())); + QProcess app; + app.start(customInstallRoot + "/usr/local/bin/the app", QStringList()); + QVERIFY(waitForProcessSuccess(app)); + const QByteArray appStdout = app.readAllStandardOutput(); + QVERIFY2(appStdout.contains("Hello, World!"), appStdout.constData()); + make.start("make", QStringList("clean")); + QVERIFY(waitForProcessSuccess(make)); + QVERIFY(!QFile::exists(relativeExecutableFilePath("the app"))); +} + +void TestBlackbox::maximumCLanguageVersion() +{ + QDir::setCurrent(testDataDir + "/maximum-c-language-version"); + QCOMPARE(runQbs(QbsRunParameters("resolve", + QStringList("products.app.enableNewestModule:true"))), 0); + if (m_qbsStdout.contains("is msvc")) + QSKIP("MSVC has no support for setting the C language version."); + QCOMPARE(runQbs(QStringList({"--command-echo-mode", "command-line", "-n"})), 0); + QVERIFY2(m_qbsStdout.contains("c11") || m_qbsStdout.contains("c1x"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QbsRunParameters("resolve", + QStringList("products.app.enableNewestModule:false"))), 0); + QCOMPARE(runQbs(QStringList({"--command-echo-mode", "command-line", "-n"})), 0); + QVERIFY2(m_qbsStdout.contains("c99"), m_qbsStdout.constData()); +} + +void TestBlackbox::maximumCxxLanguageVersion() +{ + QDir::setCurrent(testDataDir + "/maximum-cxx-language-version"); + QCOMPARE(runQbs(QbsRunParameters("resolve", + QStringList("products.app.enableNewestModule:true"))), 0); + QCOMPARE(runQbs(QStringList({"--command-echo-mode", "command-line", "-n"})), 0); + QVERIFY2(m_qbsStdout.contains("c++17") || m_qbsStdout.contains("c++1z") + || m_qbsStdout.contains("c++latest"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QbsRunParameters("resolve", + QStringList("products.app.enableNewestModule:false"))), 0); + QCOMPARE(runQbs(QStringList({"--command-echo-mode", "command-line", "-n"})), 0); + QVERIFY2(m_qbsStdout.contains("c++14") || m_qbsStdout.contains("c++1y"), + m_qbsStdout.constData()); +} + +void TestBlackbox::moduleProviders() +{ + QDir::setCurrent(testDataDir + "/module-providers"); + + // Resolving in dry-run mode must not leave any data behind. + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 2); + QVERIFY(!QFile::exists(relativeBuildDir())); + + // Initial build. + QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0); + QVERIFY(QFile::exists(relativeBuildDir())); + QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 2); + QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("The MY_DEFINE is app1"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0); + QVERIFY2(m_qbsStdout.contains("The letters are Z and Y"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("The MY_DEFINE is app2"), m_qbsStdout.constData()); + + // Rebuild with overridden module provider config. The output for product 2 must change, + // but no setup script must be re-run, because both config values have already been + // handled in the first run. + const QStringList resolveArgs("moduleProviders.mygenerator.chooseLettersFrom:beginning"); + QCOMPARE(runQbs(QbsRunParameters("resolve", resolveArgs)), 0); + QVERIFY2(!m_qbsStdout.contains("Running setup script"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0); + QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0); + QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData()); + + // Forcing Probe execution triggers a re-run of the setup script. But only once, + // because the module provider config is the same now. + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList(resolveArgs) + << "--force-probe-execution")), 0); + QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 1); + QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0); + QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0); + QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData()); + + // Now re-run without the module provider config override. Again, the setup script must + // run once, for the config value that was not present in the last run. + QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); + QCOMPARE(m_qbsStdout.count("Running setup script for mygenerator"), 1); + QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app1"})), 0); + QVERIFY2(m_qbsStdout.contains("The letters are A and B"), m_qbsStdout.constData()); + QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app2"})), 0); + QVERIFY2(m_qbsStdout.contains("The letters are Z and Y"), m_qbsStdout.constData()); +} + +void TestBlackbox::fallbackModuleProvider_data() +{ + QTest::addColumn("fallbacksEnabledGlobally"); + QTest::addColumn("fallbacksEnabledInProduct"); + QTest::addColumn("pkgConfigLibDirs"); + QTest::addColumn("successExpected"); + QTest::newRow("without custom lib dir, fallbacks disabled globally and in product") + << false << false << QStringList() << false; + QTest::newRow("without custom lib dir, fallbacks disabled globally, enabled in product") + << false << true << QStringList() << false; + QTest::newRow("without custom lib dir, fallbacks enabled globally, disabled in product") + << true << false << QStringList() << false; + QTest::newRow("without custom lib dir, fallbacks enabled globally and in product") + << true << true << QStringList() << false; + QTest::newRow("with custom lib dir, fallbacks disabled globally and in product") + << false << false << QStringList(testDataDir + "/fallback-module-provider/libdir") + << false; + QTest::newRow("with custom lib dir, fallbacks disabled globally, enabled in product") + << false << true << QStringList(testDataDir + "/fallback-module-provider/libdir") + << false; + QTest::newRow("with custom lib dir, fallbacks enabled globally, disabled in product") + << true << false << QStringList(testDataDir + "/fallback-module-provider/libdir") + << false; + QTest::newRow("with custom lib dir, fallbacks enabled globally and in product") + << true << true << QStringList(testDataDir + "/fallback-module-provider/libdir") + << true; +} + +void TestBlackbox::fallbackModuleProvider() +{ + QFETCH(bool, fallbacksEnabledInProduct); + QFETCH(bool, fallbacksEnabledGlobally); + QFETCH(QStringList, pkgConfigLibDirs); + QFETCH(bool, successExpected); + QDir::setCurrent(testDataDir + "/fallback-module-provider"); + static const auto b2s = [](bool b) { return QString(b ? "true" : "false"); }; + QbsRunParameters resolveParams("resolve", + QStringList{"modules.pkgconfig.libDirs:" + pkgConfigLibDirs.join(','), + "products.p.fallbacksEnabled:" + b2s(fallbacksEnabledInProduct), + "--force-probe-execution"}); + if (!fallbacksEnabledGlobally) + resolveParams.arguments << "--no-fallback-module-provider"; + QCOMPARE(runQbs(resolveParams), 0); + const bool pkgConfigPresent = m_qbsStdout.contains("pkg-config present: true"); + const bool pkgConfigNotPresent = m_qbsStdout.contains("pkg-config present: false"); + QVERIFY(pkgConfigPresent != pkgConfigNotPresent); + if (pkgConfigNotPresent) + successExpected = false; + QbsRunParameters buildParams; + buildParams.expectFailure = !successExpected; + QCOMPARE(runQbs(buildParams) == 0, successExpected); +} + +void TestBlackbox::minimumSystemVersion() +{ + rmDirR(relativeBuildDir()); + QDir::setCurrent(testDataDir + "/minimumSystemVersion"); + QFETCH(QString, file); + QFETCH(QString, output); + QbsRunParameters params({ "-f", file + ".qbs" }); + params.command = "resolve"; + QCOMPARE(runQbs(params), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + params.command = "run"; + QCOMPARE(runQbs(params), 0); + if (m_qbsStdout.contains("Unsupported compiler")) + QSKIP("Unsupported compiler"); + 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::Version fromMinimumDeploymentTargetValue(int v, bool isMacOS) +{ + if (isMacOS && v < 100000) + return qbs::Version(v / 100, v / 10 % 10, v % 10); + return qbs::Version(v / 10000, v / 100 % 100, v % 100); +} + +static int toMinimumDeploymentTargetValue(const qbs::Version &v, bool isMacOS) +{ + if (isMacOS && v < qbs::Version(10, 10)) + return (v.majorVersion() * 100) + (v.minorVersion() * 10) + v.patchLevel(); + return (v.majorVersion() * 10000) + (v.minorVersion() * 100) + v.patchLevel(); +} + +static qbs::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::Version(); +} + +void TestBlackbox::minimumSystemVersion_data() +{ + QTest::addColumn("file"); + QTest::addColumn("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(); + auto result = "__MAC_OS_X_VERSION_MIN_REQUIRED=" + + QString::number(toMinimumDeploymentTargetValue(v, true)); + if (v >= qbs::Version(10, 14)) + result += "\nminos "; + else + result += "\nversion "; + result += QString::number(v.majorVersion()) + "." + QString::number(v.minorVersion()); + return result; + } + + if (HostOsInfo::isWindowsHost()) + return "WINVER is not defined\n"; + + return ""; + }(); + + const QString specific = []() -> QString { + if (HostOsInfo::isMacosHost()) + return "__MAC_OS_X_VERSION_MIN_REQUIRED=1070\nversion 10.7\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=1071\n" + "version 10.7"; +} + +void TestBlackbox::missingBuildGraph() +{ + QTemporaryDir tmpDir; + QVERIFY(tmpDir.isValid()); + QDir::setCurrent(tmpDir.path()); + QFETCH(QString, configName); + const QStringList commands({"clean", "dump-nodes-tree", "status", "update-timestamps"}); + const QString actualConfigName = configName.isEmpty() ? QString("default") : configName; + QbsRunParameters params; + params.expectFailure = true; + params.arguments << QLatin1String("config:") + actualConfigName; + for (const QString &command : qAsConst(commands)) { + params.command = command; + QVERIFY2(runQbs(params) != 0, qPrintable(command)); + const QString expectedErrorMessage = QString("Build graph not found for " + "configuration '%1'").arg(actualConfigName); + if (!m_qbsStderr.contains(expectedErrorMessage.toLocal8Bit())) { + qDebug() << command; + qDebug() << expectedErrorMessage; + qDebug() << m_qbsStderr; + QFAIL("unexpected error message"); + } + } +} + +void TestBlackbox::missingBuildGraph_data() +{ + QTest::addColumn("configName"); + QTest::newRow("implicit config name") << QString(); + QTest::newRow("explicit config name") << QString("customConfig"); +} + +void TestBlackbox::missingDependency() +{ + QDir::setCurrent(testDataDir + "/missing-dependency"); + QbsRunParameters params; + params.expectFailure = true; + params.arguments << "-p" << "theApp"; + QVERIFY(runQbs(params) != 0); + QVERIFY2(!m_qbsStderr.contains("ASSERT"), m_qbsStderr.constData()); + QCOMPARE(runQbs(QbsRunParameters(QStringList() << "-p" << "theDep")), 0); + params.expectFailure = false; + params.arguments << "-vv"; + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStderr.contains("false positive")); +} + +void TestBlackbox::missingProjectFile() +{ + QDir::setCurrent(testDataDir + "/missing-project-file/empty-dir"); + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("No project file given and none found in current directory"), + m_qbsStderr.constData()); + QDir::setCurrent(testDataDir + "/missing-project-file"); + params.arguments << "-f" << "empty-dir"; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("No project file found in directory"), m_qbsStderr.constData()); + params.arguments = QStringList() << "-f" << "ambiguous-dir"; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("More than one project file found in directory"), + m_qbsStderr.constData()); + params.expectFailure = false; + params.arguments = QStringList() << "-f" << "project-dir"; + QCOMPARE(runQbs(params), 0); + WAIT_FOR_NEW_TIMESTAMP(); + touch("project-dir/file.cpp"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling file.cpp"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); +} + +void TestBlackbox::missingOverridePrefix() +{ + QDir::setCurrent(testDataDir + "/missing-override-prefix"); + QbsRunParameters params; + params.expectFailure = true; + params.arguments << "blubb.whatever:false"; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("Property override key 'blubb.whatever' not understood"), + m_qbsStderr.constData()); +} + +void TestBlackbox::moduleConditions() +{ + QDir::setCurrent(testDataDir + "/module-conditions"); + QCOMPARE(runQbs(), 0); + QCOMPARE(m_qbsStdout.count("loaded m1"), 1); + QCOMPARE(m_qbsStdout.count("loaded m2"), 2); + QCOMPARE(m_qbsStdout.count("loaded m3"), 1); + QCOMPARE(m_qbsStdout.count("loaded m4"), 1); +} + +void TestBlackbox::movedFileDependency() +{ + QDir::setCurrent(testDataDir + "/moved-file-dependency"); + const QString subdir2 = QDir::currentPath() + "/subdir2"; + QVERIFY(QDir::current().mkdir(subdir2)); + const QString oldHeaderFilePath = QDir::currentPath() + "/subdir1/theheader.h"; + const QString newHeaderFilePath = subdir2 + "/theheader.h"; + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + + QFile f(oldHeaderFilePath); + QVERIFY2(f.rename(newHeaderFilePath), qPrintable(f.errorString())); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + + f.setFileName(newHeaderFilePath); + QVERIFY2(f.rename(oldHeaderFilePath), qPrintable(f.errorString())); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); +} + +void TestBlackbox::badInterpreter() +{ + if (!HostOsInfo::isAnyUnixHost()) + QSKIP("only applies on Unix"); + + QDir::setCurrent(testDataDir + QLatin1String("/badInterpreter")); + QCOMPARE(runQbs(), 0); + + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + + QbsRunParameters params("run"); + params.expectFailure = true; + + const QRegExp reNoSuchFileOrDir("bad interpreter:.* No such file or directory"); + const QRegExp rePermissionDenied("bad interpreter:.* Permission denied"); + + params.arguments = QStringList() << "-p" << "script-interp-missing"; + QCOMPARE(runQbs(params), 1); + QString strerr = QString::fromLocal8Bit(m_qbsStderr); + QVERIFY2(strerr.contains(reNoSuchFileOrDir), m_qbsStderr); + + params.arguments = QStringList() << "-p" << "script-interp-noexec"; + QCOMPARE(runQbs(params), 1); + strerr = QString::fromLocal8Bit(m_qbsStderr); + QVERIFY2(strerr.contains(reNoSuchFileOrDir) || strerr.contains(rePermissionDenied) + || strerr.contains("script-noexec: bad interpreter: execve: Exec format error"), + qPrintable(strerr)); + + params.arguments = QStringList() << "-p" << "script-noexec"; + QCOMPARE(runQbs(params), 1); + QCOMPARE(runQbs(QbsRunParameters("run", QStringList() << "-p" << "script-ok")), 0); +} + +void TestBlackbox::bomSources() +{ + QDir::setCurrent(testDataDir + "/bom-sources"); + const bool success = runQbs() == 0; + if (!success) + QSKIP("Assuming compiler cannot deal with byte order mark"); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + touch("theheader.h"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); +} + +void TestBlackbox::buildDataOfDisabledProduct() +{ + QDir::setCurrent(testDataDir + QLatin1String("/build-data-of-disabled-product")); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("compiling test.cpp"), m_qbsStdout.constData()); + + // Touch a source file, disable the product, rebuild the project, verify nothing happens. + WAIT_FOR_NEW_TIMESTAMP(); + touch("test.cpp"); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("products.app.condition:false"))), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("compiling"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("linking"), m_qbsStdout.constData()); + + // Enable the product again, rebuild the project, verify that only the changed source file + // is rebuilt. + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("products.app.condition:true"))), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("compiling main.cpp"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("compiling test.cpp"), m_qbsStdout.constData()); +} + +void TestBlackbox::qbsVersion() +{ + const auto v = qbs::LanguageInfo::qbsVersion(); + QDir::setCurrent(testDataDir + QLatin1String("/qbsVersion")); + QbsRunParameters params; + params.arguments = QStringList() + << "project.qbsVersion:" + v.toString() + << "project.qbsVersionMajor:" + QString::number(v.majorVersion()) + << "project.qbsVersionMinor:" + QString::number(v.minorVersion()) + << "project.qbsVersionPatch:" + QString::number(v.patchLevel()); + QCOMPARE(runQbs(params), 0); + + params.arguments.push_back("project.qbsVersionPatch:" + QString::number(v.patchLevel() + 1)); + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); +} + +void TestBlackbox::transitiveInvalidDependencies() +{ + QDir::setCurrent(testDataDir + "/transitive-invalid-dependencies"); + QbsRunParameters params; + QCOMPARE(runQbs(params), 0); + QVERIFY2(m_qbsStdout.contains("b.present = false"), m_qbsStdout); + QVERIFY2(m_qbsStdout.contains("c.present = true"), m_qbsStdout); + QVERIFY2(m_qbsStdout.contains("d.present = false"), m_qbsStdout); +} + +void TestBlackbox::transitiveOptionalDependencies() +{ + QDir::setCurrent(testDataDir + "/transitive-optional-dependencies"); + QbsRunParameters params; + QCOMPARE(runQbs(params), 0); +} + +void TestBlackbox::groupsInModules() +{ + QDir::setCurrent(testDataDir + "/groups-in-modules"); + QCOMPARE(runQbs({"resolve"}), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QbsRunParameters params; + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("compile rock.coal => rock.diamond")); + QVERIFY(m_qbsStdout.contains("compile chunk.coal => chunk.diamond")); + QVERIFY(m_qbsStdout.contains("compiling helper2.c")); + QVERIFY(!m_qbsStdout.contains("compiling helper3.c")); + QVERIFY(m_qbsStdout.contains("compiling helper4.c")); + QVERIFY(m_qbsStdout.contains("compiling helper5.c")); + QVERIFY(!m_qbsStdout.contains("compiling helper6.c")); + + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compile rock.coal => rock.diamond")); + QVERIFY(!m_qbsStdout.contains("compile chunk.coal => chunk.diamond")); + + WAIT_FOR_NEW_TIMESTAMP(); + touch("modules/helper/diamondc.c"); + + waitForFileUnlock(); + QCOMPARE(runQbs(params), 0); + QVERIFY(m_qbsStdout.contains("compiling diamondc.c")); + QVERIFY(m_qbsStdout.contains("compile rock.coal => rock.diamond")); + QVERIFY(m_qbsStdout.contains("compile chunk.coal => chunk.diamond")); + QVERIFY(regularFileExists(relativeProductBuildDir("groups-in-modules") + "/rock.diamond")); + QFile output(relativeProductBuildDir("groups-in-modules") + "/rock.diamond"); + QVERIFY(output.open(QIODevice::ReadOnly)); + QCOMPARE(output.readAll().trimmed(), QByteArray("diamond")); +} + +void TestBlackbox::grpc_data() +{ + QTest::addColumn("projectFile"); + QTest::newRow("cpp") << QString("grpc_cpp.qbs"); +} + +void TestBlackbox::grpc() +{ + QDir::setCurrent(testDataDir + "/grpc"); + QFETCH(QString, projectFile); + rmDirR(relativeBuildDir()); + QbsRunParameters resolveParams("resolve", QStringList{"-f", projectFile}); + QCOMPARE(runQbs(resolveParams), 0); + const bool withGrpc = m_qbsStdout.contains("has grpc: true"); + const bool withoutGrpc = m_qbsStdout.contains("has grpc: false"); + QVERIFY2(withGrpc || withoutGrpc, m_qbsStdout.constData()); + if (withoutGrpc) + QSKIP("grpc module not present"); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + + QbsRunParameters runParams; + QCOMPARE(runQbs(runParams), 0); +} + +void TestBlackbox::hostOsProperties() +{ + QDir::setCurrent(testDataDir + "/host-os-properties"); + QCOMPARE(runQbs(QStringLiteral("resolve")), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QCOMPARE(runQbs(QStringLiteral("run")), 0); + QVERIFY2(m_qbsStdout.contains( + ("HOST_ARCHITECTURE = " + HostOsInfo::hostOSArchitecture()).data()), + m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains(("HOST_PLATFORM = " + HostOsInfo::hostOSIdentifier()).data()), + m_qbsStdout.constData()); +} + +void TestBlackbox::ico() +{ + QDir::setCurrent(testDataDir + "/ico"); + QbsRunParameters params; + params.expectFailure = true; + params.arguments << "--command-echo-mode" << "command-line"; + const int status = runQbs(params); + if (status != 0) { + if (m_qbsStderr.contains("Could not find icotool in any of the following locations:")) + QSKIP("icotool is not installed"); + if (!m_qbsStderr.isEmpty()) + qDebug("%s", m_qbsStderr.constData()); + if (!m_qbsStdout.isEmpty()) + qDebug("%s", m_qbsStdout.constData()); + } + QCOMPARE(status, 0); + + QVERIFY(QFileInfo::exists(relativeProductBuildDir("icon") + "/icon.ico")); + { + QFile f(relativeProductBuildDir("icon") + "/icon.ico"); + QVERIFY(f.open(QIODevice::ReadOnly)); + const auto b = f.readAll().toStdString(); + QCOMPARE(b.at(2), '\x1'); // icon + QCOMPARE(b.at(4), '\x2'); // 2 images + QVERIFY(b.find("\x89PNG") == std::string::npos); + } + + QVERIFY(QFileInfo::exists(relativeProductBuildDir("icon-alpha") + "/icon-alpha.ico")); + { + QFile f(relativeProductBuildDir("icon-alpha") + "/icon-alpha.ico"); + QVERIFY(f.open(QIODevice::ReadOnly)); + const auto b = f.readAll().toStdString(); + QCOMPARE(b.at(2), '\x1'); // icon + QCOMPARE(b.at(4), '\x2'); // 2 images + QVERIFY(b.find("\x89PNG") == std::string::npos); + QVERIFY2(m_qbsStdout.contains("--alpha-threshold="), m_qbsStdout.constData()); + } + + QVERIFY(QFileInfo::exists(relativeProductBuildDir("icon-big") + "/icon-big.ico")); + { + QFile f(relativeProductBuildDir("icon-big") + "/icon-big.ico"); + QVERIFY(f.open(QIODevice::ReadOnly)); + const auto b = f.readAll().toStdString(); + QCOMPARE(b.at(2), '\x1'); // icon + QCOMPARE(b.at(4), '\x5'); // 5 images + QVERIFY(b.find("\x89PNG") != std::string::npos); + } + + QVERIFY(QFileInfo::exists(relativeProductBuildDir("cursor") + "/cursor.cur")); + { + QFile f(relativeProductBuildDir("cursor") + "/cursor.cur"); + QVERIFY(f.open(QIODevice::ReadOnly)); + const auto b = f.readAll(); + QVERIFY(b.size() > 0); + QCOMPARE(b.at(2), '\x2'); // cursor + QCOMPARE(b.at(4), '\x2'); // 2 images + QCOMPARE(b.at(10), '\0'); + QCOMPARE(b.at(12), '\0'); + QCOMPARE(b.at(26), '\0'); + QCOMPARE(b.at(28), '\0'); + } + + QVERIFY(QFileInfo::exists(relativeProductBuildDir("cursor-hotspot") + "/cursor-hotspot.cur")); + { + QFile f(relativeProductBuildDir("cursor-hotspot") + "/cursor-hotspot.cur"); + QVERIFY(f.open(QIODevice::ReadOnly)); + const auto b = f.readAll(); + QVERIFY(b.size() > 0); + QCOMPARE(b.at(2), '\x2'); // cursor + QCOMPARE(b.at(4), '\x2'); // 2 images + const bool hasCursorHotspotBug = m_qbsStderr.contains( + "does not support setting the hotspot for cursor files with multiple images"); + if (hasCursorHotspotBug) { + QCOMPARE(b.at(10), '\0'); + QCOMPARE(b.at(12), '\0'); + QCOMPARE(b.at(26), '\0'); + QCOMPARE(b.at(28), '\0'); + QWARN("this version of icoutil does not support setting the hotspot " + "for cursor files with multiple images"); + } else { + QCOMPARE(b.at(10), '\x8'); + QCOMPARE(b.at(12), '\x9'); + QCOMPARE(b.at(26), '\x10'); + QCOMPARE(b.at(28), '\x11'); + } + } + + QVERIFY(QFileInfo::exists(relativeProductBuildDir("cursor-hotspot-single") + + "/cursor-hotspot-single.cur")); + { + QFile f(relativeProductBuildDir("cursor-hotspot-single") + "/cursor-hotspot-single.cur"); + QVERIFY(f.open(QIODevice::ReadOnly)); + const auto b = f.readAll(); + QVERIFY(b.size() > 0); + QCOMPARE(b.at(2), '\x2'); // cursor + QCOMPARE(b.at(4), '\x1'); // 1 image + + // No version check needed because the hotspot can always be set if there's only one image + QCOMPARE(b.at(10), '\x8'); + QCOMPARE(b.at(12), '\x9'); + } + + QVERIFY(QFileInfo::exists(relativeProductBuildDir("iconset") + "/dmg.ico")); + { + QFile f(relativeProductBuildDir("iconset") + "/dmg.ico"); + QVERIFY(f.open(QIODevice::ReadOnly)); + const auto b = f.readAll(); + QVERIFY(b.size() > 0); + QCOMPARE(b.at(2), '\x1'); // icon + QCOMPARE(b.at(4), '\x5'); // 5 images + } + + QVERIFY(QFileInfo::exists(relativeProductBuildDir("iconset") + "/dmg.cur")); + { + QFile f(relativeProductBuildDir("iconset") + "/dmg.cur"); + QVERIFY(f.open(QIODevice::ReadOnly)); + const auto b = f.readAll(); + QVERIFY(b.size() > 0); + QCOMPARE(b.at(2), '\x2'); // cursor + QCOMPARE(b.at(4), '\x5'); // 5 images + QCOMPARE(b.at(10), '\0'); + QCOMPARE(b.at(12), '\0'); + QCOMPARE(b.at(26), '\0'); + QCOMPARE(b.at(28), '\0'); + } +} + +void TestBlackbox::importAssignment() +{ + QDir::setCurrent(testDataDir + "/import-assignment"); + QCOMPARE(runQbs(QStringList("project.qbsSearchPaths:" + QDir::currentPath())), 0); + QVERIFY2(m_qbsStdout.contains("key 1 = value1") && m_qbsStdout.contains("key 2 = value2"), + m_qbsStdout.constData()); +} + +void TestBlackbox::importChangeTracking() +{ + QDir::setCurrent(testDataDir + "/import-change-tracking"); + QCOMPARE(runQbs(QStringList({"-f", "import-change-tracking.qbs"})), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running probe1"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running probe2"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData()); + + // Change in imported file that is not used in any rule or command. + WAIT_FOR_NEW_TIMESTAMP(); + touch("irrelevant.js"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe1 "), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData()); + + // Change in directly imported file only used by one prepare script. + WAIT_FOR_NEW_TIMESTAMP(); + touch("custom1prepare1.js"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe1 "), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData()); + + // Change in recursively imported file only used by one prepare script. + WAIT_FOR_NEW_TIMESTAMP(); + touch("custom1prepare2.js"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe1 "), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData()); + + // Change in imported file used only by one command. + WAIT_FOR_NEW_TIMESTAMP(); + touch("custom1command.js"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe1 "), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData()); + + // Change in file only used by one prepare script, using directory import. + WAIT_FOR_NEW_TIMESTAMP(); + touch("custom2prepare/custom2prepare2.js"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe1 "), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData()); + + // Change in file used only by one command, imported via search path. + WAIT_FOR_NEW_TIMESTAMP(); + touch("imports/custom2command/custom2command1.js"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe1 "), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData()); + + // Change in directly imported file only used by one Probe + WAIT_FOR_NEW_TIMESTAMP(); + touch("probe1.js"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running probe1"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData()); + + // Change in indirectly imported file only used by one Probe + WAIT_FOR_NEW_TIMESTAMP(); + touch("probe2.js"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running probe1"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData()); + + // Change everything at once. + WAIT_FOR_NEW_TIMESTAMP(); + touch("irrelevant.js"); + touch("custom1prepare1.js"); + touch("custom1prepare2.js"); + touch("custom1command.js"); + touch("custom2prepare/custom2prepare1.js"); + touch("imports/custom2command/custom2command2.js"); + touch("probe2.js"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running probe1"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("running probe2"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running custom1 prepare script"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running custom2 prepare script"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running custom1 command"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("running custom2 command"), m_qbsStdout.constData()); +} + +void TestBlackbox::probesInNestedModules() +{ + QDir::setCurrent(testDataDir + "/probes-in-nested-modules"); + QbsRunParameters params; + QCOMPARE(runQbs(params), 0); + + QCOMPARE(m_qbsStdout.count("running probe a"), 1); + QCOMPARE(m_qbsStdout.count("running probe b"), 1); + QCOMPARE(m_qbsStdout.count("running probe c"), 1); + QCOMPARE(m_qbsStdout.count("running second probe a"), 1); + + QVERIFY(m_qbsStdout.contains("product a, outer.somethingElse = goodbye")); + QVERIFY(m_qbsStdout.contains("product b, inner.something = hahaha")); + QVERIFY(m_qbsStdout.contains("product c, inner.something = hello")); + + QVERIFY(m_qbsStdout.contains("product a, inner.something = hahaha")); + QVERIFY(m_qbsStdout.contains("product a, outer.something = hahaha")); +} + +QTEST_MAIN(TestBlackbox) diff --git a/tests/auto/blackbox/tst_blackbox.h b/tests/auto/blackbox/tst_blackbox.h new file mode 100644 index 00000000..578ccd3a --- /dev/null +++ b/tests/auto/blackbox/tst_blackbox.h @@ -0,0 +1,350 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 Jochen Ulrich +** 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$ +** +****************************************************************************/ + +#ifndef TST_BLACKBOX_H +#define TST_BLACKBOX_H + +#include "tst_blackboxbase.h" + +class TestBlackbox : public TestBlackboxBase +{ + Q_OBJECT + +public: + TestBlackbox(); + +private slots: + void addFileTagToGeneratedArtifact(); + void alwaysRun(); + void alwaysRun_data(); + void artifactsMapChangeTracking(); + void artifactsMapInvalidation(); + void artifactsMapRaceCondition(); + void artifactScanning(); + void assembly(); + void autotestWithDependencies(); + void autotestTimeout(); + void autotestTimeout_data(); + void autotests_data(); + void autotests(); + void auxiliaryInputsFromDependencies(); + void badInterpreter(); + void bomSources(); + void buildDataOfDisabledProduct(); + void buildDirectories(); + void buildEnvChange(); + void buildGraphVersions(); + void buildVariantDefaults_data(); + void buildVariantDefaults(); + void capnproto(); + void capnproto_data(); + void changedFiles_data(); + void changedFiles(); + void changedInputsFromDependencies(); + void changedRuleInputs(); + void changeInDisabledProduct(); + void changeInImportedFile(); + void changeTrackingAndMultiplexing(); + void checkProjectFilePath(); + void checkTimestamps(); + void chooseModuleInstanceByPriority(); + void chooseModuleInstanceByPriority_data(); + void clean(); + void cli(); + void combinedSources(); + void commandFile(); + void compilerDefinesByLanguage(); + void concurrentExecutor(); + void conditionalExport(); + void conditionalFileTagger(); + void configure(); + void conflictingArtifacts(); + void cxxLanguageVersion(); + void cxxLanguageVersion_data(); + void conanfileProbe(); + void cpuFeatures(); + void dependenciesProperty(); + void dependencyScanningLoop(); + void deprecatedProperty(); + void disappearedProfile(); + void discardUnusedData(); + void discardUnusedData_data(); + void driverLinkerFlags(); + void driverLinkerFlags_data(); + void dynamicLibraryInModule(); + void dynamicMultiplexRule(); + void dynamicProject(); + void dynamicRuleOutputs(); + void emptyProfile(); + void enableExceptions(); + void enableExceptions_data(); + void enableRtti(); + void envMerging(); + void envNormalization(); + void erroneousFiles_data(); + void erroneousFiles(); + void errorInfo(); + void escapedLinkerFlags(); + void explicitlyDependsOn(); + void explicitlyDependsOn_data(); + void exportedDependencyInDisabledProduct(); + void exportedDependencyInDisabledProduct_data(); + void exportedPropertyInDisabledProduct(); + void exportedPropertyInDisabledProduct_data(); + void exportRule(); + void exportToOutsideSearchPath(); + void exportsPkgconfig(); + void exportsQbs(); + void externalLibs(); + void fileDependencies(); + void fileTagsFilterMerging(); + void freedesktop(); + void generatedArtifactAsInputToDynamicRule(); + void generateLinkerMapFile(); + void generator(); + void generator_data(); + void groupsInModules(); + void grpc_data(); + void grpc(); + void hostOsProperties(); + void ico(); + void importAssignment(); + void importChangeTracking(); + void importInPropertiesCondition(); + void importSearchPath(); + void importingProduct(); + void importsConflict(); + void includeLookup(); + void innoSetup(); + void innoSetupDependencies(); + void inputTagsChangeTracking_data(); + void inputTagsChangeTracking(); + void inputsFromDependencies(); + void installable(); + void installableAsAuxiliaryInput(); + void installedApp(); + void installDuplicates(); + void installDuplicatesNoError(); + void installedSourceFiles(); + void installedTransformerOutput(); + void installLocations_data(); + void installLocations(); + void installPackage(); + void installRootFromProjectFile(); + void installTree(); + void invalidCommandProperty_data(); + void invalidCommandProperty(); + void invalidExtensionInstantiation(); + void invalidExtensionInstantiation_data(); + void invalidInstallDir(); + void invalidLibraryNames(); + void invalidLibraryNames_data(); + void jsExtensionsFile(); + void jsExtensionsFileInfo(); + void jsExtensionsProcess(); + void jsExtensionsPropertyList(); + void jsExtensionsTemporaryDir(); + void jsExtensionsTextFile(); + void jsExtensionsBinaryFile(); + void lastModuleCandidateBroken(); + void ld(); + void linkerMode(); + void linkerVariant_data(); + void linkerVariant(); + void lexyacc(); + void lexyaccOutputs(); + void lexyaccOutputs_data(); + void linkerLibraryDuplicates(); + void linkerLibraryDuplicates_data(); + void linkerScripts(); + void linkerModuleDefinition(); + void listProducts(); + void listPropertiesWithOuter(); + void listPropertyOrder(); + void loadableModule(); + void localDeployment(); + void makefileGenerator(); + void maximumCLanguageVersion(); + void maximumCxxLanguageVersion(); + void moduleProviders(); + void fallbackModuleProvider_data(); + void fallbackModuleProvider(); + void minimumSystemVersion(); + void minimumSystemVersion_data(); + void missingBuildGraph(); + void missingBuildGraph_data(); + void missingDependency(); + void missingProjectFile(); + void missingOverridePrefix(); + void moduleConditions(); + void movedFileDependency(); + void multipleChanges(); + void multipleConfigurations(); + void multiplexedTool(); + void nestedGroups(); + void nestedProperties(); + void newOutputArtifact(); + void noExportedSymbols_data(); + void noExportedSymbols(); + void noProfile(); + void noSuchProfile(); + void nodejs(); + void nonBrokenFilesInBrokenProduct(); + void nonDefaultProduct(); + void notAlwaysUpdated(); + void nsis(); + void nsisDependencies(); + void outOfDateMarking(); + void outputArtifactAutoTagging(); + void outputRedirection(); + void overrideProjectProperties(); + void pathProbe_data(); + void pathProbe(); + void pchChangeTracking(); + void perGroupDefineInExportItem(); + void pkgConfigProbe(); + void pkgConfigProbe_data(); + void pkgConfigProbeSysroot(); + void pluginDependency(); + void precompiledAndPrefixHeaders(); + void precompiledHeaderAndRedefine(); + void preventFloatingPointValues(); + void probeChangeTracking(); + void probeProperties(); + void probesAndShadowProducts(); + void probeInExportedModule(); + void probesAndArrayProperties(); + void probesInNestedModules(); + void productDependenciesByType(); + void productInExportedModule(); + void productProperties(); + void propertyAssignmentOnNonPresentModule(); + void propertyAssignmentInFailedModule(); + void propertyChanges(); + void propertyEvaluationContext(); + void propertyPrecedence(); + void properQuoting(); + void propertiesInExportItems(); + void protobuf_data(); + void protobuf(); + void pseudoMultiplexing(); + void qbsConfig(); + void qbsSession(); + void qbsVersion(); + void qtBug51237(); + void radAfterIncompleteBuild(); + void radAfterIncompleteBuild_data(); + void recursiveRenaming(); + void recursiveWildcards(); + void referenceErrorInExport(); + void removeDuplicateLibraries_data(); + void removeDuplicateLibraries(); + void reproducibleBuild(); + void reproducibleBuild_data(); + void require(); + void requireDeprecated(); + void rescueTransformerData(); + void responseFiles(); + void retaggedOutputArtifact(); + void ruleConditions(); + void ruleConnectionWithExcludedInputs(); + void ruleCycle(); + void ruleWithNoInputs(); + void ruleWithNonRequiredInputs(); + void sanitizer_data(); + void sanitizer(); + void scannerItem(); + void scanResultInOtherProduct(); + void scanResultInNonDependency(); + void setupBuildEnvironment(); + void setupRunEnvironment(); + void smartRelinking(); + void smartRelinking_data(); + void soVersion(); + void soVersion_data(); + void sourceArtifactChanges(); + void subProfileChangeTracking(); + void successiveChanges(); + void symbolLinkMode(); + void symlinkRemoval(); + void renameDependency(); + void separateDebugInfo(); + void sevenZip(); + void sourceArtifactInInputsFromDependencies(); + void staticLibWithoutSources(); + void suspiciousCalls(); + void suspiciousCalls_data(); + void systemIncludePaths(); + void distributionIncludePaths(); + void systemRunPaths(); + void systemRunPaths_data(); + void tar(); + void textTemplate(); + void toolLookup(); + void topLevelSearchPath(); + void trackAddFile(); + void trackAddFileTag(); + void trackAddProduct(); + void trackExternalProductChanges(); + void trackGroupConditionChange(); + void trackRemoveFile(); + void trackRemoveFileTag(); + void trackRemoveProduct(); + void transitiveInvalidDependencies(); + void transitiveOptionalDependencies(); + void typescript(); + void undefinedTargetPlatform(); + void usingsAsSoleInputsNonMultiplexed(); + void variantSuffix(); + void variantSuffix_data(); + void vcsGit(); + void vcsSubversion(); + void versionCheck(); + void versionCheck_data(); + void versionScript(); + void wholeArchive(); + void wholeArchive_data(); + void wildCardsAndRules(); + void wildcardRenaming(); + void wix(); + void wixDependencies(); + void zip(); + void zip_data(); + void zipInvalid(); + +private: + QMap findCli(int *status); + QMap findNodejs(int *status); + QMap findTypeScript(int *status); + QString findArchiver(const QString &fileName, int *status = nullptr); + static bool lexYaccExist(); + static qbs::Version bisonVersion(); +}; + +#endif // TST_BLACKBOX_H diff --git a/tests/auto/blackbox/tst_blackboxandroid.cpp b/tests/auto/blackbox/tst_blackboxandroid.cpp new file mode 100644 index 00000000..5e850874 --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxandroid.cpp @@ -0,0 +1,820 @@ +/**************************************************************************** +** +** 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 +#include +#include + +#include +#include +#include + +using qbs::Internal::none_of; +using qbs::Profile; + +QMap 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 {}; + const auto tools = QJsonDocument::fromJson(file.readAll()).toVariant().toMap(); + return { + {"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())}, + {"jar", QDir::fromNativeSeparators(tools["jar"].toString())}, + }; +} + +TestBlackboxAndroid::TestBlackboxAndroid() + : TestBlackboxBase(SRCDIR "/testdata-android", "blackbox-android") +{ +} + +static QString theProfileName(bool forQt) +{ + return forQt ? "qbs_autotests-android-qt" : profileName(); +} + +void TestBlackboxAndroid::android() +{ + QFETCH(QString, projectDir); + QFETCH(QStringList, productNames); + QFETCH(QList, expectedFilesLists); + QFETCH(QStringList, qmlAppCustomProperties); + QFETCH(bool, enableAapt2); + QFETCH(bool, generateAab); + QFETCH(bool, isIncrementalBuild); + + const SettingsPtr s = settings(); + Profile p(theProfileName(projectDir == "qml-app"), s.get()); + if (!p.exists()) + p = Profile("none", s.get()); + int status; + const auto androidPaths = findAndroid(&status, p.name()); + QCOMPARE(status, 0); + + const auto sdkPath = androidPaths["sdk"]; + if (sdkPath.isEmpty()) + QSKIP("Android SDK is not installed"); + + const auto ndkPath = androidPaths["ndk"]; + if (ndkPath.isEmpty() && projectDir != "no-native") + QSKIP("Android NDK is not installed"); + + 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"); + + const QString buildSubDir = enableAapt2 ? (generateAab ? "aab" : "aapt2") : "aapt"; + QDir::setCurrent(testDataDir + "/" + projectDir); + + static const QStringList configNames { "debug", "release" }; + for (const QString &configName : configNames) { + auto currentExpectedFilesLists = expectedFilesLists; + const QString configArgument = "config:" + configName; + QbsRunParameters resolveParams("resolve"); + resolveParams.buildDirectory = buildSubDir; + resolveParams.arguments << configArgument << qmlAppCustomProperties; + resolveParams.profile = p.name(); + QCOMPARE(runQbs(resolveParams), 0); + QbsRunParameters buildParams(QStringList{"--command-echo-mode", "command-line", + configArgument}); + buildParams.buildDirectory = buildSubDir; + buildParams.profile = p.name(); + QCOMPARE(runQbs(buildParams), 0); + for (const QString &productName : qAsConst(productNames)) { + const QByteArray tag(QTest::currentDataTag()); + QCOMPARE(m_qbsStdout.count("Generating BuildConfig.java"), + isIncrementalBuild ? 0 : productNames.size()); + const QString packageName = productName + (generateAab ? ".aab" : ".apk"); + QVERIFY(m_qbsStdout.contains(packageName.toLocal8Bit())); + const QString packageFilePath = buildSubDir + "/" + relativeProductBuildDir(productName, + configName) + + '/' + packageName; + QVERIFY2(regularFileExists(packageFilePath), qPrintable(packageFilePath)); + const QString jarFilePath = androidPaths["jar"]; + QVERIFY(!jarFilePath.isEmpty()); + QProcess jar; + jar.start(jarFilePath, QStringList() << "-tf" << packageFilePath); + QVERIFY2(jar.waitForStarted(), qPrintable(jar.errorString())); + QVERIFY2(jar.waitForFinished(), qPrintable(jar.errorString())); + QVERIFY2(jar.exitCode() == 0, qPrintable(jar.readAllStandardError().constData())); + QByteArrayList actualFiles = jar.readAllStandardOutput().trimmed().split('\n'); + for (QByteArray &f : actualFiles) + f = f.trimmed(); + QByteArrayList missingExpectedFiles; + QByteArrayList expectedFiles = currentExpectedFilesLists.takeFirst(); + for (const QByteArray &expectedFile : expectedFiles) { + if (expectedFile.endsWith("/libgdbserver.so") && configName == "release") + continue; + auto it = std::find(actualFiles.begin(), actualFiles.end(), expectedFile); + if (it != actualFiles.end()) { + actualFiles.erase(it); + continue; + } + missingExpectedFiles << expectedFile; + } + if (!missingExpectedFiles.empty()) + QFAIL(QByteArray("missing expected files:\n") + missingExpectedFiles.join('\n')); + if (!actualFiles.empty()) { + QByteArray msg = "unexpected files encountered:\n" + actualFiles.join('\n'); + auto isFileSharedObject = [](const QByteArray &f) { + return f.endsWith(".so"); + }; + const auto isQmlToolingLib = [](const QByteArray &f) { + return f.contains("qmltooling"); + }; + if (none_of(actualFiles, isFileSharedObject) + || std::all_of(actualFiles.cbegin(), actualFiles.cend(), isQmlToolingLib)) { + QWARN(msg); + } else { + QFAIL(msg); + } + } + } + + 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(profileName(), s.get()); + const Profile pQt(theProfileName(true), s.get()); + QStringList archsStringList = p.value(QStringLiteral("qbs.architectures")).toStringList(); + if (archsStringList.empty()) + archsStringList << QStringLiteral("armv7a"); // must match default in common.qbs + QByteArrayList archs; + std::transform(archsStringList.begin(), archsStringList.end(), std::back_inserter(archs), + [] (const QString &s) { + return s.toUtf8().replace("armv7a", "armeabi-v7a") + .replace("armv5te", "armeabi") + .replace("arm64", "arm64-v8a"); + }); + const auto cxxLibPath = [&p, &pQt](const QByteArray &oldcxxLib, bool forQt) { + const bool usesClang = (forQt ? pQt : p).value(QStringLiteral("qbs.toolchainType")) + .toString() == "clang"; + const QByteArray path = "lib/${ARCH}/"; + return path + (usesClang ? "libc++_shared.so" : oldcxxLib); + }; + bool usingOldQt = true; + QStringList qmakeFilePaths = pQt.value(QStringLiteral("moduleProviders.Qt.qmakeFilePaths")). + toStringList(); + if (qmakeFilePaths.size() == 1) { + qbs::Version version = TestBlackboxBase::qmakeVersion(qmakeFilePaths[0]); + if (version.isValid() && version >= qbs::Version(5, 14)) + usingOldQt = false; + } + + QByteArrayList archsForQt; + if (usingOldQt) { + archsForQt = { pQt.value("qbs.architecture").toString().toUtf8() }; + if (archsStringList.empty()) + archsStringList << QStringLiteral("armv7a"); // must match default in common.qbs + } else { + QStringList archsForQtStringList = pQt.value(QStringLiteral("qbs.architectures")) + .toStringList(); + if (archsForQtStringList.empty()) + archsForQtStringList << pQt.value("qbs.architecture").toString(); + std::transform(archsForQtStringList.begin(), + archsForQtStringList.end(), + std::back_inserter(archsForQt), + [] (const QString &s) { + return s.toUtf8(); + }); + } + + QByteArrayList ndkArchsForQt; + std::transform(archsForQt.begin(), archsForQt.end(), std::back_inserter(ndkArchsForQt), + [] (const QString &s) { + return s.toUtf8().replace("armv7a", "armeabi-v7a") + .replace("armv5te", "armeabi") + .replace("arm64", "arm64-v8a"); + }); + + auto expandArchs = [] (const QByteArrayList &archs, const QByteArrayList &lst, bool aabPackage) { + const QByteArray &archPlaceHolder = "${ARCH}"; + QByteArrayList result; + QByteArray base( aabPackage ? "base/" : QByteArray()); + for (const QByteArray &entry : lst) { + if (entry.contains(archPlaceHolder)) { + for (const QByteArray &arch : qAsConst(archs)) + result << (base + QByteArray(entry).replace(archPlaceHolder, arch)); + } else { + result << (base + entry); + } + } + return result; + }; + + auto commonFiles = [](bool generateAab) { + if (generateAab) + return (QByteArrayList() + << "base/manifest/AndroidManifest.xml" << "base/dex/classes.dex" + << "BundleConfig.pb"); + return (QByteArrayList() + << "AndroidManifest.xml" << "META-INF/ANDROIDD.RSA" << "META-INF/ANDROIDD.SF" + << "META-INF/MANIFEST.MF" << "classes.dex"); + }; + + QTest::addColumn("projectDir"); + QTest::addColumn("productNames"); + QTest::addColumn>("expectedFilesLists"); + QTest::addColumn("qmlAppCustomProperties"); + QTest::addColumn("enableAapt2"); + QTest::addColumn("generateAab"); + QTest::addColumn("isIncrementalBuild"); + + const auto aaptVersion = [](bool enableAapt2) { + return QString("modules.Android.sdk.aaptName:") + (enableAapt2 ? "aapt2" : "aapt"); + }; + bool enableAapt2 = false; + const auto packageType = [](bool generateAab) { + return QString("modules.Android.sdk.packageType:") + (generateAab ? "aab" : "apk"); + }; + bool generateAab = false; + bool isIncrementalBuild = false; + + auto teaPotAppExpectedFiles = [&](const QByteArrayList &archs, bool generateAab) { + QByteArrayList expectedFile; + expectedFile << commonFiles(generateAab) + expandArchs(archs, { + "assets/Shaders/ShaderPlain.fsh", + "assets/Shaders/VS_ShaderPlain.vsh", + cxxLibPath("libgnustl_shared.so", false), + "lib/${ARCH}/libTeapotNativeActivity.so", + "res/layout/widgets.xml", + "res/mipmap-hdpi-v4/ic_launcher.png", + "res/mipmap-mdpi-v4/ic_launcher.png", + "res/mipmap-xhdpi-v4/ic_launcher.png", + "res/mipmap-xxhdpi-v4/ic_launcher.png"}, generateAab); + if (generateAab) + expectedFile << "base/resources.pb" << "base/assets.pb" << "base/native.pb"; + else + expectedFile << "resources.arsc"; + return expectedFile; + }; + + QTest::newRow("teapot") + << "teapot" << QStringList("TeapotNativeActivity") + << (QList() << teaPotAppExpectedFiles(archs, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = true; + QTest::newRow("teapot aapt2") + << "teapot" << QStringList("TeapotNativeActivity") + << (QList() << teaPotAppExpectedFiles(archs, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("teapot aapt2 aab") + << "teapot" << QStringList("TeapotNativeActivity") + << (QList() << teaPotAppExpectedFiles(archs, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = false; + generateAab = false; + QTest::newRow("minimal-native") + << "minimal-native" << QStringList("minimalnative") + << (QList() << commonFiles(generateAab) + expandArchs({archs.first()}, { + "lib/${ARCH}/libminimalnative.so", + cxxLibPath("libstlport_shared.so", false), + "lib/${ARCH}/libdependency.so"}, generateAab)) + << QStringList{"products.minimalnative.multiplexByQbsProperties:[]", + "modules.qbs.architecture:" + archsStringList.first(), + aaptVersion(enableAapt2)} + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = true; + QTest::newRow("minimal-native aapt2") + << "minimal-native" << QStringList("minimalnative") + << (QList() << commonFiles(generateAab) + + (QByteArrayList() << "resources.arsc") + expandArchs({archs.first()}, { + "lib/${ARCH}/libminimalnative.so", + cxxLibPath("libstlport_shared.so", false), + "lib/${ARCH}/libdependency.so"}, generateAab)) + << QStringList{"products.minimalnative.multiplexByQbsProperties:[]", + "modules.qbs.architecture:" + archsStringList.first(), + aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("minimal-native aapt2 aab") + << "minimal-native" << QStringList("minimalnative") + << (QList() << commonFiles(generateAab) + + (QByteArrayList() << "base/resources.pb" << "base/native.pb") + + expandArchs({archs.first()}, { + "lib/${ARCH}/libminimalnative.so", + cxxLibPath("libstlport_shared.so", false), + "lib/${ARCH}/libdependency.so"}, generateAab)) + << QStringList{"products.minimalnative.multiplexByQbsProperties:[]", + "modules.qbs.architecture:" + archsStringList.first(), + aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + auto qmlAppExpectedFiles = [&](bool generateAab) { + QByteArrayList expectedFile; + if (usingOldQt) { + expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { + "assets/--Added-by-androiddeployqt--/qml/QtQuick.2/plugins.qmltypes", + "assets/--Added-by-androiddeployqt--/qml/QtQuick.2/qmldir", + "assets/--Added-by-androiddeployqt--/qml/QtQuick/Window.2/plugins.qmltypes", + "assets/--Added-by-androiddeployqt--/qml/QtQuick/Window.2/qmldir", + "assets/--Added-by-androiddeployqt--/qt_cache_pregenerated_file_list", + cxxLibPath("libgnustl_shared.so", true), + "lib/${ARCH}/libplugins_bearer_libqandroidbearer.so", + "lib/${ARCH}/libplugins_imageformats_libqgif.so", + "lib/${ARCH}/libplugins_imageformats_libqicns.so", + "lib/${ARCH}/libplugins_imageformats_libqico.so", + "lib/${ARCH}/libplugins_imageformats_libqjpeg.so", + "lib/${ARCH}/libplugins_imageformats_libqtga.so", + "lib/${ARCH}/libplugins_imageformats_libqtiff.so", + "lib/${ARCH}/libplugins_imageformats_libqwbmp.so", + "lib/${ARCH}/libplugins_imageformats_libqwebp.so", + "lib/${ARCH}/libplugins_platforms_android_libqtforandroid.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_debugger.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_inspector.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_local.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_messages.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_native.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_nativedebugger.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_profiler.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_preview.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_quickprofiler.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_server.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_tcp.so", + "lib/${ARCH}/libqml_QtQuick.2_libqtquick2plugin.so", + "lib/${ARCH}/libqml_QtQuick_Window.2_libwindowplugin.so", + "lib/${ARCH}/libQt5Core.so", + "lib/${ARCH}/libQt5Gui.so", + "lib/${ARCH}/libQt5Network.so", + "lib/${ARCH}/libQt5Qml.so", + "lib/${ARCH}/libQt5QuickParticles.so", + "lib/${ARCH}/libQt5Quick.so", + "lib/${ARCH}/libqmlapp.so"}, generateAab); + } else { + expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { + "assets/android_rcc_bundle.rcc", + cxxLibPath("libgnustl_shared.so", true), + "lib/${ARCH}/libplugins_bearer_qandroidbearer_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qgif_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qicns_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qico_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qjpeg_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qtga_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qtiff_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qwbmp_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qwebp_${ARCH}.so", + "lib/${ARCH}/libplugins_platforms_qtforandroid_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_debugger_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_inspector_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_local_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_messages_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_native_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_nativedebugger_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_profiler_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_preview_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_quickprofiler_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_server_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_tcp_${ARCH}.so", + "lib/${ARCH}/libqml_QtQuick.2_qtquick2plugin_${ARCH}.so", + "lib/${ARCH}/libqml_QtQuick_Window.2_windowplugin_${ARCH}.so", + "lib/${ARCH}/libQt5Core_${ARCH}.so", + "lib/${ARCH}/libQt5Gui_${ARCH}.so", + "lib/${ARCH}/libQt5Network_${ARCH}.so", + "lib/${ARCH}/libQt5Qml_${ARCH}.so", + "lib/${ARCH}/libQt5QuickParticles_${ARCH}.so", + "lib/${ARCH}/libQt5Quick_${ARCH}.so", + "lib/${ARCH}/libQt5QmlModels_${ARCH}.so", + "lib/${ARCH}/libQt5QmlWorkerScript_${ARCH}.so", + "lib/${ARCH}/libqmlapp_${ARCH}.so"}, generateAab); + } + if (generateAab) + expectedFile << "base/resources.pb" << "base/assets.pb" << "base/native.pb"; + else + expectedFile << "resources.arsc"; + return expectedFile; + }; + + auto qmlAppMinistroExpectedFiles = [&](bool generateAab) { + QByteArrayList expectedFile; + if (usingOldQt) { + expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { + "assets/--Added-by-androiddeployqt--/qt_cache_pregenerated_file_list", + cxxLibPath("libgnustl_shared.so", true), + "lib/${ARCH}/libqmlapp.so"}, generateAab); + } else { + expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { + "assets/android_rcc_bundle.rcc", + cxxLibPath("libgnustl_shared.so", true), + "lib/${ARCH}/libqmlapp_${ARCH}.so"}, generateAab); + } + if (generateAab) + expectedFile << "base/resources.pb" << "base/assets.pb" << "base/native.pb"; + else + expectedFile << "resources.arsc"; + return expectedFile; + }; + auto qmlAppCustomMetaDataExpectedFiles = [&](bool generateAab) { + QByteArrayList expectedFile; + if (usingOldQt) { + expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { + "assets/--Added-by-androiddeployqt--/qml/QtQuick.2/plugins.qmltypes", + "assets/--Added-by-androiddeployqt--/qml/QtQuick.2/qmldir", + "assets/--Added-by-androiddeployqt--/qml/QtQuick/Window.2/plugins.qmltypes", + "assets/--Added-by-androiddeployqt--/qml/QtQuick/Window.2/qmldir", + "assets/--Added-by-androiddeployqt--/qt_cache_pregenerated_file_list", + "assets/dummyasset.txt", + cxxLibPath("libgnustl_shared.so", true), + "lib/${ARCH}/libplugins_bearer_libqandroidbearer.so", + "lib/${ARCH}/libplugins_imageformats_libqgif.so", + "lib/${ARCH}/libplugins_imageformats_libqicns.so", + "lib/${ARCH}/libplugins_imageformats_libqico.so", + "lib/${ARCH}/libplugins_imageformats_libqjpeg.so", + "lib/${ARCH}/libplugins_imageformats_libqtga.so", + "lib/${ARCH}/libplugins_imageformats_libqtiff.so", + "lib/${ARCH}/libplugins_imageformats_libqwbmp.so", + "lib/${ARCH}/libplugins_imageformats_libqwebp.so", + "lib/${ARCH}/libplugins_platforms_android_libqtforandroid.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_debugger.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_inspector.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_local.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_messages.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_native.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_nativedebugger.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_profiler.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_preview.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_quickprofiler.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_server.so", + "lib/${ARCH}/libplugins_qmltooling_libqmldbg_tcp.so", + "lib/${ARCH}/libqml_QtQuick.2_libqtquick2plugin.so", + "lib/${ARCH}/libqml_QtQuick_Window.2_libwindowplugin.so", + "lib/${ARCH}/libQt5Core.so", + "lib/${ARCH}/libQt5Gui.so", + "lib/${ARCH}/libQt5Network.so", + "lib/${ARCH}/libQt5Qml.so", + "lib/${ARCH}/libQt5QuickParticles.so", + "lib/${ARCH}/libQt5Quick.so", + "lib/${ARCH}/libqmlapp.so"}, generateAab); + } else { + expectedFile << commonFiles(generateAab) + expandArchs(ndkArchsForQt, { + "assets/android_rcc_bundle.rcc", + "assets/dummyasset.txt", + cxxLibPath("libgnustl_shared.so", true), + "lib/${ARCH}/libplugins_bearer_qandroidbearer_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qgif_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qicns_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qico_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qjpeg_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qtga_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qtiff_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qwbmp_${ARCH}.so", + "lib/${ARCH}/libplugins_imageformats_qwebp_${ARCH}.so", + "lib/${ARCH}/libplugins_platforms_qtforandroid_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_debugger_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_inspector_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_local_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_messages_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_native_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_nativedebugger_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_profiler_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_preview_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_quickprofiler_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_server_${ARCH}.so", + "lib/${ARCH}/libplugins_qmltooling_qmldbg_tcp_${ARCH}.so", + "lib/${ARCH}/libqml_QtQuick.2_qtquick2plugin_${ARCH}.so", + "lib/${ARCH}/libqml_QtQuick_Window.2_windowplugin_${ARCH}.so", + "lib/${ARCH}/libQt5Core_${ARCH}.so", + "lib/${ARCH}/libQt5Gui_${ARCH}.so", + "lib/${ARCH}/libQt5Network_${ARCH}.so", + "lib/${ARCH}/libQt5Qml_${ARCH}.so", + "lib/${ARCH}/libQt5QuickParticles_${ARCH}.so", + "lib/${ARCH}/libQt5Quick_${ARCH}.so", + "lib/${ARCH}/libQt5QmlModels_${ARCH}.so", + "lib/${ARCH}/libQt5QmlWorkerScript_${ARCH}.so", + "lib/${ARCH}/libqmlapp_${ARCH}.so"}, generateAab); + } + if (generateAab) + expectedFile << "base/resources.pb" << "base/assets.pb" << "base/native.pb"; + else + expectedFile << "resources.arsc"; + return expectedFile; + }; + QStringList qmlAppCustomProperties; + if (usingOldQt) { + qmlAppCustomProperties = QStringList{"modules.Android.sdk.automaticSources:false", + "modules.qbs.architecture:" + archsForQt.first()}; + } else { + qmlAppCustomProperties = QStringList{"modules.Android.sdk.automaticSources:false"}; + } + + // aapt tool for the resources works with a directory option pointing to the parent directory + // of the resources (res). + // The Qt.android_support module adds res/values/libs.xml (from Qt install dir). So the res from + // Qt install res directory is added to aapt. This results in adding res/layout/splash.xml to + // the package eventhough the file is not needed. + // On the other hand aapt2 requires giving all the resources files. + // Also when enabling aapt2 the resources.arsc is always created, eventhough no resources are + // declared. + enableAapt2 = false; + generateAab = false; + QTest::newRow("qml app") + << "qml-app" << QStringList("qmlapp") + << (QList() << (QByteArrayList() << qmlAppExpectedFiles(generateAab) + << "res/layout/splash.xml")) + << (QStringList() << qmlAppCustomProperties << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; + + enableAapt2 = true; + QTest::newRow("qml app aapt2") + << "qml-app" << QStringList("qmlapp") + << (QList() << qmlAppExpectedFiles(generateAab)) + << (QStringList() << qmlAppCustomProperties << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("qml app aab") + << "qml-app" << QStringList("qmlapp") + << (QList() << qmlAppExpectedFiles(generateAab)) + << (QStringList() << qmlAppCustomProperties << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = false; + generateAab = false; + isIncrementalBuild = true; + QTest::newRow("qml app using Ministro") + << "qml-app" << QStringList("qmlapp") + << (QList() << (QByteArrayList() + << qmlAppMinistroExpectedFiles(generateAab) + << "res/layout/splash.xml")) + << (QStringList() << "modules.Qt.android_support.useMinistro:true" + << "modules.Android.sdk.automaticSources:false" << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = true; + QTest::newRow("qml app using Ministro aapt2") + << "qml-app" << QStringList("qmlapp") + << (QList() << qmlAppMinistroExpectedFiles(generateAab)) + << (QStringList() << "modules.Qt.android_support.useMinistro:true" + << "modules.Android.sdk.automaticSources:false" << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("qml app using Ministro aab") + << "qml-app" << QStringList("qmlapp") + << (QList() << qmlAppMinistroExpectedFiles(generateAab)) + << (QStringList() << "modules.Qt.android_support.useMinistro:true" + << "modules.Android.sdk.automaticSources:false" << aaptVersion(enableAapt2) + << packageType(generateAab)) + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = false; + generateAab = false; + QTest::newRow("qml app with custom metadata") + << "qml-app" << QStringList("qmlapp") + << (QList() << (QByteArrayList() + << qmlAppCustomMetaDataExpectedFiles(generateAab) + << "res/layout/splash.xml")) + << QStringList{"modules.Android.sdk.automaticSources:true", + aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = true; + QTest::newRow("qml app with custom metadata aapt2") + << "qml-app" << QStringList("qmlapp") + << (QList() << (QByteArrayList() + << qmlAppCustomMetaDataExpectedFiles(generateAab))) + << QStringList{"modules.Android.sdk.automaticSources:true", aaptVersion(enableAapt2), + packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("qml app with custom metadata aab") + << "qml-app" << QStringList("qmlapp") + << (QList() << (QByteArrayList() + << qmlAppCustomMetaDataExpectedFiles(generateAab))) + << QStringList{"modules.Android.sdk.automaticSources:true", aaptVersion(enableAapt2), + packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + isIncrementalBuild = false; + enableAapt2 = false; + generateAab = false; + QTest::newRow("no native") + << "no-native" + << QStringList("com.example.android.basicmediadecoder") + << (QList() << commonFiles(generateAab) + expandArchs(archs, { + "resources.arsc", + "res/drawable-hdpi-v4/ic_action_play_disabled.png", + "res/drawable-hdpi-v4/ic_action_play.png", + "res/drawable-hdpi-v4/ic_launcher.png", + "res/drawable-hdpi-v4/tile.9.png", + "res/drawable-mdpi-v4/ic_action_play_disabled.png", + "res/drawable-mdpi-v4/ic_action_play.png", + "res/drawable-mdpi-v4/ic_launcher.png", + "res/drawable/selector_play.xml", + "res/drawable-xhdpi-v4/ic_action_play_disabled.png", + "res/drawable-xhdpi-v4/ic_action_play.png", + "res/drawable-xhdpi-v4/ic_launcher.png", + "res/drawable-xxhdpi-v4/ic_launcher.png", + "res/layout/sample_main.xml", + "res/menu/action_menu.xml", + "res/menu-v11/action_menu.xml", + "res/raw/vid_bigbuckbunny.mp4"}, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = true; + auto noNativeExpectedFiles = [&](bool generateAab) { + QByteArrayList expectedFile; + expectedFile << commonFiles(generateAab) + expandArchs(archs, { + "res/drawable-hdpi-v4/ic_action_play_disabled.png", + "res/drawable-hdpi-v4/ic_action_play.png", + "res/drawable-hdpi-v4/ic_launcher.png", + "res/drawable-hdpi-v4/tile.9.png", + "res/drawable-mdpi-v4/ic_action_play_disabled.png", + "res/drawable-mdpi-v4/ic_action_play.png", + "res/drawable-mdpi-v4/ic_launcher.png", + "res/drawable/selector_play.xml", + "res/drawable-xhdpi-v4/ic_action_play_disabled.png", + "res/drawable-xhdpi-v4/ic_action_play.png", + "res/drawable-xhdpi-v4/ic_launcher.png", + "res/drawable-xxhdpi-v4/ic_launcher.png", + "res/layout/sample_main.xml", + "res/menu/action_menu.xml", + // I have no idea why this file is generated with aapt and not with aapt2 + //"res/menu-v11/action_menu.xml", + "res/raw/vid_bigbuckbunny.mp4"}, generateAab); + if (generateAab) + expectedFile << "base/resources.pb"; + else + expectedFile << "resources.arsc"; + return expectedFile; + }; + QTest::newRow("no native aapt2") + << "no-native" + << QStringList("com.example.android.basicmediadecoder") + << (QList() << noNativeExpectedFiles(generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("no native aab") + << "no-native" + << QStringList("com.example.android.basicmediadecoder") + << (QList() << noNativeExpectedFiles(generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = false; + generateAab = false; + QTest::newRow("aidl") << "aidl" << QStringList("io.qbs.aidltest") + << (QList() << (QByteArrayList() + << commonFiles(generateAab))) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = true; + QTest::newRow("aidl") << "aidl" << QStringList("io.qbs.aidltest") + << (QList() << (QByteArrayList() + << commonFiles(generateAab) + << "resources.arsc")) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("aidl") << "aidl" << QStringList("io.qbs.aidltest") + << (QList() << (QByteArrayList() + << commonFiles(generateAab) + << "base/resources.pb")) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = false; + generateAab = false; + QTest::newRow("multiple libs") + << "multiple-libs-per-apk" + << QStringList("twolibs") + << (QList() << commonFiles(generateAab) + expandArchs(archs, { + "resources.arsc", + "lib/${ARCH}/liblib1.so", + "lib/${ARCH}/liblib2.so", + cxxLibPath("libstlport_shared.so", false)}, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = true; + QTest::newRow("multiple libs aapt2") + << "multiple-libs-per-apk" + << QStringList("twolibs") + << (QList() << commonFiles(generateAab) + expandArchs(archs, { + "resources.arsc", + "lib/${ARCH}/liblib1.so", + "lib/${ARCH}/liblib2.so", + cxxLibPath("libstlport_shared.so", false)}, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("multiple libs aab") + << "multiple-libs-per-apk" + << QStringList("twolibs") + << (QList() << commonFiles(generateAab) + expandArchs(archs, { + "resources.pb", "native.pb", + "lib/${ARCH}/liblib1.so", + "lib/${ARCH}/liblib2.so", + cxxLibPath("libstlport_shared.so", false)}, generateAab)) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = false; + generateAab = false; + auto expectedFiles1 = [&](bool generateAab) { + QByteArrayList expectedFile = qbs::toList(qbs::toSet(commonFiles(generateAab) + + expandArchs(QByteArrayList{"armeabi-v7a", "x86"}, { + "lib/${ARCH}/libp1lib1.so", + cxxLibPath("libstlport_shared.so", false)}, generateAab) + + expandArchs(QByteArrayList{archs}, { + "lib/${ARCH}/libp1lib2.so", + cxxLibPath("libstlport_shared.so", false)}, generateAab))); + if (generateAab) + expectedFile << "base/resources.pb" << "base/native.pb"; + else + expectedFile << "resources.arsc"; + return expectedFile; + }; + auto expectedFiles2 = [&](bool generateAab) { + QByteArrayList expectedFile = commonFiles(generateAab) + expandArchs(archs, { + "lib/${ARCH}/libp2lib1.so", + "lib/${ARCH}/libp2lib2.so", + cxxLibPath("libstlport_shared.so", false)}, generateAab); + return expectedFile; + }; + + QTest::newRow("multiple apks") + << "multiple-apks-per-project" + << (QStringList() << "twolibs1" << "twolibs2") + << QList{expectedFiles1(generateAab), expectedFiles2(generateAab)} + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + enableAapt2 = true; + QTest::newRow("multiple apks aapt2") + << "multiple-apks-per-project" + << (QStringList() << "twolibs1" << "twolibs2") + << (QList() << expectedFiles1(generateAab) + << (QByteArrayList() << expectedFiles2(generateAab) << "resources.arsc")) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; + generateAab = true; + QTest::newRow("multiple apks aab") + << "multiple-apks-per-project" + << (QStringList() << "twolibs1" << "twolibs2") + << (QList() << expectedFiles1(generateAab) + << (QByteArrayList() << expectedFiles2(generateAab) << "base/resources.pb" + << "base/native.pb")) + << QStringList{aaptVersion(enableAapt2), packageType(generateAab)} + << enableAapt2 << generateAab << isIncrementalBuild; +} + +QTEST_MAIN(TestBlackboxAndroid) diff --git a/tests/auto/blackbox/tst_blackboxandroid.h b/tests/auto/blackbox/tst_blackboxandroid.h new file mode 100644 index 00000000..a690eaa8 --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxandroid.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef TST_BLACKBOX_H +#define TST_BLACKBOX_H + +#include "tst_blackboxbase.h" + +class TestBlackboxAndroid : public TestBlackboxBase +{ + Q_OBJECT + +public: + TestBlackboxAndroid(); + +private slots: + void android(); + void android_data(); + +private: + QMap 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 new file mode 100644 index 00000000..87bea9ae --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxapple.cpp @@ -0,0 +1,975 @@ +/**************************************************************************** +** +** 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 "tst_blackboxapple.h" + +#include "../shared.h" +#include +#include +#include + +#include +#include +#include + +#include + +#define WAIT_FOR_NEW_TIMESTAMP() waitForNewTimestamp(testDataDir) + +using qbs::Internal::HostOsInfo; +using qbs::Profile; + +class QFileInfo2 : public QFileInfo { +public: + QFileInfo2(const QString &path) : QFileInfo(path) { } + bool isRegularFile() const { return isFile() && !isSymLink(); } + bool isRegularDir() const { return isDir() && !isSymLink(); } + bool isFileSymLink() const { return isFile() && isSymLink(); } + bool isDirSymLink() const { return isDir() && isSymLink(); } +}; + +static QString getEmbeddedBinaryPlist(const QString &file) +{ + QProcess p; + p.start("otool", QStringList() << "-v" << "-X" << "-s" << "__TEXT" << "__info_plist" << file); + p.waitForFinished(); + return QString::fromUtf8(p.readAllStandardOutput()).trimmed(); +} + +static QVariantMap readInfoPlistFile(const QString &infoPlistPath) +{ + if (!QFile::exists(infoPlistPath)) { + qWarning() << infoPlistPath << "doesn't exist"; + return {}; + } + + QProcess plutil; + plutil.start("plutil", { + QStringLiteral("-convert"), + QStringLiteral("json"), + infoPlistPath + }); + if (!plutil.waitForStarted()) { + qWarning() << plutil.errorString(); + return {}; + } + if (!plutil.waitForFinished()) { + qWarning() << plutil.errorString(); + return {}; + } + if (plutil.exitCode() != 0) { + qWarning() << plutil.readAllStandardError().constData(); + return {}; + } + + QFile infoPlist(infoPlistPath); + if (!infoPlist.open(QIODevice::ReadOnly)) { + qWarning() << infoPlist.errorString(); + return {}; + } + QJsonParseError error; + const auto json = QJsonDocument::fromJson(infoPlist.readAll(), &error); + if (error.error != QJsonParseError::NoError) { + qWarning() << error.errorString(); + return {}; + } + return json.object().toVariantMap(); +} + +static QString getInfoPlistPath(const QString &bundlePath) +{ + QFileInfo contents(bundlePath + "/Contents"); + if (contents.exists() && contents.isDir()) + return contents.filePath() + "/Info.plist"; // macOS bundle + return bundlePath + "/Info.plist"; +} + +static bool testVariantListType(const QVariant &variant, QMetaType::Type type) +{ + if (variant.userType() != QMetaType::QVariantList) + return false; + for (const auto &value : variant.toList()) { + if (value.userType() != type) + return false; + } + return true; +} + +TestBlackboxApple::TestBlackboxApple() + : TestBlackboxBase (SRCDIR "/testdata-apple", "blackbox-apple") +{ +} + +void TestBlackboxApple::initTestCase() +{ + if (!HostOsInfo::isMacosHost()) { + QSKIP("only applies on macOS"); + return; + } + + TestBlackboxBase::initTestCase(); +} + +void TestBlackboxApple::appleMultiConfig() +{ + const auto xcodeVersion = findXcodeVersion(); + QDir::setCurrent(testDataDir + "/apple-multiconfig"); + QCOMPARE(runQbs(QbsRunParameters(QStringList{ + "qbs.installPrefix:''", + QStringLiteral("project.xcodeVersion:") + xcodeVersion.toString()})), 0); + + if (m_qbsStdout.contains("isShallow: false")) { + QVERIFY(QFileInfo2(defaultInstallRoot + "/singleapp.app/Contents/MacOS/singleapp").isExecutable()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singleapp.app/Contents/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singleapp.app/Contents/PkgInfo").isRegularFile()); + + QVERIFY(QFileInfo2(defaultInstallRoot + "/singleapp_agg.app/Contents/MacOS/singleapp_agg").isExecutable()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singleapp_agg.app/Contents/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singleapp_agg.app/Contents/PkgInfo").isRegularFile()); + + QVERIFY(QFileInfo2(defaultInstallRoot + "/singlelib.framework/singlelib").isFileSymLink()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singlelib.framework/Resources").isDirSymLink()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singlelib.framework/Versions").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singlelib.framework/Versions/A").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singlelib.framework/Versions/A/singlelib").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singlelib.framework/Versions/A/Resources").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singlelib.framework/Versions/A/Resources/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singlelib.framework/Versions/Current").isDirSymLink()); + + QVERIFY(QFileInfo2(defaultInstallRoot + "/multiapp.app/Contents/MacOS/multiapp").isExecutable()); + 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").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()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/Versions/A").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/Versions/A/multilib").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/Versions/A/multilib_debug").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/Versions/A/multilib_profile").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/Versions/A/Resources").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/Versions/A/Resources/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/Versions/Current").isDirSymLink()); + + for (const QString &variant : { "release", "debug", "profile" }) { + for (const QString &arch : { "x86_64" }) { + QProcess process; + process.setProgram("/usr/bin/arch"); + process.setArguments({ + "-arch", arch, + "-e", "DYLD_IMAGE_SUFFIX=_" + variant, + defaultInstallRoot + "/multiapp.app/Contents/MacOS/multiapp" + }); + process.start(); + process.waitForFinished(); + QCOMPARE(process.exitCode(), 0); + const auto processStdout = process.readAllStandardOutput(); + QVERIFY2(processStdout.contains("Hello from " + variant.toUtf8() + " " + arch.toUtf8()), + processStdout.constData()); + } + } + } else if (m_qbsStdout.contains("isShallow: true")) { + QVERIFY(QFileInfo2(defaultInstallRoot + "/singleapp.app/singleapp").isExecutable()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singleapp.app/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singleapp.app/PkgInfo").isRegularFile()); + + QVERIFY(QFileInfo2(defaultInstallRoot + "/singleapp_agg.app/singleapp_agg").isExecutable()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singleapp_agg.app/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singleapp_agg.app/PkgInfo").isRegularFile()); + + QVERIFY(QFileInfo2(defaultInstallRoot + "/singlelib.framework/singlelib").isExecutable()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/singlelib.framework/Info.plist").isRegularFile()); + + QVERIFY(QFileInfo2(defaultInstallRoot + "/multiapp.app/multiapp").isExecutable()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/multiapp.app/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/multiapp.app/PkgInfo").isRegularFile()); + + QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiapp.app/fatmultiapp").isExecutable()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiapp.app/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiapp.app/PkgInfo").isRegularFile()); + + QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiappmultivariant.app/" + "fatmultiappmultivariant").isFileSymLink()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiappmultivariant.app/" + "fatmultiappmultivariant_debug").isExecutable()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiappmultivariant.app/" + "fatmultiappmultivariant_profile").isExecutable()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiappmultivariant.app/Info.plist") + .isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/fatmultiappmultivariant.app/PkgInfo") + .isRegularFile()); + + QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/multilib").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/multilib_debug").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/multilib_profile").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/multilib.framework/Info.plist").isRegularFile()); + } else { + QVERIFY2(false, qPrintable(m_qbsStdout)); + } +} + +void TestBlackboxApple::aggregateDependencyLinking() +{ + // XCode 11 produces warning about deprecation of 32-bit apps, so skip the test + // for future XCode versions as well + const auto xcodeVersion = findXcodeVersion(); + if (xcodeVersion >= qbs::Version(11)) + QSKIP("32-bit arch build is no longer supported on macOS higher than 10.13.4."); + + QDir::setCurrent(testDataDir + "/aggregateDependencyLinking"); + QCOMPARE(runQbs(QStringList{"-p", "multi_arch_lib"}), 0); + + QCOMPARE(runQbs(QStringList{"-p", "just_app", "--command-echo-mode", "command-line"}), 0); + int linkedInLibrariesCount = + QString::fromUtf8(m_qbsStdout).count(QStringLiteral("multi_arch_lib.a")); + QCOMPARE(linkedInLibrariesCount, 1); +} + +void TestBlackboxApple::assetCatalog() +{ + QFETCH(bool, flatten); + + const auto xcodeVersion = findXcodeVersion(); + QDir::setCurrent(testDataDir + QLatin1String("/ib/assetcatalog")); + + rmDirR(relativeBuildDir()); + + QbsRunParameters params; + const auto v = HostOsInfo::hostOsVersion(); + const QString flattens = "modules.ib.flatten:" + QString(flatten ? "true" : "false"); + const QString macosTarget = "modules.cpp.minimumMacosVersion:'" + v.toString() + "'"; + + // Make sure a dry run does not write anything + params.arguments = QStringList() << "-f" << "assetcatalogempty.qbs" << "--dry-run" + << flattens << macosTarget; + QCOMPARE(runQbs(params), 0); + QVERIFY(!directoryExists(relativeBuildDir())); + + if (m_qbsStdout.contains("Skip this test")) + QSKIP("Skip this test"); + + params.arguments = QStringList() << "-f" << "assetcatalogempty.qbs" + << flattens << macosTarget; + QCOMPARE(runQbs(params), 0); + + // empty asset catalogs must still produce output + if (xcodeVersion >= qbs::Version(5)) + QVERIFY((bool)m_qbsStdout.contains("compiling empty.xcassets")); + + // should additionally produce raw assets since deployment target will be < 10.9 + // older versions of ibtool generated either raw assets OR .car files; + // newer versions always generate the .car file regardless of the deployment target + if (v < qbs::Version(10, 9)) { + QVERIFY(regularFileExists(relativeProductBuildDir("assetcatalogempty") + + "/assetcatalogempty.app/Contents/Resources/other.png")); + QVERIFY(regularFileExists(relativeProductBuildDir("assetcatalogempty") + + "/assetcatalogempty.app/Contents/Resources/other@2x.png")); + } + + rmDirR(relativeBuildDir()); + params.arguments.push_back("modules.cpp.minimumMacosVersion:'10.10'"); // force CAR generation + QCOMPARE(runQbs(params), 0); + + // empty asset catalogs must still produce output + if (xcodeVersion >= qbs::Version(5)) { + QVERIFY((bool)m_qbsStdout.contains("compiling empty.xcassets")); + // No matter what, we need a 10.9 host to build CAR files + if (HostOsInfo::hostOsVersion() >= qbs::Version(10, 9)) { + QVERIFY(regularFileExists(relativeProductBuildDir("assetcatalogempty") + + "/assetcatalogempty.app/Contents/Resources/Assets.car")); + } else { + QVERIFY(regularFileExists(relativeProductBuildDir("assetcatalogempty") + + "/assetcatalogempty.app/Contents/Resources/empty.icns")); + QVERIFY(regularFileExists(relativeProductBuildDir("assetcatalogempty") + + "/assetcatalogempty.app/Contents/Resources/other.png")); + QVERIFY(regularFileExists(relativeProductBuildDir("assetcatalogempty") + + "/assetcatalogempty.app/Contents/Resources/other@2x.png")); + } + } + + // this asset catalog happens to have an embedded icon set, + // but this should NOT be built since it is not in the files list + QVERIFY(!(bool)m_qbsStdout.contains(".iconset")); + + // now we'll add the iconset + rmDirR(relativeBuildDir()); + params.arguments.push_back("project.includeIconset:true"); + QCOMPARE(runQbs(params), 0); + QVERIFY(!(bool)m_qbsStdout.contains("compiling empty.xcassets")); + QVERIFY((bool)m_qbsStdout.contains("compiling empty.iconset")); + + // make sure the nibs/storyboards are in there + QString nib = relativeProductBuildDir("assetcatalogempty") + "/assetcatalogempty.app/Contents/Resources/MainMenu.nib"; + QStringList nibFiles; + if (flatten) { + QVERIFY(regularFileExists(nib)); + } else { + QVERIFY(directoryExists(nib)); + nibFiles = QStringList() << "designable.nib" << "keyedobjects.nib"; + } + + QString storyboardc = relativeProductBuildDir("assetcatalogempty") + "/assetcatalogempty.app/Contents/Resources/Storyboard.storyboardc"; + QStringList storyboardcFiles; + if (HostOsInfo::hostOsVersion() >= qbs::Version(10, 10)) { + QVERIFY(directoryExists(storyboardc)); + + storyboardcFiles = QStringList() + << "1os-k8-h10-view-qKA-a5-eUe.nib" + << "Info.plist" + << "Iqk-Fi-Vhk-view-HRv-3O-Qxh.nib" + << "Main.nib" + << "NSViewController-Iqk-Fi-Vhk.nib" + << "NSViewController-Yem-rc-72E.nib" + << "Yem-rc-72E-view-ODp-aO-Dmf.nib"; + + if (!flatten) { + storyboardcFiles << "designable.storyboard"; + storyboardcFiles.sort(); + } + } + + QCOMPARE(QDir(nib).entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name), nibFiles); + QCOMPARE(QDir(storyboardc).entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::Name), storyboardcFiles); + 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()); +} + +void TestBlackboxApple::assetCatalog_data() +{ + QTest::addColumn("flatten"); + QTest::newRow("flattened") << true; + QTest::newRow("unflattened") << false; +} + +void TestBlackboxApple::assetCatalogsEmpty() { + if (findXcodeVersion() < qbs::Version(5)) + QSKIP("requires Xcode 5 or above"); + QDir::setCurrent(testDataDir + QLatin1String("/ib/empty-asset-catalogs")); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("compiling assetcatalog1.xcassets"), m_qbsStdout); + QVERIFY2(!m_qbsStdout.contains("compiling assetcatalog2.xcassets"), m_qbsStdout); +} + +void TestBlackboxApple::assetCatalogsMultiple() { + if (findXcodeVersion() < qbs::Version(5)) + QSKIP("requires Xcode 5 or above"); + QDir::setCurrent(testDataDir + QLatin1String("/ib/multiple-asset-catalogs")); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling assetcatalog1.xcassets"), m_qbsStdout); + QVERIFY2(m_qbsStdout.contains("compiling assetcatalog2.xcassets"), m_qbsStdout); +} + +void TestBlackboxApple::bundleStructure() +{ + QFETCH(QString, productName); + QFETCH(QString, productTypeIdentifier); + + QDir::setCurrent(testDataDir + "/bundle-structure"); + QbsRunParameters params(QStringList{"qbs.installPrefix:''"}); + params.arguments << "project.buildableProducts:" + productName; + + if (productName == "ABadApple" || productName == "ABadThirdParty") + params.expectFailure = true; + + rmDirR(relativeBuildDir()); + const int status = runQbs(params); + if (status != 0) { + QVERIFY2(m_qbsStderr.contains("Bundle product type " + + productTypeIdentifier.toLatin1() + + " is not supported."), + m_qbsStderr.constData()); + return; + } + + QCOMPARE(status, 0); + + if (m_qbsStdout.contains("bundle.isShallow: false")) { + // Test shallow bundles detection - bundles are not shallow only on macOS, so also check + // the qbs.targetOS property + QVERIFY2(m_qbsStdout.contains("qbs.targetOS: macos"), m_qbsStdout); + if (productName == "A") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/A.app").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/A.app/Contents").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/A.app/Contents/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/A.app/Contents/MacOS").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/A.app/Contents/MacOS/A").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/A.app/Contents/PkgInfo").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/A.app/Contents/Resources").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/A.app/Contents/Resources/resource.txt").isRegularFile()); + } + + if (productName == "B") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/B").isFileSymLink()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Headers").isDirSymLink()); + //QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Modules").isDirSymLink()); + QVERIFY(!QFileInfo2(defaultInstallRoot + "/B.framework/PkgInfo").exists()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/PrivateHeaders").isDirSymLink()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Resources").isDirSymLink()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Versions").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Versions/A").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Versions/A/B").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Versions/A/Headers").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Versions/A/Headers/dummy.h").isRegularFile()); + //QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Versions/A/Modules").isRegularDir()); + //QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Versions/A/Modules/module.modulemap").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Versions/A/PrivateHeaders").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Versions/A/PrivateHeaders/dummy_p.h").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Versions/A/Resources").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Versions/A/Resources/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Versions/Current").isDirSymLink()); + } + + if (productName == "C") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/C").isFileSymLink()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Headers").isDirSymLink()); + //QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Modules").isDirSymLink()); + QVERIFY(!QFileInfo2(defaultInstallRoot + "/C.framework/PkgInfo").exists()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/PrivateHeaders").isDirSymLink()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Resources").isDirSymLink()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Versions").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Versions/A").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Versions/A/C").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Versions/A/Headers").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Versions/A/Headers/dummy.h").isRegularFile()); + //QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Versions/A/Modules").isRegularDir()); + //QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Versions/A/Modules/module.modulemap").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Versions/A/PrivateHeaders").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Versions/A/PrivateHeaders/dummy_p.h").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Versions/A/Resources").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Versions/A/Resources/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Versions/Current").isDirSymLink()); + } + + if (productName == "D") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle/Contents").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle/Contents/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle/Contents/MacOS").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle/Contents/MacOS/D").isRegularFile()); + QVERIFY(!QFileInfo2(defaultInstallRoot + "/D.bundle/Contents/PkgInfo").exists()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle/Contents/Resources").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle/Contents/Resources/resource.txt").isRegularFile()); + } + + if (productName == "E") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex/Contents").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex/Contents/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex/Contents/MacOS").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex/Contents/MacOS/E").isRegularFile()); + QVERIFY(!QFileInfo2(defaultInstallRoot + "/E.appex/Contents/PkgInfo").exists()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex/Contents/Resources").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex/Contents/Resources/resource.txt").isRegularFile()); + } + + if (productName == "F") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc/Contents").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc/Contents/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc/Contents/MacOS").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc/Contents/MacOS/F").isRegularFile()); + QVERIFY(!QFileInfo2(defaultInstallRoot + "/F.xpc/Contents/PkgInfo").exists()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc/Contents/Resources").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc/Contents/Resources/resource.txt").isRegularFile()); + } + + if (productName == "G") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/G").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/G/ContentInfo.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/G/Contents/resource.txt").isRegularFile()); + } + } else if (m_qbsStdout.contains("bundle.isShallow: true")) { + QVERIFY2(m_qbsStdout.contains("qbs.targetOS:"), m_qbsStdout); + QVERIFY2(!m_qbsStdout.contains("qbs.targetOS: macos"), m_qbsStdout); + if (productName == "A") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/A.app").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/A.app/A").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/A.app/Info.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/A.app/PkgInfo").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/A.app/resource.txt").isRegularFile()); + } + + if (productName == "B") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/B").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Headers").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Headers/dummy.h").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Info.plist").isRegularFile()); + //QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Modules").isRegularDir()); + //QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/Modules/module.modulemap").isRegularFile()); + QVERIFY(!QFileInfo2(defaultInstallRoot + "/B.framework/PkgInfo").exists()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/PrivateHeaders").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/PrivateHeaders/dummy_p.h").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/B.framework/resource.txt").isRegularFile()); + } + + if (productName == "C") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/C").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Headers").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Headers/dummy.h").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Info.plist").isRegularFile()); + //QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Modules").isRegularDir()); + //QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/Modules/module.modulemap").isRegularFile()); + QVERIFY(!QFileInfo2(defaultInstallRoot + "/C.framework/PkgInfo").exists()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/PrivateHeaders").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/PrivateHeaders/dummy_p.h").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/C.framework/resource.txt").isRegularFile()); + } + + if (productName == "D") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle/D").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle/Headers").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle/Headers/dummy.h").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle/Info.plist").isRegularFile()); + QVERIFY(!QFileInfo2(defaultInstallRoot + "/D.bundle/PkgInfo").exists()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle/PrivateHeaders").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle/PrivateHeaders/dummy_p.h").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/D.bundle/resource.txt").isRegularFile()); + } + + if (productName == "E") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex/E").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex/Headers").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex/Headers/dummy.h").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex/Info.plist").isRegularFile()); + QVERIFY(!QFileInfo2(defaultInstallRoot + "/E.appex/PkgInfo").exists()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex/PrivateHeaders").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex/PrivateHeaders/dummy_p.h").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/E.appex/resource.txt").isRegularFile()); + } + + if (productName == "F") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc/F").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc/Headers").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc/Headers/dummy.h").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc/Info.plist").isRegularFile()); + QVERIFY(!QFileInfo2(defaultInstallRoot + "/F.xpc/PkgInfo").exists()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc/PrivateHeaders").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc/PrivateHeaders/dummy_p.h").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/F.xpc/resource.txt").isRegularFile()); + } + + if (productName == "G") { + QVERIFY(QFileInfo2(defaultInstallRoot + "/G").isRegularDir()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/G/ContentInfo.plist").isRegularFile()); + QVERIFY(QFileInfo2(defaultInstallRoot + "/G/Contents/resource.txt").isRegularFile()); + } + } else { + QVERIFY2(false, qPrintable(m_qbsStdout)); + } +} + +void TestBlackboxApple::bundleStructure_data() +{ + QTest::addColumn("productName"); + QTest::addColumn("productTypeIdentifier"); + QTest::addColumn("isShallow"); + + QTest::newRow("A") << "A" << "com.apple.product-type.application"; + QTest::newRow("ABadApple") << "ABadApple" << "com.apple.product-type.will.never.exist.ever.guaranteed"; + QTest::newRow("ABadThirdParty") << "ABadThirdParty" << "org.special.third.party.non.existent.product.type"; + QTest::newRow("B") << "B" << "com.apple.product-type.framework"; + QTest::newRow("C") << "C" << "com.apple.product-type.framework.static"; + QTest::newRow("D") << "D" << "com.apple.product-type.bundle"; + QTest::newRow("E") << "E" << "com.apple.product-type.app-extension"; + QTest::newRow("F") << "F" << "com.apple.product-type.xpc-service"; + QTest::newRow("G") << "G" << "com.apple.product-type.in-app-purchase-content"; +} + +void TestBlackboxApple::deploymentTarget() +{ + QFETCH(QString, sdk); + QFETCH(QString, os); + QFETCH(QString, arch); + QFETCH(QString, cflags); + QFETCH(QString, lflags); + + QDir::setCurrent(testDataDir + "/deploymentTarget"); + + QbsRunParameters params; + params.arguments = QStringList() + << "--command-echo-mode" + << "command-line" + << "modules.qbs.targetPlatform:" + os + << "qbs.architectures:" + arch; + + rmDirR(relativeBuildDir()); + int status = runQbs(params); + + const QStringList skippableMessages = QStringList() + << "There is no matching SDK available for " + sdk + "." + << "x86_64h will be mis-detected as x86_64 with Apple Clang < 6.0" + << "clang: error: unknown argument: '-mtvos-version-min" + << "clang: error: unknown argument: '-mtvos-simulator-version-min" + << "clang: error: unknown argument: '-mwatchos-version-min" + << "clang: error: unknown argument: '-mwatchos-simulator-version-min"; + if (status != 0) { + for (const auto &message : skippableMessages) { + if (m_qbsStderr.contains(message.toUtf8())) + QSKIP(message.toUtf8()); + } + } + + QCOMPARE(status, 0); + QVERIFY2(m_qbsStderr.contains(cflags.toLatin1()), m_qbsStderr.constData()); + QVERIFY2(m_qbsStderr.contains(lflags.toLatin1()), m_qbsStderr.constData()); +} + +void TestBlackboxApple::deploymentTarget_data() +{ + static const QString macos = QStringLiteral("macos"); + static const QString ios = QStringLiteral("ios"); + static const QString ios_sim = QStringLiteral("ios-simulator"); + static const QString tvos = QStringLiteral("tvos"); + static const QString tvos_sim = QStringLiteral("tvos-simulator"); + static const QString watchos = QStringLiteral("watchos"); + static const QString watchos_sim = QStringLiteral("watchos-simulator"); + + QTest::addColumn("sdk"); + QTest::addColumn("os"); + QTest::addColumn("arch"); + QTest::addColumn("cflags"); + QTest::addColumn("lflags"); + + const auto xcodeVersion = findXcodeVersion(); + if (xcodeVersion < qbs::Version(10)) { + QTest::newRow("macos x86") << "macosx" << macos << "x86" + << "-triple i386-apple-macosx10.6" + << "-macosx_version_min 10.6"; + } + QTest::newRow("macos x86_64") << "macosx" << macos << "x86_64" + << "-triple x86_64-apple-macosx10.6" + << "10.6"; + + if (xcodeVersion >= qbs::Version(6)) + QTest::newRow("macos x86_64h") << "macosx" << macos << "x86_64h" + << "-triple x86_64h-apple-macosx10.12" + << "10.12"; + + QTest::newRow("ios armv7a") << "iphoneos" << ios << "armv7a" + << "-triple thumbv7-apple-ios6.0" + << "6.0"; + QTest::newRow("ios armv7s") << "iphoneos" <= qbs::Version(5)) + QTest::newRow("ios arm64") << "iphoneos" <= qbs::Version(5)) + QTest::newRow("ios-simulator x86_64") << "iphonesimulator" << ios_sim << "x86_64" + << "-triple x86_64-apple-ios7.0" + << "7.0"; + + if (xcodeVersion >= qbs::Version(7)) { + if (xcodeVersion >= qbs::Version(7, 1)) { + QTest::newRow("tvos arm64") << "appletvos" << tvos << "arm64" + << "-triple arm64-apple-tvos9.0" + << "9.0"; + QTest::newRow("tvos-simulator x86_64") << "appletvsimulator" << tvos_sim << "x86_64" + << "-triple x86_64-apple-tvos9.0" + << "9.0"; + } + + QTest::newRow("watchos armv7k") << "watchos" << watchos << "armv7k" + << "-triple thumbv7k-apple-watchos2.0" + << "2.0"; + QTest::newRow("watchos-simulator x86") << "watchsimulator" << watchos_sim << "x86" + << "-triple i386-apple-watchos2.0" + << "2.0"; + } +} + +void TestBlackboxApple::dmg() +{ + QDir::setCurrent(testDataDir + "/apple-dmg"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxApple::embedInfoPlist() +{ + QDir::setCurrent(testDataDir + QLatin1String("/embedInfoPlist")); + + QbsRunParameters params(QStringList{"qbs.installPrefix:''"}); + QCOMPARE(runQbs(params), 0); + + QVERIFY(!getEmbeddedBinaryPlist(defaultInstallRoot + "/app").isEmpty()); + QVERIFY(!getEmbeddedBinaryPlist(defaultInstallRoot + "/liblib.dylib").isEmpty()); + QVERIFY(!getEmbeddedBinaryPlist(defaultInstallRoot + "/mod.bundle").isEmpty()); + + params.arguments = QStringList(QLatin1String("modules.bundle.embedInfoPlist:false")); + params.expectFailure = true; + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(params), 0); + + QVERIFY(getEmbeddedBinaryPlist(defaultInstallRoot + "/app").isEmpty()); + QVERIFY(getEmbeddedBinaryPlist(defaultInstallRoot + "/liblib.dylib").isEmpty()); + QVERIFY(getEmbeddedBinaryPlist(defaultInstallRoot + "/mod.bundle").isEmpty()); +} + +void TestBlackboxApple::frameworkStructure() +{ + QDir::setCurrent(testDataDir + QLatin1String("/frameworkStructure")); + + QbsRunParameters params; + QCOMPARE(runQbs(params), 0); + + if (m_qbsStdout.contains("isShallow: false")) { + QVERIFY(regularFileExists(relativeProductBuildDir("Widget") + "/Widget.framework/Versions/A/Widget")); + QVERIFY(regularFileExists(relativeProductBuildDir("Widget") + "/Widget.framework/Versions/A/Headers/Widget.h")); + QVERIFY(regularFileExists(relativeProductBuildDir("Widget") + "/Widget.framework/Versions/A/PrivateHeaders/WidgetPrivate.h")); + QVERIFY(regularFileExists(relativeProductBuildDir("Widget") + "/Widget.framework/Versions/A/Resources/BaseResource")); + QVERIFY(regularFileExists(relativeProductBuildDir("Widget") + "/Widget.framework/Versions/A/Resources/en.lproj/EnglishResource")); + QVERIFY(directoryExists(relativeProductBuildDir("Widget") + "/Widget.framework/Versions/Current")); + QVERIFY(regularFileExists(relativeProductBuildDir("Widget") + "/Widget.framework/Widget")); + QVERIFY(directoryExists(relativeProductBuildDir("Widget") + "/Widget.framework/Headers")); + QVERIFY(directoryExists(relativeProductBuildDir("Widget") + "/Widget.framework/PrivateHeaders")); + QVERIFY(directoryExists(relativeProductBuildDir("Widget") + "/Widget.framework/Resources")); + } else if (m_qbsStdout.contains("isShallow: true")) { + QVERIFY(directoryExists(relativeProductBuildDir("Widget") + "/Widget.framework/Headers")); + QVERIFY(directoryExists(relativeProductBuildDir("Widget") + "/Widget.framework/PrivateHeaders")); + QVERIFY(regularFileExists(relativeProductBuildDir("Widget") + "/Widget.framework/Widget")); + QVERIFY(regularFileExists(relativeProductBuildDir("Widget") + "/Widget.framework/Headers/Widget.h")); + QVERIFY(regularFileExists(relativeProductBuildDir("Widget") + "/Widget.framework/PrivateHeaders/WidgetPrivate.h")); + QVERIFY(regularFileExists(relativeProductBuildDir("Widget") + "/Widget.framework/BaseResource")); + QVERIFY(regularFileExists(relativeProductBuildDir("Widget") + "/Widget.framework/en.lproj/EnglishResource")); + QVERIFY(regularFileExists(relativeProductBuildDir("Widget") + "/Widget.framework/Widget")); + } else { + QVERIFY2(false, qPrintable(m_qbsStdout)); + } + + params.command = "resolve"; + params.arguments = QStringList() << "project.includeHeaders:false"; + QCOMPARE(runQbs(params), 0); + QCOMPARE(runQbs(), 0); + + QVERIFY(!directoryExists(relativeProductBuildDir("Widget") + "/Widget.framework/Headers")); + QVERIFY(!directoryExists(relativeProductBuildDir("Widget") + "/Widget.framework/PrivateHeaders")); +} + +void TestBlackboxApple::iconset() +{ + QDir::setCurrent(testDataDir + QLatin1String("/ib/iconset")); + + QbsRunParameters params; + params.arguments = QStringList() << "-f" << "iconset.qbs"; + QCOMPARE(runQbs(params), 0); + + QVERIFY(regularFileExists(relativeProductBuildDir("iconset") + "/white.icns")); +} + +void TestBlackboxApple::iconsetApp() +{ + QDir::setCurrent(testDataDir + QLatin1String("/ib/iconsetapp")); + + QbsRunParameters params; + params.arguments = QStringList() << "-f" << "iconsetapp.qbs"; + QCOMPARE(runQbs(params), 0); + + if (m_qbsStdout.contains("isShallow: false")) { + QVERIFY(regularFileExists(relativeProductBuildDir("iconsetapp") + + "/iconsetapp.app/Contents/Resources/white.icns")); + } else if (m_qbsStdout.contains("isShallow: true")) { + QVERIFY(regularFileExists(relativeProductBuildDir("iconsetapp") + + "/iconsetapp.app/white.icns")); + } else { + QVERIFY2(false, qPrintable(m_qbsStdout)); + } +} + +void TestBlackboxApple::infoPlist() +{ + QDir::setCurrent(testDataDir + "/infoplist"); + + QbsRunParameters params; + params.arguments = QStringList() << "-f" << "infoplist.qbs"; + QCOMPARE(runQbs(params), 0); + + const auto infoPlistPath = getInfoPlistPath( + relativeProductBuildDir("infoplist") + "/infoplist.app"); + QVERIFY(QFile::exists(infoPlistPath)); + const auto content = readInfoPlistFile(infoPlistPath); + QVERIFY(!content.isEmpty()); + + // common values + QCOMPARE(content.value(QStringLiteral("CFBundleIdentifier")), + QStringLiteral("org.example.infoplist")); + QCOMPARE(content.value(QStringLiteral("CFBundleName")), QStringLiteral("infoplist")); + QCOMPARE(content.value(QStringLiteral("CFBundleExecutable")), + QStringLiteral("infoplist")); + + if (!content.contains(QStringLiteral("SDKROOT"))) { // macOS-specific values + QCOMPARE(content.value("LSMinimumSystemVersion"), QStringLiteral("10.7")); + QCOMPARE(content.value("NSPrincipalClass"), QStringLiteral("NSApplication")); + } else { + // QBS-1447: UIDeviceFamily was set to a string instead of an array + const auto family = content.value(QStringLiteral("UIDeviceFamily")); + if (family.isValid()) { + // int gets converted to a double when exporting plist as JSON + QVERIFY(testVariantListType(family, QMetaType::Double)); + } + const auto caps = content.value(QStringLiteral("UIRequiredDeviceCapabilities")); + if (caps.isValid()) + QVERIFY(testVariantListType(caps, QMetaType::QString)); + const auto orientations = content.value(QStringLiteral("UIRequiredDeviceCapabilities")); + if (orientations.isValid()) + QVERIFY(testVariantListType(orientations, QMetaType::QString)); + } +} + +void TestBlackboxApple::objcArc() +{ + QDir::setCurrent(testDataDir + QLatin1String("/objc-arc")); + + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxApple::overrideInfoPlist() +{ + QDir::setCurrent(testDataDir + "/overrideInfoPlist"); + + QCOMPARE(runQbs(), 0); + + const auto infoPlistPath = getInfoPlistPath( + relativeProductBuildDir("overrideInfoPlist") + "/overrideInfoPlist.app"); + QVERIFY(QFile::exists(infoPlistPath)); + const auto content = readInfoPlistFile(infoPlistPath); + QVERIFY(!content.isEmpty()); + + // test we do not override custom values by default + QCOMPARE(content.value(QStringLiteral("DefaultValue")), + QStringLiteral("The default value")); + // test we can override custom values + QCOMPARE(content.value(QStringLiteral("OverriddenValue")), + QStringLiteral("The overridden value")); + // test we do not override special values set by Qbs by default + QCOMPARE(content.value(QStringLiteral("CFBundleExecutable")), + QStringLiteral("overrideInfoPlist")); + // test we can override special values set by Qbs + QCOMPARE(content.value(QStringLiteral("CFBundleName")), QStringLiteral("My Bundle")); +} + +void TestBlackboxApple::xcode() +{ + QProcess xcodeSelect; + xcodeSelect.start("xcode-select", QStringList() << "--print-path"); + QVERIFY2(xcodeSelect.waitForStarted(), qPrintable(xcodeSelect.errorString())); + QVERIFY2(xcodeSelect.waitForFinished(), qPrintable(xcodeSelect.errorString())); + QVERIFY2(xcodeSelect.exitCode() == 0, qPrintable(xcodeSelect.readAllStandardError().constData())); + const QString developerPath(QString::fromLocal8Bit(xcodeSelect.readAllStandardOutput().trimmed())); + + std::multimap sdks; + + QProcess xcodebuildShowSdks; + xcodebuildShowSdks.start("xcrun", QStringList() << "xcodebuild" << "-showsdks"); + QVERIFY2(xcodebuildShowSdks.waitForStarted(), qPrintable(xcodebuildShowSdks.errorString())); + QVERIFY2(xcodebuildShowSdks.waitForFinished(), qPrintable(xcodebuildShowSdks.errorString())); + QVERIFY2(xcodebuildShowSdks.exitCode() == 0, + qPrintable(xcodebuildShowSdks.readAllStandardError().constData())); + const auto lines = QString::fromLocal8Bit(xcodebuildShowSdks.readAllStandardOutput().trimmed()) + .split('\n', QBS_SKIP_EMPTY_PARTS); + for (const QString &line : lines) { + static const std::regex regexp("^.+\\s+\\-sdk\\s+([a-z]+)([0-9]+\\.[0-9]+)$"); + const auto ln = line.toStdString(); + std::smatch match; + if (std::regex_match(ln, match, regexp)) + sdks.insert({ match[1], match[2] }); + } + + const auto getSdksByType = [&sdks]() + { + QStringList result; + std::string sdkType; + QStringList sdkValues; + for (const auto &sdk: sdks) { + if (!sdkType.empty() && sdkType != sdk.first) { + result.append(QStringLiteral("%1:['%2']") + .arg(QString::fromStdString(sdkType), sdkValues.join("','"))); + sdkValues.clear(); + } + sdkType = sdk.first; + sdkValues.append(QString::fromStdString(sdk.second)); + } + return result; + }; + + QDir::setCurrent(testDataDir + "/xcode"); + QbsRunParameters params; + params.arguments = (QStringList() + << (QStringLiteral("modules.xcode.developerPath:") + developerPath) + << (QStringLiteral("project.sdks:{") + getSdksByType().join(",") + "}")); + QCOMPARE(runQbs(params), 0); +} + +QTEST_MAIN(TestBlackboxApple) + +QVariantMap TestBlackboxApple::findXcode(int *status) +{ + QTemporaryDir temp; + QbsRunParameters params = QStringList({"-f", testDataDir + "/find/find-xcode.qbs"}); + params.profile = "none"; + params.buildDirectory = temp.path(); + const int res = runQbs(params); + if (status) + *status = res; + QFile file(temp.path() + "/" + relativeProductBuildDir("find-xcode") + + "/xcode.json"); + if (!file.open(QIODevice::ReadOnly)) + return {}; + return QJsonDocument::fromJson(file.readAll()).toVariant().toMap(); +} + +qbs::Version TestBlackboxApple::findXcodeVersion() +{ + return qbs::Version::fromString(findXcode().value("version").toString()); +} diff --git a/tests/auto/blackbox/tst_blackboxapple.h b/tests/auto/blackbox/tst_blackboxapple.h new file mode 100644 index 00000000..be2e5c5b --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxapple.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef TST_BLACKBOXAPPLE_H +#define TST_BLACKBOXAPPLE_H + +#include "tst_blackboxbase.h" + +namespace qbs { +class Version; +} // namespace qbs + +class TestBlackboxApple : public TestBlackboxBase +{ + Q_OBJECT + +public: + TestBlackboxApple(); + +public slots: + void initTestCase() override; + +private slots: + void appleMultiConfig(); + void aggregateDependencyLinking(); + void assetCatalog(); + void assetCatalog_data(); + void assetCatalogsEmpty(); + void assetCatalogsMultiple(); + void bundleStructure(); + void bundleStructure_data(); + void deploymentTarget(); + void deploymentTarget_data(); + void dmg(); + void embedInfoPlist(); + void frameworkStructure(); + void iconset(); + void iconsetApp(); + void infoPlist(); + void objcArc(); + void overrideInfoPlist(); + void xcode(); + +private: + QVariantMap findXcode(int *status = nullptr); + qbs::Version findXcodeVersion(); +}; + +#endif // TST_BLACKBOXAPPLE_H diff --git a/tests/auto/blackbox/tst_blackboxbaremetal.cpp b/tests/auto/blackbox/tst_blackboxbaremetal.cpp new file mode 100644 index 00000000..41f50a0e --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxbaremetal.cpp @@ -0,0 +1,219 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** Contact: https://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. +** +****************************************************************************/ + +#include "tst_blackboxbaremetal.h" + +#include "../shared.h" + +#include + +static bool extractToolset(const QByteArray &output, + QByteArray &toolchain, QByteArray &architecture) +{ + const QRegularExpression re("%%([\\w\\-]+)%%, %%(\\w+)%%"); + QRegularExpressionMatchIterator it = re.globalMatch(output); + if (!it.hasNext()) + return false; + const QRegularExpressionMatch match = it.next(); + toolchain = match.captured(1).toLocal8Bit(); + architecture = match.captured(2).toLocal8Bit(); + return true; +} + +static QByteArray unsupportedToolsetMessage(const QByteArray &output) +{ + QByteArray toolchain; + QByteArray architecture; + extractToolset(output, toolchain, architecture); + return "Unsupported toolchain '" + toolchain + + "' for architecture '" + architecture + "'"; +} + +static QString linkerMapFileExtension(const QByteArray &toolchain, const QByteArray &architecture) +{ + if (toolchain == "keil") { + if (architecture == "mcs51") + return QStringLiteral(".m51"); + if (architecture == "c166") + return QStringLiteral(".m66"); + } + return QStringLiteral(".map"); +} + +TestBlackboxBareMetal::TestBlackboxBareMetal() + : TestBlackboxBase (SRCDIR "/testdata-baremetal", "blackbox-baremetal") +{ +} + +void TestBlackboxBareMetal::targetPlatform() +{ + QDir::setCurrent(testDataDir + "/target-platform"); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0); + if (m_qbsStdout.contains("unsupported toolset:")) + QSKIP(unsupportedToolsetMessage(m_qbsStdout)); + const bool hasNoPlatform = m_qbsStdout.contains("has no platform: true"); + QCOMPARE(hasNoPlatform, true); + const bool hasNoOS = m_qbsStdout.contains("has no os: true"); + QCOMPARE(hasNoOS, true); +} + +void TestBlackboxBareMetal::application_data() +{ + QTest::addColumn("testPath"); + QTest::newRow("one-object-application") << "/one-object-application"; + QTest::newRow("two-object-application") << "/two-object-application"; + QTest::newRow("one-object-asm-application") << "/one-object-asm-application"; +} + +void TestBlackboxBareMetal::application() +{ + QFETCH(QString, testPath); + QDir::setCurrent(testDataDir + testPath); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0); + if (m_qbsStdout.contains("unsupported toolset:")) + QSKIP(unsupportedToolsetMessage(m_qbsStdout)); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxBareMetal::staticLibraryDependencies() +{ + QDir::setCurrent(testDataDir + "/static-library-dependencies"); + QCOMPARE(runQbs(QStringList{"-p", "lib-a,lib-b,lib-c,lib-d,lib-e"}), 0); + QCOMPARE(runQbs(QStringList{"--command-echo-mode", "command-line"}), 0); + const QByteArray output = m_qbsStdout + '\n' + m_qbsStderr; + QVERIFY(output.contains("lib-a")); + QVERIFY(output.contains("lib-b")); + QVERIFY(output.contains("lib-c")); + QVERIFY(output.contains("lib-d")); + QVERIFY(output.contains("lib-e")); +} + +void TestBlackboxBareMetal::externalStaticLibraries() +{ + QDir::setCurrent(testDataDir + "/external-static-libraries"); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0); + if (m_qbsStdout.contains("unsupported toolset:")) + QSKIP(unsupportedToolsetMessage(m_qbsStdout)); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxBareMetal::userIncludePaths() +{ + QDir::setCurrent(testDataDir + "/user-include-paths"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxBareMetal::systemIncludePaths() +{ + QDir::setCurrent(testDataDir + "/system-include-paths"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxBareMetal::distributionIncludePaths() +{ + QDir::setCurrent(testDataDir + "/distribution-include-paths"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxBareMetal::preincludeHeaders() +{ + QDir::setCurrent(testDataDir + "/preinclude-headers"); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0); + if (m_qbsStdout.contains("unsupported toolset:")) + QSKIP(unsupportedToolsetMessage(m_qbsStdout)); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxBareMetal::defines() +{ + QDir::setCurrent(testDataDir + "/defines"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxBareMetal::compilerListingFiles_data() +{ + QTest::addColumn("testPath"); + QTest::addColumn("generateListing"); + QTest::newRow("do-not-generate-compiler-listing") << "/do-not-generate-compiler-listing" << false; + QTest::newRow("generate-compiler-listing") << "/generate-compiler-listing" << true; +} + +void TestBlackboxBareMetal::compilerListingFiles() +{ + QFETCH(QString, testPath); + QFETCH(bool, generateListing); + QDir::setCurrent(testDataDir + testPath); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0); + if (m_qbsStdout.contains("unsupported toolset:")) + QSKIP(unsupportedToolsetMessage(m_qbsStdout)); + QCOMPARE(runQbs(), 0); + const bool isShortListingNames = m_qbsStdout.contains("using short listing file names"); + const QString productName = testPath.mid(1); + const QString productBuildDir = relativeProductBuildDir(productName); + const QString hash = inputDirHash("."); + const QString mainListing = productBuildDir + "/" + hash + (isShortListingNames ? "/main.lst" : "/main.c.lst"); + QCOMPARE(regularFileExists(mainListing), generateListing); + const QString funListing = productBuildDir + "/" + hash + (isShortListingNames ? "/fun.lst" : "/fun.c.lst"); + QCOMPARE(regularFileExists(funListing), generateListing); +} + +void TestBlackboxBareMetal::linkerMapFile_data() +{ + QTest::addColumn("testPath"); + QTest::addColumn("generateMap"); + QTest::newRow("do-not-generate-linker-map") << "/do-not-generate-linker-map" << false; + QTest::newRow("generate-linker-map") << "/generate-linker-map" << true; +} + +void TestBlackboxBareMetal::linkerMapFile() +{ + QFETCH(QString, testPath); + QFETCH(bool, generateMap); + QDir::setCurrent(testDataDir + testPath); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("-n"))), 0); + if (m_qbsStdout.contains("unsupported toolset:")) + QSKIP(unsupportedToolsetMessage(m_qbsStdout)); + if (!m_qbsStdout.contains("current toolset:")) + QFAIL("No current toolset pattern exists"); + + QByteArray toolchain; + QByteArray architecture; + if (!extractToolset(m_qbsStdout, toolchain, architecture)) + QFAIL("Unable to extract current toolset"); + + QCOMPARE(runQbs(), 0); + const QString productName = testPath.mid(1); + const QString productBuildDir = relativeProductBuildDir(productName); + const auto extension = linkerMapFileExtension(toolchain, architecture); + const QString linkerMap = productBuildDir + "/" + productName + extension; + QCOMPARE(regularFileExists(linkerMap), generateMap); +} + +QTEST_MAIN(TestBlackboxBareMetal) diff --git a/tests/auto/blackbox/tst_blackboxbaremetal.h b/tests/auto/blackbox/tst_blackboxbaremetal.h new file mode 100644 index 00000000..3695cb1c --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxbaremetal.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Denis Shienkov +** Contact: https://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. +** +****************************************************************************/ + +#ifndef TST_BLACKBOXBAREMETAL_H +#define TST_BLACKBOXBAREMETAL_H + +#include "tst_blackboxbase.h" + +class TestBlackboxBareMetal : public TestBlackboxBase +{ + Q_OBJECT + +public: + TestBlackboxBareMetal(); + +private slots: + void targetPlatform(); + + void application_data(); + void application(); + + void staticLibraryDependencies(); + void externalStaticLibraries(); + + void userIncludePaths(); + void systemIncludePaths(); + void distributionIncludePaths(); + + void preincludeHeaders(); + + void defines(); + + void compilerListingFiles_data(); + void compilerListingFiles(); + + void linkerMapFile_data(); + void linkerMapFile(); + +private: + +}; + +#endif // TST_BLACKBOXBAREMETAL_H diff --git a/tests/auto/blackbox/tst_blackboxbase.cpp b/tests/auto/blackbox/tst_blackboxbase.cpp new file mode 100644 index 00000000..0e383c83 --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxbase.cpp @@ -0,0 +1,272 @@ +/**************************************************************************** +** +** 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 "tst_blackboxbase.h" + +#include "../shared.h" + +#include +#include +#include +#include + +#include +#include +#include + +using qbs::Internal::HostOsInfo; +using qbs::Internal::removeDirectoryWithContents; +using qbs::InstallOptions; +using qbs::Profile; + +static QString initQbsExecutableFilePath() +{ + const QString qbsInstallDir = QString::fromLocal8Bit(qgetenv("QBS_INSTALL_DIR")); + return HostOsInfo::appendExecutableSuffix(QDir::cleanPath(!qbsInstallDir.isEmpty() + ? qbsInstallDir + QLatin1String("/bin/qbs") + : QCoreApplication::applicationDirPath() + QLatin1String("/qbs"))); +} + +static bool supportsBuildDirectoryOption(const QString &command) { + return !(QStringList() << "help" << "config" << "config-ui" + << "setup-android" << "setup-qt" << "setup-toolchains" << "create-project") + .contains(command); +} + +static bool supportsSettingsDirOption(const QString &command) { + return !(QStringList() << "help" << "create-project").contains(command); +} + +TestBlackboxBase::TestBlackboxBase(const QString &testDataSrcDir, const QString &testName) + : testDataDir(testWorkDir(testName)), + testSourceDir(testDataSourceDir(testDataSrcDir)), + qbsExecutableFilePath(initQbsExecutableFilePath()), + defaultInstallRoot(relativeBuildDir() + QLatin1Char('/') + InstallOptions::defaultInstallRoot()) +{ + QLocale::setDefault(QLocale::c()); +} + +int TestBlackboxBase::runQbs(const QbsRunParameters ¶ms) +{ + QStringList args; + if (!params.command.isEmpty()) + args << params.command; + if (!params.settingsDir.isEmpty() && supportsSettingsDirOption(params.command)) + args << "--settings-dir" << params.settingsDir; + if (supportsBuildDirectoryOption(params.command)) { + args.push_back(QStringLiteral("-d")); + args.push_back(params.buildDirectory.isEmpty() + ? QStringLiteral(".") + : params.buildDirectory); + } + args << params.arguments; + const bool commandImpliesResolve = params.command.isEmpty() || params.command == "resolve" + || params.command == "build" || params.command == "install" || params.command == "run" + || params.command == "generate"; + if (!params.profile.isEmpty() && commandImpliesResolve) { + args.push_back(QLatin1String("profile:") + params.profile); + } + QProcess process; + if (!params.workingDir.isEmpty()) + process.setWorkingDirectory(params.workingDir); + process.setProcessEnvironment(params.environment); + process.start(qbsExecutableFilePath, args); + if (!process.waitForStarted() || !process.waitForFinished(testTimeoutInMsecs()) + || process.exitStatus() != QProcess::NormalExit) { + m_qbsStderr = process.readAllStandardError(); + if (!params.expectCrash) { + QTest::qFail("qbs did not run correctly", __FILE__, __LINE__); + qDebug("%s", qPrintable(process.errorString())); + } + return -1; + } + + m_qbsStderr = process.readAllStandardError(); + m_qbsStdout = process.readAllStandardOutput(); + sanitizeOutput(&m_qbsStderr); + sanitizeOutput(&m_qbsStdout); + const bool shouldLog = (process.exitStatus() != QProcess::NormalExit + || process.exitCode() != 0) && !params.expectFailure; + if (!m_qbsStderr.isEmpty() + && (shouldLog || qEnvironmentVariableIsSet("QBS_AUTOTEST_ALWAYS_LOG_STDERR"))) + qDebug("%s", m_qbsStderr.constData()); + if (!m_qbsStdout.isEmpty() + && (shouldLog || qEnvironmentVariableIsSet("QBS_AUTOTEST_ALWAYS_LOG_STDOUT"))) + qDebug("%s", m_qbsStdout.constData()); + return process.exitCode(); +} + +/*! + Recursive copy from directory to another. + Note that this updates the file stamps on Linux but not on Windows. + */ +void TestBlackboxBase::ccp(const QString &sourceDirPath, const QString &targetDirPath) +{ + QDir currentDir; + QDirIterator dit(sourceDirPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden); + while (dit.hasNext()) { + dit.next(); + const QString targetPath = targetDirPath + QLatin1Char('/') + dit.fileName(); + currentDir.mkpath(targetPath); + ccp(dit.filePath(), targetPath); + } + + QDirIterator fit(sourceDirPath, QDir::Files | QDir::Hidden); + while (fit.hasNext()) { + fit.next(); + const QString targetPath = targetDirPath + QLatin1Char('/') + fit.fileName(); + QFile::remove(targetPath); // allowed to fail + QFile src(fit.filePath()); + QVERIFY2(src.copy(targetPath), qPrintable(src.errorString())); + } +} + +void TestBlackboxBase::rmDirR(const QString &dir) +{ + QString errorMessage; + removeDirectoryWithContents(dir, &errorMessage); +} + +QByteArray TestBlackboxBase::unifiedLineEndings(const QByteArray &ba) +{ + if (HostOsInfo::isWindowsHost()) { + QByteArray result; + result.reserve(ba.size()); + for (const char &c : ba) { + if (c != '\r') + result.append(c); + } + return result; + } else { + return ba; + } +} + +void TestBlackboxBase::sanitizeOutput(QByteArray *ba) +{ + if (HostOsInfo::isWindowsHost()) + ba->replace('\r', ""); +} + +void TestBlackboxBase::initTestCase() +{ + QVERIFY(regularFileExists(qbsExecutableFilePath)); + + const SettingsPtr s = settings(); + 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.")); + + validateTestProfile(); + + // Initialize the test data directory. + QVERIFY(testDataDir != testSourceDir); + rmDirR(testDataDir); + QDir().mkpath(testDataDir); + ccp(testSourceDir, testDataDir); + QDir().mkpath(testDataDir + "/find"); + ccp(testSourceDir + "/../find", testDataDir + "/find"); + QVERIFY(copyDllExportHeader(testSourceDir, testDataDir)); +} + +void TestBlackboxBase::validateTestProfile() +{ + const SettingsPtr s = settings(); + 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.")); + if (!m_needsQt) + return; + const QStringList qmakeFilePaths = Profile(profileName(), s.get()) + .value("moduleProviders.Qt.qmakeFilePaths").toStringList(); + if (!qmakeFilePaths.empty()) + return; + if (!findExecutable(QStringList{"qmake"}).isEmpty()) + return; + QSKIP(QByteArray("The build profile '" + profileName().toLocal8Bit() + + "' is not a valid Qt profile and Qt was not found " + "in the global search paths.")); + +} + +QString TestBlackboxBase::findExecutable(const QStringList &fileNames) +{ + const QStringList path = QString::fromLocal8Bit(qgetenv("PATH")) + .split(HostOsInfo::pathListSeparator(), QBS_SKIP_EMPTY_PARTS); + + for (const QString &fileName : fileNames) { + QFileInfo fi(fileName); + if (fi.isAbsolute()) + return fi.exists() ? fileName : QString(); + for (const QString &ppath : path) { + const QString fullPath + = HostOsInfo::appendExecutableSuffix(ppath + QLatin1Char('/') + fileName); + if (QFileInfo(fullPath).exists()) + return QDir::cleanPath(fullPath); + } + } + return {}; +} + +QMap TestBlackboxBase::findJdkTools(int *status) +{ + QTemporaryDir temp; + QDir::setCurrent(testDataDir + "/find"); + QbsRunParameters params = QStringList() << "-f" << "find-jdk.qbs"; + params.buildDirectory = temp.path(); + const int res = runQbs(params); + if (status) + *status = res; + QFile file(temp.path() + "/" + relativeProductBuildDir("find-jdk") + "/jdk.json"); + if (!file.open(QIODevice::ReadOnly)) + return {}; + const auto tools = QJsonDocument::fromJson(file.readAll()).toVariant().toMap(); + return { + {"java", QDir::fromNativeSeparators(tools["java"].toString())}, + {"javac", QDir::fromNativeSeparators(tools["javac"].toString())}, + {"jar", QDir::fromNativeSeparators(tools["jar"].toString())} + }; +} + +qbs::Version TestBlackboxBase::qmakeVersion(const QString &qmakeFilePath) +{ + QStringList arguments; + arguments << "-query" << "QT_VERSION"; + QProcess qmakeProcess; + qmakeProcess.start(qmakeFilePath, arguments); + if (!qmakeProcess.waitForStarted() || !qmakeProcess.waitForFinished() + || qmakeProcess.exitStatus() != QProcess::NormalExit) { + qDebug() << "qmake '" << qmakeFilePath << "' could not be run."; + return qbs::Version(); + } + QByteArray result = qmakeProcess.readAll().simplified(); + qbs::Version version = qbs::Version::fromString(result); + if (!version.isValid()) + qDebug() << "qmake '" << qmakeFilePath << "' version is not valid."; + return version; +} diff --git a/tests/auto/blackbox/tst_blackboxbase.h b/tests/auto/blackbox/tst_blackboxbase.h new file mode 100644 index 00000000..ed9a233d --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxbase.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef TST_BLACKBOXBASE_H +#define TST_BLACKBOXBASE_H + +#include "../shared.h" + +#include +#include +#include + +class QbsRunParameters +{ +public: + QbsRunParameters() + { + init(); + } + + QbsRunParameters(QString cmd, QStringList args = QStringList()) + : command(std::move(cmd)), arguments(std::move(args)) + { + init(); + } + + QbsRunParameters(QStringList args) + : arguments(std::move(args)) + { + init(); + } + + void init() + { + expectFailure = false; + expectCrash = false; + profile = profileName(); + settingsDir = settings()->baseDirectory(); + environment = QProcessEnvironment::systemEnvironment(); + } + + QString command; + QStringList arguments; + QString buildDirectory; + QProcessEnvironment environment; + QString profile; + QString settingsDir; + QString workingDir; + bool expectFailure = false; + bool expectCrash = false; +}; + +class TestBlackboxBase : public QObject +{ + Q_OBJECT +public: + TestBlackboxBase(const QString &testDataSrcDir, const QString &testName); + +public slots: + virtual void initTestCase(); + +protected: + virtual void validateTestProfile(); + + void setNeedsQt() { m_needsQt = true; } + int runQbs(const QbsRunParameters ¶ms = QbsRunParameters()); + void rmDirR(const QString &dir); + static QByteArray unifiedLineEndings(const QByteArray &ba); + static void sanitizeOutput(QByteArray *ba); + static void ccp(const QString &sourceDirPath, const QString &targetDirPath); + static QString findExecutable(const QStringList &fileNames); + QMap findJdkTools(int *status); + static qbs::Version qmakeVersion(const QString &qmakeFilePath); + + const QString testDataDir; + const QString testSourceDir; + const QString qbsExecutableFilePath; + const QString defaultInstallRoot; + + QByteArray m_qbsStderr; + QByteArray m_qbsStdout; + int m_needsQt = false; +}; + +#endif // TST_BLACKBOXBASE_H diff --git a/tests/auto/blackbox/tst_blackboxexamples.cpp b/tests/auto/blackbox/tst_blackboxexamples.cpp new file mode 100644 index 00000000..b6a26b7f --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxexamples.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://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. +** +****************************************************************************/ + +#include "tst_blackboxexamples.h" + +#include +#include + +QStringList TestBlackboxExamples::collectExamples(const QString &dirPath) +{ + QStringList result; + QDir dir(dirPath); + const auto subDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name); + for (const auto &subDir : subDirs) { + const auto path = dir.filePath(subDir); + if (!QFileInfo::exists(path + "/" + subDir + ".qbs")) + continue; + result.append(QDir(testDataDir).relativeFilePath(path)); + } + return result; +} + +TestBlackboxExamples::TestBlackboxExamples() + : TestBlackboxBase(SRCDIR "/../../../examples/", "blackbox-examples") +{ + setNeedsQt(); +} + +void TestBlackboxExamples::baremetal_data() +{ + QTest::addColumn("example"); + + QDir baremetal(testDataDir + "/baremetal/"); + const auto subDirs = baremetal.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name); + for (const auto &subDir : subDirs) { + const auto examples = collectExamples(baremetal.filePath(subDir)); + for (const auto &example: examples) { + const auto relativePath = baremetal.relativeFilePath(example); + QTest::newRow(relativePath.toUtf8().data()) << relativePath; + } + } +} + +void TestBlackboxExamples::baremetal() +{ + QFETCH(QString, example); + + QVERIFY(QDir::setCurrent(testDataDir + "/" + example)); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxExamples::examples_data() +{ + QTest::addColumn("example"); + + auto examples = collectExamples(testDataDir); + examples.append(collectExamples(testDataDir + "/protobuf")); + std::sort(examples.begin(), examples.end()); + + for (const auto &example: examples) { + if (example == u"baremetal") + continue; + QTest::newRow(example.toUtf8().data()) << example; + } +} + +void TestBlackboxExamples::examples() +{ + QFETCH(QString, example); + + QVERIFY(QDir::setCurrent(testDataDir + "/" + example)); + QbsRunParameters params( + {QStringLiteral("-f"), QFileInfo(example).fileName() + QStringLiteral(".qbs")}); + QCOMPARE(runQbs(params), 0); +} + +QTEST_MAIN(TestBlackboxExamples) diff --git a/tests/auto/blackbox/tst_blackboxexamples.h b/tests/auto/blackbox/tst_blackboxexamples.h new file mode 100644 index 00000000..bea8be76 --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxexamples.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2020 Ivan Komissarov (abbapoh@gmail.com) +** Contact: https://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. +** +****************************************************************************/ + +#ifndef TST_BLACKBOXEXAMPLES_H +#define TST_BLACKBOXEXAMPLES_H + +#include "tst_blackboxbase.h" + +class TestBlackboxExamples : public TestBlackboxBase +{ + Q_OBJECT + +private: + QStringList collectExamples(const QString &dirPath); + +public: + TestBlackboxExamples(); + +private slots: + void baremetal_data(); + void baremetal(); + void examples_data(); + void examples(); +}; + +#endif // TST_BLACKBOXEXAMPLES_H diff --git a/tests/auto/blackbox/tst_blackboxjava.cpp b/tests/auto/blackbox/tst_blackboxjava.cpp new file mode 100644 index 00000000..a815a84f --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxjava.cpp @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** 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 "tst_blackboxjava.h" + +#include "../shared.h" +#include +#include +#include + +#include +#include + +using qbs::Internal::HostOsInfo; +using qbs::Profile; + +TestBlackboxJava::TestBlackboxJava() : TestBlackboxBase (SRCDIR "/testdata-java", "blackbox-java") +{ +} + +static QProcessEnvironment processEnvironmentWithCurrentDirectoryInLibraryPath() +{ + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(HostOsInfo::libraryPathEnvironmentVariable(), + (QStringList() << env.value(HostOsInfo::libraryPathEnvironmentVariable()) << ".") + .join(HostOsInfo::pathListSeparator())); + return env; +} + +void TestBlackboxJava::java() +{ +#if defined(Q_OS_WIN32) && !defined(Q_OS_WIN64) + QSKIP("QTBUG-3845"); +#endif + + const SettingsPtr s = settings(); + Profile p(profileName(), s.get()); + + int status; + const auto jdkTools = findJdkTools(&status); + QCOMPARE(status, 0); + + QDir::setCurrent(testDataDir + "/java"); + + status = runQbs(); + if (p.value("java.jdkPath").toString().isEmpty() + && status != 0 && m_qbsStderr.contains("jdkPath")) { + QSKIP("java.jdkPath not set and automatic detection failed"); + } + + QCOMPARE(status, 0); + + const QStringList classFiles = + QStringList() << "Jet" << "Ship" << "Vehicles"; + QStringList classFiles1 = QStringList(classFiles) << "io/qt/qbs/HelloWorld" << "NoPackage"; + for (QString &classFile : classFiles1) { + classFile = relativeProductBuildDir("cc") + "/classes/" + classFile + ".class"; + QVERIFY2(regularFileExists(classFile), qPrintable(classFile)); + } + + for (const QString &classFile : classFiles) { + const QString filePath = relativeProductBuildDir("jar_file") + "/classes/" + classFile + + ".class"; + QVERIFY2(regularFileExists(filePath), qPrintable(filePath)); + } + const QString jarFilePath = relativeProductBuildDir("jar_file") + '/' + "jar_file.jar"; + QVERIFY2(regularFileExists(jarFilePath), qPrintable(jarFilePath)); + + // Now check whether we correctly predicted the class file output paths. + QCOMPARE(runQbs(QbsRunParameters("clean")), 0); + for (const QString &classFile : qAsConst(classFiles1)) { + QVERIFY2(!regularFileExists(classFile), qPrintable(classFile)); + } + + // This tests various things: java.manifestClassPath, JNI, etc. + QDir::setCurrent(relativeBuildDir() + "/install-root"); + QProcess process; + process.setProcessEnvironment(processEnvironmentWithCurrentDirectoryInLibraryPath()); + process.start(HostOsInfo::appendExecutableSuffix(jdkTools["java"]), + QStringList() << "-jar" << "jar_file.jar"); + if (process.waitForStarted()) { + QVERIFY2(process.waitForFinished(), qPrintable(process.errorString())); + QVERIFY2(process.exitCode() == 0, process.readAllStandardError().constData()); + const QByteArray stdOut = process.readAllStandardOutput(); + QVERIFY2(stdOut.contains("Driving!"), stdOut.constData()); + QVERIFY2(stdOut.contains("Flying!"), stdOut.constData()); + QVERIFY2(stdOut.contains("Flying (this is a space ship)!"), stdOut.constData()); + QVERIFY2(stdOut.contains("Sailing!"), stdOut.constData()); + QVERIFY2(stdOut.contains("Native code performing complex internal combustion process ("), + stdOut.constData()); + } + + process.start("unzip", QStringList() << "-p" << "jar_file.jar"); + if (process.waitForStarted()) { + QVERIFY2(process.waitForFinished(), qPrintable(process.errorString())); + const QByteArray stdOut = process.readAllStandardOutput(); + QVERIFY2(stdOut.contains("Class-Path: random_stuff.jar car_jar.jar"), stdOut.constData()); + QVERIFY2(stdOut.contains("Main-Class: Vehicles"), stdOut.constData()); + QVERIFY2(stdOut.contains("Some-Property: Some-Value"), stdOut.constData()); + QVERIFY2(stdOut.contains("Additional-Property: Additional-Value"), stdOut.constData()); + QVERIFY2(stdOut.contains("Extra-Property: Crazy-Value"), stdOut.constData()); + } +} + +static QString dpkgArch(const QString &prefix = QString()) +{ + QProcess dpkg; + dpkg.start("/usr/bin/dpkg", QStringList() << "--print-architecture"); + dpkg.waitForFinished(); + if (dpkg.exitStatus() == QProcess::NormalExit && dpkg.exitCode() == 0) + return prefix + QString::fromLocal8Bit(dpkg.readAllStandardOutput().trimmed()); + return {}; +} + +void TestBlackboxJava::javaDependencyTracking() +{ + QFETCH(QString, jdkPath); + QFETCH(QString, javaVersion); + + QDir::setCurrent(testDataDir + "/java"); + QbsRunParameters rp; + rp.arguments.push_back("--check-outputs"); + if (!jdkPath.isEmpty()) + rp.arguments << ("modules.java.jdkPath:" + jdkPath); + if (!javaVersion.isEmpty()) + rp.arguments << ("modules.java.languageVersion:'" + javaVersion + "'"); + rmDirR(relativeBuildDir()); + const bool defaultJdkPossiblyTooOld = jdkPath.isEmpty() && !javaVersion.isEmpty(); + rp.expectFailure = defaultJdkPossiblyTooOld; + QVERIFY(runQbs(rp) == 0 + || (defaultJdkPossiblyTooOld && m_qbsStderr.contains("invalid source release"))); +} + +void TestBlackboxJava::javaDependencyTracking_data() +{ + QTest::addColumn("jdkPath"); + QTest::addColumn("javaVersion"); + + const SettingsPtr s = settings(); + Profile p(profileName(), s.get()); + + auto getSpecificJdkVersion = [](const QString &jdkVersion) -> QString { + if (HostOsInfo::isMacosHost()) { + QProcess java_home; + java_home.start("/usr/libexec/java_home", QStringList() << "--version" << jdkVersion); + java_home.waitForFinished(); + if (java_home.exitStatus() == QProcess::NormalExit && java_home.exitCode() == 0) + return QString::fromLocal8Bit(java_home.readAllStandardOutput().trimmed()); + } else if (HostOsInfo::isWindowsHost()) { + QSettings settings("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\" + + jdkVersion, QSettings::NativeFormat); + return settings.value("JavaHome").toString(); + } else { + QString minorVersion = jdkVersion; + if (minorVersion.startsWith("1.")) + minorVersion.remove(0, 2); + + const QStringList searchPaths = { + "/usr/lib/jvm/java-" + minorVersion + "-openjdk" + dpkgArch("-"), // Debian + "/usr/lib/jvm/java-" + minorVersion + "-openjdk", // Arch + "/usr/lib/jvm/jre-1." + minorVersion + ".0-openjdk", // Fedora + "/usr/lib64/jvm/java-1." + minorVersion + ".0-openjdk", // OpenSuSE + }; + for (const QString &searchPath : searchPaths) { + if (QFile::exists(searchPath + "/bin/javac")) + return searchPath; + } + } + + return {}; + }; + + static const auto knownJdkVersions = QStringList() << "1.6" << "1.7" << "1.8" << "1.9" + << QString(); // default JDK; + QStringList seenJdkVersions; + for (const auto &jdkVersion : knownJdkVersions) { + QString specificJdkPath = getSpecificJdkVersion(jdkVersion); + if (jdkVersion.isEmpty() || !specificJdkPath.isEmpty()) { + const auto jdkPath = jdkVersion.isEmpty() ? jdkVersion : specificJdkPath; + + if (!jdkVersion.isEmpty()) + seenJdkVersions << jdkVersion; + + if (!seenJdkVersions.empty()) { + const auto javaVersions = QStringList() + << knownJdkVersions.mid(0, knownJdkVersions.indexOf(seenJdkVersions.last()) + 1) + << QString(); // also test with no explicitly specified source version + + for (const auto ¤tJavaVersion : javaVersions) { + const QString rowName = (!jdkPath.isEmpty() ? jdkPath : "default JDK") + + QStringLiteral(", ") + + (!currentJavaVersion.isEmpty() + ? ("Java " + currentJavaVersion) + : "default Java version"); + QTest::newRow(rowName.toLatin1().constData()) + << jdkPath << currentJavaVersion; + } + } + } + } + + if (seenJdkVersions.empty()) + QSKIP("No JDKs installed"); +} + +void TestBlackboxJava::javaDependencyTrackingInnerClass() +{ + const SettingsPtr s = settings(); + Profile p(profileName(), s.get()); + + QDir::setCurrent(testDataDir + "/java/inner-class"); + QbsRunParameters params; + int status = runQbs(params); + if (p.value("java.jdkPath").toString().isEmpty() + && status != 0 && m_qbsStderr.contains("jdkPath")) { + QSKIP("java.jdkPath not set and automatic detection failed"); + } + QCOMPARE(status, 0); +} + +QTEST_MAIN(TestBlackboxJava) diff --git a/tests/auto/blackbox/tst_blackboxjava.h b/tests/auto/blackbox/tst_blackboxjava.h new file mode 100644 index 00000000..68d8a7f8 --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxjava.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef TST_BLACKBOX_H +#define TST_BLACKBOX_H + +#include "tst_blackboxbase.h" + +class TestBlackboxJava : public TestBlackboxBase +{ + Q_OBJECT + +public: + TestBlackboxJava(); + +private slots: + void java(); + void javaDependencyTracking(); + void javaDependencyTracking_data(); + void javaDependencyTrackingInnerClass(); +}; + +#endif // TST_BLACKBOX_H diff --git a/tests/auto/blackbox/tst_blackboxjoblimits.cpp b/tests/auto/blackbox/tst_blackboxjoblimits.cpp new file mode 100644 index 00000000..89b5f638 --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxjoblimits.cpp @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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_blackboxbase.h" + +#include "../shared.h" +#include + +class TestBlackboxJobLimits : public TestBlackboxBase +{ + Q_OBJECT + +public: + TestBlackboxJobLimits(); + +private slots: + void jobLimits_data(); + void jobLimits(); +}; + +TestBlackboxJobLimits::TestBlackboxJobLimits() + : TestBlackboxBase (SRCDIR "/testdata-joblimits", "blackbox-joblimits") +{ +} + +void TestBlackboxJobLimits::jobLimits_data() +{ + QTest::addColumn("projectJobCount"); + QTest::addColumn("productJobCount"); + QTest::addColumn("moduleJobCount"); + QTest::addColumn("prefsJobCount"); + QTest::addColumn("cliJobCount"); + QTest::addColumn("projectPrecedence"); + QTest::addColumn("expectSuccess"); + for (int projectJobCount = -1; projectJobCount <= 1; ++projectJobCount) { + for (int productJobCount = -1; productJobCount <= 1; ++productJobCount) { + for (int moduleJobCount = -1; moduleJobCount <= 1; ++moduleJobCount) { + for (int prefsJobCount = -1; prefsJobCount <= 1; ++prefsJobCount) { + for (int cliJobCount = -1; cliJobCount <= 1; ++cliJobCount) { + QString description = QString("project:%1/" + "product:%2/module:%3/prefs:%4/cli:%5/project precedence") + .arg(projectJobCount).arg(productJobCount).arg(moduleJobCount) + .arg(prefsJobCount).arg(cliJobCount).toLocal8Bit(); + bool expectSuccess; + switch (productJobCount) { + case 1: expectSuccess = true; break; + case 0: expectSuccess = false; break; + case -1: + switch (projectJobCount) { + case 1: expectSuccess = true; break; + case 0: expectSuccess = false; break; + case -1: + switch (moduleJobCount) { + case 1: expectSuccess = true; break; + case 0: expectSuccess = false; break; + case -1: + switch (cliJobCount) { + case 1: expectSuccess = true; break; + case 0: expectSuccess = false; break; + case -1: expectSuccess = prefsJobCount == 1; break; + } + break; + } + break; + } + break; + } + QTest::newRow(qPrintable(description)) + << projectJobCount << productJobCount << moduleJobCount + << prefsJobCount << cliJobCount << true << expectSuccess; + description = QString("project:%1/" + "product:%2/module:%3/prefs:%4/cli:%5/default precedence") + .arg(projectJobCount).arg(productJobCount).arg(moduleJobCount) + .arg(prefsJobCount).arg(cliJobCount).toLocal8Bit(); + switch (cliJobCount) { + case 1: expectSuccess = true; break; + case 0: expectSuccess = false; break; + case -1: + switch (prefsJobCount) { + case 1: expectSuccess = true; break; + case 0: expectSuccess = false; break; + case -1: + switch (productJobCount) { + case 1: expectSuccess = true; break; + case 0: expectSuccess = false; break; + case -1: + switch (projectJobCount) { + case 1: expectSuccess = true; break; + case 0: expectSuccess = false; break; + case -1: expectSuccess = moduleJobCount == 1; break; + } + break; + } + break; + } + break; + } + QTest::newRow(qPrintable(description)) + << projectJobCount << productJobCount << moduleJobCount + << prefsJobCount << cliJobCount << false << expectSuccess; + } + } + } + } + } +} + +void TestBlackboxJobLimits::jobLimits() +{ + QDir::setCurrent(testDataDir + "/job-limits"); + QFETCH(int, projectJobCount); + QFETCH(int, productJobCount); + QFETCH(int, moduleJobCount); + QFETCH(int, prefsJobCount); + QFETCH(int, cliJobCount); + QFETCH(bool, projectPrecedence); + QFETCH(bool, expectSuccess); + SettingsPtr theSettings = settings(); + qbs::Internal::TemporaryProfile profile("jobLimitsProfile", theSettings.get()); + profile.p.setValue("preferences.jobLimit.singleton", prefsJobCount); + theSettings->sync(); + QbsRunParameters resolveParams("resolve"); + resolveParams.profile = profile.p.name(); + resolveParams.arguments << ("project.projectJobCount:" + QString::number(projectJobCount)) + << ("project.productJobCount:" + QString::number(productJobCount)) + << ("project.moduleJobCount:" + QString::number(moduleJobCount)); + QCOMPARE(runQbs(resolveParams), 0); + QbsRunParameters buildParams; + buildParams.expectFailure = !expectSuccess; + if (cliJobCount != -1) + buildParams.arguments << "--job-limits" << ("singleton:" + QString::number(cliJobCount)); + if (projectPrecedence) + buildParams.arguments << "--enforce-project-job-limits"; + buildParams.profile = profile.p.name(); + const int exitCode = runQbs(buildParams); + if (expectSuccess) + QCOMPARE(exitCode, 0); + else if (exitCode == 0) + QSKIP("no failure with no limit in place, result inconclusive"); + else + QVERIFY2(m_qbsStderr.contains("exclusive"), m_qbsStderr.constData()); + if (exitCode == 0) + QCOMPARE(m_qbsStdout.count("Running tool"), 5); +} + +QTEST_MAIN(TestBlackboxJobLimits) + +#include diff --git a/tests/auto/blackbox/tst_blackboxqt.cpp b/tests/auto/blackbox/tst_blackboxqt.cpp new file mode 100644 index 00000000..4fc03892 --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxqt.cpp @@ -0,0 +1,614 @@ +/**************************************************************************** +** +** 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 "tst_blackboxqt.h" + +#include "../shared.h" +#include +#include +#include + +#include + +#define WAIT_FOR_NEW_TIMESTAMP() waitForNewTimestamp(testDataDir) + +using qbs::Internal::HostOsInfo; +using qbs::Profile; + +TestBlackboxQt::TestBlackboxQt() : TestBlackboxBase (SRCDIR "/testdata-qt", "blackbox-qt") +{ + setNeedsQt(); +} + +void TestBlackboxQt::addQObjectMacroToGeneratedCppFile() +{ + QDir::setCurrent(testDataDir + "/add-qobject-macro-to-generated-cpp-file"); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("moc"), m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("object.cpp.in", "// ", ""); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("moc"), m_qbsStdout.constData()); +} + +void TestBlackboxQt::autoQrc() +{ + QDir::setCurrent(testDataDir + "/auto-qrc"); + QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QCOMPARE(runQbs(QbsRunParameters("run", QStringList{"-p", "app"})), 0); + QVERIFY2(m_qbsStdout.simplified().contains("resource data: resource1 resource2"), + m_qbsStdout.constData()); +} + +void TestBlackboxQt::cachedQml() +{ + QDir::setCurrent(testDataDir + "/cached-qml"); + QCOMPARE(runQbs(), 0); + QString dataDir = relativeBuildDir() + "/install-root/data"; + QVERIFY2(m_qbsStdout.contains("qmlcachegen must work: true") + || m_qbsStdout.contains("qmlcachegen must work: false"), + m_qbsStdout.constData()); + if (m_qbsStdout.contains("qmlcachegen must work: false") + && 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"); + QCOMPARE(runQbs(), 0); + QVERIFY(m_qbsStdout.contains("compiling moc_theobject.cpp")); + QVERIFY(!m_qbsStdout.contains("creating amalgamated_moc_theapp.cpp")); + QVERIFY(!m_qbsStdout.contains("compiling amalgamated_moc_theapp.cpp")); + QbsRunParameters params(QStringList("modules.Qt.core.combineMocOutput:true")); + params.command = "resolve"; + QCOMPARE(runQbs(params), 0); + params.command = "build"; + QCOMPARE(runQbs(params), 0); + QVERIFY(!m_qbsStdout.contains("compiling moc_theobject.cpp")); + QVERIFY(m_qbsStdout.contains("creating amalgamated_moc_theapp.cpp")); + QVERIFY(m_qbsStdout.contains("compiling amalgamated_moc_theapp.cpp")); +} + +void TestBlackboxQt::createProject() +{ + QDir::setCurrent(testDataDir + "/create-project"); + QVERIFY(QFile::copy(testSourceDir + "/../../../../examples/helloworld-qt/main.cpp", + QDir::currentPath() + "/main.cpp")); + QbsRunParameters createParams("create-project"); + createParams.profile.clear(); + QCOMPARE(runQbs(createParams), 0); + createParams.expectFailure = true; + QVERIFY(runQbs(createParams) != 0); + QVERIFY2(m_qbsStderr.contains("already contains qbs files"), m_qbsStderr.constData()); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("compiling"), m_qbsStdout.constData()); +} + +void TestBlackboxQt::dbusAdaptors() +{ + QDir::setCurrent(testDataDir + "/dbus-adaptors"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxQt::dbusInterfaces() +{ + QDir::setCurrent(testDataDir + "/dbus-interfaces"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxQt::forcedMoc() +{ + QDir::setCurrent(testDataDir + "/forced-moc"); + QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QCOMPARE(runQbs(QbsRunParameters("run")), 0); + QVERIFY2(m_qbsStderr.contains("Hello from slot"), m_qbsStderr.constData()); +} + +void TestBlackboxQt::includedMocCpp() +{ + QDir::setCurrent(testDataDir + "/included-moc-cpp"); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("compiling moc_myobject.cpp"), m_qbsStdout.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE("myobject.cpp", "#include ("generate"); + QTest::addColumn("installDir"); + QTest::newRow("don't generate") << false << QString(); + QTest::newRow("don't generate with install info") << false << QString("blubb"); + QTest::newRow("generate only") << true << QString(); + QTest::newRow("generate and install") << true << QString("blubb"); +} + +void TestBlackboxQt::metaTypes() +{ + QDir::setCurrent(testDataDir + "/metatypes"); + QFETCH(bool, generate); + QFETCH(QString, installDir); + const QStringList args{"modules.Qt.core.generateMetaTypesFile:" + + QString(generate ? "true" : "false"), + "modules.Qt.core.metaTypesInstallDir:" + installDir, + "-v", "--force-probe-execution"}; + QCOMPARE(runQbs(QbsRunParameters("resolve", args)), 0); + const bool canGenerate = m_qbsStdout.contains("can generate"); + const bool cannotGenerate = m_qbsStdout.contains("cannot generate"); + QVERIFY(canGenerate != cannotGenerate); + const bool expectFiles = generate && canGenerate; + const bool expectInstalledFiles = expectFiles && !installDir.isEmpty(); + QCOMPARE(runQbs(QStringList("--clean-install-root")), 0); + const QString productDir = relativeProductBuildDir("mylib"); + const QString outputDir = productDir + "/qt.headers"; + QVERIFY(!regularFileExists(outputDir + "/moc_unmocableclass.cpp.json")); + QCOMPARE(regularFileExists(outputDir + "/moc_mocableclass1.cpp.json"), expectFiles); + QCOMPARE(regularFileExists(outputDir + "/mocableclass2.moc.json"), expectFiles); + QCOMPARE(regularFileExists(productDir + "/mylib_metatypes.json"), expectFiles); + QCOMPARE(regularFileExists(relativeBuildDir() + "/install-root/some-prefix/" + installDir + + "/mylib_metatypes.json"), expectInstalledFiles); +} + +void TestBlackboxQt::mixedBuildVariants() +{ + QDir::setCurrent(testDataDir + "/mixed-build-variants"); + const SettingsPtr s = settings(); + Profile profile(profileName(), s.get()); + if (profileToolchain(profile).contains("msvc")) { + QbsRunParameters params; + params.arguments << "qbs.buildVariant:debug"; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("not allowed"), m_qbsStderr.constData()); + } else { + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + QVERIFY2(m_qbsStderr.contains("not supported"), m_qbsStderr.constData()); + } +} + +void TestBlackboxQt::mocAndCppCombining() +{ + QDir::setCurrent(testDataDir + "/moc-and-cxx-combining"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxQt::mocFlags() +{ + QDir::setCurrent(testDataDir + "/moc-flags"); + QCOMPARE(runQbs(), 0); + WAIT_FOR_NEW_TIMESTAMP(); + QbsRunParameters params; + params.expectFailure = true; + params.arguments << "Qt.core.mocFlags:-E"; + QVERIFY(runQbs(params) != 0); +} + +void TestBlackboxQt::mocCompilerDefines() +{ + QDir::setCurrent(testDataDir + "/moc-compiler-defines"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxQt::mocSameFileName() +{ + QDir::setCurrent(testDataDir + "/moc-same-file-name"); + QCOMPARE(runQbs(), 0); + QCOMPARE(m_qbsStdout.count("compiling moc_someclass.cpp"), 2); +} + +void TestBlackboxQt::pkgconfig() +{ + QDir::setCurrent(testDataDir + "/pkgconfig"); + QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QbsRunParameters params; + params.command = "run"; + QCOMPARE(runQbs(params), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("pkgconfig or Qt not found"); +} + +void TestBlackboxQt::pluginMetaData() +{ + QDir::setCurrent(testDataDir + "/plugin-meta-data"); + QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + + QVERIFY2(runQbs(QbsRunParameters("run", QStringList{"-p", "app"})) == 0, + m_qbsStderr.constData()); + QVERIFY2(m_qbsStderr.contains("all ok!"), m_qbsStderr.constData()); + WAIT_FOR_NEW_TIMESTAMP(); + touch("metadata.json"); + waitForFileUnlock(); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("moc"), m_qbsStdout.constData()); +} + +void TestBlackboxQt::pluginSupport_data() +{ + QTest::addColumn("invalidPlugin"); + QTest::newRow("request valid plugins") << false; + QTest::newRow("request invalid plugin") << true; +} + +void TestBlackboxQt::pluginSupport() +{ + QDir::setCurrent(testDataDir + "/plugin-support"); + QFETCH(bool, invalidPlugin); + QbsRunParameters resolveParams("resolve"); + if (invalidPlugin) { + resolveParams.arguments << "modules.m1.useDummy:true"; + resolveParams.expectFailure = true; + } + QCOMPARE(runQbs(resolveParams) == 0, !invalidPlugin); + if (invalidPlugin) { + QVERIFY2(m_qbsStderr.contains("Plugin 'dummy' of type 'imageformats' was requested, " + "but is not available"), m_qbsStderr.constData()); + return; + } + const bool isStaticQt = m_qbsStdout.contains("static Qt: true"); + const bool isDynamicQt = m_qbsStdout.contains("static Qt: false"); + QVERIFY(isStaticQt != isDynamicQt); + if (isStaticQt) + QVERIFY2(m_qbsStdout.contains("platform plugin count: 1"), m_qbsStdout.constData()); + else + QVERIFY2(m_qbsStdout.contains("platform plugin count: 0"), m_qbsStdout.constData()); + const auto extractList = [this](const char sep) { + const int listStartIndex = m_qbsStdout.indexOf(sep); + const int listEndIndex = m_qbsStdout.indexOf(sep, listStartIndex + 1); + const QByteArray listString = m_qbsStdout.mid(listStartIndex + 1, + listEndIndex - listStartIndex - 1); + return listString.isEmpty() ? QByteArrayList() : listString.split(','); + }; + const QByteArrayList enabledPlugins = extractList('%'); + if (isStaticQt) + QCOMPARE(enabledPlugins.size(), 2); + else + QVERIFY(enabledPlugins.empty()); + const QByteArrayList allPlugins = extractList('#'); + QVERIFY(allPlugins.size() >= enabledPlugins.size()); + QCOMPARE(runQbs(), 0); + for (const QByteArray &plugin : allPlugins) { + QCOMPARE(m_qbsStdout.contains("qt_plugin_import_" + plugin + ".cpp"), + enabledPlugins.contains(plugin)); + } +} + +void TestBlackboxQt::qmlDebugging() +{ + QDir::setCurrent(testDataDir + "/qml-debugging"); + QCOMPARE(runQbs(), 0); + const SettingsPtr s = settings(); + Profile profile(profileName(), s.get()); + if (!profileToolchain(profile).contains("gcc")) + return; + QProcess nm; + nm.start("nm", QStringList(relativeExecutableFilePath("debuggable-app"))); + if (nm.waitForStarted()) { // Let's ignore hosts without nm. + QVERIFY2(nm.waitForFinished(), qPrintable(nm.errorString())); + QVERIFY2(nm.exitCode() == 0, nm.readAllStandardError().constData()); + const QByteArray output = nm.readAllStandardOutput(); + QVERIFY2(output.toLower().contains("debugginghelper"), output.constData()); + } +} + +void TestBlackboxQt::qobjectInObjectiveCpp() +{ + if (!HostOsInfo::isMacosHost()) + QSKIP("only applies on macOS"); + const QString testDir = testDataDir + "/qobject-in-mm"; + QDir::setCurrent(testDir); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxQt::qmlTypeRegistrar_data() +{ + QTest::addColumn("importName"); + QTest::addColumn("installDir"); + QTest::newRow("don't generate") << QString() << QString(); + QTest::newRow("don't generate with install info") << QString() << QString("blubb"); + QTest::newRow("generate only") << QString("People") << QString(); + QTest::newRow("generate and install") << QString("People") << QString("blubb"); +} + +void TestBlackboxQt::qmlTypeRegistrar() +{ + QDir::setCurrent(testDataDir + "/qmltyperegistrar"); + QFETCH(QString, importName); + QFETCH(QString, installDir); + rmDirR(relativeBuildDir()); + const QStringList args{"modules.Qt.qml.importName:" + importName, + "modules.Qt.qml.typesInstallDir:" + installDir}; + QCOMPARE(runQbs(QbsRunParameters("resolve", args)), 0); + const bool hasRegistrar = m_qbsStdout.contains("has registrar"); + const bool doesNotHaveRegistrar = m_qbsStdout.contains("does not have registrar"); + QVERIFY(hasRegistrar != doesNotHaveRegistrar); + if (doesNotHaveRegistrar) + QSKIP("Qt version too old"); + QCOMPARE(runQbs(), 0); + const bool enabled = !importName.isEmpty(); + QCOMPARE(m_qbsStdout.contains("running qmltyperegistrar"), enabled); + QCOMPARE(m_qbsStdout.contains("compiling myapp_qmltyperegistrations.cpp"), enabled); + const QString buildDir = relativeProductBuildDir("myapp"); + QCOMPARE(regularFileExists(buildDir + "/myapp.qmltypes"), enabled); + QCOMPARE(regularFileExists(relativeBuildDir() + "/install-root/" + installDir + + "/myapp.qmltypes"), enabled && !installDir.isEmpty()); +} + +void TestBlackboxQt::qtKeywords() +{ + QDir::setCurrent(testDataDir + "/qt-keywords"); + QbsRunParameters params(QStringList("modules.Qt.core.enableKeywords:false")); + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + params.arguments.clear(); + QVERIFY(runQbs(params) != 0); + params.command = "resolve"; + QCOMPARE(runQbs(params), 0); + params.command = "build"; + QCOMPARE(runQbs(params), 0); +} + +void TestBlackboxQt::quickCompiler() +{ + QDir::setCurrent(testDataDir + "/quick-compiler"); + QCOMPARE(runQbs(), 0); + const bool hasCompiler = m_qbsStdout.contains("compiler available"); + const bool doesNotHaveCompiler = m_qbsStdout.contains("compiler not available"); + QVERIFY2(hasCompiler || doesNotHaveCompiler, m_qbsStdout.constData()); + QCOMPARE(m_qbsStdout.contains("compiling qml_subdir_test_qml.cpp"), hasCompiler); + if (doesNotHaveCompiler) + QSKIP("qtquickcompiler not available"); + QVERIFY2(m_qbsStdout.contains("generating loader source"), m_qbsStdout.constData()); + + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("generating loader source"), m_qbsStdout.constData()); + + WAIT_FOR_NEW_TIMESTAMP(); + touch("qml/subdir/test.qml"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("generating loader source"), m_qbsStdout.constData()); + + QCOMPARE(runQbs(QbsRunParameters(QStringList{"config:off", + "modules.Qt.quick.useCompiler:false"})), 0); + QVERIFY2(m_qbsStdout.contains("compiling"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("compiling qml_subdir_test_qml.cpp"), m_qbsStdout.constData()); +} + +void TestBlackboxQt::qtScxml() +{ + QDir::setCurrent(testDataDir + "/qtscxml"); + QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QCOMPARE(runQbs(), 0); + if (m_qbsStdout.contains("QtScxml not present")) + QSKIP("QtScxml module not present"); + QVERIFY2(m_qbsStdout.contains("state machine name: qbs_test_machine"), + m_qbsStdout.constData()); + QCOMPARE(runQbs(QbsRunParameters("resolve", + QStringList("modules.Qt.scxml.additionalCompilerFlags:--blubb"))), 0); + QbsRunParameters params; + params.expectFailure = true; + QVERIFY2(runQbs(params) != 0, m_qbsStdout.constData()); +} + +void TestBlackboxQt::removeMocHeaderFromFileList() +{ + QDir::setCurrent(testDataDir + "/remove-moc-header-from-file-list"); + QCOMPARE(runQbs(), 0); + QString projectFile("remove-moc-header-from-file-list.qbs"); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "\"file.h\"", "// \"file.h\""); + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + WAIT_FOR_NEW_TIMESTAMP(); + REPLACE_IN_FILE(projectFile, "// \"file.h\"", "\"file.h\""); + QCOMPARE(runQbs(), 0); +} + + +void TestBlackboxQt::staticQtPluginLinking() +{ + QDir::setCurrent(testDataDir + "/static-qt-plugin-linking"); + QCOMPARE(runQbs(QStringList("products.p.type:application")), 0); + const bool isStaticQt = m_qbsStdout.contains("Qt is static"); + QVERIFY2(m_qbsStdout.contains("Creating static import") == isStaticQt, m_qbsStdout.constData()); + QCOMPARE(runQbs(QbsRunParameters("resolve", QStringList("products.p.type:staticlibrary"))), 0); + QCOMPARE(runQbs(), 0); + QVERIFY2(!m_qbsStdout.contains("Creating static import"), m_qbsStdout.constData()); +} + +void TestBlackboxQt::trackAddMocInclude() +{ + QDir::setCurrent(testDataDir + "/trackAddMocInclude"); + if (QFile::exists("work")) + rmDirR("work"); + QDir().mkdir("work"); + ccp("before", "work"); + QDir::setCurrent(testDataDir + "/trackAddMocInclude/work"); + // The build must fail because the main.moc include is missing. + QbsRunParameters params; + params.expectFailure = true; + QVERIFY(runQbs(params) != 0); + + WAIT_FOR_NEW_TIMESTAMP(); + ccp("../after", "."); + touch("main.cpp"); + QCOMPARE(runQbs(), 0); +} + +void TestBlackboxQt::track_qobject_change() +{ + QDir::setCurrent(testDataDir + "/trackQObjChange"); + copyFileAndUpdateTimestamp("bla_qobject.h", "bla.h"); + QCOMPARE(runQbs(), 0); + const QString productFilePath = relativeExecutableFilePath("i"); + QVERIFY2(regularFileExists(productFilePath), qPrintable(productFilePath)); + QString moc_bla_objectFileName = relativeProductBuildDir("i") + '/' + + inputDirHash("qt.headers") + objectFileName("/moc_bla.cpp", profileName()); + QVERIFY2(regularFileExists(moc_bla_objectFileName), qPrintable(moc_bla_objectFileName)); + + WAIT_FOR_NEW_TIMESTAMP(); + copyFileAndUpdateTimestamp("bla_noqobject.h", "bla.h"); + QCOMPARE(runQbs(), 0); + QVERIFY(regularFileExists(productFilePath)); + QVERIFY(!QFile(moc_bla_objectFileName).exists()); +} + +void TestBlackboxQt::track_qrc() +{ + QDir::setCurrent(testDataDir + "/qrc"); + QCOMPARE(runQbs(QbsRunParameters("resolve")), 0); + if (m_qbsStdout.contains("targetPlatform differs from hostPlatform")) + QSKIP("Cannot run binaries in cross-compiled build"); + QCOMPARE(runQbs(QbsRunParameters("run")), 0); + QVERIFY2(m_qbsStdout.contains("rcc"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("compiling test.cpp"), m_qbsStdout.constData()); + const QString fileName = relativeExecutableFilePath("i"); + QVERIFY2(regularFileExists(fileName), qPrintable(fileName)); + + QDateTime dt = QFileInfo(fileName).lastModified(); + WAIT_FOR_NEW_TIMESTAMP(); + { + QFile f("stuff.txt"); + f.remove(); + QVERIFY(f.open(QFile::WriteOnly)); + f.write("bla"); + f.close(); + } + REPLACE_IN_FILE("i.qbs", "//\"test.cpp\"", "\"test.cpp\""); + waitForFileUnlock(); + QCOMPARE(runQbs(QbsRunParameters("run")), 0); + QVERIFY2(m_qbsStdout.contains("rcc bla.qrc"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("compiling test.cpp"), m_qbsStdout.constData()); + QVERIFY(regularFileExists(fileName)); + QVERIFY(dt < QFileInfo(fileName).lastModified()); + + WAIT_FOR_NEW_TIMESTAMP(); + touch("i.qbs"); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Resolving"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("rcc"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("compiling test.cpp"), m_qbsStdout.constData()); + + // Turn on big resources. + WAIT_FOR_NEW_TIMESTAMP(); + QCOMPARE(runQbs(QbsRunParameters("resolve", {"modules.Qt.core.enableBigResources:true"})), 0); + QCOMPARE(runQbs(QbsRunParameters("run")), 0); + QVERIFY2(m_qbsStdout.contains("rcc (pass 1) bla.qrc"), m_qbsStdout.constData()); + QVERIFY2(m_qbsStdout.contains("rcc (pass 2) bla.qrc"), m_qbsStdout.constData()); + + // Check null build. + WAIT_FOR_NEW_TIMESTAMP(); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Building"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("rcc"), m_qbsStdout.constData()); + + // Turn off big resources. + WAIT_FOR_NEW_TIMESTAMP(); + QCOMPARE(runQbs(QbsRunParameters("resolve", {"modules.Qt.core.enableBigResources:false"})), 0); + QCOMPARE(runQbs(QbsRunParameters("run")), 0); + QVERIFY2(m_qbsStdout.contains("rcc bla.qrc"), m_qbsStdout.constData()); + + // Check null build. + WAIT_FOR_NEW_TIMESTAMP(); + QCOMPARE(runQbs(), 0); + QVERIFY2(m_qbsStdout.contains("Building"), m_qbsStdout.constData()); + QVERIFY2(!m_qbsStdout.contains("rcc"), m_qbsStdout.constData()); +} + +void TestBlackboxQt::unmocable() +{ + QDir::setCurrent(testDataDir + "/unmocable"); + QCOMPARE(runQbs(), 0); + QVERIFY(!m_qbsStderr.contains("No relevant classes found. No output generated.")); +} + +QTEST_MAIN(TestBlackboxQt) diff --git a/tests/auto/blackbox/tst_blackboxqt.h b/tests/auto/blackbox/tst_blackboxqt.h new file mode 100644 index 00000000..820df26d --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxqt.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef TST_BLACKBOXQT_H +#define TST_BLACKBOXQT_H + +#include "tst_blackboxbase.h" + +class TestBlackboxQt : public TestBlackboxBase +{ + Q_OBJECT + +public: + TestBlackboxQt(); + +private slots: + void addQObjectMacroToGeneratedCppFile(); + void autoQrc(); + void cachedQml(); + void combinedMoc(); + void createProject(); + void dbusAdaptors(); + void dbusInterfaces(); + void forcedMoc(); + void includedMocCpp(); + void linkerVariant(); + void lrelease(); + void metaTypes_data(); + void metaTypes(); + void mixedBuildVariants(); + void mocAndCppCombining(); + void mocFlags(); + void mocCompilerDefines(); + void mocSameFileName(); + void pkgconfig(); + void pluginMetaData(); + void pluginSupport_data(); + void pluginSupport(); + void qmlDebugging(); + void qobjectInObjectiveCpp(); + void qmlTypeRegistrar_data(); + void qmlTypeRegistrar(); + void qtKeywords(); + void quickCompiler(); + void qtScxml(); + void removeMocHeaderFromFileList(); + void staticQtPluginLinking(); + void trackAddMocInclude(); + void track_qobject_change(); + void track_qrc(); + void unmocable(); +}; + +#endif // TST_BLACKBOXQT_H diff --git a/tests/auto/blackbox/tst_clangdb.cpp b/tests/auto/blackbox/tst_clangdb.cpp new file mode 100644 index 00000000..65e56248 --- /dev/null +++ b/tests/auto/blackbox/tst_clangdb.cpp @@ -0,0 +1,218 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Christian Gagneraud. +** 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_clangdb.h" + +#include "../shared.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +using qbs::InstallOptions; +using qbs::Internal::HostOsInfo; + +int TestClangDb::runProcess(const QString &exec, const QStringList &args, QByteArray &stdErr, + QByteArray &stdOut) +{ + QProcess process; + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(processEnvironment); + process.setProcessEnvironment(env); + + process.start(exec, args); + const int waitTime = 10 * 60000; + if (!process.waitForStarted() || !process.waitForFinished(waitTime)) { + stdErr = process.readAllStandardError(); + return -1; + } + + stdErr = process.readAllStandardError(); + stdOut = process.readAllStandardOutput(); + sanitizeOutput(&stdErr); + sanitizeOutput(&stdOut); + + if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) { + if (!stdErr.isEmpty()) + qDebug("%s", stdErr.constData()); + if (!stdOut.isEmpty()) + qDebug("%s", stdOut.constData()); + } + + return process.exitStatus() == QProcess::NormalExit ? process.exitCode() : -1; +} + +qbs::Version TestClangDb::clangVersion() +{ + QByteArray stdErr; + QByteArray stdOut; + if (runProcess("clang-check", QStringList("--version"), stdErr, stdOut) != 0) + return qbs::Version(); + stdOut.remove(0, stdOut.indexOf("LLVM version ") + 13); + stdOut.truncate(stdOut.indexOf('\n')); + return qbs::Version::fromString(QString::fromLocal8Bit(stdOut)); +} + + +TestClangDb::TestClangDb() : TestBlackboxBase(SRCDIR "/testdata-clangdb", "blackbox-clangdb"), + projectDir(QDir::cleanPath(testDataDir + "/project1")), + projectFileName("project.qbs"), + buildDir(QDir::cleanPath(projectDir + "/" + relativeBuildDir())), + sourceFilePath(QDir::cleanPath(projectDir + "/i like spaces.cpp")), + dbFilePath(QDir::cleanPath(buildDir + "/compile_commands.json")) +{ +} + +void TestClangDb::initTestCase() +{ + TestBlackboxBase::initTestCase(); + QDir::setCurrent(projectDir); +} + +void TestClangDb::ensureBuildTreeCreated() +{ + QCOMPARE(runQbs(), 0); + QVERIFY(QFile::exists(buildDir)); + + if (m_qbsStdout.contains("is msvc") || m_qbsStdout.contains("is mingw")) { + sanitizeOutput(&m_qbsStdout); + const auto lines = m_qbsStdout.split('\n'); + for (const auto &line : lines) { + static const QByteArray includeEnv = "INCLUDE="; + static const QByteArray libEnv = "LIB="; + static const QByteArray pathEnv = "PATH="; + if (line.startsWith(includeEnv)) + processEnvironment.insert("INCLUDE", line.mid(includeEnv.size())); + if (line.startsWith(libEnv)) + processEnvironment.insert("LIB", line.mid(libEnv.size())); + if (line.startsWith(pathEnv)) + processEnvironment.insert("PATH", line.mid(pathEnv.size())); + } + } +} + +void TestClangDb::checkCanGenerateDb() +{ + QbsRunParameters params; + params.command = "generate"; + params.arguments << "--generator" << "clangdb"; + QCOMPARE(runQbs(params), 0); + QVERIFY(QFile::exists(dbFilePath)); +} + +void TestClangDb::checkDbIsValidJson() +{ + QFile file(dbFilePath); + QVERIFY(file.open(QFile::ReadOnly)); + const QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + QVERIFY(!doc.isNull()); + QVERIFY(doc.isArray()); +} + +void TestClangDb::checkDbIsConsistentWithProject() +{ + QFile file(dbFilePath); + QVERIFY(file.open(QFile::ReadOnly)); + const QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + + // We expect only one command for now + const QJsonArray array = doc.array(); + QVERIFY(array.size() == 1); + + // Validate the "command object" + const QJsonObject entry = array.at(0).toObject(); + QVERIFY(entry.contains("directory")); + QVERIFY(entry.value("directory").isString()); + QVERIFY(entry.contains("arguments")); + QVERIFY(entry.value("arguments").isArray()); + QVERIFY(entry.value("arguments").toArray().size() >= 2); + QVERIFY(entry.contains("file")); + QVERIFY(entry.value("file").isString()); + QVERIFY(entry.value("file").toString() == sourceFilePath); + + // Validate the compile command itself, this requires a previous build since the command + // line contains 'deep' path that are created during Qbs build run + QByteArray stdErr; + QByteArray stdOut; + QStringList arguments; + const QJsonArray jsonArguments = entry.value("arguments").toArray(); + QString executable = jsonArguments.at(0).toString(); + for (int i=1; i/i like spaces.cpp:11:5: warning: Assigned value is garbage or undefined +// int unused = garbage; +// ^~~~~~~~~~ ~~~~~~~ +// <...>/i like spaces.cpp:11:9: warning: Value stored to 'unused' during its initialization is never read +// int unused = garbage; +// ^~~~~~ ~~~~~~~ +// 2 warnings generated. +void TestClangDb::checkClangDetectsSourceCodeProblems() +{ + QByteArray stdErr; + QByteArray stdOut; + QStringList arguments; + const QString executable = findExecutable(QStringList("clang-check")); + if (executable.isEmpty()) + QSKIP("No working clang-check executable found"); + + // Older clang versions do not support the "arguments" array in the compilation database. + // Should we really want to support them, we would have to fall back to "command" instead. + if (clangVersion() < qbs::Version(3, 7)) + QSKIP("This test requires clang-check to be based on at least LLVM 3.7.0."); + + // clang-check.exe does not understand MSVC command-line syntax + const SettingsPtr s = settings(); + qbs::Profile profile(profileName(), s.get()); + if (profileToolchain(profile).contains("msvc")) { + arguments << "-extra-arg-before=--driver-mode=cl"; + } else if (profileToolchain(profile).contains("mingw")) { + arguments << "-extra-arg-before=--driver-mode=g++"; + } + + arguments << "-analyze" << "-p" << relativeBuildDir() << sourceFilePath; + QVERIFY(runProcess(executable, arguments, stdErr, stdOut) == 0); + const QString output = QString::fromLocal8Bit(stdErr); + QVERIFY(output.contains(QRegExp(QStringLiteral("warning.*undefined"), Qt::CaseInsensitive))); + QVERIFY(output.contains(QRegExp(QStringLiteral("warning.*never read"), Qt::CaseInsensitive))); +} + +QTEST_MAIN(TestClangDb) diff --git a/tests/auto/blackbox/tst_clangdb.h b/tests/auto/blackbox/tst_clangdb.h new file mode 100644 index 00000000..1ae6db46 --- /dev/null +++ b/tests/auto/blackbox/tst_clangdb.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QBS_TST_CLANGDB_H +#define QBS_TST_CLANGDB_H + +#include "tst_blackbox.h" + +#include + +class TestClangDb : public TestBlackboxBase +{ + Q_OBJECT + +public: + TestClangDb(); + +private slots: + void initTestCase() override; + void ensureBuildTreeCreated(); + void checkCanGenerateDb(); + void checkDbIsValidJson(); + void checkDbIsConsistentWithProject(); + void checkClangDetectsSourceCodeProblems(); + +private: + int runProcess(const QString &exec, const QStringList &args, QByteArray &stdErr, + QByteArray &stdOut); + qbs::Version clangVersion(); + + const QString projectDir; + const QString projectFileName; + const QString buildDir; + const QString sourceFilePath; + const QString dbFilePath; + QProcessEnvironment processEnvironment; +}; + +#endif // Include guard. diff --git a/tests/auto/buildgraph/buildgraph.pro b/tests/auto/buildgraph/buildgraph.pro new file mode 100644 index 00000000..9230bb74 --- /dev/null +++ b/tests/auto/buildgraph/buildgraph.pro @@ -0,0 +1,13 @@ +TARGET = tst_buildgraph + +SOURCES = tst_buildgraph.cpp +HEADERS = tst_buildgraph.h + +include(../auto.pri) +include(../../../src/app/shared/logging/logging.pri) +include(../../../src/lib/bundledlibs.pri) + +qbs_use_bundled_qtscript { + CONFIG += qbs_do_not_link_bundled_qtscript + include(../../../src/lib/scriptengine/use_scriptengine.pri) +} diff --git a/tests/auto/buildgraph/buildgraph.qbs b/tests/auto/buildgraph/buildgraph.qbs new file mode 100644 index 00000000..aa3cdc3f --- /dev/null +++ b/tests/auto/buildgraph/buildgraph.qbs @@ -0,0 +1,10 @@ +import qbs + +QbsAutotest { + testName: "buildgraph" + condition: qbsbuildconfig.enableUnitTests + files: [ + "tst_buildgraph.cpp", + "tst_buildgraph.h" + ] +} diff --git a/tests/auto/buildgraph/tst_buildgraph.cpp b/tests/auto/buildgraph/tst_buildgraph.cpp new file mode 100644 index 00000000..20f2cc6a --- /dev/null +++ b/tests/auto/buildgraph/tst_buildgraph.cpp @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** 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 "tst_buildgraph.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../shared/logging/consolelogger.h" + +#include + +#include + +using namespace qbs; +using namespace qbs::Internal; + +const TopLevelProjectPtr project = TopLevelProject::create(); + +TestBuildGraph::TestBuildGraph(ILogSink *logSink) : m_logSink(logSink) +{ + project->buildData = std::make_unique(); +} + +void TestBuildGraph::initTestCase() +{ +} + +void TestBuildGraph::cleanupTestCase() +{ +} + + +bool TestBuildGraph::cycleDetected(const ResolvedProductConstPtr &product) +{ + try { + CycleDetector(Logger(m_logSink)).visitProduct(product); + return false; + } catch (const ErrorInfo &) { + return true; + } +} + +ResolvedProductConstPtr TestBuildGraph::productWithDirectCycle() +{ + const ResolvedProductPtr product = ResolvedProduct::create(); + product->project = project; + product->buildData = std::make_unique(); + const auto root = new Artifact; + root->product = product; + const auto child = new Artifact; + child->product = product; + product->buildData->addRootNode(root); + product->buildData->addNode(root); + product->buildData->addNode(child); + qbs::Internal::connect(root, child); + qbs::Internal::connect(child, root); + return product; +} + +ResolvedProductConstPtr TestBuildGraph::productWithLessDirectCycle() +{ + const ResolvedProductPtr product = ResolvedProduct::create(); + product->project = project; + product->buildData = std::make_unique(); + const auto root = new Artifact; + const auto child = new Artifact; + const auto grandchild = new Artifact; + root->product = product; + child->product = product; + grandchild->product = product; + product->buildData->addRootNode(root); + product->buildData->addNode(root); + product->buildData->addNode(child); + product->buildData->addNode(grandchild); + qbs::Internal::connect(root, child); + qbs::Internal::connect(child, grandchild); + qbs::Internal::connect(grandchild, root); + return product; +} + +// root appears as a child, but in a different tree +ResolvedProductConstPtr TestBuildGraph::productWithNoCycle() +{ + const ResolvedProductPtr product = ResolvedProduct::create(); + product->project = project; + product->buildData = std::make_unique(); + const auto root = new Artifact; + const auto root2 = new Artifact; + root->product = product; + root2->product = product; + product->buildData->addRootNode(root); + product->buildData->addRootNode(root2); + product->buildData->addNode(root); + product->buildData->addNode(root2); + qbs::Internal::connect(root2, root); + return product; +} + +void TestBuildGraph::testCycle() +{ + QVERIFY(cycleDetected(productWithDirectCycle())); + QVERIFY(cycleDetected(productWithLessDirectCycle())); + QVERIFY(!cycleDetected(productWithNoCycle())); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + TestBuildGraph tbg(ConsoleLogger::instance().logSink()); + return QTest::qExec(&tbg, argc, argv); +} diff --git a/tests/auto/buildgraph/tst_buildgraph.h b/tests/auto/buildgraph/tst_buildgraph.h new file mode 100644 index 00000000..756be2f0 --- /dev/null +++ b/tests/auto/buildgraph/tst_buildgraph.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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 TST_BUILDGRAPH_H +#define TST_BUILDGRAPH_H + +#include +#include +#include + +#include +#include + +class TestBuildGraph : public QObject +{ + Q_OBJECT +public: + TestBuildGraph(qbs::ILogSink *logSink); + +private slots: + void initTestCase(); + void cleanupTestCase(); + void testCycle(); + +private: + qbs::Internal::ResolvedProductConstPtr productWithDirectCycle(); + qbs::Internal::ResolvedProductConstPtr productWithLessDirectCycle(); + qbs::Internal::ResolvedProductConstPtr productWithNoCycle(); + bool cycleDetected(const qbs::Internal::ResolvedProductConstPtr &product); + + qbs::ILogSink * const m_logSink; +}; + +#endif // TST_BUILDGRAPH_H + diff --git a/tests/auto/cmdlineparser/cmdlineparser.pro b/tests/auto/cmdlineparser/cmdlineparser.pro new file mode 100644 index 00000000..a95676be --- /dev/null +++ b/tests/auto/cmdlineparser/cmdlineparser.pro @@ -0,0 +1,7 @@ +TARGET = tst_cmdlineparser + +SOURCES = tst_cmdlineparser.cpp ../../../src/app/qbs/qbstool.cpp + +include(../auto.pri) +include(../../../src/app/qbs/parser/parser.pri) +include(../../../src/app/shared/logging/logging.pri) diff --git a/tests/auto/cmdlineparser/cmdlineparser.qbs b/tests/auto/cmdlineparser/cmdlineparser.qbs new file mode 100644 index 00000000..a45e61f5 --- /dev/null +++ b/tests/auto/cmdlineparser/cmdlineparser.qbs @@ -0,0 +1,31 @@ +import qbs +import qbs.Utilities + +QbsAutotest { + Depends { name: "qbsversion" } + testName: "cmdlineparser" + files: ["tst_cmdlineparser.cpp", "../../../src/app/qbs/qbstool.cpp"] + cpp.defines: base.concat([ + "SRCDIR=" + Utilities.cStringQuote(path), + "QBS_VERSION=" +Utilities.cStringQuote(qbsversion.version) + ]) + + // TODO: Make parser a static library? + Group { + name: "parser" + prefix: "../../../src/app/qbs/parser/" + files: [ + "commandlineoption.cpp", + "commandlineoption.h", + "commandlineoptionpool.cpp", + "commandlineoptionpool.h", + "commandlineparser.cpp", + "commandlineparser.h", + "commandpool.cpp", + "commandpool.h", + "commandtype.h", + "parsercommand.cpp", + "parsercommand.h", + ] + } +} diff --git a/tests/auto/cmdlineparser/tst_cmdlineparser.cpp b/tests/auto/cmdlineparser/tst_cmdlineparser.cpp new file mode 100644 index 00000000..15b9ec38 --- /dev/null +++ b/tests/auto/cmdlineparser/tst_cmdlineparser.cpp @@ -0,0 +1,215 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace qbs; + +class TestCmdLineParser : public QObject +{ + Q_OBJECT +public: + TestCmdLineParser() + { + ConsoleLogger::instance().logSink()->setEnabled(false); + } + +private slots: + void initTestCase() + { + QVERIFY(m_projectFile.open()); + m_fileArgs = QStringList() << "-f" << m_projectFile.fileName(); + } + + void testValidCommandLine() + { + QStringList args; + args.push_back("-vvk"); + args.push_back("-v"); + args << "--products" << "blubb"; + args << "--changed-files" << "foo,bar" << m_fileArgs; + args << "--check-timestamps"; + args << "--check-outputs"; + CommandLineParser parser; + + QVERIFY(parser.parseCommandLine(args)); + QCOMPARE(ConsoleLogger::instance().logSink()->logLevel(), LoggerTrace); + QCOMPARE(parser.command(), BuildCommandType); + QCOMPARE(parser.products(), QStringList() << "blubb"); + QCOMPARE(parser.buildOptions(QString()).changedFiles().size(), 2); + QVERIFY(parser.buildOptions(QString()).keepGoing()); + QVERIFY(parser.forceTimestampCheck()); + QVERIFY(parser.forceOutputCheck()); + QVERIFY(!parser.logTime()); + QCOMPARE(parser.buildConfigurations().size(), 1); + + QVERIFY(parser.parseCommandLine(QStringList() << "-vvvqqq" << m_fileArgs)); + QCOMPARE(ConsoleLogger::instance().logSink()->logLevel(), defaultLogLevel()); + + QVERIFY(parser.parseCommandLine(QStringList() << "-t" << m_fileArgs)); + QVERIFY(parser.logTime()); + + // Note: We cannot just check for !parser.logTime() here, because if the test is not + // run in a terminal, "--show-progress" is ignored, in which case "--log-time" + // takes effect. + QVERIFY(parser.parseCommandLine(QStringList() << "-t" << "--show-progress" << m_fileArgs)); + QVERIFY(parser.showProgress() != parser.logTime()); + + QVERIFY(parser.parseCommandLine(QStringList() << "-vvqqq" << m_fileArgs)); + QCOMPARE(ConsoleLogger::instance().logSink()->logLevel(), LoggerWarning); + + QVERIFY(parser.parseCommandLine(QStringList() << "-vvvqq" << m_fileArgs)); + QCOMPARE(ConsoleLogger::instance().logSink()->logLevel(), LoggerDebug); + + QVERIFY(parser.parseCommandLine(QStringList() << "--log-level" << "trace" << m_fileArgs)); + QCOMPARE(ConsoleLogger::instance().logSink()->logLevel(), LoggerTrace); + + // Second "global" profile overwrites first. + QVERIFY(parser.parseCommandLine(QStringList() << "profile:a" << m_fileArgs << "profile:b")); + QCOMPARE(parser.buildConfigurations().size(), 1); + QCOMPARE(parser.buildConfigurations().front().value("qbs.profile").toString(), QLatin1String("b")); + + // Second build configuration-specific profile overwrites first. + QVERIFY(parser.parseCommandLine(QStringList(m_fileArgs) << "config:debug" << "profile:a" + << "profile:b")); + QCOMPARE(parser.buildConfigurations().size(), 1); + QCOMPARE(parser.buildConfigurations().front().value("qbs.profile").toString(), QLatin1String("b")); + + QVERIFY(parser.parseCommandLine(QStringList(m_fileArgs) << "config:a-debug" << "profile:a" + << "config:b-debug" << "profile:b")); + QCOMPARE(parser.buildConfigurations().size(), 2); + QCOMPARE(parser.buildConfigurations().front().value("qbs.configurationName").toString(), QLatin1String("a-debug")); + QCOMPARE(parser.buildConfigurations().front().value("qbs.profile").toString(), QLatin1String("a")); + QCOMPARE(parser.buildConfigurations().at(1).value("qbs.configurationName").toString(), QLatin1String("b-debug")); + QCOMPARE(parser.buildConfigurations().at(1).value("qbs.profile").toString(), QLatin1String("b")); + + // Redundant build request + QVERIFY(parser.parseCommandLine(QStringList(m_fileArgs) << "config:debug" << "profile:a" + << "config:debug" << "profile:a")); + QCOMPARE(parser.buildConfigurations().size(), 1); + + QVERIFY(parser.parseCommandLine(QStringList() << "config:debug" << "profile:a" + << "config:release" << "profile:b" << m_fileArgs)); + QCOMPARE(parser.buildConfigurations().size(), 2); + QCOMPARE(parser.buildConfigurations().front().value("qbs.configurationName").toString(), QLatin1String("debug")); + QCOMPARE(parser.buildConfigurations().front().value("qbs.profile").toString(), QLatin1String("a")); + QCOMPARE(parser.buildConfigurations().at(1).value("qbs.configurationName").toString(), QLatin1String("release")); + QCOMPARE(parser.buildConfigurations().at(1).value("qbs.profile").toString(), QLatin1String("b")); + + // Non-global property takes precedence. + QVERIFY(parser.parseCommandLine(QStringList(m_fileArgs) << "profile:a" << "config:debug" + << "profile:b")); + QCOMPARE(parser.buildConfigurations().size(), 1); + QCOMPARE(parser.buildConfigurations().front().value("qbs.configurationName").toString(), QLatin1String("debug")); + QCOMPARE(parser.buildConfigurations().front().value("qbs.profile").toString(), QLatin1String("b")); + + // Digits are always handled as option parameters. + QVERIFY(parser.parseCommandLine(QStringList(m_fileArgs) << "-j" << "123")); + QCOMPARE(parser.buildOptions(QString()).maxJobCount(), 123); + QVERIFY(parser.parseCommandLine(QStringList(m_fileArgs) << "-j123")); + QCOMPARE(parser.buildOptions(QString()).maxJobCount(), 123); + + // Argument list separation for the "run" command. + QVERIFY(parser.parseCommandLine(QStringList("run") << m_fileArgs << "config:custom" + << "-j123")); + QCOMPARE(parser.command(), RunCommandType); + QCOMPARE(parser.buildOptions(QString()).maxJobCount(), 123); + QCOMPARE(parser.buildConfigurations().front().value("qbs.configurationName").toString(), + QLatin1String("custom")); + QVERIFY(parser.runArgs().empty()); + QVERIFY(parser.parseCommandLine(QStringList("run") << m_fileArgs << "-j" << "123" << "--" + << "config:custom")); + QCOMPARE(parser.command(), RunCommandType); + QCOMPARE(parser.buildOptions(QString()).maxJobCount(), 123); + QCOMPARE(parser.buildConfigurations().front().value("qbs.configurationName").toString(), + QLatin1String("default")); + QCOMPARE(parser.runArgs(), QStringList({"config:custom"})); + + // show-version + QVERIFY(parser.parseCommandLine(QStringList("show-version"))); + QVERIFY(parser.showVersion()); + QVERIFY(parser.parseCommandLine(QStringList("--version"))); + QVERIFY(parser.showVersion()); + QVERIFY(parser.parseCommandLine(QStringList("-V"))); + QVERIFY(parser.showVersion()); + + QVERIFY(parser.parseCommandLine(QStringList{"run", "--setup-run-env-config", "x,y,z"})); + QCOMPARE(parser.runEnvConfig(), QStringList({"x", "y", "z"})); + } + + void testInvalidCommandLine() + { + QFETCH(QStringList, commandLine); + CommandLineParser parser; + QVERIFY(!parser.parseCommandLine(commandLine)); + } + + void testInvalidCommandLine_data() + { + QTest::addColumn("commandLine"); + QTest::newRow("Unknown short option") << (QStringList() << m_fileArgs << "-x"); + QTest::newRow("Unknown long option") << (QStringList() << m_fileArgs << "--xyz"); + QTest::newRow("Invalid position") << (QStringList() << m_fileArgs << "-vjv"); + QTest::newRow("Missing jobs argument") << (QStringList() << m_fileArgs << "-j"); + QTest::newRow("Missing products argument") << (QStringList() << m_fileArgs << "--products"); + QTest::newRow("Wrong argument") << (QStringList() << "-j" << "0" << m_fileArgs); + QTest::newRow("Invalid list argument") + << (QStringList() << "--changed-files" << "," << m_fileArgs); + QTest::newRow("Invalid log level") + << (QStringList() << "--log-level" << "blubb" << m_fileArgs); + QTest::newRow("Unknown numeric argument") << (QStringList() << m_fileArgs << "-123"); + QTest::newRow("Unknown parameter") << (QStringList() << m_fileArgs << "debug"); + QTest::newRow("Too many arguments") << (QStringList("help") << "build" << "clean"); + QTest::newRow("Property assignment for clean") << (QStringList("clean") << "profile:x"); + QTest::newRow("Property assignment for dump-nodes-tree") + << (QStringList("dump-nodes-tree") << "profile:x"); + QTest::newRow("Property assignment for status") << (QStringList("status") << "profile:x"); + QTest::newRow("Property assignment for update-timestamps") + << (QStringList("update-timestamps") << "profile:x"); + QTest::newRow("Argument for show-version") + << (QStringList("show-version") << "config:debug"); + } + +private: + QTemporaryFile m_projectFile; + QStringList m_fileArgs; +}; + +QTEST_MAIN(TestCmdLineParser) + +#include "tst_cmdlineparser.moc" diff --git a/tests/auto/dllexport.h b/tests/auto/dllexport.h new file mode 100644 index 00000000..44495778 --- /dev/null +++ b/tests/auto/dllexport.h @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef DLLEXPORT_H +#define DLLEXPORT_H + +#if defined(_WIN32) || defined(WIN32) +# define DLL_EXPORT __declspec(dllexport) +# define DLL_IMPORT __declspec(dllimport) +#else +# define DLL_EXPORT __attribute__((visibility("default"))) +# define DLL_IMPORT __attribute__((visibility("default"))) +# endif + +#endif // include guard diff --git a/tests/auto/language/language.pro b/tests/auto/language/language.pro new file mode 100644 index 00000000..cdb06719 --- /dev/null +++ b/tests/auto/language/language.pro @@ -0,0 +1,24 @@ +TARGET = tst_language + +SOURCES = tst_language.cpp +HEADERS = tst_language.h + +include(../auto.pri) +include(../../../src/app/shared/logging/logging.pri) +include(../../../src/lib/bundledlibs.pri) + +!qbs_use_bundled_qtscript: QT += script + +DATA_DIRS = testdata + +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 + +qbs_use_bundled_qtscript { + include(../../../src/lib/scriptengine/use_scriptengine.pri) +} diff --git a/tests/auto/language/language.qbs b/tests/auto/language/language.qbs new file mode 100644 index 00000000..f43ca0e4 --- /dev/null +++ b/tests/auto/language/language.qbs @@ -0,0 +1,30 @@ +import qbs +import qbs.Utilities + +QbsAutotest { + Depends { name: "qbsversion" } + Depends { + name: "Qt.script" + condition: !qbsbuildconfig.useBundledQtScript + required: false + } + + testName: "language" + condition: qbsbuildconfig.enableUnitTests + files: [ + "tst_language.cpp", + "tst_language.h" + ] + + cpp.defines: base.concat([ + "QBS_VERSION=" + Utilities.cStringQuote(qbsversion.version), + "SRCDIR=" + Utilities.cStringQuote(path) + ]) + + Group { + name: "testdata" + prefix: "testdata/" + files: ["**/*"] + fileTags: [] + } +} diff --git a/tests/auto/language/testdata/Banana b/tests/auto/language/testdata/Banana new file mode 100644 index 00000000..53164be8 --- /dev/null +++ b/tests/auto/language/testdata/Banana @@ -0,0 +1 @@ +Peanut butter jelly time! diff --git a/tests/auto/language/testdata/MyProperties.qbs b/tests/auto/language/testdata/MyProperties.qbs new file mode 100644 index 00000000..cca5a9c9 --- /dev/null +++ b/tests/auto/language/testdata/MyProperties.qbs @@ -0,0 +1,2 @@ +Properties { +} diff --git a/tests/auto/language/testdata/ParentWithExport.qbs b/tests/auto/language/testdata/ParentWithExport.qbs new file mode 100644 index 00000000..16f9a2cd --- /dev/null +++ b/tests/auto/language/testdata/ParentWithExport.qbs @@ -0,0 +1,6 @@ +Product { + Export { + Depends { name: "dummy" } + dummy.defines: [product.name.toUpperCase()] + } +} diff --git a/tests/auto/language/testdata/aboutdialog.cpp b/tests/auto/language/testdata/aboutdialog.cpp new file mode 100644 index 00000000..e69de29b 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 00000000..686650f4 --- /dev/null +++ b/tests/auto/language/testdata/additional-product-types.qbs @@ -0,0 +1,12 @@ +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/base-validate/base-validate.qbs b/tests/auto/language/testdata/base-validate/base-validate.qbs new file mode 100644 index 00000000..65791773 --- /dev/null +++ b/tests/auto/language/testdata/base-validate/base-validate.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "m" } +} diff --git a/tests/auto/language/testdata/base-validate/modules/m/MParent.qbs b/tests/auto/language/testdata/base-validate/modules/m/MParent.qbs new file mode 100644 index 00000000..8b147cfa --- /dev/null +++ b/tests/auto/language/testdata/base-validate/modules/m/MParent.qbs @@ -0,0 +1,4 @@ +Module { + condition: false + validate: true +} diff --git a/tests/auto/language/testdata/base-validate/modules/m/m.qbs b/tests/auto/language/testdata/base-validate/modules/m/m.qbs new file mode 100644 index 00000000..6fca37ad --- /dev/null +++ b/tests/auto/language/testdata/base-validate/modules/m/m.qbs @@ -0,0 +1,9 @@ +MParent { + condition: true + validate: { + var parentResult = base; + if (!parentResult) + throw "Parent failed"; + throw "Parent succeeded, child failed."; + } +} diff --git a/tests/auto/language/testdata/baseproperty.qbs b/tests/auto/language/testdata/baseproperty.qbs new file mode 100644 index 00000000..74024aed --- /dev/null +++ b/tests/auto/language/testdata/baseproperty.qbs @@ -0,0 +1,7 @@ +import "baseproperty_base.qbs" as BaseProduct + +BaseProduct { + name: "product1" + narf: base.concat(["boo"]) + zort: base.concat(["boo"]) +} diff --git a/tests/auto/language/testdata/baseproperty_base.qbs b/tests/auto/language/testdata/baseproperty_base.qbs new file mode 100644 index 00000000..85b64b76 --- /dev/null +++ b/tests/auto/language/testdata/baseproperty_base.qbs @@ -0,0 +1,4 @@ +Product { + property var narf + property var zort: ["bar"] +} 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 00000000..aef4eb58 --- /dev/null +++ b/tests/auto/language/testdata/broken-dependency-cycle1.qbs @@ -0,0 +1,18 @@ +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 00000000..69a6e4f8 --- /dev/null +++ b/tests/auto/language/testdata/broken-dependency-cycle2.qbs @@ -0,0 +1,18 @@ +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/buildconfigstringlistsyntax.qbs b/tests/auto/language/testdata/buildconfigstringlistsyntax.qbs new file mode 100644 index 00000000..62391931 --- /dev/null +++ b/tests/auto/language/testdata/buildconfigstringlistsyntax.qbs @@ -0,0 +1,3 @@ +Project { + property stringList someStrings +} diff --git a/tests/auto/language/testdata/builtinFunctionInSearchPathsProperty.qbs b/tests/auto/language/testdata/builtinFunctionInSearchPathsProperty.qbs new file mode 100644 index 00000000..50ff2914 --- /dev/null +++ b/tests/auto/language/testdata/builtinFunctionInSearchPathsProperty.qbs @@ -0,0 +1,8 @@ +import qbs.Environment + +Project { + qbsSearchPaths: { + if (!Environment.getEnv("PATH")) + throw "Environment.getEnv doesn't seem to work"; + } +} diff --git a/tests/auto/language/testdata/canonicalArchitecture.qbs b/tests/auto/language/testdata/canonicalArchitecture.qbs new file mode 100644 index 00000000..4f548251 --- /dev/null +++ b/tests/auto/language/testdata/canonicalArchitecture.qbs @@ -0,0 +1,5 @@ +import qbs.Utilities + +Product { + name: Utilities.canonicalArchitecture("i386") +} diff --git a/tests/auto/language/testdata/chained-probes/chained-probes.qbs b/tests/auto/language/testdata/chained-probes/chained-probes.qbs new file mode 100644 index 00000000..65791773 --- /dev/null +++ b/tests/auto/language/testdata/chained-probes/chained-probes.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "m" } +} diff --git a/tests/auto/language/testdata/chained-probes/modules/m/m.qbs b/tests/auto/language/testdata/chained-probes/modules/m/m.qbs new file mode 100644 index 00000000..3c6bcd1c --- /dev/null +++ b/tests/auto/language/testdata/chained-probes/modules/m/m.qbs @@ -0,0 +1,15 @@ +Module { + Probe { + id: probe1 + property string probe1Prop + configure: { probe1Prop = "probe1Val"; found = true; } + } + Probe { + id: probe2 + property string inputProp: prop1 + property string probe2Prop + configure: { probe2Prop = inputProp + "probe2Val"; found = true; } + } + property string prop1: probe1.probe1Prop + property string prop2: probe2.probe2Prop +} diff --git a/tests/auto/language/testdata/conditionaldepends.qbs b/tests/auto/language/testdata/conditionaldepends.qbs new file mode 100644 index 00000000..9a499da9 --- /dev/null +++ b/tests/auto/language/testdata/conditionaldepends.qbs @@ -0,0 +1,87 @@ +import "conditionaldepends_base.qbs" as CondBase + +Project { + CondBase { + name: 'conditionaldepends_derived' + someProp: true + } + + CondBase { + name: 'conditionaldepends_derived_false' + someProp: "knolf" === "narf" + } + + Product { + name: "product_props_true" + property bool someTrueProp: true + Depends { condition: someTrueProp; name: "dummy"} + } + + Product { + name: "product_props_false" + property bool someFalseProp: false + Depends { condition: someFalseProp; name: "dummy"} + } + + property bool someTruePrjProp: true + Product { + name: "project_props_true" + Depends { condition: project.someTruePrjProp; name: "dummy"} + } + + property bool someFalsePrjProp: false + Product { + name: "project_props_false" + Depends { condition: project.someFalsePrjProp; name: "dummy"} + } + + Product { + name: "module_props_true" + Depends { name: "dummy2" } + Depends { condition: dummy2.someTrueProp; name: "dummy" } + } + + Product { + name: "module_props_false" + Depends { name: "dummy2" } + Depends { condition: dummy2.someFalseProp; name: "dummy" } + } + + Product { + name: "multilevel_module_props_true" + Depends { name: "dummy3" } + dummy3.loadDummy: true + } + + Product { + name: "multilevel_module_props_false" + Depends { name: "dummy3" } + } + + 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 + } + + Product { + name: "contradictory_conditions2" + Depends { condition: true; name: "dummy" } // this one wins + Depends { condition: false; name: "dummy" } + } + + Product { + name: "unknown_dependency_condition_false" + Depends { condition: false; name: "doesonlyexistifhellfreezesover" } + } +} diff --git a/tests/auto/language/testdata/conditionaldepends_base.qbs b/tests/auto/language/testdata/conditionaldepends_base.qbs new file mode 100644 index 00000000..5ab5b973 --- /dev/null +++ b/tests/auto/language/testdata/conditionaldepends_base.qbs @@ -0,0 +1,8 @@ +Application { + name: 'conditionaldepends_base' + property bool someProp: false + Depends { + condition: someProp + name: 'dummy' + } +} diff --git a/tests/auto/language/testdata/defaultvalue/egon.qbs b/tests/auto/language/testdata/defaultvalue/egon.qbs new file mode 100644 index 00000000..6313e080 --- /dev/null +++ b/tests/auto/language/testdata/defaultvalue/egon.qbs @@ -0,0 +1,12 @@ +Project { + Product { + name: "dep" + Export { Depends { name: "higher" } } + } + + Product { + name: "egon" + Depends { name: "dep" } + lower.prop1: "blubb" + } +} diff --git a/tests/auto/language/testdata/defaultvalue/modules/higher/higher.qbs b/tests/auto/language/testdata/defaultvalue/modules/higher/higher.qbs new file mode 100644 index 00000000..6639b811 --- /dev/null +++ b/tests/auto/language/testdata/defaultvalue/modules/higher/higher.qbs @@ -0,0 +1,5 @@ +Module { + Depends { name: "lower" } + lower.prop2: lower.prop1 === "egon" ? "withEgon" : original + lower.listProp: lower.prop1 === "egon" ? ["egon"] : original +} diff --git a/tests/auto/language/testdata/defaultvalue/modules/lower/lower.qbs b/tests/auto/language/testdata/defaultvalue/modules/lower/lower.qbs new file mode 100644 index 00000000..72cb2301 --- /dev/null +++ b/tests/auto/language/testdata/defaultvalue/modules/lower/lower.qbs @@ -0,0 +1,5 @@ +Module { + property string prop1 + property string prop2: prop1 === "blubb" ? "withBlubb" : "withoutBlubb" + property stringList listProp: prop1 === "blubb" ? ["blubb", "other"] : ["other"] +} diff --git a/tests/auto/language/testdata/defaultvalue/test.txt b/tests/auto/language/testdata/defaultvalue/test.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/language/testdata/delayed-error/modules/m/m.qbs b/tests/auto/language/testdata/delayed-error/modules/m/m.qbs new file mode 100644 index 00000000..4b562806 --- /dev/null +++ b/tests/auto/language/testdata/delayed-error/modules/m/m.qbs @@ -0,0 +1,3 @@ +Module { + validate: { throw "Validation error!"; } +} diff --git a/tests/auto/language/testdata/delayed-error/nonexisting.qbs b/tests/auto/language/testdata/delayed-error/nonexisting.qbs new file mode 100644 index 00000000..4034b7b9 --- /dev/null +++ b/tests/auto/language/testdata/delayed-error/nonexisting.qbs @@ -0,0 +1,8 @@ +Project { + property bool enableProduct: true + Product { + name: "theProduct" + condition: project.enableProduct + Depends { name: "nosuchmodule" } + } +} diff --git a/tests/auto/language/testdata/delayed-error/validation.qbs b/tests/auto/language/testdata/delayed-error/validation.qbs new file mode 100644 index 00000000..ee9fe05a --- /dev/null +++ b/tests/auto/language/testdata/delayed-error/validation.qbs @@ -0,0 +1,8 @@ +Project { + property bool enableProduct: true + Product { + name: "theProduct" + condition: project.enableProduct + Depends { name: "m" } + } +} diff --git a/tests/auto/language/testdata/dependencyOnAllProfiles.qbs b/tests/auto/language/testdata/dependencyOnAllProfiles.qbs new file mode 100644 index 00000000..314a3116 --- /dev/null +++ b/tests/auto/language/testdata/dependencyOnAllProfiles.qbs @@ -0,0 +1,16 @@ +Project { + property string profile1 + property string profile2 + + Product { + name: "dep" + qbs.profiles: [project.profile1, project.profile2] + } + + Product { + name: "main" + Depends { + name: "dep" + } + } +} diff --git a/tests/auto/language/testdata/derived-sub-project/DerivedSubProject.qbs b/tests/auto/language/testdata/derived-sub-project/DerivedSubProject.qbs new file mode 100644 index 00000000..391f5614 --- /dev/null +++ b/tests/auto/language/testdata/derived-sub-project/DerivedSubProject.qbs @@ -0,0 +1,2 @@ +SubProject { +} diff --git a/tests/auto/language/testdata/derived-sub-project/project.qbs b/tests/auto/language/testdata/derived-sub-project/project.qbs new file mode 100644 index 00000000..f001b422 --- /dev/null +++ b/tests/auto/language/testdata/derived-sub-project/project.qbs @@ -0,0 +1,8 @@ +Project { + DerivedSubProject { + filePath: "subproject.qbs" + Properties { + name: "something" + } + } +} diff --git a/tests/auto/language/testdata/derived-sub-project/subproject.qbs b/tests/auto/language/testdata/derived-sub-project/subproject.qbs new file mode 100644 index 00000000..bdef20fd --- /dev/null +++ b/tests/auto/language/testdata/derived-sub-project/subproject.qbs @@ -0,0 +1,2 @@ +Product { +} diff --git a/tests/auto/language/testdata/dirwithmultipleprojects/project.qbs b/tests/auto/language/testdata/dirwithmultipleprojects/project.qbs new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/language/testdata/dirwithmultipleprojects/project2.qbs b/tests/auto/language/testdata/dirwithmultipleprojects/project2.qbs new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/language/testdata/dirwithnoprojects/.gitignore b/tests/auto/language/testdata/dirwithnoprojects/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/tests/auto/language/testdata/dirwithnoprojects/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/auto/language/testdata/dirwithoneproject/project.qbs b/tests/auto/language/testdata/dirwithoneproject/project.qbs new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/language/testdata/disabled-subproject.qbs b/tests/auto/language/testdata/disabled-subproject.qbs new file mode 100644 index 00000000..41891ef8 --- /dev/null +++ b/tests/auto/language/testdata/disabled-subproject.qbs @@ -0,0 +1,26 @@ +Project { + SubProject { + condition: false + filePath: "nosuchfile.qbs" + } + SubProject { + Properties { + condition: false + } + filePath: "nosuchfile.qbs" + } + SubProject { + condition: true + Properties { + condition: false + } + filePath: "nosuchfile.qbs" + } + SubProject { + condition: false + Properties { + condition: true + } + filePath: "nosuchfile.qbs" + } +} diff --git a/tests/auto/language/testdata/dotted-names/dotted-names.qbs b/tests/auto/language/testdata/dotted-names/dotted-names.qbs new file mode 100644 index 00000000..cf565838 --- /dev/null +++ b/tests/auto/language/testdata/dotted-names/dotted-names.qbs @@ -0,0 +1,24 @@ +import qbs + +Project { + name: "theProject" + property bool includeDottedProduct + property bool includeDottedModule + + Project { + condition: project.includeDottedProduct + Product { + name: "a.b" + Export { property string c: "default" } + } + } + + Product { + name: "p" + Depends { name: "a.b"; condition: project.includeDottedProduct } + Depends { name: "x.y"; condition: project.includeDottedModule } + a.b.c: "p" + x.y.z: "p" + } +} + diff --git a/tests/auto/language/testdata/dotted-names/modules/x/y/xy.qbs b/tests/auto/language/testdata/dotted-names/modules/x/y/xy.qbs new file mode 100644 index 00000000..71cfac9c --- /dev/null +++ b/tests/auto/language/testdata/dotted-names/modules/x/y/xy.qbs @@ -0,0 +1,5 @@ +import qbs + +Module { + property string z: "default" +} diff --git a/tests/auto/language/testdata/drawline.asm b/tests/auto/language/testdata/drawline.asm new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/language/testdata/dummy.txt b/tests/auto/language/testdata/dummy.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/language/testdata/empty-js-file.js b/tests/auto/language/testdata/empty-js-file.js new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/language/testdata/empty-js-file.qbs b/tests/auto/language/testdata/empty-js-file.qbs new file mode 100644 index 00000000..405a657c --- /dev/null +++ b/tests/auto/language/testdata/empty-js-file.qbs @@ -0,0 +1,4 @@ +import "empty-js-file.js" as Empty + +Product { +} diff --git a/tests/auto/language/testdata/enum-project-props.qbs b/tests/auto/language/testdata/enum-project-props.qbs new file mode 100644 index 00000000..8e2927ab --- /dev/null +++ b/tests/auto/language/testdata/enum-project-props.qbs @@ -0,0 +1,12 @@ +Project { + property string anExistingFile: "dummy.txt" + Product { + files: { + for (var k in project) { + if (k === "anExistingFile") + return [project[k]]; + } + return []; + } + } +} diff --git a/tests/auto/language/testdata/environmentvariable.qbs b/tests/auto/language/testdata/environmentvariable.qbs new file mode 100644 index 00000000..2e04c799 --- /dev/null +++ b/tests/auto/language/testdata/environmentvariable.qbs @@ -0,0 +1,5 @@ +import qbs.Environment + +Product { + name: Environment.getEnv("PRODUCT_NAME") +} diff --git a/tests/auto/language/testdata/erroneous/ParentItem.qbs b/tests/auto/language/testdata/erroneous/ParentItem.qbs new file mode 100644 index 00000000..38cee264 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/ParentItem.qbs @@ -0,0 +1,4 @@ +Product { + property bool cpp + readonly property string readOnlyString: "I cannot be changed!" +} diff --git a/tests/auto/language/testdata/erroneous/ParentWithExport.qbs b/tests/auto/language/testdata/erroneous/ParentWithExport.qbs new file mode 100644 index 00000000..d9ed0247 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/ParentWithExport.qbs @@ -0,0 +1,5 @@ +Product { + Export { + property bool theProp + } +} diff --git a/tests/auto/language/testdata/erroneous/ambiguous-multiplex-dependency.qbs b/tests/auto/language/testdata/erroneous/ambiguous-multiplex-dependency.qbs new file mode 100644 index 00000000..6f60b1fa --- /dev/null +++ b/tests/auto/language/testdata/erroneous/ambiguous-multiplex-dependency.qbs @@ -0,0 +1,14 @@ +Project { + Product { + name: "a" + multiplexByQbsProperties: ["architectures", "buildVariants"] + qbs.architectures: ["x86", "arm"] + qbs.buildVariants: ["debug", "release"] + } + Product { + name: "b" + Depends { name: "a" } + multiplexByQbsProperties: ["architectures"] + qbs.architectures: ["x86", "arm"] + } +} 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 00000000..62a08f02 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/conflicting-module-instances.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "conflicting-instances" } +} diff --git a/tests/auto/language/testdata/erroneous/conflicting-properties-in-export-items.qbs b/tests/auto/language/testdata/erroneous/conflicting-properties-in-export-items.qbs new file mode 100644 index 00000000..309d1b4f --- /dev/null +++ b/tests/auto/language/testdata/erroneous/conflicting-properties-in-export-items.qbs @@ -0,0 +1,5 @@ +ParentWithExport { + Export { + property string theProp + } +} diff --git a/tests/auto/language/testdata/erroneous/conflicting_fileTagsFilter.qbs b/tests/auto/language/testdata/erroneous/conflicting_fileTagsFilter.qbs new file mode 100644 index 00000000..97e11bb9 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/conflicting_fileTagsFilter.qbs @@ -0,0 +1,11 @@ +Application { + Group { + fileTagsFilter: "application" + qbs.install: true + } + Group { + fileTagsFilter: "application" + qbs.install: false + } +} + diff --git a/tests/auto/language/testdata/erroneous/dependency-profile-mismatch-2.qbs b/tests/auto/language/testdata/erroneous/dependency-profile-mismatch-2.qbs new file mode 100644 index 00000000..d76907a3 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/dependency-profile-mismatch-2.qbs @@ -0,0 +1,17 @@ +Project { + Profile { + name: "profile1" + } + Profile { + name: "profile2" + } + + Product { + name: "dep" + qbs.profiles: ["profile1", "profile2"] + } + Product { + name: "main" + Depends { name: "dep"; profiles: ["profile47"]; } + } +} diff --git a/tests/auto/language/testdata/erroneous/dependency-profile-mismatch.qbs b/tests/auto/language/testdata/erroneous/dependency-profile-mismatch.qbs new file mode 100644 index 00000000..e014fa9b --- /dev/null +++ b/tests/auto/language/testdata/erroneous/dependency-profile-mismatch.qbs @@ -0,0 +1,14 @@ +Project { + Profile { + name: "profile1" + } + + Product { + name: "dep" + qbs.profiles: ["profile1"] + } + Product { + name: "main" + Depends { name: "dep"; profiles: ["profile47"]; } + } +} diff --git a/tests/auto/language/testdata/erroneous/dependency_cycle.qbs b/tests/auto/language/testdata/erroneous/dependency_cycle.qbs new file mode 100644 index 00000000..83a6e35f --- /dev/null +++ b/tests/auto/language/testdata/erroneous/dependency_cycle.qbs @@ -0,0 +1,17 @@ +Project { + CppApplication { + name: "A" + Depends { name: "B" } + files: ["main.cpp"] + } + CppApplication { + name: "B" + Depends { name: "C" } + files: ["main.cpp"] + } + CppApplication { + name: "C" + Depends { name: "A" } + files: ["main.cpp"] + } +} diff --git a/tests/auto/language/testdata/erroneous/dependency_cycle2.qbs b/tests/auto/language/testdata/erroneous/dependency_cycle2.qbs new file mode 100644 index 00000000..33535548 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/dependency_cycle2.qbs @@ -0,0 +1,21 @@ +Project { + CppApplication { + name: "A" + Depends { name: "B" } + files: ["main.cpp"] + } + CppApplication { + name: "B" + Depends { name: "C" } + files: ["main.cpp"] + } + CppApplication { + name: "C" + Depends { name: "A" } + files: ["main.cpp"] + } + CppApplication { + name: "D" + files: ["main.cpp"] + } +} diff --git a/tests/auto/language/testdata/erroneous/dependency_cycle3.qbs b/tests/auto/language/testdata/erroneous/dependency_cycle3.qbs new file mode 100644 index 00000000..3297d5f5 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/dependency_cycle3.qbs @@ -0,0 +1,11 @@ +Project { + Product { + type: ["a"] + name: "A" + Depends { name: "B" } + } + Product { + name: "B" + Depends { productTypes: ["a"] } + } +} diff --git a/tests/auto/language/testdata/erroneous/dependency_cycle4.qbs b/tests/auto/language/testdata/erroneous/dependency_cycle4.qbs new file mode 100644 index 00000000..bd3a9feb --- /dev/null +++ b/tests/auto/language/testdata/erroneous/dependency_cycle4.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "module-a" } +} diff --git a/tests/auto/language/testdata/erroneous/duplicate-multiplex-value.qbs b/tests/auto/language/testdata/erroneous/duplicate-multiplex-value.qbs new file mode 100644 index 00000000..56da41af --- /dev/null +++ b/tests/auto/language/testdata/erroneous/duplicate-multiplex-value.qbs @@ -0,0 +1,8 @@ +import qbs + +Product { + name: "p" + multiplexByQbsProperties: "architectures" + aggregate: false + qbs.architectures: ["x86", "arm", "x86"] +} diff --git a/tests/auto/language/testdata/erroneous/duplicate-multiplex-value2.qbs b/tests/auto/language/testdata/erroneous/duplicate-multiplex-value2.qbs new file mode 100644 index 00000000..e412e521 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/duplicate-multiplex-value2.qbs @@ -0,0 +1,8 @@ +import qbs + +Product { + name: "p" + multiplexByQbsProperties: ["architectures", "buildVariants", "architectures"] + aggregate: false + qbs.architectures: ["x86", "arm"] +} diff --git a/tests/auto/language/testdata/erroneous/duplicate_sources.qbs b/tests/auto/language/testdata/erroneous/duplicate_sources.qbs new file mode 100644 index 00000000..c056dc95 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/duplicate_sources.qbs @@ -0,0 +1,6 @@ +Product { + files: ["main.cpp"] + Group { + files: ["main.cpp"] + } +} diff --git a/tests/auto/language/testdata/erroneous/duplicate_sources_wildcards.qbs b/tests/auto/language/testdata/erroneous/duplicate_sources_wildcards.qbs new file mode 100644 index 00000000..6443590b --- /dev/null +++ b/tests/auto/language/testdata/erroneous/duplicate_sources_wildcards.qbs @@ -0,0 +1,6 @@ +Product { + files: ["*.qbs"] + Group { + files: ["duplicate_sources_wildcards.qbs"] + } +} diff --git a/tests/auto/language/testdata/erroneous/importloop1.qbs b/tests/auto/language/testdata/erroneous/importloop1.qbs new file mode 100644 index 00000000..07a2487c --- /dev/null +++ b/tests/auto/language/testdata/erroneous/importloop1.qbs @@ -0,0 +1,4 @@ +import "importloop2.qbs" as X + +X {} + diff --git a/tests/auto/language/testdata/erroneous/importloop2.qbs b/tests/auto/language/testdata/erroneous/importloop2.qbs new file mode 100644 index 00000000..a49a331c --- /dev/null +++ b/tests/auto/language/testdata/erroneous/importloop2.qbs @@ -0,0 +1,4 @@ +import "importloop1.qbs" as X + +X {} + diff --git a/tests/auto/language/testdata/erroneous/invalid-parameter-rhs.qbs b/tests/auto/language/testdata/erroneous/invalid-parameter-rhs.qbs new file mode 100644 index 00000000..6389a999 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/invalid-parameter-rhs.qbs @@ -0,0 +1,5 @@ +Product { + Depends { name: "prefix2.suffix" } + Depends { name: "readonly"; prefix2.suffix.nope: access.will.fail } +} + diff --git a/tests/auto/language/testdata/erroneous/invalid-parameter-type.qbs b/tests/auto/language/testdata/erroneous/invalid-parameter-type.qbs new file mode 100644 index 00000000..72d07593 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/invalid-parameter-type.qbs @@ -0,0 +1,8 @@ +Product { + Depends { name: "module_with_parameters" } + Depends { + name: "readonly" + module_with_parameters.boolParameter: "This is not an error." + module_with_parameters.stringParameter: 123 + } +} diff --git a/tests/auto/language/testdata/erroneous/invalid-property-option.qbs b/tests/auto/language/testdata/erroneous/invalid-property-option.qbs new file mode 100644 index 00000000..9c8d592e --- /dev/null +++ b/tests/auto/language/testdata/erroneous/invalid-property-option.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "module-with-wrong-property-option" } +} diff --git a/tests/auto/language/testdata/erroneous/invalid-references.qbs b/tests/auto/language/testdata/erroneous/invalid-references.qbs new file mode 100644 index 00000000..6ea32aed --- /dev/null +++ b/tests/auto/language/testdata/erroneous/invalid-references.qbs @@ -0,0 +1,3 @@ +Project { + references: "nosuchproject.qbs" +} diff --git a/tests/auto/language/testdata/erroneous/invalid_child_item_type.qbs b/tests/auto/language/testdata/erroneous/invalid_child_item_type.qbs new file mode 100644 index 00000000..45c42666 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/invalid_child_item_type.qbs @@ -0,0 +1,4 @@ +Project { + Depends { name: "foo" } +} + diff --git a/tests/auto/language/testdata/erroneous/invalid_file.qbs b/tests/auto/language/testdata/erroneous/invalid_file.qbs new file mode 100644 index 00000000..c461b87e --- /dev/null +++ b/tests/auto/language/testdata/erroneous/invalid_file.qbs @@ -0,0 +1,3 @@ +Application { + files: ["main.cpp", "other.h"] +} diff --git a/tests/auto/language/testdata/erroneous/invalid_property_type.qbs b/tests/auto/language/testdata/erroneous/invalid_property_type.qbs new file mode 100644 index 00000000..e8907eff --- /dev/null +++ b/tests/auto/language/testdata/erroneous/invalid_property_type.qbs @@ -0,0 +1,3 @@ +Product { + property nonsense esnesnon +} diff --git a/tests/auto/language/testdata/erroneous/invalid_stringlist_element.qbs b/tests/auto/language/testdata/erroneous/invalid_stringlist_element.qbs new file mode 100644 index 00000000..fc30a2af --- /dev/null +++ b/tests/auto/language/testdata/erroneous/invalid_stringlist_element.qbs @@ -0,0 +1,3 @@ +Product { + files: ["foo", ["zoo"], "bar"] +} diff --git a/tests/auto/language/testdata/erroneous/main.cpp b/tests/auto/language/testdata/erroneous/main.cpp new file mode 100644 index 00000000..4fdd6384 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/main.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +int main() { } diff --git a/tests/auto/language/testdata/erroneous/mismatching-multiplex-dependency.qbs b/tests/auto/language/testdata/erroneous/mismatching-multiplex-dependency.qbs new file mode 100644 index 00000000..f75db396 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/mismatching-multiplex-dependency.qbs @@ -0,0 +1,13 @@ +Project { + Product { + name: "a" + multiplexByQbsProperties: ["architectures"] + qbs.architectures: ["x86", "arm"] + } + Product { + name: "b" + Depends { name: "a" } + multiplexByQbsProperties: ["architectures"] + qbs.architectures: ["mips", "ppc"] + } +} diff --git a/tests/auto/language/testdata/erroneous/missing-colon.qbs b/tests/auto/language/testdata/erroneous/missing-colon.qbs new file mode 100644 index 00000000..e62eb7cf --- /dev/null +++ b/tests/auto/language/testdata/erroneous/missing-colon.qbs @@ -0,0 +1,3 @@ +CppApplication { + cpp.dynamicLibraries { } +} diff --git a/tests/auto/language/testdata/erroneous/misused-inherited-property.qbs b/tests/auto/language/testdata/erroneous/misused-inherited-property.qbs new file mode 100644 index 00000000..2a87b66b --- /dev/null +++ b/tests/auto/language/testdata/erroneous/misused-inherited-property.qbs @@ -0,0 +1,3 @@ +ParentItem { + cpp.compilerName: "blubb" +} diff --git a/tests/auto/language/testdata/erroneous/module-depends-on-product.qbs b/tests/auto/language/testdata/erroneous/module-depends-on-product.qbs new file mode 100644 index 00000000..a7db9e03 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/module-depends-on-product.qbs @@ -0,0 +1,9 @@ +Project { + Product { + name: "p1" + Depends { name: "module-with-product-dependency" } + } + Product { + name: "p2" + } +} 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 00000000..84957060 --- /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 00000000..84957060 --- /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/erroneous/modules/module-a/module-a.qbs b/tests/auto/language/testdata/erroneous/modules/module-a/module-a.qbs new file mode 100644 index 00000000..e4ebde40 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/modules/module-a/module-a.qbs @@ -0,0 +1,3 @@ +Module { + Depends { name: "module-b" } +} diff --git a/tests/auto/language/testdata/erroneous/modules/module-b/module-b.qbs b/tests/auto/language/testdata/erroneous/modules/module-b/module-b.qbs new file mode 100644 index 00000000..482cdb13 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/modules/module-b/module-b.qbs @@ -0,0 +1,3 @@ +Module { + Depends { name: "module-a" } +} diff --git a/tests/auto/language/testdata/erroneous/modules/module-with-invalid-original/module-with-invalid-original.qbs b/tests/auto/language/testdata/erroneous/modules/module-with-invalid-original/module-with-invalid-original.qbs new file mode 100644 index 00000000..ef240441 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/modules/module-with-invalid-original/module-with-invalid-original.qbs @@ -0,0 +1,3 @@ +Module { + property string p: original +} diff --git a/tests/auto/language/testdata/erroneous/modules/module-with-product-dependency/module-with-product-dependency.qbs b/tests/auto/language/testdata/erroneous/modules/module-with-product-dependency/module-with-product-dependency.qbs new file mode 100644 index 00000000..5781bd6d --- /dev/null +++ b/tests/auto/language/testdata/erroneous/modules/module-with-product-dependency/module-with-product-dependency.qbs @@ -0,0 +1,3 @@ +Module { + Depends { name: "p2" } +} diff --git a/tests/auto/language/testdata/erroneous/modules/module-with-wrong-property-option/m.qbs b/tests/auto/language/testdata/erroneous/modules/module-with-wrong-property-option/m.qbs new file mode 100644 index 00000000..fb5c8655 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/modules/module-with-wrong-property-option/m.qbs @@ -0,0 +1,7 @@ +Module { + property string someProp + PropertyOptions { + name: "s0meProp" + description: "Oops, spelt the property name wrong" + } +} diff --git a/tests/auto/language/testdata/erroneous/modules/module_with_parameters/module_with_parameters.qbs b/tests/auto/language/testdata/erroneous/modules/module_with_parameters/module_with_parameters.qbs new file mode 100644 index 00000000..869576b0 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/modules/module_with_parameters/module_with_parameters.qbs @@ -0,0 +1,6 @@ +Module { + Parameter { property bool boolParameter } + Parameter { property int intParameter } + Parameter { property stringList stringListParameter } + Parameter { property string stringParameter } +} diff --git a/tests/auto/language/testdata/erroneous/modules/no_such_property/no-such-property.qbs b/tests/auto/language/testdata/erroneous/modules/no_such_property/no-such-property.qbs new file mode 100644 index 00000000..a9d64f17 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/modules/no_such_property/no-such-property.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "module_with_parameters" } + module_with_parameters.noSuchProperty: true +} diff --git a/tests/auto/language/testdata/erroneous/modules/prefix1/prefix1.qbs b/tests/auto/language/testdata/erroneous/modules/prefix1/prefix1.qbs new file mode 100644 index 00000000..84957060 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/modules/prefix1/prefix1.qbs @@ -0,0 +1,2 @@ +Module { +} diff --git a/tests/auto/language/testdata/erroneous/modules/prefix1/suffix/suffix.qbs b/tests/auto/language/testdata/erroneous/modules/prefix1/suffix/suffix.qbs new file mode 100644 index 00000000..218a4feb --- /dev/null +++ b/tests/auto/language/testdata/erroneous/modules/prefix1/suffix/suffix.qbs @@ -0,0 +1,3 @@ +Module { + Depends { name: "prefix1" } +} diff --git a/tests/auto/language/testdata/erroneous/modules/prefix2/prefix2.qbs b/tests/auto/language/testdata/erroneous/modules/prefix2/prefix2.qbs new file mode 100644 index 00000000..a5aaa6f8 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/modules/prefix2/prefix2.qbs @@ -0,0 +1,3 @@ +Module { + Depends { name: "prefix2.suffix" } +} diff --git a/tests/auto/language/testdata/erroneous/modules/prefix2/suffix/suffix.qbs b/tests/auto/language/testdata/erroneous/modules/prefix2/suffix/suffix.qbs new file mode 100644 index 00000000..84957060 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/modules/prefix2/suffix/suffix.qbs @@ -0,0 +1,2 @@ +Module { +} diff --git a/tests/auto/language/testdata/erroneous/modules/readonly/readonly.qbs b/tests/auto/language/testdata/erroneous/modules/readonly/readonly.qbs new file mode 100644 index 00000000..da3990db --- /dev/null +++ b/tests/auto/language/testdata/erroneous/modules/readonly/readonly.qbs @@ -0,0 +1,3 @@ +Module { + readonly property string readOnlyString: "I cannot be changed!" +} diff --git a/tests/auto/language/testdata/erroneous/multiple_exports.qbs b/tests/auto/language/testdata/erroneous/multiple_exports.qbs new file mode 100644 index 00000000..17c7f6a1 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/multiple_exports.qbs @@ -0,0 +1,4 @@ +Product { + Export {} + Export {} +} diff --git a/tests/auto/language/testdata/erroneous/multiple_properties_in_subproject.qbs b/tests/auto/language/testdata/erroneous/multiple_properties_in_subproject.qbs new file mode 100644 index 00000000..04c5fe77 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/multiple_properties_in_subproject.qbs @@ -0,0 +1,6 @@ +Project { + SubProject { + Properties { condition: false } + Properties { name: "blubb" } + } +} diff --git a/tests/auto/language/testdata/erroneous/no-configure-in-probe.qbs b/tests/auto/language/testdata/erroneous/no-configure-in-probe.qbs new file mode 100644 index 00000000..42a36c21 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/no-configure-in-probe.qbs @@ -0,0 +1,5 @@ +Product { + Probe { + id: hurz + } +} diff --git a/tests/auto/language/testdata/erroneous/nonexistentouter.qbs b/tests/auto/language/testdata/erroneous/nonexistentouter.qbs new file mode 100644 index 00000000..fb7de461 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/nonexistentouter.qbs @@ -0,0 +1,5 @@ +Project { + Product { + name: outer + } +} diff --git a/tests/auto/language/testdata/erroneous/oldQbsVersion.qbs b/tests/auto/language/testdata/erroneous/oldQbsVersion.qbs new file mode 100644 index 00000000..90658e97 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/oldQbsVersion.qbs @@ -0,0 +1,6 @@ +Project { + minimumQbsVersion: "999.5.4" + Product { + qbs.enableSound: true + } +} diff --git a/tests/auto/language/testdata/erroneous/original-in-export-item.qbs b/tests/auto/language/testdata/erroneous/original-in-export-item.qbs new file mode 100644 index 00000000..51fedb9a --- /dev/null +++ b/tests/auto/language/testdata/erroneous/original-in-export-item.qbs @@ -0,0 +1,14 @@ +import qbs + +Project { + Product { + name: "a" + Export { + property string p: original + } + } + Product { + name: "b" + Depends { name: "a" } + } +} diff --git a/tests/auto/language/testdata/erroneous/original-in-export-item2.qbs b/tests/auto/language/testdata/erroneous/original-in-export-item2.qbs new file mode 100644 index 00000000..d932d4ae --- /dev/null +++ b/tests/auto/language/testdata/erroneous/original-in-export-item2.qbs @@ -0,0 +1,14 @@ +import qbs + +Project { + Product { + name: "a" + Export { + x.y.z: original + } + } + Product { + name: "b" + Depends { name: "a" } + } +} diff --git a/tests/auto/language/testdata/erroneous/original-in-export-item3.qbs b/tests/auto/language/testdata/erroneous/original-in-export-item3.qbs new file mode 100644 index 00000000..d7bcb322 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/original-in-export-item3.qbs @@ -0,0 +1,17 @@ +import qbs + +Project { + Product { + name: "a" + Export { + Properties { + condition: true + x.y.z: original + } + } + } + Product { + name: "b" + Depends { name: "a" } + } +} diff --git a/tests/auto/language/testdata/erroneous/original-in-module-prototype.qbs b/tests/auto/language/testdata/erroneous/original-in-module-prototype.qbs new file mode 100644 index 00000000..e7128092 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/original-in-module-prototype.qbs @@ -0,0 +1,5 @@ +import qbs + +Product { + Depends { name: "module-with-invalid-original" } +} diff --git a/tests/auto/language/testdata/erroneous/original-in-product-property.qbs b/tests/auto/language/testdata/erroneous/original-in-product-property.qbs new file mode 100644 index 00000000..cbc1cbe1 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/original-in-product-property.qbs @@ -0,0 +1,3 @@ +Product { + property int n: original +} diff --git a/tests/auto/language/testdata/erroneous/overwrite-inherited-readonly-property.qbs b/tests/auto/language/testdata/erroneous/overwrite-inherited-readonly-property.qbs new file mode 100644 index 00000000..35b238a4 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/overwrite-inherited-readonly-property.qbs @@ -0,0 +1,3 @@ +ParentItem { + readOnlyString: "changing the unchangeable" +} diff --git a/tests/auto/language/testdata/erroneous/overwrite-readonly-module-property.qbs b/tests/auto/language/testdata/erroneous/overwrite-readonly-module-property.qbs new file mode 100644 index 00000000..6b400a5e --- /dev/null +++ b/tests/auto/language/testdata/erroneous/overwrite-readonly-module-property.qbs @@ -0,0 +1,4 @@ +Product { + Depends { name: "readonly" } + readonly.readOnlyString: "changing the unchangeable" +} diff --git a/tests/auto/language/testdata/erroneous/properties-item-with-invalid-condition.qbs b/tests/auto/language/testdata/erroneous/properties-item-with-invalid-condition.qbs new file mode 100644 index 00000000..5b1a41f1 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/properties-item-with-invalid-condition.qbs @@ -0,0 +1,7 @@ +Product { + Depends { name: "cpp" } + Properties { + condition: cpp.nonexistingproperty.contains("somevalue") + cpp.defines: ["ABC"] + } +} diff --git a/tests/auto/language/testdata/erroneous/references_cycle.qbs b/tests/auto/language/testdata/erroneous/references_cycle.qbs new file mode 100644 index 00000000..2dba2300 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/references_cycle.qbs @@ -0,0 +1,4 @@ +Project { + references: ["references_cycle2.qbs"] +} + diff --git a/tests/auto/language/testdata/erroneous/references_cycle2.qbs b/tests/auto/language/testdata/erroneous/references_cycle2.qbs new file mode 100644 index 00000000..20a48a10 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/references_cycle2.qbs @@ -0,0 +1,4 @@ +Project { + references: ["references_cycle3.qbs"] +} + diff --git a/tests/auto/language/testdata/erroneous/references_cycle3.qbs b/tests/auto/language/testdata/erroneous/references_cycle3.qbs new file mode 100644 index 00000000..9af06d47 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/references_cycle3.qbs @@ -0,0 +1,4 @@ +Project { + references: ["references_cycle.qbs"] +} + diff --git a/tests/auto/language/testdata/erroneous/reserved_name_in_import.qbs b/tests/auto/language/testdata/erroneous/reserved_name_in_import.qbs new file mode 100644 index 00000000..d688556a --- /dev/null +++ b/tests/auto/language/testdata/erroneous/reserved_name_in_import.qbs @@ -0,0 +1,3 @@ +import "../idusagebase.qbs" as TextFile + +Product { } diff --git a/tests/auto/language/testdata/erroneous/rule-without-output-tags.qbs b/tests/auto/language/testdata/erroneous/rule-without-output-tags.qbs new file mode 100644 index 00000000..a05800e8 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/rule-without-output-tags.qbs @@ -0,0 +1,11 @@ +Product { + Rule { + inputs: "input-tag" + prepare: { + var cmd = new JavaScriptCommand; + cmd.silent = true; + cmd.sourceCode = function() {}; + return cmd; + } + } +} diff --git a/tests/auto/language/testdata/erroneous/same-module-prefix1.qbs b/tests/auto/language/testdata/erroneous/same-module-prefix1.qbs new file mode 100644 index 00000000..8aba31c2 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/same-module-prefix1.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "prefix1.suffix" } +} diff --git a/tests/auto/language/testdata/erroneous/same-module-prefix2.qbs b/tests/auto/language/testdata/erroneous/same-module-prefix2.qbs new file mode 100644 index 00000000..6679091c --- /dev/null +++ b/tests/auto/language/testdata/erroneous/same-module-prefix2.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "prefix2" } +} diff --git a/tests/auto/language/testdata/erroneous/subproject_cycle.qbs b/tests/auto/language/testdata/erroneous/subproject_cycle.qbs new file mode 100644 index 00000000..ff1904ab --- /dev/null +++ b/tests/auto/language/testdata/erroneous/subproject_cycle.qbs @@ -0,0 +1,6 @@ +Project { + SubProject { + filePath: "subproject_cycle2.qbs" + } +} + diff --git a/tests/auto/language/testdata/erroneous/subproject_cycle2.qbs b/tests/auto/language/testdata/erroneous/subproject_cycle2.qbs new file mode 100644 index 00000000..2fe73571 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/subproject_cycle2.qbs @@ -0,0 +1,6 @@ +Project { + SubProject { + filePath: "subproject_cycle3.qbs" + } +} + diff --git a/tests/auto/language/testdata/erroneous/subproject_cycle3.qbs b/tests/auto/language/testdata/erroneous/subproject_cycle3.qbs new file mode 100644 index 00000000..28892212 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/subproject_cycle3.qbs @@ -0,0 +1,6 @@ +Project { + SubProject { + filePath: "subproject_cycle.qbs" + } +} + diff --git a/tests/auto/language/testdata/erroneous/syntax-error-in-probe.qbs b/tests/auto/language/testdata/erroneous/syntax-error-in-probe.qbs new file mode 100644 index 00000000..a1f1c74a --- /dev/null +++ b/tests/auto/language/testdata/erroneous/syntax-error-in-probe.qbs @@ -0,0 +1,6 @@ +Product { + Probe { + id: hurz + configure: { fngkgsdjfgklkf } + } +} diff --git a/tests/auto/language/testdata/erroneous/throw_in_property_binding.qbs b/tests/auto/language/testdata/erroneous/throw_in_property_binding.qbs new file mode 100644 index 00000000..88deb229 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/throw_in_property_binding.qbs @@ -0,0 +1,5 @@ +Product { + name: { + throw "something is wrong"; + } +} diff --git a/tests/auto/language/testdata/erroneous/undeclared-parameter1.qbs b/tests/auto/language/testdata/erroneous/undeclared-parameter1.qbs new file mode 100644 index 00000000..fb858ef8 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/undeclared-parameter1.qbs @@ -0,0 +1,5 @@ +Product { + Depends { name: "prefix2.suffix" } + Depends { name: "readonly"; prefix2.suffix.nope: "nope" } +} + diff --git a/tests/auto/language/testdata/erroneous/undeclared-parameter2.qbs b/tests/auto/language/testdata/erroneous/undeclared-parameter2.qbs new file mode 100644 index 00000000..6fe2ce51 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/undeclared-parameter2.qbs @@ -0,0 +1,5 @@ +Product { + name: "myproduct" + Depends { name: "readonly"; foo.bar: "bla" } +} + diff --git a/tests/auto/language/testdata/erroneous/undeclared_item.qbs b/tests/auto/language/testdata/erroneous/undeclared_item.qbs new file mode 100644 index 00000000..ab25735b --- /dev/null +++ b/tests/auto/language/testdata/erroneous/undeclared_item.qbs @@ -0,0 +1,4 @@ +Product { + cpp.defines: ["SUPERCRAZY"] +} + diff --git a/tests/auto/language/testdata/erroneous/undeclared_module_property_in_module.qbs b/tests/auto/language/testdata/erroneous/undeclared_module_property_in_module.qbs new file mode 100644 index 00000000..421302f2 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/undeclared_module_property_in_module.qbs @@ -0,0 +1,4 @@ +Product { + name: "p" + Depends { name: "no_such_property" } +} diff --git a/tests/auto/language/testdata/erroneous/undeclared_property.qbs b/tests/auto/language/testdata/erroneous/undeclared_property.qbs new file mode 100644 index 00000000..a16ab3b9 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/undeclared_property.qbs @@ -0,0 +1,4 @@ +Product { + doesntexist: 123 +} + diff --git a/tests/auto/language/testdata/erroneous/undeclared_property_in_Properties_item.qbs b/tests/auto/language/testdata/erroneous/undeclared_property_in_Properties_item.qbs new file mode 100644 index 00000000..814513b1 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/undeclared_property_in_Properties_item.qbs @@ -0,0 +1,6 @@ +Product { + Properties { + condition: true + blubb.bla: "x" + } +} diff --git a/tests/auto/language/testdata/erroneous/undeclared_property_in_export_item.qbs b/tests/auto/language/testdata/erroneous/undeclared_property_in_export_item.qbs new file mode 100644 index 00000000..badfc7bf --- /dev/null +++ b/tests/auto/language/testdata/erroneous/undeclared_property_in_export_item.qbs @@ -0,0 +1,12 @@ +Project { + Product { + name: "p1" + Export { + Depends { name: "cpp" } + cpp.blubb: "x" + } + } + Product { + Depends { name: "p1" } + } +} diff --git a/tests/auto/language/testdata/erroneous/undeclared_property_in_export_item2.qbs b/tests/auto/language/testdata/erroneous/undeclared_property_in_export_item2.qbs new file mode 100644 index 00000000..82258ec0 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/undeclared_property_in_export_item2.qbs @@ -0,0 +1,11 @@ +Project { + Product { + name: "p1" + Export { + something.other: "x" + } + } + Product { + Depends { name: "p1" } + } +} diff --git a/tests/auto/language/testdata/erroneous/undeclared_property_in_export_item3.qbs b/tests/auto/language/testdata/erroneous/undeclared_property_in_export_item3.qbs new file mode 100644 index 00000000..ddeaf01d --- /dev/null +++ b/tests/auto/language/testdata/erroneous/undeclared_property_in_export_item3.qbs @@ -0,0 +1,7 @@ +Project { + Product { + name: "p1" + Export { blubb: false } + } + Product { Depends { name: "p1" } } +} diff --git a/tests/auto/language/testdata/erroneous/undeclared_property_wrapper.qbs b/tests/auto/language/testdata/erroneous/undeclared_property_wrapper.qbs new file mode 100644 index 00000000..9ab75076 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/undeclared_property_wrapper.qbs @@ -0,0 +1,5 @@ +Project { + SubProject { + filePath: "undeclared_property.qbs" + } +} diff --git a/tests/auto/language/testdata/erroneous/undefined_stringlist_element.qbs b/tests/auto/language/testdata/erroneous/undefined_stringlist_element.qbs new file mode 100644 index 00000000..8e33cd76 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/undefined_stringlist_element.qbs @@ -0,0 +1,4 @@ +Product { + property string blubb + files: ["foo", blubb, "bar"] +} diff --git a/tests/auto/language/testdata/erroneous/undefined_stringlist_element_in_probe.qbs b/tests/auto/language/testdata/erroneous/undefined_stringlist_element_in_probe.qbs new file mode 100644 index 00000000..54d8e541 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/undefined_stringlist_element_in_probe.qbs @@ -0,0 +1,9 @@ +Product { + Probe { + id: dummy + property stringList l + configure: { + l = ["a", undefined, "b"] + } + } +} diff --git a/tests/auto/language/testdata/erroneous/unknown_item_type.qbs b/tests/auto/language/testdata/erroneous/unknown_item_type.qbs new file mode 100644 index 00000000..9e34e924 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/unknown_item_type.qbs @@ -0,0 +1,3 @@ +Narf { + zort: 1 // This invalid binding should not hide the "Unexpected item type" error. +} diff --git a/tests/auto/language/testdata/erroneous/unknown_module.qbs b/tests/auto/language/testdata/erroneous/unknown_module.qbs new file mode 100644 index 00000000..dcfc79a9 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/unknown_module.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "neitherModuleNorProduct" } +} diff --git a/tests/auto/language/testdata/erroneous/wrong-toplevel-item.qbs b/tests/auto/language/testdata/erroneous/wrong-toplevel-item.qbs new file mode 100644 index 00000000..b441c2ea --- /dev/null +++ b/tests/auto/language/testdata/erroneous/wrong-toplevel-item.qbs @@ -0,0 +1,2 @@ +Artifact { +} diff --git a/tests/auto/language/testdata/erroneous/wrongQbsVersionFormat.qbs b/tests/auto/language/testdata/erroneous/wrongQbsVersionFormat.qbs new file mode 100644 index 00000000..41b195e7 --- /dev/null +++ b/tests/auto/language/testdata/erroneous/wrongQbsVersionFormat.qbs @@ -0,0 +1,3 @@ +Project { + minimumQbsVersion: "hfyh1234wat?" +} diff --git a/tests/auto/language/testdata/error-in-disabled-product.qbs b/tests/auto/language/testdata/error-in-disabled-product.qbs new file mode 100644 index 00000000..1d13412e --- /dev/null +++ b/tests/auto/language/testdata/error-in-disabled-product.qbs @@ -0,0 +1,54 @@ +Project { + Product { + name: "a" + condition: false + property stringList l: [undefined] + } + + Product { + name: "b" + condition: false + Group { + name: { throw "boo!" } + } + } + + Product { + name: "c" + Group { + condition: false + name: { throw "boo!" } + } + } + + Project { + condition: false + + Project { + condition: true + Product { + name: "d" + condition: { throw "ouch!" } + } + } + } + + Product { + condition: false + Rule { + inputs: [5] + } + } + + Project { + condition: false + minimumQbsVersion: false + } + + Product { + name: "e" + condition: dummy.falseProperty + Depends { name: "does.not.exist" } + Depends { name: "dummy" } + } +} diff --git a/tests/auto/language/testdata/eval-error-in-non-present-module.qbs b/tests/auto/language/testdata/eval-error-in-non-present-module.qbs new file mode 100644 index 00000000..c112bc6d --- /dev/null +++ b/tests/auto/language/testdata/eval-error-in-non-present-module.qbs @@ -0,0 +1,7 @@ +import qbs + +Product { + name: "p" + property bool moduleRequired + Depends { name: "broken"; required: moduleRequired } +} diff --git a/tests/auto/language/testdata/exports.qbs b/tests/auto/language/testdata/exports.qbs new file mode 100644 index 00000000..cc86b1e4 --- /dev/null +++ b/tests/auto/language/testdata/exports.qbs @@ -0,0 +1,158 @@ +import "exports_product.qbs" as ProductWithInheritedExportItem + +Project { + Application { + name: "myapp" + Depends { name: "mylib" } + Depends { name: "dummy" } + Depends { name: "qbs" } + dummy.defines: ["BUILD_" + product.name.toUpperCase()] + dummy.includePaths: ["./app"] + } + + references: [ + "subdir/exports-mylib.qbs", + "subdir2/exports-mylib2.qbs" + ] + + Application { + name: "A" + Depends { name: "qbs" } + Depends { name: "B" } + } + StaticLibrary { + name: "B" + Export { + Depends { name: "C" } + Depends { name: "qbs" } + } + } + StaticLibrary { + name: "C" + Export { + Depends { name: "D" } + Depends { name: "qbs" } + } + } + StaticLibrary { + name: "D" + } + + Application { + name: "myapp2" + Depends { name: "productWithInheritedExportItem" } + Depends { name: "qbs" } + } + ProductWithInheritedExportItem { + name: "productWithInheritedExportItem" + Export { + dummy.cFlags: base.concat("PRODUCT_" + product.name.toUpperCase()) + dummy.cxxFlags: ["-bar"] + Properties { + condition: true + dummy.defines: base.concat(["DEF"]) + } + } + } + Application { + name: "myapp3" + Depends { name: "productWithInheritedExportItem"; versionAtLeast: "2.0" } + } + + Project { + name: "sub1" + Product { + name: "sub p1" + Export { + Depends { name: "dummy" } + dummy.someString: project.name + } + } + } + + Project { + name: "sub2" + Product { + name: "sub p2" + Depends { name: "sub p1" } + } + } + + ParentWithExport { + name: "libA" + Export { Depends { name: "libB" } } + } + + ParentWithExport { name: "libB" } + + ParentWithExport { + name: "libC" + Export { Depends { name: "libA" } } + } + + ParentWithExport { + name: "libD" + Export { Depends { name: "libA" } } + } + + Product { + name: "libE" + + Depends { name: "libD" } + Depends { name: "libC" } + + Group { + qbs.install: false + } + } + + Product { + name: "dependency" + + Probe { + id: configProbe + property var config + configure: { + var obj = {}; + obj.featureX = true; + obj.featureY = false; + obj.featureZ = true; + config = obj; + found = true; + } + } + property var config: configProbe.config + + Export { + property bool depend: false + property var config: product.config + Depends { condition: depend; name: "cpp" } + Properties { condition: depend; cpp.includePaths: ["."] } + } + } + Product { + name: "depender" + Depends { name: "dependency" } + property bool featureX: dependency.config.featureX + property bool featureY: dependency.config.featureY + property bool featureZ: dependency.config.featureZ + } + + 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/exports_product.qbs b/tests/auto/language/testdata/exports_product.qbs new file mode 100644 index 00000000..dc4c9f46 --- /dev/null +++ b/tests/auto/language/testdata/exports_product.qbs @@ -0,0 +1,9 @@ +Product { + Export { + version: "2.0" + Depends { name: "dummy" } + dummy.cFlags: ["BASE_" + product.name.toUpperCase()] + dummy.cxxFlags: ["-foo"] + dummy.defines: ["ABC"] + } +} diff --git a/tests/auto/language/testdata/file-in-product-and-module.qbs b/tests/auto/language/testdata/file-in-product-and-module.qbs new file mode 100644 index 00000000..1cb2fa86 --- /dev/null +++ b/tests/auto/language/testdata/file-in-product-and-module.qbs @@ -0,0 +1,9 @@ +Product { + name: "p" + Depends { name: "module_with_file" } + property bool addFileToProduct + Group { + files: "zort" + condition: addFileToProduct + } +} diff --git a/tests/auto/language/testdata/filecontextproperties.qbs b/tests/auto/language/testdata/filecontextproperties.qbs new file mode 100644 index 00000000..5c435b3b --- /dev/null +++ b/tests/auto/language/testdata/filecontextproperties.qbs @@ -0,0 +1,5 @@ +Product { + name: "product1" + property string narf: filePath + property string zort: path +} diff --git a/tests/auto/language/testdata/filetags.qbs b/tests/auto/language/testdata/filetags.qbs new file mode 100644 index 00000000..d055b7b1 --- /dev/null +++ b/tests/auto/language/testdata/filetags.qbs @@ -0,0 +1,79 @@ +Project { + FileTagger { + patterns: "*.cpp" + fileTags: ["cpp"] + } + + Product { + name: "filetagger_project_scope" + files: ["main.cpp"] + } + + Product { + name: "filetagger_product_scope" + files: ["drawline.asm"] + FileTagger { + patterns: "*.asm" + fileTags: ["asm"] + } + } + + Product { + name: "filetagger_static_pattern" + files: "Banana" + FileTagger { + patterns: "Banana" + fileTags: ["yellow"] + } + } + + Product { + name: "unknown_file_tag" + files: "narf.zort" + } + + Product { + name: "set_file_tag_via_group" + Group { + files: ["main.cpp"] + fileTags: ["c++"] + } + } + + Product { + name: "override_file_tag_via_group" + Group { + files: "main.cpp" // gets file tag "cpp" through the FileTagger + fileTags: ["c++"] + } + } + + Product { + name: "add_file_tag_via_group" + Group { + overrideTags: false + files: "main.cpp" + 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/getNativeSetting.qbs b/tests/auto/language/testdata/getNativeSetting.qbs new file mode 100644 index 00000000..975aefeb --- /dev/null +++ b/tests/auto/language/testdata/getNativeSetting.qbs @@ -0,0 +1,26 @@ +import qbs.FileInfo +import qbs.Utilities + +Project { + Product { + name: "p1" + targetName: { + if (qbs.hostOS.contains("macos")) { + return Utilities.getNativeSetting("/System/Library/CoreServices/SystemVersion.plist", "ProductName"); + } else if (qbs.hostOS.contains("windows")) { + var productName = Utilities.getNativeSetting("HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion", "ProductName"); + if (productName.contains("Windows")) { + return "Windows"; + } + return undefined; + } else { + return Utilities.getNativeSetting(FileInfo.joinPaths(path, "nativesettings.ini"), "osname"); + } + } + } + + Product { + name: "p2" + targetName: Utilities.getNativeSetting("/dev/null", undefined, "fallback"); + } +} diff --git a/tests/auto/language/testdata/groupconditions.qbs b/tests/auto/language/testdata/groupconditions.qbs new file mode 100644 index 00000000..b7f383d4 --- /dev/null +++ b/tests/auto/language/testdata/groupconditions.qbs @@ -0,0 +1,51 @@ +Project { + property bool someTrueProperty: true + Product { + name: "no_condition_no_group" + files: ["main.cpp"] + } + Product { + name: "no_condition" + Group { + files: ["main.cpp"] + } + } + Product { + name: "true_condition" + Group { + condition: true + files: ["main.cpp"] + } + } + Product { + name: "false_condition" + Group { + condition: false + files: ["main.cpp"] + } + } + Product { + name: "true_condition_from_product" + property bool anotherTrueProperty: true + Group { + condition: anotherTrueProperty + files: ["main.cpp"] + } + } + Product { + name: "true_condition_from_project" + Group { + condition: project.someTrueProperty + files: ["main.cpp"] + } + } + + Product { + name: "condition_accessing_module_property" + Group { + condition: qbs.targetOS.contains("narf") + files: ["main.cpp"] + qbs.install: false + } + } +} diff --git a/tests/auto/language/testdata/groupname.qbs b/tests/auto/language/testdata/groupname.qbs new file mode 100644 index 00000000..22e58765 --- /dev/null +++ b/tests/auto/language/testdata/groupname.qbs @@ -0,0 +1,20 @@ +Project { + Product { + name: "MyProduct" + Group { + name: product.name + ".MyGroup" + files: "*" + } + } + + Product { + name: "My2ndProduct" + Group { + name: product.name + ".MyGroup" + files: ["narf"] + } + Group { + files: ["zort"] + } + } +} diff --git a/tests/auto/language/testdata/homeDirectory.qbs b/tests/auto/language/testdata/homeDirectory.qbs new file mode 100644 index 00000000..f04c173b --- /dev/null +++ b/tests/auto/language/testdata/homeDirectory.qbs @@ -0,0 +1,16 @@ +Project { + Product { + name: "home" + + // These should resolve + property path home: "~" + property path homeSlash: "~/" + property path homeUp: "~/.." + property path homeFile: "~/a" + + // These are sanity checks and should not + property path bogus1: "a~b" + property path bogus2: "a/~/bb" + property path user: "~foo/bar" // we don't resolve other-user paths + } +} diff --git a/tests/auto/language/testdata/id-uniqueness.qbs b/tests/auto/language/testdata/id-uniqueness.qbs new file mode 100644 index 00000000..9f1bb701 --- /dev/null +++ b/tests/auto/language/testdata/id-uniqueness.qbs @@ -0,0 +1,11 @@ +import "idusagebase.qbs" as DerivedProduct + +Project { + id: theProject + DerivedProduct { + id: baseProduct // OK - even though 'baseProduct' is used in the base item. + } + DerivedProduct { + id: baseProduct // ERROR + } +} diff --git a/tests/auto/language/testdata/idusage.qbs b/tests/auto/language/testdata/idusage.qbs new file mode 100644 index 00000000..9a070cea --- /dev/null +++ b/tests/auto/language/testdata/idusage.qbs @@ -0,0 +1,29 @@ +import "idusagebase.qbs" as DerivedProduct + +Project { + id: theProject + property int initialNr: 0 + DerivedProduct { + id: product1 + } + Product { + id: product2 + property int nr: theProject.initialNr + product1.nr + 1 + name: "product2_" + nr + } + Product { + id: product3 + property int nr: product2.nr + 1 + name: "product3_" + nr + } + DerivedProduct { + id: product4 + nr: product3.nr + 1 + name: "product4_" + nr + } + + Product { + name: "product5" + Depends { name: "deepdummy.deep.moat" } + } +} diff --git a/tests/auto/language/testdata/idusage_group.qbs b/tests/auto/language/testdata/idusage_group.qbs new file mode 100644 index 00000000..7da7984f --- /dev/null +++ b/tests/auto/language/testdata/idusage_group.qbs @@ -0,0 +1,5 @@ +Group { + id: baseGroup + name: "base" + prefix: baseGroup.name +} diff --git a/tests/auto/language/testdata/idusage_group2.qbs b/tests/auto/language/testdata/idusage_group2.qbs new file mode 100644 index 00000000..697e21c3 --- /dev/null +++ b/tests/auto/language/testdata/idusage_group2.qbs @@ -0,0 +1,5 @@ +import "idusage_group.qbs" as MyGroup + +MyGroup { + name: "between the hammer and the anvil" +} diff --git a/tests/auto/language/testdata/idusagebase.qbs b/tests/auto/language/testdata/idusagebase.qbs new file mode 100644 index 00000000..e7424d64 --- /dev/null +++ b/tests/auto/language/testdata/idusagebase.qbs @@ -0,0 +1,16 @@ +import "idusagebasebase.qbs" as DeriveMeCrazy +import "idusage_group.qbs" as MyGroup +import "idusage_group2.qbs" as MyGroup2 + +DeriveMeCrazy { + id: baseProduct + property int nr: theProject.initialNr + 1 + name: "product1_" + nr + property string productName: baseProduct.name + MyGroup { + name: "group in base product" + } + MyGroup2 { + name: "another group in base product" + } +} diff --git a/tests/auto/language/testdata/idusagebasebase.qbs b/tests/auto/language/testdata/idusagebasebase.qbs new file mode 100644 index 00000000..addc6b9f --- /dev/null +++ b/tests/auto/language/testdata/idusagebasebase.qbs @@ -0,0 +1,5 @@ +Product { + id: baseBaseProduct + name: "ace of base" + property string productNameInBaseOfBase: baseBaseProduct.name +} diff --git a/tests/auto/language/testdata/import-collection/collection/file1.js b/tests/auto/language/testdata/import-collection/collection/file1.js new file mode 100644 index 00000000..09c5cc75 --- /dev/null +++ b/tests/auto/language/testdata/import-collection/collection/file1.js @@ -0,0 +1 @@ +function f1() { return "C2f1"; } diff --git a/tests/auto/language/testdata/import-collection/collection/file2.js b/tests/auto/language/testdata/import-collection/collection/file2.js new file mode 100644 index 00000000..ecc0f7c6 --- /dev/null +++ b/tests/auto/language/testdata/import-collection/collection/file2.js @@ -0,0 +1 @@ +function f2() { return "C2f2"; } diff --git a/tests/auto/language/testdata/import-collection/imports/Collection/file1.js b/tests/auto/language/testdata/import-collection/imports/Collection/file1.js new file mode 100644 index 00000000..d305c528 --- /dev/null +++ b/tests/auto/language/testdata/import-collection/imports/Collection/file1.js @@ -0,0 +1 @@ +function f1() { return "C1f1"; } diff --git a/tests/auto/language/testdata/import-collection/imports/Collection/file2.js b/tests/auto/language/testdata/import-collection/imports/Collection/file2.js new file mode 100644 index 00000000..b3516822 --- /dev/null +++ b/tests/auto/language/testdata/import-collection/imports/Collection/file2.js @@ -0,0 +1 @@ +function f2() { return "C1f2"; } diff --git a/tests/auto/language/testdata/import-collection/product.qbs b/tests/auto/language/testdata/import-collection/product.qbs new file mode 100644 index 00000000..0682fcfa --- /dev/null +++ b/tests/auto/language/testdata/import-collection/product.qbs @@ -0,0 +1,7 @@ +import Collection as Collection1 +import "collection" as Collection2 + +Product { + name: "da product" + targetName: Collection1.f1() + Collection1.f2() + Collection2.f1() + Collection2.f2() +} diff --git a/tests/auto/language/testdata/import-collection/project.qbs b/tests/auto/language/testdata/import-collection/project.qbs new file mode 100644 index 00000000..056cb3b4 --- /dev/null +++ b/tests/auto/language/testdata/import-collection/project.qbs @@ -0,0 +1,4 @@ +Project { + references: ["product.qbs"] +} + diff --git a/tests/auto/language/testdata/inherited-properties-items/imports/DebugName.qbs b/tests/auto/language/testdata/inherited-properties-items/imports/DebugName.qbs new file mode 100644 index 00000000..5ac15658 --- /dev/null +++ b/tests/auto/language/testdata/inherited-properties-items/imports/DebugName.qbs @@ -0,0 +1,6 @@ +import qbs + +Properties { + condition: qbs.buildVariant === "debug" + name: "product_debug" +} diff --git a/tests/auto/language/testdata/inherited-properties-items/imports/ReleaseName.qbs b/tests/auto/language/testdata/inherited-properties-items/imports/ReleaseName.qbs new file mode 100644 index 00000000..e33c5652 --- /dev/null +++ b/tests/auto/language/testdata/inherited-properties-items/imports/ReleaseName.qbs @@ -0,0 +1,6 @@ +import qbs + +Properties { + condition: qbs.buildVariant === "release" + name: "product_release" +} diff --git a/tests/auto/language/testdata/inherited-properties-items/inherited-properties-items-product.qbs b/tests/auto/language/testdata/inherited-properties-items/inherited-properties-items-product.qbs new file mode 100644 index 00000000..c20f045c --- /dev/null +++ b/tests/auto/language/testdata/inherited-properties-items/inherited-properties-items-product.qbs @@ -0,0 +1,7 @@ +import qbs + +Product { + name: "product_default" + DebugName {} + ReleaseName {} +} diff --git a/tests/auto/language/testdata/inherited-properties-items/inherited-properties-items.qbs b/tests/auto/language/testdata/inherited-properties-items/inherited-properties-items.qbs new file mode 100644 index 00000000..40d41c2f --- /dev/null +++ b/tests/auto/language/testdata/inherited-properties-items/inherited-properties-items.qbs @@ -0,0 +1,6 @@ +import qbs + +Project { + qbsSearchPaths: sourceDirectory + references: "inherited-properties-items-product.qbs" +} diff --git a/tests/auto/language/testdata/invalid-overrides.qbs b/tests/auto/language/testdata/invalid-overrides.qbs new file mode 100644 index 00000000..af113ec1 --- /dev/null +++ b/tests/auto/language/testdata/invalid-overrides.qbs @@ -0,0 +1,14 @@ +Project { + name: "My.Project" + property bool x + + Product { + name: "MyProduct" + property bool x + } + + Product { + name: "MyOtherProduct" + Depends { name: "cpp" } + } +} diff --git a/tests/auto/language/testdata/invalidBindingInDisabledItem.qbs b/tests/auto/language/testdata/invalidBindingInDisabledItem.qbs new file mode 100644 index 00000000..80895a51 --- /dev/null +++ b/tests/auto/language/testdata/invalidBindingInDisabledItem.qbs @@ -0,0 +1,14 @@ +Project { + Product { + name: "product1" + condition: false + someNonsense: "Bitte stellen Sie die Tassen auf den Tisch." + } + Product { + name: "product2" + Group { + condition: false + moreNonsense: "Follen. Follen. Hünuntergefollen. Auf dön Töppüch." + } + } +} diff --git a/tests/auto/language/testdata/jsextensions.js b/tests/auto/language/testdata/jsextensions.js new file mode 100644 index 00000000..df74a263 --- /dev/null +++ b/tests/auto/language/testdata/jsextensions.js @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2015 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. +** +****************************************************************************/ + +(function() { // Function wrapper to keep the environment clean. + +/* + * poor man's JS test suite + */ +var testctx = {}; + +function initTestContext(name) +{ + testctx.nr = 1; + testctx.name = name; +} + +function verify(c) +{ + if (!c) + throw testctx.name + ": verification #" + testctx.nr + " failed."; + testctx.nr++; +} + + +/* + * Tests for extensions of JavaScript builtin types. + */ + +var a = ["one", "two", "three"]; +initTestContext("Array.prototype.contains"); +for (var k in a) + verify(k !== "contains"); +verify(a.contains("one")); +verify(a.contains("two")); +verify(a.contains("three")); +verify(!a.contains("four")); + +})() // END function wrapper diff --git a/tests/auto/language/testdata/jsimportsinmultiplescopes.js b/tests/auto/language/testdata/jsimportsinmultiplescopes.js new file mode 100644 index 00000000..4e939505 --- /dev/null +++ b/tests/auto/language/testdata/jsimportsinmultiplescopes.js @@ -0,0 +1,12 @@ +function getName(qbsModule) +{ + if (qbsModule.debugInformation) + return "MyProduct_debug"; + else + return "MyProduct"; +} + +function getInstallDir() +{ + return "somewhere"; +} diff --git a/tests/auto/language/testdata/jsimportsinmultiplescopes.qbs b/tests/auto/language/testdata/jsimportsinmultiplescopes.qbs new file mode 100644 index 00000000..388cf974 --- /dev/null +++ b/tests/auto/language/testdata/jsimportsinmultiplescopes.qbs @@ -0,0 +1,7 @@ +import "jsimportsinmultiplescopes.js" as MyFunctions + +Product { + name: MyFunctions.getName(qbs) + qbs.installDir: MyFunctions.getInstallDir() + files: "main.cpp" +} diff --git a/tests/auto/language/testdata/main.cpp b/tests/auto/language/testdata/main.cpp new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/language/testdata/module-merging-variant-values/module-merging-variant-values.qbs b/tests/auto/language/testdata/module-merging-variant-values/module-merging-variant-values.qbs new file mode 100644 index 00000000..c633dcc4 --- /dev/null +++ b/tests/auto/language/testdata/module-merging-variant-values/module-merging-variant-values.qbs @@ -0,0 +1,5 @@ +Product { + multiplexByQbsProperties: ["architectures"] + qbs.architectures: ["a1", "a2"] + Depends { name: "m2" } +} diff --git a/tests/auto/language/testdata/module-merging-variant-values/modules/m1/m1.qbs b/tests/auto/language/testdata/module-merging-variant-values/modules/m1/m1.qbs new file mode 100644 index 00000000..6fc9c6b8 --- /dev/null +++ b/tests/auto/language/testdata/module-merging-variant-values/modules/m1/m1.qbs @@ -0,0 +1,6 @@ +Module { + condition: qbs.architecture === "a1" || qbs.architecture === "a2" + + property string arch + qbs.architecture: undefined // We do something like this in GenericGCC.qbs +} diff --git a/tests/auto/language/testdata/module-merging-variant-values/modules/m2/m2.qbs b/tests/auto/language/testdata/module-merging-variant-values/modules/m2/m2.qbs new file mode 100644 index 00000000..8361b91d --- /dev/null +++ b/tests/auto/language/testdata/module-merging-variant-values/modules/m2/m2.qbs @@ -0,0 +1,14 @@ +Module { + Depends { name: "m1" } + m1.arch: qbs.architecture + property string arch: qbs.architecture + + validate: { + if (qbs.architecture !== "a1" && qbs.architecture !== "a2") + throw "Unexpected arch " + qbs.architecture; + if (arch !== qbs.architecture) + throw "Oops: " + arch + "/" + qbs.architecture; + if (m1.arch !== qbs.architecture) + throw "Oops: " + m1.arch + "/" + qbs.architecture; + } +} diff --git a/tests/auto/language/testdata/module-prioritization-by-search-path/bar/modules/conflicting-instances/bar.qbs b/tests/auto/language/testdata/module-prioritization-by-search-path/bar/modules/conflicting-instances/bar.qbs new file mode 100644 index 00000000..4977557c --- /dev/null +++ b/tests/auto/language/testdata/module-prioritization-by-search-path/bar/modules/conflicting-instances/bar.qbs @@ -0,0 +1,3 @@ +Module { + property string moduleVariant: "bar" +} diff --git a/tests/auto/language/testdata/module-prioritization-by-search-path/foo/modules/conflicting-instances/foo.qbs b/tests/auto/language/testdata/module-prioritization-by-search-path/foo/modules/conflicting-instances/foo.qbs new file mode 100644 index 00000000..3704662b --- /dev/null +++ b/tests/auto/language/testdata/module-prioritization-by-search-path/foo/modules/conflicting-instances/foo.qbs @@ -0,0 +1,3 @@ +Module { + property string moduleVariant: "foo" +} diff --git a/tests/auto/language/testdata/module-prioritization-by-search-path/product.qbs b/tests/auto/language/testdata/module-prioritization-by-search-path/product.qbs new file mode 100644 index 00000000..62a08f02 --- /dev/null +++ b/tests/auto/language/testdata/module-prioritization-by-search-path/product.qbs @@ -0,0 +1,3 @@ +Product { + Depends { name: "conflicting-instances" } +} diff --git a/tests/auto/language/testdata/module-prioritization-by-search-path/project.qbs b/tests/auto/language/testdata/module-prioritization-by-search-path/project.qbs new file mode 100644 index 00000000..af1fe0e5 --- /dev/null +++ b/tests/auto/language/testdata/module-prioritization-by-search-path/project.qbs @@ -0,0 +1,3 @@ +Project { + references: ["product.qbs"] +} diff --git a/tests/auto/language/testdata/module-property-overrides-per-product.qbs b/tests/auto/language/testdata/module-property-overrides-per-product.qbs new file mode 100644 index 00000000..88fee111 --- /dev/null +++ b/tests/auto/language/testdata/module-property-overrides-per-product.qbs @@ -0,0 +1,17 @@ +Project { + Product { + Depends { name: "dummy" } + name: "a" + property stringList rpaths: dummy.rpaths + } + Product { + Depends { name: "dummy" } + name: "b" + property stringList rpaths: dummy.rpaths + } + Product { + Depends { name: "dummy" } + name: "c" + property stringList rpaths: dummy.rpaths + } +} diff --git a/tests/auto/language/testdata/moduleproperties.qbs b/tests/auto/language/testdata/moduleproperties.qbs new file mode 100644 index 00000000..8c9d57f1 --- /dev/null +++ b/tests/auto/language/testdata/moduleproperties.qbs @@ -0,0 +1,73 @@ +Project { + name: "MyProject" + property string projectName: name + + Product { + name: "merge_lists" + Depends { name: "dummyqt"; submodules: ["gui", "network"] } + Depends { name: "dummy" } + dummy.defines: ["THE_PRODUCT"] + } + Product { + name: "merge_lists_and_values" + Depends { name: "dummyqt"; submodules: ["network", "gui"] } + Depends { name: "dummy" } + dummy.defines: "THE_PRODUCT" + } + Product { + name: "merge_lists_with_duplicates" + Depends { name: "dummy" } + dummy.cxxFlags: ["-foo", "BAR", "-foo", "BAZ"] + } + Product { + name: "merge_lists_with_prototype_values" + Depends { name: "dummyqt"; submodules: ["gui", "network"] } + Depends { name: "dummy" } + } + + Product { + name: "list_property_that_references_product" + type: ["blubb"] + Depends { name: "dummy" } + dummy.listProp: ["x"] + } + + Product { + name: "list_property_depending_on_overridden_property" + Depends { name: "dummy" } + dummy.listProp2: ["PRODUCT_STUFF"] + dummy.controllingProp: true + } + + Product { + name: "overridden_list_property" + Depends { name: "dummy" } + Properties { + condition: true + overrideListProperties: true + dummy.listProp: ["PRODUCT_STUFF"] + } + } + + Product { + name: "shadowed-list-property" + property string productName: name + Depends { name: "dummy" } + dummy.defines: [projectName, productName] + } + + Product { + name: "shadowed-scalar-property" + property string productName: name + Depends { name: "dummy" } + dummy.someString: projectName + "_" + productName + } + Product { + name: "merged-varlist" + property string productName: name + Depends { name: "dummy" } + Depends { name: "dummyqt.core" } + dummy.controllingProp: true + dummy.varListProp: ({d: "product"}) + } +} diff --git a/tests/auto/language/testdata/modulepropertiesingroups.qbs b/tests/auto/language/testdata/modulepropertiesingroups.qbs new file mode 100644 index 00000000..e3857bdf --- /dev/null +++ b/tests/auto/language/testdata/modulepropertiesingroups.qbs @@ -0,0 +1,83 @@ +Project { + Product { + name: "grouptest" + + Depends { name: "gmod.gmod1" } + Depends { name: "gmod3" } + Depends { name: "gmod4" } + + gmod.gmod1.gmod1_list2: base.concat([name, gmod.gmod1.gmod1_string]) + gmod.gmod1.gmod1_list3: ["product"] + gmod.gmod1.p1: 1 + + Group { + name: "g1" + files: ["Banana"] + + gmod.gmod1.gmod1_string: name + gmod.gmod1.gmod1_list2: outer.concat([name]) + gmod.gmod1.p2: 2 + gmod2.prop: 1 + gmod2.commonName: "g1" + gmod3.gmod3_string: "g1_gmod3" + gmod4.gmod4_string: "g1_gmod4" + + Group { + name: "g1.1" + + gmod.gmod1.gmod1_string: name + gmod.gmod1.gmod1_list2: outer.concat([name]) + gmod.gmod1.p2: 4 + gmod2.prop: 2 + gmod2.commonName: name + gmod3.gmod3_string: "g1.1_gmod3" + gmod4.gmod4_string: "g1.1_gmod4" + } + + Group { + name: "g1.2" + + gmod.gmod1.gmod1_string: name + gmod.gmod1.gmod1_list2: outer.concat([name]) + gmod.gmod1.p2: 8 + gmod2.commonName: name + gmod3.gmod3_string: "g1.2_gmod3" + } + } + + Group { + name: "g2" + files: ["zort"] + + gmod.gmod1.gmod1_string: name + gmod.gmod1.p1: 2 + gmod.gmod1.p2: 4 + gmod2.prop: 2 + gmod3.gmod3_string: name + "_gmod3" + gmod4.gmod4_string: name + "_gmod4" + + Group { + name: "g2.1" + + Group { + name: "g2.1.1" + + gmod.gmod1.gmod1_list2: [name] + gmod.gmod1.p2: 15 + } + } + } + } + Product { + name: "grouptest2" + Depends { name: "gmod.gmod1" } + Group { + name: "g1" + gmod.gmod1.gmod1_list2: ["G1"] + Group { + name: "g1.1" + gmod.gmod1.gmod1_string: "G1.1" + } + } + } +} diff --git a/tests/auto/language/testdata/modules.qbs b/tests/auto/language/testdata/modules.qbs new file mode 100644 index 00000000..c9591631 --- /dev/null +++ b/tests/auto/language/testdata/modules.qbs @@ -0,0 +1,57 @@ +Project { + Product { + name: "no_modules" + property var foo + } + Product { + name: "qt_core" + dummyqt.core.version: "1.2.3" + property var foo: dummyqt.core.coreVersion + Depends { + name: "dummyqt.core" + } + } + Product { + name: "qt_gui" + property var foo: dummyqt.gui.guiProperty + Depends { + name: "dummyqt.gui" + } + } + Product { + name: "qt_gui_network" + property var foo: dummyqt.gui.guiProperty + ',' + dummyqt.network.networkProperty + Depends { + name: "dummyqt" + submodules: ["gui", "network"] + } + } + Product { + name: "deep_module_name" + property var foo: deepdummy.deep.moat.depth + Depends { + name: "deepdummy.deep.moat" + } + } + Product { + name: "deep_module_name_submodule_syntax1" + property var foo: deepdummy.deep.moat.depth + Depends { + name: "deepdummy.deep" + submodules: ["moat"] + } + } + Product { + name: "deep_module_name_submodule_syntax2" + property var foo: deepdummy.deep.moat.depth + Depends { + name: "deepdummy" + submodules: ["deep.moat"] + } + } + Product { + name: "dummy_twice" + Depends { name: "dummy" } + Depends { name: "dummy" } + } +} diff --git a/tests/auto/language/testdata/modules/broken/broken.qbs b/tests/auto/language/testdata/modules/broken/broken.qbs new file mode 100644 index 00000000..b960117c --- /dev/null +++ b/tests/auto/language/testdata/modules/broken/broken.qbs @@ -0,0 +1,19 @@ +import qbs + +Module { + Probe { + id: theProbe + + property stringList broken + property stringList fine + + configure: { + broken = [["x"]]; + fine = ["x"] + found = true; + } + } + + property stringList broken: theProbe.broken + property stringList fine: theProbe.fine.filter(function(incl) { return incl != "y"; }); +} diff --git a/tests/auto/language/testdata/modules/deepdummy/deep/moat/dummydeepmoat.qbs b/tests/auto/language/testdata/modules/deepdummy/deep/moat/dummydeepmoat.qbs new file mode 100644 index 00000000..d78a6a55 --- /dev/null +++ b/tests/auto/language/testdata/modules/deepdummy/deep/moat/dummydeepmoat.qbs @@ -0,0 +1,5 @@ +Module { + property string depth: "abysmal" + Depends { name: "dummy"; id: dummyId } + property string zort: dummyId.zort +} diff --git a/tests/auto/language/testdata/modules/dummy/dummy.qbs b/tests/auto/language/testdata/modules/dummy/dummy.qbs new file mode 100644 index 00000000..b1791ac9 --- /dev/null +++ b/tests/auto/language/testdata/modules/dummy/dummy.qbs @@ -0,0 +1,25 @@ +import "dummy_base.qbs" as DummyBase + +DummyBase { + condition: true + + additionalProductTypes: ["tag2"] + + property bool falseProperty: false + property stringList defines + property stringList cFlags + property stringList cxxFlags + property stringList rpaths: ["$ORIGIN"] + property string someString + property string productName: product.name + property string upperCaseProductName: productName.toUpperCase() + property string zort: "zort in dummy" + property pathList includePaths + property path somePath + property stringList listProp: product.type.contains("blubb") ? ["123"] : ["456"] + + property bool controllingProp: false + property stringList listProp2: controllingProp + ? ["DEFAULT_STUFF", "EXTRA_STUFF"] : ["DEFAULT_STUFF"] + property varList varListProp: [{a: controllingProp, b: someString}] +} diff --git a/tests/auto/language/testdata/modules/dummy/dummy_base.qbs b/tests/auto/language/testdata/modules/dummy/dummy_base.qbs new file mode 100644 index 00000000..0ecd8a1d --- /dev/null +++ b/tests/auto/language/testdata/modules/dummy/dummy_base.qbs @@ -0,0 +1,4 @@ +Module { + condition: false + property pathList includePaths +} diff --git a/tests/auto/language/testdata/modules/dummy2/dummy2.qbs b/tests/auto/language/testdata/modules/dummy2/dummy2.qbs new file mode 100644 index 00000000..9e47ee7d --- /dev/null +++ b/tests/auto/language/testdata/modules/dummy2/dummy2.qbs @@ -0,0 +1,7 @@ +Module { + additionalProductTypes: ["tag3"] + + property var defines + property var someTrueProp: true + property var someFalseProp: false +} diff --git a/tests/auto/language/testdata/modules/dummy3/dummy3.qbs b/tests/auto/language/testdata/modules/dummy3/dummy3.qbs new file mode 100644 index 00000000..c7451693 --- /dev/null +++ b/tests/auto/language/testdata/modules/dummy3/dummy3.qbs @@ -0,0 +1,4 @@ +Module { + property bool loadDummy: false + Depends { name: "dummy"; condition: loadDummy } +} 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 00000000..fc4a3d6b --- /dev/null +++ b/tests/auto/language/testdata/modules/dummy3_loader/dummy3_loader.qbs @@ -0,0 +1,4 @@ +Module { + Depends { name: "dummy3" } + dummy3.loadDummy: true +} diff --git a/tests/auto/language/testdata/modules/dummyqt/core/dummycore.qbs b/tests/auto/language/testdata/modules/dummyqt/core/dummycore.qbs new file mode 100644 index 00000000..746c7e37 --- /dev/null +++ b/tests/auto/language/testdata/modules/dummyqt/core/dummycore.qbs @@ -0,0 +1,21 @@ +Module { + id: qtcore + property int versionMajor: 5 + property int versionMinor: 0 + property int versionPatch: 0 + property string version: versionMajor.toString() + "." + versionMinor.toString() + "." + versionPatch.toString() + property string coreProperty: "coreProperty" + property string coreVersion: qtcore.version + property string zort: "zort in dummyqt.core" + + Depends { name: "dummy" } + dummy.defines: ["QT_CORE"] + dummy.rpaths: ["/opt/qt/lib"] + dummy.cFlags: [zort] + dummy.varListProp: [{c: "qtcore"}] + + Properties { + condition: true + dummy.productName: product.name + } +} diff --git a/tests/auto/language/testdata/modules/dummyqt/gui/dummygui.qbs b/tests/auto/language/testdata/modules/dummyqt/gui/dummygui.qbs new file mode 100644 index 00000000..b65d5ca1 --- /dev/null +++ b/tests/auto/language/testdata/modules/dummyqt/gui/dummygui.qbs @@ -0,0 +1,10 @@ +Module { + Depends { name: "dummyqt.core" } + property string guiProperty: "guiProperty" + property string someString: "ene mene muh" + + Depends { name: "dummy" } + dummy.defines: ["QT_GUI"] + dummy.someString: someString + dummy.zort: dummyqt.core.zort +} diff --git a/tests/auto/language/testdata/modules/dummyqt/network/dummynetwork.qbs b/tests/auto/language/testdata/modules/dummyqt/network/dummynetwork.qbs new file mode 100644 index 00000000..8a685b9a --- /dev/null +++ b/tests/auto/language/testdata/modules/dummyqt/network/dummynetwork.qbs @@ -0,0 +1,7 @@ +Module { + Depends { name: "dummyqt"; submodules: ["core"] } + property string networkProperty: "networkProperty" + + Depends { name: "dummy" } + dummy.defines: ["QT_NETWORK"] +} diff --git a/tests/auto/language/testdata/modules/gmod/gmod1/gmod1.qbs b/tests/auto/language/testdata/modules/gmod/gmod1/gmod1.qbs new file mode 100644 index 00000000..9a9c6087 --- /dev/null +++ b/tests/auto/language/testdata/modules/gmod/gmod1/gmod1.qbs @@ -0,0 +1,16 @@ +Module { + Depends { name: "gmod2" } + Depends { name: "gmod4" } + property stringList gmod1_list1: ["gmod1_list1_proto", gmod1_string] + property stringList gmod1_list2: ["gmod1_list2_proto"] + property stringList gmod1_list3: [gmod1_string] + property string gmod1_string: "gmod1_string_proto" + property string commonName: "commonName_in_gmod1" + property int depProp: gmod2.prop + property int p0: p1 + p2 + property int p1: 0 + property int p2: 0 + + gmod2.gmod2_string: gmod1_string + gmod2.gmod2_list: [gmod1_string, commonName] +} diff --git a/tests/auto/language/testdata/modules/gmod2/gmod2.qbs b/tests/auto/language/testdata/modules/gmod2/gmod2.qbs new file mode 100644 index 00000000..dbaad572 --- /dev/null +++ b/tests/auto/language/testdata/modules/gmod2/gmod2.qbs @@ -0,0 +1,6 @@ +Module { + property int prop: 0 + property string gmod2_string: "gmod2_string_proto" + property string commonName: "commonName_in_gmod2" + property stringList gmod2_list: ["gmod2_list_proto"] +} diff --git a/tests/auto/language/testdata/modules/gmod3/qmod3.qbs b/tests/auto/language/testdata/modules/gmod3/qmod3.qbs new file mode 100644 index 00000000..dfd9c8c7 --- /dev/null +++ b/tests/auto/language/testdata/modules/gmod3/qmod3.qbs @@ -0,0 +1,5 @@ +Module { + Depends { name: "gmod2" } + property string gmod3_string: "gmod3_string_proto" + gmod2.gmod2_list: [gmod3_string] +} diff --git a/tests/auto/language/testdata/modules/gmod4/gmod4.qbs b/tests/auto/language/testdata/modules/gmod4/gmod4.qbs new file mode 100644 index 00000000..47c0b751 --- /dev/null +++ b/tests/auto/language/testdata/modules/gmod4/gmod4.qbs @@ -0,0 +1,6 @@ +Module { + Depends { name: "gmod2" } + Depends { name: "gmod3" } + property string gmod4_string: "gmod4_string_proto" + gmod2.gmod2_list: [gmod4_string + "_" + gmod3.gmod3_string] +} diff --git a/tests/auto/language/testdata/modules/module-with-properties-item/module-with-properties-item.qbs b/tests/auto/language/testdata/modules/module-with-properties-item/module-with-properties-item.qbs new file mode 100644 index 00000000..3c2ab4ab --- /dev/null +++ b/tests/auto/language/testdata/modules/module-with-properties-item/module-with-properties-item.qbs @@ -0,0 +1,8 @@ +Module { + property bool boolProperty: true + property string stringProperty: "set in Module item" + Properties { + condition: boolProperty + stringProperty: "overridden in Properties item" + } +} diff --git a/tests/auto/language/testdata/modules/module_with_file/module-with-file.qbs b/tests/auto/language/testdata/modules/module_with_file/module-with-file.qbs new file mode 100644 index 00000000..ded81e04 --- /dev/null +++ b/tests/auto/language/testdata/modules/module_with_file/module-with-file.qbs @@ -0,0 +1,14 @@ +Module { + property bool file1IsTarget + property bool file2IsTarget + Group { + prefix: product.sourceDirectory + '/' + files: "zort" + filesAreTargets: product.module_with_file.file1IsTarget + } + Group { + prefix: product.sourceDirectory + '/' + files: "zort" + filesAreTargets: product.module_with_file.file2IsTarget + } +} diff --git a/tests/auto/language/testdata/modules/multiple_backends/backend1.qbs b/tests/auto/language/testdata/modules/multiple_backends/backend1.qbs new file mode 100644 index 00000000..794c6782 --- /dev/null +++ b/tests/auto/language/testdata/modules/multiple_backends/backend1.qbs @@ -0,0 +1,4 @@ +Module { + condition: qbs.targetOS.contains("os1") + property string prop: "backend 1" +} diff --git a/tests/auto/language/testdata/modules/multiple_backends/backend2.qbs b/tests/auto/language/testdata/modules/multiple_backends/backend2.qbs new file mode 100644 index 00000000..2073c4d0 --- /dev/null +++ b/tests/auto/language/testdata/modules/multiple_backends/backend2.qbs @@ -0,0 +1,5 @@ +Module { + condition: qbs.targetOS.contains("os2") + property string prop: "backend 2" + property string backend2Prop +} diff --git a/tests/auto/language/testdata/modules/multiple_backends/backend3.qbs b/tests/auto/language/testdata/modules/multiple_backends/backend3.qbs new file mode 100644 index 00000000..16228108 --- /dev/null +++ b/tests/auto/language/testdata/modules/multiple_backends/backend3.qbs @@ -0,0 +1,5 @@ +Module { + condition: qbs.targetOS.contains("os2") && qbs.toolchain.contains("tc") + priority: 1 + property string backend3Prop +} diff --git a/tests/auto/language/testdata/modules/scopemod/scopemod.qbs b/tests/auto/language/testdata/modules/scopemod/scopemod.qbs new file mode 100644 index 00000000..a2c611dd --- /dev/null +++ b/tests/auto/language/testdata/modules/scopemod/scopemod.qbs @@ -0,0 +1,10 @@ +Module { + property int a: 1 + property int b: 1 + property int c: a + 1 + property int d: b + 1 + property int e: 1 + property int f: 1 + property int g: 1 + property int h: 1 +} diff --git a/tests/auto/language/testdata/modulescope.qbs b/tests/auto/language/testdata/modulescope.qbs new file mode 100644 index 00000000..c3c9db5d --- /dev/null +++ b/tests/auto/language/testdata/modulescope.qbs @@ -0,0 +1,13 @@ +import "modulescope_base.qbs" as MyProduct + +Project { + MyProduct { + name: "product1" + property int e: 12 + property int f: 13 + scopemod.a: 2 + scopemod.f: 2 + scopemod.g: e * f + scopemod.h: base + 2 + } +} diff --git a/tests/auto/language/testdata/modulescope_base.qbs b/tests/auto/language/testdata/modulescope_base.qbs new file mode 100644 index 00000000..85db4ec1 --- /dev/null +++ b/tests/auto/language/testdata/modulescope_base.qbs @@ -0,0 +1,4 @@ +Product { + Depends { name: "scopemod" } + scopemod.h: e * f +} diff --git a/tests/auto/language/testdata/multiplexed-exports.qbs b/tests/auto/language/testdata/multiplexed-exports.qbs new file mode 100644 index 00000000..f98ceff9 --- /dev/null +++ b/tests/auto/language/testdata/multiplexed-exports.qbs @@ -0,0 +1,18 @@ +Project { + Product { + name: "dep" + multiplexByQbsProperties: ["buildVariants"] + qbs.buildVariants: ["debug", "release"] + property string includeDir: qbs.buildVariant === "debug" ? "/d" : "/r" + Export { + Depends { name: "cpp" } + cpp.includePaths: product.includeDir + } + } + Product { + name: "p" + Depends { name: "dep" } + multiplexByQbsProperties: ["buildVariants"] + qbs.buildVariants: ["debug", "release"] + } +} 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 00000000..b7abd942 --- /dev/null +++ b/tests/auto/language/testdata/multiplexing-by-profile/p1.qbs @@ -0,0 +1,15 @@ +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 00000000..94a0e476 --- /dev/null +++ b/tests/auto/language/testdata/multiplexing-by-profile/p2.qbs @@ -0,0 +1,19 @@ +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 00000000..49f99882 --- /dev/null +++ b/tests/auto/language/testdata/multiplexing-by-profile/p3.qbs @@ -0,0 +1,27 @@ +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 00000000..c71b78b9 --- /dev/null +++ b/tests/auto/language/testdata/multiplexing-by-profile/p4.qbs @@ -0,0 +1,19 @@ +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/testdata/narf b/tests/auto/language/testdata/narf new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/language/testdata/narf.zort b/tests/auto/language/testdata/narf.zort new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/language/testdata/nativesettings.ini b/tests/auto/language/testdata/nativesettings.ini new file mode 100644 index 00000000..4caf32e5 --- /dev/null +++ b/tests/auto/language/testdata/nativesettings.ini @@ -0,0 +1 @@ +osname = Unix diff --git a/tests/auto/language/testdata/non-applicable-module-property-in-profile.qbs b/tests/auto/language/testdata/non-applicable-module-property-in-profile.qbs new file mode 100644 index 00000000..ebcefbb5 --- /dev/null +++ b/tests/auto/language/testdata/non-applicable-module-property-in-profile.qbs @@ -0,0 +1,16 @@ +Project { + property string targetOS + property string toolchain + Product { + name: "p" + multiplexByQbsProperties: ["profiles"] + qbs.profiles: ["theProfile"] + Depends { name: "multiple_backends" } + Profile { + name: "theProfile" + qbs.targetOS: [project.targetOS] + qbs.toolchain: [project.toolchain] + multiple_backends.backend3Prop: "value" + } + } +} diff --git a/tests/auto/language/testdata/non-required-products.qbs b/tests/auto/language/testdata/non-required-products.qbs new file mode 100644 index 00000000..6fc2cfe1 --- /dev/null +++ b/tests/auto/language/testdata/non-required-products.qbs @@ -0,0 +1,33 @@ +Project { + Product { + name: "depender" + Depends { name: "dummy" } + Depends { name: "dependee"; required: false } + Properties { + condition: dependee.present + dummy.defines: ["WITH_DEPENDEE"] + } + } + Project { + name: "subproject" + Product { + name: "dependee" + } + } + + Product { + name: "p1" + condition: p2.present + Depends { name: "p2"; required: false } + } + Product { + name: "p2" + condition: p3.present + Depends { name: "p3"; required: false } + } + Product { + name: "p3" + condition: nosuchmodule.present + Depends { name: "nosuchmodule"; required: false } + } +} diff --git a/tests/auto/language/testdata/outerInGroup.qbs b/tests/auto/language/testdata/outerInGroup.qbs new file mode 100644 index 00000000..21136225 --- /dev/null +++ b/tests/auto/language/testdata/outerInGroup.qbs @@ -0,0 +1,12 @@ +Project { + Product { + name: "OuterInGroup" + qbs.installDir: "/somewhere" + files: ["main.cpp"] + Group { + name: "Special Group" + files: ["aboutdialog.cpp"] + qbs.installDir: outer + "/else" + } + } +} diff --git a/tests/auto/language/testdata/overridden-properties-and-prototypes.qbs b/tests/auto/language/testdata/overridden-properties-and-prototypes.qbs new file mode 100644 index 00000000..0f6a1f81 --- /dev/null +++ b/tests/auto/language/testdata/overridden-properties-and-prototypes.qbs @@ -0,0 +1,4 @@ +Product { + name: "p" + Depends { name: "multiple_backends" } +} diff --git a/tests/auto/language/testdata/overridden-variant-property.qbs b/tests/auto/language/testdata/overridden-variant-property.qbs new file mode 100644 index 00000000..09636d33 --- /dev/null +++ b/tests/auto/language/testdata/overridden-variant-property.qbs @@ -0,0 +1,4 @@ +Product { + name: "p" + property var myObject +} diff --git a/tests/auto/language/testdata/parameter-types.qbs b/tests/auto/language/testdata/parameter-types.qbs new file mode 100644 index 00000000..16415179 --- /dev/null +++ b/tests/auto/language/testdata/parameter-types.qbs @@ -0,0 +1,17 @@ +Project { + Product { + name: "foo" + qbsSearchPaths: "./erroneous" + Depends { name: "module_with_parameters" } + Depends { + name: "bar" + module_with_parameters.boolParameter: true + module_with_parameters.intParameter: 156 + module_with_parameters.stringParameter: "hello" + module_with_parameters.stringListParameter: ["la", "le", "lu"] + } + } + Product { + name: "bar" + } +} diff --git a/tests/auto/language/testdata/pathproperties.qbs b/tests/auto/language/testdata/pathproperties.qbs new file mode 100644 index 00000000..f0eeabf5 --- /dev/null +++ b/tests/auto/language/testdata/pathproperties.qbs @@ -0,0 +1,9 @@ +import "subdir/pathproperties_base.qbs" as ProductBase + +ProductBase { + name: "product1" + property path projectFileDir: "." + property pathList filesInProjectFileDir: ["./aboutdialog.h", "aboutdialog.cpp"] + Depends { name: "dummy" } + dummy.includePaths: ["."] +} diff --git a/tests/auto/language/testdata/productconditions.qbs b/tests/auto/language/testdata/productconditions.qbs new file mode 100644 index 00000000..bc367113 --- /dev/null +++ b/tests/auto/language/testdata/productconditions.qbs @@ -0,0 +1,35 @@ +import qbs.Probes + +Project { + Product { + name: "product_no_condition" + } + Product { + name: "product_true_condition" + condition: 1 === 1 + } + Product { + name: "product_false_condition" + condition: 1 === 2 + } + Product { + name: "product_condition_dependent_of_module" + condition: qbs.architecture !== (qbs.architecture + "foo") + } + Product { + name: "product_probe_condition_true" + condition: trueProbe.found + Probe { + id: trueProbe + configure: { found = true; } + } + } + Product { + name: "product_probe_condition_false" + condition: falseProbe.found + Probe { + id: falseProbe + configure: { found = false; } + } + } +} diff --git a/tests/auto/language/testdata/productdirectories.qbs b/tests/auto/language/testdata/productdirectories.qbs new file mode 100644 index 00000000..ec24352a --- /dev/null +++ b/tests/auto/language/testdata/productdirectories.qbs @@ -0,0 +1,3 @@ +Product { + name: "MyApp" +} diff --git a/tests/auto/language/testdata/profilevaluesandoverriddenvalues.qbs b/tests/auto/language/testdata/profilevaluesandoverriddenvalues.qbs new file mode 100644 index 00000000..3aaa0cfa --- /dev/null +++ b/tests/auto/language/testdata/profilevaluesandoverriddenvalues.qbs @@ -0,0 +1,19 @@ +Project { + Application { + name: "product1" + property bool dummyProp: { + if (!(dummy.cFlags instanceof Array)) + throw new Error("dummy.cFlags: Array type expected."); + if (!(dummy.cxxFlags instanceof Array)) + throw new Error("dummy.cxxFlags: Array type expected."); + if (!(dummy.defines instanceof Array)) + throw new Error("dummy.defines: Array type expected."); + return true; + } + consoleApplication: true + Depends { name: "dummy" } + // dummy.cxxFlags is set via profile and is not overridden + dummy.defines: ["IN_FILE"] // set in profile, overridden in file + dummy.cFlags: ["IN_FILE"] // set in profile, overridden on command line + } +} diff --git a/tests/auto/language/testdata/properties-block-in-group.qbs b/tests/auto/language/testdata/properties-block-in-group.qbs new file mode 100644 index 00000000..b77de7c6 --- /dev/null +++ b/tests/auto/language/testdata/properties-block-in-group.qbs @@ -0,0 +1,14 @@ +Product { + name: "in-group" + property bool featureEnabled: true + Depends { name: "dummy" } + dummy.defines: ["BASEDEF"] + Group { + name: "the group" + files: ["dummy.txt" ] + Properties { + condition: featureEnabled + dummy.defines: outer.concat("FEATURE_ENABLED") + } + } +} diff --git a/tests/auto/language/testdata/properties-item-in-module.qbs b/tests/auto/language/testdata/properties-item-in-module.qbs new file mode 100644 index 00000000..60f3afea --- /dev/null +++ b/tests/auto/language/testdata/properties-item-in-module.qbs @@ -0,0 +1,4 @@ +Project { + Product { name: "a"; Depends { name: "dummyqt.core" } } + Product { name: "b"; Depends { name: "dummyqt.core" } } +} diff --git a/tests/auto/language/testdata/propertiesblocks.qbs b/tests/auto/language/testdata/propertiesblocks.qbs new file mode 100644 index 00000000..34776cfa --- /dev/null +++ b/tests/auto/language/testdata/propertiesblocks.qbs @@ -0,0 +1,235 @@ +import "propertiesblocks_base.qbs" as ProductBase + +Project { + Product { + name: "property_overwrite" + Depends { name: "dummy" } + dummy.defines: ["SOMETHING"] + Properties { + condition: true + dummy.defines: ["OVERWRITTEN"] + } + } + Product { + name: "property_set_indirect" + Depends { name: "dummyqt.core" } + Properties { + condition: true + dummyqt.core.zort: "VAL" + } + } + Product { + name: "property_overwrite_no_outer" + Depends { name: "dummy" } + Properties { + condition: true + dummy.defines: ["OVERWRITTEN"] + } + } + Product { + name: "property_append_to_outer" + Depends { name: "dummy" } + dummy.defines: ["ONE"] + Properties { + condition: true + dummy.defines: outer.concat(["TWO"]) + } + } + Product { + name: "property_append_to_indirect_outer" + Depends { name: "dummy" } + property stringList myDefines: ["ONE"] + dummy.defines: myDefines + Properties { + condition: true + dummy.defines: outer.concat(["TWO"]) + } + } + ProductBase { + name: "property_append_to_indirect_derived_outer1" + Properties { + condition: true + dummy.cFlags: outer.concat("PROPS") + } + } + ProductBase { + name: "property_append_to_indirect_derived_outer2" + Properties { + condition: true + dummy.cFlags: outer.concat("PROPS") + } + dummy.cFlags: ["PRODUCT"] + } + ProductBase { + name: "property_append_to_indirect_derived_outer3" + Properties { + condition: true + dummy.cFlags: outer.concat("PROPS") + } + dummy.cFlags: base.concat("PRODUCT") + } + Product { + name: "property_append_to_indirect_merged_outer" + Depends { name: "dummy" } + property string justOne: "ONE" + dummy.rpaths: [justOne] + Properties { + condition: true + dummy.rpaths: outer.concat(["TWO"]) + } + } + Product { + name: "multiple_exclusive_properties" + Depends { name: "dummy" } + dummy.defines: ["SOMETHING"] + Properties { + condition: true + dummy.defines: ["OVERWRITTEN"] + } + Properties { + condition: false + dummy.defines: ["IMPOSSIBLE"] + } + } + Product { + name: "multiple_exclusive_properties_no_outer" + Depends { name: "dummy" } + Properties { + condition: true + dummy.defines: ["OVERWRITTEN"] + } + Properties { + condition: false + dummy.defines: ["IMPOSSIBLE"] + } + } + Product { + name: "multiple_exclusive_properties_append_to_outer" + Depends { name: "dummy" } + dummy.defines: ["ONE"] + Properties { + condition: true + dummy.defines: outer.concat(["TWO"]) + } + Properties { + condition: false + dummy.defines: ["IMPOSSIBLE"] + } + } + Product { + name: "ambiguous_properties" + Depends { name: "dummy" } + dummy.defines: ["ONE"] + Properties { + condition: true + dummy.defines: outer.concat(["TWO"]) + } + Properties { + condition: false + dummy.defines: outer.concat(["IMPOSSIBLE"]) + } + Properties { // will be ignored + condition: true + dummy.defines: outer.concat(["THREE"]) + } + } + Product { + name: "condition_refers_to_product_property" + property bool narf: true + property string someString: "SOMETHING" + Depends { name: "dummy" } + Properties { + condition: narf + dummy.defines: ["OVERWRITTEN"] + someString: "OVERWRITTEN" + } + } + property bool zort: true + Product { + name: "condition_refers_to_project_property" + property string someString: "SOMETHING" + Depends { name: "dummy" } + Properties { + condition: project.zort + dummy.defines: ["OVERWRITTEN"] + someString: "OVERWRITTEN" + } + } + ProductBase { + name: "inheritance_overwrite_in_subitem" + dummy.defines: ["OVERWRITTEN_IN_SUBITEM"] + } + ProductBase { + name: "inheritance_retain_base1" + dummy.defines: base.concat("SUB") + } + ProductBase { + name: "inheritance_retain_base2" + Properties { + condition: true + dummy.defines: base.concat("SUB") + } + dummy.defines: ["GNAMPF"] + } + ProductBase { + name: "inheritance_retain_base3" + Properties { + condition: true + dummy.defines: base.concat("SUB") + } + // no dummy.defines binding + } + ProductBase { + name: "inheritance_retain_base4" + Properties { + condition: false + dummy.defines: ["NEVERMORE"] + } + // no "else case" for dummy.defines. The value is derived from ProductBase. + } + ProductBase { + name: "inheritance_condition_in_subitem1" + defineBase: false + dummy.defines: base.concat("SUB") + } + ProductBase { + name: "inheritance_condition_in_subitem2" + defineBase: false + // no dummy.defines binding + } + Product { + id: knolf + name: "gnampf" + } + Product { + name: "condition_references_id" + Depends { name: "dummy" } + Properties { + condition: knolf.name === "gnampf" + dummy.defines: ["OVERWRITTEN"] + } + } + Product { + name: "using_derived_Properties_item" + Depends { name: "dummy" } + MyProperties { + condition: true + dummy.defines: ["string from MyProperties"] + } + } + Product { + name: "conditional-depends" + Depends { + name: "dummy" + condition: false + } + Properties { + condition: false + dummy.defines: ["a string"] + } + } + Product { + name: "use-module-with-properties-item" + Depends { name: "module-with-properties-item" } + } +} diff --git a/tests/auto/language/testdata/propertiesblocks_base.qbs b/tests/auto/language/testdata/propertiesblocks_base.qbs new file mode 100644 index 00000000..be6e57d6 --- /dev/null +++ b/tests/auto/language/testdata/propertiesblocks_base.qbs @@ -0,0 +1,11 @@ +Product { + property bool defineBase: true + Depends { name: "dummy" } + Properties { + condition: defineBase + dummy.defines: ["BASE"] + } + dummy.defines: ["SOMETHING"] + property stringList myCFlags: ["BASE"] + dummy.cFlags: myCFlags +} diff --git a/tests/auto/language/testdata/property-assignment-in-exported-group.qbs b/tests/auto/language/testdata/property-assignment-in-exported-group.qbs new file mode 100644 index 00000000..7d48cf06 --- /dev/null +++ b/tests/auto/language/testdata/property-assignment-in-exported-group.qbs @@ -0,0 +1,16 @@ +Project { + Product { + name: "dep" + Export { + Depends { name: "dummy" } + Group { + name: "exported_group" + dummy.someString: "test" + files: ["narf"] + } + } + } + Product { + Depends { name: "dep" } + } +} diff --git a/tests/auto/language/testdata/qbs-properties-in-project-condition.qbs b/tests/auto/language/testdata/qbs-properties-in-project-condition.qbs new file mode 100644 index 00000000..31f64823 --- /dev/null +++ b/tests/auto/language/testdata/qbs-properties-in-project-condition.qbs @@ -0,0 +1,7 @@ +Project { + condition: qbs.targetOS.contains("whatever") + + Product { + name: "never reached" + } +} diff --git a/tests/auto/language/testdata/qbs-property-convenience-override.qbs b/tests/auto/language/testdata/qbs-property-convenience-override.qbs new file mode 100644 index 00000000..85f2d232 --- /dev/null +++ b/tests/auto/language/testdata/qbs-property-convenience-override.qbs @@ -0,0 +1,4 @@ +Product { + name: "p" + qbs.installPrefix: "/usr/local" +} diff --git a/tests/auto/language/testdata/qbs1275.qbs b/tests/auto/language/testdata/qbs1275.qbs new file mode 100644 index 00000000..f88d62a7 --- /dev/null +++ b/tests/auto/language/testdata/qbs1275.qbs @@ -0,0 +1,40 @@ +Project { + Product { + name: "v-bug" + + Export { + Depends { name: "cpp"} + cpp.defines: "" + } + } + + Product { + name: "e-bug" + + Export { Depends { name: "v-bug" } } + } + + Product { + name: "u-bug" + + Export { Depends { name: "c-bug" } } + } + + Product { + name: "c-bug" + + Export { Depends { name: "e-bug" } } + } + + Product + { + name: "H-bug" + + Depends { name: "e-bug" } + Depends { name: "u-bug" } + + Group { + qbs.install: true + } + } +} diff --git a/tests/auto/language/testdata/recursive-dependencies/recursive-dependencies.qbs b/tests/auto/language/testdata/recursive-dependencies/recursive-dependencies.qbs new file mode 100644 index 00000000..e63ae5f5 --- /dev/null +++ b/tests/auto/language/testdata/recursive-dependencies/recursive-dependencies.qbs @@ -0,0 +1,17 @@ +Project { + Product { + name: "p1" + Depends { name: "p3" } + } + Product { + name: "p2" + Depends { name: "p3" } + } + Product { + name: "p3" + Export { + Depends { name: "p4" } + } + } + Product { name: "p4" } +} diff --git a/tests/auto/language/testdata/relaxed-error-mode/file1.txt b/tests/auto/language/testdata/relaxed-error-mode/file1.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/language/testdata/relaxed-error-mode/file2.txt b/tests/auto/language/testdata/relaxed-error-mode/file2.txt new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/language/testdata/relaxed-error-mode/relaxed-error-mode.qbs b/tests/auto/language/testdata/relaxed-error-mode/relaxed-error-mode.qbs new file mode 100644 index 00000000..561cda09 --- /dev/null +++ b/tests/auto/language/testdata/relaxed-error-mode/relaxed-error-mode.qbs @@ -0,0 +1,29 @@ +Project { + Product { + name: "recursive depender" + Depends { name: "depender required" } + files: "file1.txt" + } + Product { + name: "broken" + Depends { name: "nosuchmodule" } + } + Product { + name: "depender required" + Depends { name: "broken" } + files: "file1.txt" + } + Product { + name: "depender nonrequired" + Depends { name: "broken"; required: false } + files: "file1.txt" + } + Product { + name: "missing file" + files: ["file1.txt", "file3.txt", "file2.txt"] + } + Product { + name: "fine" + files: "file2.txt" + } +} diff --git a/tests/auto/language/testdata/required-and-nonrequired-dependencies/complicated.qbs b/tests/auto/language/testdata/required-and-nonrequired-dependencies/complicated.qbs new file mode 100644 index 00000000..2286c4c6 --- /dev/null +++ b/tests/auto/language/testdata/required-and-nonrequired-dependencies/complicated.qbs @@ -0,0 +1,5 @@ +Product { + Depends { name: "failing-validation"; required: false } + Depends { name: "failing-validation-indirect" } + Depends { name: "failing-validation-indirect"; required: false } +} diff --git a/tests/auto/language/testdata/required-and-nonrequired-dependencies/dependency-via-export.qbs b/tests/auto/language/testdata/required-and-nonrequired-dependencies/dependency-via-export.qbs new file mode 100644 index 00000000..2998308d --- /dev/null +++ b/tests/auto/language/testdata/required-and-nonrequired-dependencies/dependency-via-export.qbs @@ -0,0 +1,13 @@ +Project { + Product { + name: "dep" + Export { + Depends { name: "failing-validation" } + } + } + + Product { + Depends { name: "failing-validation"; required: false } + Depends { name: "dep" } + } +} diff --git a/tests/auto/language/testdata/required-and-nonrequired-dependencies/dependency-via-module.qbs b/tests/auto/language/testdata/required-and-nonrequired-dependencies/dependency-via-module.qbs new file mode 100644 index 00000000..f279cbd8 --- /dev/null +++ b/tests/auto/language/testdata/required-and-nonrequired-dependencies/dependency-via-module.qbs @@ -0,0 +1,4 @@ +Product { + Depends { name: "failing-validation"; required: false } + Depends { name: "failing-validation-indirect" } +} diff --git a/tests/auto/language/testdata/required-and-nonrequired-dependencies/direct-dependencies.qbs b/tests/auto/language/testdata/required-and-nonrequired-dependencies/direct-dependencies.qbs new file mode 100644 index 00000000..671a1966 --- /dev/null +++ b/tests/auto/language/testdata/required-and-nonrequired-dependencies/direct-dependencies.qbs @@ -0,0 +1,4 @@ +Product { + Depends { name: "failing-validation"; required: false } + Depends { name: "failing-validation" } +} diff --git a/tests/auto/language/testdata/required-and-nonrequired-dependencies/modules/failing-validation-indirect/failing-validation-indirect.qbs b/tests/auto/language/testdata/required-and-nonrequired-dependencies/modules/failing-validation-indirect/failing-validation-indirect.qbs new file mode 100644 index 00000000..b715ae68 --- /dev/null +++ b/tests/auto/language/testdata/required-and-nonrequired-dependencies/modules/failing-validation-indirect/failing-validation-indirect.qbs @@ -0,0 +1,3 @@ +Module { + Depends { name: "failing-validation" } +} diff --git a/tests/auto/language/testdata/required-and-nonrequired-dependencies/modules/failing-validation/failing-validation.qbs b/tests/auto/language/testdata/required-and-nonrequired-dependencies/modules/failing-validation/failing-validation.qbs new file mode 100644 index 00000000..f7fdc415 --- /dev/null +++ b/tests/auto/language/testdata/required-and-nonrequired-dependencies/modules/failing-validation/failing-validation.qbs @@ -0,0 +1,3 @@ +Module { + validate: { throw "validation error!"; } +} diff --git a/tests/auto/language/testdata/required-and-nonrequired-dependencies/required-chain-export-indirect.qbs b/tests/auto/language/testdata/required-and-nonrequired-dependencies/required-chain-export-indirect.qbs new file mode 100644 index 00000000..3b3d86e9 --- /dev/null +++ b/tests/auto/language/testdata/required-and-nonrequired-dependencies/required-chain-export-indirect.qbs @@ -0,0 +1,20 @@ +Project { + Product { + name: "dep2" + Export { + Depends { name: "dep1" } + } + } + + Product { + name: "dep1" + Export { + Depends { name: "failing-validation-indirect" } + } + } + + Product { + Depends { name: "failing-validation"; required: false } + Depends { name: "dep2"; required: false } + } +} diff --git a/tests/auto/language/testdata/required-and-nonrequired-dependencies/required-chain-export.qbs b/tests/auto/language/testdata/required-and-nonrequired-dependencies/required-chain-export.qbs new file mode 100644 index 00000000..19921c82 --- /dev/null +++ b/tests/auto/language/testdata/required-and-nonrequired-dependencies/required-chain-export.qbs @@ -0,0 +1,13 @@ +Project { + Product { + name: "dep" + Export { + Depends { name: "failing-validation" } + } + } + + Product { + Depends { name: "failing-validation"; required: false } + Depends { name: "dep"; required: false } + } +} diff --git a/tests/auto/language/testdata/required-and-nonrequired-dependencies/required-chain-module.qbs b/tests/auto/language/testdata/required-and-nonrequired-dependencies/required-chain-module.qbs new file mode 100644 index 00000000..262d5b6b --- /dev/null +++ b/tests/auto/language/testdata/required-and-nonrequired-dependencies/required-chain-module.qbs @@ -0,0 +1,4 @@ +Product { + Depends { name: "failing-validation"; required: false } + Depends { name: "failing-validation-indirect"; required: false } +} diff --git a/tests/auto/language/testdata/rfc1034identifier.qbs b/tests/auto/language/testdata/rfc1034identifier.qbs new file mode 100644 index 00000000..4d320f89 --- /dev/null +++ b/tests/auto/language/testdata/rfc1034identifier.qbs @@ -0,0 +1,9 @@ +import qbs.Utilities + +CppApplication { + name: Utilities.rfc1034Identifier("this!has@special#characters$uh-oh,Undersc0r3s_Are.Bad") + Properties { + condition: qbs.targetOS.contains("darwin") + bundle.infoPlist: { return {"CFBundleIdentifier": "$(PRODUCT_NAME:rfc1034identifier)"}; } + } +} diff --git a/tests/auto/language/testdata/subdir/exports-mylib.qbs b/tests/auto/language/testdata/subdir/exports-mylib.qbs new file mode 100644 index 00000000..92f39483 --- /dev/null +++ b/tests/auto/language/testdata/subdir/exports-mylib.qbs @@ -0,0 +1,15 @@ +StaticLibrary { + name: "mylib" + Depends { name: "dummy" } + dummy.defines: ["BUILD_" + product.name.toUpperCase()] + property string definePrefix: "USE_" + property path aPath: "." + dummy.somePath: aPath + Export { + Depends { name: "dummy" } + Depends { name: "mylib2" } + dummy.defines: [product.definePrefix + product.name.toUpperCase()] + dummy.includePaths: ["./lib"] + dummy.somePath: product.aPath + } +} diff --git a/tests/auto/language/testdata/subdir/pathproperties_base.qbs b/tests/auto/language/testdata/subdir/pathproperties_base.qbs new file mode 100644 index 00000000..62427169 --- /dev/null +++ b/tests/auto/language/testdata/subdir/pathproperties_base.qbs @@ -0,0 +1,4 @@ +Product { + property path base_fileInProductDir: "foo" + property path base_fileInBaseProductDir: path + "/bar" +} diff --git a/tests/auto/language/testdata/subdir2/exports-mylib2.qbs b/tests/auto/language/testdata/subdir2/exports-mylib2.qbs new file mode 100644 index 00000000..ac8b9ebe --- /dev/null +++ b/tests/auto/language/testdata/subdir2/exports-mylib2.qbs @@ -0,0 +1,11 @@ +StaticLibrary { + name: "mylib2" + Depends { name: "dummy" } + dummy.defines: ["BUILD_" + product.name.toUpperCase()] + property string definePrefix: "USE_" + Export { + Depends { name: "dummy" } + dummy.defines: [product.definePrefix + product.name.toUpperCase()] + dummy.includePaths: ["./lib"] + } +} diff --git a/tests/auto/language/testdata/suppressed-and-non-suppressed-errors.qbs b/tests/auto/language/testdata/suppressed-and-non-suppressed-errors.qbs new file mode 100644 index 00000000..4d521915 --- /dev/null +++ b/tests/auto/language/testdata/suppressed-and-non-suppressed-errors.qbs @@ -0,0 +1,11 @@ +Project { + CppApplication { + name: "mysterious creature" + files: ["easter bunny"] + } + Product { + name: "tasty food" + condition: false + Depends { name: "TheBeautifulSausage" } + } +} diff --git a/tests/auto/language/testdata/throwing-probe.qbs b/tests/auto/language/testdata/throwing-probe.qbs new file mode 100644 index 00000000..15a95325 --- /dev/null +++ b/tests/auto/language/testdata/throwing-probe.qbs @@ -0,0 +1,11 @@ +Product { + name: "theProduct" + property bool enableProbe + Probe { + id: whatever + condition: enableProbe + configure: { + throw "Error!"; + } + } +} diff --git a/tests/auto/language/testdata/use-internal-profile.qbs b/tests/auto/language/testdata/use-internal-profile.qbs new file mode 100644 index 00000000..c2e20ff1 --- /dev/null +++ b/tests/auto/language/testdata/use-internal-profile.qbs @@ -0,0 +1,13 @@ +Project { + name: "theproject" + + Profile { + name: "theprofile" + dummy.defines: name + } + + Product { + name: "theproduct" + Depends { name: "dummy" } + } +} \ No newline at end of file diff --git a/tests/auto/language/testdata/versionCompare.qbs b/tests/auto/language/testdata/versionCompare.qbs new file mode 100644 index 00000000..8a363bf6 --- /dev/null +++ b/tests/auto/language/testdata/versionCompare.qbs @@ -0,0 +1,20 @@ +import qbs.Utilities + +Product { + name: { + var e = "unexpected comparison result"; + if (Utilities.versionCompare("1.5", "1.5") !== 0) + throw e; + if (Utilities.versionCompare("1.5", "1.5.0") !== 0) + throw e; + if (Utilities.versionCompare("1.5", "1.6") >= 0) + throw e; + if (Utilities.versionCompare("1.6", "1.5") <= 0) + throw e; + if (Utilities.versionCompare("2.5", "1.6") <= 0) + throw e; + if (Utilities.versionCompare("1.6", "2.5") >= 0) + throw e; + return "versionCompare"; + } +} diff --git a/tests/auto/language/testdata/zort b/tests/auto/language/testdata/zort new file mode 100644 index 00000000..e69de29b diff --git a/tests/auto/language/tst_language.cpp b/tests/auto/language/tst_language.cpp new file mode 100644 index 00000000..d3406982 --- /dev/null +++ b/tests/auto/language/tst_language.cpp @@ -0,0 +1,3290 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#undef QT_NO_CAST_FROM_ASCII // I am qmake, and I approve this hack. + +#include "tst_language.h" + +#include "../shared.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../shared/logging/consolelogger.h" + +#include + +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QList) + +using namespace qbs; +using namespace qbs::Internal; + +static QString testDataDir() { + return testDataSourceDir(SRCDIR "/testdata"); +} +static QString testProject(const char *fileName) { + return testDataDir() + QLatin1Char('/') + QLatin1String(fileName); +} + +TestLanguage::TestLanguage(ILogSink *logSink, Settings *settings) + : m_logSink(logSink) + , m_settings(settings) + , m_wildcardsTestDirPath(QDir::tempPath() + QLatin1String("/_wildcards_test_dir_")) +{ + m_rand.seed(QTime::currentTime().msec()); + qRegisterMetaType >("QList"); + defaultParameters.setBuildRoot(m_tempDir.path() + "/buildroot"); + defaultParameters.setPropertyCheckingMode(ErrorHandlingMode::Strict); + defaultParameters.setSettingsDirectory(m_settings->baseDirectory()); +} + +TestLanguage::~TestLanguage() +{ +} + +QHash TestLanguage::productsFromProject(ResolvedProjectPtr project) +{ + QHash result; + const auto products = project->allProducts(); + for (const ResolvedProductPtr &product : products) + result.insert(product->name, product); + return result; +} + +template +typename C::value_type findByName(const C &container, const QString &name) +{ + auto endIt = std::end(container); + auto it = std::find_if(std::begin(container), endIt, + [&name] (const typename C::value_type &thing) { + return thing->name == name; + }); + if (it != endIt) + return *it; + return typename C::value_type(); +} + +ResolvedModuleConstPtr TestLanguage::findModuleByName(ResolvedProductPtr product, const QString &name) +{ + return findByName(product->modules, name); +} + +QVariant TestLanguage::productPropertyValue(ResolvedProductPtr product, QString propertyName) +{ + QStringList propertyNameComponents = propertyName.split(QLatin1Char('.')); + if (propertyNameComponents.size() > 1) + return product->moduleProperties->property(propertyNameComponents); + return getConfigProperty(product->productProperties, propertyNameComponents); +} + +void TestLanguage::handleInitCleanupDataTags(const char *projectFileName, bool *handled) +{ + const QByteArray dataTag = QTest::currentDataTag(); + if (dataTag == "init") { + *handled = true; + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject(projectFileName)); + project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); + } else if (dataTag == "cleanup") { + *handled = true; + project.reset(); + } else { + *handled = false; + } +} + +void TestLanguage::init() +{ + m_logSink->setLogLevel(LoggerInfo); + QVERIFY(m_tempDir.isValid()); +} + +#define HANDLE_INIT_CLEANUP_DATATAGS(fn) {\ + bool handled;\ + handleInitCleanupDataTags(fn, &handled);\ + if (handled)\ + return;\ + QVERIFY(!!project);\ +} + +void TestLanguage::initTestCase() +{ + m_logger = Logger(m_logSink); + m_engine = ScriptEngine::create(m_logger, EvalContext::PropertyEvaluation, this); + loader = new Loader(m_engine, m_logger); + loader->setSearchPaths(QStringList() + << (testDataDir() + "/../../../../share/qbs")); + defaultParameters.setTopLevelProfile(profileName()); + defaultParameters.setConfigurationName("default"); + defaultParameters.expandBuildConfiguration(); + defaultParameters.setEnvironment(QProcessEnvironment::systemEnvironment()); + QVERIFY(QFileInfo(m_wildcardsTestDirPath).isAbsolute()); +} + +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 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; + try { + defaultParameters.setProjectFilePath(testProject("baseproperty.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(!!product); + QVariantMap cfg = product->productProperties; + QCOMPARE(cfg.value("narf").toStringList(), QStringList() << "boo"); + QCOMPARE(cfg.value("zort").toStringList(), QStringList() << "bar" << "boo"); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::baseValidation() +{ + qbs::SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("base-validate/base-validate.qbs")); + try { + project = loader->loadProject(params); + QVERIFY2(false, "exception expected"); + } catch (const qbs::ErrorInfo &e) { + QVERIFY2(e.toString().contains("Parent succeeded, child failed."), + qPrintable(e.toString())); + } +} + +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("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; + try { + SetupProjectParameters parameters = defaultParameters; + QVariantMap overriddenValues; + overriddenValues.insert("project.someStrings", "foo,bar,baz"); + parameters.setOverriddenValues(overriddenValues); + parameters.setProjectFilePath(testProject("buildconfigstringlistsyntax.qbs")); + project = loader->loadProject(parameters); + QVERIFY(!!project); + QCOMPARE(project->projectProperties().value("someStrings").toStringList(), + QStringList() << "foo" << "bar" << "baz"); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::builtinFunctionInSearchPathsProperty() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters parameters = defaultParameters; + parameters.setProjectFilePath(testProject("builtinFunctionInSearchPathsProperty.qbs")); + QVERIFY(!!loader->loadProject(parameters)); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::chainedProbes() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters parameters = defaultParameters; + parameters.setProjectFilePath(testProject("chained-probes/chained-probes.qbs")); + const TopLevelProjectConstPtr project = loader->loadProject(parameters); + QVERIFY(!!project); + QCOMPARE(project->products.size(), size_t(1)); + const QString prop2Val = project->products.front()->moduleProperties + ->moduleProperty("m", "prop2").toString(); + QCOMPARE(prop2Val, QLatin1String("probe1Valprobe2Val")); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); + +} + +void TestLanguage::versionCompare() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters parameters = defaultParameters; + parameters.setProjectFilePath(testProject("versionCompare.qbs")); + QVERIFY(!!loader->loadProject(parameters)); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::canonicalArchitecture() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("canonicalArchitecture.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + ResolvedProductPtr product = products.value(QStringLiteral("x86")); + QVERIFY(!!product); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::rfc1034Identifier() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("rfc1034identifier.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + ResolvedProductPtr product = products.value(QStringLiteral("this-has-special-characters-" + "uh-oh-Undersc0r3s-Are.Bad")); + QVERIFY(!!product); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::conditionalDepends() +{ + bool exceptionCaught = false; + ResolvedProductPtr product; + ResolvedModuleConstPtr dependency; + try { + 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 products = productsFromProject(project); + + product = products.value("conditionaldepends_derived"); + QVERIFY(!!product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(!!dependency); + + product = products.value("conditionaldepends_derived_false"); + QVERIFY(!!product); + dependency = findModuleByName(product, "dummy"); + QCOMPARE(dependency, ResolvedModuleConstPtr()); + + product = products.value("product_props_true"); + QVERIFY(!!product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(!!dependency); + + product = products.value("product_props_false"); + QVERIFY(!!product); + dependency = findModuleByName(product, "dummy"); + QCOMPARE(dependency, ResolvedModuleConstPtr()); + + product = products.value("project_props_true"); + QVERIFY(!!product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(!!dependency); + + product = products.value("project_props_false"); + QVERIFY(!!product); + dependency = findModuleByName(product, "dummy"); + QCOMPARE(dependency, ResolvedModuleConstPtr()); + + product = products.value("module_props_true"); + QVERIFY(!!product); + dependency = findModuleByName(product, "dummy2"); + QVERIFY(!!dependency); + dependency = findModuleByName(product, "dummy"); + QVERIFY(!!dependency); + + product = products.value("module_props_false"); + QVERIFY(!!product); + dependency = findModuleByName(product, "dummy2"); + QVERIFY(!!dependency); + dependency = findModuleByName(product, "dummy"); + QCOMPARE(dependency, ResolvedModuleConstPtr()); + + product = products.value("multilevel_module_props_true"); + QVERIFY(!!product); + dependency = findModuleByName(product, "dummy3"); + QVERIFY(!!dependency); + dependency = findModuleByName(product, "dummy"); + QVERIFY(!!dependency); + + product = products.value("multilevel_module_props_false"); + QVERIFY(!!product); + dependency = findModuleByName(product, "dummy3"); + QVERIFY(!!dependency); + 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"); + QVERIFY(!!dependency); + + product = products.value("contradictory_conditions2"); + QVERIFY(!!product); + dependency = findModuleByName(product, "dummy"); + QVERIFY(!!dependency); + + product = products.value("unknown_dependency_condition_false"); + QVERIFY(!!product); + dependency = findModuleByName(product, "doesonlyexistifhellfreezesover"); + QVERIFY(!dependency); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::delayedError() +{ + QFETCH(bool, productEnabled); + try { + QFETCH(QString, projectFileName); + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject(projectFileName.toLatin1())); + QVariantMap overriddenValues; + overriddenValues.insert("project.enableProduct", productEnabled); + params.setOverriddenValues(overriddenValues); + project = loader->loadProject(params); + QCOMPARE(productEnabled, false); + QVERIFY(!!project); + QCOMPARE(project->products.size(), size_t(1)); + const ResolvedProductConstPtr theProduct = productsFromProject(project).value("theProduct"); + QVERIFY(!!theProduct); + QCOMPARE(theProduct->enabled, false); + } catch (const ErrorInfo &e) { + if (!productEnabled) + qDebug() << e.toString(); + QCOMPARE(productEnabled, true); + } +} + +void TestLanguage::delayedError_data() +{ + QTest::addColumn("projectFileName"); + QTest::addColumn("productEnabled"); + QTest::newRow("product enabled, module validation error") + << "delayed-error/validation.qbs" << true; + QTest::newRow("product disabled, module validation error") + << "delayed-error/validation.qbs" << false; + QTest::newRow("product enabled, module not found") + << "delayed-error/nonexisting.qbs" << true; + QTest::newRow("product disabled, module not found") + << "delayed-error/nonexisting.qbs" << false; +} + +void TestLanguage::dependencyOnAllProfiles() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("dependencyOnAllProfiles.qbs")); + TemporaryProfile p1("p1", m_settings); + p1.p.setValue("qbs.architecture", "arch1"); + TemporaryProfile p2("p2", m_settings); + p2.p.setValue("qbs.architecture", "arch2"); + QVariantMap overriddenValues; + overriddenValues.insert("project.profile1", "p1"); + overriddenValues.insert("project.profile2", "p2"); + params.setOverriddenValues(overriddenValues); + project = loader->loadProject(params); + QVERIFY(!!project); + QCOMPARE(project->products.size(), size_t(3)); + const ResolvedProductConstPtr mainProduct = productsFromProject(project).value("main"); + QVERIFY(!!mainProduct); + QCOMPARE(mainProduct->dependencies.size(), size_t { 2 }); + for (const ResolvedProductPtr &p : mainProduct->dependencies) { + QCOMPARE(p->name, QLatin1String("dep")); + QVERIFY(p->profile() == "p1" || p->profile() == "p2"); + } + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::derivedSubProject() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("derived-sub-project/project.qbs")); + const TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + const QHash products = productsFromProject(project); + QCOMPARE(products.size(), 1); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::disabledSubProject() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("disabled-subproject.qbs")); + const TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + const QHash products = productsFromProject(project); + QCOMPARE(products.size(), 0); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::dottedNames_data() +{ + QTest::addColumn("useProduct"); + QTest::addColumn("useModule"); + QTest::addColumn("expectSuccess"); + QTest::addColumn("expectedErrorMessage"); + QTest::newRow("missing product dependency") << false << true << false + << QString("Item 'a.b' is not declared. Did you forget to add a Depends item"); + QTest::newRow("missing module dependency") << true << false << false + << QString("Item 'x.y' is not declared. Did you forget to add a Depends item"); + QTest::newRow("missing both dependencies") << false << false << false << QString(); + QTest::newRow("ok") << true << true << true << QString(); +} + +void TestLanguage::dottedNames() +{ + QFETCH(bool, expectSuccess); + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("dotted-names/dotted-names.qbs")); + QFETCH(bool, useProduct); + QFETCH(bool, useModule); + const QVariantMap overridden{ + std::make_pair("projects.theProject.includeDottedProduct", useProduct), + std::make_pair("projects.theProject.includeDottedModule", useModule) + }; + params.setOverriddenValues(overridden); + TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(expectSuccess); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), useProduct ? 2 : 1); + const ResolvedProductPtr product = products.value("p"); + QVERIFY(!!product); + QCOMPARE(product->moduleProperties->moduleProperty("a.b", "c").toString(), QString("p")); + QCOMPARE(product->moduleProperties->moduleProperty("x.y", "z").toString(), QString("p")); + } catch (const ErrorInfo &e) { + QVERIFY(!expectSuccess); + QFETCH(QString, expectedErrorMessage); + if (!expectedErrorMessage.isEmpty()) + QVERIFY2(e.toString().contains(expectedErrorMessage), qPrintable(e.toString())); + } +} + +void TestLanguage::emptyJsFile() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("empty-js-file.qbs")); + const TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + const QHash products = productsFromProject(project); + QCOMPARE(products.size(), 1); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::enumerateProjectProperties() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("enum-project-props.qbs")); + auto project = loader->loadProject(params); + QVERIFY(!!project); + auto products = productsFromProject(project); + QCOMPARE(products.size(), 1); + auto product = products.values().front(); + auto files = product->groups.front()->allFiles(); + QCOMPARE(product->groups.size(), size_t(1)); + QCOMPARE(files.size(), size_t(1)); + auto fileName = FileInfo::fileName(files.front()->absoluteFilePath); + QCOMPARE(fileName, QString("dummy.txt")); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::evalErrorInNonPresentModule_data() +{ + QTest::addColumn("moduleRequired"); + QTest::addColumn("errorMessage"); + + QTest::newRow("module required") + << true << "broken.qbs:4:5 Element at index 0 of list property 'broken' " + "does not have string type"; + QTest::newRow("module not required") << false << QString(); +} + +void TestLanguage::evalErrorInNonPresentModule() +{ + QFETCH(bool, moduleRequired); + QFETCH(QString, errorMessage); + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("eval-error-in-non-present-module.qbs")); + QVariantMap overridden{std::make_pair("products.p.moduleRequired", moduleRequired)}; + params.setOverriddenValues(overridden); + TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(errorMessage.isEmpty()); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 1); + const ResolvedProductPtr product = products.value("p"); + QVERIFY(!!product); + } + catch (const ErrorInfo &e) { + QVERIFY(!errorMessage.isEmpty()); + QVERIFY2(e.toString().contains(errorMessage), qPrintable(e.toString())); + } +} + +void TestLanguage::defaultValue() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("defaultvalue/egon.qbs")); + QFETCH(QString, prop1Value); + QVariantMap overridden; + if (!prop1Value.isEmpty()) + overridden.insert("modules.lower.prop1", prop1Value); + params.setOverriddenValues(overridden); + TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 2); + const ResolvedProductPtr product = products.value("egon"); + QVERIFY(!!product); + QStringList propertyName = QStringList() << "lower" << "prop2"; + QVariant propertyValue = product->moduleProperties->property(propertyName); + QFETCH(QVariant, expectedProp2Value); + QCOMPARE(propertyValue, expectedProp2Value); + propertyName = QStringList() << "lower" << "listProp"; + propertyValue = product->moduleProperties->property(propertyName); + QFETCH(QVariant, expectedListPropValue); + QCOMPARE(propertyValue.toStringList(), expectedListPropValue.toStringList()); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::defaultValue_data() +{ + QTest::addColumn("prop1Value"); + QTest::addColumn("expectedProp2Value"); + QTest::addColumn("expectedListPropValue"); + QTest::newRow("controlling property with random value") << "random" << QVariant("withoutBlubb") + << QVariant(QStringList({"other"})); + QTest::newRow("controlling property with blubb value") << "blubb" << QVariant("withBlubb") + << QVariant(QStringList({"blubb", "other"})); + QTest::newRow("controlling property with egon value") << "egon" << QVariant("withEgon") + << QVariant(QStringList({"egon", "other"})); + QTest::newRow("controlling property not overwritten") << "" << QVariant("withBlubb") + << QVariant(QStringList({"blubb", "other"})); +} + +void TestLanguage::environmentVariable() +{ + bool exceptionCaught = false; + try { + // Create new environment: + const QString varName = QStringLiteral("PRODUCT_NAME"); + const QString productName = QLatin1String("MyApp") + QString::number(m_rand.generate()); + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(varName, productName); + + QProcessEnvironment origEnv = defaultParameters.environment(); // store orig environment + + defaultParameters.setEnvironment(env); + defaultParameters.setProjectFilePath(testProject("environmentvariable.qbs")); + project = loader->loadProject(defaultParameters); + + defaultParameters.setEnvironment(origEnv); // reset environment + + QVERIFY(!!project); + QHash products = productsFromProject(project); + ResolvedProductPtr product = products.value(productName); + QVERIFY(!!product); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::errorInDisabledProduct() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("error-in-disabled-product.qbs")); + auto project = loader->loadProject(params); + QVERIFY(!!project); + auto products = productsFromProject(project); + QCOMPARE(products.size(), 5); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::erroneousFiles_data() +{ + QTest::addColumn("errorMessage"); + QTest::newRow("unknown_module") + << "Dependency 'neitherModuleNorProduct' not found"; + QTest::newRow("multiple_exports") + << "Multiple Export items in one product are prohibited."; + QTest::newRow("multiple_properties_in_subproject") + << "Multiple instances of item 'Properties' found where at most one " + "is allowed."; + QTest::newRow("importloop1") + << "Loop detected when importing"; + QTest::newRow("nonexistentouter") + << "Can't find variable: outer"; + QTest::newRow("invalid_file") + << "does not exist"; + QTest::newRow("invalid-parameter-rhs") + << "ReferenceError: Can't find variable: access"; + QTest::newRow("invalid-parameter-type") + << "Value assigned to property 'stringParameter' does not have type 'string'."; + QTest::newRow("invalid_property_type") + << "Unknown type 'nonsense' in property declaration."; + QTest::newRow("reserved_name_in_import") + << "Cannot reuse the name of built-in extension 'TextFile'."; + QTest::newRow("throw_in_property_binding") + << "something is wrong"; + QTest::newRow("no-configure-in-probe") + << "no-configure-in-probe.qbs:2:5.*Probe.configure must be set"; + QTest::newRow("dependency_cycle") + << "Cyclic dependencies detected."; + QTest::newRow("dependency_cycle2") + << "Cyclic dependencies detected."; + QTest::newRow("dependency_cycle3") + << "Cyclic dependencies detected."; + QTest::newRow("dependency_cycle4") + << "Cyclic dependencies detected."; + QTest::newRow("references_cycle") + << "Cycle detected while referencing file 'references_cycle.qbs'."; + QTest::newRow("subproject_cycle") + << "Cycle detected while loading subproject file 'subproject_cycle.qbs'."; + QTest::newRow("invalid_stringlist_element") + << "Element at index 1 of list property 'files' does not have string type."; + QTest::newRow("undefined_stringlist_element") + << "Element at index 1 of list property 'files' is undefined. String expected."; + QTest::newRow("undefined_stringlist_element_in_probe") + << "Element at index 1 of list property 'l' is undefined. String expected."; + QTest::newRow("undeclared_item") + << "Item 'cpp' is not declared."; + QTest::newRow("undeclared-parameter1") + << "Parameter 'prefix2.suffix.nope' is not declared."; + QTest::newRow("undeclared-parameter2") + << "Cannot set parameter 'foo.bar', " + "because 'myproduct' does not have a dependency on 'foo'."; + QTest::newRow("undeclared_property_wrapper") + << "Property 'doesntexist' is not declared."; + QTest::newRow("undeclared_property_in_export_item") + << "Property 'blubb' is not declared."; + QTest::newRow("undeclared_property_in_export_item2") + << "Item 'something' is not declared."; + QTest::newRow("undeclared_property_in_export_item3") + << "Property 'blubb' is not declared."; + QTest::newRow("undeclared_module_property_in_module") + << "Property 'noSuchProperty' is not declared."; + QTest::newRow("unknown_item_type") + << "Unexpected item type 'Narf'"; + QTest::newRow("invalid_child_item_type") + << "Items of type 'Project' cannot contain items of type 'Depends'."; + QTest::newRow("conflicting_fileTagsFilter") + << "Conflicting fileTagsFilter in Group items"; + QTest::newRow("duplicate_sources") + << "Duplicate source file '.*main.cpp'" + ".*duplicate_sources.qbs:2:12.*duplicate_sources.qbs:4:16."; + QTest::newRow("duplicate_sources_wildcards") + << "Duplicate source file '.*duplicate_sources_wildcards.qbs'" + ".*duplicate_sources_wildcards.qbs:2:12" + ".*duplicate_sources_wildcards.qbs:4:16."; + QTest::newRow("oldQbsVersion") + << "The project requires at least qbs version \\d+\\.\\d+.\\d+, " + "but this is qbs version " QBS_VERSION "."; + QTest::newRow("wrongQbsVersionFormat") + << "The value '.*' of Project.minimumQbsVersion is not a valid version string."; + QTest::newRow("properties-item-with-invalid-condition") + << "properties-item-with-invalid-condition.qbs:4:19.*TypeError: Result of expression " + "'cpp.nonexistingproperty'"; + QTest::newRow("misused-inherited-property") << "Binding to non-item property"; + QTest::newRow("undeclared_property_in_Properties_item") << "Item 'blubb' is not declared"; + QTest::newRow("same-module-prefix1") << "The name of module 'prefix1' is equal to the first " + "component of the name of module 'prefix1.suffix'"; + QTest::newRow("same-module-prefix2") << "The name of module 'prefix2' is equal to the first " + "component of the name of module 'prefix2.suffix'"; + QTest::newRow("conflicting-properties-in-export-items") + << "Export item in inherited item redeclares property 'theProp' with different type."; + QTest::newRow("invalid-property-option") + << "PropertyOptions item refers to non-existing property 's0meProp'"; + QTest::newRow("missing-colon") + << "Invalid item 'cpp.dynamicLibraries'. Did you mean to set a module property?"; + QTest::newRow("syntax-error-in-probe") + << "syntax-error-in-probe.qbs:4:20.*ReferenceError"; + QTest::newRow("wrong-toplevel-item") + << "wrong-toplevel-item.qbs:1: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 equally prioritized candidate for module " + "'conflicting-instances'."; + QTest::newRow("module-depends-on-product") + << "module-with-product-dependency.qbs:2:5.*Modules cannot depend on products."; + QTest::newRow("overwrite-inherited-readonly-property") + << "overwrite-inherited-readonly-property.qbs" + ":2:21.*Cannot set read-only property 'readOnlyString'."; + QTest::newRow("overwrite-readonly-module-property") + << "overwrite-readonly-module-property.qbs" + ":3:30.*Cannot set read-only property 'readOnlyString'."; + QTest::newRow("original-in-product-property") + << "original-in-product-property.qbs" + ":2:21.*The special value 'original' can only be used with module properties."; + QTest::newRow("rule-without-output-tags") + << "rule-without-output-tags.qbs:2:5.*A rule needs to have Artifact items or " + "a non-empty outputFileTags property."; + QTest::newRow("original-in-module-prototype") + << "module-with-invalid-original.qbs:2:24.*The special value 'original' cannot be used " + "on the right-hand side of a property declaration."; + QTest::newRow("original-in-export-item") + << "original-in-export-item.qbs:7:32.*The special value 'original' cannot be used " + "on the right-hand side of a property declaration."; + QTest::newRow("original-in-export-item2") + << "original-in-export-item2.qbs:6:9.*Item 'x.y' is not declared. Did you forget " + "to add a Depends item"; + QTest::newRow("original-in-export-item3") + << "original-in-export-item3.qbs:6:9.*Item 'x.y' is not declared. Did you forget " + "to add a Depends item"; + QTest::newRow("mismatching-multiplex-dependency") + << "mismatching-multiplex-dependency.qbs:9:9.*Dependency from product " + "'b \\{\"architecture\":\"mips\"\\}' to product 'a'" + " not fulfilled. There are no eligible multiplex candidates."; + QTest::newRow("ambiguous-multiplex-dependency") + << "ambiguous-multiplex-dependency.qbs:10:9.*Dependency from product 'b " + "\\{\"architecture\":\"x86\"\\}' to product 'a' is ambiguous. Eligible multiplex " + "candidates: a \\{\"architecture\":\"x86\",\"buildVariant\":\"debug\"\\}, " + "a \\{\"architecture\":\"x86\",\"buildVariant\":\"release\"\\}."; + QTest::newRow("dependency-profile-mismatch") + << "dependency-profile-mismatch.qbs:10:5.*Product 'main' depends on 'dep', " + "which does not exist for the requested profile 'profile47'."; + QTest::newRow("dependency-profile-mismatch-2") + << "dependency-profile-mismatch-2.qbs:15:9 Dependency from product 'main' to " + "product 'dep' not fulfilled. There are no eligible multiplex candidates."; + QTest::newRow("duplicate-multiplex-value") + << "duplicate-multiplex-value.qbs:3:1.*Duplicate entry 'x86' in qbs.architectures."; + QTest::newRow("duplicate-multiplex-value2") + << "duplicate-multiplex-value2.qbs:3:1.*Duplicate entry 'architecture' in " + "Product.multiplexByQbsProperties."; + QTest::newRow("invalid-references") + << "invalid-references.qbs:2:17.*Cannot open '.*nosuchproject.qbs'"; +} + +void TestLanguage::erroneousFiles() +{ + QFETCH(QString, errorMessage); + QString fileName = QString::fromLocal8Bit(QTest::currentDataTag()) + QLatin1String(".qbs"); + try { + defaultParameters.setProjectFilePath(testProject("/erroneous/") + fileName); + loader->loadProject(defaultParameters); + } catch (const ErrorInfo &e) { + if (!e.toString().contains(QRegExp(errorMessage))) { + qDebug() << "Message: " << e.toString(); + qDebug() << "Expected: " << errorMessage; + QFAIL("Unexpected error message."); + } + return; + } + QEXPECT_FAIL("undeclared_property_in_Properties_item", "Too expensive to check", Continue); + QEXPECT_FAIL("original-in-export-item3", "Too expensive to check", Continue); + QVERIFY(!"No error thrown on invalid input."); +} + +void TestLanguage::exports() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("exports.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 22); + ResolvedProductPtr product; + product = products.value("myapp"); + QVERIFY(!!product); + QStringList propertyName = QStringList() << "dummy" << "defines"; + QVariant propertyValue = product->moduleProperties->property(propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList() << "BUILD_MYAPP" << "USE_MYLIB" + << "USE_MYLIB2"); + propertyName = QStringList() << "dummy" << "includePaths"; + QVariantList propertyValues = product->moduleProperties->property(propertyName).toList(); + QCOMPARE(propertyValues.size(), 3); + QVERIFY(propertyValues.at(0).toString().endsWith("/app")); + QVERIFY(propertyValues.at(1).toString().endsWith("/subdir/lib")); + QVERIFY(propertyValues.at(2).toString().endsWith("/subdir2/lib")); + + QCOMPARE(product->moduleProperties->moduleProperty("dummy", "productName").toString(), + QString("myapp")); + QVERIFY(product->moduleProperties->moduleProperty("dummy", "somePath").toString() + .endsWith("/subdir")); + + product = products.value("mylib"); + QVERIFY(!!product); + propertyName = QStringList() << "dummy" << "defines"; + propertyValue = product->moduleProperties->property(propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList() << "BUILD_MYLIB"); + QVERIFY(product->moduleProperties->moduleProperty("dummy", "somePath").toString() + .endsWith("/subdir")); + + product = products.value("mylib2"); + QVERIFY(!!product); + propertyName = QStringList() << "dummy" << "defines"; + propertyValue = product->moduleProperties->property(propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList() << "BUILD_MYLIB2"); + + product = products.value("A"); + QVERIFY(!!product); + QVERIFY(contains(product->dependencies, products.value("B"))); + QVERIFY(contains(product->dependencies, products.value("C"))); + QVERIFY(contains(product->dependencies, products.value("D"))); + product = products.value("B"); + QVERIFY(!!product); + QVERIFY(product->dependencies.empty()); + product = products.value("C"); + QVERIFY(!!product); + QVERIFY(product->dependencies.empty()); + product = products.value("D"); + QVERIFY(!!product); + QVERIFY(product->dependencies.empty()); + + product = products.value("myapp2"); + QVERIFY(!!product); + propertyName = QStringList() << "dummy" << "cFlags"; + propertyValue = product->moduleProperties->property(propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList() + << "BASE_PRODUCTWITHINHERITEDEXPORTITEM" + << "PRODUCT_PRODUCTWITHINHERITEDEXPORTITEM"); + propertyName = QStringList() << "dummy" << "cxxFlags"; + propertyValue = product->moduleProperties->property(propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList() << "-bar"); + propertyName = QStringList() << "dummy" << "defines"; + propertyValue = product->moduleProperties->property(propertyName); + QCOMPARE(propertyValue.toStringList(), QStringList({"ABC", "DEF"})); + QCOMPARE(product->moduleProperties->moduleProperty("dummy", "productName").toString(), + QString("myapp2")); + QCOMPARE(product->moduleProperties->moduleProperty("dummy", + "upperCaseProductName").toString(), QString("MYAPP2")); + + // Check whether we're returning incorrect cached values. + product = products.value("myapp3"); + QVERIFY(!!product); + QCOMPARE(product->moduleProperties->moduleProperty("dummy", "productName").toString(), + QString("myapp3")); + QCOMPARE(product->moduleProperties->moduleProperty("dummy", + "upperCaseProductName").toString(), QString("MYAPP3")); + + // Verify we refer to the right "project" variable. + product = products.value("sub p2"); + QVERIFY(!!product); + QCOMPARE(product->moduleProperties->moduleProperty("dummy", "someString").toString(), + QString("sub1")); + + product = products.value("libE"); + QVERIFY(!!product); + propertyName = QStringList() << "dummy" << "defines"; + propertyValue = product->moduleProperties->property(propertyName); + QCOMPARE(propertyValue.toStringList(), + QStringList() << "LIBD" << "LIBC" << "LIBA" << "LIBB"); + propertyName = QStringList() << "dummy" << "productName"; + propertyValue = product->moduleProperties->property(propertyName); + QCOMPARE(propertyValue.toString(), QString("libE")); + + product = products.value("depender"); + QVERIFY(!!product); + QCOMPARE(product->modules.size(), size_t(2)); + for (const ResolvedModulePtr &m : product->modules) { + QVERIFY2(m->name == QString("qbs") || m->name == QString("dependency"), + qPrintable(m->name)); + } + QCOMPARE(product->productProperties.value("featureX").toBool(), true); + QCOMPARE(product->productProperties.value("featureY").toBool(), false); + QCOMPARE(product->productProperties.value("featureZ").toBool(), true); + + product = products.value("broken_cycle3"); + QVERIFY(!!product); + QCOMPARE(product->modules.size(), size_t(3)); + for (const ResolvedModulePtr &m : 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; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::fileContextProperties() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("filecontextproperties.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(!!product); + QVariantMap cfg = product->productProperties; + QCOMPARE(cfg.value("narf").toString(), defaultParameters.projectFilePath()); + QString dirPath = QFileInfo(defaultParameters.projectFilePath()).absolutePath(); + QCOMPARE(cfg.value("zort").toString(), dirPath); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::fileInProductAndModule_data() +{ + QTest::addColumn("file1IsTarget"); + QTest::addColumn("file2IsTarget"); + QTest::addColumn("addFileToProduct"); + QTest::addColumn("successExpected"); + QTest::newRow("file occurs twice in module as non-target") << false << false << false << false; + QTest::newRow("file occurs twice in module as non-target, and in product as well") + << false << false << true << false; + QTest::newRow("file occurs in module as non-target and target") + << false << true << false << true; + QTest::newRow("file occurs in module as non-target and target, and in product as well") + << false << true << true << false; + QTest::newRow("file occurs in module as target and non-target") + << true << false << false << true; + QTest::newRow("file occurs in module as target and non-target, and in product as well") + << true << false << true << false; + QTest::newRow("file occurs twice in module as target") << true << true << false << false; + QTest::newRow("file occurs twice in module as target, and in product as well") + << true << true << true << false; +} + +void TestLanguage::fileInProductAndModule() +{ + bool exceptionCaught = false; + QFETCH(bool, file1IsTarget); + QFETCH(bool, file2IsTarget); + QFETCH(bool, addFileToProduct); + QFETCH(bool, successExpected); + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("file-in-product-and-module.qbs")); + params.setOverriddenValues(QVariantMap{ + std::make_pair("modules.module_with_file.file1IsTarget", file1IsTarget), + std::make_pair("modules.module_with_file.file2IsTarget", file2IsTarget), + std::make_pair("products.p.addFileToProduct", addFileToProduct), + }); + project = loader->loadProject(params); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 1); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + QVERIFY2(e.toString().contains("Duplicate"), qPrintable(e.toString())); + } + QCOMPARE(exceptionCaught, !successExpected); +} + +void TestLanguage::getNativeSetting() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("getNativeSetting.qbs")); + project = loader->loadProject(defaultParameters); + + QString expectedTargetName; + if (HostOsInfo::isMacosHost()) + expectedTargetName = QStringLiteral("Mac OS X"); + else if (HostOsInfo::isWindowsHost()) + expectedTargetName = QStringLiteral("Windows"); + else + expectedTargetName = QStringLiteral("Unix"); + + QVERIFY(!!project); + QHash products; + for (const ResolvedProductPtr &product : project->allProducts()) + products.insert(product->targetName, product); + ResolvedProductPtr product = products.value(expectedTargetName); + QVERIFY(!!product); + ResolvedProductPtr product2 = products.value(QStringLiteral("fallback")); + QVERIFY(!!product2); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::groupConditions_data() +{ + QTest::addColumn("groupCount"); + QTest::addColumn>("groupEnabled"); + QTest::newRow("init") << size_t(0) << std::vector(); + QTest::newRow("no_condition_no_group") + << size_t(1) << std::vector{ true }; + QTest::newRow("no_condition") + << size_t(2) << std::vector{ true, true }; + QTest::newRow("true_condition") + << size_t(2) << std::vector{ true, true }; + QTest::newRow("false_condition") + << size_t(2) << std::vector{ true, false }; + QTest::newRow("true_condition_from_product") + << size_t(2) << std::vector{ true, true }; + QTest::newRow("true_condition_from_project") + << size_t(2) << std::vector{ true, true }; + QTest::newRow("condition_accessing_module_property") + << size_t(2) << std::vector{ true, false }; + QTest::newRow("cleanup") << size_t(0) << std::vector(); +} + +void TestLanguage::groupConditions() +{ + HANDLE_INIT_CLEANUP_DATATAGS("groupconditions.qbs"); + QFETCH(size_t, groupCount); + QFETCH(std::vector, groupEnabled); + QCOMPARE(groupCount, groupEnabled.size()); + const QHash products = productsFromProject(project); + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + ResolvedProductPtr product = products.value(productName); + QVERIFY(!!product); + QCOMPARE(product->name, productName); + QCOMPARE(product->groups.size(), groupCount); + for (size_t i = 0; i < groupCount; ++i) { + if (product->groups.at(i)->enabled != groupEnabled.at(i)) { + QFAIL(qPrintable( + QString("groups.at(%1)->enabled != %2").arg(i).arg(groupEnabled.at(i)))); + } + } +} + +void TestLanguage::groupName() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("groupname.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 2); + + ResolvedProductPtr product = products.value("MyProduct"); + QVERIFY(!!product); + QCOMPARE(product->groups.size(), size_t(2)); + GroupConstPtr group = product->groups.at(0); + QVERIFY(!!group); + QCOMPARE(group->name, QString("MyProduct")); + group = product->groups.at(1); + QVERIFY(!!group); + QCOMPARE(group->name, QString("MyProduct.MyGroup")); + + product = products.value("My2ndProduct"); + QVERIFY(!!product); + QCOMPARE(product->groups.size(), size_t(3)); + group = product->groups.at(0); + QVERIFY(!!group); + QCOMPARE(group->name, QString("My2ndProduct")); + group = product->groups.at(1); + QVERIFY(!!group); + QCOMPARE(group->name, QString("My2ndProduct.MyGroup")); + group = product->groups.at(2); + QVERIFY(!!group); + QCOMPARE(group->name, QString("Group 2")); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::homeDirectory() +{ + try { + defaultParameters.setProjectFilePath(testProject("homeDirectory.qbs")); + ResolvedProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 1); + + ResolvedProductPtr product = products.value("home"); + QVERIFY(!!product); + + QDir dir = QDir::home(); + QCOMPARE(product->productProperties.value("home").toString(), dir.path()); + QCOMPARE(product->productProperties.value("homeSlash").toString(), dir.path()); + + dir.cdUp(); + QCOMPARE(product->productProperties.value("homeUp").toString(), dir.path()); + + dir = QDir::home(); + QCOMPARE(product->productProperties.value("homeFile").toString(), + dir.filePath("a")); + + QCOMPARE(product->productProperties.value("bogus1").toString(), + FileInfo::resolvePath(product->sourceDirectory, QStringLiteral("a~b"))); + QCOMPARE(product->productProperties.value("bogus2").toString(), + FileInfo::resolvePath(product->sourceDirectory, QStringLiteral("a/~/bb"))); + QCOMPARE(product->productProperties.value("user").toString(), + FileInfo::resolvePath(product->sourceDirectory, QStringLiteral("~foo/bar"))); + } + catch (const ErrorInfo &e) { + qDebug() << e.toString(); + } +} + +void TestLanguage::identifierSearch_data() +{ + QTest::addColumn("expectedHasNarf"); + QTest::addColumn("expectedHasZort"); + QTest::addColumn("sourceCode"); + QTest::newRow("no narf, no zort") << false << false << QString( + "Product {\n" + " name: {\n" + " var foo = 'bar';\n" + " console.info(foo);\n" + " return foo;\n" + " }\n" + "}\n"); + QTest::newRow("narf, no zort") << true << false << QString( + "Product {\n" + " name: {\n" + " var foo = 'zort';\n" + " console.info(narf + foo);\n" + " return foo;\n" + " }\n" + "}\n"); + QTest::newRow("no narf, zort") << false << true << QString( + "Product {\n" + " name: {\n" + " var foo = 'narf';\n" + " console.info(zort + foo);\n" + " return foo;\n" + " }\n" + "}\n"); + QTest::newRow("narf, zort") << true << true << QString( + "Product {\n" + " name: {\n" + " var foo = narf;\n" + " foo = foo + zort;\n" + " return foo;\n" + " }\n" + "}\n"); + QTest::newRow("2 narfs, 1 zort") << true << true << QString( + "Product {\n" + " name: {\n" + " var foo = narf;\n" + " foo = narf + foo + zort;\n" + " return foo;\n" + " }\n" + "}\n"); +} + +void TestLanguage::identifierSearch() +{ + QFETCH(bool, expectedHasNarf); + QFETCH(bool, expectedHasZort); + QFETCH(QString, sourceCode); + + bool hasNarf = !expectedHasNarf; + bool hasZort = !expectedHasZort; + IdentifierSearch isearch; + isearch.add("narf", &hasNarf); + isearch.add("zort", &hasZort); + + QbsQmlJS::Engine engine; + QbsQmlJS::Lexer lexer(&engine); + lexer.setCode(sourceCode, 1); + QbsQmlJS::Parser parser(&engine); + QVERIFY(parser.parse()); + QVERIFY(parser.ast()); + isearch.start(parser.ast()); + QCOMPARE(hasNarf, expectedHasNarf); + QCOMPARE(hasZort, expectedHasZort); +} + +void TestLanguage::idUsage() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("idusage.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 5); + QVERIFY(products.contains("product1_1")); + QVERIFY(products.contains("product2_2")); + QVERIFY(products.contains("product3_3")); + ResolvedProductPtr product4 = products.value("product4_4"); + QVERIFY(!!product4); + auto product4Property = [&product4] (const char *name) { + return product4->productProperties.value(QString::fromUtf8(name)).toString(); + }; + QCOMPARE(product4Property("productName"), product4->name); + QCOMPARE(product4Property("productNameInBaseOfBase"), product4->name); + GroupPtr group = findByName(product4->groups, "group in base product"); + QVERIFY(!!group); + QCOMPARE(qPrintable(group->prefix), "group in base product"); + group = findByName(product4->groups, "another group in base product"); + QVERIFY(!!group); + QCOMPARE(qPrintable(group->prefix), "another group in base product"); + ResolvedProductPtr product5 = products.value("product5"); + QVERIFY(!!product5); + QCOMPARE(product5->moduleProperties->moduleProperty("deepdummy.deep.moat", "zort") + .toString(), QString("zort in dummy")); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QVERIFY(!exceptionCaught); +} + +void TestLanguage::idUniqueness() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("id-uniqueness.qbs")); + loader->loadProject(defaultParameters); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + const QList items = e.items(); + QCOMPARE(items.size(), 3); + QCOMPARE(items.at(0).toString(), QString::fromUtf8("The id 'baseProduct' is not unique.")); + QVERIFY(items.at(1).toString().contains("id-uniqueness.qbs:5:5 First occurrence is here.")); + QVERIFY(items.at(2).toString().contains("id-uniqueness.qbs:8:5 Next occurrence is here.")); + } + QVERIFY(exceptionCaught); +} + +void TestLanguage::importCollection() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("import-collection/project.qbs")); + const TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + const ResolvedProductConstPtr product = products.value("da product"); + QCOMPARE(product->productProperties.value("targetName").toString(), + QLatin1String("C1f1C1f2C2f1C2f2")); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QVERIFY(!exceptionCaught); +} + +void TestLanguage::inheritedPropertiesItems_data() +{ + QTest::addColumn("buildVariant"); + QTest::addColumn("productName"); + QTest::newRow("debug build") << "debug" << "product_debug"; + QTest::newRow("release build") << "release" << "product_release"; +} + +void TestLanguage::inheritedPropertiesItems() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + QFETCH(QString, buildVariant); + QFETCH(QString, productName); + params.setProjectFilePath + (testProject("inherited-properties-items/inherited-properties-items.qbs")); + params.setOverriddenValues(QVariantMap{std::make_pair("qbs.buildVariant", buildVariant)}); + TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 1); + QVERIFY(!!products.value(productName)); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QVERIFY(!exceptionCaught); +} + +void TestLanguage::invalidBindingInDisabledItem() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("invalidBindingInDisabledItem.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 2); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QVERIFY(!exceptionCaught); +} + +void TestLanguage::invalidOverrides() +{ + QFETCH(QString, key); + QFETCH(QString, expectedErrorMessage); + const bool successExpected = expectedErrorMessage.isEmpty(); + bool exceptionCaught = false; + try { + qbs::SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("invalid-overrides.qbs")); + params.setOverriddenValues(QVariantMap{std::make_pair(key, true)}); + const TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + if (successExpected) + qDebug() << e.toString(); + else + QVERIFY2(e.toString().contains(expectedErrorMessage), qPrintable(e.toString())); + } + QEXPECT_FAIL("no such module in product", "not easily checkable", Continue); + QCOMPARE(!exceptionCaught, successExpected); +} + +void TestLanguage::invalidOverrides_data() +{ + QTest::addColumn("key"); + QTest::addColumn("expectedErrorMessage"); + + QTest::newRow("no such project") << "projects.myproject.x" + << QString("Unknown project 'myproject' in property override."); + QTest::newRow("no such project property") << "projects.My.Project.y" + << QString("Unknown property: projects.My.Project.y"); + QTest::newRow("valid project property override") << "projects.My.Project.x" << QString(); + QTest::newRow("no such product") << "products.myproduct.x" + << QString("Unknown product 'myproduct' in property override."); + QTest::newRow("no such product (with module)") << "products.myproduct.cpp.useRPaths" + << QString("Unknown product 'myproduct' in property override."); + QTest::newRow("no such product property") << "products.MyProduct.y" + << QString("Unknown property: products.MyProduct.y"); + QTest::newRow("valid product property override") << "products.MyProduct.x" << QString(); + + // This cannot be an error, because the semantics are "if some product in the project has + // such a module, then set that property", and the code that does the property overrides + // does not have a global view. + QTest::newRow("no such module") << "modules.blubb.x" << QString(); + + QTest::newRow("no such module in product") << "products.MyProduct.cpp.useRPaths" + << QString("Invalid module 'cpp' in property override."); + QTest::newRow("no such module property") << "modules.cpp.blubb" + << QString("Unknown property: modules.cpp.blubb"); + QTest::newRow("no such module property (per product)") << "products.MyOtherProduct.cpp.blubb" + << QString("Unknown property: products.MyOtherProduct.cpp.blubb"); + QTest::newRow("valid per-product module property override") + << "products.MyOtherProduct.cpp.useRPaths" << QString(); +} + +class JSSourceValueCreator +{ + FileContextPtr m_fileContext; + QList m_strings; +public: + JSSourceValueCreator(const FileContextPtr &fileContext) + : m_fileContext(fileContext) + { + } + + ~JSSourceValueCreator() + { + qDeleteAll(m_strings); + } + + JSSourceValuePtr create(const QString &sourceCode) + { + JSSourceValuePtr value = JSSourceValue::create(); + value->setFile(m_fileContext); + const auto str = new QString(sourceCode); + m_strings.push_back(str); + value->setSourceCode(QStringRef(str)); + return value; + } +}; + +void TestLanguage::itemPrototype() +{ + FileContextPtr fileContext = FileContext::create(); + fileContext->setFilePath("/dev/null"); + JSSourceValueCreator sourceValueCreator(fileContext); + ItemPool pool; + Item *proto = Item::create(&pool, ItemType::Product); + proto->setProperty("x", sourceValueCreator.create("1")); + proto->setProperty("y", sourceValueCreator.create("1")); + Item *item = Item::create(&pool, ItemType::Product); + item->setPrototype(proto); + item->setProperty("y", sourceValueCreator.create("x + 1")); + item->setProperty("z", sourceValueCreator.create("2")); + + Evaluator evaluator(m_engine); + QCOMPARE(evaluator.property(proto, "x").toVariant().toInt(), 1); + QCOMPARE(evaluator.property(proto, "y").toVariant().toInt(), 1); + QVERIFY(!evaluator.property(proto, "z").isValid()); + QCOMPARE(evaluator.property(item, "x").toVariant().toInt(), 1); + QCOMPARE(evaluator.property(item, "y").toVariant().toInt(), 2); + QCOMPARE(evaluator.property(item, "z").toVariant().toInt(), 2); +} + +void TestLanguage::itemScope() +{ + FileContextPtr fileContext = FileContext::create(); + fileContext->setFilePath("/dev/null"); + JSSourceValueCreator sourceValueCreator(fileContext); + ItemPool pool; + Item *scope1 = Item::create(&pool, ItemType::Scope); + scope1->setProperty("x", sourceValueCreator.create("1")); + Item *scope2 = Item::create(&pool, ItemType::Scope); + scope2->setScope(scope1); + scope2->setProperty("y", sourceValueCreator.create("x + 1")); + Item *item = Item::create(&pool, ItemType::Scope); + item->setScope(scope2); + item->setProperty("z", sourceValueCreator.create("x + y")); + + Evaluator evaluator(m_engine); + QCOMPARE(evaluator.property(scope1, "x").toVariant().toInt(), 1); + QCOMPARE(evaluator.property(scope2, "y").toVariant().toInt(), 2); + QVERIFY(!evaluator.property(scope2, "x").isValid()); + QCOMPARE(evaluator.property(item, "z").toVariant().toInt(), 3); +} + +void TestLanguage::jsExtensions() +{ + QFile file(testProject("jsextensions.js")); + QVERIFY(file.open(QFile::ReadOnly)); + QTextStream ts(&file); + QString code = ts.readAll(); + QVERIFY(!code.isEmpty()); + QScriptValue evaluated = m_engine->evaluate(code, file.fileName(), 1); + if (m_engine->hasErrorOrException(evaluated)) { + qDebug() << m_engine->uncaughtExceptionBacktrace(); + QFAIL(qPrintable(m_engine->lastErrorString(evaluated))); + } +} + +void TestLanguage::jsImportUsedInMultipleScopes_data() +{ + QTest::addColumn("buildVariant"); + QTest::addColumn("expectedProductName"); + QTest::newRow("debug") << QString("debug") << QString("MyProduct_debug"); + QTest::newRow("release") << QString("release") << QString("MyProduct"); +} + +void TestLanguage::jsImportUsedInMultipleScopes() +{ + QFETCH(QString, buildVariant); + QFETCH(QString, expectedProductName); + + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("jsimportsinmultiplescopes.qbs")); + params.setOverriddenValues({std::make_pair(QStringLiteral("qbs.buildVariant"), + buildVariant)}); + params.expandBuildConfiguration(); + TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 1); + ResolvedProductPtr product = products.values().front(); + QVERIFY(!!product); + QCOMPARE(product->name, expectedProductName); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QVERIFY(!exceptionCaught); +} + +void TestLanguage::moduleMergingVariantValues() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath + (testProject("module-merging-variant-values/module-merging-variant-values.qbs")); + params.expandBuildConfiguration(); + const TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + QCOMPARE(int(project->products.size()), 2); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::modulePrioritizationBySearchPath_data() +{ + QTest::addColumn("searchPaths"); + QTest::addColumn("expectedVariant"); + QTest::newRow("foo has priority") << QStringList{"./foo", "./bar"} << "foo"; + QTest::newRow("bar has priority") << QStringList{"./bar", "./foo"} << "bar"; +} + +void TestLanguage::modulePrioritizationBySearchPath() +{ + QFETCH(QStringList, searchPaths); + QFETCH(QString, expectedVariant); + + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("module-prioritization-by-search-path/project.qbs")); + params.setOverriddenValues({std::make_pair(QStringLiteral("project.qbsSearchPaths"), + searchPaths)}); + params.expandBuildConfiguration(); + TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 1); + ResolvedProductPtr product = products.values().front(); + QVERIFY(!!product); + const QString actualVariant = product->moduleProperties->moduleProperty + ("conflicting-instances", "moduleVariant").toString(); + QCOMPARE(actualVariant, expectedVariant); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QVERIFY(!exceptionCaught); +} + +void TestLanguage::moduleProperties_data() +{ + QTest::addColumn("propertyName"); + QTest::addColumn("expectedValue"); + QTest::newRow("init") << QString() << QVariant(); + QTest::newRow("merge_lists") + << "defines" + << QVariant(QStringList() << "THE_PRODUCT" << "QT_NETWORK" << "QT_GUI" << "QT_CORE"); + QTest::newRow("merge_lists_and_values") + << "defines" + << QVariant(QStringList() << "THE_PRODUCT" << "QT_NETWORK" << "QT_GUI" << "QT_CORE"); + QTest::newRow("merge_lists_with_duplicates") + << "cxxFlags" + << QVariant(QStringList() << "-foo" << "BAR" << "-foo" << "BAZ"); + QTest::newRow("merge_lists_with_prototype_values") + << "rpaths" + << QVariant(QStringList() << "/opt/qt/lib" << "$ORIGIN"); + QTest::newRow("list_property_that_references_product") + << "listProp" + << QVariant(QStringList() << "x" << "123"); + QTest::newRow("list_property_depending_on_overridden_property") + << "listProp2" + << QVariant(QStringList() << "PRODUCT_STUFF" << "DEFAULT_STUFF" << "EXTRA_STUFF"); + QTest::newRow("overridden_list_property") + << "listProp" + << QVariant(QStringList() << "PRODUCT_STUFF"); + QTest::newRow("shadowed-list-property") + << "defines" + << QVariant(QStringList() << "MyProject" << "shadowed-list-property"); + QTest::newRow("shadowed-scalar-property") + << "someString" + << QVariant(QString("MyProject_shadowed-scalar-property")); + QTest::newRow("merged-varlist") + << "varListProp" + << QVariant(QVariantList() << QVariantMap({std::make_pair("d", "product")}) + << QVariantMap({std::make_pair("c", "qtcore")}) + << QVariantMap({std::make_pair("a", true), + std::make_pair("b", QVariant())})); + QTest::newRow("cleanup") << QString() << QVariant(); +} + +void TestLanguage::moduleProperties() +{ + HANDLE_INIT_CLEANUP_DATATAGS("moduleproperties.qbs"); + QFETCH(QString, propertyName); + QFETCH(QVariant, expectedValue); + QHash products = productsFromProject(project); + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + ResolvedProductPtr product = products.value(productName); + QVERIFY(!!product); + const QVariant value = product->moduleProperties->moduleProperty("dummy", propertyName); + QCOMPARE(value, expectedValue); +} + +void TestLanguage::modulePropertiesInGroups() +{ + defaultParameters.setProjectFilePath(testProject("modulepropertiesingroups.qbs")); + bool exceptionCaught = false; + try { + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + const QHash products = productsFromProject(project); + ResolvedProductPtr product = products.value("grouptest"); + QVERIFY(!!product); + GroupConstPtr g1; + GroupConstPtr g11; + GroupConstPtr g12; + GroupConstPtr g2; + GroupConstPtr g21; + GroupConstPtr g211; + for (const GroupPtr &g : product->groups) { + if (g->name == "g1") + g1= g; + else if (g->name == "g2") + g2 = g; + else if (g->name == "g1.1") + g11 = g; + else if (g->name == "g1.2") + g12 = g; + else if (g->name == "g2.1") + g21 = g; + else if (g->name == "g2.1.1") + g211 = g; + } + QVERIFY(!!g1); + QVERIFY(!!g2); + QVERIFY(!!g11); + QVERIFY(!!g12); + QVERIFY(!!g21); + QVERIFY(!!g211); + + const QVariantMap productProps = product->moduleProperties->value(); + const auto &productGmod1List1 = moduleProperty(productProps, "gmod.gmod1", "gmod1_list1") + .toStringList(); + QCOMPARE(productGmod1List1, QStringList() << "gmod1_list1_proto" << "gmod1_string_proto"); + const auto &productGmod1List2 = moduleProperty(productProps, "gmod.gmod1", "gmod1_list2") + .toStringList(); + QCOMPARE(productGmod1List2, QStringList() << "grouptest" << "gmod1_string_proto" + << "gmod1_list2_proto"); + const auto &productGmod1List3 = moduleProperty(productProps, "gmod.gmod1", "gmod1_list3") + .toStringList(); + QCOMPARE(productGmod1List3, QStringList() << "product" << "gmod1_string_proto"); + const int productP0 = moduleProperty(productProps, "gmod.gmod1", "p0").toInt(); + QCOMPARE(productP0, 1); + const int productDepProp = moduleProperty(productProps, "gmod.gmod1", "depProp").toInt(); + QCOMPARE(productDepProp, 0); + const auto &productGmod2String = moduleProperty(productProps, "gmod2", "gmod2_string") + .toString(); + QCOMPARE(productGmod2String, QString("gmod1_string_proto")); + const auto &productGmod2List = moduleProperty(productProps, "gmod2", "gmod2_list") + .toStringList(); + QCOMPARE(productGmod2List, QStringList() << "gmod1_string_proto" << "commonName_in_gmod1" + << "gmod4_string_proto_gmod3_string_proto" << "gmod3_string_proto" + << "gmod2_list_proto"); + + const QVariantMap g1Props = g1->properties->value(); + const auto &g1Gmod1List1 = moduleProperty(g1Props, "gmod.gmod1", "gmod1_list1") + .toStringList(); + QCOMPARE(g1Gmod1List1, QStringList() << "gmod1_list1_proto" << "g1"); + const auto &g1Gmod1List2 = moduleProperty(g1Props, "gmod.gmod1", "gmod1_list2") + .toStringList(); + QCOMPARE(g1Gmod1List2, QStringList() << "grouptest" << "gmod1_string_proto" + << "gmod1_list2_proto" << "g1"); + const auto &g1Gmod1List3 = moduleProperty(g1Props, "gmod.gmod1", "gmod1_list3") + .toStringList(); + QCOMPARE(g1Gmod1List3, QStringList() << "product" << "g1"); + const int g1P0 = moduleProperty(g1Props, "gmod.gmod1", "p0").toInt(); + QCOMPARE(g1P0, 3); + const int g1DepProp = moduleProperty(g1Props, "gmod.gmod1", "depProp").toInt(); + QCOMPARE(g1DepProp, 1); + const auto &g1Gmod2String = moduleProperty(g1Props, "gmod2", "gmod2_string").toString(); + QCOMPARE(g1Gmod2String, QString("g1")); + const auto &g1Gmod2List = moduleProperty(g1Props, "gmod2", "gmod2_list") + .toStringList(); + QCOMPARE(g1Gmod2List, QStringList() << "g1" << "commonName_in_gmod1" << "g1_gmod4_g1_gmod3" + << "g1_gmod3" << "gmod2_list_proto"); + + const QVariantMap g11Props = g11->properties->value(); + const auto &g11Gmod1List1 = moduleProperty(g11Props, "gmod.gmod1", "gmod1_list1") + .toStringList(); + QCOMPARE(g11Gmod1List1, QStringList() << "gmod1_list1_proto" << "g1.1"); + const auto &g11Gmod1List2 = moduleProperty(g11Props, "gmod.gmod1", "gmod1_list2") + .toStringList(); + QCOMPARE(g11Gmod1List2, QStringList() << "grouptest" << "gmod1_string_proto" + << "gmod1_list2_proto" << "g1" << "g1.1"); + const auto &g11Gmod1List3 = moduleProperty(g11Props, "gmod.gmod1", "gmod1_list3") + .toStringList(); + QCOMPARE(g11Gmod1List3, QStringList() << "product" << "g1.1"); + const int g11P0 = moduleProperty(g11Props, "gmod.gmod1", "p0").toInt(); + QCOMPARE(g11P0, 5); + const int g11DepProp = moduleProperty(g11Props, "gmod.gmod1", "depProp").toInt(); + QCOMPARE(g11DepProp, 2); + const auto &g11Gmod2String = moduleProperty(g11Props, "gmod2", "gmod2_string").toString(); + QCOMPARE(g11Gmod2String, QString("g1.1")); + const auto &g11Gmod2List = moduleProperty(g11Props, "gmod2", "gmod2_list") + .toStringList(); + QCOMPARE(g11Gmod2List, QStringList() << "g1.1" << "commonName_in_gmod1" + << "g1.1_gmod4_g1.1_gmod3" << "g1.1_gmod3" << "gmod2_list_proto"); + + const QVariantMap g12Props = g12->properties->value(); + const auto &g12Gmod1List1 = moduleProperty(g12Props, "gmod.gmod1", "gmod1_list1") + .toStringList(); + QCOMPARE(g12Gmod1List1, QStringList() << "gmod1_list1_proto" << "g1.2"); + const auto &g12Gmod1List2 = moduleProperty(g12Props, "gmod.gmod1", "gmod1_list2") + .toStringList(); + QCOMPARE(g12Gmod1List2, QStringList() << "grouptest" << "gmod1_string_proto" + << "gmod1_list2_proto" << "g1" << "g1.2"); + const auto &g12Gmod1List3 = moduleProperty(g12Props, "gmod.gmod1", "gmod1_list3") + .toStringList(); + QCOMPARE(g12Gmod1List3, QStringList() << "product" << "g1.2"); + const int g12P0 = moduleProperty(g12Props, "gmod.gmod1", "p0").toInt(); + QCOMPARE(g12P0, 9); + const int g12DepProp = moduleProperty(g12Props, "gmod.gmod1", "depProp").toInt(); + QCOMPARE(g12DepProp, 1); + const auto &g12Gmod2String = moduleProperty(g12Props, "gmod2", "gmod2_string").toString(); + QCOMPARE(g12Gmod2String, QString("g1.2")); + const auto &g12Gmod2List = moduleProperty(g12Props, "gmod2", "gmod2_list") + .toStringList(); + QCOMPARE(g12Gmod2List, QStringList() << "g1.2" << "commonName_in_gmod1" + << "g1_gmod4_g1.2_gmod3" << "g1.2_gmod3" << "gmod2_list_proto"); + + const QVariantMap g2Props = g2->properties->value(); + const auto &g2Gmod1List1 = moduleProperty(g2Props, "gmod.gmod1", "gmod1_list1") + .toStringList(); + QCOMPARE(g2Gmod1List1, QStringList() << "gmod1_list1_proto" << "g2"); + const auto &g2Gmod1List2 = moduleProperty(g2Props, "gmod.gmod1", "gmod1_list2") + .toStringList(); + QCOMPARE(g2Gmod1List2, QStringList() << "grouptest" << "g2" << "gmod1_list2_proto"); + const int g2P0 = moduleProperty(g2Props, "gmod.gmod1", "p0").toInt(); + QCOMPARE(g2P0, 6); + const int g2DepProp = moduleProperty(g2Props, "gmod.gmod1", "depProp").toInt(); + QCOMPARE(g2DepProp, 2); + const auto &g2Gmod2String = moduleProperty(g2Props, "gmod2", "gmod2_string").toString(); + QCOMPARE(g2Gmod2String, QString("g2")); + const auto &g2Gmod2List = moduleProperty(g2Props, "gmod2", "gmod2_list") + .toStringList(); + QCOMPARE(g2Gmod2List, QStringList() << "g2" << "commonName_in_gmod1" << "g2_gmod4_g2_gmod3" + << "g2_gmod3" << "gmod2_list_proto"); + + const QVariantMap g21Props = g21->properties->value(); + const auto &g21Gmod1List1 = moduleProperty(g21Props, "gmod.gmod1", "gmod1_list1") + .toStringList(); + QCOMPARE(g21Gmod1List1, QStringList() << "gmod1_list1_proto" << "g2"); + const auto &g21Gmod1List2 = moduleProperty(g21Props, "gmod.gmod1", "gmod1_list2") + .toStringList(); + QEXPECT_FAIL(0, "no re-eval when no module props set", Continue); + QCOMPARE(g21Gmod1List2, QStringList() << "grouptest" << "g2.1" << "gmod1_list2_proto"); + const int g21P0 = moduleProperty(g21Props, "gmod.gmod1", "p0").toInt(); + QCOMPARE(g21P0, 6); + const int g21DepProp = moduleProperty(g21Props, "gmod.gmod1", "depProp").toInt(); + QCOMPARE(g21DepProp, 2); + const auto &g21Gmod2String = moduleProperty(g21Props, "gmod2", "gmod2_string").toString(); + QCOMPARE(g21Gmod2String, QString("g2")); + const auto &g21Gmod2List = moduleProperty(g21Props, "gmod2", "gmod2_list") + .toStringList(); + QEXPECT_FAIL(0, "no re-eval when no module props set", Continue); + QCOMPARE(g21Gmod2List, QStringList() << "g2" << "commonName_in_gmod1" + << "g2.1_gmod4_g2.1_gmod3" << "g2.1_gmod3" << "gmod2_list_proto"); + + const QVariantMap g211Props = g211->properties->value(); + const auto &g211Gmod1List1 = moduleProperty(g211Props, "gmod.gmod1", "gmod1_list1") + .toStringList(); + QCOMPARE(g211Gmod1List1, QStringList() << "gmod1_list1_proto" << "g2"); + const auto &g211Gmod1List2 = moduleProperty(g211Props, "gmod.gmod1", "gmod1_list2") + .toStringList(); + QCOMPARE(g211Gmod1List2, QStringList() << "g2.1.1"); + const int g211P0 = moduleProperty(g211Props, "gmod.gmod1", "p0").toInt(); + QCOMPARE(g211P0, 17); + const int g211DepProp = moduleProperty(g211Props, "gmod.gmod1", "depProp").toInt(); + QCOMPARE(g211DepProp, 2); + const auto &g211Gmod2String + = moduleProperty(g211Props, "gmod2", "gmod2_string").toString(); + QEXPECT_FAIL(0, "re-eval not triggered", Continue); + QCOMPARE(g211Gmod2String, QString("g2.1.1")); + const auto &g211Gmod2List = moduleProperty(g211Props, "gmod2", "gmod2_list") + .toStringList(); + QEXPECT_FAIL(0, "re-eval not triggered", Continue); + QCOMPARE(g211Gmod2List, QStringList() << "g2.1.1" << "commonName_in_gmod1" + << "g2.1.1_gmod4_g2.1.1_gmod3" << "g2.1.1_gmod3" << "gmod2_list_proto"); + + product = products.value("grouptest2"); + QVERIFY(!!product); + g1.reset(); + g11.reset(); + for (const GroupPtr &g : product->groups) { + if (g->name == "g1") + g1= g; + else if (g->name == "g1.1") + g11 = g; + } + QVERIFY(!!g1); + QVERIFY(!!g11); + QCOMPARE(moduleProperty(g1->properties->value(), "gmod.gmod1", "gmod1_list2") + .toStringList(), QStringList({"G1"})); + QCOMPARE(moduleProperty(g11->properties->value(), "gmod.gmod1", "gmod1_list2") + .toStringList(), + moduleProperty(g1->properties->value(), "gmod.gmod1", "gmod1_list2") + .toStringList()); + QCOMPARE(moduleProperty(g11->properties->value(), "gmod.gmod1", "gmod1_string").toString(), + QString("G1.1")); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::modulePropertyOverridesPerProduct() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setOverriddenValues({ + std::make_pair("modules.dummy.rpaths", QStringList({"/usr/lib"})), + std::make_pair("modules.dummy.someString", "m"), + std::make_pair("products.b.dummy.someString", "b"), + std::make_pair("products.c.dummy.someString", "c"), + std::make_pair("products.c.dummy.rpaths", QStringList({"/home", "/tmp"})) + }); + params.setProjectFilePath( + testProject("module-property-overrides-per-product.qbs")); + const TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 3); + const ResolvedProductConstPtr a = products.value("a"); + QVERIFY(!!a); + const ResolvedProductConstPtr b = products.value("b"); + QVERIFY(!!b); + const ResolvedProductConstPtr c = products.value("c"); + QVERIFY(!!c); + + const auto stringPropertyValue = [](const ResolvedProductConstPtr &p) -> QString + { + return p->moduleProperties->moduleProperty("dummy", "someString").toString(); + }; + const auto listPropertyValue = [](const ResolvedProductConstPtr &p) -> QStringList + { + return p->moduleProperties->moduleProperty("dummy", "rpaths").toStringList(); + }; + const auto productPropertyValue = [](const ResolvedProductConstPtr &p) -> QStringList + { + return p->productProperties.value("rpaths").toStringList(); + }; + + QCOMPARE(stringPropertyValue(a), QString("m")); + QCOMPARE(stringPropertyValue(b), QString("b")); + QCOMPARE(stringPropertyValue(c), QString("c")); + QCOMPARE(listPropertyValue(a), QStringList({"/usr/lib"})); + QCOMPARE(listPropertyValue(b), QStringList({"/usr/lib"})); + QCOMPARE(listPropertyValue(c), QStringList({"/home", "/tmp"})); + QCOMPARE(listPropertyValue(a), productPropertyValue(a)); + QCOMPARE(listPropertyValue(b), productPropertyValue(b)); + QCOMPARE(listPropertyValue(c), productPropertyValue(c)); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::moduleScope() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("modulescope.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 1); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(!!product); + + auto intModuleValue = [product] (const QString &name) -> int + { + return product->moduleProperties->moduleProperty("scopemod", name).toInt(); + }; + + QCOMPARE(intModuleValue("a"), 2); // overridden in module instance + QCOMPARE(intModuleValue("b"), 1); // genuine + QCOMPARE(intModuleValue("c"), 3); // genuine, dependent on overridden value + QCOMPARE(intModuleValue("d"), 2); // genuine, dependent on genuine value + QCOMPARE(intModuleValue("e"), 1); // genuine + QCOMPARE(intModuleValue("f"), 2); // overridden + QCOMPARE(intModuleValue("g"), 156); // overridden, dependent on product properties + QCOMPARE(intModuleValue("h"), 158); // overridden, base dependent on product properties + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); + +} + +void TestLanguage::modules_data() +{ + QTest::addColumn("expectedModulesInProduct"); + QTest::addColumn("expectedProductProperty"); + QTest::newRow("init") << QStringList(); + QTest::newRow("no_modules") + << (QStringList() << "qbs") + << QString(); + QTest::newRow("qt_core") + << (QStringList() << "qbs" << "dummy" << "dummyqt.core") + << QString("1.2.3"); + QTest::newRow("qt_gui") + << (QStringList() << "qbs" << "dummy" << "dummyqt.core" << "dummyqt.gui") + << QString("guiProperty"); + QTest::newRow("qt_gui_network") + << (QStringList() << "qbs" << "dummy" << "dummyqt.core" << "dummyqt.gui" + << "dummyqt.network") + << QString("guiProperty,networkProperty"); + QTest::newRow("deep_module_name") + << (QStringList() << "qbs" << "deepdummy.deep.moat" << "dummy") + << QString("abysmal"); + QTest::newRow("deep_module_name_submodule_syntax1") + << (QStringList() << "qbs" << "deepdummy.deep.moat" << "dummy") + << QString("abysmal"); + QTest::newRow("deep_module_name_submodule_syntax2") + << (QStringList() << "qbs" << "deepdummy.deep.moat" << "dummy") + << QString("abysmal"); + QTest::newRow("dummy_twice") + << (QStringList() << "qbs" << "dummy") + << QString(); + QTest::newRow("cleanup") << QStringList(); +} + +void TestLanguage::modules() +{ + HANDLE_INIT_CLEANUP_DATATAGS("modules.qbs"); + QFETCH(QStringList, expectedModulesInProduct); + QFETCH(QString, expectedProductProperty); + QHash products = productsFromProject(project); + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + const ResolvedProductPtr product = products.value(productName); + QVERIFY(!!product); + QCOMPARE(product->name, productName); + QStringList modulesInProduct; + for (ResolvedModuleConstPtr m : product->modules) + modulesInProduct += m->name; + modulesInProduct.sort(); + expectedModulesInProduct.sort(); + QCOMPARE(modulesInProduct, expectedModulesInProduct); + QCOMPARE(product->productProperties.value("foo").toString(), expectedProductProperty); +} + +void TestLanguage::multiplexedExports() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("multiplexed-exports.qbs")); + const TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + const auto products = project->allProducts(); + QCOMPARE(products.size(), size_t(4)); + std::set pVariants; + for (const auto &product : products) { + if (product->name != "p") + continue; + static const auto buildVariant = [](const ResolvedProductConstPtr &p) { + return p->moduleProperties->qbsPropertyValue("buildVariant").toString(); + }; + static const auto cppIncludePaths = [](const ResolvedProductConstPtr &p) { + return p->moduleProperties->moduleProperty("cpp", "includePaths").toStringList(); + }; + if (buildVariant(product) == "debug") { + pVariants.insert(product); + QCOMPARE(cppIncludePaths(product), QStringList("/d")); + } else if (buildVariant(product) == "release") { + pVariants.insert(product); + QCOMPARE(cppIncludePaths(product), QStringList("/r")); + } + } + QCOMPARE(int(pVariants.size()), 2); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +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("projectFileName"); + QTest::addColumn("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::nonApplicableModulePropertyInProfile() +{ + QFETCH(QString, targetOS); + QFETCH(QString, toolchain); + QFETCH(bool, successExpected); + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("non-applicable-module-property-in-profile.qbs")); + params.setOverriddenValues(QVariantMap{std::make_pair("project.targetOS", targetOS), + std::make_pair("project.toolchain", toolchain)}); + const TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + QVERIFY(successExpected); + } catch (const ErrorInfo &e) { + QVERIFY2(!successExpected, qPrintable(e.toString())); + QVERIFY2(e.toString().contains("Loading module 'multiple_backends' for product 'p' failed " + "due to invalid values in profile 'theProfile'"), + qPrintable(e.toString())); + QVERIFY2(e.toString().contains("backend3Prop"), qPrintable(e.toString())); + } +} + +void TestLanguage::nonApplicableModulePropertyInProfile_data() +{ + QTest::addColumn("targetOS"); + QTest::addColumn("toolchain"); + QTest::addColumn("successExpected"); + + QTest::newRow("no matching property (1)") << "os1" << QString() << false; + QTest::newRow("no matching property (2)") << "os2" << QString() << false; + + // The point here is that there's a second, lower-prioritized candidate with a matching + // condition that doesn't have the property. This candidate must not throw an error. + QTest::newRow("matching property") << "os2" << "tc" << true; +} + +void TestLanguage::nonRequiredProducts() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("non-required-products.qbs")); + QFETCH(bool, subProjectEnabled); + QFETCH(bool, dependeeEnabled); + QVariantMap overriddenValues; + if (!subProjectEnabled) + overriddenValues.insert("projects.subproject.condition", false); + else if (!dependeeEnabled) + overriddenValues.insert("products.dependee.condition", false); + params.setOverriddenValues(overriddenValues); + const TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + const auto products = productsFromProject(project); + QCOMPARE(products.size(), 4 + !!subProjectEnabled); + const ResolvedProductConstPtr dependee = products.value("dependee"); + QCOMPARE(subProjectEnabled, !!dependee); + if (dependee) + QCOMPARE(dependeeEnabled, dependee->enabled); + const ResolvedProductConstPtr depender = products.value("depender"); + QVERIFY(!!depender); + const QStringList defines = depender->moduleProperties->moduleProperty("dummy", "defines") + .toStringList(); + QCOMPARE(subProjectEnabled && dependeeEnabled, defines.contains("WITH_DEPENDEE")); + + for (const auto &name : std::vector({ "p3", "p2", "p1"})) { + const ResolvedProductConstPtr &product = products.value(name); + QVERIFY2(product, name); + QVERIFY2(!product->enabled, name); + } + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::nonRequiredProducts_data() +{ + QTest::addColumn("subProjectEnabled"); + QTest::addColumn("dependeeEnabled"); + QTest::newRow("dependee enabled") << true << true; + QTest::newRow("dependee disabled") << true << false; + QTest::newRow("sub project disabled") << false << true; +} + +void TestLanguage::outerInGroup() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("outerInGroup.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 1); + ResolvedProductPtr product = products.value("OuterInGroup"); + QVERIFY(!!product); + QCOMPARE(product->groups.size(), size_t(2)); + GroupPtr group = product->groups.at(0); + QVERIFY(!!group); + QCOMPARE(group->name, product->name); + QCOMPARE(group->files.size(), size_t(1)); + SourceArtifactConstPtr artifact = group->files.front(); + QVariant installDir = artifact->properties->qbsPropertyValue("installDir"); + QCOMPARE(installDir.toString(), QString("/somewhere")); + group = product->groups.at(1); + QVERIFY(!!group); + QCOMPARE(group->name, QString("Special Group")); + QCOMPARE(group->files.size(), size_t(1)); + artifact = group->files.front(); + installDir = artifact->properties->qbsPropertyValue("installDir"); + QCOMPARE(installDir.toString(), QString("/somewhere/else")); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::overriddenPropertiesAndPrototypes() +{ + bool exceptionCaught = false; + try { + QFETCH(QString, osName); + QFETCH(QString, backendName); + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("overridden-properties-and-prototypes.qbs")); + params.setOverriddenValues({std::make_pair("modules.qbs.targetPlatform", osName)}); + TopLevelProjectConstPtr project = loader->loadProject(params); + QVERIFY(!!project); + QCOMPARE(project->products.size(), size_t(1)); + QCOMPARE(project->products.front()->moduleProperties->moduleProperty( + "multiple_backends", "prop").toString(), backendName); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::overriddenPropertiesAndPrototypes_data() +{ + QTest::addColumn("osName"); + QTest::addColumn("backendName"); + QTest::newRow("first backend") << "os1" << "backend 1"; + QTest::newRow("second backend") << "os2" << "backend 2"; +} + +void TestLanguage::overriddenVariantProperty() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + const QVariantMap objectValue{std::make_pair("x", 1), std::make_pair("y", 2)}; + params.setOverriddenValues({std::make_pair("products.p.myObject", objectValue)}); + params.setProjectFilePath(testProject("overridden-variant-property.qbs")); + TopLevelProjectConstPtr project = loader->loadProject(params); + QVERIFY(!!project); + QCOMPARE(project->products.size(), size_t(1)); + QCOMPARE(project->products.front()->productProperties.value("myObject").toMap(), + objectValue); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::parameterTypes() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("parameter-types.qbs")); + loader->loadProject(defaultParameters); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::pathProperties() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("pathproperties.qbs")); + project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(!!product); + QString projectFileDir = QFileInfo(defaultParameters.projectFilePath()).absolutePath(); + const QVariantMap productProps = product->productProperties; + QCOMPARE(productProps.value("projectFileDir").toString(), projectFileDir); + QStringList filesInProjectFileDir = QStringList() + << FileInfo::resolvePath(projectFileDir, "aboutdialog.h") + << FileInfo::resolvePath(projectFileDir, "aboutdialog.cpp"); + QCOMPARE(productProps.value("filesInProjectFileDir").toStringList(), filesInProjectFileDir); + QStringList includePaths = product->moduleProperties->property( + QStringList() << "dummy" << "includePaths").toStringList(); + QCOMPARE(includePaths, QStringList() << projectFileDir); + QCOMPARE(productProps.value("base_fileInProductDir").toString(), + FileInfo::resolvePath(projectFileDir, QStringLiteral("foo"))); + QCOMPARE(productProps.value("base_fileInBaseProductDir").toString(), + FileInfo::resolvePath(projectFileDir, QStringLiteral("subdir/bar"))); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::profileValuesAndOverriddenValues() +{ + bool exceptionCaught = false; + try { + TemporaryProfile tp(QStringLiteral("tst_lang_profile"), m_settings); + Profile profile = tp.p; + profile.setValue("dummy.defines", "IN_PROFILE"); + profile.setValue("dummy.cFlags", "IN_PROFILE"); + profile.setValue("dummy.cxxFlags", "IN_PROFILE"); + profile.setValue("qbs.architecture", "x86"); + SetupProjectParameters parameters = defaultParameters; + parameters.setTopLevelProfile(profile.name()); + QVariantMap overriddenValues; + overriddenValues.insert("modules.dummy.cFlags", "OVERRIDDEN"); + parameters.setOverriddenValues(overriddenValues); + parameters.setProjectFilePath(testProject("profilevaluesandoverriddenvalues.qbs")); + parameters.expandBuildConfiguration(); + project = loader->loadProject(parameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + ResolvedProductPtr product = products.value("product1"); + QVERIFY(!!product); + QVariantList values; + values = product->moduleProperties->moduleProperty("dummy", "cxxFlags").toList(); + QCOMPARE(values.length(), 1); + QCOMPARE(values.front().toString(), QString("IN_PROFILE")); + values = product->moduleProperties->moduleProperty("dummy", "defines").toList(); + QCOMPARE(values, QVariantList() << QStringLiteral("IN_FILE") << QStringLiteral("IN_PROFILE")); + values = product->moduleProperties->moduleProperty("dummy", "cFlags").toList(); + QCOMPARE(values.length(), 1); + QCOMPARE(values.front().toString(), QString("OVERRIDDEN")); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::projectFileLookup() +{ + QFETCH(QString, projectFileInput); + QFETCH(QString, projectFileOutput); + QFETCH(bool, failureExpected); + + try { + SetupProjectParameters params; + params.setProjectFilePath(projectFileInput); + Loader::setupProjectFilePath(params); + QVERIFY(!failureExpected); + QCOMPARE(params.projectFilePath(), projectFileOutput); + } catch (const ErrorInfo &) { + QVERIFY(failureExpected); + } +} + +void TestLanguage::projectFileLookup_data() +{ + QTest::addColumn("projectFileInput"); + QTest::addColumn("projectFileOutput"); + QTest::addColumn("failureExpected"); + + const QString baseDir = testDataDir(); + const QString multiProjectsDir = baseDir + "/dirwithmultipleprojects"; + const QString noProjectsDir = baseDir + "/dirwithnoprojects"; + const QString oneProjectDir = baseDir + "/dirwithoneproject"; + QVERIFY(QDir(noProjectsDir).exists() && QDir(oneProjectDir).exists() + && QDir(multiProjectsDir).exists()); + const QString fullFilePath = multiProjectsDir + "/project.qbs"; + QTest::newRow("full file path") << fullFilePath << fullFilePath << false; + QTest::newRow("base dir ") << oneProjectDir << (oneProjectDir + "/project.qbs") << false; + QTest::newRow("empty dir") << noProjectsDir << QString() << true; + QTest::newRow("ambiguous dir") << multiProjectsDir << QString() << true; +} + +void TestLanguage::productConditions() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("productconditions.qbs")); + TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 6); + ResolvedProductPtr product; + product = products.value("product_no_condition"); + QVERIFY(!!product); + QVERIFY(product->enabled); + + product = products.value("product_true_condition"); + QVERIFY(!!product); + QVERIFY(product->enabled); + + product = products.value("product_condition_dependent_of_module"); + QVERIFY(!!product); + QVERIFY(product->enabled); + + product = products.value("product_false_condition"); + QVERIFY(!!product); + QVERIFY(!product->enabled); + + product = products.value("product_probe_condition_false"); + QVERIFY(!!product); + QVERIFY(!product->enabled); + + product = products.value("product_probe_condition_true"); + QVERIFY(!!product); + QVERIFY(product->enabled); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::productDirectories() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("productdirectories.qbs")); + ResolvedProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QHash products = productsFromProject(project); + QCOMPARE(products.size(), 1); + ResolvedProductPtr product; + product = products.value("MyApp"); + QVERIFY(!!product); + const QVariantMap config = product->productProperties; + QCOMPARE(config.value(QStringLiteral("buildDirectory")).toString(), + product->buildDirectory()); + QCOMPARE(config.value(QStringLiteral("sourceDirectory")).toString(), testDataDir()); + } + catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::propertiesBlocks_data() +{ + QTest::addColumn("propertyName"); + QTest::addColumn("expectedValue"); + QTest::addColumn("expectedStringValue"); + + QTest::newRow("init") << QString() << QVariant() << QString(); + QTest::newRow("property_overwrite") + << QString("dummy.defines") + << QVariant(QStringList("OVERWRITTEN")) + << QString(); + QTest::newRow("property_set_indirect") + << QString("dummy.cFlags") + << QVariant(QStringList("VAL")) + << QString(); + QTest::newRow("property_overwrite_no_outer") + << QString("dummy.defines") + << QVariant(QStringList("OVERWRITTEN")) + << QString(); + QTest::newRow("property_append_to_outer") + << QString("dummy.defines") + << QVariant(QStringList() << QString("ONE") << QString("TWO")) + << QString(); + QTest::newRow("property_append_to_indirect_outer") + << QString("dummy.defines") + << QVariant(QStringList() << QString("ONE") << QString("TWO")) + << QString(); + QTest::newRow("property_append_to_indirect_derived_outer1") + << QString("dummy.cFlags") + << QVariant(QStringList() << QString("BASE") << QString("PROPS")) + << QString(); + QTest::newRow("property_append_to_indirect_derived_outer2") + << QString("dummy.cFlags") + << QVariant(QStringList() << QString("PRODUCT") << QString("PROPS")) + << QString(); + QTest::newRow("property_append_to_indirect_derived_outer3") + << QString("dummy.cFlags") + << QVariant(QStringList() << QString("BASE") << QString("PRODUCT") << QString("PROPS")) + << QString(); + QTest::newRow("property_append_to_indirect_merged_outer") + << QString("dummy.rpaths") + << QVariant(QStringList() << QString("ONE") << QString("TWO") << QString("$ORIGIN")) + << QString(); + + QTest::newRow("multiple_exclusive_properties") + << QString("dummy.defines") + << QVariant(QStringList("OVERWRITTEN")) + << QString(); + QTest::newRow("multiple_exclusive_properties_no_outer") + << QString("dummy.defines") + << QVariant(QStringList("OVERWRITTEN")) + << QString(); + QTest::newRow("multiple_exclusive_properties_append_to_outer") + << QString("dummy.defines") + << QVariant(QStringList() << QString("ONE") << QString("TWO")) + << QString(); + + QTest::newRow("condition_refers_to_product_property") + << QString("dummy.defines") + << QVariant(QStringList("OVERWRITTEN")) + << QString("OVERWRITTEN"); + QTest::newRow("condition_refers_to_project_property") + << QString("dummy.defines") + << QVariant(QStringList("OVERWRITTEN")) + << QString("OVERWRITTEN"); + + QTest::newRow("ambiguous_properties") + << QString("dummy.defines") + << QVariant(QStringList() << QString("ONE") << QString("TWO")) + << QString(); + QTest::newRow("inheritance_overwrite_in_subitem") + << QString("dummy.defines") + << QVariant(QStringList() << QString("OVERWRITTEN_IN_SUBITEM")) + << QString(); + QTest::newRow("inheritance_retain_base1") + << QString("dummy.defines") + << QVariant(QStringList() << QString("BASE") << QString("SUB")) + << QString(); + QTest::newRow("inheritance_retain_base2") + << QString("dummy.defines") + << QVariant(QStringList() << QString("BASE") << QString("SUB")) + << QString(); + QTest::newRow("inheritance_retain_base3") + << QString("dummy.defines") + << QVariant(QStringList() << QString("BASE") << QString("SUB")) + << QString(); + QTest::newRow("inheritance_retain_base4") + << QString("dummy.defines") + << QVariant(QStringList() << QString("BASE")) + << QString(); + QTest::newRow("inheritance_condition_in_subitem1") + << QString("dummy.defines") + << QVariant(QStringList() << QString("SOMETHING") << QString("SUB")) + << QString(); + QTest::newRow("inheritance_condition_in_subitem2") + << QString("dummy.defines") + << QVariant(QStringList() << QString("SOMETHING")) + << QString(); + QTest::newRow("condition_references_id") + << QString("dummy.defines") + << QVariant(QStringList() << QString("OVERWRITTEN")) + << QString(); + QTest::newRow("using_derived_Properties_item") << "dummy.defines" + << QVariant(QStringList() << "string from MyProperties") << QString(); + QTest::newRow("conditional-depends") + << QString("dummy.defines") + << QVariant() + << QString(); + QTest::newRow("use-module-with-properties-item") + << QString("module-with-properties-item.stringProperty") + << QVariant(QString("overridden in Properties item")) + << QString(); + QTest::newRow("cleanup") << QString() << QVariant() << QString(); +} + +void TestLanguage::propertiesBlocks() +{ + HANDLE_INIT_CLEANUP_DATATAGS("propertiesblocks.qbs"); + QFETCH(QString, propertyName); + QFETCH(QVariant, expectedValue); + QFETCH(QString, expectedStringValue); + QVERIFY(!!project); + QHash products = productsFromProject(project); + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + ResolvedProductPtr product = products.value(productName); + QVERIFY(!!product); + QCOMPARE(product->name, productName); + QVariant v = productPropertyValue(product, propertyName); + QCOMPARE(v, expectedValue); + if (!expectedStringValue.isEmpty()) { + v = productPropertyValue(product, "someString"); + QCOMPARE(v.toString(), expectedStringValue); + } +} + +void TestLanguage::propertiesBlockInGroup() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("properties-block-in-group.qbs")); + const TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + QCOMPARE(project->allProducts().size(), size_t(1)); + const ResolvedProductConstPtr product = project->allProducts().front(); + const auto groupIt = std::find_if(product->groups.cbegin(), product->groups.cend(), + [](const GroupConstPtr &g) { return g->name == "the group"; }); + QVERIFY(groupIt != product->groups.cend()); + const QVariantMap propertyMap = (*groupIt)->properties->value(); + const QVariantList value = moduleProperty(propertyMap, "dummy", "defines").toList(); + QStringList stringListValue; + std::transform(value.constBegin(), value.constEnd(), std::back_inserter(stringListValue), + [](const QVariant &v) { return v.toString(); }); + QCOMPARE(stringListValue, QStringList() << "BASEDEF" << "FEATURE_ENABLED"); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::propertiesItemInModule() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath( + testProject("properties-item-in-module.qbs")); + const TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + const QHash products = productsFromProject(project); + QCOMPARE(products.size(), 2); + for (const ResolvedProductPtr &p : products) { + QCOMPARE(p->moduleProperties->moduleProperty("dummy", "productName").toString(), + p->name); + } + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::propertyAssignmentInExportedGroup() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath( + testProject("property-assignment-in-exported-group.qbs")); + const TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + const QHash products = productsFromProject(project); + QCOMPARE(products.size(), 2); + for (const ResolvedProductPtr &p : products) { + QCOMPARE(p->moduleProperties->moduleProperty("dummy", "someString").toString(), + QString()); + for (const GroupPtr &g : p->groups) { + const QString propValue + = g->properties->moduleProperty("dummy", "someString").toString(); + if (g->name == "exported_group") + QCOMPARE(propValue, QString("test")); + else + QCOMPARE(propValue, QString()); + } + } + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::qbs1275() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath(testProject("qbs1275.qbs")); + const TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + const QHash products = productsFromProject(project); + QCOMPARE(products.count(), 5); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::qbsPropertiesInProjectCondition() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath( + testProject("qbs-properties-in-project-condition.qbs")); + const TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + const QHash products = productsFromProject(project); + QCOMPARE(products.size(), 0); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::qbsPropertyConvenienceOverride() +{ + bool exceptionCaught = false; + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("qbs-property-convenience-override.qbs")); + params.setOverriddenValues({std::make_pair("qbs.installPrefix", "/opt")}); + TopLevelProjectConstPtr project = loader->loadProject(params); + QVERIFY(!!project); + QCOMPARE(project->products.size(), size_t(1)); + QCOMPARE(project->products.front()->moduleProperties->qbsPropertyValue("installPrefix") + .toString(), QString("/opt")); + } + catch (const ErrorInfo &e) { + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::relaxedErrorMode() +{ + m_logSink->setLogLevel(LoggerMinLevel); + QFETCH(bool, strictMode); + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("relaxed-error-mode/relaxed-error-mode.qbs")); + params.setProductErrorMode(strictMode ? ErrorHandlingMode::Strict + : ErrorHandlingMode::Relaxed); + const TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!strictMode); + const auto productMap = productsFromProject(project); + const ResolvedProductConstPtr brokenProduct = productMap.value("broken"); + QVERIFY(!brokenProduct->enabled); + QVERIFY(brokenProduct->location.isValid()); + QCOMPARE(brokenProduct->allFiles().size(), size_t(0)); + const ResolvedProductConstPtr dependerRequired = productMap.value("depender required"); + QVERIFY(!dependerRequired->enabled); + QVERIFY(dependerRequired->location.isValid()); + QCOMPARE(dependerRequired->allFiles().size(), size_t(1)); + const ResolvedProductConstPtr dependerNonRequired + = productMap.value("depender nonrequired"); + QVERIFY(dependerNonRequired->enabled); + QCOMPARE(dependerNonRequired->allFiles().size(), size_t(1)); + const ResolvedProductConstPtr recursiveDepender = productMap.value("recursive depender"); + QVERIFY(!recursiveDepender->enabled); + QVERIFY(recursiveDepender->location.isValid()); + QCOMPARE(recursiveDepender->allFiles().size(), size_t(1)); + const ResolvedProductConstPtr missingFile = productMap.value("missing file"); + QVERIFY(missingFile->enabled); + QCOMPARE(missingFile->groups.size(), size_t(1)); + QVERIFY(missingFile->groups.front()->enabled); + QCOMPARE(missingFile->groups.front()->allFiles().size(), size_t(2)); + const ResolvedProductConstPtr fine = productMap.value("fine"); + QVERIFY(fine->enabled); + QCOMPARE(fine->allFiles().size(), size_t(1)); + } catch (const ErrorInfo &e) { + QVERIFY2(strictMode, qPrintable(e.toString())); + } +} + +void TestLanguage::relaxedErrorMode_data() +{ + QTest::addColumn("strictMode"); + + QTest::newRow("strict mode") << true; + QTest::newRow("relaxed mode") << false; +} + +void TestLanguage::requiredAndNonRequiredDependencies() +{ + QFETCH(QString, projectFile); + QFETCH(bool, exceptionExpected); + try { + SetupProjectParameters params = defaultParameters; + const QString projectFilePath = "required-and-nonrequired-dependencies/" + projectFile; + params.setProjectFilePath(testProject(projectFilePath.toLocal8Bit())); + const TopLevelProjectConstPtr project = loader->loadProject(params); + QVERIFY(!!project); + QVERIFY(!exceptionExpected); + } catch (const ErrorInfo &e) { + QVERIFY(exceptionExpected); + QVERIFY2(e.toString().contains("validation error!"), qPrintable(e.toString())); + } +} + +void TestLanguage::requiredAndNonRequiredDependencies_data() +{ + QTest::addColumn("projectFile"); + QTest::addColumn("exceptionExpected"); + + QTest::newRow("same file") << "direct-dependencies.qbs" << true; + QTest::newRow("dependency via module") << "dependency-via-module.qbs" << true; + QTest::newRow("dependency via export") << "dependency-via-export.qbs" << true; + QTest::newRow("more indirection") << "complicated.qbs" << true; + QTest::newRow("required chain (module)") << "required-chain-module.qbs" << false; + QTest::newRow("required chain (export)") << "required-chain-export.qbs" << false; + QTest::newRow("required chain (export indirect)") << "required-chain-export-indirect.qbs" + << false; +} + +void TestLanguage::suppressedAndNonSuppressedErrors() +{ + try { + SetupProjectParameters params = defaultParameters; + const QString projectFilePath = "suppressed-and-non-suppressed-errors.qbs"; + params.setProjectFilePath(testProject(projectFilePath.toLocal8Bit())); + const TopLevelProjectConstPtr project = loader->loadProject(params); + QFAIL("failure expected"); + } catch (const ErrorInfo &e) { + QVERIFY2(e.toString().contains("easter bunny"), qPrintable(e.toString())); + QVERIFY2(!e.toString().contains("TheBeautifulSausage"), qPrintable(e.toString())); + } +} + +void TestLanguage::throwingProbe() +{ + QFETCH(bool, enableProbe); + try { + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("throwing-probe.qbs")); + QVariantMap properties; + properties.insert(QStringLiteral("products.theProduct.enableProbe"), enableProbe); + params.setOverriddenValues(properties); + const TopLevelProjectPtr project = loader->loadProject(params); + QVERIFY(!!project); + QVERIFY(!enableProbe); + } catch (const ErrorInfo &e) { + QVERIFY2(enableProbe, qPrintable(e.toString())); + } +} + +void TestLanguage::throwingProbe_data() +{ + QTest::addColumn("enableProbe"); + + QTest::newRow("enabled probe") << true; + QTest::newRow("disabled probe") << false; +} + +void TestLanguage::qualifiedId() +{ + QString str = "foo.bar.baz"; + QualifiedId id = QualifiedId::fromString(str); + QCOMPARE(id.size(), 3); + QCOMPARE(id.toString(), str); + + id = QualifiedId("blubb.di.blubb"); // c'tor does not split + QCOMPARE(id.size(), 1); + + QList ids; + ids << QualifiedId::fromString("a") + << QualifiedId::fromString("a.a") + << QualifiedId::fromString("b") + << QualifiedId::fromString("c.a") + << QualifiedId::fromString("c.b.a") + << QualifiedId::fromString("c.c"); + QList sorted = ids; + std::sort(sorted.begin(), sorted.end()); + QCOMPARE(ids, sorted); +} + +void TestLanguage::recursiveProductDependencies() +{ + bool exceptionCaught = false; + try { + defaultParameters.setProjectFilePath( + testProject("recursive-dependencies/recursive-dependencies.qbs")); + const TopLevelProjectPtr project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + const QHash products = productsFromProject(project); + QCOMPARE(products.size(), 4); + const ResolvedProductConstPtr p1 = products.value("p1"); + QVERIFY(!!p1); + const ResolvedProductConstPtr p2 = products.value("p2"); + QVERIFY(!!p2); + const ResolvedProductPtr p3 = products.value("p3"); + QVERIFY(!!p3); + const ResolvedProductPtr p4 = products.value("p4"); + QVERIFY(!!p4); + QVERIFY(p1->dependencies == std::vector({p3, p4})); + QVERIFY(p2->dependencies == std::vector({p3, p4})); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +void TestLanguage::fileTags_data() +{ + QTest::addColumn("numberOfGroups"); + QTest::addColumn("expectedFileTags"); + + QTest::newRow("init") << size_t(0) << QStringList(); + QTest::newRow("filetagger_project_scope") << size_t(1) << (QStringList() << "cpp"); + QTest::newRow("filetagger_product_scope") << size_t(1) << (QStringList() << "asm"); + QTest::newRow("filetagger_static_pattern") << size_t(1) << (QStringList() << "yellow"); + QTest::newRow("unknown_file_tag") << size_t(1) << (QStringList() << "unknown-file-tag"); + QTest::newRow("set_file_tag_via_group") << size_t(2) << (QStringList() << "c++"); + QTest::newRow("override_file_tag_via_group") << size_t(2) << (QStringList() << "c++"); + QTest::newRow("add_file_tag_via_group") << size_t(2) << (QStringList() << "cpp" << "zzz"); + QTest::newRow("prioritized_filetagger") << size_t(1) << (QStringList() << "cpp1" << "cpp2"); + QTest::newRow("cleanup") << size_t(0) << QStringList(); +} + +void TestLanguage::fileTags() +{ + HANDLE_INIT_CLEANUP_DATATAGS("filetags.qbs"); + QFETCH(size_t, numberOfGroups); + QFETCH(QStringList, expectedFileTags); + QHash products = productsFromProject(project); + ResolvedProductPtr product; + const QString productName = QString::fromLocal8Bit(QTest::currentDataTag()); + QVERIFY(!!(product = products.value(productName))); + QCOMPARE(product->groups.size(), numberOfGroups); + GroupPtr group = *(product->groups.end() - 1); + QVERIFY(!!group); + QCOMPARE(group->files.size(), size_t(1)); + SourceArtifactConstPtr sourceFile = group->files.front(); + QStringList fileTags = sourceFile->fileTags.toStringList(); + fileTags.sort(); + QCOMPARE(fileTags, expectedFileTags); +} + +void TestLanguage::useInternalProfile() +{ + const QString profile(QStringLiteral("theprofile")); + SetupProjectParameters params = defaultParameters; + params.setProjectFilePath(testProject("use-internal-profile.qbs")); + params.setTopLevelProfile(profile); + TopLevelProjectConstPtr project = loader->loadProject(params); + QVERIFY(!!project); + QCOMPARE(project->profile(), profile); + QCOMPARE(project->products.size(), size_t(1)); + const ResolvedProductConstPtr product = project->products[0]; + QCOMPARE(product->profile(), profile); + QCOMPARE(product->moduleProperties->moduleProperty("dummy", "defines").toString(), profile); +} + +void TestLanguage::wildcards_data() +{ + QTest::addColumn("useGroup"); + QTest::addColumn("filesToCreate"); + QTest::addColumn("projectFileSubDir"); + QTest::addColumn("prefix"); + QTest::addColumn("patterns"); + QTest::addColumn("excludePatterns"); + QTest::addColumn("expected"); + + const bool useGroup = true; + for (int i = 0; i <= 1; ++i) { + const bool useGroup = i; + const QByteArray dataTagSuffix = useGroup ? " group" : " nogroup"; + QTest::newRow(QByteArray("simple 1") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "*.h") + << QStringList() + << (QStringList() << "foo.h" << "bar.h"); + QTest::newRow(QByteArray("simple 2") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "foo.*") + << QStringList() + << (QStringList() << "foo.h" << "foo.cpp"); + QTest::newRow(QByteArray("simple 3") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "*.h" << "*.cpp") + << QStringList() + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp"); + QTest::newRow(QByteArray("exclude 1") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "*.h" << "*.cpp") + << (QStringList() << "bar*") + << (QStringList() << "foo.h" << "foo.cpp"); + QTest::newRow(QByteArray("exclude 2") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << "*") + << (QStringList() << "*.qbs") + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp"); + QTest::newRow(QByteArray("non-recursive") + dataTagSuffix) + << useGroup + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp") + << QString() + << QString() + << (QStringList() << "a/*") + << QStringList() + << (QStringList() << "a/foo.h" << "a/foo.cpp"); + QTest::newRow(QByteArray("absolute paths") + dataTagSuffix) + << useGroup + << (QStringList() << "foo.h" << "foo.cpp" << "bar.h" << "bar.cpp") + << QString() + << QString() + << (QStringList() << m_wildcardsTestDirPath + "/?oo.*") + << QStringList() + << (QStringList() << "foo.h" << "foo.cpp"); + QTest::newRow(QByteArray("relative paths with dotdot") + dataTagSuffix) + << useGroup + << (QStringList() << "bar.h" << "bar.cpp") + << QString("TheLaughingLlama") + << QString() + << (QStringList() << "../bar.*") + << QStringList() + << (QStringList() << "bar.h" << "bar.cpp"); + } + QTest::newRow(QByteArray("recursive 1")) + << useGroup + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp") + << QString() + << QString() + << (QStringList() << "a/**") + << QStringList() + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp"); + QTest::newRow(QByteArray("recursive 2")) + << useGroup + << (QStringList() + << "d/1.h" << "b/d/1.h" << "b/c/d/1.h" + << "d/e/1.h" << "b/d/e/1.h" << "b/c/d/e/1.h" + << "a/d/1.h" << "a/b/d/1.h" << "a/b/c/d/1.h" + << "a/d/e/1.h" << "a/b/d/e/1.h" << "a/b/c/d/e/1.h" + << "a/d/1.cpp" << "a/b/d/1.cpp" << "a/b/c/d/1.h" + << "a/d/e/1.cpp" << "a/b/d/e/1.cpp" << "a/b/c/d/e/1.cpp") + << QString() + << QString() + << (QStringList() << "a/**/d/*.h") + << QStringList() + << (QStringList() << "a/d/1.h" << "a/b/d/1.h" << "a/b/c/d/1.h"); + QTest::newRow(QByteArray("recursive 3")) + << useGroup + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp") + << QString() + << QString() + << (QStringList() << "a/**/**/**") + << QStringList() + << (QStringList() << "a/foo.h" << "a/foo.cpp" << "a/b/bar.h" << "a/b/bar.cpp"); + QTest::newRow(QByteArray("prefix")) + << useGroup + << (QStringList() << "subdir/foo.h" << "subdir/foo.cpp" << "subdir/bar.h" + << "subdir/bar.cpp") + << QString() + << QString("subdir/") + << (QStringList() << "*.h") + << QStringList() + << (QStringList() << "subdir/foo.h" << "subdir/bar.h"); + QTest::newRow(QByteArray("non-existing absolute path")) + << useGroup + << QStringList() + << QString() + << QString("/dir") + << (QStringList() << "*.whatever") + << QStringList() + << QStringList(); +} + +void TestLanguage::wildcards() +{ + QFETCH(bool, useGroup); + QFETCH(QStringList, filesToCreate); + QFETCH(QString, projectFileSubDir); + QFETCH(QString, prefix); + QFETCH(QStringList, patterns); + QFETCH(QStringList, excludePatterns); + QFETCH(QStringList, expected); + + // create test directory + QDir::setCurrent(QDir::tempPath()); + { + QString errorMessage; + if (QFile::exists(m_wildcardsTestDirPath)) { + if (!removeDirectoryWithContents(m_wildcardsTestDirPath, &errorMessage)) { + qDebug() << errorMessage; + QVERIFY2(false, "removeDirectoryWithContents failed"); + } + } + QVERIFY(QDir().mkdir(m_wildcardsTestDirPath)); + } + + // create project file + const QString groupName = "Keks"; + QString dataTag = QString::fromLocal8Bit(QTest::currentDataTag()); + dataTag.replace(' ', '_'); + if (!projectFileSubDir.isEmpty()) { + if (!projectFileSubDir.startsWith('/')) + projectFileSubDir.prepend('/'); + if (projectFileSubDir.endsWith('/')) + projectFileSubDir.chop(1); + QVERIFY(QDir().mkpath(m_wildcardsTestDirPath + projectFileSubDir)); + } + const QString projectFilePath = m_wildcardsTestDirPath + projectFileSubDir + "/test_" + dataTag + + ".qbs"; + { + QFile projectFile(projectFilePath); + QVERIFY(projectFile.open(QIODevice::WriteOnly)); + QTextStream s(&projectFile); + using Qt::endl; + s << "import qbs.base 1.0" << endl << endl + << "Application {" << endl + << " name: \"MyProduct\"" << endl; + if (useGroup) { + s << " Group {" << endl + << " name: " << toJSLiteral(groupName) << endl; + } + if (!prefix.isEmpty()) + s << " prefix: " << toJSLiteral(prefix) << endl; + if (!patterns.empty()) + s << " files: " << toJSLiteral(patterns) << endl; + if (!excludePatterns.empty()) + s << " excludeFiles: " << toJSLiteral(excludePatterns) << endl; + if (useGroup) + s << " }" << endl; + s << "}" << endl << endl; + } + + // create files + for (QString filePath : qAsConst(filesToCreate)) { + filePath.prepend(m_wildcardsTestDirPath + '/'); + QFileInfo fi(filePath); + if (!QDir(fi.path()).exists()) + QVERIFY(QDir().mkpath(fi.path())); + QFile file(filePath); + QVERIFY(file.open(QIODevice::WriteOnly)); + } + + // read the project + bool exceptionCaught = false; + ResolvedProductPtr product; + try { + defaultParameters.setProjectFilePath(projectFilePath); + project = loader->loadProject(defaultParameters); + QVERIFY(!!project); + const QHash products = productsFromProject(project); + product = products.value("MyProduct"); + QVERIFY(!!product); + GroupPtr group; + if (useGroup) { + QCOMPARE(product->groups.size(), size_t(HostOsInfo::isMacosHost() ? 3 : 2)); + for (const GroupPtr &rg : product->groups) { + if (rg->name == groupName) { + group = rg; + break; + } + } + } else { + QCOMPARE(product->groups.size(), size_t(HostOsInfo::isMacosHost() ? 2 : 1)); + group = product->groups.front(); + } + QVERIFY(!!group); + QCOMPARE(group->files.size(), size_t(0)); + QVERIFY(!!group->wildcards); + QStringList actualFilePaths; + for (const SourceArtifactPtr &artifact : group->wildcards->files) { + QString str = artifact->absoluteFilePath; + int idx = str.indexOf(m_wildcardsTestDirPath); + if (idx != -1) + str.remove(0, idx + m_wildcardsTestDirPath.size() + 1); + actualFilePaths << str; + } + actualFilePaths.sort(); + expected.sort(); + QCOMPARE(actualFilePaths, expected); + } catch (const ErrorInfo &e) { + exceptionCaught = true; + qDebug() << e.toString(); + } + QCOMPARE(exceptionCaught, false); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + const SettingsPtr s = settings(); + TestLanguage tl(ConsoleLogger::instance().logSink(), s.get()); + return QTest::qExec(&tl, argc, argv); +} + diff --git a/tests/auto/language/tst_language.h b/tests/auto/language/tst_language.h new file mode 100644 index 00000000..7e521c05 --- /dev/null +++ b/tests/auto/language/tst_language.h @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** 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 TST_LANGUAGE_H +#define TST_LANGUAGE_H + +#include +#include +#include +#include + +#include +#include +#include + +class TestLanguage : public QObject +{ + Q_OBJECT +public: + TestLanguage(qbs::ILogSink *logSink, qbs::Settings *settings); + ~TestLanguage(); + +private: + qbs::ILogSink *m_logSink; + qbs::Settings * const m_settings; + qbs::Internal::Logger m_logger; + qbs::Internal::ScriptEngine *m_engine; + qbs::Internal::Loader *loader; + qbs::Internal::TopLevelProjectPtr project; + qbs::SetupProjectParameters defaultParameters; + const QString m_wildcardsTestDirPath; + + QHash productsFromProject( + qbs::Internal::ResolvedProjectPtr project); + qbs::Internal::ResolvedModuleConstPtr findModuleByName( + qbs::Internal::ResolvedProductPtr product, const QString &name); + QVariant productPropertyValue(qbs::Internal::ResolvedProductPtr product, QString propertyName); + void handleInitCleanupDataTags(const char *projectFileName, bool *handled); + +private slots: + void init(); + void initTestCase(); + void cleanupTestCase(); + + void additionalProductTypes(); + void baseProperty(); + void baseValidation(); + void brokenDependencyCycle(); + void brokenDependencyCycle_data(); + void buildConfigStringListSyntax(); + void builtinFunctionInSearchPathsProperty(); + void chainedProbes(); + void canonicalArchitecture(); + void conditionalDepends(); + void delayedError(); + void delayedError_data(); + void dependencyOnAllProfiles(); + void derivedSubProject(); + void disabledSubProject(); + void dottedNames_data(); + void dottedNames(); + void emptyJsFile(); + void enumerateProjectProperties(); + void evalErrorInNonPresentModule_data(); + void evalErrorInNonPresentModule(); + void environmentVariable(); + void errorInDisabledProduct(); + void erroneousFiles_data(); + void erroneousFiles(); + void exports(); + void fileContextProperties(); + void fileInProductAndModule_data(); + void fileInProductAndModule(); + void fileTags_data(); + void fileTags(); + void groupConditions_data(); + void groupConditions(); + void groupName(); + void getNativeSetting(); + void homeDirectory(); + void identifierSearch_data(); + void identifierSearch(); + void idUsage(); + void idUniqueness(); + void importCollection(); + void inheritedPropertiesItems_data(); + void inheritedPropertiesItems(); + void invalidBindingInDisabledItem(); + void invalidOverrides(); + void invalidOverrides_data(); + void itemPrototype(); + void itemScope(); + void jsExtensions(); + void jsImportUsedInMultipleScopes_data(); + void jsImportUsedInMultipleScopes(); + void moduleMergingVariantValues(); + void modulePrioritizationBySearchPath_data(); + void modulePrioritizationBySearchPath(); + void moduleProperties_data(); + void moduleProperties(); + void modulePropertiesInGroups(); + void modulePropertyOverridesPerProduct(); + void moduleScope(); + void modules_data(); + void modules(); + void multiplexedExports(); + void multiplexingByProfile(); + void multiplexingByProfile_data(); + void nonApplicableModulePropertyInProfile(); + void nonApplicableModulePropertyInProfile_data(); + void nonRequiredProducts(); + void nonRequiredProducts_data(); + void outerInGroup(); + void overriddenPropertiesAndPrototypes(); + void overriddenPropertiesAndPrototypes_data(); + void overriddenVariantProperty(); + void parameterTypes(); + void pathProperties(); + void productConditions(); + void productDirectories(); + void profileValuesAndOverriddenValues(); + void projectFileLookup(); + void projectFileLookup_data(); + void propertiesBlocks_data(); + void propertiesBlocks(); + void propertiesBlockInGroup(); + void propertiesItemInModule(); + void propertyAssignmentInExportedGroup(); + void qbs1275(); + void qbsPropertiesInProjectCondition(); + void qbsPropertyConvenienceOverride(); + void relaxedErrorMode(); + void relaxedErrorMode_data(); + void requiredAndNonRequiredDependencies(); + void requiredAndNonRequiredDependencies_data(); + void suppressedAndNonSuppressedErrors(); + void throwingProbe(); + void throwingProbe_data(); + void defaultValue(); + void defaultValue_data(); + void qualifiedId(); + void recursiveProductDependencies(); + void rfc1034Identifier(); + void useInternalProfile(); + void versionCompare(); + void wildcards_data(); + void wildcards(); + +private: + QTemporaryDir m_tempDir; + QRandomGenerator m_rand; +}; + +#endif // TST_LANGUAGE_H + diff --git a/tests/auto/shared.h b/tests/auto/shared.h new file mode 100644 index 00000000..8dced206 --- /dev/null +++ b/tests/auto/shared.h @@ -0,0 +1,366 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QBS_TEST_SHARED_H +#define QBS_TEST_SHARED_H + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + + + +#define REPLACE_IN_FILE(filePath, oldContent, newContent) \ + do { \ + QFile f((filePath)); \ + QVERIFY2(f.open(QIODevice::ReadWrite), qPrintable(f.errorString())); \ + QByteArray content = f.readAll(); \ + const QByteArray savedContent = content; \ + content.replace((oldContent), (newContent)); \ + QVERIFY(content != savedContent); \ + f.resize(0); \ + f.write(content); \ + } while (false) + +inline int testTimeoutInMsecs() +{ + bool ok; + int timeoutInSecs = qEnvironmentVariableIntValue("QBS_AUTOTEST_TIMEOUT", &ok); + if (!ok) + timeoutInSecs = 600; + return timeoutInSecs * 1000; +} + +// On Windows, it appears that a lock is sometimes held on files for a short while even after +// they are closed. The likelihood for that seems to increase with the slowness of the machine. +inline void waitForFileUnlock() +{ + bool ok; + int timeoutInSecs = qEnvironmentVariableIntValue("QBS_AUTOTEST_IO_GRACE_PERIOD", &ok); + if (!ok) + timeoutInSecs = qbs::Internal::HostOsInfo::isWindowsHost() ? 1 : 0; + if (timeoutInSecs > 0) + QTest::qWait(timeoutInSecs * 1000); +} + +using SettingsPtr = std::unique_ptr; +inline SettingsPtr settings() +{ + const QString settingsDir = QLatin1String(qgetenv("QBS_AUTOTEST_SETTINGS_DIR")); + return std::make_unique(settingsDir); +} + +inline QString profileName() +{ + const QString suiteProfile = QLatin1String( + qgetenv("QBS_AUTOTEST_PROFILE_" QBS_TEST_SUITE_NAME)); + if (!suiteProfile.isEmpty()) + return suiteProfile; + 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"); +} + +inline QString relativeBuildGraphFilePath(const QString &configName = QString()) { + return relativeBuildDir(configName) + QLatin1Char('/') + relativeBuildDir(configName) + + QLatin1String(".bg"); +} + +inline bool regularFileExists(const QString &filePath) +{ + const QFileInfo fi(filePath); + return fi.exists() && fi.isFile(); +} + +inline bool directoryExists(const QString &dirPath) +{ + const QFileInfo fi(dirPath); + return fi.exists() && fi.isDir(); +} + +struct ReadFileContentResult +{ + QByteArray content; + QString errorString; +}; + +inline ReadFileContentResult readFileContent(const QString &filePath) +{ + ReadFileContentResult result; + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly)) { + result.errorString = file.errorString(); + return result; + } + result.content = file.readAll(); + return result; +} + +inline QByteArray diffText(const QByteArray &actual, const QByteArray &expected) +{ + QByteArray result; + QList actualLines = actual.split('\n'); + QList expectedLines = expected.split('\n'); + int n = 1; + while (!actualLines.isEmpty() && !expectedLines.isEmpty()) { + QByteArray actualLine = actualLines.takeFirst(); + QByteArray expectedLine = expectedLines.takeFirst(); + if (actualLine != expectedLine) { + result += QStringLiteral("%1: actual: %2\n%1:expected: %3\n") + .arg(n, 2) + .arg(QString::fromUtf8(actualLine)) + .arg(QString::fromUtf8(expectedLine)) + .toUtf8(); + } + n++; + } + auto addLines = [&result, &n] (const QList &lines) { + for (const QByteArray &line : qAsConst(lines)) { + result += QStringLiteral("%1: %2\n").arg(n).arg(QString::fromUtf8(line)); + n++; + } + }; + if (!actualLines.isEmpty()) { + result += "Extra unexpected lines:\n"; + addLines(actualLines); + } + if (!expectedLines.isEmpty()) { + result += "Missing expected lines:\n"; + addLines(expectedLines); + } + return result; +} + +#define READ_TEXT_FILE(filePath, contentVariable) \ + QByteArray contentVariable; \ + { \ + auto c = readFileContent(filePath); \ + QVERIFY2(c.errorString.isEmpty(), \ + qUtf8Printable(QStringLiteral("Cannot open file %1. %2") \ + .arg(filePath, c.errorString))); \ + contentVariable = std::move(c.content); \ + } + +#define TEXT_FILE_COMPARE(actualFilePath, expectedFilePath) \ + { \ + READ_TEXT_FILE(actualFilePath, ba1); \ + READ_TEXT_FILE(expectedFilePath, ba2); \ + if (ba1 != ba2) { \ + QByteArray msg = "File contents differ:\n" + diffText(ba1, ba2); \ + QFAIL(msg.constData()); \ + } \ + } + +template +inline QString prefixedIfNonEmpty(const T &prefix, const QString &str) +{ + if (str.isEmpty()) + return QString(); + return prefix + str; +} + +inline QString uniqueProductName(const QString &productName, + const QString &multiplexConfigurationId) +{ + return productName + prefixedIfNonEmpty(QLatin1Char('.'), multiplexConfigurationId); +} + +inline QString relativeProductBuildDir(const QString &productName, + const QString &configurationName = QString(), + const QString &multiplexConfigurationId = QString()) +{ + const QString fullName = uniqueProductName(productName, multiplexConfigurationId); + QString dirName = qbs::Internal::HostOsInfo::rfc1034Identifier(fullName); + const QByteArray hash = QCryptographicHash::hash(fullName.toUtf8(), QCryptographicHash::Sha1); + dirName.append('.').append(hash.toHex().left(8)); + return relativeBuildDir(configurationName) + '/' + dirName; +} + +inline QString relativeExecutableFilePath(const QString &productName, + const QString &configName = QString()) +{ + return relativeProductBuildDir(productName, configName) + '/' + + qbs::Internal::HostOsInfo::appendExecutableSuffix(productName); +} + +inline void waitForNewTimestamp(const QString &testDir) +{ + // Waits for the time that corresponds to the host file system's time stamp granularity. + if (qbs::Internal::HostOsInfo::isWindowsHost()) { + QTest::qWait(1); // NTFS has 100 ns precision. Let's ignore exFAT. + } else { + const QString nameTemplate = testDir + "/XXXXXX"; + QTemporaryFile f1(nameTemplate); + if (!f1.open()) + qFatal("Failed to open temp file"); + const QDateTime initialTime = QFileInfo(f1).lastModified(); + int totalMsPassed = 0; + while (totalMsPassed <= 2000) { + static const int increment = 50; + QTest::qWait(increment); + totalMsPassed += increment; + QTemporaryFile f2(nameTemplate); + if (!f2.open()) + qFatal("Failed to open temp file"); + if (QFileInfo(f2).lastModified() > initialTime) + return; + } + qWarning("Got no new timestamp after two seconds, going ahead anyway. Subsequent " + "test failure might not be genuine."); + } +} + +inline void touch(const QString &fn) +{ + QFile f(fn); + int s = f.size(); + if (!f.open(QFile::ReadWrite)) + qFatal("cannot open file %s", qPrintable(fn)); + f.resize(s+1); + f.resize(s); +} + +inline void copyFileAndUpdateTimestamp(const QString &source, const QString &target) +{ + QFile::remove(target); + if (!QFile::copy(source, target)) + qFatal("Failed to copy '%s' to '%s'", qPrintable(source), qPrintable(target)); + touch(target); +} + +inline QStringList profileToolchain(const qbs::Profile &profile) +{ + const auto toolchainType = profile.value(QStringLiteral("qbs.toolchainType")).toString(); + if (!toolchainType.isEmpty()) + return qbs::canonicalToolchain(toolchainType); + return profile.value(QStringLiteral("qbs.toolchain")).toStringList(); +} + +inline QString objectFileName(const QString &baseName, const QString &profileName) +{ + const SettingsPtr s = settings(); + qbs::Profile profile(profileName, s.get()); + + const auto tcList = profileToolchain(profile); + const bool isMsvc = tcList.contains("msvc") + || (tcList.isEmpty() && qbs::Internal::HostOsInfo::isWindowsHost()); + const QString suffix = isMsvc ? "obj" : "o"; + return baseName + '.' + suffix; +} + +inline QString inputDirHash(const QString &dir) +{ + return QCryptographicHash::hash(dir.toLatin1(), QCryptographicHash::Sha1).toHex().left(16); +} + +inline QString testDataSourceDir(const QString &dir) +{ + QDir result; + QString testSourceRootDirFromEnv = QDir::fromNativeSeparators(qEnvironmentVariable("QBS_TEST_SOURCE_ROOT")); + if (testSourceRootDirFromEnv.isEmpty()) { + result.setPath(dir); + } else { + QDir testSourceRootDir(dir); + while (testSourceRootDir.dirName() != "tests") + testSourceRootDir = QFileInfo(testSourceRootDir, "").dir(); + + QString relativeDataPath = testSourceRootDir.relativeFilePath(dir); + QString absoluteDataPath = QDir(testSourceRootDirFromEnv).absoluteFilePath(relativeDataPath); + result.setPath(absoluteDataPath); + } + + if (!result.exists()) + qFatal("Expected data folder '%s' to be present, but it does not exist. You may set " + "QBS_TEST_SOURCE_ROOT to the 'tests' folder in your qbs repository to configure " + "a custom location.", qPrintable(result.absolutePath())); + + return result.absolutePath(); +} + +inline QString testWorkDir(const QString &testName) +{ + QString dir = QDir::fromNativeSeparators(QString::fromLocal8Bit(qgetenv("QBS_TEST_WORK_ROOT"))); + if (dir.isEmpty()) { + dir = QCoreApplication::applicationDirPath() + QStringLiteral("/../tests/auto/"); + } else { + if (!dir.endsWith(QLatin1Char('/'))) + dir += QLatin1Char('/'); + } + return dir + testName + "/testWorkDir"; +} + +inline bool copyDllExportHeader(const QString &srcDataDir, const QString &targetDataDir) +{ + QFile sourceFile(srcDataDir + "/../../dllexport.h"); + if (!sourceFile.exists()) + return true; + const QString targetPath = targetDataDir + "/dllexport.h"; + QFile::remove(targetPath); + return sourceFile.copy(targetPath); +} + +inline qbs::Internal::HostOsInfo::HostOs targetOs() +{ + const SettingsPtr s = settings(); + const qbs::Profile buildProfile(profileName(), s.get()); + const QString targetPlatform = buildProfile.value("qbs.targetPlatform").toString(); + if (!targetPlatform.isEmpty()) { + const std::vector targetOS = qbs::Internal::HostOsInfo::canonicalOSIdentifiers( + targetPlatform.toStdString()); + if (qbs::Internal::contains(targetOS, "windows")) + return qbs::Internal::HostOsInfo::HostOsWindows; + if (qbs::Internal::contains(targetOS, "linux")) + return qbs::Internal::HostOsInfo::HostOsLinux; + if (qbs::Internal::contains(targetOS, "macos")) + return qbs::Internal::HostOsInfo::HostOsMacos; + if (qbs::Internal::contains(targetOS, "unix")) + return qbs::Internal::HostOsInfo::HostOsOtherUnix; + return qbs::Internal::HostOsInfo::HostOsOther; + } + return qbs::Internal::HostOsInfo::hostOs(); +} + +#endif // Include guard. diff --git a/tests/auto/tools/tools.pro b/tests/auto/tools/tools.pro new file mode 100644 index 00000000..ba293f41 --- /dev/null +++ b/tests/auto/tools/tools.pro @@ -0,0 +1,6 @@ +TARGET = tst_tools + +SOURCES = tst_tools.cpp ../../../src/app/qbs/qbstool.cpp +HEADERS = tst_tools.h + +include(../auto.pri) diff --git a/tests/auto/tools/tools.qbs b/tests/auto/tools/tools.qbs new file mode 100644 index 00000000..64cced80 --- /dev/null +++ b/tests/auto/tools/tools.qbs @@ -0,0 +1,15 @@ +import qbs +import qbs.Utilities + +QbsAutotest { + Depends { name: "qbsversion" } + + testName: "tools" + condition: qbsbuildconfig.enableUnitTests + files: [ + "tst_tools.cpp", + "tst_tools.h" + ] + + cpp.defines: base.concat(["QBS_VERSION=" + Utilities.cStringQuote(qbsversion.version)]) +} diff --git a/tests/auto/tools/tst_tools.cpp b/tests/auto/tools/tst_tools.cpp new file mode 100644 index 00000000..edf5a130 --- /dev/null +++ b/tests/auto/tools/tst_tools.cpp @@ -0,0 +1,1284 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#undef QT_NO_CAST_FROM_ASCII // I am qmake, and I approve this hack. + +#include "tst_tools.h" + +#include "../shared.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace qbs; +using namespace qbs::Internal; + +TestTools::TestTools(Settings *settings) + : m_settings(settings), testDataDir(testWorkDir("tools")) +{ +} + +TestTools::~TestTools() +{ + qDeleteAll(m_tmpDirs); +} + +void TestTools::initTestCase() +{ + QDir().mkpath(testDataDir); +} + +void TestTools::fileSaver() +{ + QVERIFY(QDir::setCurrent(testDataDir)); + + static const char *fn = "foo.txt"; + const auto run = [](const std::function &func) { + if (QFile::exists(fn)) + QVERIFY(QFile::remove(fn)); + func(); + if (QFile::exists(fn)) + QVERIFY(QFile::remove(fn)); + }; + + // failing to open the file means nothing works + run([] { + Internal::FileSaver fs(fn); + QVERIFY(!fs.device()); + QVERIFY(!fs.write("hello")); + QVERIFY(!fs.commit()); + QVERIFY(!QFile::exists(fn)); + }); + + // verify that correct usage creates a file with the right contents + run([] { + Internal::FileSaver fs(fn); + QVERIFY(fs.open()); + QVERIFY(fs.device()); + QVERIFY(fs.write("hello")); + QVERIFY(fs.commit()); + QVERIFY(QFile::exists(fn)); + QFile f(fn); + QVERIFY(f.open(QIODevice::ReadOnly)); + QCOMPARE(f.readAll(), QByteArrayLiteral("hello")); + }); + + // failing to commit writes nothing + run([] { + Internal::FileSaver fs(fn); + QVERIFY(fs.open()); + QVERIFY(fs.device()); + QVERIFY(fs.write("hello")); + QVERIFY(!QFile::exists(fn)); + }); + + // verify that correct usage creates a file with the right contents and does not overwrite + run([] { + { + Internal::FileSaver fs(fn); + QVERIFY(fs.open()); + QVERIFY(fs.device()); + QVERIFY(fs.write("hello")); + QVERIFY(fs.commit()); + QVERIFY(QFile::exists(fn)); + QFile f(fn); + QVERIFY(f.open(QIODevice::ReadOnly)); + QCOMPARE(f.readAll(), QByteArrayLiteral("hello")); + } + + const auto lm = QFileInfo(fn).lastModified(); + QVERIFY(lm.isValid()); + + waitForNewTimestamp("."); + + { + Internal::FileSaver fs(fn); + QVERIFY(fs.open()); + QVERIFY(fs.device()); + QVERIFY(fs.write("hello")); + QVERIFY(fs.commit()); + QVERIFY(QFile::exists(fn)); + } + + const auto lm2 = QFileInfo(fn).lastModified(); + QVERIFY(lm2.isValid()); + + QCOMPARE(lm, lm2); // timestamps should be the same since content did not change + + waitForNewTimestamp("."); + + { + Internal::FileSaver fs(fn); + QVERIFY(fs.open()); + QVERIFY(fs.device()); + QVERIFY(fs.write("hello2")); + QVERIFY(fs.commit()); + QVERIFY(QFile::exists(fn)); + QFile f(fn); + QVERIFY(f.open(QIODevice::ReadOnly)); + QCOMPARE(f.readAll(), QByteArrayLiteral("hello2")); + } + + const auto lm3 = QFileInfo(fn).lastModified(); + QVERIFY(lm3.isValid()); + + QVERIFY(lm != lm3); // timestamps should differ since the content changed + + waitForNewTimestamp("."); + + { + // Test overwriteIfUnchanged + Internal::FileSaver fs(fn, true); + QVERIFY(fs.open()); + QVERIFY(fs.device()); + QVERIFY(fs.write("hello2")); + QVERIFY(fs.commit()); + QVERIFY(QFile::exists(fn)); + QFile f(fn); + QVERIFY(f.open(QIODevice::ReadOnly)); + QCOMPARE(f.readAll(), QByteArrayLiteral("hello2")); + } + + const auto lm4 = QFileInfo(fn).lastModified(); + QVERIFY(lm4.isValid()); + + QVERIFY(lm3 != lm4); // timestamps should differ since we always overwrite + }); +} + +void TestTools::testFileInfo() +{ + QCOMPARE(FileInfo::fileName("C:/waffl/copter.exe"), QString("copter.exe")); + QCOMPARE(FileInfo::baseName("C:/waffl/copter.exe.lib"), QString("copter")); + QCOMPARE(FileInfo::completeBaseName("C:/waffl/copter.exe.lib"), QString("copter.exe")); + QCOMPARE(FileInfo::suffix("C:/waffl/copter.exe.lib"), QString("lib")); + QCOMPARE(FileInfo::completeSuffix("C:/waffl/copter.exe.lib"), QString("exe.lib")); + QCOMPARE(FileInfo::path("abc"), QString(".")); + QCOMPARE(FileInfo::path("/abc/lol"), QString("/abc")); + QCOMPARE(FileInfo::path("/fileInRoot"), QString(QLatin1Char('/'))); + if (HostOsInfo::isWindowsHost()) + QCOMPARE(FileInfo::path("C:/fileInDriveRoot"), QString("C:/")); + QVERIFY(!FileInfo::isAbsolute("bla/lol")); + QVERIFY(FileInfo::isAbsolute("/bla/lol")); + if (HostOsInfo::isWindowsHost()) { + QVERIFY(FileInfo::isAbsolute("C:\\bla\\lol")); + QVERIFY(FileInfo::isAbsolute("C:\\")); + QVERIFY(FileInfo::isAbsolute("C:/")); + QVERIFY(!FileInfo::isAbsolute("C:")); + } + QCOMPARE(FileInfo::resolvePath("/abc/lol", "waffl"), QString("/abc/lol/waffl")); + QCOMPARE(FileInfo::resolvePath("/abc/def/ghi/jkl/", "../foo/bar"), QString("/abc/def/ghi/foo/bar")); + QCOMPARE(FileInfo::resolvePath("/abc/def/ghi/jkl/", "../../foo/bar"), QString("/abc/def/foo/bar")); + QCOMPARE(FileInfo::resolvePath("/abc", "../../../foo/bar"), QString("/foo/bar")); + if (HostOsInfo::isWindowsHost()) { + QCOMPARE(FileInfo::resolvePath("C:/share", ".."), QString("C:/")); + QCOMPARE(FileInfo::resolvePath("C:/share", "D:/"), QString("D:/")); + QCOMPARE(FileInfo::resolvePath("C:/share", "D:"), QString()); // should soft-assert + } + QCOMPARE(FileInfo("/does/not/exist").lastModified(), FileTime()); +} + +void TestTools::fileCaseCheck() +{ + QTemporaryFile tempFile(QDir::tempPath() + QLatin1String("/CamelCase")); + QVERIFY2(tempFile.open(), qPrintable(tempFile.errorString())); + QFileInfo tempFileInfo(tempFile.fileName()); + const QString lowerFilePath = tempFileInfo.absolutePath() + QLatin1Char('/') + + tempFileInfo.fileName().toLower(); + const QString upperFilePath = tempFileInfo.absolutePath() + QLatin1Char('/') + + tempFileInfo.fileName().toUpper(); + QVERIFY(FileInfo::isFileCaseCorrect(tempFileInfo.absoluteFilePath())); + if (QFile::exists(lowerFilePath)) + QVERIFY(!FileInfo::isFileCaseCorrect(lowerFilePath)); + if (QFile::exists(upperFilePath)) + QVERIFY(!FileInfo::isFileCaseCorrect(upperFilePath)); +} + +void TestTools::testProfiles() +{ + TemporaryProfile tpp("parent", m_settings); + Profile parentProfile = tpp.p; + TemporaryProfile tpc("child", m_settings); + Profile childProfile = tpc.p; + parentProfile.removeBaseProfile(); + parentProfile.remove("testKey"); + QCOMPARE(parentProfile.value("testKey", "none").toString(), QLatin1String("none")); + parentProfile.setValue("testKey", "testValue"); + QCOMPARE(parentProfile.value("testKey").toString(), QLatin1String("testValue")); + + childProfile.remove("testKey"); + childProfile.removeBaseProfile(); + QCOMPARE(childProfile.value("testKey", "none").toString(), QLatin1String("none")); + childProfile.setBaseProfile("parent"); + QCOMPARE(childProfile.value("testKey").toString(), QLatin1String("testValue")); + + // Change base profile and check if the inherited value also changes. + TemporaryProfile tpf("foo", m_settings); + Profile fooProfile = tpf.p; + fooProfile.setValue("testKey", "gnampf"); + childProfile.setBaseProfile("foo"); + QCOMPARE(childProfile.value("testKey", "none").toString(), QLatin1String("gnampf")); + + ErrorInfo errorInfo; + childProfile.setBaseProfile("SmurfAlongWithMe"); + childProfile.value("blubb", QString(), &errorInfo); + QVERIFY(errorInfo.hasError()); + + errorInfo.clear(); + childProfile.setBaseProfile("parent"); + parentProfile.setBaseProfile("child"); + QVERIFY(!childProfile.value("blubb", QString(), &errorInfo).isValid()); + QVERIFY(errorInfo.hasError()); + + QVERIFY(!childProfile.allKeys(Profile::KeySelectionNonRecursive).empty()); + + errorInfo.clear(); + QVERIFY(childProfile.allKeys(Profile::KeySelectionRecursive, &errorInfo).empty()); + QVERIFY(errorInfo.hasError()); +} + +void TestTools::testSettingsMigration() +{ + QFETCH(QString, baseDir); + QFETCH(bool, hasOldSettings); + Settings settings(baseDir); + if (hasOldSettings) { + QVERIFY(QFileInfo(settings.baseDirectory() + "/qbs/" QBS_VERSION "/profiles/right.txt") + .exists()); + QCOMPARE(settings.value("key", Settings::UserScope).toString(), + settings.baseDirectory() + "/qbs/" QBS_VERSION "/profilesright"); + } else { + QVERIFY(!QFileInfo(settings.baseDirectory() + "/qbs/" QBS_VERSION "/profiles").exists()); + QVERIFY(settings.allKeys(Settings::UserScope).empty()); + } +} + +void TestTools::testSettingsMigration_data() +{ + QTest::addColumn("baseDir"); + QTest::addColumn("hasOldSettings"); + QTest::newRow("settings dir with lots of versions") << setupSettingsDir1() << true; + QTest::newRow("settings dir with only a fallback") << setupSettingsDir2() << true; + QTest::newRow("no previous settings") << setupSettingsDir3() << false; +} + +QString TestTools::setupSettingsDir1() +{ + const auto baseDir = new QTemporaryDir; + m_tmpDirs.push_back(baseDir); + + const Version thisVersion = Version::fromString(QBS_VERSION); + Version predecessor; + if (thisVersion.patchLevel() > 0) { + predecessor.setMajorVersion(thisVersion.majorVersion()); + predecessor.setMinorVersion(thisVersion.minorVersion()); + predecessor.setPatchLevel(thisVersion.patchLevel() - 1); + } else if (thisVersion.minorVersion() > 0) { + predecessor.setMajorVersion(thisVersion.majorVersion()); + predecessor.setMinorVersion(thisVersion.minorVersion() - 1); + predecessor.setPatchLevel(99); + } else { + predecessor.setMajorVersion(thisVersion.majorVersion() - 1); + predecessor.setMajorVersion(99); + predecessor.setPatchLevel(99); + } + const auto versions = QList() << Version(0, 1, 0) << Version(1, 0, 5) << predecessor + << Version(thisVersion.majorVersion() + 1, thisVersion.minorVersion(), + thisVersion.patchLevel()) + << Version(thisVersion.majorVersion(), thisVersion.minorVersion() + 1, + thisVersion.patchLevel()) + << Version(thisVersion.majorVersion(), thisVersion.minorVersion(), + thisVersion.patchLevel() + 1) + << Version(99, 99, 99); + for (const Version &v : versions) { + const QString settingsDir = baseDir->path() + "/qbs/" + v.toString(); + QSettings s(settingsDir + "/qbs.conf", + HostOsInfo::isWindowsHost() ? QSettings::IniFormat : QSettings::NativeFormat); + const QString profilesDir = settingsDir + "/profiles"; + QDir::root().mkpath(profilesDir); + const QString magicString = v == predecessor ? "right" : "wrong"; + QFile f(profilesDir + '/' + magicString + ".txt"); + f.open(QIODevice::WriteOnly); + s.setValue("org/qt-project/qbs/key", profilesDir + magicString); + } + + return baseDir->path(); +} + +QString TestTools::setupSettingsDir2() +{ + const auto baseDir = new QTemporaryDir; + m_tmpDirs.push_back(baseDir); + const QString settingsDir = baseDir->path(); + QSettings s(settingsDir + QLatin1String("/qbs.conf"), + HostOsInfo::isWindowsHost() ? QSettings::IniFormat : QSettings::NativeFormat); + const QString profilesDir = settingsDir + QLatin1String("/qbs/profiles"); + QDir::root().mkpath(profilesDir); + QFile f(profilesDir + "/right.txt"); + f.open(QIODevice::WriteOnly); + s.setValue("org/qt-project/qbs/key", profilesDir + "right"); + + return baseDir->path(); +} + +QString TestTools::setupSettingsDir3() +{ + const auto baseDir = new QTemporaryDir; + m_tmpDirs.push_back(baseDir); + return baseDir->path(); +} + +void TestTools::testBuildConfigMerging() +{ + TemporaryProfile tp(QLatin1String("tst_tools_profile"), m_settings); + Profile profile = tp.p; + profile.setValue(QStringLiteral("topLevelKey"), QStringLiteral("topLevelValue")); + profile.setValue(QStringLiteral("qbs.toolchain"), QStringLiteral("gcc")); + profile.setValue(QStringLiteral("qbs.architecture"), + QStringLiteral("Jean-Claude Pillemann")); + profile.setValue(QStringLiteral("cpp.treatWarningsAsErrors"), true); + QVariantMap overrideMap; + overrideMap.insert(QStringLiteral("qbs.toolchain"), QStringLiteral("clang")); + overrideMap.insert(QStringLiteral("qbs.installRoot"), QStringLiteral("/blubb")); + SetupProjectParameters params; + params.setSettingsDirectory(m_settings->baseDirectory()); + params.setTopLevelProfile(profile.name()); + params.setConfigurationName(QStringLiteral("debug")); + params.setOverriddenValues(overrideMap); + const ErrorInfo error = params.expandBuildConfiguration(); + QVERIFY2(!error.hasError(), qPrintable(error.toString())); + const QVariantMap finalMap = params.finalBuildConfigurationTree(); + QCOMPARE(finalMap.size(), 3); + QCOMPARE(finalMap.value(QStringLiteral("topLevelKey")).toString(), + QStringLiteral("topLevelValue")); + const QVariantMap finalQbsMap = finalMap.value(QStringLiteral("qbs")).toMap(); + QCOMPARE(finalQbsMap.size(), 4); + QCOMPARE(finalQbsMap.value(QStringLiteral("toolchain")).toString(), + QStringLiteral("clang")); + QCOMPARE(finalQbsMap.value(QStringLiteral("configurationName")).toString(), + QStringLiteral("debug")); + QCOMPARE(finalQbsMap.value(QStringLiteral("architecture")).toString(), + QStringLiteral("Jean-Claude Pillemann")); + QCOMPARE(finalQbsMap.value(QStringLiteral("installRoot")).toString(), QLatin1String("/blubb")); + const QVariantMap finalCppMap = finalMap.value(QStringLiteral("cpp")).toMap(); + QCOMPARE(finalCppMap.size(), 1); + QCOMPARE(finalCppMap.value(QStringLiteral("treatWarningsAsErrors")).toBool(), true); +} + +void TestTools::testProcessNameByPid() +{ + QCOMPARE(qAppName(), processNameByPid(QCoreApplication::applicationPid())); +} + + +int toNumber(const QString &str) +{ + int res = 0; + for (const QChar &c : str) + res = (res * 10) + c.digitValue(); + return res; +} + +void TestTools::set_operator_eq() +{ + { + Set set1, set2; + QVERIFY(set1 == set2); + QVERIFY(!(set1 != set2)); + + set1.insert(1); + QVERIFY(set1 != set2); + QVERIFY(!(set1 == set2)); + + set2.insert(1); + QVERIFY(set1 == set2); + QVERIFY(!(set1 != set2)); + + set2.insert(1); + QVERIFY(set1 == set2); + QVERIFY(!(set1 != set2)); + + set1.insert(2); + QVERIFY(set1 != set2); + QVERIFY(!(set1 == set2)); + } + + { + Set set1, set2; + QVERIFY(set1 == set2); + QVERIFY(!(set1 != set2)); + + set1.insert("one"); + QVERIFY(set1 != set2); + QVERIFY(!(set1 == set2)); + + set2.insert("one"); + QVERIFY(set1 == set2); + QVERIFY(!(set1 != set2)); + + set2.insert("one"); + QVERIFY(set1 == set2); + QVERIFY(!(set1 != set2)); + + set1.insert("two"); + QVERIFY(set1 != set2); + QVERIFY(!(set1 == set2)); + } + + { + Set a; + Set b; + + a += "otto"; + b += "willy"; + + QVERIFY(a != b); + QVERIFY(!(a == b)); + } + + { + Set s1, s2; + s1.reserve(100); + s2.reserve(4); + QVERIFY(s1 == s2); + s1 << 100 << 200 << 300 << 400; + s2 << 400 << 300 << 200 << 100; + QVERIFY(s1 == s2); + } +} + +void TestTools::set_swap() +{ + Set s1, s2; + s1.insert(1); + s2.insert(2); + s1.swap(s2); + QCOMPARE(*s1.begin(),2); + QCOMPARE(*s2.begin(),1); +} + +void TestTools::set_size() +{ + Set set; + QVERIFY(set.size() == 0); + QVERIFY(set.empty()); + QVERIFY(set.size() == set.size()); + + set.insert(1); + QVERIFY(set.size() == 1); + QVERIFY(!set.empty()); + QVERIFY(set.size() == set.size()); + + set.insert(1); + QVERIFY(set.size() == 1); + QVERIFY(!set.empty()); + QVERIFY(set.size() == set.size()); + + set.insert(2); + QVERIFY(set.size() == 2); + QVERIFY(!set.empty()); + QVERIFY(set.size() == set.size()); + + set.remove(1); + QVERIFY(set.size() == 1); + QVERIFY(!set.empty()); + QVERIFY(set.size() == set.size()); + + set.remove(1); + QVERIFY(set.size() == 1); + QVERIFY(!set.empty()); + QVERIFY(set.size() == set.size()); + + set.remove(2); + QVERIFY(set.size() == 0); + QVERIFY(set.empty()); + QVERIFY(set.size() == set.size()); +} + +void TestTools::set_capacity() +{ + Set set; + size_t n = set.capacity(); + QVERIFY(n == 0); + + for (int i = 0; i < 1000; ++i) { + set.insert(i); + QVERIFY(set.capacity() >= set.size()); + } +} + +void TestTools::set_reserve() +{ + Set set; + + set.reserve(1000); + QVERIFY(set.capacity() >= 1000); + + for (int i = 0; i < 500; ++i) + set.insert(i); + + QVERIFY(set.capacity() >= 1000); + + for (int j = 0; j < 500; ++j) + set.remove(j); + + QVERIFY(set.capacity() >= 1000); +} + +void TestTools::set_clear() +{ + Set set1, set2; + QVERIFY(set1.size() == 0); + + set1.clear(); + QVERIFY(set1.size() == 0); + + set1.insert("foo"); + QVERIFY(set1.size() != 0); + + set2 = set1; + + set1.clear(); + QVERIFY(set1.size() == 0); + QVERIFY(set2.size() != 0); + + set2.clear(); + QVERIFY(set1.size() == 0); + QVERIFY(set2.size() == 0); +} + +void TestTools::set_remove() +{ + Set set1; + + const size_t max = 500; + + for (size_t i = 0; i < max; ++i) + set1.insert(QString::number(i)); + + QCOMPARE(set1.size(), max); + + for (size_t j = 0; j < max; ++j) { + set1.remove(QString::number((j * 17) % max)); + QCOMPARE(set1.size(), max - j - 1); + } +} + +void TestTools::set_contains() +{ + Set set1; + + for (int i = 0; i < 500; ++i) { + QVERIFY(!set1.contains(QString::number(i))); + set1.insert(QString::number(i)); + QVERIFY(set1.contains(QString::number(i))); + } + + QCOMPARE(set1.size(), size_t { 500 }); + + for (int j = 0; j < 500; ++j) { + int i = (j * 17) % 500; + QVERIFY(set1.contains(QString::number(i))); + set1.remove(QString::number(i)); + QVERIFY(!set1.contains(QString::number(i))); + } +} + +void TestTools::set_containsSet() +{ + Set set1; + Set set2; + + // empty set contains the empty set + QVERIFY(set1.contains(set2)); + + for (int i = 0; i < 500; ++i) { + set1.insert(QString::number(i)); + set2.insert(QString::number(i)); + } + QVERIFY(set1.contains(set2)); + + set2.remove(QString::number(19)); + set2.remove(QString::number(82)); + set2.remove(QString::number(7)); + QVERIFY(set1.contains(set2)); + + set1.remove(QString::number(23)); + QVERIFY(!set1.contains(set2)); + + // filled set contains the empty set as well + Set set3; + QVERIFY(set1.contains(set3)); + + // the empty set doesn't contain a filled set + QVERIFY(!set3.contains(set1)); + + // verify const signature + const Set set4; + QVERIFY(set3.contains(set4)); +} + +void TestTools::set_begin() +{ + Set set1; + Set set2 = set1; + + { + const auto i = set1.constBegin(); + const auto j = set1.cbegin(); + const auto k = set2.constBegin(); + const auto ell = set2.cbegin(); + + QVERIFY(i == j); + QVERIFY(k == ell); + } + + set1.insert(44); + + { + const auto i = set1.constBegin(); + const auto j = set1.cbegin(); + const auto k = set2.constBegin(); + const auto ell = set2.cbegin(); + + QVERIFY(i == j); + QVERIFY(k == ell); + } + + set2 = set1; + + { + const auto i = set1.constBegin(); + const auto j = set1.cbegin(); + const auto k = set2.constBegin(); + const auto ell = set2.cbegin(); + + QVERIFY(i == j); + QVERIFY(k == ell); + } +} + +void TestTools::set_end() +{ + Set set1; + Set set2 = set1; + + { + const auto i = set1.constEnd(); + const auto j = set1.cend(); + const auto k = set2.constEnd(); + const auto ell = set2.cend(); + + QVERIFY(i == j); + QVERIFY(k == ell); + + QVERIFY(set1.constBegin() == set1.constEnd()); + QVERIFY(set2.constBegin() == set2.constEnd()); + } + + set1.insert(44); + + { + const auto i = set1.constEnd(); + const auto j = set1.cend(); + const auto k = set2.constEnd(); + const auto ell = set2.cend(); + + QVERIFY(i == j); + QVERIFY(k == ell); + + QVERIFY(set1.constBegin() != set1.constEnd()); + QVERIFY(set2.constBegin() == set2.constEnd()); + } + + set2 = set1; + + { + const auto i = set1.constEnd(); + const auto j = set1.cend(); + const auto k = set2.constEnd(); + const auto ell = set2.cend(); + + QVERIFY(i == j); + QVERIFY(k == ell); + + QVERIFY(set1.constBegin() != set1.constEnd()); + QVERIFY(set2.constBegin() != set2.constEnd()); + } + + set1.clear(); + set2.clear(); + QVERIFY(set1.constBegin() == set1.constEnd()); + QVERIFY(set2.constBegin() == set2.constEnd()); +} + +struct IdentityTracker { + int value, id; +}; +inline bool operator==(IdentityTracker lhs, IdentityTracker rhs) { return lhs.value == rhs.value; } +inline bool operator<(IdentityTracker lhs, IdentityTracker rhs) { return lhs.value < rhs.value; } + +void TestTools::set_insert() +{ + { + Set set1; + QVERIFY(set1.size() == 0); + set1.insert(1); + QVERIFY(set1.size() == 1); + set1.insert(2); + QVERIFY(set1.size() == 2); + set1.insert(2); + QVERIFY(set1.size() == 2); + QVERIFY(set1.contains(2)); + set1.remove(2); + QVERIFY(set1.size() == 1); + QVERIFY(!set1.contains(2)); + set1.insert(2); + QVERIFY(set1.size() == 2); + QVERIFY(set1.contains(2)); + } + + { + Set set1; + QVERIFY(set1.size() == 0); + set1 << 1; + QVERIFY(set1.size() == 1); + set1 << 2; + QVERIFY(set1.size() == 2); + set1 << 2; + QVERIFY(set1.size() == 2); + QVERIFY(set1.contains(2)); + set1.remove(2); + QVERIFY(set1.size() == 1); + QVERIFY(!set1.contains(2)); + set1 << 2; + QVERIFY(set1.size() == 2); + QVERIFY(set1.contains(2)); + } + + { + Set set; + QCOMPARE(set.size(), size_t { 0 }); + const int dummy = -1; + IdentityTracker id00 = {0, 0}, id01 = {0, 1}, searchKey = {0, dummy}; + QCOMPARE(set.insert(id00).first->id, id00.id); + QCOMPARE(set.size(), size_t { 1 }); + QCOMPARE(set.insert(id01).first->id, id00.id); // first inserted is kept + QCOMPARE(set.size(), size_t { 1 }); + QCOMPARE(set.find(searchKey)->id, id00.id); + } +} + +void TestTools::set_reverseIterators() +{ + Set s; + s << 1 << 17 << 61 << 127 << 911; + std::vector v(s.begin(), s.end()); + std::reverse(v.begin(), v.end()); + const Set &cs = s; + QVERIFY(std::equal(v.begin(), v.end(), s.rbegin())); + QVERIFY(std::equal(v.begin(), v.end(), s.crbegin())); + QVERIFY(std::equal(v.begin(), v.end(), cs.rbegin())); + QVERIFY(std::equal(s.rbegin(), s.rend(), v.begin())); + QVERIFY(std::equal(s.crbegin(), s.crend(), v.begin())); + QVERIFY(std::equal(cs.rbegin(), cs.rend(), v.begin())); +} + +void TestTools::set_stlIterator() +{ + Set set1; + for (int i = 0; i < 25000; ++i) + set1.insert(QString::number(i)); + + { + int sum = 0; + auto i = set1.cbegin(); + while (i != set1.end()) { + sum += toNumber(*i); + ++i; + } + QVERIFY(sum == 24999 * 25000 / 2); + } + + { + int sum = 0; + auto i = set1.cend(); + while (i != set1.begin()) { + --i; + sum += toNumber(*i); + } + QVERIFY(sum == 24999 * 25000 / 2); + } +} + +void TestTools::set_stlMutableIterator() +{ + Set set1; + for (int i = 0; i < 25000; ++i) + set1.insert(QString::number(i)); + + { + int sum = 0; + auto i = set1.begin(); + while (i != set1.end()) { + sum += toNumber(*i); + ++i; + } + QVERIFY(sum == 24999 * 25000 / 2); + } + + { + int sum = 0; + auto i = set1.end(); + while (i != set1.begin()) { + --i; + sum += toNumber(*i); + } + QVERIFY(sum == 24999 * 25000 / 2); + } + + { + Set set2 = set1; + Set set3 = set2; + + auto i = set2.begin(); + auto j = set3.begin(); + + while (i != set2.end()) { + i = set2.erase(i); + } + QVERIFY(set2.empty()); + QVERIFY(!set3.empty()); + + j = set3.end(); + while (j != set3.begin()) { + j--; + if (j + 1 != set3.end()) + set3.erase(j + 1); + } + if (set3.begin() != set3.end()) + set3.erase(set3.begin()); + + QVERIFY(set2.empty()); + QVERIFY(set3.empty()); + + i = set2.insert("foo").first; + QCOMPARE(*i, QLatin1String("foo")); + } +} + +void TestTools::set_setOperations() +{ + Set set1, set2; + set1 << "alpha" << "beta" << "gamma" << "delta" << "zeta" << "omega"; + set2 << "beta" << "gamma" << "delta" << "epsilon" << "iota" << "omega"; + + Set set3 = set1; + set3.unite(set2); + QVERIFY(set3.size() == 8); + QVERIFY(set3.contains("alpha")); + QVERIFY(set3.contains("beta")); + QVERIFY(set3.contains("gamma")); + QVERIFY(set3.contains("delta")); + QVERIFY(set3.contains("epsilon")); + QVERIFY(set3.contains("zeta")); + QVERIFY(set3.contains("iota")); + QVERIFY(set3.contains("omega")); + + Set set4 = set2; + set4.unite(set1); + QVERIFY(set4.size() == 8); + QVERIFY(set4.contains("alpha")); + QVERIFY(set4.contains("beta")); + QVERIFY(set4.contains("gamma")); + QVERIFY(set4.contains("delta")); + QVERIFY(set4.contains("epsilon")); + QVERIFY(set4.contains("zeta")); + QVERIFY(set4.contains("iota")); + QVERIFY(set4.contains("omega")); + + QVERIFY(set3 == set4); + + Set set5 = set1; + set5.intersect(set2); + QVERIFY(set5.size() == 4); + QVERIFY(set5.contains("beta")); + QVERIFY(set5.contains("gamma")); + QVERIFY(set5.contains("delta")); + QVERIFY(set5.contains("omega")); + + Set set6 = set2; + set6.intersect(set1); + QVERIFY(set6.size() == 4); + QVERIFY(set6.contains("beta")); + QVERIFY(set6.contains("gamma")); + QVERIFY(set6.contains("delta")); + QVERIFY(set6.contains("omega")); + + QVERIFY(set5 == set6); + + Set set7 = set1; + set7.subtract(set2); + QVERIFY(set7.size() == 2); + QVERIFY(set7.contains("alpha")); + QVERIFY(set7.contains("zeta")); + + Set set8 = set2; + set8.subtract(set1); + QVERIFY(set8.size() == 2); + QVERIFY(set8.contains("epsilon")); + QVERIFY(set8.contains("iota")); + + Set set9 = set1 | set2; + QVERIFY(set9 == set3); + + Set set10 = set1 & set2; + QVERIFY(set10 == set5); + + Set set11 = set1 + set2; + QVERIFY(set11 == set3); + + Set set12 = set1 - set2; + QVERIFY(set12 == set7); + + Set set13 = set2 - set1; + QVERIFY(set13 == set8); + + Set set14 = set1; + set14 |= set2; + QVERIFY(set14 == set3); + + Set set15 = set1; + set15 &= set2; + QVERIFY(set15 == set5); + + Set set16 = set1; + set16 += set2; + QVERIFY(set16 == set3); + + Set set17 = set1; + set17 -= set2; + QVERIFY(set17 == set7); + + Set set18 = set2; + set18 -= set1; + QVERIFY(set18 == set8); +} + +void TestTools::set_makeSureTheComfortFunctionsCompile() +{ + Set set1, set2, set3; + set1 << 5; + set1 |= set2; + set1 |= 5; + set1 &= set2; + set1 &= 5; + set1 += set2; + set1 += 5; + set1 -= set2; + set1 -= 5; + set1 = set2 | set3; + set1 = set2 & set3; + set1 = set2 + set3; + set1 = set2 - set3; +} + +void TestTools::set_initializerList() +{ + Set set = {1, 1, 2, 3, 4, 5}; + QCOMPARE(set.size(), size_t { 5 }); + QVERIFY(set.contains(1)); + QVERIFY(set.contains(2)); + QVERIFY(set.contains(3)); + QVERIFY(set.contains(4)); + QVERIFY(set.contains(5)); + + // check _which_ of the equal elements gets inserted (in the QHash/QMap case, it's the last): + const Set set2 = {{1, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}, {5, 5}}; + QCOMPARE(set2.size(), size_t { 5 }); + const int dummy = -1; + const IdentityTracker searchKey = {1, dummy}; + QCOMPARE(set2.find(searchKey)->id, 0); + + Set emptySet{}; + QVERIFY(emptySet.empty()); + + Set set3{{}, {}, {}}; + QVERIFY(!set3.empty()); +} + +void TestTools::set_intersects() +{ + Set s1; + Set s2; + + QVERIFY(!s1.intersects(s1)); + QVERIFY(!s1.intersects(s2)); + + s1 << 100; + QVERIFY(s1.intersects(s1)); + QVERIFY(!s1.intersects(s2)); + + s2 << 200; + QVERIFY(!s1.intersects(s2)); + + s1 << 200; + QVERIFY(s1.intersects(s2)); + + Set s3; + s3 << 500; + QVERIFY(!s1.intersects(s3)); + s3 << 200; + QVERIFY(s1.intersects(s3)); +} + +void TestTools::stringutils_join() +{ + QFETCH(std::vector, input); + QFETCH(std::string, separator); + QFETCH(std::string, expectedResult); + + QCOMPARE(join(input, separator), expectedResult); +} + +void TestTools::stringutils_join_data() +{ + QTest::addColumn>("input"); + QTest::addColumn("separator"); + QTest::addColumn("expectedResult"); + + QTest::newRow("data1") + << std::vector() + << std::string() + << std::string(); + + QTest::newRow("data2") + << std::vector() + << std::string("separator") + << std::string(); + + QTest::newRow("data3") + << std::vector({"one"}) + << std::string("separator") + << std::string("one"); + + QTest::newRow("data4") + << std::vector({"one"}) + << std::string("separator") + << std::string("one"); + + + QTest::newRow("data5") + << std::vector({"a", "b"}) + << std::string(" ") + << std::string("a b"); + + QTest::newRow("data6") + << std::vector({"a", "b", "c"}) + << std::string(" ") + << std::string("a b c"); +} + +void TestTools::stringutils_join_empty() +{ + std::vector list; + std::string string = join(list, std::string()); + + QVERIFY(string.empty()); +} + +void TestTools::stringutils_join_char() +{ + QFETCH(std::vector, input); + QFETCH(char, separator); + QFETCH(std::string, expectedResult); + + QCOMPARE(join(input, separator), expectedResult); +} + +void TestTools::stringutils_join_char_data() +{ + QTest::addColumn>("input"); + QTest::addColumn("separator"); + QTest::addColumn("expectedResult"); + + QTest::newRow("data1") + << std::vector() + << ' ' + << std::string(); + + QTest::newRow("data5") + << std::vector({"a", "b"}) + << ' ' + << std::string("a b"); + + QTest::newRow("data6") + << std::vector({"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); + const SettingsPtr s = settings(); + TestTools tt(s.get()); + return QTest::qExec(&tt, argc, argv); +} diff --git a/tests/auto/tools/tst_tools.h b/tests/auto/tools/tst_tools.h new file mode 100644 index 00000000..bd8538be --- /dev/null +++ b/tests/auto/tools/tst_tools.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** 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 +#include + +QT_BEGIN_NAMESPACE +class QTemporaryDir; +QT_END_NAMESPACE + +namespace qbs { +class Settings; +} + +class TestTools : public QObject +{ + Q_OBJECT + +public: + TestTools(qbs::Settings *settings); + ~TestTools(); + +public slots: + virtual void initTestCase(); + +private slots: + void fileSaver(); + + void fileCaseCheck(); + void testBuildConfigMerging(); + void testFileInfo(); + void testProcessNameByPid(); + void testProfiles(); + void testSettingsMigration(); + void testSettingsMigration_data(); + + void set_operator_eq(); + void set_swap(); + void set_size(); + void set_capacity(); + void set_reserve(); + void set_clear(); + void set_remove(); + void set_contains(); + void set_containsSet(); + void set_begin(); + void set_end(); + void set_insert(); + void set_reverseIterators(); + void set_stlIterator(); + void set_stlMutableIterator(); + void set_setOperations(); + void set_makeSureTheComfortFunctionsCompile(); + 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(); + QString setupSettingsDir3(); + + qbs::Settings * const m_settings; + QList m_tmpDirs; + + const QString testDataDir; +}; diff --git a/tests/benchmarker/activities.h b/tests/benchmarker/activities.h new file mode 100644 index 00000000..f348f11d --- /dev/null +++ b/tests/benchmarker/activities.h @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QBS_BENCHMARKER_ACTIVITY_H +#define QBS_BENCHMARKER_ACTIVITY_H + +#include + +namespace qbsBenchmarker { + +enum Activity { ActivityResolving = 1, ActivityRuleExecution = 2, ActivityNullBuild = 4 }; +Q_DECLARE_FLAGS(Activities, Activity) +Q_DECLARE_OPERATORS_FOR_FLAGS(Activities) + +} // namespace qbsBenchmarker + +#endif // Include guard. + diff --git a/tests/benchmarker/benchmarker-main.cpp b/tests/benchmarker/benchmarker-main.cpp new file mode 100644 index 00000000..2aa37937 --- /dev/null +++ b/tests/benchmarker/benchmarker-main.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** 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 "benchmarker.h" +#include "commandlineparser.h" +#include "exception.h" + +#include + +#include +#include + +using namespace qbsBenchmarker; + +static bool hasRegression = false; + +static int relativeChange(qint64 oldVal, qint64 newVal) +{ + return newVal == 0 ? 0 : newVal * 100 / oldVal - 100; +} + +static QByteArray relativeChangeString(int change) +{ + QByteArray changeString = QByteArray::number(change); + changeString += " %"; + if (change > 0) + changeString.prepend('+'); + return changeString; +} + +static void printResults(Activity activity, const BenchmarkResults &results, + int regressionThreshold) +{ + std::cout << "========== Performance data for "; + switch (activity) { + case ActivityResolving: + std::cout << "Resolving"; + break; + case ActivityRuleExecution: + std::cout << "Rule Execution"; + break; + case ActivityNullBuild: + std::cout << "Null Build"; + break; + } + std::cout << " ==========" << std::endl; + const BenchmarkResult result = results.value(activity); + const char * const indent = " "; + std::cout << indent << "Old instruction count: " << result.oldInstructionCount << std::endl; + std::cout << indent << "New instruction count: " << result.newInstructionCount << std::endl; + int change = relativeChange(result.oldInstructionCount, result.newInstructionCount); + if (change > regressionThreshold) + hasRegression = true; + std::cout << indent << "Relative change: " + << relativeChangeString(change).constData() + << std::endl; + std::cout << indent << "Old peak memory usage: " << result.oldPeakMemoryUsage << " Bytes" + << std::endl; + std::cout << indent + << "New peak memory usage: " << result.newPeakMemoryUsage << " Bytes" << std::endl; + change = relativeChange(result.oldPeakMemoryUsage, result.newPeakMemoryUsage); + if (change > regressionThreshold) + hasRegression = true; + std::cout << indent << "Relative change: " + << relativeChangeString(change).constData() + << std::endl; +} + +static void printResults(Activities activities, const BenchmarkResults &results, + int regressionThreshold) +{ + if (activities & ActivityResolving) + printResults(ActivityResolving, results, regressionThreshold); + if (activities & ActivityRuleExecution) + printResults(ActivityRuleExecution, results, regressionThreshold); + if (activities & ActivityNullBuild) + printResults(ActivityNullBuild, results, regressionThreshold); +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + CommandLineParser clParser; + try { + clParser.parse(); + } catch (const Exception &e) { + std::cerr << qPrintable(e.description()) << std::endl; + return EXIT_FAILURE; + } + + Benchmarker benchmarker(clParser.activies(), clParser.oldCommit(), clParser.newCommit(), + clParser.testProjectFilePath(), clParser.qbsRepoDirPath()); + try { + benchmarker.benchmark(); + printResults(clParser.activies(), benchmarker.results(), clParser.regressionThreshold()); + if (hasRegression) { + benchmarker.keepRawData(); + std::cout << "Performance regression detected. Raw benchmarking data available " + "under " << qPrintable(benchmarker.rawDataBaseDir()) << '.' << std::endl; + } + } catch (const Exception &e) { + benchmarker.keepRawData(); + std::cerr << qPrintable(e.description()) << std::endl; + std::cerr << "Build data available under " << qPrintable(benchmarker.rawDataBaseDir()) + << '.' << std::endl; + return EXIT_FAILURE; + } +} diff --git a/tests/benchmarker/benchmarker.cpp b/tests/benchmarker/benchmarker.cpp new file mode 100644 index 00000000..2cc442a7 --- /dev/null +++ b/tests/benchmarker/benchmarker.cpp @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** 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 "benchmarker.h" + +#include "exception.h" +#include "runsupport.h" +#include "valgrindrunner.h" + +#include + +#include +#include + +namespace qbsBenchmarker { + +Benchmarker::Benchmarker(Activities activities, QString oldCommit, QString newCommit, + QString testProject, QString qbsRepo) + : m_activities(activities) + , m_oldCommit(std::move(oldCommit)) + , m_newCommit(std::move(newCommit)) + , m_testProject(std::move(testProject)) + , m_qbsRepo(std::move(qbsRepo)) +{ +} + +Benchmarker::~Benchmarker() +{ + if (!m_commitToRestore.isEmpty()) { + try { + runProcess(QStringList() << "git" << "checkout" << m_commitToRestore, m_qbsRepo); + } catch (const Exception &e) { + qDebug("Failed to restore original commit %s: %s", qPrintable(m_commitToRestore), + qPrintable(e.description())); + } + } +} + +void Benchmarker::benchmark() +{ + rememberCurrentRepoState(); + runProcess(QStringList() << "git" << "checkout" << m_oldCommit, m_qbsRepo); + const QString oldQbsBuildDir = m_baseOutputDir.path() + "/qbs-build." + m_oldCommit; + std::cout << "Building from old repo state..." << std::endl; + buildQbs(oldQbsBuildDir); + runProcess(QStringList() << "git" << "checkout" << m_newCommit, m_qbsRepo); + const QString newQbsBuildDir = m_baseOutputDir.path() + "/qbs-build." + m_newCommit; + std::cout << "Building from new repo state..." << std::endl; + buildQbs(newQbsBuildDir); + std::cout << "Now running valgrind. This can take a while." << std::endl; + + ValgrindRunner oldDataRetriever(m_activities, m_testProject, oldQbsBuildDir, + m_baseOutputDir.path() + "/benchmark-data." + m_oldCommit); + ValgrindRunner newDataRetriever(m_activities, m_testProject, newQbsBuildDir, + m_baseOutputDir.path() + "/benchmark-data." + m_newCommit); + QFuture oldFuture = QtConcurrent::run(&oldDataRetriever, &ValgrindRunner::run); + QFuture newFuture = QtConcurrent::run(&newDataRetriever, &ValgrindRunner::run); + oldFuture.waitForFinished(); + const auto oldValgrindResults = oldDataRetriever.results(); + for (const ValgrindResult &valgrindResult : oldValgrindResults) { + BenchmarkResult &benchmarkResult = m_results[valgrindResult.activity]; + benchmarkResult.oldInstructionCount = valgrindResult.instructionCount; + benchmarkResult.oldPeakMemoryUsage = valgrindResult.peakMemoryUsage; + } + newFuture.waitForFinished(); + const auto newValgrindResults = newDataRetriever.results(); + for (const ValgrindResult &valgrindResult : newValgrindResults) { + BenchmarkResult &benchmarkResult = m_results[valgrindResult.activity]; + benchmarkResult.newInstructionCount = valgrindResult.instructionCount; + benchmarkResult.newPeakMemoryUsage = valgrindResult.peakMemoryUsage; + } + std::cout << "Done!" << std::endl; +} + +void Benchmarker::rememberCurrentRepoState() +{ + QByteArray commit; + int exitCode = 0; + try { + runProcess(QStringList() << "git" << "symbolic-ref" << "--short" << "HEAD", m_qbsRepo, + &commit, &exitCode); + } catch (const Exception &) { + if (exitCode == 0) { + // runProcess did not throw because of the exit code. + throw; + } + // Fallback, in case git cannot retrieve a nice symbolic name. + runProcess(QStringList() << "git" << "describe" << "HEAD", m_qbsRepo, &commit); + } + m_commitToRestore = QString::fromLatin1(commit); +} + +void Benchmarker::buildQbs(const QString &buildDir) const +{ + if (!QDir::root().mkpath(buildDir)) + throw Exception(QStringLiteral("Failed to create directory '%1'.").arg(buildDir)); + runProcess(QStringList() << "qmake" << "CONFIG+=force_debug_info" + << (m_qbsRepo + "/qbs.pro"), buildDir); + runProcess(QStringList() << "make" << "-s", buildDir); +} + +} // namespace qbsBenchmarker diff --git a/tests/benchmarker/benchmarker.h b/tests/benchmarker/benchmarker.h new file mode 100644 index 00000000..6313e809 --- /dev/null +++ b/tests/benchmarker/benchmarker.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QBS_BENCHMARKER_BENCHMARKER_H +#define QBS_BENCHMARKER_BENCHMARKER_H + +#include "activities.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QStringList; +QT_END_NAMESPACE + +namespace qbsBenchmarker { + +class BenchmarkResult +{ +public: + qint64 oldInstructionCount; + qint64 newInstructionCount; + qint64 oldPeakMemoryUsage; + qint64 newPeakMemoryUsage; +}; +using BenchmarkResults = QHash; + +class Benchmarker +{ +public: + Benchmarker(Activities activities, QString oldCommit, QString newCommit, + QString testProject, QString qbsRepo); + ~Benchmarker(); + + void benchmark(); + void keepRawData() { m_baseOutputDir.setAutoRemove(false ); } + + BenchmarkResults results() const { return m_results; } + QString rawDataBaseDir() const { return m_baseOutputDir.path(); } + +private: + void rememberCurrentRepoState(); + void buildQbs(const QString &buildDir) const; + + const Activities m_activities; + const QString m_oldCommit; + const QString m_newCommit; + const QString m_testProject; + const QString m_qbsRepo; + QString m_commitToRestore; + QTemporaryDir m_baseOutputDir; + BenchmarkResults m_results; +}; + +} // namespace qbsBenchmarker + +#endif // Include guard. diff --git a/tests/benchmarker/benchmarker.pro b/tests/benchmarker/benchmarker.pro new file mode 100644 index 00000000..5342e650 --- /dev/null +++ b/tests/benchmarker/benchmarker.pro @@ -0,0 +1,20 @@ +TARGET = qbs_benchmarker +DESTDIR = ../../bin +CONFIG += console +CONFIG -= app_bundle +CONFIG += c++14 +QT += concurrent +SOURCES = \ + benchmarker-main.cpp \ + benchmarker.cpp \ + commandlineparser.cpp \ + runsupport.cpp \ + valgrindrunner.cpp + +HEADERS = \ + activities.h \ + benchmarker.h \ + commandlineparser.h \ + exception.h \ + runsupport.h \ + valgrindrunner.h diff --git a/tests/benchmarker/benchmarker.qbs b/tests/benchmarker/benchmarker.qbs new file mode 100644 index 00000000..72b535b4 --- /dev/null +++ b/tests/benchmarker/benchmarker.qbs @@ -0,0 +1,32 @@ +import qbs + +QtApplication { + name: "qbs_benchmarker" + type: "application" + consoleApplication: true + cpp.cxxLanguageVersion: "c++14" + condition: Qt.concurrent.present + Depends { name: "qbsbuildconfig" } + Depends { + name: "Qt.concurrent" + required: false + } + files: [ + "activities.h", + "benchmarker-main.cpp", + "benchmarker.cpp", + "benchmarker.h", + "commandlineparser.cpp", + "commandlineparser.h", + "exception.h", + "runsupport.cpp", + "runsupport.h", + "valgrindrunner.cpp", + "valgrindrunner.h", + ] + Group { + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: qbsbuildconfig.appInstallDir + } +} diff --git a/tests/benchmarker/commandlineparser.cpp b/tests/benchmarker/commandlineparser.cpp new file mode 100644 index 00000000..80241b44 --- /dev/null +++ b/tests/benchmarker/commandlineparser.cpp @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** 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 "commandlineparser.h" + +#include "exception.h" + +#include +#include +#include +#include + +namespace qbsBenchmarker { + +static QString resolveActivity() { return "resolving"; } +static QString ruleExecutionActivity() { return "rule-execution"; } +static QString nullBuildActivity() { return "null-build"; } +static QString allActivities() { return "all"; } + +CommandLineParser::CommandLineParser() = default; + +void CommandLineParser::parse() +{ + QCommandLineParser parser; + parser.setApplicationDescription("This tool aims to detect qbs performance regressions " + "using valgrind."); + parser.addHelpOption(); + QCommandLineOption oldCommitOption(QStringList{"old-commit", "o"}, "The old qbs commit.", + "old commit"); + parser.addOption(oldCommitOption); + QCommandLineOption newCommitOption(QStringList{"new-commit", "n"}, "The new qbs commit.", + "new commit"); + parser.addOption(newCommitOption); + QCommandLineOption testProjectOption(QStringList{"test-project", "p"}, + "The example project to use for the benchmark.", "project file path"); + parser.addOption(testProjectOption); + QCommandLineOption qbsRepoOption(QStringList{"qbs-repo", "r"}, "The qbs repository.", + "repo path"); + parser.addOption(qbsRepoOption); + QCommandLineOption activitiesOption(QStringList{"activities", "a"}, + QStringLiteral("The activities to benchmark. Possible values (CSV): %1,%2,%3,%4") + .arg(resolveActivity(), ruleExecutionActivity(), nullBuildActivity(), + allActivities()), "activities", allActivities()); + parser.addOption(activitiesOption); + QCommandLineOption thresholdOption(QStringList{"regression-threshold", "t"}, + "A relative increase higher than this is considered a performance regression. " + "All temporary data from running the benchmarks will be kept if that happens.", + "value in per cent"); + parser.addOption(thresholdOption); + parser.process(*QCoreApplication::instance()); + const QList mandatoryOptions = QList() + << oldCommitOption << newCommitOption << testProjectOption << qbsRepoOption; + for (const QCommandLineOption &o : mandatoryOptions) { + if (!parser.isSet(o)) + throwException(o.names().constFirst(), parser.helpText()); + if (parser.value(o).isEmpty()) + throwException(o.names().constFirst(), QString(), parser.helpText()); + } + m_oldCommit = parser.value(oldCommitOption); + m_newCommit = parser.value(newCommitOption); + if (m_oldCommit == m_newCommit) { + throw Exception(QStringLiteral("Error parsing command line: " + "'new commit' and 'old commit' must be different commits.\n%1").arg(parser.helpText())); + } + m_testProjectFilePath = parser.value(testProjectOption); + m_qbsRepoDirPath = parser.value(qbsRepoOption); + const QStringList activitiesList = parser.value(activitiesOption).split(','); + m_activities = Activities(); + for (const QString &activityString : activitiesList) { + if (activityString == allActivities()) { + m_activities = ActivityResolving | ActivityRuleExecution | ActivityNullBuild; + break; + } else if (activityString == resolveActivity()) { + m_activities = ActivityResolving; + } else if (activityString == ruleExecutionActivity()) { + m_activities |= ActivityRuleExecution; + } else if (activityString == nullBuildActivity()) { + m_activities |= ActivityNullBuild; + } else { + throwException(activitiesOption.names().constFirst(), + activityString, + parser.helpText()); + } + } + m_regressionThreshold = 5; + if (parser.isSet(thresholdOption)) { + bool ok = true; + const QString rawThresholdValue = parser.value(thresholdOption); + m_regressionThreshold = rawThresholdValue.toInt(&ok); + if (!ok) + throwException(thresholdOption.names().constFirst(), + rawThresholdValue, + parser.helpText()); + } +} + +void CommandLineParser::throwException(const QString &optionName, const QString &illegalValue, + const QString &helpText) +{ + const QString errorText(QStringLiteral("Error parsing command line: Illegal value '%1' " + "for option '--%2'.\n%3").arg(illegalValue, optionName, helpText)); + throw Exception(errorText); +} + +void CommandLineParser::throwException(const QString &missingOption, const QString &helpText) +{ + const QString errorText(QStringLiteral("Error parsing command line: Missing mandatory " + "option '--%1'.\n%3").arg(missingOption, helpText)); + throw Exception(errorText); +} + + +} // namespace qbsBenchmarker diff --git a/tests/benchmarker/commandlineparser.h b/tests/benchmarker/commandlineparser.h new file mode 100644 index 00000000..72a36456 --- /dev/null +++ b/tests/benchmarker/commandlineparser.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QBS_BENCHMARKER_COMMANDLINEPARSER_H +#define QBS_BENCHMARKER_COMMANDLINEPARSER_H + +#include "activities.h" + +#include + +namespace qbsBenchmarker { + +class CommandLineParser +{ +public: + CommandLineParser(); + + void parse(); + + Activities activies() const { return m_activities; } + QString oldCommit() const { return m_oldCommit; } + QString newCommit() const { return m_newCommit; } + QString testProjectFilePath() const { return m_testProjectFilePath; } + QString qbsRepoDirPath() const { return m_qbsRepoDirPath; } + int regressionThreshold() const { return m_regressionThreshold; } + +private: + [[noreturn]] void throwException(const QString &optionName, const QString &illegalValue, + const QString &helpText); + [[noreturn]] void throwException(const QString &missingOption, const QString &helpText); + + Activities m_activities; + QString m_oldCommit; + QString m_newCommit; + QString m_testProjectFilePath; + QString m_qbsRepoDirPath; + int m_regressionThreshold = 0; +}; + +} // namespace qbsBenchmarker + +#endif // Include guard. diff --git a/tests/benchmarker/exception.h b/tests/benchmarker/exception.h new file mode 100644 index 00000000..9fab479e --- /dev/null +++ b/tests/benchmarker/exception.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QBS_BENCHMARKER_EXCEPTION_H +#define QBS_BENCHMARKER_EXCEPTION_H + +#include +#include + +namespace qbsBenchmarker { + +class Exception : public QException { +public: + explicit Exception(QString description) : m_description(std::move(description)) {} + ~Exception() throw() override = default; + + QString description() const { return m_description; } + +private: + void raise() const override { throw *this; } + Exception *clone() const override { return new Exception(*this); } + + QString m_description; +}; + +} // namespace qbsBenchmarker + +#endif // Include guard. + diff --git a/tests/benchmarker/runsupport.cpp b/tests/benchmarker/runsupport.cpp new file mode 100644 index 00000000..3f8f4647 --- /dev/null +++ b/tests/benchmarker/runsupport.cpp @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 "runsupport.h" + +#include "exception.h" + +#include +#include +#include +#include + +namespace qbsBenchmarker { + +void runProcess(const QStringList &commandLine, const QString &workingDir, QByteArray *output, + int *exitCode) +{ + QStringList args = commandLine; + const QString command = args.takeFirst(); + QProcess p; + if (!workingDir.isEmpty()) + p.setWorkingDirectory(workingDir); + p.start(command, args); + if (!p.waitForStarted()) + throw Exception(QStringLiteral("Process '%1' failed to start.").arg(command)); + p.waitForFinished(-1); + if (p.exitStatus() != QProcess::NormalExit) { + throw Exception(QStringLiteral("Error running '%1': %2") + .arg(command, p.errorString())); + } + if (exitCode) + *exitCode = p.exitCode(); + if (p.exitCode() != 0) { + QString errorString = QStringLiteral("Command '%1' finished with exit code %2.") + .arg(command).arg(p.exitCode()); + const QByteArray stdErr = p.readAllStandardError(); + if (!stdErr.isEmpty()) { + errorString += QStringLiteral("\nStandard error output was: '%1'") + .arg(QString::fromLocal8Bit(stdErr)); + } + throw Exception(errorString); + } + if (output) + *output = p.readAllStandardOutput().trimmed(); +} + +} // namespace qbsBenchmarker diff --git a/tests/benchmarker/runsupport.h b/tests/benchmarker/runsupport.h new file mode 100644 index 00000000..6ee83100 --- /dev/null +++ b/tests/benchmarker/runsupport.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QBS_BENCHMARKER_RUNSUPPORT_H +#define QBS_BENCHMARKER_RUNSUPPORT_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QByteArray; +class QStringList; +QT_END_NAMESPACE + +namespace qbsBenchmarker { + +void runProcess(const QStringList &commandLine, const QString& workingDir = QString(), + QByteArray *output = nullptr, int *exitCode = nullptr); + +} // namespace qbsBenchmarker + +#endif // Include guard. + diff --git a/tests/benchmarker/valgrindrunner.cpp b/tests/benchmarker/valgrindrunner.cpp new file mode 100644 index 00000000..17478131 --- /dev/null +++ b/tests/benchmarker/valgrindrunner.cpp @@ -0,0 +1,249 @@ +/**************************************************************************** +** +** 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 "valgrindrunner.h" + +#include "exception.h" +#include "runsupport.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace qbsBenchmarker { + +ValgrindRunner::ValgrindRunner(Activities activities, QString testProject, + const QString &qbsBuildDir, const QString &baseOutputDir) + : m_activities(activities) + , m_testProject(std::move(testProject)) + , m_qbsBinary(qbsBuildDir + "/bin/qbs") + , m_baseOutputDir(baseOutputDir) +{ + if (!QDir::root().mkpath(m_baseOutputDir)) + throw Exception(QStringLiteral("Failed to create directory '%1'.").arg(baseOutputDir)); +} + +void ValgrindRunner::run() +{ + std::deque> futures; + if (m_activities & ActivityResolving) + futures.push_back(QtConcurrent::run(this, &ValgrindRunner::traceResolving)); + if (m_activities & ActivityRuleExecution) + futures.push_back(QtConcurrent::run(this, &ValgrindRunner::traceRuleExecution)); + if (m_activities & ActivityNullBuild) + futures.push_back(QtConcurrent::run(this, &ValgrindRunner::traceNullBuild)); + while (!futures.empty()) { + futures.front().waitForFinished(); + futures.pop_front(); + } +} + +void ValgrindRunner::traceResolving() +{ + const QString buildDirCallgrind = m_baseOutputDir + "/build-dir.resolving.callgrind"; + const QString buildDirMassif = m_baseOutputDir + "/build-dir.resolving.massif"; + traceActivity(ActivityResolving, buildDirCallgrind, buildDirMassif); +} + +void ValgrindRunner::traceRuleExecution() +{ + const QString buildDirCallgrind = m_baseOutputDir + "/build-dir.rule-execution.callgrind"; + const QString buildDirMassif = m_baseOutputDir + "/build-dir.rule-execution.massif"; + runProcess(qbsCommandLine("resolve", buildDirCallgrind, false)); + runProcess(qbsCommandLine("resolve", buildDirMassif, false)); + traceActivity(ActivityRuleExecution, buildDirCallgrind, buildDirMassif); +} + +void ValgrindRunner::traceNullBuild() +{ + const QString buildDirCallgrind = m_baseOutputDir + "/build-dir.null-build.callgrind"; + const QString buildDirMassif = m_baseOutputDir + "/build-dir.null-build.massif"; + runProcess(qbsCommandLine("build", buildDirCallgrind, false)); + runProcess(qbsCommandLine("build", buildDirMassif, false)); + traceActivity(ActivityNullBuild, buildDirCallgrind, buildDirMassif); +} + +void ValgrindRunner::traceActivity(Activity activity, const QString &buildDirCallgrind, + const QString &buildDirMassif) +{ + QString activityString; + QString qbsCommand; + bool dryRun; + switch (activity) { + case ActivityResolving: + activityString = "resolving"; + qbsCommand = "resolve"; + dryRun = false; + break; + case ActivityRuleExecution: + activityString = "rule-execution"; + qbsCommand = "build"; + dryRun = true; + break; + case ActivityNullBuild: + activityString = "null-build"; + qbsCommand = "build"; + dryRun = false; + break; + } + + const QString outFileCallgrind = m_baseOutputDir + "/outfile." + activityString + ".callgrind"; + const QString outFileMassif = m_baseOutputDir + "/outfile." + activityString + ".massif"; + QFuture callGrindFuture = QtConcurrent::run(this, &ValgrindRunner::runCallgrind, + qbsCommand, buildDirCallgrind, dryRun, outFileCallgrind); + QFuture massifFuture = QtConcurrent::run(this, &ValgrindRunner::runMassif, qbsCommand, + buildDirMassif, dryRun, outFileMassif); + callGrindFuture.waitForFinished(); + massifFuture.waitForFinished(); + addToResults(ValgrindResult(activity, callGrindFuture.result(), massifFuture.result())); +} + +QStringList ValgrindRunner::qbsCommandLine(const QString &command, const QString &buildDir, + bool dryRun) const +{ + QStringList commandLine = QStringList() << m_qbsBinary << command << "-qq" << "-d" << buildDir + << "-f" << m_testProject; + if (dryRun) + commandLine << "--dry-run"; + return commandLine; +} + +QStringList ValgrindRunner::wrapForValgrind(const QStringList &commandLine, const QString &tool, + const QString &outFile) const +{ + return QStringList() << "valgrind" << "--smc-check=all" << "--trace-children=yes" + << ("--tool=" + tool) << ("--" + tool + "-out-file=" + outFile) + << commandLine; +} + +QStringList ValgrindRunner::valgrindCommandLine(const QString &qbsCommand, const QString &buildDir, + bool dryRun, const QString &tool, const QString &outFile) const +{ + return wrapForValgrind(qbsCommandLine(qbsCommand, buildDir, dryRun), tool, outFile); +} + +void ValgrindRunner::addToResults(const ValgrindResult &result) +{ + std::lock_guard locker(m_resultsMutex); + m_results.push_back(result); +} + +qint64 ValgrindRunner::runCallgrind(const QString &qbsCommand, const QString &buildDir, + bool dryRun, const QString &outFile) +{ + runProcess(valgrindCommandLine(qbsCommand, buildDir, dryRun, "callgrind", outFile)); + QFile f(outFile); + if (!f.open(QIODevice::ReadOnly)) { + throw Exception(QStringLiteral("Failed to open file '%1': %2") + .arg(outFile, f.errorString())); + } + while (!f.atEnd()) { + const QByteArray line = f.readLine().trimmed(); + static const QByteArray magicString = "summary: "; + if (!line.startsWith(magicString)) + continue; + const QByteArray icString = line.mid(magicString.size()); + bool ok; + const qint64 iCount = icString.toLongLong(&ok); + if (!ok) { + throw Exception(QStringLiteral("Unexpected line in callgrind output file " + "'%1': '%2'.") + .arg(outFile, QString::fromLocal8Bit(line))); + } + return iCount; + } + + throw Exception(QStringLiteral("Failed to find summary line in callgrind " + "output file '%1'.").arg(outFile)); +} + +qint64 ValgrindRunner::runMassif(const QString &qbsCommand, const QString &buildDir, bool dryRun, + const QString &outFile) +{ + runProcess(valgrindCommandLine(qbsCommand, buildDir, dryRun, "massif", outFile)); + QByteArray ms_printOutput; + runProcess(QStringList() << "ms_print" << outFile, QString(), &ms_printOutput); + QBuffer buffer(&ms_printOutput); + buffer.open(QIODevice::ReadOnly); + QByteArray peakSnapshot; + const QString exceptionStringPattern = QStringLiteral("Failed to extract peak memory " + "usage from file '%1': %2").arg(outFile); + while (!buffer.atEnd()) { + const QByteArray line = buffer.readLine(); + static const QByteArray magicString = " (peak)"; + const int magicStringOffset = line.indexOf(magicString); + if (magicStringOffset == -1) + continue; + int delimiterOffset = line.lastIndexOf(',', magicStringOffset); + if (delimiterOffset == -1) + delimiterOffset = line.lastIndexOf('[', magicStringOffset); + if (delimiterOffset == -1) { + const QString details = QStringLiteral("Failed to extract peak snapshot from " + "line '%1'.").arg(QString::fromLocal8Bit(line)); + throw Exception(exceptionStringPattern.arg(details)); + } + peakSnapshot = line.mid(delimiterOffset + 1, magicStringOffset - delimiterOffset).trimmed(); + break; + } + if (peakSnapshot.isEmpty()) + throw Exception(exceptionStringPattern.arg("No peak marker found")); + while (!buffer.atEnd()) { + const QByteArray line = buffer.readLine().simplified(); + if (!line.startsWith(peakSnapshot + ' ')) + continue; + const QList entries = line.split(' '); + if (entries.size() != 6) { + const QString details = QStringLiteral("Expected 6 entries in line '%1', but " + "there are %2.").arg(QString::fromLocal8Bit(line)).arg(entries.size()); + throw Exception(exceptionStringPattern.arg(details)); + } + QByteArray peakMemoryString = entries.at(2); + peakMemoryString.replace(',', QByteArray()); + bool ok; + qint64 peakMemoryUsage = peakMemoryString.toLongLong(&ok); + if (!ok) { + const QString details = QStringLiteral("Failed to parse peak memory value '%1' " + "as a number.").arg(QString::fromLocal8Bit(peakMemoryString)); + throw Exception(exceptionStringPattern.arg(details)); + } + return peakMemoryUsage; + } + + const QString details = QStringLiteral("Failed to find snapshot '%1'.") + .arg(QString::fromLocal8Bit(peakSnapshot)); + throw Exception(exceptionStringPattern.arg(details)); +} + +} // namespace qbsBenchmarker diff --git a/tests/benchmarker/valgrindrunner.h b/tests/benchmarker/valgrindrunner.h new file mode 100644 index 00000000..37872386 --- /dev/null +++ b/tests/benchmarker/valgrindrunner.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QBS_BENCHMARKER_BENCHMARKRUNNER_H +#define QBS_BENCHMARKER_BENCHMARKRUNNER_H + +#include "activities.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE +class QStringList; +QT_END_NAMESPACE + +namespace qbsBenchmarker { + +class ValgrindResult +{ +public: + ValgrindResult(Activity a, qint64 ic, qint64 mem) + : activity(a), instructionCount(ic), peakMemoryUsage(mem) {} + + Activity activity; + qint64 instructionCount; + qint64 peakMemoryUsage; +}; + +class ValgrindRunner +{ +public: + ValgrindRunner(Activities activities, QString testProject, const QString &qbsBuildDir, + const QString &baseOutputDir); + + void run(); + QList results() const { return m_results; } + +private: + void traceResolving(); + void traceRuleExecution(); + void traceNullBuild(); + void traceActivity(Activity activity, const QString &buildDirCallgrind, + const QString &buildDirMassif); + QStringList qbsCommandLine(const QString &command, const QString &buildDir, bool dryRun) const; + QStringList wrapForValgrind(const QStringList &commandLine, const QString &tool, + const QString &outFile) const; + QStringList valgrindCommandLine(const QString &qbsCommand, const QString &buildDir, bool dryRun, + const QString &tool, const QString &outFile) const; + void addToResults(const ValgrindResult &results); + qint64 runCallgrind(const QString &qbsCommand, const QString &buildDir, bool dryRun, + const QString &outFile); + qint64 runMassif(const QString &qbsCommand, const QString &buildDir, bool dryRun, + const QString &outFile); + + const Activities m_activities; + const QString m_testProject; + const QString m_qbsBinary; + const QString m_baseOutputDir; + QList m_results; + std::mutex m_resultsMutex; +}; + +} // namespace qbsBenchmarker + +#endif // Include guard. diff --git a/tests/fuzzy-test/commandlineparser.cpp b/tests/fuzzy-test/commandlineparser.cpp new file mode 100644 index 00000000..23be7b0a --- /dev/null +++ b/tests/fuzzy-test/commandlineparser.cpp @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** 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 "commandlineparser.h" + +#include + +#include + +static QString profileOption() { return "--profile"; } +static QString startCommitOption() { return "--start-commit"; } +static QString maxDurationoption() { return "--max-duration"; } +static QString jobCountOption() { return "--jobs"; } +static QString logOption() { return "--log"; } + +CommandLineParser::CommandLineParser() = default; + +void CommandLineParser::parse(const QStringList &commandLine) +{ + m_profile.clear(); + m_startCommit.clear(); + m_maxDuration = 0; + m_jobCount = 0; + m_log = false; + m_commandLine = commandLine; + Q_ASSERT(!m_commandLine.empty()); + m_command = m_commandLine.takeFirst(); + while (!m_commandLine.empty()) { + const QString arg = m_commandLine.takeFirst(); + if (arg == profileOption()) + assignOptionArgument(arg, m_profile); + else if (arg == startCommitOption()) + assignOptionArgument(arg, m_startCommit); + else if (arg == jobCountOption()) + assignOptionArgument(arg, m_jobCount); + else if (arg == maxDurationoption()) + parseDuration(); + else if (arg == logOption()) + m_log = true; + else + throw ParseException(QStringLiteral("Unknown parameter '%1'").arg(arg)); + } + if (m_profile.isEmpty()) + throw ParseException("No profile given."); + if (m_startCommit.isEmpty()) + throw ParseException("No start commit given."); +} + +QString CommandLineParser::usageString() const +{ + return QStringLiteral("%1 %2 %3 [%4 ] " + "[%5 ] [%6]") + .arg(QFileInfo(m_command).fileName(), profileOption(), startCommitOption(), + maxDurationoption(), jobCountOption(), logOption()); +} + +void CommandLineParser::assignOptionArgument(const QString &option, QString &argument) +{ + if (m_commandLine.empty()) + throw ParseException(QStringLiteral("Option '%1' needs an argument.").arg(option)); + argument = m_commandLine.takeFirst(); + if (argument.isEmpty()) { + throw ParseException(QStringLiteral("Argument for option '%1' must not be empty.") + .arg(option)); + } +} + +void CommandLineParser::assignOptionArgument(const QString &option, int &argument) +{ + QString numberString; + assignOptionArgument(option, numberString); + bool ok; + argument = numberString.toInt(&ok); + if (!ok || argument <= 0) { + throw ParseException(QStringLiteral("Invalid argument '%1' for option '%2'.") + .arg(numberString, option)); + } +} + +void CommandLineParser::parseDuration() +{ + QString durationString; + QString choppedDurationString; + assignOptionArgument(maxDurationoption(), durationString); + choppedDurationString = durationString; + const char suffix = durationString.at(durationString.size() - 1).toLatin1(); + const bool hasSuffix = !std::isdigit(suffix); + if (hasSuffix) + choppedDurationString.chop(1); + bool ok; + m_maxDuration = choppedDurationString.toInt(&ok); + if (!ok || m_maxDuration <= 0) { + throw ParseException(QStringLiteral("Invalid duration argument '%1'.") + .arg(durationString)); + } + if (hasSuffix) { + switch (suffix) { + case 'm': break; + case 'd': m_maxDuration *= 24; // Fall-through. + case 'h': m_maxDuration *= 60; break; + default: + throw ParseException(QStringLiteral("Invalid duration suffix '%1'.") + .arg(suffix)); + } + } +} diff --git a/tests/fuzzy-test/commandlineparser.h b/tests/fuzzy-test/commandlineparser.h new file mode 100644 index 00000000..76b49331 --- /dev/null +++ b/tests/fuzzy-test/commandlineparser.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QBS_FUZZYTEST_COMMANDLINEPARSER_H +#define QBS_FUZZYTEST_COMMANDLINEPARSER_H + +#include + +#include + +class ParseException : public std::exception +{ +public: + ParseException(QString error) : errorMessage(std::move(error)) { } + ~ParseException() throw() override = default; + + QString errorMessage; + +private: + const char *what() const throw() override { return qPrintable(errorMessage); } +}; + +class CommandLineParser +{ +public: + CommandLineParser(); + + void parse(const QStringList &commandLine); + + QString profile() const { return m_profile; } + QString startCommit() const { return m_startCommit; } + int maxDurationInMinutes() const { return m_maxDuration; } + int jobCount() const { return m_jobCount; } + bool log() const { return m_log; } + + QString usageString() const; + +private: + void assignOptionArgument(const QString &option, QString &argument); + void assignOptionArgument(const QString &option, int &argument); + void parseDuration(); + + QStringList m_commandLine; + QString m_command; + QString m_profile; + QString m_startCommit; + int m_maxDuration = 0; + int m_jobCount = 0; + bool m_log = false; +}; + +#endif // Include guard. diff --git a/tests/fuzzy-test/fuzzy-test.pro b/tests/fuzzy-test/fuzzy-test.pro new file mode 100644 index 00000000..97083ef7 --- /dev/null +++ b/tests/fuzzy-test/fuzzy-test.pro @@ -0,0 +1,11 @@ +TARGET = qbs_fuzzy-test +DESTDIR = ../../bin +CONFIG += console +CONFIG -= app_bundle +SOURCES = main.cpp \ + commandlineparser.cpp \ + fuzzytester.cpp + +HEADERS += \ + commandlineparser.h \ + fuzzytester.h diff --git a/tests/fuzzy-test/fuzzy-test.qbs b/tests/fuzzy-test/fuzzy-test.qbs new file mode 100644 index 00000000..539374de --- /dev/null +++ b/tests/fuzzy-test/fuzzy-test.qbs @@ -0,0 +1,21 @@ +import qbs + +QtApplication { + name: "qbs_fuzzy-test" + type: "application" + consoleApplication: true + Depends { name: "qbsbuildconfig" } + cpp.cxxLanguageVersion: "c++14" + files: [ + "commandlineparser.cpp", + "commandlineparser.h", + "fuzzytester.cpp", + "fuzzytester.h", + "main.cpp", + ] + Group { + fileTagsFilter: product.type + qbs.install: true + qbs.installDir: qbsbuildconfig.appInstallDir + } +} diff --git a/tests/fuzzy-test/fuzzytester.cpp b/tests/fuzzy-test/fuzzytester.cpp new file mode 100644 index 00000000..2af8c689 --- /dev/null +++ b/tests/fuzzy-test/fuzzytester.cpp @@ -0,0 +1,308 @@ +/**************************************************************************** +** +** 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 "fuzzytester.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static QString resolveIncrementalActivity() { return "resolve-incremental"; } +static QString buildIncrementalActivity() { return "build-incremental"; } +static QString buildFromScratchActivity() { return "build-from-scratch"; } + +FuzzyTester::FuzzyTester() +{ + loadSettings(); +} + +FuzzyTester::~FuzzyTester() +{ + storeSettings(); +} + +void FuzzyTester::runTest(const QString &profile, const QString &startCommit, + int maxDurationInMinutes, int jobCount, bool log) +{ + m_profile = profile; + m_jobCount = jobCount; + m_log = log; + + runGit(QStringList() << "rev-parse" << "HEAD", &m_headCommit); + qDebug("HEAD is %s", qPrintable(m_headCommit)); + + qDebug("Trying to find a buildable commit to start with..."); + const QString workingStartCommit = findWorkingStartCommit(startCommit); + qDebug("Found buildable start commit %s.", qPrintable(workingStartCommit)); + QStringList allCommits = findAllCommits(workingStartCommit); + qDebug("The test set comprises all %d commits between the start commit and HEAD.", + allCommits.size()); + + // Shuffle the initial sequence. Otherwise all invocations of the tool with the same start + // commit would try the same sequence of commits. + std::srand(std::time(nullptr)); + std::shuffle(allCommits.begin(), allCommits.end(), std::mt19937(std::random_device()())); + + quint64 run = 0; + QStringList buildSequence(workingStartCommit); + QElapsedTimer timer; + const qint64 maxDurationInMillis = maxDurationInMinutes * 60 * 1000; + if (maxDurationInMillis != 0) + timer.start(); + + bool timerHasExpired = false; + while (std::next_permutation(allCommits.begin(), allCommits.end()) && !timerHasExpired) { + qDebug("Testing permutation %llu...", ++run); + const auto &allCommitsImmutable = allCommits; + for (const QString ¤tCommit : allCommitsImmutable) { + if (timer.isValid() && timer.hasExpired(maxDurationInMillis)) { + timerHasExpired = true; + break; + } + + m_currentCommit = currentCommit; + buildSequence << currentCommit; + checkoutCommit(currentCommit); + qDebug("Testing incremental build #%d (%s)", buildSequence.size() - 1, + qPrintable(currentCommit)); + + // Doing "resolve" and "build" separately introduces additional possibilities + // for errors, as information from change tracking has to be serialized correctly. + QString qbsError; + m_currentActivity = resolveIncrementalActivity(); + bool success = runQbs(defaultBuildDir(), QStringLiteral("resolve"), &qbsError); + if (success) { + m_currentActivity = buildIncrementalActivity(); + success = runQbs(defaultBuildDir(), QStringLiteral("build"), &qbsError); + } + m_currentActivity = buildFromScratchActivity(); + if (success) { + if (!doCleanBuild(&qbsError)) { + QString message = "An incremental build succeeded " + "with a commit for which a clean build failed."; + if (!m_log) { + message += QStringLiteral("\nThe qbs error message " + "for the clean build was: '%1'").arg(qbsError); + } + throwIncrementalBuildError(message, buildSequence); + } + } else { + qDebug("Incremental build failed. Checking whether clean build works..."); + if (doCleanBuild()) { + QString message = "An incremental build failed " + "with a commit for which a clean build succeeded."; + if (!m_log) { + message += QStringLiteral("\nThe qbs error message for " + "the incremental build was: '%1'").arg(qbsError); + } + throwIncrementalBuildError(message, buildSequence); + } else { + qDebug("Clean build also fails. Continuing."); + } + } + } + } + + if (timerHasExpired) + qDebug("Maximum duration reached."); + else + qDebug("All possible permutations were tried."); +} + +void FuzzyTester::checkoutCommit(const QString &commit) +{ + runGit(QStringList() << "checkout" << commit); + runGit(QStringList() << "submodule" << "update" << "--init"); +} + +QStringList FuzzyTester::findAllCommits(const QString &startCommit) +{ + QString allCommitsString; + runGit(QStringList() << "log" << (startCommit + "~1.." + m_headCommit) << "--format=format:%h", + &allCommitsString); + return allCommitsString.simplified().split(QLatin1Char(' '), +#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) + QString::SkipEmptyParts); +#else + Qt::SkipEmptyParts); +#endif +} + +QString FuzzyTester::findWorkingStartCommit(const QString &startCommit) +{ + const QStringList allCommits = findAllCommits(startCommit); + QString qbsError; + m_currentActivity = buildFromScratchActivity(); + for (auto it = allCommits.crbegin(), end = allCommits.crend(); it != end; ++it) { + m_currentCommit = *it; + if (m_unbuildableCommits.contains(m_currentCommit)) { + qDebug("Skipping known bad commit %s.", qPrintable(m_currentCommit)); + continue; + } + checkoutCommit(m_currentCommit); + removeDir(defaultBuildDir()); + if (runQbs(defaultBuildDir(), QStringLiteral("build"), &qbsError)) { + m_buildableCommits << m_currentCommit; + return m_currentCommit; + } + qDebug("Commit %s is not buildable.", qPrintable(m_currentCommit)); + m_unbuildableCommits << m_currentCommit; + } + throw TestError(QStringLiteral("Cannot run test: Failed to find a single commit that " + "builds successfully with qbs. The last qbs error was: '%1'").arg(qbsError)); +} + +void FuzzyTester::runGit(const QStringList &arguments, QString *output) +{ + QProcess git; + git.start("git", arguments); + if (!git.waitForStarted()) + throw TestError("Failed to start git. It is expected to be in the PATH."); + if (!git.waitForFinished(300000) || git.exitStatus() != QProcess::NormalExit) // 5 minutes ought to be enough for everyone + throw TestError(QStringLiteral("git failed: %1").arg(git.errorString())); + if (git.exitCode() != 0) { + throw TestError(QStringLiteral("git failed: %1") + .arg(QString::fromLocal8Bit(git.readAllStandardError()))); + } + if (output) + *output = QString::fromLocal8Bit(git.readAllStandardOutput()).trimmed(); +} + +bool FuzzyTester::runQbs(const QString &buildDir, const QString &command, QString *errorOutput) +{ + if (errorOutput) + errorOutput->clear(); + QProcess qbs; + QStringList commandLine = QStringList(command) << "-d" << buildDir; + if (m_log) { + commandLine << "-vv"; + const size_t maxLoggedCommits = 2; + Q_ASSERT(m_commitsWithLogFiles.size() <= maxLoggedCommits + 1); + if (m_commitsWithLogFiles.size() == maxLoggedCommits + 1) { + static const QStringList allActivities = QStringList() << resolveIncrementalActivity() + << buildIncrementalActivity() << buildFromScratchActivity(); + const QString oldCommit = m_commitsWithLogFiles.front(); + m_commitsWithLogFiles.pop(); + for (const QString &a : allActivities) + QFile::remove(logFilePath(oldCommit, a)); + } + qbs.setStandardErrorFile(logFilePath(m_currentCommit, m_currentActivity)); + if (m_commitsWithLogFiles.empty() || m_commitsWithLogFiles.back() != m_currentCommit) + m_commitsWithLogFiles.push(m_currentCommit); + } else { + commandLine << "-qq"; + } + if (m_jobCount != 0) + commandLine << "--jobs" << QString::number(m_jobCount); + commandLine << ("profile:" + m_profile); + qbs.start("qbs", commandLine); + if (!qbs.waitForStarted()) { + throw TestError(QStringLiteral("Failed to start qbs. It is expected to be " + "in the PATH. QProcess error string: '%1'").arg(qbs.errorString())); + } + if (!qbs.waitForFinished(-1) || qbs.exitCode() != 0) { + if (errorOutput) + *errorOutput = QString::fromLocal8Bit(qbs.readAllStandardError()); + return false; + } + return true; +} + +void FuzzyTester::removeDir(const QString &dirPath) +{ + QDir dir(dirPath); + if (!dir.removeRecursively()) { + throw TestError(QStringLiteral("Failed to remove temporary dir '%1'.") + .arg(dir.absolutePath())); + } +} + +bool FuzzyTester::doCleanBuild(QString *errorMessage) +{ + if (m_unbuildableCommits.contains(m_currentCommit)) { + qDebug("Commit is known not to be buildable, not running qbs."); + return false; + } + if (m_buildableCommits.contains(m_currentCommit)) { + qDebug("Commit is known to be buildable, not running qbs."); + return true; + } + const QString cleanBuildDir = "fuzzytest-verification-build"; + removeDir(cleanBuildDir); + if (runQbs(cleanBuildDir, QStringLiteral("build"), errorMessage)) { + m_buildableCommits << m_currentCommit; + return true; + } + m_unbuildableCommits << m_currentCommit; + return false; +} + +void FuzzyTester::throwIncrementalBuildError(const QString &message, + const QStringList &commitSequence) +{ + const QString commitSequenceString = commitSequence.join(QLatin1Char(',')); + throw TestError(QStringLiteral("Found qbs bug with incremental build!\n" + "%1\n" + "The sequence of commits was: %2.").arg(message, commitSequenceString)); +} + +QString FuzzyTester::logFilePath(const QString &commit, const QString &activity) +{ + return "log." + commit + '.' + activity; +} + +QString FuzzyTester::defaultBuildDir() +{ + return "fuzzytest-build"; +} + + +static QString organization() { return "QtProject"; } +static QString app() { return "qbs-fuzzy-tester"; } +static QString unbuildableCommitsKey() { return "unbuildable-commits"; } +static QString buildableCommitsKey() { return "buildable-commits"; } + +void FuzzyTester::loadSettings() +{ + QSettings s(organization(), app()); + m_unbuildableCommits = s.value(unbuildableCommitsKey()).toStringList(); + m_buildableCommits = s.value(buildableCommitsKey()).toStringList(); +} + +void FuzzyTester::storeSettings() const +{ + QSettings s(organization(), app()); + s.setValue(unbuildableCommitsKey(), m_unbuildableCommits); + s.setValue(buildableCommitsKey(), m_buildableCommits); +} diff --git a/tests/fuzzy-test/fuzzytester.h b/tests/fuzzy-test/fuzzytester.h new file mode 100644 index 00000000..2d0279e1 --- /dev/null +++ b/tests/fuzzy-test/fuzzytester.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QBS_FUZZYTESTER_H +#define QBS_FUZZYTESTER_H + +#include + +#include +#include + +class TestError { +public: + TestError(QString errorMessage) : errorMessage(std::move(errorMessage)) {} + ~TestError() throw() = default; + + QString errorMessage; + +private: + const char *what() const throw() { return qPrintable(errorMessage); } +}; + + +class FuzzyTester +{ +public: + FuzzyTester(); + ~FuzzyTester(); + + void runTest(const QString &profile, const QString &startCommit, int maxDurationInMinutes, + int jobCount, bool log); + +private: + void checkoutCommit(const QString &commit); + QStringList findAllCommits(const QString &startCommit); + QString findWorkingStartCommit(const QString &startCommit); + void runGit(const QStringList &arguments, QString *output = 0); + bool runQbs(const QString &buildDir, const QString &command, QString *errorOutput = 0); + void removeDir(const QString &dir); + bool doCleanBuild(QString *errorMessage = 0); + void throwIncrementalBuildError(const QString &message, const QStringList &commitSequence); + + void loadSettings(); + void storeSettings() const; + + static QString logFilePath(const QString &commit, const QString &activity); + static QString defaultBuildDir(); + + QString m_profile; + int m_jobCount = 0; + bool m_log = false; + QString m_headCommit; + QString m_currentCommit; + QString m_currentActivity; + std::queue m_commitsWithLogFiles; + QStringList m_unbuildableCommits; + QStringList m_buildableCommits; +}; + +#endif // Include guard. diff --git a/tests/fuzzy-test/main.cpp b/tests/fuzzy-test/main.cpp new file mode 100644 index 00000000..7c79bcf5 --- /dev/null +++ b/tests/fuzzy-test/main.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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 "commandlineparser.h" +#include "fuzzytester.h" + +#include + +#include +#include + +static bool parseCommandLine(const QStringList &commandLine, QString &profile, + QString &startCommi, int &maxDuration, int &jobCount, bool &log); +static bool runTest(const QString &profile, const QString &startCommit, int maxDuration, + int jobCount, bool log); + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QString profile; + QString startCommit; + int maxDuration; + int jobCount; + bool log; + if (!parseCommandLine(app.arguments(), profile, startCommit, maxDuration, jobCount, log)) + return EXIT_FAILURE; + if (!runTest(profile, startCommit, maxDuration, jobCount, log)) + return EXIT_FAILURE; + std::cout << "Test finished successfully." << std::endl; + return EXIT_SUCCESS; +} + +bool parseCommandLine(const QStringList &commandLine, QString &profile, QString &startCommit, + int &maxDuration, int &jobCount, bool &log) +{ + CommandLineParser cmdParser; + try { + cmdParser.parse(commandLine); + } catch (const ParseException &e) { + std::cerr << "Invalid command line: " << qPrintable(e.errorMessage) << std::endl; + std::cerr << "Usage:" << std::endl << qPrintable(cmdParser.usageString()) << std::endl; + return false; + } + profile = cmdParser.profile(); + startCommit = cmdParser.startCommit(); + maxDuration = cmdParser.maxDurationInMinutes(); + jobCount = cmdParser.jobCount(); + log = cmdParser.log(); + return true; +} + +bool runTest(const QString &profile, const QString &startCommit, int maxDuration, int jobCount, + bool log) +{ + try { + FuzzyTester().runTest(profile, startCommit, maxDuration, jobCount, log); + } catch (const TestError &e) { + std::cerr << qPrintable(e.errorMessage) << std::endl; + return false; + } + return true; +} diff --git a/tests/tests.pro b/tests/tests.pro new file mode 100644 index 00000000..e454771e --- /dev/null +++ b/tests/tests.pro @@ -0,0 +1,4 @@ +TEMPLATE = subdirs +SUBDIRS = auto fuzzy-test + +qtHaveModule(concurrent): SUBDIRS += benchmarker diff --git a/tests/tests.qbs b/tests/tests.qbs new file mode 100644 index 00000000..52eafb3f --- /dev/null +++ b/tests/tests.qbs @@ -0,0 +1,43 @@ +import qbs +import qbs.FileInfo + +Project { + references: [ + "auto/auto.qbs", + "benchmarker/benchmarker.qbs", + "fuzzy-test/fuzzy-test.qbs", + ] + + AutotestRunner { + Depends { name: "Qt.core" } + Depends { name: "qbs resources" } + Depends { name: "qbs_cpp_scanner" } + Depends { name: "qbs_qt_scanner" } + arguments: project.autotestArguments + wrapper: project.autotestWrapper + environment: { + var env = base; + env.push("QBS_INSTALL_DIR=" + FileInfo.joinPaths(qbs.installRoot, qbs.installPrefix)); + if (qbs.hostOS.contains("windows") && qbs.targetOS.contains("windows")) { + var path = ""; + for (var i = 0; i < env.length; ++i) { + if (env[i].startsWith("PATH=")) { + path = env[i].substring(5); + break; + } + } + path = Qt.core.binPath + ";" + path; + var arrayElem = "PATH=" + path; + if (i < env.length) + env[i] = arrayElem; + else + env.push(arrayElem); + } + if (qbs.hostOS.contains("darwin") && qbs.targetOS.contains("darwin")) { + env.push("DYLD_FRAMEWORK_PATH=" + Qt.core.libPath); + env.push("DYLD_LIBRARY_PATH=" + Qt.core.libPath); + } + return env; + } + } +} -- 2.30.2